红联Linux门户
Linux帮助

Linux和Windows堆栈溢出利用对比

发布时间:2015-09-07 09:44:23来源:不急不躁作者:tan6600

堆栈溢出的利用,有三大条件,一是返回点的定位,二是ShellCode的编写,三是跳转到ShellCode。


1.返回点的定位

在Windows下,F.ZH的在它经典的《菜鸟版Exploit编写指南之一》的文章中,详细的讲述了利用报错对话框精确定位溢出返回点的方法。
Windows下弹出报错对话框,显示“0x74737271”指令引用“0x74737271”内存,该地址不能为“read”。

里面隐含的意思就是返回点EIP被覆盖成了“0x74737271”,去执行的时候该地址是不能读的,就有错误了。“0x74737271”是什么呢?前面说了,就是字符串“qrst”,离我们填充的第一个字符“a”正好距离是“q”–“a”=0x71 – 0x61=0x10=16,所以在报错对话框的帮助下,我们可以轻松的知道,在覆盖了16个字符之后,就到达返回点的位置了。

在知道了返回点EIP的位置后,我们就可以覆盖返回点为任意值,让程序运行到一个错误的地方,从而出现报错提示。那如果我们特意把EIP覆盖成我们想去的程序的地方呢?当然就可以运行我们想要的程序了,而一般的,我们想要的程序称为ShellCode!看到胜利的曙光了吧?!


2.ShellCode的编写方法

Shell最先指人机交互的界面,而这里Shellcode不仅仅指交互,它还指可以实现任意功能的代码,可以涉及到很多方面。我们想要的程序功能最好是能够开一个DOS窗口,那我们可以做很多事情了。

在Windows下,开DOS窗口的程序如下:

#include<Windows.h>
int main()
{
LoadLibrary(“msvcrt.dll”);
system(“command.com”);
return 0;
}

Windows下执行一个Command.com就可以获得一个DOS窗口,在C库函数里面,语句System(“command.com”);将完成我们需要的功能。Windows是通过动态链接库DLL来提供系统函数的。System函数由Msvcrt.dll(The Microsoft VIsual C++ Runtime library)提供,所以要想执行System,我们必须首先使用LoadLibrary(“msvcrt.dll”);装载动态链接库,svcrt.dll之后才能调用System函数。
我们执行上面的代码,看看效果吧:弹出来了一个DOS对话框!可以执行DIR,COPY等任意DOS命令。

我们要得到机器码形式的ShellCode,可以在VC中按F10调试,然后在Debug工具栏中,点最后一个按钮Disassemble,这样出现了源程序的汇编代码,再在代码窗口上点右键,在弹出菜单中选择Code Bytes,这样就出现了源程序的机器码。

原理上我们抄下来就可以了,但一般情况下,需要用汇编优化一下,并且处理好调用函数地址的问题,这样我们就可以得到Windows2000 SPN下,开DOS窗口的ShellCode了,没你想象中那么神秘吧?

代码:

char shellcode[] =
{0x8B,0xE5, 0x55,0x8B,0xEC,0x83,0xEC,0x0C,0xB8,0x63,0x6F,0x6D,0x6D,6D,6D,6F,63,0x89,0x45,0xF4,0xB8,
0x61,0x6E,0x64,0x2E,0x89,0x45,0xF8,0xB8,0x63,0x6F,0x6D,0x22,0x89,0x45,0xFC, 0x33,0xD2, 0x88,0x55,0xFF,
0x8D,0x45,0xF4, 0x50, 0xB8,0x24,0x98,0x01,0x78, 0xFF,0xD0 };

把它构造成可以实战使用的ShellCode还有很多问题要解决,比如通用性、编码、功能等,需要很高深的技术功底,但在本文这就不是重点了,也就不深入下去了。
我们的重点还是放在对比上吧,开一个Shell窗口的Linux代码如下:
#include

