TRYING TO MAKE YOUR BINARY SHUT UP

EDB-ID:

13188

CVE:

N/A

Author:

l0om

Type:

papers

Platform:

Multiple

Published:

2006-04-13

                     __           __           __
  .-----.--.--.----.|  |.--.--.--|  |.-----.--|  |  .-----.----.-----.
  |  -__|_   _|  __||  ||  |  |  _  ||  -__|  _  |__|  _  |   _|  _  |
  |_____|__.__|____||__||_____|_____||_____|_____|__|_____|__| |___  |
   by l0om - member of excluded-team                           |_____|


   TRYING TO MAKE YOUR BINARY SHUT UP

   0x00  short intro
   0x01  hiding strings
   0x02  Prevent tracing
   0x03  Prevent from changing our code
   0x04  Confusing
   ret   end


0x00 short intro
################

Once i have written a small virus on a linux box for infecting ELF binarys. When i was ready i started to think about how to prevent someone from detecting the sence of the program itself. I tryed to prevent the techniqs i use on new binarys before i execute them on my box. That are thinks like dumping strings, debugging etc..

Most of the things i have written down are of course well known and most of the techniqs are stolen by sources of viruses, trojans, textfiles and stuff like that.



0x01 Hiding strings
###################

 One of our aims is to prevent someone from viewing strings which can be found in the binary.
 Those could include IP addresses, shell commands or filenames which could lead someone to detect eg payload, trigger or even the whole propose of the program itself. Furthermore think of software where hard coded pathnames can be found. A found pathname like "/tmp/.antivir_pid.%d" could lead someone to try to put up a symlink attack (nearly the same could happen to shell command strings).

 Mostly someone will try to dump cleartext strings with the "strings" utiltiy. In "strings"  manaul is meantioned that it will show all strings which are four or more bytes long.

 Lets take a small example:
 #include <stdio.h>

 void main(void) {
 FILE *fd = fopen("/etc/passwd", "r");
 if(fd == NULL) return;
 else printf("file opend\n");
 }

 admin@box:~> strings k
 /lib/ld-linux.so.2
 libc.so.6
 printf
 fopen
 _IO_stdin_used
 __libc_start_main
 __gmon_start__
 GLIBC_2.1
 GLIBC_2.0
 PTRh0
 /etc/passwd
 file opend

 As the pathname is longer than four bytes the strings command dumps the path in cleartext.

 How to prevent this?
 One simple method would be to cut the string in parts about three bytes and put them together dynamicly if needed. The "strings" utility would show nothing. An example:

 #include <stdio.h>
 #include <string.h>

 #define ETC_PASSWD "3412"  // put the parts together in this combination

 static char *bstr(char *construct);

 void main(int argc, char **argv)
 {
   FILE *fd;
   fd = fopen(bstr(ETC_PASSWD), "r");
   if(fd == NULL) return;
   else printf("file opend\n");
 }

 static char *bstr(char *construct)
 {
   char *ptr, *part, *finstr;
   size_t nlen;

   nlen = strlen(construct);
   if(!nlen) return NULL;
   finstr = (char *)malloc(1);

   ptr = construct;
   while(nlen--) {
    switch(*ptr++) {
    case '3':
     part = "/et";
     break;
    case '4':
     part = "c/";
     break;
    case '1':
     part = "pas";
     break;
    case '2':
     part = "swd";
     break;
    }

    finstr = (char *)realloc(finstr, strlen(finstr)+strlen(part));
    strcat(finstr, part);
    }
    return finstr;
 }

 admin@box:~> strings newk
 strings test
 /lib/ld-linux.so.2
 libc.so.6
 printf
 malloc
 strcat
 realloc
 fopen
 _IO_stdin_used
 __libc_start_main
 strlen
 __gmon_start__
 GLIBC_2.1
 GLIBC_2.0
 PTRh
 QVh\
 3412
 file opend

 Anyone have seen a path?

 Another method is to save the string encrypted and decrypt the string "on the fly" if needed. In this case we dont need a strong encryption because our iam is simple: no one should see the string in plaintext.

 #include <stdio.h>

 #define CCHAR(x)  ((x)+10)   // rot13 like ascii shifting
 #define DCHAR(x)  ((x)-10)

 static char *bstr(char *chiffer, int nbytes);

 void main(void) {
 char filename[] = { CCHAR('/'), CCHAR('e'), CCHAR('t'), CCHAR('c'), CCHAR('/'),
                     CCHAR('p'), CCHAR('a'), CCHAR('s'), CCHAR('s'), CCHAR('w'),
                     CCHAR('d'), '\0' };

 FILE *fd = fopen(bstr(filename,11), "r");
 if(fd == NULL) return;
 else printf("file opend\n");
 }

 static char *bstr(char *chiffer, int nbytes)
 {
  char *ptr;

  ptr = chiffer;
  while(nbytes--) *ptr = DCHAR(*ptr++);
  return(chiffer);
 }


 The macro CCHAR shifts the ascii character plus 10. This way the string isnt saved in the binary in cleartext.
 As you can see the DCHAR macro is used to decrypt the string and "Tadaa!" - "strings" utility will show us nothing.



