动态定位API函数之shellcode编写

通用shellcode编写动态定位API
背景:
如果编写的 ShellCode 采用了硬编址的方式来调用相应的API函数,这会存在操作系统的版本不一样,调用函数在内存中的地址不同而出现失败的现象。如下图在win10中就不能正常运行
动态定位API函数之shellcode编写_第1张图片

这时需要通过编写一些定位程序,让 ShellCode 能够动态定位所需要的API函数地址,从中解决ShellCode 的通用性问题。

1.上述弹窗程序中最重要的函数MessageBox,它是位于 User32.dll 这个动态链接库里,默认情况下是无法直接调用的,为了能够调用它,就需要调用 LoadLibraryA 函数来加载User32.dll模块,而 LoadLibraryA 又位于 kernel32.dll 链接库中。
有这么一个信息,就是所有的win_32程序都会加载ntdll.dll和kerner32.dll这两个最基础的动态链接库。所以只要找到 LoadLibraryA 函数,就能加载动态链接库,并调用其它的函数。
在win_32平台下定位kernel32.dll动态链接库中的API函数地址,有如下公式:
(1).首先通过段选择字FS在内存中找到当前的线程环境快TEB。

(2).TEB线程环境快偏移位置为0x30的地方存放着指向进程环境块PEB的指针。

(3).进程环境块PEB中偏移位置为0x0C的地方存放着指向PEB_LDR_DATA结构体的指针,其中,存放着已经被进程装载的动态链接库的信息。

(4).PEB_LDR_DATA结构体偏移位置为0x1C的地方存放着指向模块初始化链表的头指针InInitizationOrderModuleList.

(5).模块初始化链表InInitizationOrderModuleList中按顺序存放着PE装入运行时初始化模块信息,第一个链表结点是ntdll.dll,第二个链表结点就是kernel32.dll。

(6).找到属于kernel32.dll的结点后,在其基础上再偏移0x08就是kernel32.dll在内存中的加载基地址。

(7).从kernel32.dll的加载基址算起,偏移0x3C的地方就是其PE头。

(8).PE头偏移0x78的地方存放着指向函数导出表的指针。

(9).导出表0x1C处的指针指向存储导出函数偏移地址(RVA)的列表

导出表偏移0x20处的指针指向存储导出函数函数名的列表

函数的RVA地址和名字按照顺序存放在上述两个列表中,可以在名称列表中定位到所需的函数是第几个,然后在地址列表中找到对应的RVA

获得RVA后,再加上前面已经得到的动态链接库的加载基址,就获得了所需API此刻在内存中的虚拟地址。如下图:
动态定位API函数之shellcode编写_第2张图片

  1. 下面使用Windbg内核调试,按照公式来查找Kernel32.dll的地址
    (1) 打开Windbg,然后按【Ctrl + K】-> 选择本地(Local) ,点击确定。
    动态定位API函数之shellcode编写_第3张图片

(2) 按【Ctrl + S】
选择要加载的符号文件,否则无法进行查看
动态定位API函数之shellcode编写_第4张图片

(3) 通过段选择字FS在内存中找到当前的线程环境快TEB,输入命令!teb
动态定位API函数之shellcode编写_第5张图片

(4) 在TEB线程环境快偏移位置为0x30的地方存放着指向进程环境块PEB的指针
动态定位API函数之shellcode编写_第6张图片

(5) 进程环境块PEB中偏移位置为0x0C的地方存放着指向PEB_LDR_DATA结构体的指针,其中,存放着已经被进程装载的动态链接库的信息。
动态定位API函数之shellcode编写_第7张图片

(6) PEB_LDR_DATA结构体偏移位置为0x1C的地方存放着指向模块初始化链表的头指针InInitizationOrderModuleList.
动态定位API函数之shellcode编写_第8张图片

(7) 在模块初始化链表InInitizationOrderModuleList中按顺序存放着PE装入运行时初始化模块信息,第一个链表结点是ntdll.dll
动态定位API函数之shellcode编写_第9张图片

地址0x00191f28保存第一个链表结点的指针,解析这个链表结点,
动态定位API函数之shellcode编写_第10张图片

(8) 找到属于kernel32.dll的结点后,在其基础上再偏移0x08就是kernel32.dll在内存中的加载的基地址。
动态定位API函数之shellcode编写_第11张图片

动态定位API函数之shellcode编写_第12张图片

通过一段汇编来获取kernel32.dll 的基地址
动态定位API函数之shellcode编写_第13张图片

3.查找kernel32.dll 的导出表及函数API
(1)打开win32下的kernel32.dll文件,偏移 0x3c 的地方就是PE头
动态定位API函数之shellcode编写_第14张图片

(2) PE头偏移0x78的地方存放着指向函数导出表
动态定位API函数之shellcode编写_第15张图片
动态定位API函数之shellcode编写_第16张图片

导出表的RVA地址是0x0000262C;那么它的文件OFFSET是多少?
在LordPE先查看各个段的RVA和OFFSET:
动态定位API函数之shellcode编写_第17张图片

显然,导出表在.text区段
.data区段的RVA为0x1000,.data区段的起始Offset是0x400
RVA到Offset的转化:
Offset = 导出表RVA - 导出表所在区段的RVA + 导出表所在区段的 Offset
即:Offset=0x0262C – 0x1000 + 0x400= 0x1a2c

