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 | |--------------| 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