0x02 Prevent tracing
####################

All this preventing from dumping strings does not prevent from simple trace the program and find out what it is doing. for normal "trace" programs like "strace" or "ltrace" work with the "ptrace" function.

<from the ptrace manual>
      "The ptrace system call provides a means by which a  parent
       process  may  observe and control the execution of another
       process, and examine and change its core image and  regis­
       ters.  It is primarily used to implement breakpoint debug­
       ging and system call tracing."
</from the ptrace manual>

lets see how this works with our test program:

admin@box:~> strace ./test
[...]
brk(0x806a674)                          = 0x806a674
brk(0)                                  = 0x806a674
brk(0x806b000)                          = 0x806b000
open("/etc/passwd", O_RDONLY)           = 3
fstat64(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 1), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4001a000
write(1, "file opend\n", 11file opend
[...]

crap... all this hiding for nothing?

we can prevent a process from beeing traced like this. Just read the manual further:

<from the manual>
   PTRACE_TRACEME
              Indicates that this process is to be traced by  its
              parent.   Any  signal (except SIGKILL) delivered to
              this process will cause it to stop and  its  parent
              to  be  notified  via  wait.   Also, all subsequent
              calls to exec by this process will cause a  SIGTRAP
              to  be  sent  to  it, giving the parent a chance to
              gain control before the new program  begins  execu­
              tion.   A  process  probably  shouldn't  make  this
              request if its parent isn't expecting to trace  it.
</from the maual>

mh.. so lets implement this to our code to prevent beeing "ptraced"...

#include <stdio.h>
#include <sys/ptrace.h>

void main(void)
{
 FILE *fd;
 if (ptrace(PTRACE_TRACEME, 0, 1, 0) == -1)
 {
   printf("so you wanna trace me?...\n");
   return(-1);
 }

 fd = fopen("/etc/passwd", "r");
 if(fd == NULL) return;
 else printf("file opend\n");
 exit(-1);
}

admin@box:~> strace ./test
[...]
close(3)                                = 0
munmap(0x4001a000, 58768)               = 0
ptrace(PTRACE_TRACEME, 0, 0x1, 0)       = -1 EPERM (Operation not permitted)
fstat64(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 1), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4001a000
write(1, "so you wanna trace me?...\n", 26so you wanna trace me?...
) = 26
munmap(0x4001a000, 4096)                = 0
exit_group(26)                          = ?
[...]

Suprice! So we prevented the guy from ptrace us and this could also have been a evil trigger.

by the way: if someone is trying to "debug" us with eg "gdb" and he is using breakepoints this can be detected too. on linux/*nix the debuger sends the process on every breakpoint a singal called SIGTRAP which will cause the process to sleep. we can simply put up a signal handler for the SIGTRAP signal and cause our program to quit.

#include <stdio.h>
#include <signal.h>

static void sig_trp(int sig);

void main(void)
{
    if(signal(SIGTRAP, sig_trp) == SIG_ERR) {
        perror("signal");
        exit(-1);
    }
    sleep(10);
    printf("iam done\n");
}

static void sig_trp(int sig)
{
    printf("... AND THIS IS YOUR LAST BREAKPOINT!\n");
    exit(-1);
}



0x03 Prevent from changing our code
###################################

We have seen how to prevent from "trace" our program. Now we come to the point where we have to think more about skilled people who could now view the hex code and "nop" out "jmps" or "cmps" and cause the program to do what ever they want. they could eg noop out the whole ptrace function call.

gdb ./test
GNU gdb 5.3.92
[...]
This GDB was configured as "i586-suse-linux"...
(gdb) disas main
Dump of assembler code for function main:
0x080483ec <main+0>:    push   %ebp
0x080483ed <main+1>:    mov    %esp,%ebp
0x080483ef <main+3>:    sub    $0x8,%esp
0x080483f2 <main+6>:    and    $0xfffffff0,%esp
0x080483f5 <main+9>:    mov    $0x0,%eax
0x080483fa <main+14>:   sub    %eax,%esp
0x080483fc <main+16>:   push   $0x0                 <--- push the arguments on the stack
0x080483fe <main+18>:   push   $0x1
0x08048400 <main+20>:   push   $0x0
0x08048402 <main+22>:   push   $0x0
0x08048404 <main+24>:   call   0x80482dc 	     <--- execute it - the return = %eax
0x08048409 <main+29>:   add    $0x10,%esp
0x0804840c <main+32>:   cmp    $0xffffffff,%eax      <--- ptrace() == -1?
0x0804840f <main+35>:   jne    0x8048423 <main+55>
[...]

We need some method to make clear our code has not been changed or even cracked. lets think for a moment about the binary itself:


File Offset               File
           0 +-----------------------------+
             |          ELF Header         |
             +-----------------------------+
             |     Program header table    |
       0x100 +-----------------------------+
             |          Text Segment       |
             |             [...]           |
             |          0x2be00 bytes      |
     0x2bf00 +-----------------------------+
             |          Data Segment       |
             |             [...]           |
             |          0x4e00 bytes       |
     0x30d00 +-----------------------------+
             |          Segment n          |
             |             [...]           |
             |          0x???? bytes       |
     0x????? +-----------------------------+
	     |      Section header table   |
	     |            index            |
	     +-----------------------------+


First there comes the ELF Header which is something like a roadmap for the file itself. It includes offset to the Section header (if there is one), how many entries there are and the same for the Program header table. Detailed descriptions on the ELF format can be found in the internet.

What we should take care of are the Segments. Every Elf file includes varius Segments what builds the program itself (maybe with some linker help).

badass@home:~> readelf --segments test

Elf file type is EXEC (Executable file)
Entry point 0x80482b0
There are 6 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x000c0 0x000c0 R E 0x4
  INTERP         0x0000f4 0x080480f4 0x080480f4 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x08048000 0x08048000 0x004f9 0x004f9 R E 0x1000
  LOAD           0x0004fc 0x080494fc 0x080494fc 0x00108 0x0010c RW  0x1000
  DYNAMIC        0x00050c 0x0804950c 0x0804950c 0x000c8 0x000c8 RW  0x4
  NOTE           0x000108 0x08048108 0x08048108 0x00020 0x00020 R   0x4

 Section to Segment mapping:
  Segment Sections...
   00
   01     .interp
   02     .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata
   03     .data .eh_frame .dynamic .ctors .dtors .jcr .got .bss
   04     .dynamic
   05     .note.ABI-tag

badass@home:~>

.interp: includes the path to the binarys interpret.
.note: includes programmers and licence notes.
.init: includes program code which is executed before the main part of the program
.fini: includes program code which is executed after the main part of the program
.data: includes the variables- lets say the programs data.
.got: the global offset table.
.bss: lets call this the heap...
.text: includes the program code instructions.

STOP! Thats what we are looking for. If someone trys to modify our code in some hex editor he will modify the ".text" segment. Lets think about the following. We will create a hash value of the ".text" segment and let the binary itself check for a viald hash value on every run.

The hash may be somethin like:

long llhash(char *buf, size_t len)  // yes- this may be the stupides|lames hash you have ever seen...
{
    int i = 0;
    char *ptr;
    unsigned long hash = 0x0defaced;

    ptr = buf;
    while(len--) {
       hash = (hash << i) ^ *ptr++;
     //  if(!(i%4)) hash = hash ~ *ptr;
       if(!((i+=2)%8)) hash = (hash >> 16);
     }
    return(hash);  // give the hash back to read_text
}

Then we have to read the ".text" segment and therefor we should include the elf.h which includes all needed structures for handling the ELF binary. I have written down a small example for getting the ".text" segment.
the argument "me" is the targets filename.

long read_text(char *me)
{
    int fd, i;
    Elf32_Ehdr hdr;           //elf header
    Elf32_Shdr shdr, strtab;  //section header
    char *ptr, buf[1];
    off_t soff, strsecoff;
    size_t nbytes;

    if( (fd = open(me, O_RDONLY)) == -1) {
        perror("open");
        return(-1);
    }

    /*
   elf execution view

   ELF header
   Program header table
   Segment 1
   ...
   Segment n
   Section header table (optimal)

   */

    read(fd, &hdr, sizeof(hdr));  //read elf header
    soff = hdr.e_shoff + hdr.e_shstrndx * hdr.e_shentsize; //e_shstrndx holds the section header table index of the entry associated with the section name string table
    lseek(fd, soff, SEEK_SET);  // "Lets go!" --rancid
    read(fd, &strtab, sizeof(strtab)); //read string secteion
    strsecoff = strtab.sh_offset;      // give me string section offset
    nbytes = strtab.sh_size;           // and the size

    // printf("string table offset %p with %d bytes\n",strsecoff,strtab.sh_size);

    /*lseek(fd, strsecoff, SEEK_SET);   // dump all strings
    while(nbytes-- && read(fd, buf, 1) != -1)
        printf("%x",buf[0]);
    printf("\ndone\n");
*/

    /* hdr.e_shoff: gives the byte offset form beginning of file to secion hader table
       hdr.e_shnum: tells how many entries the secion header table contains
       hdr.e_shentsize: gives the size in bytes of each entry
       hdr.e_phoff: holds the program header tables file offset in bytes
       hdr.e_phentsize: holds the size in bytes of one entry in files program header table
       hdr.e_phnum: holds the number of entries in the pogram header
     */

    for(i = 0; i < hdr.e_shnum; i++) {  // for every section header

    lseek(fd, hdr.e_shoff + (i * hdr.e_shentsize), SEEK_SET); // go to every section header

	if(read(fd, &shdr, sizeof(shdr)) == -1) { //read the stuff
	    perror("read");
	    return(-1);
	}

     lseek(fd, shdr.sh_name + strsecoff, SEEK_SET);  // sh_name + the string table offset gives us the location of the string- like ".text"
     read(fd, buf, 6);
     if(!strncmp(buf, ".text", 5)) {  // is ".text" ?
	 lseek(fd, shdr.sh_offset, SEEK_SET); // if yes go there
	 ptr = (char *)malloc(shdr.sh_size);
	 if(ptr == NULL) {
	     perror("malloc");
	     return(-1);
	 }
	 if(read(fd, ptr, shdr.sh_size) <= 0) {   //read .text -> ptr
	     perror("read");
	     return(-1);
	 }
         return(llhash(ptr, shdr.sh_size)); // hash it
     }
    }
    return(0);
}


