利用shellcode来实现API的自动定位

代码附详细解析

#include<stdio.h>

void main()
{
    
    
	_asm{
    
    
		CLD//CF标志位清0,防止影响跳转
		push 0x1e380a6a
		push 0x4fd18963
		push 0x0c917432//保存MassageBox,ExitProcess,LoadLibrary
		//的hash值
		mov esi,esp//保存栈顶的值
		lea edi,[esi - 0xc]

		xor ebx,ebx
		mov bh,0x04
		sub esp,ebx

		mov bx,0x3233//32
		push ebx
		push 0x72657375//user
		push esp
		xor edx,edx//把user32放入栈

		mov ebx,fs:[edx + 0x30]//FS偏移0x30的位置存放着指PEB表的指针
		mov ecx,[ebx+ 0x0c]//PEB表偏移0x0c处是 _PEB_LDR_DATA 进程
		//加载模块链表
		mov ecx,[ecx + 0x1c]//得到InInitializationOrderModuleList
		//的地址
		mov ecx,[ecx]
		mov ebp,[ecx + 0x08]//第一个是EXE模块本身的ImageBase,第
		//二个是NTDLL.DLL,第三个是KERNEL32.DLL。
		
find_lib_functions:
		lodsd//把栈顶的值导入eax中
		cmp eax,0x1e380a6a//比较MassageBox的hash值
		jne find_functions
		xchg eax,ebp
		call [edi - 0x8]//LoadLibrary(user32)
		xchg eax,ebp//交换再交换回来

find_functions:
		pushad
		mov eax,[ebp + 0x3c]//e_lfanew
		mov ecx,[ebp + eax + 0x78]//导出表位于dll中的位置
		add ecx,ebp//加上dll位于文件中导入地址得到user32位
		//于该文件中的导出表的位置
		mov ebx,[ecx + 0x20]
		add ebx,ebp//根据EXPORT_DIRECTORY,找到位于里面的导出函数名称表
		xor edi,edi

next_function_loop:
		inc edi//edi = 1
		mov esi,[ebx + edi * 4]
		add esi,ebp//遍历名称
		cdq//扩展成64位符号数

hash_loop:
		movsx eax,byte ptr[esi]//取每个字符的ASCII码
		cmp al,ah//eax中低八位和高八位一样就跳转,由于高八位一定为0
		//所以就是要把这个名称跑完
		jz compare_hash
		ror edx,7//循环右移七位
		add edx,eax//再加上eax的值
		inc esi//移到下一个字节,hash算法
		jmp hash_loop

compare_hash:
		cmp edx,[esp + 0x1c]//跟之前存的hash值比较,pushad压栈进来的
		//hash值作比较,不对就继续找
		jnz next_function_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]//根据序号找到对应的函数地址
		xchg eax,ebp
		pop edi
		stosd
		push edi
		popad
		cmp eax,0x1e380a6a//判断是否所有的API地址都找到了
		jne find_lib_functions


		xor ebx,ebx
		push ebx
		push 0x74736577
		push 0x6c696166
		mov eax,esp
		push ebx
		push eax
		push eax
		push ebx
		call [edi - 0x04]
		push ebx
		call [edi - 0x08]//弹出窗口并退出          
		nop
		nop
		nop
		nop
		nop
	}
}

拖入OD中把shellcode转换成机器码(太麻烦了就不做了)

在这里插入图片描述

思考

但是这串代码寻找KERNEL32.DLL的方法是直接mov ebp,[ecx + 0x08],根据偏移量直接找到。Ldr->InInitializationModuleList链表在win7后把KERNEL32.dll移到了第四位,因此没有办法保证shellcode的通用性。
保险一点感觉可以选用逐字的比对的方法。再把dll的名称也遍历一遍。

mov edx,ecx
cmp byte ptr [edx],'K'

类似这种对比验证。
同样该种方法也不能避免hash碰撞,如果遍历的函数多了,很有可能会发生这种情况,以后有时间再来优化这个shellcode吧。

附:LDR_DATA结构

typedef struct _PEB_LDR_DATA
{
    
    
 ULONG Length; // +0x00
 BOOLEAN Initialized; // +0x04
 PVOID SsHandle; // +0x08
 LIST_ENTRY InLoadOrderModuleList; // +0x0c
 LIST_ENTRY InMemoryOrderModuleList; // +0x14
 LIST_ENTRY InInitializationOrderModuleList;// +0x1c
} PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24

EXPORT_DIRECTORY结构:

typedef struct _IMAGE_EXPORT_DIRECTORY {
    
    
    DWORD   Characteristics;    //未使用
    DWORD   TimeDateStamp;      //时间戳
    WORD    MajorVersion;       //未使用
    WORD    MinorVersion;       //未使用
    DWORD   Name;               //指向改导出表文件名字符串
    DWORD   Base;               //导出表的起始序号
    DWORD   NumberOfFunctions;  //导出函数的个数(更准确来说是AddressOfFunctions的元素数,而不是函数个数)
    DWORD   NumberOfNames;      //以函数名字导出的函数个数
    DWORD   AddressOfFunctions;     //导出函数地址表RVA:存储所有导出函数地址(表元素宽度为4,总大小NumberOfFunctions * 4)
    DWORD   AddressOfNames;         //导出函数名称表RVA:存储函数名字符串所在的地址(表元素宽度为4,总大小为NumberOfNames * 4)
    DWORD   AddressOfNameOrdinals;  //导出函数序号表RVA:存储函数序号(表元素宽度为2,总大小为NumberOfNames * 2)
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

猜你喜欢

转载自blog.csdn.net/Misaka10046/article/details/108630885