参考天正师傅的博客学习了下^^。

ret2libc这种攻击方式主要是针对动态链接(Dynamic linking) 编译的程序,因为正常情况下是无法在程序中找到像 system() 、execve() 这种系统级函数(如果程序中直接包含了这种函数就可以直接控制返回地址指向他们,而不用通过这种麻烦的方式)。

Q:什么是动态链接?

  • 动态链接是指在程序装载时通过动态链接器将程序所需的所有动态链接(Dynamic linking library) 装载至进程空间中( 程序按照模块拆分成各个相对独立的部分),当程序运行时才将他们链接在一起形成一个完整程序的过程。
  • 它诞生的最主要的的原因就是静态链接太过于浪费内存和磁盘的空间,并且现在的软件开发都是模块化开发,不同的模块都是由不同的厂家开发,在静态链接的情况下,一旦其中某一模块发生改变就会导致整个软件都需要重新编译,而通过动态链接的方式就推迟这个链接过程到了程序运行时进行。
  • 动态链接的程序在运行时会根据自己所依赖的动态链接库,通过动态链接器将他们加载至内存中,并在此时将他们链接成一个完整的程序。Linux系统中,ELF动态链接文件被称为动态共享对象(Dynamic Shared Objects),简称共享对象。一般都是以 “.so” 为扩展名的文件,在pwn中我们常称之为libc库;在windows系统中就是常常软件报错缺少xxx.dll文件。

Q:什么是GOT表和PLT表?

  • GOT(Global Offset Table,全局偏移表)是Linux ELF文件中用于定位全局变量和函数的一个表。
  • PLT(Procedure Linkage Table,过程链接表)是Linux ELF文件中用于延迟绑定的表,即函数第一次被调用的时候才进行绑定(包括符号查找、重定位等),如果函数从来没有用到过就不进行绑定。基于延迟绑定可以大大加快程序的启动速度,特别有利于一些引用了大量函数的程序。
  • 打个比方,我发布文章把别人的段落复制到我的文章里就是静态链接,做一个超链接需要点击就属于动态链接。PLT是超链接本身,GOT是超链接中的内容。

Q:延迟绑定的基本原理?

  • 假如存在一个puts函数,这个函数在PLT中的条目为puts@plt,在GOT中的条目为puts@got,那么在第一次调用puts函数的时候,首先会跳转到PLT表,伪代码如下:
puts@plt:
    jmp puts@got
    patch puts@got
  • 这里会从PLT跳转到GOT,如果函数从来没有调用过,那么这时候GOT会跳转回PLT并调用patch puts@got,这一行代码的作用是将puts函数真正的地址填充到puts@got,然后跳转到puts函数真正的地址执行代码。当我们下次再调用puts函数的时候,执行路径就是先后跳转到puts@plt、puts@got、puts真正的地址。
  • 也就是说,PLT表和GOT表是一一对应的,GOT表中存的是函数的实际地址,而PLT表中存的是函数GOT表的地址。另外,PLT、GOT之类的地址是固定的。
  • 延时绑定是只有动态库函数在被调用时,才会地址解析和重定位工作。ret2libc需要构造两次payload。

Ret2libc题目的特点如下:

  • 在做题的时候,会给两个文件。一个是elf程序文件,另一个则是libc库文件。不过有的题目也不会给出libc库,需要我们根据函数在libc库中的偏移量来查找对应的libc库。

  • 程序中不会有system()或者bin字符串。

  • NX一般也是关闭的,没有RWX(读取、写入、执行)的位置。

  • 考虑动态链接的情况,libc库中有system()binsh字符串。

  • 由于ASLR(地址随机化),每次运行libc的位置都会有变动(一般题目默认开启ALSR)。

  • 函数地址、字符串地址、gadgets(程序中存在的程序小片段)地址不知道。

  • 由于按页分配,libc地址一定000结尾。ldd+程序查看libc地址。
    2021-07-06_151002.png

  • 不同版本的libc中,函数的偏移基本不同。虽然libc的位置在变动,但是装载时是一个整体,内部的偏移不变。例如,system()距libc起始地址不变(已知)、read函数距离libc起始地址不变(已知),system()read()之间的距离不变。如果read()的真实地址已知,就可以计算出system()的真实地址。

Q:确定libc的两种方法?

  • 方法一:在线寻找libc。(在线查询:https://libc.nullbyte.cat/)
    不同版本的libc中函数的偏移基本不同。gbd调试,run程序后,ctrl+c中断程序,使用p(print)查看函数地址,再次run,对比systemread函数的地址。
    2021-07-06_151711.png
    通过低3位可判断使用的libc版本,并得到其他函数的偏移。
    2021-07-06_151847.png
    点击匹配的版本,可以看到指定函数和其他函数的位置偏移,减掉“Difference”的地址差就可以得到想要的函数的地址:
    2021-07-06_152242.png
  • 方法二:使用LibcSearcher(线下无网络时常用)
git clone https://hub.fastgit.org/lieanu/LibcSearcher.git
cd LibSearcher
sudo python setup.py develop

简单的使用示例

from LibcSearcher import *

#第二个参数,为已泄露的实际地址,或最后12位(比如:d90),int类型
obj = LibcSearcher("fgets", 0X7ff39014bd90)
libcbase = 0X7ff39014bd90 - obj.dump("puts")         #基址
obj.dump("system")        #system 偏移
obj.dump("str_bin_sh")        #/bin/sh 偏移
system_addr = libcbase + obj.dump("system")         #system实际地址
bin_sh_addr = libcbase+obj.dump("str_bin_sh")          #/bin/sh实际地址

使用时出现报错:解决方法