Linux栈溢出利用之return to dl-resolve payload 构造原理(二)

0x00. payload构造思路

前面我们已经大致介绍了dl-resolve如何进行函数地址的解析。

主要步骤是:通过函数的function@plt,将reloc_arg参数压栈。再跳转到过程链接表的开始PLT[0],将link_map地址压栈。然后调用_dl_runtime_resolve(link_map, reloc_arg)解析函数的地址,并写回到函数的全局偏移表(.got.plt)中,最后返回到需要解析的函数中。在_dl_runtime_resolve中调用_dl_fixup函数,主要的操作是:通过参数reloc_arg确定重定位表中该函数的重定位表项;再通过该重定位表项的r_info字段,在动态链接符号表中确定该函数的符号表项,以及类型,并进行一些检查。再由动态链接符号表项的st_name在动态链接字符串表中确定函数名称。

payload构造:
如果我们伪造reloc_arg,使该函数的重定位表项位于我们可控制的位置;再伪造重定位表项(即r_offset和r_info),可使该函数的动态链接符号表表项位于我们可控制的位置;然后,伪造动态链接符号表表项(即st_name、st_value、st_size、st_info、st_other、st_shndx),主要是st_name的值,使该函数动态链接字符串表表项位于我们控制的位置;最后,伪造动态链接字符串表表项值为我们想要解析的函数名,就可以了。

0x01. 32位应用的payload构造

下面以XMAN level4中的32位ELF为例,介绍32位应用payload的构造过程,该ELF有个明显的栈溢出漏洞。

首先,通过栈溢出向bss段写入我们精心构造的payload2,一般选择bss段偏移为0x400的位置(.bss+0x400)写入。该payload包含解析函数的地址入口、返回值、需要解析函数的参数、伪造的重定位表项、动态链接符号表表项、动态链接字符串表表项、以及函数的参数值。然后,通过平衡堆栈,将程序的执行流交给解析函数。

32位应用 payload的主要构成如下:
payload1: 利用漏洞进行读操作,将数据写入bss区。

1
2
3
4
5
6
7
___________________________________________________________________________________________________________
| 'A' * offset | read_plt | p_p_p_ret | 0 | base_stage | 100 | p_ebp_ret | base_stage | leave_ret |
|--------------|-------------|-----------|-------|------------|------|-----------|------------|------------|
| 溢出填充 |返回地址 |返回地址 | arg1 |arg2,写地址 | arg3 |以写入地址恢复ebp,构造假的栈帧,返回 |
| |覆盖为read |为gadget平 | |.bss+0x400 | | |
| |的plt地址 |衡堆栈 | | | | |
|--------------|-------------|-----------|-------|------------|------|-------------------------------------|

payload2: 构造假的表项
32位应用,构造假的重定位表项、动态链接符号表项、动态链接字符串表项

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
___       ______________
| | "BBBB" | 栈顶地址
| |--------------|
| | PLT[0] | PLT表基址,即调用_dl_runtime_resolve函数的入口
| |--------------|
| | reloc_offset | 重定位偏移,即相对于重定位表(.rel.plt)的偏移
| |--------------|
| | ret | 返回地址,一般随便填充。
| |--------------|
| | arg1 | 函数参数1,arg1
| |--------------|
| | arg2 | 函数参数2,arg2
| |--------------|
| | arg3 | 函数参数3,arg3 //不足3个参数,随便填充其他值
| |--------------|
80Bytes | r_offset | 假的重定位表项: r_offset ,即函数的got
| |--------------|
| | r_info | 假的重定位表项: r_info
| |--------------|
| | align | 由于动态链接符号表字节对齐,因此需要对齐字节
| |--------------|
| | st_name | 假的动态链接符号表表项: st_name
| |--------------|
| | st_value | 假的动态链接符号表表项: st_value
| |--------------|
| | st_size | 假的动态链接符号表表项: st_size
| |--------------|
| | st_info.... | 假的动态链接符号表表项: st_info、st_other、st_shndx
| |--------------|
| | "system" | 假的动态链接字符串表表相: 字符串如:"system""write"等等
| |--------------|
| |'AAAA....' | 填充字符: 'A',使长度达到80字节
--- |--------------|
| | "/bin/sh" | 写入的参数字符串:"/bin/sh"
20Bytes |--------------|
| | 'AAAA....' | 填充字符: 'A'
_|_ |______________|

