远程线程指把当前进程部分代码注入到其他进程做为线程执行,虽然钩子程序能挂钩其他程序的消息,但钩子程序退出,注入的dll也就退出了,而远程线程不会 随着本地进程退出而结束。而且可以处理更多的事情,而不局限于消息。由于98不支持所以只能在nt内核上运行,下面是制作远程线程需要使用的api。
获取进程句柄方法之一是使用GetWindowThreadProcessId函数,这个函数可以从一个窗口句柄获得创建窗口进程的id,而获得一个窗口句柄可以用FindWindow轻易得到。
HWND FindWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName);
lpClassName, // 窗口类名称,可以指定为NULL,光指定窗口名称即可
lpWindowName // 窗口名称。
如果两个参数都为0,则获得最顶层窗口的句柄。
DWORD GetWindowThreadProcessId(HWND hWnd,LPDWORD lpdwProcessId);
Hwnd 进程拥有的窗口句柄
LpdwProcessID 指向用来存放进程ID的变量
得到进程ID 之后,可以使用 OpenProcess函数来获得进程句柄
HANDLE OpenProcess(DWORD dwDesiredAccess,BOOL bInheritHandle,DWORD dwProcessId);
DwDesiredAccess,对打开的进程的访问权限,可以是下列值的组合:
PROCESS_ALL_ACCESS--------------------等于下面所有权限的组合
PROCESS_CREATE_THREAD-----------------允许创建远程线程
PROCESS_DUP_HANDLE--------------------允许进程句柄被复制
PROCESS_QUERY_INFORMATION-------------允许使用GetExitCodeProcess函数查询和GetProrityClass 查询进程信息
PROCESS_SET_INFORMATION---------------允许使用SetPriorityClass函数设置进程的优先级
PROCESS_TERMINATE----------------------允许结束进程
PROCESS_VM_OPERATION-------------------允许使用WriteProcessMemory函数或者VirtualProtectEx来修改进程的地址空间
PROCESS_VM_READ------------------------允许对读取进程地址空间
PROCESS_VM_WRITE-----------------------允许使用写入进程地址空间
BInheritHandle参数,指定返回的进程句柄是否可以被当前进程的子进程继承
DwProcessId 参数指定目标进程的进程ID
还有一种方法是使用CreateToolhelp32Snapshot(快照)函数来获得进程句柄,上面的方法进程必须要有窗口,而快照函数不需要进程拥有窗口,暂不介绍
下面是读写进程数据的两个api函数:
BOOL ReadProcessMemory(
HANDLE hProcess, // 进程句柄
LPCVOID lpBaseAddress, // 要读取的目标进程起始内存
LPVOID lpBuffer, // 本地进程用来存放读取内容的数据缓冲区
SIZE_T nSize, // 要从目标进程读取得数据长度
SIZE_T * lpNumberOfBytesRead // 要读出的到本地的数量,为NULL则忽略这个参数
BOOL WriteProcessMemory(
HANDLE hProcess, // handle to process
LPVOID lpBaseAddress, // base of memory area
LPVOID lpBuffer, // data buffer
SIZE_T nSize, // count of bytes to write
SIZE_T * lpNumberOfBytesWritten // count of bytes written
);
要注入远程线程,必须要在目标进程中开辟一段空间,来存放远程线程代码。
LPVOID VirtualAllocEx(
HANDLE hProcess, // 要开辟内存的进程
LPVOID lpAddress, // 从进程那个地址开始分配,为NULL,则系统决定
SIZE_T dwSize, // 要分配的空间大小
DWORD flAllocationType, // 分配的类型,一般用 MEM_COMMIT即可
DWORD flProtect // 这段内存访问的权限,PAGE_EXECUTE_READWRITE,远程线程所处空间必须可读可执行
);
下面是注入远程线程的需要使用的函数:
HANDLE CreateRemoteThread(
HANDLE hProcess, // 要写入远程线程进程句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程安全属性
DWORD dwStackSize, // 初始化堆栈大小
LPTHREAD_START_ROUTINE lpStartAddress, // 远程线程函数
LPVOID lpParameter, // 远程线程参数
DWORD dwCreationFlags, // 标志,可以创建挂起的线程等等
LPDWORD lpThreadId // 用来返回线程ID的指针
);
代码重定位
有 了这些函数就可以把一段代码插入到目标进程,这段代码将作为目标进程中一个独立的线程运行。但是代码编译时,全局变量、Api函数等等,将被编译为地址形 式,这是地址对于本地进程是可读可执行的,对于目标进程,读取这些地址是非法的,windows这样做,可以保证每个进程都拥有自己独立的4GB空间,而 不互相干扰(处于ring3的进程互相访问是非法的)。对于所有的高级语言,包括C语言,根本不能解决重定位问题,程序只能先写一个dll文件,然后用 CreateRemoteThread 把LoadLibrary 函数注入到目标进程中。LoadLibrary 函数调用dll 文件,执行自己想要的功能,不过这样用一些进程工具可以看到目标进程多了一个dll。重定位是汇编语言的拿手好戏。
Call @F
@@:
pop ebx
sub ebx,offset @B
现在 ebx 即得到了代码的实际地址和汇编地址之间的偏差,所以在需要重定位的代码上加上这个偏移值即可。
远程线程小例子
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;使用远程线程注入到explorer中,避免出现在win2k任务管理器中,并实现看护win2k进程,发现进程退出
;马上启动同样的另一个进程
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 使用 nmake 或下列命令进行编译和链接:
; ml /c /coff RemoteThread.asm
; rc RemoteThread.rc
; Link /subsystem:windows RemoteThread.obj RemoteThread.res
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
include macro.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
KnlOpenProcessStr db 'OpenProcess',0
KnlWaitForObjectStr db 'WaitForSingleObject',0
KnlWinExecStr db 'WinExec',0
KnlGetModuleHandleStr db 'GetModuleHandleA',0
KnlGetProcAddressStr db 'GetProcAddress',0
FileName db 'nodead.exe',0
szDllKernel db 'Kernel32.dll',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;下面两个变量是explorer.exe 进程窗口的类名(RegisterClassA参数中设定的)和进程标题名字
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
szDesktopClass db 'Progman',0
szDesktopWindow db 'Program Manager',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
KnlOpenProcess dd ?
KnlWaitForSingleObject dd ?
KnlWinExec dd ?
KnlGetModuleHandle dd ?
KnlGetProcAddress dd ?
dwProcessID dd ?
dwThreadID dd ?
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include RemoteCode.asm
Start:
invoke GetModuleHandle,addr szDllKernel
mov ebx,eax
invoke GetProcAddress,ebx,offset KnlOpenProcessStr
mov KnlOpenProcess,eax
invoke GetProcAddress,ebx,offset KnlWaitForObjectStr
mov KnlWaitForSingleObject,eax
invoke GetProcAddress,ebx,offset KnlWinExecStr
mov KnlWinExec,eax
invoke GetProcAddress,ebx,offset KnlGetProcAddressStr
mov KnlGetProcAddress,eax
invoke GetProcAddress,ebx,offset KnlGetModuleHandleStr
mov KnlGetModuleHandle,eax
invoke FindWindow,addr szDesktopClass,addr szDesktopWindow
invoke GetWindowThreadProcessId,eax,offset dwProcessID
mov dwThreadID,eax
invoke OpenProcess,PROCESS_ALL_ACCESS,FALSE,dwProcessID
test eax,eax
jz OpenProcessError
mov ebx,eax
invoke VirtualAllocEx,ebx,NULL,REMOTE_CODE_LENGTH,MEM_COMMIT,PAGE_EXECUTE_READWRITE
or eax,eax
jz OpenProcessError
mov edi,eax
push eax
invoke WriteProcessMemory,ebx,edi,offset REMOTE_CODE_START,REMOTE_CODE_LENGTH,NULL
invoke WriteProcessMemory,ebx,edi,offset KnlOpenProcess,sizeof dword * 5,NULL
add edi,offset Protect2kProc - offset REMOTE_CODE_START
invoke CreateRemoteThread,ebx,NULL,0,edi,0,0,NULL
invoke CloseHandle,ebx
invoke Sleep,100h
invoke MessageBoxA,0,offset FileName,offset FileName,0
OpenProcessError:
invoke ExitProcess,0
end Start
macro.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 将参数列表的顺序翻转
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
reverseArgs macro arglist:VARARG
local txt,count
txt TEXTEQU <>
count = 0
for i,
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.asm
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 注入远程进程执行的代码
;equ this [:类型],则变量包含的段和偏移地址都和下一句相同,类型指变量类型
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
REMOTE_CODE_START equ this byte
_KnlOpenProcess dd ?
_KnlWaitForSingleObject dd ?
_KnlWinExec dd ?
_KnlGetModuleHandle dd ?
_KnlGetProcAddress dd ?
_KnlSleep dd ?
_Error2 db 'overflow2',0;循环把这个字符串覆盖了?
_FileName db 'c:\wap32.exe',0; 要看护的进程路径
_WinName db 'Our First Dialog Box',0;要看护的进程窗口名字
_hInstance dd ?
_KnlFindWindow dd ?
_KnlMessageBox dd ?
_ErrorMsg db 'overflow',0 ;循环把这个字符串覆盖了?
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 要从User32 中提取使用的api函数名称
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_szDllUser db 'user32.dll',0
_szDllKernel db 'kernel32.dll',0
szFindWindow db 'FindWindowA',0
szMessageBox db 'MessageBoxA',0,0 ;多一个0用于结束循环
szSleep db 'Sleep',0,0
Protect2kProc proc uses ebx edi esi
local hModuleUser
local hModuleKernel
call @F
@@:
pop ebx
sub ebx,offset @B
_invoke [ebx+ _KnlGetModuleHandle],NULL
test eax,eax
jz ExitProtectProc
mov [ebx+ _hInstance],eax
lea eax,[ebx+ offset _szDllUser]
_invoke [ebx+_KnlGetModuleHandle],eax
mov hModuleUser,eax
lea esi,[ebx+offset szFindWindow]
lea edi,[ebx+offset _KnlFindWindow]
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 从User32.dll中取api函数地址的循环
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.while TRUE
_invoke [ebx+_KnlGetProcAddress],hModuleUser,esi
mov [edi],eax
add edi,4
@@:
lodsb
or al,al
jnz @B
.break .if ! byte ptr [esi+1]
.endw
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 从Kernel32.dll中取api函数地址的循环
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
lea eax,[ebx+ offset _szDllKernel]
_invoke [ebx+_KnlGetModuleHandle],eax
mov hModuleKernel,eax
lea esi,[ebx+offset szSleep]
lea edi,[ebx+offset _KnlSleep]
.while TRUE
_invoke [ebx+_KnlGetProcAddress],hModuleKernel,esi
mov [edi],eax
add edi,4
@@:
lodsb
or al,al
jnz @B
.break .if ! byte ptr [esi+1]
.endw
call _WinMain
ExitProtectProc:
ret
Protect2kProc endp
_WinMain proc uses ebx esi edi
call @F
@@:
pop ebx
sub ebx,@B
lea edi,[ebx+ offset _ErrorMsg]
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 看护循环,发现进程退出马上重启一个
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.while TRUE
lea esi,[ebx+offset _WinName]
_invoke [ebx+_KnlFindWindow],NULL,esi
.if ! eax
lea esi,[ebx+ offset _FileName]
_invoke [ebx+_KnlWinExec],esi,SW_SHOWNORMAL
.endif
_invoke [ebx+_KnlSleep],110h
.endw
ret
_WinMain endp
REMOTE_CODE_END equ this byte
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;REMOTE_CODE_LENGTH 获取整个插入代码的长度,在nodead.asm中,VirtualAllocEx 中制定远程线程
;长度可以使用此参数
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
REMOTE_CODE_LENGTH equ offset REMOTE_CODE_END - offset REMOTE_CODE_START