您的位置:首页 > 其它

hash扫描获得api函数地址学习笔记

2013-09-15 11:33 351 查看
原文:http://www.pediy.com/kssd/index.html -- 病毒技术 -- 病毒知识 -- Anti Virus专题

搜索获得api函数地址的实现

我们的程序能正常的调用函数。那么这个动态链接库是如何输出函数来供我们的用户程序调用呢?它实际上是采用输出表结构来描述本dll需要导出哪些函数来供其他的程序调用,这样其他的用户程序才能正常的调用此动态链接库的输出函数.

导出表结构:

IMAGE_EXPORT_DIRECTORY struct
Characteristics		DWORD	?	; 未使用,总是为0
TimeDateStamp		DWORD 	?	; 文件的产生时刻
MajorVersion		WORD	?	; 未使用,总是为0
MinorVersion		WORD	?	; 未使用,总是为0
nName			DWORD	?	; 指向文件名的RVA
nBase			DWORD 	? 	; 导出函数的起始序号
NumberOfFunctions	DWORD	?	; 导出函数的总数
NumberOfNames		DWORD 	?	; 以名称导出的函数总数
AddressOfFunctions	DWORD	?	; 指向导出函数地址表的RVA
AddressOfNames		DWORD 	?	; 指向函数名地址表的RVA
AddressOfNameOrdinals	DWORD	?	; 指向函数名序号表的RVA
IMAGE_EXPORT_DIRECTORY ends

在这里说下AddressOfFunctions、AddressOfNames和AddressOfNameOrdinals这三个成员的对应关系,比如说我们通过名称搜索MessageBoxA函数的地址, AddressOfNames的值是一个RVA数组,每个RVA加上模块基址就是实际的函数名称字符串地址,如果说MessageBoxA这个字符串在这个数组的索引位置0处, 而AddressOfNames和AddressOfNameOrdinals是对应的,用”MessageBoxA“字符串在AddressOfNames中的索引在AddressOfNameOrdinals指向的函数名序号表中取值,取出的序号值再用在AddressOfFunctions中用来取值,取得的RVA加上模块基址就是MessageBoxA函数的地址了。



下面的代码用来测试获取MessageBoxA函数的地址并调用

.386
.model flat, stdcall
option casemap:none

include windows.inc
include user32.inc
include kernel32.inc
includelib user32.lib
includelib kernel32.lib

.const
szDllName   db 'user32.dll', 0
szApiString db 'MessageBoxA', 0
szText      db 'Get API Address Success!', 0
szCaption   db 'Success', 0

.code
_GetApiAddress proc _hDllHandle, _lpApiString

push ebp
mov eax, _hDllHandle    	; hModule
mov ecx, _lpApiString   	; lpApiString
mov ebx, eax        	; ebx = hModule
mov edi, ecx        	; edi = lpApiString
xor al, al
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 获得_lpApiString长度
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Scasb:
scasb
jnz _Scasb  		; 单字节扫描edi指向的字符串,不和al相等跳转
dec edi     		; 减去字符串结尾0
sub edi, ecx    		; 字符串结尾减去起始获得长度
xchg edi, ecx   		; 将长度放到ecx, 字符串放到edi
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 读取导出表的一些数据
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
mov eax, [ebx + 3ch]        ; 获得PE头的位置
mov esi, [ebx + eax + 78h]  ; 获得IMAGE_NT_HEADERS.IMAGE_OPTIONAL_HEADER32.IMAGE_DATA_DIRECTORY
lea esi, [esi + ebx + IMAGE_EXPORT_DIRECTORY.NumberOfNames] ; esi指向导出表结构的NumberOfNames
lodsd      			; 将NumberOfNames读取到eax
xchg eax, edx   		; edx = NumberOfNames
lodsd       		; 将AddressOfFunctions读取到eax
push eax    		; [esp] = AddressOfFunctions
lodsd       		; eax = AddressOfNames
xchg eax, ebp   		; ebp = AddressOfNames
lodsd       		; eax = AddressOfNameOrdinals
xchg eax, ebp   		; eax = AddressOfNames, ebp = AddressOfNameOrdinals
add eax, ebx    		; eax = 函数名称RVA数组

mov [esp - 4 * 1], edi  	; 临时存储
mov [esp - 4 * 2], ecx  	; 临时存储
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 扫描指定api是否存在dll的导出表中
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_LoopScas:
dec edx             	; edx = NumberOfNames
js _Ret  			; 为负转移,即为-1转移
mov esi, [eax + edx * 4]    ; 取得函数名称RVA, 乘4是因为函数名称RVA数组是dword类型的
add esi, ebx            	; esi = 函数名称的实际地址
repz cmpsb          	; esi和edi循环单字节比较,相等循环
jz _GetAddr
mov edi, [esp - 4 * 1]      ; 还原edi
mov ecx, [esp - 4 * 2]      ; 还原ecx
jmp _LoopScas
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 获取指定api的地址
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_GetAddr:
shl edx, 1          	; edx = 函数名地址表索引, 乘以2是因为AddressOfNameOrdinals是word数组
add ebp, edx            	; ebp = 函数序号表的RVA, 加edx为取得指定索引的序号
movzx eax, word ptr [ebp + ebx] ; 取得指定序号值
shl eax, 2          	; 乘以4是因为AddressOfFunctions为dowrd数组
add eax, [esp]
mov eax, [ebx + eax]        ; 取得指定序号函数的地址RVA
add eax, ebx            	; 获得函数实际地址

_Ret:
pop ecx             	; 将AddressOfFunctions弹出堆栈
pop ebp
ret

_GetApiAddress endp

_WinMain proc