We will have to write down our program till there is nothn more to do. Then include read_me with argv[0] as target and put it into a "if" statement. Now another program must calculate the hash and print it on the screen. Well put the hash into our "if" statement and recompile it. done.

But wait a second? cant the guy just eg. "nop" out the "if" statement where the hash is checked? Of curse he can. We always have to life with the fact that we CANT prevent crackers from their job. All we can do is making it harder to crack.

0x04 Confusing  (if you cannot convince then confuse)
##############

Back to the example which was about disassembeld output:

[...]
0x08048409 <main+29>:   add    $0x10,%esp
0x0804840c <main+32>:   cmp    $0xffffffff,%eax      <--- ptrace() == -1?
0x0804840f <main+35>:   jne    0x8048423 <main+55>
[...]

Lets think about confusing the badguy with nonsence functions like this:

[...]
srand(time(0));
if( (rand()%88) > 100) exit(-1);
[...]

This if will never exit because its impossible that the result is greater then 100. But for sure the debugging guy will be confused by stuff like this.
You can think of every other nonsence stuff to make the crackers job more complicated.

[...]
for(i = 100; i; i--);
i+=0xbad00bad;
i+=0xh4h4h4h4;
i = (i | i);
[...]

Creating nonsence in sourcecodes is my easiest job :D


ret end
#######

greets to the excluded.org guys.
greets to peps like: proxy, maximilian, detach, murfie, johnny, !ntruder
		     and all other geeks/freaks i know.
greets to my friends and family.

last but not least:
	greets to all cyberpunks out there
	       "Phree the cyberspace!"

# milw0rm.com [2006-04-13]