l0om.org 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 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 #include #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 #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. "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." 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: 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. mh.. so lets implement this to our code to prevent beeing "ptraced"... #include #include 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 #include 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 : push %ebp 0x080483ed : mov %esp,%ebp 0x080483ef : sub $0x8,%esp 0x080483f2 : and $0xfffffff0,%esp 0x080483f5 : mov $0x0,%eax 0x080483fa : sub %eax,%esp 0x080483fc : push $0x0 <--- push the arguments on the stack 0x080483fe : push $0x1 0x08048400 : push $0x0 0x08048402 : push $0x0 0x08048404 : call 0x80482dc <--- execute it - the return = %eax 0x08048409 : add $0x10,%esp 0x0804840c : cmp $0xffffffff,%eax <--- ptrace() == -1? 0x0804840f : jne 0x8048423 [...] 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 : add $0x10,%esp 0x0804840c : cmp $0xffffffff,%eax <--- ptrace() == -1? 0x0804840f : jne 0x8048423 [...] 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!"