;这个程序是从罗云彬老师的《windows环境下32位汇编语言》书上抄下来的,因为之前看的时候看得不是很懂,后来总结了一些资料后终于是弄明白了,
;这里贴出我的分析供初学者学习,大神飘过吧,若有不对之处,还请大家提出来。
;首先是一个SEH异常处理代码,这段程序是作为一个子程序来调用的,为了避免使用全局变量,这里用_lpSEH这个参数来传递需要保护的寄存器。
_SEHHandler proc C _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContext
pushad
mov esi,_lpExceptionRecord
mov edi,_lpContext
assume esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT
mov eax,_lpSEH
push [eax + 0ch]
pop [edi].regEbp ;恢复Ebp
push [eax + 8]
pop [edi].regEip ;恢复Eip
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
and edi,0ffff0000h ;重点:为什么要和0ffff0000h进行and运算?因为PE文件被装入内存时是按64K对齐的,取and后刚好是64K的整数
;倍,这样查找的时候就更容易找到,提高效率。
.while TRUE
.if word ptr [edi] == IMAGE_DOS_SIGNATURE ;先比较是不是一个DOS头部
mov esi,edi
add esi,[esi+003ch]
.if word ptr [esi] == IMAGE_NT_SIGNATURE ;再看是不是一个有效的PE头
mov @dwReturn,edi
.break
.endif
.endif
_PageError:
sub edi,010000h ;这儿为什么是减010000h,往低地址找?因为按照PE结构的顺序,导出表的地址是在PE文件地址的后面
;而内存的增长方向又是从低到高,所以要找kernerl32.dll的基址就必须往前面(低地址找)。
.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),这段程序的原理是:将al赋值为0,然后依次与API的每一个字符进行比较,当比较到API最后一个字符'0'时
;表示API的字符串结束,然后用edi现在的地址值减去API之前的地址,之差就是API的长度。
;********************************************************************
mov edi,_lpszApi
mov ecx,-1
xor al,al
cld
repnz scasb ;这条指令之前一直没弄懂,后来查看了一些资料才明白。repnz 表示在ecx!=0且ZF==0时,程序继续循环。scasb
;是对每一个字符进行比较(比较对象 是:al 与edi 中的字符。)用C语言可以这样翻译:
;while(--ecx)
;{
; if(*(edi++)==al)
; break;
;}
mov ecx,edi
sub ecx,_lpszApi
mov @dwStringLength,ecx
;********************************************************************
; 从 PE 文件头的数据目录获取导出表地址
;********************************************************************
mov esi,_hModule
add esi,[esi + 3ch] ;3ch这个长度是从DOS头部到PE头的大小,从基地址加3ch刚好到达NT结构。
assume esi:ptr IMAGE_NT_HEADERS
mov esi,[esi].OptionalHeader.DataDirectory.VirtualAddress
add esi,_hModule ;因为数据目录的第一个就是导出表,所以直接取其地址,就是EXPORT结构体。
assume esi:ptr IMAGE_EXPORT_DIRECTORY
;********************************************************************
; 查找符合名称的导出函数名
;********************************************************************
mov ebx,[esi].AddressOfNames
add ebx,_hModule
xor edx,edx
.repeat
push esi
mov edi,[ebx]
add edi,_hModule
mov esi,_lpszApi
mov ecx,@dwStringLength
repz cmpsb ;这个repz指令与上面的repnz类似,也是串循环指令。不同之处在于比较的对象不一样,
;这里比较的对象是si与di中的字符,若相等则继续循环,不相等则退出循环。
.if ZERO?
pop esi
jmp @F ;找到要查找的API,则直接跳到下一步。
.endif
pop esi
add ebx,4 ;没找到则继续向后找,这里,为什么是加4?因为AddressOfNames(函数名字符串地址表)是
;一个双字数组。
inc edx
.until edx >= [esi].NumberOfNames
jmp _Error
@@:
;********************************************************************
; API名称索引 --> 序号索引 --> 地址索引
;********************************************************************
sub ebx,[esi].AddressOfNames
sub ebx,_hModule
shr ebx,1 ;这里为什么要右移一位?右移是除2的意思,因为AddressOfFunctions指向的是一个word数组,
;所以要除以2,以便可以和之后的地址相加。
add ebx,[esi].AddressOfNameOrdinals
add ebx,_hModule ;这里为什么要加kernel32.dll的基址?因为目前ebx只是一个RVA,还不能直接用。
movzx eax,word ptr [ebx]
shl eax,2 ;这里左移一位,是恢复双字数据,为下面相加作准备。
add eax,[esi].AddressOfFunctions
add eax,_hModule ;这个也是个RVA
;********************************************************************
; 从地址表得到导出函数地址
;********************************************************************
mov eax,[eax]
add eax,_hModule ;eax这个RVA加上kernel32.dll的基址,就是要找的API的函数地址了。
mov @dwReturn,eax
_Error:
pop fs:[0]
add esp,0ch
assume esi:nothing
popad
mov eax,@dwReturn
ret
_GetApi endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
下面是主程序:
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.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
start:
;********************************************************************
; 从堆栈中的 Ret 地址转换 Kernel32.dll 的基址,并在 Kernel32.dll
; 的导出表中查找 GetProcAddress 函数的入口地址
;********************************************************************
invoke _GetKernelBase,[esp]
.if eax
mov hDllKernel32,eax
invoke _GetApi,hDllKernel32,addr szGetProcAddress
mov _GetProcAddress,eax
.endif
;********************************************************************
; 用得到的 GetProcAddress 函数得到 LoadLibrary 函数地址并装入其他 Dll
;********************************************************************
.if _GetProcAddress
invoke _GetProcAddress,hDllKernel32,addr szLoadLibrary
mov _LoadLibrary,eax
.if eax
invoke _LoadLibrary,addr szUser32
mov hDllUser32,eax
invoke _GetProcAddress,hDllUser32,addr szMessageBox
mov _MessageBox,eax
.endif
.endif
;********************************************************************
.if _MessageBox
invoke _MessageBox,NULL,offset szText,offset szCaption,MB_OK
.endif
ret
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!