char ShellCode[] =
{
"/x31/xc0" // xor %eax,%eax
"/x50" // push %eax
"/x68/x2f/x2f/x73/x68" // push $0x68732f2f
"/x68/x2f/x62/x69/x6e" // push $0x6e69622f
"/x89/xe3" // mov %esp,%ebx
"/x8d/x54/x24/x08" // lea 0x8(%esp,1),%edx
"/x50" // push %eax
"/x53" // push %ebx
"/x8d/x0c/x24" // lea (%esp,1),%ecx
"/xb0/x0b" // mov $0xb,%al
"/xcd/x80" // int $0x80
}

这就是Windows和Linux下ShellCode的来源。在实际编写Exploit过程中,Windows和Linux都有很多现成的ShellCode,我们直接拿来使用就可以了,但明白了原理总要好很多,如果有需要,可以自己改改功能,变换变换形式以符合编码要求,以后我们有机会再讨论,这涉及到很多有意思的东西。


3.Linux和Windows跳转到ShellCode方法对比

现在分析一下我们现在拥有的资源:
1.我们知道有问题程序返回点的精确位置,意思就是我们可以把它覆盖成任意地址,让计算机执行这个地址的代码。
2.我们有了ShellCode,一个可以给我们提供DOS窗口或新Shell的代码。
3.那接下来……就应该是把程序的返回点地址覆盖为ShellCode的地址了,这样在程序返回时,就可以执行我们的ShellCode了,意思就是我们就能得到一个Shell了!
分别看看Windows和Linux是如何实现跳转的吧。

在Windows下,ShellCode的地址一般最高位是0x00,这样就会把覆盖的字符串截断。所以以前很多人都提出了很多办法来定位ShellCode,但都不精确。随着技术的发展,1999年Dark Spyrit AKA Barnaby Jack提出了一个天才的想法:使用系统核心DLL里的指令来完成跳转!这一技巧开创了一个崭新的Windows缓冲区溢出的思路,并一直沿用到今天!

Windows的系统核心DLL包括Kernel32.dll、User32.dll、Gdi32.dll,这些DLL是一直位于内存中而且对应于固定的版本Windows加载的位置是固定的,而且最高位一般不是0x00。我们用系统核心DLL中的Jmp esp来覆盖返回地址,而把ShellCode紧跟在后面,这样就可以完成跳转到我们的ShellCode中的要求了。

其实现跳转的原理是这样的:当程序返回前,ESP指向原EIP的地方,函数返回就是执行RET=Pop EIP,这样EIP就是Jmp esp地址;而ESP会往下移,指向ShellCode的第一个字节。

计算机不知道我们做了手脚,继续往下执行:EIP指向的指令Jmp esp,而此时的ESP正好指向了我们的ShellCode,那当然下一步就是执行我们的ShellCode了!这样间接的完成了向我们ShellCode的跳转。

我们在程序中把Name赋成这样的值,它看起来很奇怪,但会得到很奇妙的结果。我们执行它就会弹出我们想要的DOS对话框啦,哈哈!

写Main函数的程序员,他只会去负责读Name并赋给Output数组,根本不会感觉到Name数组中会隐藏这样精心构造的恶意代码,对于任何的Name,他都会把它读入并作为正常的东西处理,但就有可能会出现他想不到的结果。

再看看Linux下的跳转吧。对于Linux来说,程序中数组的地址最高位不是0x00,那要实现跳转就简单多了。我们定义一个ShellCode数组,把ShellCode的地址读出来,直接把返回点覆盖成地址就可以了!

我们先读出ShellCode数组的地址。在程序中添上ShellCode数组的定义,并打印出它的地址,即Printf(“%p/n”,shellcode);如下。

#include<stdio.h>
#include<string.h>
char shellcode[] = “12345”;
char name[] = "ww0830";
int main()
{
char output[12];
strcpy(output, name);
printf("%s/n",output);
printf(“%p/n”,shellcode);
return 0;
}

执行后,程序打印出ShellCode的地址,在我的机器上为0x8049580,哈哈,果然没有0x00的字节啊!这样我们得到了在Linux下,Name数组的构造结构。

然后把ShellCode数组替换成真正打开Shell的代码即可。


Linux下Tomcat内存溢出:http://www.linuxdiyf.com/linux/10123.html

Linux下用jmap命令进行堆栈转储:http://www.linuxdiyf.com/linux/2285.html