pwn 👻
/usr/lib/debug
(可能會很亂)set exec-wrapper env "LD_PRELOAD=./libc-2.29.so"
(for 2.29)
set environment LD_PRELOAD=./libc.so
就可以,有時後不行set debug-file-directory /usr/lib/debug/lib/x86_64-linux-gnu
ref: https://stackoverflow.com/questions/10000335/how-to-use-debug-version-of-libc
libc package (dbg prefix - debug symbols)
https://mirror.tuna.tsinghua.edu.cn/ubuntu/pool/main/g/glibc/
In gdb:
dir /usr/src/glibc/glibc-2.31/exit.c
make debug symbol glibc
strace ./binary
)checksec
)\xcd\x80
) 怎麼生gcc 4.8 以上, 64 bit 下有許多通用 gadget
__libc_csu_init
不會做事情,因為他只是負責 call init entry 而已,而 init entry 也不會幹嘛pop rdx ; ret
找不到__libc_csu_init
_libc_csu_init
)_start
)pop_rsp_r13_r14_r15_ret
來把 stack 遷到 got 上,這樣就能把 got value 放到 register 內mov cs:__bass_start, 1
會有 add ebx, esi ; ret 的 gadgetcall [r12+8*rbx]
可以用
_dl_runtime_resolve
在 relro 全開時無法用_IO_file_write
可以 leak libc (大概 == write)gets
,使留下的 _IO_file_write
可以被 call 到,並藉著控制 rdi, rsi, rdx _IO_new_file_write(stdin, got, 8)
*(ptr+0x70)
為 1 就好 (stdout),這樣就能 leakhttps://github.com/lattera/glibc/blob/master/libio/fileops.c#L1180
__write (f->_fileno, data, to_do)
offset of _fileno
== 0x70
puts("/bin/sh")
== system("/bin/sh")
GOT 的前三個 entry 分別是:
_dl_runtime_resolve
整個 runtime resolve 的 function prototype:_dl_runtime_resolve(*link_map, rel_offset)
libc_start_main + 243, 242, 235 ...
)
%n, %hn 預設為將之前印出來 character 數量,寫到對應 argument ptr 指到的位置,而如果沒有指定 k$,可以在一次 fmt 之內按照順序修改多次
printf("%*");
:*
代表取對應函式參數的值
用來計算總共寫多少次的 function
FILE struct
fopen()
產生FILE struct
時為宣告 _IO_FILE_plus
_IO_FILE
_flags
"w+", "r" …_IO_read_ptr
~ _IO_buf_end
_fileno
由 system()
open 產生_IO_FILE_plus
_IO_FILE
中為 _chain
fopen()
_IO_list_all
一開始會串著 sterr, stdout and stdin_flag
, chain
and vtable
等…chain
在指向原本的 chain
)
_IO_list_all
指到,緊接著是其他 fd 與 stderr, stdout, stdin (類似 LIFO ?)sys_open()
the filefread()
vtable->_IO_file_xsgetn
判斷vtable->_IO_file_doallocate
allocatevtable->_IO_file_underflow
讀一大段到 stream buffer 上
sys_read()
讀檔fwrite()
fclose()
flush()
and release()
stream bufferfopen()
時自動 malloc 一個空間到 heap 上_IO_new_file_init_internal
會進行structure 初始化
sys_open()
傳送的一大筆資料_IO_link_in
將此 structure insert 至 _IO_list_all
的頭
_IO_list_all
為 file_chain (linked list), 會將所有 FILE structure 串起來sys_open()
的結果為 fd, assign 至 _fileno
fread()
時, 會先 vtable->_IO_file_xsgetn
看 file stream 是否為 NULL, 若是則利用 vtable->doallocate
allocate 一塊 memory 給他vtable->_IO_file_underflow
呼叫 sys_read()
, 讀 _fileno
對應的檔案的 1 page 到 stream buffer 上fread()
的參數給予對應大小的 responsefclose()
時會先把此 FILE structure 從 _IO_list_all
拔除, 清空 (flush and release) file stream 以及關閉檔案, 最後 free()
掉此 FILE structure__GI__IO_file_XXX
,就是利用 vtable 來呼叫的 function_IO_buf_base
為 buffer 開始,通常沒特別設定就會在 heap 上_IO_XXX_ptr or base or end
等等是在 _IO_new_file_underflow
,一開始除了 _IO_buf_end
,其他全部都會等於 _IO_buf_base
read_end
指向透過 syscall read 讀到的結尾read_ptr
會 == read_end
代表讀完了_IO_new_file_underflow
),再讀一次,會讀成功,但是只有 EOF (0xffffffff),而此時就會 returnfread
fwrite
_IO_buf_base
輸入輸出 buffer base_IO_buf_end
輸入輸出 buffer end
_IO_buf
在 _IO_doallocbuf
會被 init_IO_write_base
輸出 buffer base_IO_write_ptr
輸出已使用到的位置_IO_write_end
輸出 buffer end_IO_OVERFLOW
能 fflush 所有的_IO_new_file_overflow
內,會初始化沒有 write ptr 的 fp
_IO_setb
是用來 set buffer
_IO_write_base
== _IO_write_ptr
== _IO_buf_base
、_IO_write_end
== _IO_buf_end
_IO_OVERFLOW
flush buffer_IO_cleanup
-> _IO_flush_all_lockp
才會寫回_IO_new_file_overflow
, read 是 call _IO_new_file_underflow
_IO_fread
的 bytes_read = _IO_sgetn (fp, (char *) buf, bytes_requested);
_IO_sgetn
的 return _IO_XSGETN (fp, data, n);
libio/genops.c
_IO_file_xsgetn
會 call __underflow()
,如下
__underflow
的 return _IO_UNDERFLOW (fp);
_IO_new_file_underflow
的 if (was_writing && _IO_switch_to_get_mode (fp))
Flush unwritten characters.
_IO_switch_to_get_mode
_IO_read_base
設為 _IO_buf_base
_IO_new_file_underflow
,下面程式碼會把以下 ptr 都設為 buf_base
,之後在 call _IO_SYSREAD
_IO_file_read
作為 read 的 wrapper,原本要從 file 中讀 100 bytes (buffer 的關係) 寫到 buffer,但現在變成從 stdin 讀 (fileno = 0),寫到我們指定的 buffer 位置
_IO_fwrite
的 written = _IO_sputn (fp, (const char *) buf, request);
_IO_new_file_xsputn
的 if (_IO_OVERFLOW (f, EOF) == EOF)
_IO_new_file_overflow
的 if (ch == EOF)
f->_IO_write_ptr - f->_IO_write_base
這邊為要先寫回 fd 的 size,而又因為改動 fd fileno 為 1,所以會把此 range 印到 1 (stdout)_IO_new_do_write
的 new_do_write
new_do_write
的 count = _IO_SYSWRITE (fp, data, to_do);
_IO_new_file_write
的 : write (f->_fileno, data, to_do));
最重要的是此 if condition,會先判斷有沒有資料要 flush,之後再做一次 new_do_write()
,讀真正要讀的東西
https://elixir.bootlin.com/glibc/glibc-2.23/source/libio/fileops.c#L1327
正常情況會跑 _IO_default_xsputn
,把輸出寫到 buffer 內
使用到 stdout 的 function (puts, fwrite…) 可以讀東西到 buffer (任意寫),也能將 buffer 中的東西印出來 (任意讀)
任意讀的構造如上,控制 write / read 以及 fileno
任意寫的 example payload 如下,利用 _IO_new_file_xsputn
中判斷 (f->_IO_write_end > f->_IO_write_ptr)
,若成立則代表輸出 buffer (write) 還能存東西,所以先把資料寫到裡面,但是要控制好 read_end
要等於 write_base
,這樣 buffer 才不會重疊
而為什麼要 0x8000 (_IO_USER_LOCK
),因為如果沒有 _IO_acquire_lock_clear_flags2
,則 process 會陷入 for loop
https://elixir.bootlin.com/glibc/glibc-2.23/source/libio/libioP.h#L872
https://elixir.bootlin.com/glibc/glibc-2.23/source/libio/libio.h#L448
_IO_new_fclose
call _IO_un_link ((struct _IO_FILE_plus *) fp);
_IO_un_link
_IO_list_all = (struct _IO_FILE_plus *) _IO_list_all->file._chain;
就是在 unlink FILE_IO_new_fclose
,並且 call _IO_file_close_it
關閉他
_IO_new_fclose
還會 call _IO_new_file_close_it
_IO_NO_WRITES
(代表有寫) 的話,就會做 _IO_do_flush
,把要 write 的值寫? _IO_SYSCLOSE (fp) : 0);
來 close fd_IO_setX
來清空 buffer_IO_un_link
_IO_FINISH
_IO_do_flush (fp);
,並且關閉他_IO_setb (FILE *f, char *b, char *eb, int a)
:set buffer_IO_setg(fp, eb, g, eg)
:set read_XX (g 應該是指 gets)
_IO_setp(__fp, __p, __ep)
:set write_XX (p 應該是指 print)
前提為
sebvbuf(stdout, 0, 2, 0)
想辦法把:
stdout->_flags == 0xfbad1800
0xfbad0000
(file magic)
_IO_NO_WRITES
== 0xfbad0000_IO_CURRENTLY_PUTTING
(0x800) == 0xfbad0800
_IO_IS_APPENDING
(0x1000) == 0xfbad1800
_IO_write_base
&(stdout->_flags)
之類的 libc 位置 (需要比原本小,因為原本的 _IO_write_base
== _IO_write_ptr
== _IO_write_end
之後在 puts
時就會從 &(stdout->_flags)
開始噴,噴到 _IO_write_end
前提為
sebvbuf(stdin, 0, 2, 0)
用 unsorted bin attack 蓋掉 _IO_buf_end
,此時再寫就能從 _IO_buf_ptr
寫到 _IO_buf_end
,也就是 unsorted bin
(in main_arena),過程中就有 __malloc_hook
可以蓋
https://ray-cp.github.io/archivers/IO_FILE_vtable_hajack_and_fsop
https://ray-cp.github.io/archivers/IO_FILE_vtable_check_and_bypass
透過修改 vtable ptr,指向可以控制的地方,然後因為 vtable ptr 指到的地方也是一堆 funcion ptr,所以我們可以透過修改對應使用到的 function ptr 來進行攻擊。 (glibc 2.24 前)
glibc 2.24 後增加了以下 check,主要是 check vtable ptr 是不是在 glibc vtable range 中
__start___libc_IO_vtables
為 vtable range 的開始__stop___libc_IO_vtables
為 vtable range 的結束https://elixir.bootlin.com/glibc/glibc-2.23/source/libio/genops.c#L58
glibc 中有個 function 叫 _IO_flush_all_lockp
,主要負責 flush 所有 FILE,在程式結束之前會被 call 到
GLIBC 2.27 the
abort()
function no longer calls_IO_flush_all_lockp()
攻擊方法為:偽造一個 fake FILE,並將 _IO_list_all
指到我們的 fake FILE,最後繞過以下檢查,使用 _IO_OVERFLOW
來 control flow
glibc 2.23
舉 house of orange 為例子,將_IO_list_all
蓋成 unsorted bin 後,smallbin 0x60 的位置剛好會是_chain
,而透過在此 chunk 構造好 data 與 vtable,在使用 vtable_IO_OVERFLOW
時會把 fp 當作第一個參數傳入,如果我們把_IO_OVERFLOW
的位置寫成 system,fake chunk fp 一開始寫成/bin/sh\x00
,這樣在 call_IO_OVERFLOW
時就等於跑system(fp)
,getshell
glibc 2.24 後
利用_IO_str_jumps
或_IO_wstr_jumps
,而兩者只差在一個是處理 wchar (寬字元)
要打的地方在 _IO_str_finish
直接 call ((_IO_strfile *) fp)->_s._free_buffer
,並把 fp->_IO_buf_base
當作參數
如果我們能讓 vtable == _IO_str_jumps-8
,這樣對應到的 __overflow
offset 就會是 _IO_str_finish
_IO_acquire_lock(fp)
, 因為 FILE 內有 *_lock
, 預防 multihread 有 RC 的情況
_IO_FILE_plus.vtable
, 讓他指向 system
/bin/sh
_chain
and _IO_list_all
_IO_flush_all_lockp
_IO_OVERFLOW
_IO_list_all
, 同時也構造出 0x60 大小的 chunk 進入 small binmalloc()
時, 不管 unsorted bin 是否有剛好大小的 chunk, 他都會 unsorted bin 的 chunk 做 unlink
_IO_FILE_plus._flags
< 0free()
的情況下把 chunk 放入 small bin
malloc()
時, 會搜到此 chunk, 發現 size 不對後會放到 small bin (0x60)
abort()
-> _IO_flush_all_lockp()
_IO_list_all
- 0x10
_IO_list_all
因為 unlink unsorted bin 的機制會指向 unsorted_bin[0] (in main_arena)_IO_list_all
), main_arena 的 smallbin[4]
(chunk size 為 0x60) 會指向我們的 chunk, 並把我們的 chunk 當作 FILE structure 看
_IO_list_all->_chain
剛好為 smallbin[4] (0x60)_IO_write_ptr
> _IO_write_base
_IO_flush_all_lockp()
影響
_flags
改成 '/bin/sh;'system()
system("/bin/sh")
_IO_vtable
section 之內
_dl_open_hook
也能繞, 但能寫到這, 也不必要寫到此 hook_fileno
to fd of stdout_flag
& ~_IO_NO_WRITES
_flag
|= _O_CURRENTLY_PUTTING
write_base
& write_ptr
to mem you want to read_IO_read_end
== _IO_write_base
_fileno
to fd of stdin_flag
& ~_IO_NO_READS
read_base
== read_ptr
buf_base
& buf_ptr
to mem you want to readbuf_end
- buf_base
> size of fread__malloc_hook
/ __free_hook_
/ __realloc_hook_
_flags
_IO_write_base
ptr_IO_write_base
(誤以為東西還沒輸出完)_IO_buf_end
scanf("%d", &var)
read(0, buf_base, sizeof(stdin buffer))
__malloc_hook
_IO_strfile_
== struct{ _IO_streambuf
; _IO_str_fields
}
_IO_str_finish
, _IO_str_jumps
, _IO_wstr_finish
等等scanf("%d", &var)
底層會 call read(0, buf_base, sizeof(stdin buffer))
_IO_write_base
~ _IO_write_ptr
內容的東西是要被寫得,寫去哪? _fileno
_IO_buf_base
~ _IO_buf_end
內容的東西是要被讀的,從哪讀? _fileno
(1)可以控制 stdout 或是 stdin 等等 fp,可以嘗試改動 chain,改成 heap address 之類可以控制的地方,並偽造 fake _IO_file_plus
為可以在 runtime 使用並釋放的 memory space, 是由 low address -> large address
void *sbrk(intptr_t increment)
:
start_brk
為 heap 起始, brk
為 heap 結束brk
void brk(void *addr)
:
brk
addressvoid *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
:
mmap(NULL, (size_t)(4*1024+1), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)
PS. getchar()
, scanf()
等等有關 input 的 function 皆會使用到 heap
The GNU C Library ("glibc", the usual C runtime on Ubuntu) allocates memory for internal use the first time stdout is used for anything
malloc()
:
132KB
的 heap segment(rw), 並標示為使用過
, 我們稱之 main arenafree()
是釋放給 glibcmalloc()
return 回來的 ptr 指向 chunk, 為 glibc 實作 memory management 的 data struct (header + data)free()
:
呼叫流程如下
malloc()
––> __lib_malloc()
––> _int_malloc()
free()
––> _int_free()
thread arena 可以有多個 heap, 且每個 heap 都有自己的 header, 稱作 heap_info, 用來描述 heap 的 info
mmap()
or brk()
allocate memory (arena) 給 heap, 而此時該 thread 有多個 sub-heap (原 arena + 新 arena), 組成 heap
mmap()
出的 heap, 且 heap2 的 heap_info->prev
指到 heap1 的 heap_info->ar_ptr
, 而 heap1 的 heap_info->ar_ptr
指到 malloc_state
, 此作法方便後續管理malloc_state 儲存 arena 的 info, 包含 bin, top chunk, last remainder chunk 等等資訊 (arena_header)
mmap()
create相同 index 下,排列有以下特點:
mmap
拿到free()
的 chunk 為 bin 中的第一個 freed chunk, 則 fd == bk == main_arena->bin
, 因為 main_arena->bin
linked list 的頭尾為 main_arena->bin
自己的 chunkmalloc()
時才會被填入
consolidate
, 會清空 unsorted bin, 將對應大小的 chunk 分配到 small & large binglibc >= 2.26 後增加的機制 (ubuntu 17.10), 目的要提升 performance
malloc()
沒有檢查 sizemalloc()
0x100 以上的 chunk 7 次, 並且 free 7 次讓 cache 塞滿, 最後就可以進入 unsorted bin 並 leak 出 libc addrmalloc()
時被取出
打 heap 的重點
全名為 use-after-free, free 完 ptr 後並沒有
ptr = NULL;
, 導致原 ptr 仍指到 heap address, 又稱 dangling pointer
information leak
由於條件判斷錯誤, 導致在寫入時額外寫入 1 byte, 可能是任意字元, 可能是 null, 又稱 one byte overflow
而蓋過的 byte, 在 heap 段若是prev_in_use
orprev_size
, 則可能會出現漏洞
strlen(buf)
判斷長度並無判斷 \x00
, 而 strcpy(a, buf)
時會把 \x00
也一起 copy, 導致蓋到下一個 bytefor loop
多一次, 寫到不該寫入的地方主要為蓋 prev_inuse bit
fastbin 在檢查 double free 時, 只檢查 linked list 第一個是否為要 free 的 ptr, 可以用
free(A); free(B); free(A);
bypass
malloc()
取得該位址的 ptr, 透過讀寫做 exploit
__malloc_hook
- 0x23 (0x13 for padding)malloc_consolidate()
, 將 fastbin 可以 merge 的 merge 後再放入 unsorted bin, 不能 merge 的直接放入, 所以可以 bypass double free但是第三次 malloc()
拿出的 fake_chunk 必須符合 fastbin check, chunk_size 需相符
key (chunk+0x18) == tcache
這個攻擊手法必須要能在 stack 留下放 ROP chain 的 heap address,然後能寫到 realloc_hook 跟 malloc_hook
因為 malloc 跟 realloc 在執行前都會 push + sub rsp,所以很有可能會把 heap address 包在 function frame 之中,而此時假設跑 malloc,而此時剛好 rsp 放著 heap address,我們可以改寫 malloc_hook 成 realloc+6,realloc_hook 成 pop rsp ; ret,這樣 malloc 在 call malloc_hook 時,會 call realloc+6,+6 跟 + 0 相差會少 push 一個 register,而 realloc 在 call realloc_hook 時,會把 rsp 上的 value pop 回去,但是因為一開始少 push 一個,所以最後在 jmp 到 pop rsp ; ret 時,rsp 剛好會放 heap address,最後透過 pop rsp ; ret 就能跑 ROP
而此種攻擊方法也能用在 one_gadget,可以控制讓 rsp 符合 one_gadget 的條件
__free_hook
__malloc_hook
printf("%10000c")
)__realloc_hook
printf()
) => buffer 不夠大 => malloc()
=> malloc_hook__libc_start_main
底層malloc()
malloc - 0x10
calloc
skip 所有與 tcache 相關的東西__free_hook
__malloc_hook
__realloc_hook
__free_hook
為例, 若 chunk (ptr) 放的東西是 '/bin/sh', 且 __free_hook
內放 system addr, 在 call free(ptr)
時做的行為就是 system(ptr)
, 也就是 system("/bim/sh")
malloc_hook
下面 (+ 0x10),因此如果 malloc_hook 寫 one_gadget 都沒用的話,可以嘗試寫 top_chunk
free_hook
上面的地方,之後將 top chunk 寫成此 chunkfree_hook
,寫 free_hookmp_.mmap_threshold
(0x20000) 時,會使用 mmap
開一個新的 memory space 給 user,而這個位置與 libc 的 offset 是固定的,藉此可以 leak libctop chunk
,讓 libc 在判斷一些條件後,把 top chunk 丟到 unsorted bin 中(old_top == initial_top (av) && old_size == 0)
:不確定這段會不會過,initial_top 似乎沒法繞?(unsigned long) (old_size) >= MINSIZE
:old_size 是最 top chunk 的 size,至少要 >= 0x20prev_inuse (old_top)
:prev_inuse bit 要設(unsigned long) old_end & (pagesize - 1)) == 0)
:top chunk 加完 size 後的 address 需要與 page 對齊從 unsorted bin 切 chunk 出來時,剩下的 chunk (last remainder) fd bk 會指向 main_arena + 0x58,而拿到的 chunk 都會殘留原本 chunk size 對應到的 large/small bin 的 address (in libc)
http://gcc.gnu.org/onlinedocs/gcc-4.8.0/gcc/Code-Gen-Options.html#Code-Gen-Options
https://stackoverflow.com/questions/2463150/what-is-the-fpie-option-for-position-independent-executables-in-gcc-and-ld
當 resulting file call shared object 的 function 時, 才會去 so 內部找 function 實際位置並寫入 .got.plt (global offset table), 以減少 initial 大量延遲(一開始就要全找) 以及節省時間(有些根本不會被 call 到)
jmp
至 func@plt, 而 func@plt 第一行即是 jmp
至 func@got 內寫的值, 而此時為 func@plt+6. 而後 func@plt+6 會執行 push
以及 jmp
, 該 push
的值為rel_offset(.rel.plt 的 index), jmp
到 plt[0]0x8048380
, 也就是 plt[0] 會再 push
並 jmp
, 此時 push
的值為 *link_map, jmp
到 lib 中的 <dl_runtime_resolve> (.got.plt 的 第三項, 0x8040a000(.got.plt) + 8) function 的位置進行 binding._dl_runtime_resolve(*link_map, rel_offset)
_dl_runtime_resolve
會根據以下 pseudo code lookup function, 我們要做的事情就是:
trace
_dl_runtime_resolve->_dl_fixup->_dl_lookup_symbol_x->do_lookup_x->check_match
_dl_runtime_resolve
:_dl_fixup
_dl_fix_up
:link_map
以及 reloc_arg
當參數, 前者為定值 .plt[1], 後者為 function 在 .rel.plt 的 offset_dl_lookup_symbol_x
elf_machine_fixup_plt
將 got 寫入 real function address_dl_lookup_symbol_x
:
do_lookup_x
找尋 symboldo_lookup_x
:
check_match
:
strcmp
比對 symbol name, 再比對 versionref: https://quentinmeffre.fr/pwn/2017/01/26/ret_to_stack.html
舉例來說
__stack_prot
_dl_make_stack_executable
_dl_make_stack_executable(void* address)
__libc_stack_end
address 當作參數__mprotect
, 使其根據 __stack_prot
mprotect(const void *start, size_t len, int prot);
https://code.woboq.org/userspace/glibc/csu/elf-init.c.html#__libc_csu_init
__libc_csu_init
是 __libc_start_main
時會使用到來初始化的 function,但是在某些條件下,他能夠做到 "控制 register 值" + "call function" + 在 stack 留下東西,並且沒有副作用
而我們需要的部分分成兩塊:
(r12)(rdi, rsi, rdx)
在設 rbx = 0
and rbp = 1
的情況下,cmp rbx, rbp
會直接通過
這招真的很強 (X
這比較要講的是一個概念
push 會把某值從 regsiter 寫到 stack 上,pop 會把某值從 stack 拿到 regsiter 內,這一定大家都知道,但是這邊有一個關鍵:push 會把值洗掉,但是 pop 拿了值不會清零
因此執行完 libc function 後,很容易在 stack 殘留 libc address,但是要小心 rsp
與 rbp
的值,因為 libc function 可能都會使用到,如果離太近或是怎樣,可能會有值被蓋掉的疑慮,因此要多觀察、多 try
gets
利用不斷在 gets + stack pivoting,使留下的 _IO_file_write
address 可以殘留在我們控制到的地方 (bss 等等),並藉著 __libc_csu_init
,控制 rdi, rsi, rdx, r12,執行 _IO_new_file_write(stdin, got, 8)
https://github.com/lattera/glibc/blob/master/libio/fileops.c#L1180
看得出來此 function 只需要用到 fileno,因此 stdin [:0x70] 可以任意值,只要 *(fake_stdin+0x70)
為 1 就好
https://binpwn.com/papers/control-flow-enforcement-technology-preview.pdf
https://www.linuxplumbersconf.org/event/2/contributions/147/attachments/72/83/CET-LPC-2018.pdf
https://software.intel.com/content/www/us/en/develop/articles/emulating-applications-with-intel-sde-and-control-flow-enforcement-technology.html
CET
./sde -cet -- application
jmp rax
endbr mark valid jump target addresses of indirect calls and jumps in the program
gcc -fcf-protection ...
,但不知道怎樣才算有效 (?__unwind {
的 prefixsde -debug -- yourapp
__libc_stack_end
可以 leak 出 stack 的位置scanf("%u")
時可以輸入 +
, -
特殊符號來 pass 這次的讀取
+ -
:pass 這次search -8 <canary>
check_inuse_chunk
macro 只有在 MALLOC_DEBUG == 1 才會做事,而平常在檢查 inuse 的都會是 inuse
macrohttps://www.youtube.com/watch?v=1eF7fMdVoOA&ab_channel=jwang
https://www.youtube.com/watch?v=emt1yf2Fg9g&ab_channel=BlackHat
https://saelo.github.io/presentations/blackhat_us_18_attacking_client_side_jit_compilers.pdf
https://medium.com/walkout/淺淡-js-engine-機制-77391b4dd3db
add(a: smi, b: smi)
if (check int fail) {jmp bailout}
exit()
vs atexit()
:
int on_exit(void (*function)(int , void *), void *arg);
int atexit(void (*function)(void));
__libc_start_main -> main function -> __run_exit_handler -> dl-fini
其中在 __libc_start_main
中,有使用 atexit()
註冊一些 exit function
而 __cxa_atexit()
又跟 atexit()
差在哪裡呢?
__cxa_atexit()
is not limited to 32 functions.__cxa_atexit()
will call the destructor of the static of a dynamic library when this dynamic library is unloaded before the program exits.control flow
exit()
會呼叫 __run_exit_handler()
__run_exit_handler()
(struct exit_function_list *) 0x7f1ff52229a0 <initial>
PTR_DEMANGLE
: ror 0x11 ; xor qword ptr fs:[0x30]
_dl_fini
,主要做的事情是 "call the destructors for all still loaded objects"_DYNAMIC
,l_ld
會指向 _DYNAMIC
section,並且會呼叫 l_ld->l_info[DT_FINI_ARRAY]
,會從 _DYNAMIC
找到 fini_array 的位置
#define DT_FINI_ARRAY 26 /* Array with addresses of fini fct */
(Elf64_Dyn *) 0x403e70
(ELF_base + fini)[0]
來呼叫 fini function
__do_global_dtors_aux
__do_global_ctors_aux
and __do_global_dtors_aux
for calling the constructors and destructors of these static objects__do_global_dtors_aux
:會呼叫到 __cxa_finalize()
來執行用 atexit()
註冊的 function (glibc 上面是這樣寫沒錯,但是實際上好像是 _run_exit_handler
)execvpe
找RUN_HOOK (__libc_atexit, ());
會呼叫 __elf_set___libc_atexit_element__IO_cleanup__
的 _IO_cleanup
__elf_set___libc_atexit_element__IO_cleanup__
可寫,所以也能將 one_gadget 寫在這vector => [abi:cxx11]
第一個 element => vector member (data ptr, data_size),且 vector 一開始的 size 為 0x80 (4 elements)
參考文章:https://medium.com/@ktecv2000/聊聊glibc-2-32-malloc新增的保護機制-safe-linking-9fb763466773
針對 tcache 與 fastbin 的 next pointer 做 XOR 的保護
所以在 heap 上做攻擊時,應該要先 leak heap address,才能通過 tcache 與 fastbin 中 address 的檢查