Compromising analysis tools

When reverse engineering a program, the analyst would not execute that program from a work account on a valid machine. The potential for abuse by a malicious program is obvious. However, the probability that the analyst will use a work account for statically analysing the program is much higher.

An exploit for the file command was publicly released in March 2003, which allowed a specially crafted executable to execute arbitrary commands. This is referred to in Mitre's CVE dictionary: CAN-2003-0102.

Standard practise for reverse engineering a program in the UNIX environment is to first execute the file, ldd and strings commands on the target program.

A vulnerability in the file command was discussed earlier. What about the ldd command?


The ldd command shows the shared libraries required by a program:

$ ldd /bin/ls
       librt.so.1 => /lib/librt.so.1 (0x4001d000)
       libc.so.6 => /lib/libc.so.6 (0x4002e000)
       libpthread.so.0 => /lib/libpthread.so.0 (0x40149000)
       /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

Under glibc 2.2.x and 2.3.x (other environments untested), ldd is implemented by executing the program which the LD_TRACE_LOADED_OBJECTS environment variable set:

$ LD_TRACE_LOADED_OBJECTS=1 /bin/ls
       librt.so.1 => /lib/librt.so.1 (0x4001d000)
       libc.so.6 => /lib/libc.so.6 (0x4002e000)
       libpthread.so.0 => /lib/libpthread.so.0 (0x40149000)
       /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

When the glibc program interpreter is invoked to execute a shared library executable, if the LD_TRACE_LOADED_OBJECTS environment variable is set the interpreter resolves required shared libraries and outputs them to stdout then exits. The program is not executed.

This is how the glibc program interpreter works. What about others? Let's do a comparison with glibc and uclibc:

$ echo 'main() { printf("hello world\n"); }' > hello.c

First glibc:

$ gcc hello.c -o gcc_hello
$ ldd ./gcc_hello
        libc.so.6 => /lib/libc.so.6 (0x4001d000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

And now uclibc:

$ i386-uclibc-gcc hello.c -o uclibc_hello
$ ldd ./uclibc_hello 
hello world

Well that wasn't what we expected. We expected to find out the shared library dependencies and instead the program executed. How would a malicious program take advantage of this?

$ cat nasty.c
int main()
{
   if (getenv("LD_TRACE_LOADED_OBJECTS") != 0) {
      /* we are being executed under ldd */
      printf("doing funny stuff\n");
      printf("\tlibc.so.6 => /lib/libc.so.6 (0x4001d000)\n");
      printf("\t/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)\n");
      return 0;
   }

   printf("normally program execution\n");
   return 0;
}

$ i386-uclibc-gcc nasty.c -o nasty
$ ldd ./nasty
doing funny stuff
        libc.so.6 => /lib/libc.so.6 (0x4001d000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

The moral of the story? Analysts should do all analysis (yes, this includes static analysis) under an protected / isolated account or machine.

Corollary: should does not imply will.