高级ROP链构造方法(二)-- ret2dl_runtime_resovle
前面已经讲完了PLT和GOT表的作用,在那儿我留了一手,并没有讲清楚
_dl_runtime_resolve_这个函数具体实现,还记得当时程序往栈上push了两个参数就进入_dl_runtime_resolve_了吗?不记得可以回去翻下
我们回忆一下当时那两个参数是什么,没错,一个是0,一个是0x804a004,这里先讲他们是啥,0呢,我们先理解成一个Index,而0x804a004是一个函数地址叫link_map,其实_dl_runtime_resolve_是通过link_map来找到.dynamic的
而.dynamic是什么呢?我们随便拉一个程序进IDA来看,我们可以看到里面有一些结构体,其实和_dl_runtime_resolve_紧密相关的只有三个:DT_STRTAB, DT_SYMTAB,DT_JMPREL这三个根据地址我们可以发现它们指向了.dynstr,.dynsym,.rel.plt
link_map+0x8的位置就是它要找的.dynamic,接下来我们再通过.dynamic找到.dynstr, .dynsym,`.rel.plt
.dynstr 的地址是 .dynamic + 0x44 -> 0x0804821c
.dynsym 的地址是 .dynamic + 0x4c -> 0x080481cc
.rel.plt 的地址是 .dynamic + 0x84 -> 0x08048298
我们看上面的IDA也是这个地址
到这里另外一个push到栈上的参数就起作用了
将.rel.plt 的地址加上参数 reloc_arg,即0x8048298+0x0 = 0x8048298
这里就是重定位表项,这里也是一个结构体,将r_info>>8,即0x00000107>>8 = 1作为.dynsym中的下标
1 | typedef struct |
1 | typedef struct |
.dynstr + st_name就是这个函数的符号名字符串
0x0804821c+0x1a = 0x8048236
IDA上的字符串表
最后在动态链接库查找这个函数的地址,并且把地址赋值给rel->r_offset,即GOT表就可以了
前面已经讲解完了_dl_runtime_resolve_函数的执行流程,下面从源码层面来解析,不关键的代码已经删除
在进入_dl_runtime_resolve_函数之后它又call _dl_fixup_
下面是_dl_fixup_的源码
1 | _dl_fixup (struct link_map *l, ElfW(Word) reloc_arg) |
通过讲解_dl_runtime_resolve_函数,我们也很容易的想到利用点:
1.通过控制reloc_arg来实现伪造最终获取的函数字符串
2.修改.dynamic内存区域实现伪造最终获取的函数字符串
3.伪造link_map的地址
用一道经典例题讲解第一种情况
XDCTF2015_bof
先看看保护,我们看到开启了Partial RELRO,所以咱修改不了.dynamic,另外还开启了NX保护,也写不了shellcode到栈上
程序的流程十分的简单,打印出Welcome to XDCTF2015~!,紧接着就是一个溢出函数
我们按照正常的思路走一遍,一般拿到栈溢出,我们首先就是看有无libc,这题没有给出,然后就是后门函数,system( )函数,/bin/sh字符串,很遗憾都没有,看到这一般都会想到ret2libc,但是当我去查看ROPgadget的时候,发现这个动态链接的文件它的gadget少的离谱,所以ret2libc这条路也走不通了,那只能没有轮子咱们造一个轮子了—-ret2dl_runtime_resovle
先通过cyclic计算一下溢出的偏移位置
从源码中也可以看出它并没有对reloc_arg参数进行边界校验,导致我们即使传进去的reloc_arg再大,它也不会报错,所以我们就传入一个大的reloc_arg,将重定位表劫持到一个我们可控的地方,比如.bss段,所以很容易就可以想到,通过栈迁移将栈迁移到.bss段
根据上面的源码,我们可以知道,要构造的有3样东西.rel.plt,.dynstr,.dynsym
我们一步步来,先伪造.rel.plt,.rel.plt有两个结构体成员,第一个是r_offset,为函数的GOT表地址,我们将其填写成write的GOT表地址,第二个是r_info,这个就比较重要了,我们先来回忆一下它是怎么被索引到的
1 | .rel.plt + reloc_arg = r_offset |
我们刚刚说到源码中没有对reloc_arg参数进行边界校验,那我们就可以伪造r_offset和r_info到一个我们可控的地方
1 | .rel.plt + fake_arg = fake_r_offset |
代码如下,我们先不关注(fake_sym_addr - dynsym)/16是什么我们先留个印象,这里为什么要加上7呢?因为要绕过这个assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
1 | relo_offset = elf.got['write'] |
接下来就是.dynsym,它怎么索引到.dynsym的呢,是不是relo_info >> 8 作为.dynsym中的下标,所以我们要在relo_info上做手脚,通过(fake_sym_addr - dynsym)/16来让它索引到fake_dynsym,除于16是因为一个Elf32_Sym大小是16字节,这里还要注意的是要进行对齐!!!
代码如下:
1 | fake_sym_addr = bss_addr + 36 |
最后就是.dynstr了,这个就很简单了,只需要构造st_name让他找到我们伪造的字符串,它原来是通过.dynstr + st_name来索引的:
1 | .dynstr + st_name = func_str |
代码:
1 | fake_str_addr = fake_sym_addr + 16 |
完整EXP:
1 |
|

