点此返回首页

leeya_bug@home:~$

一个Coder,Attacker,Creator

浅谈ShellCode Windows查kernel.dll找LoadLibraryA最终加载任意dll全流程

0x00、前言

在编写自己的shellcode时,总会使用windbg调试
今天写shellcode时正好遇到了一些问题,需要调用user.dll,遂记录一下全过程

0x01、获得kernel.dll地址

众所周知,shellcode想要调用外部dll我们首先需要得到kernel.dll地址,然后根据kernel.dll PE导出表中导出函数名称表的地址遍历查询获得LoadLibraryA地址
进而继续调用LoadLibraryA(‘xxx.dll’)获得你想要调用的dll地址以调用里面的函数

如何获得kernel.dll的地址呢?根据windbg调试信息,我们得到了以下可行的构造链:

[FS寄存器]                -> TEB地址(线程环境块)
[TEB地址 + 0x30]          -> PEB地址(进程环境块)
[PEB地址 + 0x0c]          -> PEB_LDR_DATA(进程加载的模块信息)
      typedef struct _PEB_LDR_DATA{
        ....
       LIST_ENTRY InInitializationOrderModuleList;// +0x1c
      } PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24
[PEB_LDR_DATA地址 + 0x1c] -> IOM地址(模块初始化链表头指针,如上所示)
[IOM地址]                 -> ntdll.dll地址(链表第一个就是ntdll.dll)
[ntdll.dll地址 + 0x08]    -> kernel32.dll地址

代码如下:

xor edx, edx 				//清空edx
mov ebx, fs: [edx + 0x30]		
mov ecx, [ebx + 0x0c]			
mov ecx, [ecx + 0x1c]			
mov ecx, [ecx]	
mov ebp, [ecx + 0x08]

最后ebp中存放kernel32.dll地址

0x02、获得kernel.dll中PE导出表函数名称表内存虚拟地址

获得了kernel32.dll后,需要获得kernel.dll函数名称表内存虚拟地址,以查找函数名称遍历来寻找目标函数地址

[kernel.dll地址 + 0x3c]             -> 指向PE头
[kernel.dll地址 + 指向PE头 + 0x78]   -> PE导出表地址
kernel.dll地址 + PE导出表地址        -> PE导出表内存虚拟地址
[PE导出表内存虚拟地址 + 0x20]         -> 函数名称表地址
kernel.dll地址 + 导出函数名称表地址   -> 函数名称表内存虚拟地址

代码如下:

pushad 					//保存寄存器环境
mov eax, [ebp + 0x3c]
mov ecx, [ebp + eax + 0x78]
add ecx, ebp
mov ebx, [ecx + 0x20]
add ebx, ebp

ecx: kernel.dll函数表内存虚拟地址 ebx: kernel.dll函数名称表内存虚拟地址

0x02、循环遍历函数名称表虚拟地址,获得LoadLibraryA地址

获得了函数名称表虚拟地址后,需要获得LoadLibraryA地址,由于懒得写了以下部分采用伪代码 + 汇编来展开

for(int32_t i = 0; ; i ++){           // i为当前函数表中第几个函数
      mov esi, [ebx + edi * 4 + i]    // 得到当前函数名地址
      add esi, ebp                    // 得到当前函数名虚拟地址
      //esi: 当前函数名虚拟地址

      for(int32_t j = 0; ; j ++){               // j为当前函数名称中第几个字符
            movsx eax, byte ptr[esi + j]	// 得到当前函数名称 第esi的一个字母
            cmp al, ah				// 比较到达函数名最后的0没有
            jz compare_hash			// 函数名hash 计算完毕后跳到比较流程
            ror edx, 7				// 循环右移7位
            add edx, eax			// 累加得到hash
      }
      compare:
      //edx: 当前函数名hash值
      
      mv eax, 0x0c917432      	// 0x0c917432是LoadLibraryA的hash值
      cmp edx, eax		// 比较 目标函数名hash 和 当前函数名的hash
      jnz continue;		// 如果 不等于则继续遍历下一个函数

      mov ebx, [ecx + i + 0x24]		// 由函数表虚拟地址 得到 函数序号列表 相对位置
      add ebx, ebp		      	// 得到 函数序号列表的 绝对位置
      mov di, [ebx + 2 * edi]		// 得到 当前函数的序号

      mov ebx, [ecx + i + 0x1c]		// 得到 函数地址列表的 相对位置
      add ebx, ebp		      	// 得到 函数地址列表的 绝对位置
      add ebp, [ebx + 4 * edi]		// 得到 当前函数的绝对地址

      xchg eax, ebp                 	//LoadLibrary函数地址放在eax中
}

pop edi				// pushad中最后一个压入的是edi 正好是开始预留 用于存放的三个函数地址 的栈空间
push 0x00003233                 
push 0x72657375                 
push esp                        // "user32"
stosd				// 把找到函数地址出入 edi对应的栈空间
push edi    			// 继续压栈 平衡栈
popad				// 还原环境

现在基本上就结束了,只需要call[edi - 0x8]即可

call[edi - 0x8]

调用结束后eax中存放user32.dll地址

0x03、调用user.dll MessageBox全流程

调用user.dll MessageBox全流程代码,以下使用内联汇编程序演示:

#pragma comment(linker,"/SECTION:.data,RWE")
#include <iostream>
#include <windows.h>

void main() {
	__asm {
		nop
		nop	// 内存中占位方便观察
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		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 		// 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:					      
		      	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值的比较
            	compare_hash :
		      	cmp edx, [esp + 0x1c]			// 比较 目标函数名hash 和 当前函数名的hash
			jnz next_func_loop			// 如果 不等于 继续下一个函数名
			mov ebx, [ecx + 0x24]			// 得到 函数序号列表的 相对位置
			add ebx, ebp				// 得到 函数序号列表的 绝对位置
			mov di, [ebx + 2 * edi]			// 得到 当前函数的序号
			mov ebx, [ecx + 0x1c]			// 得到 函数地址列表的 相对位置
			add ebx, ebp				// 得到 函数地址列表的 绝对位置
			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			    
			jne find_lib_funcs

		// 下方的代码,就是弹窗
            	func_call :
		      	xor ebx, ebx		// 将 ebx 清0
			push ebx
			push 0x20202067
			push 0x75625f61
			push 0x7965656c
			mov eax, esp		// "leeya_bug"
			push ebx
			push 0x2020206b		
			push 0x6f207473
			push 0x65742067
			push 0x75625f61	
			push 0x7965656c 
			mov ecx, esp		// "leeya_bug test ok"

			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
			nop
			nop
			nop
			nop
			nop
			nop
			nop
			nop
			nop
			nop
			nop
			nop
			nop
	}
}