前言
前排广告位招租
最近忙于乱七八糟的事,导致没时间更博客。除了开发方面,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
我用文字来描述一般
- 程序流走向了strcpy@plt这里后把PPR的地址当成函数的返回地址并把system[0]和xxx@plt[0]当成了函数的两个两个参数运行
- 执行完了strcpy后
ret
返回了某处的PPR源码中,然后通过两个pop eax
将两个参数从栈中清除出去(通过增加了esp的值到了两个参数下面),然后通过ret
,取出下一个strcpy的地址,并控制程序流到了那里。 - 返回第一过程。
这样子我们就通过预先的栈溢出来控制程序的连续走向不同的plt表中的函数,来达成一个非常复杂的功能,而不是像ret-libc那样只能达成一个单独的函数的功能
PPR技术主要是为了清除参数,在其他情况中如果没有参数则不需要PPR技术只需把下一个函数的地址填入返回地址中
过程
payload我们需要从栈中注入如上图大小的块
但是我们还有几个技术问题没有解决
- PPR的代码放在哪里?
- strcpy在plt表中的位置
- system的地址在哪里?
- 放在plt表的哪里
- /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, 0xcc
的gdb命令可以在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”
局限
- 需要确切的地址难以对抗aslr防护。
- 需要过于多的条件,难以满足多变的情况。
- 在内存中需要寻找过于多的地址。
- 需要注入plt表的值,在64位天然零化的情况下难以为继
- 与ROP技术相比,不具备完备性,局限性太强
正因为许许多多的漏洞导致了ret2plt并没有流行起来。
网友评论