在LordPE计算器中计算
动态定位API函数之shellcode编写_第18张图片

在PE文件中文件偏移0x1a2c为导出表结构,一个导出表大小是 0x28个字节
动态定位API函数之shellcode编写_第19张图片

(3) 在导出表0x1C处的指针,指向存储导出函数偏移地址(RVA)的列表
导出表偏移0x20处的指针,指向存储导出函数函数名的列表
如下图是导出表的数据结构
动态定位API函数之shellcode编写_第20张图片
动态定位API函数之shellcode编写_第21张图片

如下图,在导出函数偏移地址(RVA)的列表,第一个函数在文件中的位置
动态定位API函数之shellcode编写_第22张图片

第一个导出函数在内存中的虚拟内存地址(VA)0X7C80A6E4=基地址0X7C800000 +相对虚拟地址(RVA)0X0000A6E4
动态定位API函数之shellcode编写_第23张图片

而LoadLibraryA函数在581位
动态定位API函数之shellcode编写_第24张图片

(4)函数的RVA地址和名字按照顺序存放在上述两个列表中,可以在名称列表中定位到所需的函数是第几个,然后在地址列表中找到对应的RVA
动态定位API函数之shellcode编写_第25张图片

4.shellcode 要尽可能的短小精悍,才能被大多数缓冲区容纳,这样就需要对函数名字进行 hash 处理。
在系统中搜索要使用的函数,将函数名从导出函数名表中取出进行hash运算,然后与栈中事先要搜索的函数名hash值作比较,这样就能够判定目标函数是不是要查找的函数。
(1)下面是计算三个函数的hash值:

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

动态定位API函数之shellcode编写_第26张图片

(2)综合上述,使用汇编写出一个弹窗代码

_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]	        // 用于存放后边找到的 三个函数地址
 
			// 开辟0x400大小的栈空间
			xor ebx, ebx
			mov bh, 0x04
			sub esp, ebx
 
			// 将user32.dll入栈
			mov bx, 0x3233 
			push ebx                        // 压入字符'32'
			push 0x72657375                 // 压入字符 'user'
			push esp
			xor edx, edx
 
			// 查找 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
			mov ecx, [ecx]				    // 进入链表第一个就是ntdll.dll
			mov ebp, [ecx + 0x08]			// ebp 即kernel32.dll基地址
            
			// 与 hash 的查找相关
		find_lib_funcs :
			lodsd					        // 将[esi]中的4字节 传到eax中
			cmp eax, 0x1e380a6a 			// 比较 MessageBoxA 字符串的hash值
			jne find_funcs                  // 如果不相等则继续查找
			xchg eax, ebp				    // 记录当前hash值
			call[edi - 0x8]
			xchg eax, ebp				    // 还原当前hash值 并且把exa基地址更新为 user32.dll的基地址
        
		    // 在PE文件中查找相应的API函数
		find_funcs :
			pushad					        // 保存寄存器环境
			mov eax, [ebp + 0x3c]			// 指向PE头
			mov ecx, [ebp + eax + 0x78]		// 得到导出表的指针
			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]			// 得到 PE导出表中的 函数序号列表的 相对位置
			add ebx, ebp				    // 得到 PE导出表中的 函数序号列表的 绝对位置
			mov di, [ebx + 2 * edi]			// 得到 PE导出表中的 当前函数的序号
			mov ebx, [ecx + 0x1c]			// 得到 PE导出表中的 函数地址列表的 相对位置
			add ebx, ebp				    // 得到 PE导出表中的 函数地址列表的 绝对位置
			add ebp, [ebx + 4 * edi]		// 得到 PE导出表中的 当前函数的绝对地址 
								            // 循环依次得到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 0x20343332	// 注意数据大小端问题
		    push 0x3170646D // 标题“mdp1234”
		    mov eax,esp		// 把标题赋值给 eax
		    push ebx		
		    push 0x2020206f	// 再push一个hello当做内容
		    push 0x6c6c6568 // push的时候不够了要用0x20填充为空
		    mov ecx,esp		// 把内容 hello 赋值给 ecx
		    
		    // 下面就是将MessageBox的参数压栈
		    push ebx		// messageBox 第四个参数
		    push eax		// messageBox 第三个参数
		    push ecx		// messageBox 第二个参数
		    push ebx		// messageBox 第一个参数
			
			call[edi - 0x04]			    // 调用	MessageBoxA
			push ebx
			call[edi - 0x08]			    // 调用 ExitProcess
			nop
			nop
			nop
			nop
	}

5.编译生成可执行文件以后,使用OD打开,提取出 ShellCode 的机器码
动态定位API函数之shellcode编写_第27张图片

编写代码执行提取出来的shellcode

#include
#include
unsigned 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\x32\x33\x34\x20\x68\x6D"
"\x64\x70\x31\x8B\xC4\x53\x68\x6F"
"\x20\x20\x20\x68\x68\x65\x6C\x6C"
"\x8B\xCC\x53\x50\x51\x53\xFF\x57"
"\xFC\x53\xFF\x57\xF8";
   
int main()  
    {       
        ((void(*)(void))&shellcode)();  
		return 0;
}

6.调试执行shellcode的通用性
在windows xp下运行
动态定位API函数之shellcode编写_第28张图片

在windows 10 下运行

动态定位API函数之shellcode编写_第29张图片

你可能感兴趣的:(网络安全,网络安全,安全漏洞)