您的位置:首页 > 其它

暴力搜索内存空间获得API的线性地址

2015-07-03 18:04 441 查看

暴力搜索内存空间中的API

一个PE文件在编译和连接成功后,会有一个Import Table,当需要执行 API的时候,会先在Import Table中得到API的地址,然后调用它

而病毒是在PE文件编译好之后才插入的,它本身没有Import Table ,那么要如何得到API的地址呢?

任何一个DLL,都可以用LoadLibraryA来装入,然后通过GetProcAddress 来取得这个DLL中的函数的地址

但是我们如何能够获得这两个API的地址呢?现在比较通用的技术是在整个 4GB 的内存空间中暴力搜索Kernel32.dll的基地址,然后从Kernel32.dll的Export Table中取得LoadLibraryA和GetProcAddress的地址

DLL有一个非常特殊的特性:当有别的程序调用它的时候,它的文件映象就会动态地映射到调用进程的内存地址空间。一般情况下,一个程序在运行的时候, Kernel32.dll 这个DLL都会被映射到该程序的内存地址空间,成为它的一部分

在不同的操作系统下,Kernel32.dll 的基地址是不同的,例如,98 下它是 BFF70000h , 2K 为 77E80000h , XP 为 77E60000h 。由于它们都在 70000000h 以上,所以为了加速搜索,我们可以就从 70000000h 开始,或者是反过来,从栈顶 [esp] 开始,往下递减,减少到 70000000h 为止

如果是一个一个字节地进行搜索,那么速度也太慢了吧?!由于 Dll 一般是以 1M 为边界,所以我们可以用 10000h (64k) 作为跨度,这样可以大大加快搜索速度

值得补充的是, 4GB 的内存地址并不是完全可读的,如果遇到了不能读的地方,就会产生 GPF (General Protect Fault,一般保护性错误)。幸好 Microsoft 已经为我们预留了一种办法——可以用 SEH 来解决

DataDirectory 的第一项就是 export table ,所以我们可以定位到 DataDirectory ,然后读取它的第一个 VirtualAddress ,这样就得到了 export table 的 RVA

如何通过函数名字获取函数地址:

1. 定位到 PE Header。

2. 从数据目录读取引出表的虚拟地址。

3. 定位引出表获取名字数目( NumberOfNames )。

4. 并行遍历 AddressOfNames 和 AddressOfNameOrdinals 指向的数组匹配名字。如果在 AddressOfNames 指向的数组中找到匹配名字,就从 AddressOfNameOrdinals 指向的数组中提取索引值。例如,若发现匹配名字的 RVA 存放在 AddressOfNames 数组的第 12 个元素,那就提取 AddressOfNameOrdinals 数组的第 12 个元素作为索引值。如果遍历完 NumberOfNames 个元素,说明当前模块没有所要的名字。

5. 从 AddressOfNameOrdinals 数组提取的数值作为 AddressOfFunctions 数组的索引。也就是说,如果值是 7 ,就必须读取 AddressOfFunctions 数组的第 7 个元素,此值就是所要函数的RVA。

具体到我们的病毒中,有一条公式可以用:

API’s Address = ( API’s Ordinal * 4 ) + AddressOfFunctions’ VA + Kernel32 imagebase

;[b]***********************************************[/b]

;程序名称:暴力搜索内存空间获得 Api 的线性地址

;适用OS:9x/Me/2k/XP

;作者:罗聪

;日期:2002-11-14

;出处:http://www.luocong.com/(老罗的缤纷天地)

;本代码使用了病毒技术,但纯粹只用于技术研究。

;切记:请勿用于非法用途!!!!!!

;注意事项:如欲转载,请保持本程序的完整,并注明:

