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

暴力搜索内存空间中的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
;***********************************************
;程序名称:暴力搜索内存空间获得 Api 的线性地址
;适用OS:9x/Me/2k/XP
;作者:罗聪
;日期:2002-11-14
;出处:http://www.luocong.com/(老罗的缤纷天地)
;本代码使用了病毒技术,但纯粹只用于技术研究。
;切记:请勿用于非法用途!!!!!!
;注意事项:如欲转载,请保持本程序的完整,并注明:
;转载自“老罗的缤纷天地”(http://www.luocong.com/
;***********************************************
.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]

;****************************************
;函数功能:查找 Kernel32.dll 的基地址
;****************************************
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

;************************************************************
;函数功能:从内存中 Kernel32.dll 的导出表中获取某个 API 的入口地址
;************************************************************
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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值