前面有一个朋友给我留言说要搞个链接,好的,等我把第七篇文章写完之后,会在最后一篇文章的最后把前面的六篇文章的地址都链接上去,也样第一方便了大家,也同时方便了自己阅读学习,给人方便,就是给自己方便!!
参考文献《Win32汇编语言程序设计》第二版
前面的五篇文章,所讲的都是对PE文件的结构进行了分析,以及对PE工具的编写进行了介绍,但在实际的应用中还未有涉及到很多方面的内容,其实学好PE在很多方面都有很重要的应用,比如文件的加密,压缩,编写病毒等都涉及到修改及重组PE文件,另外,像API HOOK,PE文件的内存映像DUMP等应用则涉及分析内存中的PE映像。在接下来的两篇文章中我将用几个例子来进一步说明这方面的应用,可以说这是编写病毒的基础吧!!
首先讲在病毒中用到的一种很常用的技术,就是如何从内存中动态获取某个API的地址。
动态获取API的入口地址
在Win32环境下编程,不便用API几乎是不可能的事情,一般情况下,在代码中使用API不外乎两种办法:第一是编译链接的时候使用导入库,那么生成的PE文件中就会包含导入表,这样程序执行时会由Windows装载器根据导入表中的信息来修正API调用语句中的地址;第二种方法是使用LoadLibrary函数动态装入某个DLL模块,并使用GetProcAddress函数从被装载入的模块中获取API函数的地址。
先讲解一下原理吧!
在DOS环境下,一个可执行文件既可以用INT 21h/4ch来结束程序,也可以用一个Ret指令来结束程序,实际上,在Win32下也可以用这种方法来结束程序,虽然大部分的Win32程序都使用ExitProcess函数来终止执行,但是使用Ret指令确实也是有效的。
如下图所示,当父进程要创建一个子进程的时候,它会调用Kernel32.dll中的CreateProcess函数,CreateProcess函数在完成装载应用程序后,会将一个返回地址压入堆栈并转而执行应用程序,如果应用程序用ExitProcess函数来终止,那么这个返回地址没有什么用途,但如果应用程序使用Ret指令的话,程序就会返回CreateProcess函数设定的地址。也就是说,应用程序的主程序可以看作是被Windows调用的一个子程序。
图中表示Win32可执行文件退出的示意图
那么Ret指令返回到的地址上究竟有什么指令呢?用Soft-ICE看看就会发现,它包含一句push eax指令和一句call ExitThread,也就是说,假如用Ret指令返回的话,Windows会替程序去调用ExitThread函数,如果这是进程的最后一个线程的话,ExitThread函数又会自动去调用ExitProcess,这样程序就会被终止执行。
从这个过程可以得到一个很重要的数据,那就是堆栈中的返回地址,这个地址只要在程序入口的地方用[esp]就可以将它读出,说它重要是因为它位于Kernel32.dll模块中,而LoadLibrary和GetProcAddress函数正是处于Kernel32.dll模块中,换句话说就是,我们得到的地址和这两个函数近在咫尺,完全可以从这个地址经过某种算法来找到这两个函数的入口地址,得到这两个函数的入口地址以后,什么问题都解决了。
结合本章前面内容中提到过的两个事实,可以确定这种想法是可行的。
首先,PE文件被装入内存后(包括Kernel32.dll文件),除了一些可丢弃的节如重定位节以外,其他的内容都会被装入内存,这样获取导出函数地址所需的PE文件头、导出表等数据都存在于内存中;第二,PE文件被装入内存时是按内存页对齐的,只要从Ret指令返回的地址按照页对齐的边界一页页地向低地址搜寻,就必然可以找到Kernel32.dll文件的文件头位置。
下面请看具体的源代码,我会在其中作出注释,以方便大家理解!
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Sample code for < Win32ASM Programming 2nd Edition>
; by 罗云彬
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; NoImport.asm
; 以从内存中动态获取的办法使用 API
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
_ProtoGetProcAddress typedef proto :dword,:dword
_ProtoLoadLibrary typedef proto :dword
_ProtoMessageBox typedef proto :dword,:dword,:dword,:dword
_ApiGetProcAddress typedef ptr _ProtoGetProcAddress
_ApiLoadLibrary typedef ptr _ProtoLoadLibrary
_ApiMessageBox typedef ptr _ProtoMessageBox
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hDllKernel32 dd ?
hDllUser32 dd ?
_GetProcAddress _ApiGetProcAddress ?
_LoadLibrary _ApiLoadLibrary ?
_MessageBox _ApiMessageBox ?
.const
szLoadLibrary db 'LoadLibraryA',0
szGetProcAddress db 'GetProcAddress',0
szUser32 db 'user32',0
szMessageBox db 'MessageBoxA',0
szCaption db 'A MessageBox !',0
szText db 'Hello, World !',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
include _GetKernel.asm ;包含获取Kernel32.dll的基址的源代码
start:
;********************************************************************
; 从堆栈中的 Ret 地址转换 Kernel32.dll 的基址,并在 Kernel32.dll
; 的导出表中查找 GetProcAddress 函数的入口地址
;********************************************************************
invoke _GetKernelBase,[esp] ;获取Kernel32.dll的基址
.if eax
mov hDllKernel32,eax ;保存Kernel32.dll的基址
invoke _GetApi,hDllKernel32,addr szGetProcAddress ;在Kernel32.dll的导出表中查找GetProcAddress函数的入口地址
mov _GetProcAddress,eax ;保存GetProcAddress函数的入口地址
.endif
;********************************************************************
; 用得到的 GetProcAddress 函数得到 LoadLibrary 函数地址并装入其他 Dll
;********************************************************************
.if _GetProcAddress ;如果LoadLibrary函数地址不为NULL
invoke _GetProcAddress,hDllKernel32,addr szLoadLibrary ;通过GetProcAddress得到LoadLibrary函数的地址
mov _LoadLibrary,eax ;保存LoadLibrary函数的地址
.if eax
invoke _LoadLibrary,addr szUser32 ;使用LoadLibrary函数装载User32.dll
mov hDllUser32,eax ;保存User32.dll
invoke _GetProcAddress,hDllUser32,addr szMessageBox ;获取User32.dll中MessageBox函数的地址
mov _MessageBox,eax ;保存MessageBox函数的地址
.endif
.endif
;********************************************************************
.if _MessageBox ;如果MessageBox函数的地址不为NULL
invoke _MessageBox,NULL,offset szText,offset szCaption,MB_OK
.endif
ret
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
上面这个函数很简单也没什么好说的,我已经注释的很详细了,这里就不多说了,我们来看看它所包含的一个源文件_GetKernel.asm是怎么样获取Kernel32.dll基址的
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Sample code for < Win32ASM Programming 2nd Edition>
; by 罗云彬
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 公用模块:_GetKernel.asm
; 根据程序被调用的时候堆栈中有个用于 Ret 的地址指向 Kernel32.dll
; 而从内存中扫描并获取 Kernel32.dll 的基址
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 错误 Handler
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_SEHHandler proc C _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContext ;这里我不讲了前面已经说过SEH异常处理
pushad
mov esi,_lpExceptionRecord
mov edi,_lpContext
assume esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT
mov eax,_lpSEH
push [eax + 0ch]
pop [edi].regEbp
push [eax + 8]
pop [edi].regEip
push eax
pop [edi].regEsp
assume esi:nothing,edi:nothing
popad
mov eax,ExceptionContinueExecution
ret
_SEHHandler endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 在内存中扫描 Kernel32.dll 的基址
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_GetKernelBase proc _dwKernelRet ;传入一个参数
local @dwReturn
pushad
mov @dwReturn,0
;********************************************************************
; 重定位
;********************************************************************
call @F ;这里我就不说了,在病毒中经常用到,很多病毒开始就这经典的四句
@@: ;主要就于重定位
pop ebx
sub ebx,offset @B
;********************************************************************
; 创建用于错误处理的 SEH 结构
;********************************************************************
assume fs:nothing
push ebp
lea eax,[ebx + offset _PageError]
push eax
lea eax,[ebx + offset _SEHHandler]
push eax
push fs:[0]
mov fs:[0],esp
;********************************************************************
; 查找 Kernel32.dll 的基地址
;********************************************************************
mov edi,_dwKernelRet ;将参数中传递过来的目标地址按64K对齐
and edi,0ffff0000h ;与0ffff0000h进行AND操作
.while TRUE
.if word ptr [edi] == IMAGE_DOS_SIGNATURE ;在内存中寻找DOS MZ文件头标识和PE文件头标识
mov esi,edi
add esi,[esi+003ch]
.if word ptr [esi] == IMAGE_NT_SIGNATURE
mov @dwReturn,edi
.break
.endif
.endif
_PageError: ;页面异常处理
sub edi,010000h ;以一个页面作为间隔
.break .if edi < 070000000h
.endw
pop fs:[0]
add esp,0ch
popad
mov eax,@dwReturn
ret
_GetKernelBase endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 从内存中模块的导出表中获取某个 API 的入口地址
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_GetApi proc _hModule,_lpszApi ;传入两个参数,模块名和函数名,用于获取传入的函数的入口地址
local @dwReturn,@dwStringLength
pushad
mov @dwReturn,0
;********************************************************************
; 重定位
;********************************************************************
call @F
@@:
pop ebx
sub ebx,offset @B
;********************************************************************
; 创建用于错误处理的 SEH 结构
;********************************************************************
assume fs:nothing
push ebp
lea eax,[ebx + offset _Error]
push eax
lea eax,[ebx + offset _SEHHandler]
push eax
push fs:[0]
mov fs:[0],esp
;********************************************************************
; 计算 API 字符串的长度(带尾部的0)
;********************************************************************
mov edi,_lpszApi
mov ecx,-1
xor al,al
cld ;设置方向位,使EDI增1
repnz scasb ;循环比较EDI中存放的函数名
mov ecx,edi
sub ecx,_lpszApi ;EDI的值减去函数名首地址的值为函数名的长度
mov @dwStringLength,ecx ;保存函数的长度
;********************************************************************
; 从 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
;********************************************************************
; 查找符合名称的导出函数名
;********************************************************************
mov ebx,[esi].AddressOfNames ;从AddressOfNames字段指向的函数名称地址表的第一项开始
add ebx,_hModule
xor edx,edx
.repeat
push esi
mov edi,[ebx]
add edi,_hModule
mov esi,_lpszApi
mov ecx,@dwStringLength ;以函数的长度作为循环,查找相符合的函数名
repz cmpsb ;字符串比较,比较EDI与ESI中的字符串
.if ZERO?
pop esi
jmp @F
.endif
pop esi
add ebx,4
inc edx
.until edx >= [esi].NumberOfNames
jmp _Error
@@:
;********************************************************************
; API名称索引 --> 序号索引 --> 地址索引
;********************************************************************
sub ebx,[esi].AddressOfNames ;记下这个函数名在字符串地址表中的索引值
sub ebx,_hModule
shr ebx,1
add ebx,[esi].AddressOfNameOrdinals ;然后在AddressOfNameOrdinals指向的数组中以同样的索引值取出数组项的值
add ebx,_hModule
movzx eax,word ptr [ebx]
shl eax,2
add eax,[esi].AddressOfFunctions ;以上面得到了值在AddressOfFunctions字段指向的函数入口地址表中获取函数RVA
add eax,_hModule
;********************************************************************
; 从地址表得到导出函数地址
;********************************************************************
mov eax,[eax]
add eax,_hModule
mov @dwReturn,eax
_Error:
pop fs:[0]
add esp,0ch
assume esi:nothing
popad
mov eax,@dwReturn
ret
_GetApi endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
上面的代码中GetApi有前面讲的得到导出表的代码差不多,大家可以对照的看,我这里也讲了一些简单的注释,如果大家对前面的已经掌握,在来看这段代码,应该不会有什么问题,好了,今天就先讲到了这!!明天是国庆的最后一天,希望大家在这个国庆长假中都能玩好,吃好,学好!
参考文献《Win32汇编语言程序设计》第二版
前面的五篇文章,所讲的都是对PE文件的结构进行了分析,以及对PE工具的编写进行了介绍,但在实际的应用中还未有涉及到很多方面的内容,其实学好PE在很多方面都有很重要的应用,比如文件的加密,压缩,编写病毒等都涉及到修改及重组PE文件,另外,像API HOOK,PE文件的内存映像DUMP等应用则涉及分析内存中的PE映像。在接下来的两篇文章中我将用几个例子来进一步说明这方面的应用,可以说这是编写病毒的基础吧!!
首先讲在病毒中用到的一种很常用的技术,就是如何从内存中动态获取某个API的地址。
动态获取API的入口地址
在Win32环境下编程,不便用API几乎是不可能的事情,一般情况下,在代码中使用API不外乎两种办法:第一是编译链接的时候使用导入库,那么生成的PE文件中就会包含导入表,这样程序执行时会由Windows装载器根据导入表中的信息来修正API调用语句中的地址;第二种方法是使用LoadLibrary函数动态装入某个DLL模块,并使用GetProcAddress函数从被装载入的模块中获取API函数的地址。
先讲解一下原理吧!
在DOS环境下,一个可执行文件既可以用INT 21h/4ch来结束程序,也可以用一个Ret指令来结束程序,实际上,在Win32下也可以用这种方法来结束程序,虽然大部分的Win32程序都使用ExitProcess函数来终止执行,但是使用Ret指令确实也是有效的。
如下图所示,当父进程要创建一个子进程的时候,它会调用Kernel32.dll中的CreateProcess函数,CreateProcess函数在完成装载应用程序后,会将一个返回地址压入堆栈并转而执行应用程序,如果应用程序用ExitProcess函数来终止,那么这个返回地址没有什么用途,但如果应用程序使用Ret指令的话,程序就会返回CreateProcess函数设定的地址。也就是说,应用程序的主程序可以看作是被Windows调用的一个子程序。
图中表示Win32可执行文件退出的示意图
那么Ret指令返回到的地址上究竟有什么指令呢?用Soft-ICE看看就会发现,它包含一句push eax指令和一句call ExitThread,也就是说,假如用Ret指令返回的话,Windows会替程序去调用ExitThread函数,如果这是进程的最后一个线程的话,ExitThread函数又会自动去调用ExitProcess,这样程序就会被终止执行。
从这个过程可以得到一个很重要的数据,那就是堆栈中的返回地址,这个地址只要在程序入口的地方用[esp]就可以将它读出,说它重要是因为它位于Kernel32.dll模块中,而LoadLibrary和GetProcAddress函数正是处于Kernel32.dll模块中,换句话说就是,我们得到的地址和这两个函数近在咫尺,完全可以从这个地址经过某种算法来找到这两个函数的入口地址,得到这两个函数的入口地址以后,什么问题都解决了。
结合本章前面内容中提到过的两个事实,可以确定这种想法是可行的。
首先,PE文件被装入内存后(包括Kernel32.dll文件),除了一些可丢弃的节如重定位节以外,其他的内容都会被装入内存,这样获取导出函数地址所需的PE文件头、导出表等数据都存在于内存中;第二,PE文件被装入内存时是按内存页对齐的,只要从Ret指令返回的地址按照页对齐的边界一页页地向低地址搜寻,就必然可以找到Kernel32.dll文件的文件头位置。
下面请看具体的源代码,我会在其中作出注释,以方便大家理解!
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Sample code for < Win32ASM Programming 2nd Edition>
; by 罗云彬
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; NoImport.asm
; 以从内存中动态获取的办法使用 API
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
_ProtoGetProcAddress typedef proto :dword,:dword
_ProtoLoadLibrary typedef proto :dword
_ProtoMessageBox typedef proto :dword,:dword,:dword,:dword
_ApiGetProcAddress typedef ptr _ProtoGetProcAddress
_ApiLoadLibrary typedef ptr _ProtoLoadLibrary
_ApiMessageBox typedef ptr _ProtoMessageBox
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hDllKernel32 dd ?
hDllUser32 dd ?
_GetProcAddress _ApiGetProcAddress ?
_LoadLibrary _ApiLoadLibrary ?
_MessageBox _ApiMessageBox ?
.const
szLoadLibrary db 'LoadLibraryA',0
szGetProcAddress db 'GetProcAddress',0
szUser32 db 'user32',0
szMessageBox db 'MessageBoxA',0
szCaption db 'A MessageBox !',0
szText db 'Hello, World !',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
include _GetKernel.asm ;包含获取Kernel32.dll的基址的源代码
start:
;********************************************************************
; 从堆栈中的 Ret 地址转换 Kernel32.dll 的基址,并在 Kernel32.dll
; 的导出表中查找 GetProcAddress 函数的入口地址
;********************************************************************
invoke _GetKernelBase,[esp] ;获取Kernel32.dll的基址
.if eax
mov hDllKernel32,eax ;保存Kernel32.dll的基址
invoke _GetApi,hDllKernel32,addr szGetProcAddress ;在Kernel32.dll的导出表中查找GetProcAddress函数的入口地址
mov _GetProcAddress,eax ;保存GetProcAddress函数的入口地址
.endif
;********************************************************************
; 用得到的 GetProcAddress 函数得到 LoadLibrary 函数地址并装入其他 Dll
;********************************************************************
.if _GetProcAddress ;如果LoadLibrary函数地址不为NULL
invoke _GetProcAddress,hDllKernel32,addr szLoadLibrary ;通过GetProcAddress得到LoadLibrary函数的地址
mov _LoadLibrary,eax ;保存LoadLibrary函数的地址
.if eax
invoke _LoadLibrary,addr szUser32 ;使用LoadLibrary函数装载User32.dll
mov hDllUser32,eax ;保存User32.dll
invoke _GetProcAddress,hDllUser32,addr szMessageBox ;获取User32.dll中MessageBox函数的地址
mov _MessageBox,eax ;保存MessageBox函数的地址
.endif
.endif
;********************************************************************
.if _MessageBox ;如果MessageBox函数的地址不为NULL
invoke _MessageBox,NULL,offset szText,offset szCaption,MB_OK
.endif
ret
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
上面这个函数很简单也没什么好说的,我已经注释的很详细了,这里就不多说了,我们来看看它所包含的一个源文件_GetKernel.asm是怎么样获取Kernel32.dll基址的
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Sample code for < Win32ASM Programming 2nd Edition>
; by 罗云彬
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 公用模块:_GetKernel.asm
; 根据程序被调用的时候堆栈中有个用于 Ret 的地址指向 Kernel32.dll
; 而从内存中扫描并获取 Kernel32.dll 的基址
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 错误 Handler
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_SEHHandler proc C _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContext ;这里我不讲了前面已经说过SEH异常处理
pushad
mov esi,_lpExceptionRecord
mov edi,_lpContext
assume esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT
mov eax,_lpSEH
push [eax + 0ch]
pop [edi].regEbp
push [eax + 8]
pop [edi].regEip
push eax
pop [edi].regEsp
assume esi:nothing,edi:nothing
popad
mov eax,ExceptionContinueExecution
ret
_SEHHandler endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 在内存中扫描 Kernel32.dll 的基址
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_GetKernelBase proc _dwKernelRet ;传入一个参数
local @dwReturn
pushad
mov @dwReturn,0
;********************************************************************
; 重定位
;********************************************************************
call @F ;这里我就不说了,在病毒中经常用到,很多病毒开始就这经典的四句
@@: ;主要就于重定位
pop ebx
sub ebx,offset @B
;********************************************************************
; 创建用于错误处理的 SEH 结构
;********************************************************************
assume fs:nothing
push ebp
lea eax,[ebx + offset _PageError]
push eax
lea eax,[ebx + offset _SEHHandler]
push eax
push fs:[0]
mov fs:[0],esp
;********************************************************************
; 查找 Kernel32.dll 的基地址
;********************************************************************
mov edi,_dwKernelRet ;将参数中传递过来的目标地址按64K对齐
and edi,0ffff0000h ;与0ffff0000h进行AND操作
.while TRUE
.if word ptr [edi] == IMAGE_DOS_SIGNATURE ;在内存中寻找DOS MZ文件头标识和PE文件头标识
mov esi,edi
add esi,[esi+003ch]
.if word ptr [esi] == IMAGE_NT_SIGNATURE
mov @dwReturn,edi
.break
.endif
.endif
_PageError: ;页面异常处理
sub edi,010000h ;以一个页面作为间隔
.break .if edi < 070000000h
.endw
pop fs:[0]
add esp,0ch
popad
mov eax,@dwReturn
ret
_GetKernelBase endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 从内存中模块的导出表中获取某个 API 的入口地址
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_GetApi proc _hModule,_lpszApi ;传入两个参数,模块名和函数名,用于获取传入的函数的入口地址
local @dwReturn,@dwStringLength
pushad
mov @dwReturn,0
;********************************************************************
; 重定位
;********************************************************************
call @F
@@:
pop ebx
sub ebx,offset @B
;********************************************************************
; 创建用于错误处理的 SEH 结构
;********************************************************************
assume fs:nothing
push ebp
lea eax,[ebx + offset _Error]
push eax
lea eax,[ebx + offset _SEHHandler]
push eax
push fs:[0]
mov fs:[0],esp
;********************************************************************
; 计算 API 字符串的长度(带尾部的0)
;********************************************************************
mov edi,_lpszApi
mov ecx,-1
xor al,al
cld ;设置方向位,使EDI增1
repnz scasb ;循环比较EDI中存放的函数名
mov ecx,edi
sub ecx,_lpszApi ;EDI的值减去函数名首地址的值为函数名的长度
mov @dwStringLength,ecx ;保存函数的长度
;********************************************************************
; 从 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
;********************************************************************
; 查找符合名称的导出函数名
;********************************************************************
mov ebx,[esi].AddressOfNames ;从AddressOfNames字段指向的函数名称地址表的第一项开始
add ebx,_hModule
xor edx,edx
.repeat
push esi
mov edi,[ebx]
add edi,_hModule
mov esi,_lpszApi
mov ecx,@dwStringLength ;以函数的长度作为循环,查找相符合的函数名
repz cmpsb ;字符串比较,比较EDI与ESI中的字符串
.if ZERO?
pop esi
jmp @F
.endif
pop esi
add ebx,4
inc edx
.until edx >= [esi].NumberOfNames
jmp _Error
@@:
;********************************************************************
; API名称索引 --> 序号索引 --> 地址索引
;********************************************************************
sub ebx,[esi].AddressOfNames ;记下这个函数名在字符串地址表中的索引值
sub ebx,_hModule
shr ebx,1
add ebx,[esi].AddressOfNameOrdinals ;然后在AddressOfNameOrdinals指向的数组中以同样的索引值取出数组项的值
add ebx,_hModule
movzx eax,word ptr [ebx]
shl eax,2
add eax,[esi].AddressOfFunctions ;以上面得到了值在AddressOfFunctions字段指向的函数入口地址表中获取函数RVA
add eax,_hModule
;********************************************************************
; 从地址表得到导出函数地址
;********************************************************************
mov eax,[eax]
add eax,_hModule
mov @dwReturn,eax
_Error:
pop fs:[0]
add esp,0ch
assume esi:nothing
popad
mov eax,@dwReturn
ret
_GetApi endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
上面的代码中GetApi有前面讲的得到导出表的代码差不多,大家可以对照的看,我这里也讲了一些简单的注释,如果大家对前面的已经掌握,在来看这段代码,应该不会有什么问题,好了,今天就先讲到了这!!明天是国庆的最后一天,希望大家在这个国庆长假中都能玩好,吃好,学好!