:::ret2dl学习以及对应符号表解析1(no relro)

本文最后更新于 2026年8月19日 晚上

ret2dl学习以及对应符号表解析1

前置知识

  • 动态链接(程序默认使用):程序内只存一个函数名称,用ld动态链接器来按照名字来找地址,需要.dynstr存名字、.dynsym存地址等元信息

  • .dynamic段(总索引),这是Elf32_Dyn类型数组,每项8字节,前4字节代表常量含义(d_tag),后4字节代表地址或值。

    d_tag 常量 含义
    DT_STRTAB 5 指向 .dynstr(字符串表)
    DT_SYMTAB 6 指向 .dynsym(符号表)
    DT_JMPREL 23 指向 .rel.plt(重定位表)
    DT_VERSYM 0x6ffffff0 指向 .gnu.version(版本表,64 位时可能使伪造复杂)

    用readelf -d 来直接查询值

    image-20260816204828356

  • relro保护机制:部分开启时(默认).dynamic不可写,got可写。全加载时都是只读,不可写。

  • 实际查询时调用_dl_runtime_resolve来调表查询,参数为link_map*,reloc(偏移)

  • 如图为puts初次调用时对应的过程,先plt表,plt表指向got表的地址,got表由于还没存储真实地址,指向了plt+6的位置,这里对应了调用重定位函数。

image-20260816210320886

下图第二个位置就是plt跳转的第一个位置

image-20260816210639446

栈内显示压入的是0xf7ffda70与0x18作为linkmap*与reloc,其中也可以看到dynamic段的位置

image-20260816211209712

如图是.dynamic的位置与值

image-20260816211350565

以下是linkmap对应的内容

l_info[DT_JMPREL/SYMTAB/STRTAB] 动态段索引表,链接器加载时把 .rela.plt/.dynsym/.dynstr 的地址填进对应槽位 决定 reloc_argr_infost_name 这些偏移的”基准点”,所以它们必须相对这几个表算
l_addr 该对象的加载基址(no-PIE 主程序 = 0) PIE 的关键r_offset 会被加上 l_addr,PIE 下你要把 r_offset 写成相对值或先泄基址,否则写飞
l_scope 符号查找的作用域链表 决定 dl_lookup_symbol_x 从哪些库找符号——正是它让 system 能在 libc 里被找到
l_versions 符号版本表(配 DT_VERSYM 高版本 glibc 会拿伪 Sym 索引查它,越界崩溃 → st_other=1 跳过
l_ld .dynamic 段指针 l_info[] 的数据源,利用中不直接接触
l_next/l_prev 加载对象链表 仅加载/卸载用,利用中无关

norelro

先找dynamic中strtab,即dynamic中记载着字符串表对应的位置

本题中为0x804b1a0-> 0x804826c

readelf给出来的是解析出来的值,不是存储它的地址,所以还需要这样来找。

image-20260818214346838

在dynsym中read是第三项,每项占16字节,读取前4个字节就是read符号名在dynstr中偏移。即*(dyn_str_addr + offset) =”read\x00”

image-20260818215745799

以下是动态符号表(dynsym)中数据项的定义

1
2
3
4
5
6
7
8
typedef struct {
Elf32_Word st_name; // 0-3 字节:符号名在 .dynstr 中的偏移
Elf32_Addr st_value; // 4-7 字节:符号地址(未解析时=0)
Elf32_Word st_size; // 8-11 字节:符号大小
unsigned char st_info; // 12 字节:类型+绑定(0x12 = GLOBAL | FUNC)
unsigned char st_other;// 13 字节:可见性(通常 0)
Elf32_Half st_shndx; // 14-15 字节:所在节索引
} Elf32_Sym;

保持原项的原因是解析是仍然是从read开始,先从这些表中找的偏移。修改了找dynstr表的地址,那么dynstr从自定义bss基址寻址,偏移按照原本read的偏移,所以要在该位置写入system。

这里的bss_system其实是自定义的假的dynstr表的地址,加上对应的偏移会使得它索引该位置。

1
2
3
system_str_addr = bss_system + st_name
p.send(stage_read(system_str_addr, 8).ljust(0x200, b'\x00'))
p.send(b'system\x00')

由于got表已经写入了read的真实缓存,所以不会触发解析,read正常执行,下一步在bss写入bin_sh,最后手动跳转执行read的解析

read是第三项,8+8=0x10,偏移是0x10

image-20260818221922431

1
2
3
4
typedef struct {
Elf32_Addr r_offset; // 0-3 字节:要写到哪(通常是 GOT 表项地址)
Elf32_Word r_info; // 4-7 字节:高 24 位=符号在 dynsym 中的下标,低 8 位=重定位类型
} Elf32_Rel;

plt0的位置为0x8049020,压入参数并调用dl_runtime对应的解析。plt0对应函数是所有plt桩的解析函数。

动态触发强行触发解析后,将read改为system。

未进行动态解析时

image-20260818223034830

第一次read后对应的got表地址

image-20260818222642091

经过解析后的got表地址(dl重定位完毕后,会自行跳转到解析出的地址进行执行)

image-20260818222818216

read自行重定位时压入的便是偏移,然后调转到plt0。这里push的0x10就是找的reloc_arg偏移。

image-20260818223314458

最后直接执行该plt函数,压入0x10偏移便可以执行system(“/bin/sh”)

1
2
3
4
5
6
7
8
9
10
11
12
# 阶段4: 触发解析

payload = b'A' * 76
payload += p32(plt0)
payload += p32(reloc_arg)
payload += p32(0xdeadbeef) # 伪返回地址 (占位)
payload += p32(binsh) # system 的第一个参数
#debug()

p.send(payload.ljust(0x200, b'\x00'))

p.interactive()

最终执行

image-20260819151016007


:::ret2dl学习以及对应符号表解析1(no relro)
https://xyyr-c.github.io/2026/08/16/ret2dl学习以及对应符号表解析1/
作者
xyyr
发布于
2026年8月16日
许可协议