__ __ __
.-----.--.--.----.| |.--.--.--| |.-----.--| | .-----.----.-----.
| -__|_ _| __|| || | | _ || -__| _ |__| _ | _| _ |
|_____|__.__|____||__||_____|_____||_____|_____|__|_____|__| |___ |
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]