;转载自“老罗的缤纷天地”(http://www.luocong.com/

;[b]***********************************************[/b]

.386

.model flat, stdcall

option casemap:none

;请注意,这里并没有引入 kernel32 和 user32:

;引入 comctl32 只是为了后面调用 InitCommonControls

include /masm32/include/windows.inc

include /masm32/include/comctl32.inc

includelib /masm32/lib/comctl32.lib

GetKernelBase proto :DWORD

GetApiAddress proto :DWORD, :DWORD

.data

szMyMsg db “–= 暴力搜索内存空间获得 Api 的线性地址 =–”, 13, 10, 13, 10,/

“请注意:”, 13, 10,/

“* 本对话框的线性地址是通过暴力搜索得来 *”, 13, 10, 13, 10,/

“老罗的缤纷天地”,13, 10, “http://www.luocong.com/“, 0

szMyCaption db “老罗的病毒基础教程系列 by LC”, 0

aKernel32Base dd 0

szUser32 db “user32.dll”, 0

szExitProcess db “ExitProcess”, 0

aExitProcess dd 0

szLoadLibraryA db “LoadLibraryA”, 0

aLoadLibraryA dd 0

szGetProcAddress db “GetProcAddress”, 0

aGetProcAddress dd 0

szMessageBoxA db “MessageBoxA”, 0

aMessageBoxA dd 0

.code

main:

;之所以要调用InitCommonControls(不一定非要它)

;是因为在2K下必须随便调用一个函数,否则在2K下不能加载 :(

invoke InitCommonControls

;很眼熟吧?病毒的常用手法……

call delta

delta:

pop ebp

sub ebp, offset delta

;获得 Kernel32.dll 的基地址:

invoke GetKernelBase, [esp]

mov aKernel32Base, eax

;获得 Kernel32.dll 中的所需的 Api 的线性地址:

invoke GetApiAddress, aKernel32Base, addr szExitProcess

mov aExitProcess, eax

invoke GetApiAddress, aKernel32Base, addr szLoadLibraryA

mov aLoadLibraryA, eax

invoke GetApiAddress, aKernel32Base, addr szGetProcAddress

mov aGetProcAddress, eax

;载入 User32.dll :

push offset szUser32

call [ebp + aLoadLibraryA]

;获得 User32.dll 中的 MessageBoxA 的线性地址:

push offset szMessageBoxA

push eax

call [ebp + aGetProcAddress]

mov aMessageBoxA, eax

;千呼万唤始出来,高兴了吧?

push MB_OK or MB_ICONINFORMATION

push offset szMyCaption

push offset szMyMsg

push NULL

call [ebp + aMessageBoxA]

;退出:

push 0

call [ebp + aExitProcess]

;[b]****************************************[/b]

;函数功能:查找 Kernel32.dll 的基地址

;[b]****************************************[/b]

GetKernelBase proc uses esi edi dwKernelRet:DWORD

LOCAL dwReturn: DWORD

mov edi, dwKernelRet ; edi = 堆栈顶

and edi, 0ffff0000h ; 用 AND 获得初始页

.while TRUE

.if word ptr [edi] == IMAGE_DOS_SIGNATURE ; 等于“MZ”吗?

mov esi, edi ; Yes, next…

add esi, [esi + IMAGE_DOS_HEADER.e_lfanew] ; 就是 esi + 3ch

.if word ptr [esi] == IMAGE_NT_SIGNATURE ; 等于“PE”吗?

mov dwReturn, edi ; Yes, we got it.

.break

.endif

.endif

;以下等同于sub edi, 010000h,即每次减少64k:

dec edi

xor di, di

.break .if edi < 070000000h ; 基地址一般不可能小于70000000h

.endw

mov eax, dwReturn

ret

GetKernelBase endp

;[b]************************************************************[/b]

;函数功能:从内存中 Kernel32.dll 的导出表中获取某个 API 的入口地址

;[b]************************************************************[/b]

GetApiAddress proc uses ecx ebx edx esi edi hModule:DWORD, szApiName:DWORD

LOCAL dwReturn: DWORD

LOCAL dwApiLength: DWORD

mov dwReturn, 0

;计算 API 字符串的长度(带尾部的0)

mov esi, szApiName

mov edx, esi

Continue_Searching_Null:

cmp byte ptr [esi], 0 ; 是否为 Null-terminated char ?

jz We_Got_The_Length ; Yeah, we got it. :)

inc esi ; No, continue searching.

jmp Continue_Searching_Null ; searching…….

We_Got_The_Length:

inc esi ; 别忘了还有最后一个“0”的长度

sub esi, edx ; esi = API Name size

mov dwApiLength, esi ; dwApiLength = API Name size

;从 PE 文件头的数据目录获取输出表的地址

mov esi, hModule

add esi, [esi + 3ch]

assume esi: ptr IMAGE_NT_HEADERS

mov esi, [esi].OptionalHeader.DataDirectory.VirtualAddress

add esi, hModule

assume esi:ptr IMAGE_EXPORT_DIRECTORY ; esi 指向 Kernel32.dll 的输出表

; done! :)

ret

GetApiAddress endp

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