原理:
现在虽然大部分Win32程序都使用ExitProcess函数来终止执行,但是其实用ret指令也是可以的。我们的应用程序的主程序可以被看成是一个被Windows调用的子程序。当父进程要创建一个子进程时,它会调用Kernel32.dll中的CreateProcess函数,CreateProcess函数在完成装载应用程序后,就会把一个返回地址用push压入栈,这个返回地址是子进程用ret来结束函数的时候要返回到的地方,返回到这后父进程会用ExitThread来结束这个线程,如果这个线程是子进程的最后一个线程的话,父进程就会再调用ExitProcess函数来结束子进程。重要的是:子进程返回到的地址,也就是父进程创建完子进程后压入栈的那个地址,是处于Kernel32.dll中的。所以,我们可以在程序中一开始就获取堆栈里的内容,这样我们就得到了一个处于Kernel32.dll中的地址,然后我们再顺着这个地址往低地址查询,直到找到PE文件头,那么我们就可以确定Kernel32.dll的起始地址了。得到了它的地址,我们可以根据函数名,通过数据目录找到导出表的地址,然后再遍历查找到GetProAddress函数的地址,有了这个函数,我们就可以轻松得到其他函数的地址了,当然我们还要得到LoadLibrary函数的地址,这样我们就可以得到其他动态链接库的基址了。
要注意的是,PE文件被装入内存时是按照64KB对齐的,所以Kernel32.dll的基址肯定也在某一个大小为64KB的页的开始部位,所以我们只要按照这个大小去找,就能找到Kernel32.dll的基址。
获取Kernel32.dll基址和GetProcAddress函数的入口地址的功能的代码放在一个函数中了,代码如下(参数_dwKernelRet就是程序一开始从SP处取得的值):
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;错误Handler
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_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] ;恢复ebp
pop [edi].regEbp ;
push [eax + 8]
pop [edi].regEip ;设置IP指向发生中断后要执行的代码的位置
push eax
pop [edi].regEsp ;恢复ESP
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 ;取高位
.while TRUE
.if word ptr [edi] == IMAGE_DOS_SIGNATURE ;查看第一个字段,判断是否是DOS文件头
mov esi,edi
add esi,[esi + 003ch] ;加上指向PE文件头的偏移
.if word ptr [esi] == IMAGE_NT_SIGNATURE ;判断是否是PE头
mov @dwReturn,edi ;
.break
.endif
.endif
_PageError:
sub edi,010000h ;向上寻找,一直找到Kernel32.dll的基地址为止(PE文件被装入内存时按照64KB对齐的)
.break .if edi < 070000000h
.endw
pop fs:[0] ;恢复原来的SEH链
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
repnz scasb ;于指定区域扫描制定字符,区域由ES:DI及CX指定。字符由AL指定。如果找到ES:DI指向该字符,ZF=1,否则,ZF=0
mov ecx,edi
sub ecx,_lpszApi ;减去字符串基地址得到字符串长度
mov @dwStringLength,ecx
;******************************************************************************************************************
;从PE文件头的数据目录获取导出表的位置
;******************************************************************************************************************
mov esi,_hModule
add esi,[esi + 3ch]
assume esi:ptr IMAGE_NT_HEADERS
mov esi,[esi].OptionalHeader.DataDirectory.VirtualAddress ;获取导出表的RVA
add esi,_hModule
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 ;比较esi和edi指向的内容,如果相同就将EDI指向后面一个单位继续比较,单位为一个字节,ECX中存放要比较的字符长度,存在不相同的就停止并设置zf标志位为0
.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
add ebx,_hModule
movzx eax,word ptr [ebx] ;获得序号索引
shl eax,2
add eax,[esi].AddressOfFunctions
add eax,_hModule ;获得地址
;************************************************************************************************************************
;从地址表得到导出函数的位置
;************************************************************************************************************************
mov eax,[eax] ;得到指向函数地址的RVA
add eax,_hModule
mov @dwReturn,eax
_Error:
pop fs:[0] ;恢复原来的SEH链
add esp,0ch
assume esi:nothing
popad
mov eax,@dwReturn
ret
_GetApi endp
在查找Kernel32.dll基址的时候,我们将参数(也就是一个位于Kernel32.dll中的一个地址)的低位清0,因为我们要按照64KB的大小来寻找。
当我们找到Kernel32.dll基址后,就可以通过这个基址定位到PE文件头中的IMAGE_OPTIONAL_HRADER32结构的数据目录表的基址,然后再通过数据目录找到导出表的位置,最后再通过函数名找到函数在导出函数序号表中的索引,然后获取函数的序号,最后获取到函数的真正入口地址。
可以写一个程序来验证一下:
.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, Win32 ASM !',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
include _GetKernel.asm
start:
;******************************************************************************************************************
;从堆栈中的Ret地址找到Kernel32.dll的基地址,并在Kernel32.dll
;的导出表中查找GetProcAddress函数的入口地址
;******************************************************************************************************************
invoke _GetKernelBase,[esp] ;此时ESP所指的地址在Kernel32.dll中
.if eax
mov hDllKernel32,eax
invoke _GetApi,hDllKernel32,addr szGetProcAddress ;得到GetProcAddress函数的入口地址
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
程序先调用 _GetKernelBase函数获取kernel32.dll的基址,然后再调用_GetApi获取GetProcAddress函数的入口地址,然后通过GetProcAddress函数我们可以获得LoadLibrary函数的地址,这样我们就可以轻松得到我们想要的函数和DLL的地址了,当然我们要方便一点用这些获取的函数的话,要像这个程序一样在最前面要声明函数原型。
程序编译链接后运行:
![](https://img-blog.csdnimg.cn/20200318212851435.png)
可以正确显示窗口,那么证明这样做是可以的。我们可以再用之前的查看导入表的程序查看这个程序:
![](https://img-blog.csdnimg.cn/20200318213342782.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzU3NTg1OQ==,size_16,color_FFFFFF,t_70)
可以看到这个函数里面没有任何导入函数,全都是我们自己导入的。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)