WIN32汇编语言教程:第13章 进程控制 · 13.4 进程的隐藏(4)
call @F
@@:
pop ebx
sub ebx,offset @B
;********************************************************************
invoke _ZeroMemory,addr @stWndClass,sizeof @stWndClass
_invoke [ebx + _lpLoadCursor],0,IDC_ARROW
mov @stWndClass.hCursor,eax
push [ebx + _hInstance]
pop @stWndClass.hInstance
mov @stWndClass.cbSize,sizeof WNDCLASSEX
mov @stWndClass.style,CS_HREDRAW or CS_VREDRAW
lea eax,[ebx + offset _ProcWinMain]
mov @stWndClass.lpfnWndProc,eax
mov @stWndClass.hbrBackground,COLOR_WINDOW + 1
lea eax,[ebx + offset _szClassName]
mov @stWndClass.lpszClassName,eax
lea eax,@stWndClass
_invoke [ebx + _lpRegisterClassEx],eax
;********************************************************************
; 建立并显示窗口
;********************************************************************
lea eax,[ebx + offset _szClassName]
lea ecx,[ebx + offset _szCaptionMain]
_invoke [ebx + _lpCreateWindowEx],WS_EX_CLIENTEDGE,eax,ecx,\
WS_OVERLAPPEDWINDOW,\
100,100,600,400,\
NULL,NULL,[ebx + _hInstance],NULL
mov [ebx + _hWinMain],eax
_invoke [ebx + _lpShowWindow],[ebx + _hWinMain],SW_SHOWNORMAL
_invoke [ebx + _lpUpdateWindow],[ebx + _hWinMain]
;********************************************************************
; 消息循环
;********************************************************************
.while TRUE
lea eax,@stMsg
_invoke [ebx + _lpGetMessage],eax,NULL,0,0
.break .if eax == 0
lea eax,@stMsg
_invoke [ebx + _lpTranslateMessage],eax
lea eax,@stMsg
_invoke [ebx + _lpDispatchMessage],eax
.endw
ret
_WinMain endp
REMOTE_CODE_END equ this byte
REMOTE_CODE_LENGTH equ offset REMOTE_CODE_END - offset REMOTE_CODE_START
;####################################################################
在上面的代码中,所有对API函数的调用被换成了对函数入口地址的调用,因为入口地址被存放在全局变量中,所以要用call [ebx + XXXX]的格式调用以解决重定位问题,但是这样的话就无法使用invoke伪指令了。
因为用一大堆的push指令来压入参数太麻烦,笔者写了一个宏来自动压入参数,宏的名称定为_invoke,宏定义存放在Macro.inc文件中。文件的内容是:
;####################################################################
; 将参数列表的顺序翻转
;####################################################################
reverseArgs macro arglist:VARARG
local txt,count
txt TEXTEQU <>
count = 0
for i,<arglist>
count = count + 1
txt TEXTEQU @CatStr(i,<!,>,<%txt>)
endm
if count GT 0
txt SUBSTR txt,1,@SizeStr(%txt)-1
endif
exitm txt
endm
;####################################################################
; 建立一个类似于 invoke 的 Macro
;####################################################################
_invoke macro _Proc,args:VARARG
local count
count = 0
% for i,< reverseArgs( args ) >
count = count + 1
push i
endm
call dword ptr _Proc
endm
;####################################################################
远程代码中的线程函数是_RemoteThread,在这里程序首先获取要用到的API函数的地址,所有API函数的名称被放到了一系列相连的字符串中,最后以一个附加的0结束,这样可以很方便地通过循环来处理它们。
要获取函数地址必须使用LoadLibrary,GetProcAddress和GetModuleHandle函数,但这些函数地址又从哪里得到呢(这就好像一个“先有鸡,还是先有蛋”的问题),幸亏这些函数都存在于Kernel32库中,Kernel32.dll库文件和User32.dll,Gdi32.dll一样,都是最常用的库,在不同的进程中,系统会将它们装入到同样的地址中,所以对于它们来说,在本地进程中获取的地址可以用在远程线程中。
完成获取API函数地址的工作后,就可以调用_WinMain函数来创建窗口了,注意在_WinMain函数的后面不能使用ExitProcess函数来结束进程,这样会将整个Explorer.exe结束掉,必须使用ret指令来结束线程。
_WinMain函数和其他的相关代码改编自第04章中的FirstWindow.asm程序,只不过是将程序中所有涉及全局变量的指令全部改成了以ebx为基址的指令而已。另外,在所有的子程序的开始处,都加上了call/pop/sub这3句用来计算偏移差的指令。
完成远程线程的代码后,现在来看如何将这段代码装载到目标进程中,装载代码存放在RemoteThread.asm中:
.386
.model flat, stdcall
option casemap :none
;####################################################################
; Include 文件定义
;####################################################################
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
include Macro.inc
;####################################################################
; 数据段
;####################################################################
.data ?
1pLoadLibrary dd ?
1pGetProcAddress dd ?
1pGetModu1eHandle dd ?
dwProcessID dd ?
dwThreadID dd ?
hProcess dd ?
lpRemoteCode dd ?
dwTemp dd ?