巧绕NX与ascii armoring-ret2plt

 

前面讲完了plt、got,我们来个相关的。

前言

前排广告位招租
最近忙于乱七八糟的事,导致没时间更博客。除了开发方面,bin方面也需要多多努力,避免被别人远远甩在后面


ret2plt

ascii armoring

为了针对1997年的黑客提出的 return-to-libc 技术,ascii morning被提出,以将libc的函数地址的第一个字节进行零化的方式阻止了system地址被写入栈中导致程序流被劫持到了libc中奇怪的地方,从而拿到shell。

零字节可以截断字符串使黑客无法将system地址写入栈。

思路

还记得我们是怎么写shellcode的嘛,就是千方百计地通过各种方式搞出零字节。这次我们也要使用这种精神。

为了对抗ascii armoring我们不再使用libc中的函数,我们使用plt中的strcpy一点点的把system的地址拼接出来,写入plt表中的某个函数的空间中。最后再劫持程序流到这个空间中去,从而拿到shell。

这就有一个问题,我们都知道开了NX之后程序不再可以在栈中执行shellcode,这让需要执行这么多strcpy的我们感觉很麻烦。因此我们通过一种被称为 PPR 的技术,这样子我们就可以连续地执行多个函数。

PPR由两个pop、和一个ret组成,专门负责清空两个已经用过的参数(plt表中需要写入的东西与需要被写入地system地址的一部分)并且用ret返回到下一个参数中去。

假定有

(堆栈中的内容)
strcpy@plt
system[0]
xxx@plt[0]
PPR的地址
strcpy@plt <--------EIP指向这里
(某处)
pop eax    <--------PPR的地址
pop eax
ret

我用文字来描述一般

  1. 程序流走向了strcpy@plt这里后把PPR的地址当成函数的返回地址并把system[0]和xxx@plt[0]当成了函数的两个两个参数运行
  2. 执行完了strcpy后ret返回了某处的PPR源码中,然后通过两个pop eax将两个参数从栈中清除出去(通过增加了esp的值到了两个参数下面),然后通过ret,取出下一个strcpy的地址,并控制程序流到了那里。
  3. 返回第一过程。

这样子我们就通过预先的栈溢出来控制程序的连续走向不同的plt表中的函数,来达成一个非常复杂的功能,而不是像ret-libc那样只能达成一个单独的函数的功能

PPR技术主要是为了清除参数,在其他情况中如果没有参数则不需要PPR技术只需把下一个函数的地址填入返回地址中

过程

payload

我们需要从栈中注入如上图大小的块

但是我们还有几个技术问题没有解决

  1. PPR的代码放在哪里?
  2. strcpy在plt表中的位置
  3. system的地址在哪里?
  4. 放在plt表的哪里
  5. /bin/bash放在那里

我们按照一步步流程来

对准EIP

这个打开ida可以解决问题或者用我们的gdb调一调也可以解决。

找到strcpy的位置

可以通过ida来反编译函数来获得地址(没开aslr的情况)。

找到PPR的地址

我们唯一可以控制的空间只有栈和plt表,而对后者的控制是建立在PPR的地址已经找到了上。那我们就不能自由的注入PPR了。索性这种平衡栈帧的代码非常(并不)常见。我们去可执行段里随便找一个,然后拿来用。

system的拷贝

由于ASCII armoring机制,system的地址含有零字节,造成strcpy拷贝结束,达不到预期的攻击效果。攻击者找到4个地址空间,它的首字节分别是system地址的第一个byte, 第二个byte,第三个byte和第四个byte,然后一个个byte拷贝,将这4个byte拼凑到GOT里面。从而绕过直接拷贝system地址造成失败。

时下最流行的ubantu没有这个特性哦。

我们强行逆一波,然后在内存里找到,然后在记在小本本上。

需要注意,虽然说我们都是经验并不丰富的攻击者,但是还需要特别注意一下,需要找的是内存中存放的字节,而不是字节的ascii码。

也可以使用find命令在内存中找。为了精确定位,我们应当在内存镜像中寻找以避免地址在加载中偏移,形如find /b 0xaaaaaaaa, 0xbbbbbbbb, 0xccgdb命令可以在a…到b…的空间内写入cc。

内存镜像的工作原理与硬盘的热备份类似,内存镜像是将内存数据做两个拷贝,分别放在主内存和镜像内存中。

写入哪个plt表空间为好呢?

看了我前文的内容你是否有疑惑,我用的strcpy并不是一个字符拷贝函数,而是字符串拷贝函数,也就是说这四个字符的位置可能会溢出,盖掉其他的plt表项,其中可能就包括strcpy。这就让我们需要选一个trycpy前面的,我就随便选个puts吧。

逆一波程序就可以拿到puts的地址了。

本文使用了puts作为目标地址,你当然可以选择其他的。

“\bin\bash”的地址

同样的,为了精确攻击,我们最好找一个现成的。

Linux里面有个shell环境变量,表示前使用哪个shell,它的值通常是”/bin/bash”,如下:

$ env | grep -i shell
SHELL=/bin/bash

每个进程的环境变量都保存在主线程的栈上,因此可以在主线程栈空间上找到该字符串。由于本文的代码为单线程 ,因此可以沿着esp地址往上找即可:

(gdb) x/1000s $esp
…
0xffffd8b4: "/home/ivan/exploit/stack4"
0xffffd8ce: "SHELL=/bin/bash"
0xffffd8de: "TERM=xterm"

环境变量也是作为参数传进主程序的。

攻击向量

根据上面的方法我们可以得出我们需要注入的内容。

A*x
strcpy@plt + PPR + puts@got[0] + addr of system[0]
strcpy@plt + PPR + puts@got[1] + addr of system[1]
strcpy@plt + PPR + puts@got[2] + addr of system[2]
strcpy@plt + PPR + puts@got[3] + addr of system[3]
puts@plt + exit + addr of “/bin/bash”

局限

  1. 需要确切的地址难以对抗aslr防护。
  2. 需要过于多的条件,难以满足多变的情况。
  3. 在内存中需要寻找过于多的地址。
  4. 需要注入plt表的值,在64位天然零化的情况下难以为继
  5. 与ROP技术相比,不具备完备性,局限性太强

正因为许许多多的漏洞导致了ret2plt并没有流行起来。