:::ret2dl学习以及对应符号表解析1(no relro)
本文最后更新于 2026年8月19日 晚上
ret2dl学习以及对应符号表解析1
前置知识
动态链接(程序默认使用):程序内只存一个函数名称,用ld动态链接器来按照名字来找地址,需要.dynstr存名字、.dynsym存地址等元信息
.dynamic段(总索引),这是Elf32_Dyn类型数组,每项8字节,前4字节代表常量含义(d_tag),后4字节代表地址或值。
d_tag 常量 值 含义 DT_STRTAB5 指向 .dynstr(字符串表) DT_SYMTAB6 指向 .dynsym(符号表) DT_JMPREL23 指向 .rel.plt(重定位表) DT_VERSYM0x6ffffff0 指向 .gnu.version(版本表,64 位时可能使伪造复杂) 用readelf -d 来直接查询值

relro保护机制:部分开启时(默认).dynamic不可写,got可写。全加载时都是只读,不可写。
实际查询时调用_dl_runtime_resolve来调表查询,参数为link_map*,reloc(偏移)
如图为puts初次调用时对应的过程,先plt表,plt表指向got表的地址,got表由于还没存储真实地址,指向了plt+6的位置,这里对应了调用重定位函数。

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

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

如图是.dynamic的位置与值

以下是linkmap对应的内容
l_info[DT_JMPREL/SYMTAB/STRTAB] |
动态段索引表,链接器加载时把 .rela.plt/.dynsym/.dynstr 的地址填进对应槽位 |
决定 reloc_arg、r_info、st_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给出来的是解析出来的值,不是存储它的地址,所以还需要这样来找。

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

以下是动态符号表(dynsym)中数据项的定义
1 | |
保持原项的原因是解析是仍然是从read开始,先从这些表中找的偏移。修改了找dynstr表的地址,那么dynstr从自定义bss基址寻址,偏移按照原本read的偏移,所以要在该位置写入system。
这里的bss_system其实是自定义的假的dynstr表的地址,加上对应的偏移会使得它索引该位置。
1 | |
由于got表已经写入了read的真实缓存,所以不会触发解析,read正常执行,下一步在bss写入bin_sh,最后手动跳转执行read的解析
read是第三项,8+8=0x10,偏移是0x10

1 | |
plt0的位置为0x8049020,压入参数并调用dl_runtime对应的解析。plt0对应函数是所有plt桩的解析函数。
动态触发强行触发解析后,将read改为system。
未进行动态解析时

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

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

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

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