关于ret2dl_resolve的研究,其实我个人看的不是很懂,这里算是简单记录一下而已。
等什么时候真正看懂了,再补上吧。
网络上前辈的教程:
http://rk700.github.io/2015/08/09/return-to-dl-resolve/
http://angelboy.logdown.com/posts/283218-return-to-dl-resolve
http://pwn4.fun/2016/11/09/Return-to-dl-resolve/
https://github.com/inaz2/roputils/blob/master/roputils.py
具体的,前辈们已经介绍的很详细了,在这里,我只是赘述自己学到的,整理一下自己能简单理解的东西
ret2dl_resolve的核心原理
ret2dl-resolve的核心原理是攻击符号重定位流程,使其解析库中存在的任意函数地址,从而实现got表的劫持。
前言
为什么要设置延迟绑定
要想回答这个问题,首先我们得从动态链接说起。为了减少存储器浪费,现代操作系统支持动态链接特性。即不是在程序编译的时候就把外部的库函数编译进去,而是在运行时再把包含有对应函数的库加载到内存里。由于内存空间有限,选用函数库的组合无限,显然程序不可能在运行之前就知道自己用到的函数会在哪个地址上。
比如说对于libc.so来说,我们要求把它加载到地址0x1000处,A程序只引用了libc.so,从理论上来说这个要求不难办到。但是对于用了liba,so, libb.so, libc.so……liby.so, libz.so的B程序来说,0x1000这个地址可能就被liba.so等库占据了。因此,程序在运行时碰到了外部符号,就需要去找到它们真正的内存地址,这个过程被称为重定位。
为了安全,现代操作系统的设计要求代码所在的内存必须是不可修改的,那么诸如call read一类的指令即没办法在编译阶段直接指向read函数所在地址,又没办法在运行时修改成read函数所在地址,怎么保证CPU在运行到这行指令时能正确跳到read函数呢?这就需要got表(Global Offset Table,全局偏移表)和plt表(Procedure Linkage Table,过程链接表)进行辅助了。
在延迟加载的情况下,每个外部函数的got表都会被初始化成plt表中对应项的地址。当call指令执行时,EIP直接跳转到plt表的一个jmp,这个jmp直接指向对应的got表地址,从这个地址取值。此时这个jmp会跳到保存好的,plt表中对应项的地址,在这里把每个函数重定位过程中唯一的不同点,即一个数字入栈(本例子中write是18h,read是0,对于单个程序来说,这个数字是不变的),然后push got[1]并跳转到got[2]保存的地址。在这个地址中对函数进行了重定位,并且修改got表为真正的函数地址。当第二次调用同一个函数的时候,call仍然使EIP跳转到plt表的同一个jmp,不同的是这回从got表取值取到的是真正的地址,从而避免重复进行重定位。
进行跟进分析后,发现,主要影响重定位的,是
1 | _dl_fixup ( |
中的reloc_arg, 我们主要控制reloc_arg进行攻击。
x86
1 | gdb-peda$ x/3i read 0x80482f0 <read@plt>: jmp DWORD PTR ds:0x804970c |
在第一次调用时,jmp read@got.plt
会跳回read@plt
,这是我们已经知道的。接下来,会将参数push到栈上并跳至.got.plt+0x8
,这相当于调用以下函数:
1 | _dl_runtime_resolve(link_map, rel_offset); |
这就是重定位符号表,因为第一次调用函数的时候,并不是直接跳转到libc空间中的函数,而是在这个函数被调用了,才去把这个函数在libc的地址放到GOT表中。接下来,会通过两次push,最后跳到libc的_dl_runtime_resolve去执行。_dl_runtime_resolve的目的,是根据push 的两个参数导出函数的地址,然后放到相应的GOT表,并且调用它。
那么,我们的思路是在内存中伪造Elf32_Rel和Elf32_Sym两个结构体,并手动传递reloc_arg使其指向我们伪造的结构体,让Elf32_Sym.st_name的偏移值指向预先放在内存中的字符串system完成攻击。
x64
方法变成了覆盖 (link_map + 0x1c8) 处为 NULL, 也就是if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)
这一句.
但是link_map是在ld.so上的,因此我们需要leak,若程序没有输出函数,则无法使用这个方法.
使用ROPutils简化攻击步骤
使用通常的构造payload过程繁琐,虽然格式化,但是还是不利于我们编写。故此,我们使用roputils.py这个模块进行ret2_dl_resolve攻击
以 XMAN 2016-level3/level4 为例
1 | from roputils import * |