1.常规checksec,开了Partial RELRO和NX,IDA找漏洞,很明显在vulnerable_function函数中存在栈溢出:
2.很多种方法,这里选择用ret2dl-resolve来尝试解决。
3.关于ret2dl-resolve介绍,篇幅太长,不说了,后面放资料链接,介绍一下装载流程:
(1)通过struct link_map *l获得.dynsym、.dynstr、.rel.plt地址
(2)通过reloc_arg+.rel.plt地址取得函数对应的Elf32_Rel指针,记作reloc
(3)通过reloc->r_info和.dynsym地址取得函数对应的Elf32_Sym指针,记作sym
(4)检查r_info最低位是否为7
(5)检查(sym->st_other)&0x03是否为0
(6)通过strtab+(sym->st_name)获得函数对应的字符串,进行查找,找到后赋值给rel_addr,最后调用这个函数。
4.首先思考exp编写的攻击思路,由于栈溢出的比较少,而ret2dl-resolve攻击需要构造几个结构体,所占空间较大,所以这里进行栈劫持,将栈劫持到程序运行过程中生成的bss段上。之后再在栈上布置结构体和必要数据,重新执行延迟绑定,劫持动态装载,将write函数装载成system函数,并且在劫持的同时将Binsh字符串放在栈上,这样劫持完成后就直接调用system函数,参数就是binsh。
(1)首先需要找到相关的数据地址:
这里寻找的relplt_addr,dynsym_addr,dynstr_addr一般都是位于ELF文件头部的LOAD段。用readelf -S binary也可以看到:
①relplt_addr:0x080482b0
②dynsym_addr:0x080481cc
③dynstr_addr:0x0804822c
(2)再进行栈劫持:
(3)伪造两个结构体和必要的数据:
①reloc_arg作用:作为偏移值,与relplt_addr相加得到ELF32_Rel结构体的地址。这里设置成fake_reloc_arg = fake_Elf32_Rel_addr - relplt_addr,那么相加之后就可以直达我们设置的fake_ELF32_Rel结构体位置。
②st_name_addr作用:作为偏移值,与dynstr_addr相加得到存放函数名的地址。如果按照原本的重定位,那么此处计算之后存放的应该是write,所以这里将其改为system,放在所有数据的最后面,将地址存放到fake_Elf32_Sym结构体中,设置为fake_st_name_addr = new_stack_addr + 0x6c - dynstr_addr,这样相加之后就会定位到new_stack_addr + 0x6c,即我们劫持栈上的system字符串地址处,从而劫持装载。
③r_info作用:作为偏移值,使得结构体数组Elf32_Sym[r_info>>8]来找到存放write的结构体Elf32_Sym。我们知道结构体数组寻址方式其实就是addr = head_addr + size*indx,也就是首地址加上数组中元素大小乘以索引。这里由于伪造了Elf32_Sym结构体,所以我们的r_info>>8 = indx应该是addr - head_addr/size,对应的就是(fake_Elf32_Sym_addr - dynsym_addr)/0x10,得到最终的r_info为((fake_Elf32_Sym_addr - dynsym_addr)/0x10)<<8。
④设置write的Elf32_Sym结构体时,可以通过命令readelf -r binary,找到write的r_info偏移为407:
之后输入objdump -s -j .dynsym level3,查找偏移为4的Elf32_Sym结构体内容:
这里就是0x804820c,对应的内容为:
▲其实在IDA中看的更清楚:
同时由于搜寻数据时,需要查找类型R_386_JUMP_SLOT,该索引为r_info的最后一个字节,所以需要将r_info的最后一个字节设置为0x07,来通过检查。
▲以下为两个结构体内容:
(4)将伪造的结构体和必要数据放在bss新栈上,从plt0_addr开始执行,调用write函数,重新装载write函数,劫持成system函数,同时修改参数为binsh,直接getshell。
▲不同版本的libc也不太一样,在libc2.27及以下试过都行,但2.30及以上好像就不可以,可能版本改了多了一些检查吧。
1.完全一样,只是程序改成了64位,在64位条件下有些发生了变化:
(1)两大结构体发生变化:
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)