PE权威指南第六章之重定位编程
1.无导入表的HelloWorld
.386
.model flat,stdcall
option casemap:none
include windows.inc
_QLGetProcAddress typedef proto :dword,:dword
_ApiGetProcAddress typedef ptr _QLGetProcAddress
_QLLoadLib typedef proto :dword
_ApiLoadLib typedef ptr _QLLoadLib
_QLMessageBoxA typedef proto :dword,:dword,:dword,:dword
_ApiMessageBoxA typedef ptr _QLMessageBoxA
.code
szText db 'HelloWorldPE',0
szGetProcAddr db 'GetProcAddress',0
szLoadLib db 'LoadLibraryA',0
szMessageBox db 'MessageBoxA',0
user32_DLL db 'user32.dll',0,0
_getProcAddress _ApiGetProcAddress ?
_loadLibrary _ApiLoadLib ?
_messageBox _ApiMessageBoxA ?
hKernel32Base dd ?
hUser32Base dd ?
lpGetProcAddr dd ?
lpLoadLib dd ?
_getKernelBase proc _dwKernelRetAddress
local @dwRet
pushad
mov @dwRet,0
mov edi,_dwKernelRetAddress
and edi,0ffff0000h
.repeat
.if word ptr [edi] == IMAGE_DOS_SIGNATURE
mov esi,edi
add esi,[esi+003ch]
.if word ptr [esi] == IMAGE_NT_SIGNATURE
mov @dwRet,edi
.break
.endif
.endif
sub edi,010000h
.break .if edi<070000000h
.until FALSE
popad
mov eax,@dwRet
ret
_getKernelBase endp
_getApi proc _hModule,_lpApi
local @ret
local @dwLen
pushad
mov @ret,0
mov edi,_lpApi
mov ecx,-1
xor al,al
cld
repnz scasb
mov ecx,edi
sub ecx,_lpApi
mov @dwLen,ecx
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
add ebx,_hModule
xor edx,edx
.repeat
push esi
mov edi,[ebx]
add edi,_hModule
mov esi,_lpApi
mov ecx,@dwLen
repz cmpsb
.if ZERO?
pop esi
jmp @F
.endif
pop esi
add ebx,4
inc edx
.until edx>=[esi].NumberOfNames
jmp _ret
@@:
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]
add eax,_hModule
mov @ret,eax
_ret:
assume esi:nothing
popad
mov eax,@ret
ret
_getApi endp
start:
mov eax,dword ptr [esp]
invoke _getKernelBase,eax
mov hKernel32Base,eax
invoke _getApi,hKernel32Base,addr szGetProcAddr
mov lpGetProcAddr,eax
mov _getProcAddress,eax
invoke _getProcAddress,hKernel32Base,addr szLoadLib
mov _loadLibrary,eax
invoke _loadLibrary ,addr user32_DLL
mov hUser32Base,eax
invoke _getProcAddress,hUser32Base,addr szMessageBox
mov _messageBox,eax
invoke _messageBox,NULL,offset szText,NULL,MB_OK
ret
end start
将源程序保存为hello1.asm,然后普通的编译链接,发现没有按照预期弹出信息框,经过调试,发现运行后不久就抛出异常。
因为我们把变量定义在代码区,而代码区是不可写的,所以需要修改编译生成的exe的代码区的属性。或者,改变链接的指令。
方便起见,我们直接修改链接指令。
完整指令如下:
C:\Users\17724\Desktop\214>ml -c -coff hello1.asm
Microsoft (R) Macro Assembler Version 12.00.21005.1
Copyright (C) Microsoft Corporation. All rights reserved.
Assembling: hello1.asm
***********
ASCII build
***********
C:\Users\17724\Desktop\214>link -subsystem:windows -section:.text,ERW hello1.obj
Microsoft (R) Incremental Linker Version 12.00.21005.1
Copyright (C) Microsoft Corporation. All rights reserved.
C:\Users\17724\Desktop\214>hello1.exe
如此,就会弹出信息框了。
使用PeInfo查看,可见是没有导入表的。
但是有重定位信息,简单理解就是,手动修改PE文件的加载基址以后,程序就不能正常运行。
为了改善这个方案,可以采用重定位编程。
2.无重定位的HelloWorld
.386
.model flat,stdcall
option casemap:none
include windows.inc
_QLGetProcAddress typedef proto :dword,:dword
_ApiGetProcAddress typedef ptr _QLGetProcAddress
_QLLoadLib typedef proto :dword
_ApiLoadLib typedef ptr _QLLoadLib
_QLMessageBoxA typedef proto :dword,:dword,:dword,:dword
_ApiMessageBoxA typedef ptr _QLMessageBoxA
.code
szText db 'HelloWorldPE',0
szGetProcAddr db 'GetProcAddress',0
szLoadLib db 'LoadLibraryA',0
szMessageBox db 'MessageBoxA',0
szExitProcess db 'ExitProcess',0
user32_DLL db 'user32.dll',0,0
_getProcAddress _ApiGetProcAddress ?
_loadLibrary _ApiLoadLib ?
_messageBox _ApiMessageBoxA ?
hKernel32Base dd ?
hUser32Base dd ?
lpGetProcAddr dd ?
lpLoadLib dd ?
_getKernelBase proc _dwKernelRetAddress
local @dwRet
pushad
mov @dwRet,0
mov edi,_dwKernelRetAddress
and edi,0ffff0000h
.repeat
.if word ptr [edi] == IMAGE_DOS_SIGNATURE
mov esi,edi
add esi,[esi+003ch]
.if word ptr [esi] == IMAGE_NT_SIGNATURE
mov @dwRet,edi
.break
.endif
.endif
sub edi,010000h
.break .if edi<070000000h
.until FALSE
popad
mov eax,@dwRet
ret
_getKernelBase endp
_getApi proc _hModule,_lpApi
local @ret
local @dwLen
pushad
mov @ret,0
mov edi,_lpApi
mov ecx,-1
xor al,al
cld
repnz scasb
mov ecx,edi
sub ecx,_lpApi
mov @dwLen,ecx
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
add ebx,_hModule
xor edx,edx
.repeat
push esi
mov edi,[ebx]
add edi,_hModule
mov esi,_lpApi
mov ecx,@dwLen
repz cmpsb
.if ZERO?
pop esi
jmp @F
.endif
pop esi
add ebx,4
inc edx
.until edx>=[esi].NumberOfNames
jmp _ret
@@:
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]
add eax,_hModule
mov @ret,eax
_ret:
assume esi:nothing
popad
mov eax,@ret
ret
_getApi endp
start:
mov eax,dword ptr [esp]
push eax
call @F
@@:
pop ebx
sub ebx,offset @B
pop eax
;获取kernel32.dll的基址
invoke _getKernelBase,eax
mov [ebx + offset hKernel32Base],eax
;获取ProcAddress的函数地址
mov edx,ebx
add edx,offset szGetProcAddr
invoke _getApi,eax,edx
mov [ebx + offset _getProcAddress],eax
;获取LoadLibrary的函数地址
mov edx,ebx
add edx,offset szLoadLib
push edx
push [ebx + offset hKernel32Base]
call eax
;下面这一段和上面等效,其实自己写的_getApi和系统库的GetProcAddress一个效果
;mov edx,ebx
;add edx,offset szLoadLib
;invoke _getApi,[ebx + offset hKernel32Base],edx
mov [ebx+offset _loadLibrary],eax
;加载user32.dll
mov edx,ebx
add edx,offset user32_DLL
push edx
call eax
mov [ebx + offset hUser32Base],eax
;找到MessageBox的函数地址
mov edx,ebx
add edx,offset szMessageBox
invoke _getApi,eax,edx
;下面这一段和上面等效
;mov ecx,ebx
;add ecx,offset szMessageBox
;push ecx
;push eax
;call [ebx + offset _getProcAddress]
mov ecx,ebx
add ecx,offset szText
push MB_OK
push NULL
push ecx
push NULL
call eax
;不加上ExitProcess的话,关闭信息框后进程还挂在后台
mov edx,ebx
add edx,offset szExitProcess
invoke _getApi,[ebx + offset hKernel32Base],edx
push NULL
call eax
end start
保存为hello2.asm文件,和之前的一样编译链接即可。
取巧之处在于,利用call @F等一系列指令,将start函数的指令地址保存到了ebx寄存器里面,以后使用变量,就可以用偏移的方式,这样就不会引入绝对地址了。
效果,也就是修改PE文件的基址,也能正常运行。
但是,虽然如此,hello2.exe还是有重定位表的。