invoke LoadLibrary, addr szDllName
invoke _GetApiAddress, eax, addr szApiString
push MB_OK
lea ebx, szCaption
push ebx
lea ebx, szText
push ebx
push 0
call eax
ret

_WinMain endp

start:

call _WinMain
invoke ExitProcess, 0

end start


hash算法搜索获得api函数地址的实现

我们一般要获得一个函数的地址,通常采用的是明文,例如定义一个api函数字符串"MessageBoxA",然后在GetProcAddress函数中一个字节一个字节进行比较。这样弊端很多,例如如果我们定义一个杀毒软件比较敏感的api函数字符串,那么可能就会增加杀毒软件对我们的程序的判定值,而且定义这些字符串还有一个弊端是占用的字节数较大。我们想想如何我们的api函数字符串通过算法将它定义成一个4字节的值,然后在GetProcAddress中把AddressOfNames表中的每个地址指向的api字符串通过我们的算法压缩成4字节值后,与我们之前定义的4字节值进行判断,如果匹配成功则读取函数地址。

我们来看一种rol 3移位算法,这个算法是每次将目的地址循环向左移动3位,然后将低字节与源字符串的每个字节进行异或。算法很精巧方便。还有很多方便小巧的算法,例如ROR 13等算法,其实它和我们上面的基本一样,只不过它将函数名表的字符串通过我们的算法过程获得hash值后与我们之前定义的hash值进行匹配,匹配成功则获得对应函数的地址。

下面的代码通过hash搜索WinExec函数来运行Clac:

.386
.model flat, stdcall
option casemap:none

include windows.inc
include user32.inc
include kernel32.inc
includelib user32.lib
includelib kernel32.lib

.const
szCalc      db 'calc.exe', 0

.code

_GetKrnl32 proc

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 获取kernel32.dll的地址,因为xp和win7不一样
; 故采用比较名称的方式获取地址,具体见Kernel32基地址获得学习
; http://blog.csdn.net/programmingring/article/details/11357393 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
assume fs:nothing
mov eax, fs:[30h]
mov eax, [eax + 0ch]
mov eax, [eax + 1ch]		; 第一个LDR_MODULE

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 压入kernel32.dll的unicode字符串
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
push dword ptr 006ch
push dword ptr 6c0064h
push dword ptr 2e0032h
push dword ptr 33006ch
push dword ptr 65006eh
push dword ptr 720065h
push word ptr 006bh
mov ebx, esp  			; ebx指向字符串

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 开始比较
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Search:
cmp eax, 0
jz _NotFound
cmp eax, 0ffffffffh
jz _NotFound
lea esi, [eax + 1ch]  		; esi指向UNICODE_STRING结构
xor ecx, ecx
mov cx, 13  			; 比较的长度
mov esi, [esi + 4]  		; 获得UNICODE_STRING结构的Buffer成员
mov edi, ebx  			; edi指向kernel32unicode字符串
repz cmpsw
or ecx, ecx  			; ecx减完说明相等
jz _Found
mov eax, [eax]  			; 获取下一个LDR_MODULE
jmp _Search

_NotFound:
mov eax, 0ffffffffh
jmp _Over

_Found:
mov eax, [eax + 08h]  		; 获得地址

_Over:
add esp, 26
ret

_GetKrnl32 endp

_GetRolHash proc _lpApiString

mov eax, _lpApiString
push esi
xor edx, edx
xchg eax, esi  			; esi = _lpApiString
cld  				; 递增

_Next:
lodsb  				; 从esi指向的地址中取一个字符
test al, al  			; 比较是否为0
jz _Ret
rol edx, 3  			; 左循环移位3位
xor dl, al  			; 低字节和字符异或
jmp _Next

_Ret:
xchg eax, edx  			; eax = 处理后的_lpApiString的hash
pop esi
ret

_GetRolHash endp

_GetApi proc _hDllHandle, _iHashApi

push ebp
mov eax, _hDllHandle
mov ecx, _iHashApi
mov ebx, eax
mov edi, ecx

mov eax, [ebx + 3ch]
mov esi, [ebx + eax + 78h]      	; Get Export RVA
lea esi, [esi + ebx + IMAGE_EXPORT_DIRECTORY.NumberOfNames]
lodsd
xchg eax, edx               	; edx = NumberOfNames
lodsd
push eax                		; [esp] = AddressOfFunctions
lodsd
xchg eax, ebp              		; ebp = AddressOfNames
lodsd
xchg eax, ebp               	; ebp = AddressOfNameOrdinals, eax = AddressOfNames
add eax, ebx
xchg eax, esi               	; esi = AddressOfNames

_LoopScas:
dec edx
js _Ret
lodsd  				; eax = api名称的rva
add eax, ebx  			; eax = api名称的实际地址
push edx
invoke _GetRolHash, eax     	; 计算hash字符串

pop edx
cmp eax, edi  			; 比较和传入的hash参数是否相等
jz _GetAddr

add ebp, 2  			; 递增,和esi相对应
jmp _LoopScas

_GetAddr:
movzx eax, word ptr [ebp + ebx]  	; 取得序号值
shl eax, 2  			; 乘4
add eax, [esp]  			; 在AddressOfFunctions取得相应序号值位置的值
mov eax, [ebx + eax]  		; 取得函数地址
add eax, ebx

_Ret:
pop ecx
pop ebp
ret

_GetApi endp

_WinMain proc

invoke _GetKrnl32
invoke _GetApi, eax, 016ef74bh      ; "WinExec"的hash
push SW_SHOW
lea ebx, szCalc
push ebx
call eax
ret

_WinMain endp

start:
call _WinMain
invoke ExitProcess, 0

end start
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: