2023.7.8更新:很早之前已经写了这篇博客,但是只学了一点皮毛..只在表面,后面学到了ret2dlresolve漏洞发现需要理解这部分东西,又回炉重造,添加了需要学习的更细致的知识
符号解析 基础知识 参考ctfwiki(wiki中是倒推的,感觉理解起来不如正推)
在 ELF 文件中,对于每一个需要重定位的 ELF 节都有对应的重定位表,比如说 .text 节如果需要重定位,那么其对应的重定位表为 .rel.text。 所以.rel.plt就是plt节需要重定位所产生的节了.
.rel.plt中就会包含一个指向这个符号的重定位表项. r_offset给出了要修改的位置,r_info给出了要修改的符号的符号表索引,所以r_info索引到了 .dynsym
.dynsym中,st_name保存着动态符号在dynstr中的偏移
.dynstr又是怎么来的呢? 答:当一个程序导入某个函数时,.dynstr就会包含对应函数名称的字符串(最后是根据这个字符串名字来进行解析的!!! )
以 ret2dl中的例子来说,可以用readelf查看这些节(但在ida中不会显示的这么全,会放到LOAD段里)
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 root@vultr:~/ret2dl# readelf -S main_partial_relro_32 There are 31 section headers, starting at offset 0x3848: Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .interp PROGBITS 080481b4 0001b4 000013 00 A 0 0 1 [ 2] .note.gnu.build-i NOTE 080481c8 0001c8 000024 00 A 0 0 4 [ 3] .note.gnu.propert NOTE 080481ec 0001ec 00001c 00 A 0 0 4 [ 4] .note.ABI-tag NOTE 08048208 000208 000020 00 A 0 0 4 [ 5] .gnu.hash GNU_HASH 08048228 000228 000020 04 A 6 0 4 [ 6] .dynsym DYNSYM 08048248 000248 0000a0 10 A 7 1 4 [ 7] .dynstr STRTAB 080482e8 0002e8 00006b 00 A 0 0 1 [ 8] .gnu.version VERSYM 08048354 000354 000014 02 A 6 0 2 [ 9] .gnu.version_r VERNEED 08048368 000368 000020 00 A 7 1 4 [10] .rel.dyn REL 08048388 000388 000018 08 A 6 0 4 [11] .rel.plt REL 080483a0 0003a0 000028 08 AI 6 24 4 [12] .init PROGBITS 08049000 001000 000024 00 AX 0 0 4 [13] .plt PROGBITS 08049030 001030 000060 04 AX 0 0 16 [14] .plt.sec PROGBITS 08049090 001090 000050 10 AX 0 0 16 [15] .text PROGBITS 080490e0 0010e0 000289 00 AX 0 0 16 [16] .fini PROGBITS 0804936c 00136c 000018 00 AX 0 0 4 [17] .rodata PROGBITS 0804a000 002000 000008 00 A 0 0 4 [18] .eh_frame_hdr PROGBITS 0804a008 002008 000054 00 A 0 0 4 [19] .eh_frame PROGBITS 0804a05c 00205c 000150 00 A 0 0 4 [20] .init_array INIT_ARRAY 0804bf04 002f04 000004 04 WA 0 0 4 [21] .fini_array FINI_ARRAY 0804bf08 002f08 000004 04 WA 0 0 4 [22] .dynamic DYNAMIC 0804bf0c 002f0c 0000e8 08 WA 7 0 4 [23] .got PROGBITS 0804bff4 002ff4 00000c 04 WA 0 0 4 [24] .got.plt PROGBITS 0804c000 003000 000020 04 WA 0 0 4 [25] .data PROGBITS 0804c020 003020 000008 00 WA 0 0 4 [26] .bss NOBITS 0804c028 003028 000004 00 WA 0 0 1 [27] .comment PROGBITS 00000000 003028 00002b 01 MS 0 0 1 [28] .symtab SYMTAB 00000000 003054 000480 10 29 45 4 [29] .strtab STRTAB 00000000 0034d4 000254 00 0 0 1 [30] .shstrtab STRTAB 00000000 003728 00011d 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), p (processor specific)
.rel.plt .rel.dyn是动态链接的二进制文件中需要重定位的变量的信息,.rel.plt是需要重定位的函数的信息
它的结构如下(以32位为例),两种类型区别见wiki
1 2 3 4 5 6 7 8 9 10 typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; } Elf32_Rel; typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; Elf32_Sword r_addend; } Elf32_Rela;
最重要的就是前两个字段,r_offset,给出了需要重定位的位置,对于可执行文件而言,取值是需要重定位的虚拟地址 ,一般而言也就是GOT表的地址
GOT表中开始存放的值是这个
这玩意也就是plt表,要进行真正解析的地方
r_info给出需要重定位的符号的符号表索引,以及相应的重定位类型. 换句话说,第一个参数是告诉你要把哪里的值进行修改,这个参数是告诉你,要修改哪个符号.
高三个字节对应的值表示这个动态符号在.dynsym符号表中的位置
最低字节表示的是重定位类型
.dynsym 结构如下
1 2 3 4 5 6 7 8 9 typedef struct { Elf32_Word st_name; Elf32_Addr st_value; Elf32_Word st_size; unsigned char st_info; unsigned char st_other; Elf32_Section st_shndx; } Elf32_Sym;
这个在ida中也是没有直接给出的,可以用readelf查看然后寻找.
其中比较重要的字段
st_name 保存着动态符号在.dynstr表(动态字符串表)中的偏移
st_value 如果这个符号被导出,这个符号保存着对应的虚拟地址
1 Elf32_Sym <offset aRead - offset unk_80482E8, 0 , 0 , 12 h, 0 , 0 > ; "read"
以read为例,aRead是这个符号的虚拟地址, 减去符号表开始的虚拟地址,就得到了偏移, read的这个值为0x27
0x804830F - 0x80482E8 = 0x27,也就是read这个字符串开始的地方,(以及他们每个字符串结束后都有个0,位置结束符号)
dynstr表如下
.dynmaic
解析过程 got plt .got.plt 延迟绑定 got表 got.plt表 Globle offset table全局偏移量表,位于数据段,是一个每个条目是8字节地址的数组,用来存储外部函数在内存的确切地址,GOT表存储在数据段,(在IDA中是也就是.data段)可以在程序运行中被修改。
.got 存放全局变量引用
.got.plt 存放需要延迟绑定的函数
got表的初始状态指向一段plt,首次调用时会由plt表中指令进行解析,得到真正的函数地址(即内存中的地址)并填入相应的got表项
plt表
https://blog.csdn.net/qq_52126646/article/details/119494939
延迟绑定 以上一篇博客的题目为例
call一个函数的时候,先到plt 0x400650 <system@plt>
1 2 pwndbg> p system $2 = {<text variable, no debug info>} 0x400650 <system@plt>
然后plt里面第一条 jmp cs:off_600D38 这里会跳到 system@got的地址,(此时还没有初始化)
display /3i $rip 设置单步执行后自动显示的内容,这里显示后续三条指令
1 2 3 4 5 pwndbg> display /3i $rip 2: x/3i $rip => 0x400650 <system@plt>: jmp QWORD PTR [rip+0x2006e2] # 0x600d38 0x400656 <system@plt+6>: push 0x2 0x40065b <system@plt+11>: jmp 0x40062
之后,0x600d38里面的地址是system@plt刚才jmp的下一条,push 0x2,然后再jmp 0x40062,也就是PLT[0],跳转到动态链接器进行地址解析.找到真正的地址,填入got地址,也就是0x600d38,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 pwndbg> x/20wx 0x600d38 0x600d38: 0x00400656 0x00000000 0xf7a46e40 0x00007fff 0x600d48: 0xf7a46f10 0x00007fff 0xf7af2020 0x00007fff .plt:0000000000400650 ; int system(const char *command) .plt:0000000000400650 _system proc near ; CODE XREF: main+DE↓p .plt:0000000000400650 jmp cs:off_600D38 .plt:0000000000400650 _system endp .plt:0000000000400650 .plt:0000000000400656 ; --------------------------------------------------------------------------- .plt:0000000000400656 push 2 .plt:000000000040065B jmp sub_400620
下次在执行的时候,直接就plt->got的第一个jmp -> 实际地址,也就是说0x600d38里存储的是system的真实内存地址了
1 2 3 4 5 6 7 8 9 10 pwndbg> x/20 wx 0x600d38 0x600d38 : 0xf7a31420 0x00007fff 0xf7a46e40 0x00007fff 0x600d48 : 0xf7a46f10 0x00007fff 0xf7af2020 0x00007fff pwndbg> x/20 wx 0x00007ffff7a31420 0x7ffff7a31420 <__libc_system>: 0x74ff8548 0xfa66e90b 0x0f66ffff 0x0000441f 0x7ffff7a31430 <__libc_system+16 >: 0x593d8d48 0x48001649 0xe808ec83 0xfffffa50 0x7ffff7a31440 <__libc_system+32 >: 0x940fc085 0xc48348c0 0xc0b60f08 0x001f0fc3 0x7ffff7a31450 <__GI___realpath>: 0xe5894855 0x56415741 0x54415541 0xec814853