0x02. 64位应用的payload构造

前面提到过64位应用和32位应用在解析函数地址的时候有一些区别:
1)_dl_runtime_resolve函数的参数reloc_arg的值的不同,32位应用是偏移值,64位应用是索引值;
2)在_dl_fixup中,在伪造sym之后,会造成解析过程中VERSYM取值超出范围,造成segment fault,需要将link_map + 0x1c8地址上的值置0;
3)动态链接符号表表项中的成员顺序有变化(即ELF64_Sym结构体成员顺序有变化)。

在构造payload的时候需要注意以上3点不同,并且64位应用通过寄存器传参。此外,payload的长度不要太长,构造的payload过长,传输的过程会截断,造成无法利用成功。

64位应用 payload的主要构成如下:
payload1: 泄漏link_map地址

1
2
3
4
5
6
 _________________________________________
| 'A' * offset |com_gadget | addr_vulfun|
|--------------|-------------|------------|
|溢出填充 | x86_64 通用 |漏洞函数 |
| |的gadget |的地址 |
|--------------|-------------|------------|

payload2: 覆盖link_map 地址 + 0x1c8的地方为0,并读入数据

1
2
3
4
5
6
7
   ________________________________________________________________________________
| 'A' * offset |com_gadget | com_gadget | p_rbp_ret | base_stage | leave_ret |
|--------------|--------------|------------|------------|------------|------------|
| 溢出填充 |将link_map地 |读入数据写入 | 以写入地址恢复rbp,构造假的栈帧,返回 |
| |址偏移0x1c8的 |bss区偏移 | |
| |地方置0 |0x400位置 | |
|--------------|--------------|------------|--------------------------------------|

payload3: 构造假的表项,注意函数通过寄存器传参

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
___        __________________
| | "BBBBBBBB" | 栈顶地址
| |------------------|
| | pop_rdi_ret | 函数的参数入rdi寄存器
| |------------------|
| | addr_shell | "/bin/sh" 字符串的地址
| |------------------|
| | PLT[0] | PLT表基址,即调用_dl_runtime_resolve函数的入口
| |------------------|
| | reloc_index | 重定位偏移,即相对于重定位表(.rel.plt)的索引
| |------------------|
| | reloc_align | reloc_arg值为重定位表的索引,需要对齐
| |------------------|
180Bytes | r_offset | 假的重定位表项: r_offset ,即函数的got
| |------------------|
| | r_info | 假的重定位表项: r_info
| |------------------|
| | sym_align | 由于动态链接符号表字节对齐,因此需要对齐字节
| |------------------|
| |st_name,st_info...| 假的动态链接符号表表项: st_name、st_info、st_other、st_shndx(32+8+8+16=64bits)
| |------------------|
| | st_size | 假的动态链接符号表表项: st_size(64bits)
| |------------------|
| | st_value | 假的动态链接符号表表项: st_value(64bits)
| |------------------|
| | "system" | 假的动态链接字符串表表相: 字符串如:"system""write"等等
| |------------------|
| |'AAAA....' | 填充字符: 'A',使长度达到180字节
--- |------------------|
| | "/bin/sh" | 写入的参数字符串:"/bin/sh"
200Bytes |------------------|
| | 'AAAA....' | 填充字符: 'A'
_|_ |__________________|

0x03. 参考文献

1. 通过ELF动态装载构造ROP链(Return-to-dl-resolve)
2. ROP之return to dl-resolve