(转载请注明博客地址:http://blog.csdn.net/hitetoshi)
前面我们经常提到SSDT,那么什么是SSDT呢?SSDT(SystemServices Descriptor Table):系统服务描述符表。要对它有清楚的认识,我们先以用户态下调用CreateThread来举个例子。
如果你有兴趣,可以用OllyDbg在CreateThread上下断点,你会发现,经过一些简单的处理,CreateThread会调用CreateRemoteThread,按F7进入CreateRemoteThread,再经过一段跟踪,会发现CreateRemoteThread实际上又调用ZwCreateThread,再按F7跟踪进去,ZwCreateThread的代码非常简单:
7C92D1AE > B8 35000000 MOV EAX,35
7C92D1B3 BA 0003FE7F MOVEDX,7FFE0300
7C92D1B8 FF12 CALL DWORD PTR DS:[EDX]
7C92D1BA C2 2000 RETN 20
注意MOV EDX,7FFE0300/CALL DWORD PTRDS:[EDX]这两条指令,从OD上可以看到,这实际上是调用ntdll.dll中的KiFastSystemCall,KiFastSystemCall的代码也很简单:
7C92E510 > 8BD4 MOV EDX,ESP
7C92E512 0F34 SYSENTER
7C92E514 > C3 RETN
如果这时候你再用F7一步一步的跟,你会发现,到SYSENTER这条指令时,OllyDbg无法跟踪进去,而当这条指令执行完时,实际上ZwCreateThread的功能已经执行完成――用户态的一切玄机似乎都在SYSENTER这条指令上。SYSENTER指令在用户态下向内核态发起系统调用,此时代码切换到内核,因此OllyDbg这样的用户态调试器是无法调试的。和KiFastSystemCall类似,ntdll.dll中也有一个KiIntSystemCall。PII以前的处理器并不支持SYSENTER这条指令,因此KiIntSystemCall以一条INT 2E指令切换到内核。
SYSENTER进入内核的KiFastCallEntry(该函数没有被ntoskrnl.exe导出,你可能需要下载ntoskrnl.exe的Symbol才能看到它),KiFastCallEntry大致进行的工作就是按照SSDT表找到对应服务函数的地址,并转入到服务函数,即内核NtCreateThread中去。SSDT包含在一个叫SDT(服务描述表)的结构中,SDT由ntoskrnl.exe(某些系统下可能不是这个文件名,例如多核处理器下可能是ntkrnlpa.exe,但为简便起见,下文都用ntoskrnl.exe来表述)以KeServiceDescriptorTable为名称导出,SDT的定义如下:
ServiceDescriptorEntry STRUCT
pvSSDTBase dd ? ;SSDT基地址
pvServiceCounterTable dd ? ;SSDT中每个服务被调用次数的表
ulNumberOfServices dd ? ;描述的服务的数目
pvParamTableBase dd ? ;每个系统服务参数的字节数的表
ServiceDescriptorEntry ENDS
我们可以在LiveKd中用d nt!KeServiceDescriptorTable来观察内核中这个结构的数据:
可见系统中有0x11C个SSDT项,基地址为0x80505480,我们来看看SSDT表的内容,用d 80505480命令:
从0x80505480开始,存放每个系统服务项的函数地址,那么怎么确定内核NtCreateThread的地址呢?答案就在用户态下ZwCreateThread的mov eax,0x35这条指令,它指明了NtCreateThread在SSDT中是第0x35项,计算一下0x80505480+0x35*4=0x80505554,看一看:
大约NtCreateThread应该在0x805D2018这个地址,用u nt!NtCreateThread命令看一下:
正是这个地址!(大家肯定会比较奇怪NtCreateThread的第一条指令怎么会是Jmp,我也是今天写这篇稳当时才发现我的NtCreateThread居然被Inline hook了!NND!一看模块,IsDrv122.sys,原来是开着冰刃……)。
出了冰刃这个事件也好,也就是我想跟大家说的Hook SSDT,刚才大家已经看到,冰刃对SSDT的NtCreateThread做了Inline hook,我曾经说过,我极不推荐做Inline hook,除非你技术实力非常强大。原因我会在后面的文章介绍。这里我们有一个思路,如果把0x80505554这个地址的内容替换了,换成我们的函数,不是一样达到HookNtCreateThread的效果了吗?这就是SSDT Hook。在SSDT上做Hook或者是InlineHook是非常强大的,因为用户态上所有CreateThread的调用最终都会进入这个函数。很多杀毒软件会Hook SSDT的这个位置,用来监视是否有进程试图用CreateRemoteThread向其它进程启动远程线程,这是防止远程线程的一个很有效的方法。不过我们Hook NtCreateThread的目的不是这个,我前面已经说了,我们要用它来监视进程的创建,并向进程中注入代码。
今天的这篇文章我并不会完成Hook SSDT的代码,因为在我们的内核程序中还有很多事情要做:获取NtCreateThread的服务序号、获取保存NtCreateThread地址的位置以及获取NtCreateThread的地址。
我们在上次的DynamicHook.asm的DriverEntry中加一些代码(红色部分是新加的代码):
DriverEntry proc pDriverObject:PDRIVER_OBJECT,pusRegistryPath:PUNICODE_STRING
local status:NTSTATUS
local pDeviceObject:PDEVICE_OBJECT
mov status,STATUS_DEVICE_CONFIGURATION_ERROR
invoke IoCreateDevice,pDriverObject,0,addrg_usDeviceName,FILE_DEVICE_UNKNOWN,0,FALSE,addr pDeviceObject
.if eax==STATUS_SUCCESS
invoke IoCreateSymbolicLink,addrg_usSymbolicLinkName,addr g_usDeviceName
.if eax==STATUS_SUCCESS
mov eax,pDriverObject
assume eax:ptr DRIVER_OBJECT
mov [eax].DriverUnload,offset DriverUnload
mov [eax].MajorFunction[IRP_MJ_CREATE*(sizeof PVOID)],offsetDispatchCreateClose
mov [eax].MajorFunction[IRP_MJ_CLOSE*(sizeof PVOID)],offsetDispatchCreateClose
invoke DbgPrint,$CTA0("Driver entry")
invoke HookNtCreateThread
NT_SUCCESS eax
.if eax
invoke DbgPrint,$CTA0("Hook success")
mov status,STATUS_SUCCESS
.else
invoke DbgPrint,$CTA0("Hook failure")
.endif
assume eax:nothing
.else
invoke IoDeleteDevice,pDeviceObject
.endif
.endif
@@:
mov eax,status
ret
DriverEntry endp
在.const节中加入:
CCOUNTED_UNICODE_STRING "NtCreateThread",g_usNtCreateThread,4
完成HookNtCreateThread函数:
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
HookNtCreateThread proc
local dwNtCreateThreadIndex
local dwNtCreateThreadAddress
local status:NTSTATUS
mov status,STATUS_UNSUCCESSFUL
;获取NtCreateThread服务序号
invoke GetSysCallIndex,addrg_usNtCreateThread
.if eax
mov dwNtCreateThreadIndex,eax
;获取NtCreteThread在内核中的地址
invoke GetSSDTOrigValue,dwNtCreateThreadIndex
.if eax
mov dwNtCreateThreadAddress,eax
invoke DbgPrint,$CTA0("NtCreateThreadindex:%08X,address:%08X"),dwNtCreateThreadIndex,dwNtCreateThreadAddress
mov status,STATUS_SUCCESS
.endif
.endif
mov eax,status
ret
HookNtCreateThread endp
为了完成GetSysCallIndex和GetSSDTOrigValue这两个函数,我增加了PEFile.asm和SSDT.asm两个文件以及对应的PEFile.inc、SSDT.inc和DyanmicHook.inc,你把这些文件加入进去时,注意要向上次那样设置PEFile.asm和SSDT.asm的汇编选项,设置各个文件的依赖项(自己看include去设置,就不详述了)。先把这五个文件的内容贴出来:
;PEFile.asm
.386
.model flat,stdcall
option casemap:none
include ntddk.inc
include native.inc
include hal.inc
include ntoskrnl.inc
include strings.mac
include DynamicHook.inc
include PEFile.inc
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
GetAlignedSize proc uses edx ecx dwSize,dwAlignment
xor edx,edx
mov eax,dwSize
mov ecx,dwAlignment
div ecx
.if edx==0
mov eax,dwSize
.else
inc eax
mul dwAlignment
.endif
ret
GetAlignedSize endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
GetTotalImageSize proc uses edi edx ecx pDosHeader:PIMAGE_DOS_HEADER,pNtHeaders:PIMAGE_NT_HEADERS
local dwResult:DWORD
local dwAlignment:DWORD
local dwSections:DWORD
and dwResult,0
mov edi,pNtHeaders
assume edi:ptrIMAGE_NT_HEADERS
M2M dwAlignment,[edi].OptionalHeader.SectionAlignment
xor edx,edx
mov eax,[edi].OptionalHeader.SizeOfHeaders
mov ecx,dwAlignment
div ecx
.if edx==0
mov eax,[edi].OptionalHeader.SizeOfHeaders
add dwResult,eax
.else
inc eax
mul dwAlignment
add dwResult,eax
.endif
mov edi,pNtHeaders
assume edi:ptrIMAGE_NT_HEADERS
movzx eax,[edi].FileHeader.NumberOfSections
mov dwSections,eax
add edi,sizeofIMAGE_NT_HEADERS
assume edi:ptrIMAGE_SECTION_HEADER
xor edx,edx
.while edx<dwSections
push edx
mov eax,[edi].Misc.VirtualSize
.if eax
xor edx,edx
mov ecx,dwAlignment
div ecx
.if edx==0
mov eax,[edi].Misc.VirtualSize
add dwResult,eax
.else
inc eax
mul dwAlignment
add dwResult,eax
.endif
.endif
pop edx
add edi,sizeofIMAGE_SECTION_HEADER
inc edx
.endw
assume edi:nothing
mov eax,dwResult
ret
GetTotalImageSize endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
GetPEInfo proc uses edi esi pBase:PVOID,ppDosHeader:ptr PIMAGE_DOS_HEADER,ppNtHeaders:ptrPIMAGE_NT_HEADERS
local status:NTSTATUS
mov status,STATUS_UNSUCCESSFUL
mov edi,pBase
assume edi:ptrIMAGE_DOS_HEADER
.if [edi].e_magic!=IMAGE_DOS_SIGNATURE
jmp @F
.endif
mov esi,ppDosHeader
.if esi
mov dwordptr [esi],edi
.endif
add edi,[edi].e_lfanew
assume edi:ptrIMAGE_NT_HEADERS
.if [edi].Signature!=IMAGE_NT_SIGNATURE
jmp @F
.endif
mov esi,ppNtHeaders
.if esi
mov dwordptr [esi],edi
.endif
mov status,STATUS_SUCCESS
@@:
mov eax,status
ret
GetPEInfo endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
KLoadPEFile proc uses edi esi pBaseAddress:PVOID,pLoadAddress:PVOID
local status:NTSTATUS
local pDosHeader:PIMAGE_DOS_HEADER
local pNtHeaders:PIMAGE_NT_HEADERS
local pPtr:PVOID
local dwSections:DWORD
local dwAlignment:DWORD
mov status,STATUS_UNSUCCESSFUL
invoke KeGetCurrentIrql
.if eax!=PASSIVE_LEVEL
mov status,STATUS_INVALID_LEVEL
jmp @F
.endif
M2M pPtr,pLoadAddress
invoke GetPEInfo,pBaseAddress,addrpDosHeader,addr pNtHeaders
.if eax==STATUS_SUCCESS
mov edi,pNtHeaders
assume edi:ptrIMAGE_NT_HEADERS
invoke memcpy,pPtr,pBaseAddress,[edi].OptionalHeader.SizeOfHeaders
M2M dwAlignment,[edi].OptionalHeader.SectionAlignment
invoke GetAlignedSize,[edi].OptionalHeader.SizeOfHeaders,dwAlignment
add pPtr,eax
movzx eax,[edi].FileHeader.NumberOfSections
mov dwSections,eax
add edi,sizeofIMAGE_NT_HEADERS
assume edi:ptrIMAGE_SECTION_HEADER
xor edx,edx
.while edx<dwSections
push edx
mov eax,[edi].SizeOfRawData
.if eax>0
.if eax>[edi].Misc.VirtualSize
mov eax,[edi].Misc.VirtualSize
.endif
mov esi,pBaseAddress
add esi,[edi].PointerToRawData
invoke memcpy,pPtr,esi,eax
invoke GetAlignedSize,[edi].Misc.VirtualSize,dwAlignment
add pPtr,eax
.endif
pop edx
add edi,sizeof IMAGE_SECTION_HEADER
inc edx
.endw
mov status,STATUS_SUCCESS
assume edi:nothing
.endif
@@:
mov eax,status
ret
KLoadPEFile endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;装载PE文件到内存并对齐
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
KLoadLibrary proc pusFileName:PUNICODE_STRING
local oa:OBJECT_ATTRIBUTES
local iosb:IO_STATUS_BLOCK
local hFile:HANDLE
local dwFileSize:DWORD
local fsi:FILE_STANDARD_INFORMATION
local pFile:PVOID
local pDosHeader:PIMAGE_DOS_HEADER
local pNtHeaders:PIMAGE_NT_HEADERS
local dwImageSize
local pImage:PVOID
and pImage,0
invoke KeGetCurrentIrql
.if eax!=PASSIVE_LEVEL
jmp @F
.endif
InitializeObjectAttributes addr oa,pusFileName,OBJ_CASE_INSENSITIVE orOBJ_KERNEL_HANDLE,NULL,NULL
invoke ZwOpenFile,addrhFile,FILE_READ_DATA or FILE_EXECUTE or SYNCHRONIZE,addr oa,addriosb,FILE_SHARE_READ,FILE_SYNCHRONOUS_IO_NONALERT
NT_SUCCESS eax
.if eax
invoke RtlZeroMemory,addrfsi,sizeof fsi
invoke ZwQueryInformationFile,hFile,addriosb,addr fsi,sizeof fsi,FileStandardInformation
NT_SUCCESS eax
.if eax
M2M dwFileSize,fsi.EndOfFile.LowPart
;分配内存
invoke ExAllocatePool,PagedPool,dwFileSize
.if eax
mov pFile,eax
invoke ZwReadFile,hFile,0,NULL,NULL,addr iosb,pFile,dwFileSize,0,NULL
NT_SUCCESS eax
.if eax
invoke GetPEInfo,pFile,addr pDosHeader,addrpNtHeaders
.if eax==STATUS_SUCCESS
invoke GetTotalImageSize,pDosHeader,pNtHeaders
.if eax
mov dwImageSize,eax
invoke ExAllocatePool,PagedPool,dwImageSize
.if eax
mov pImage,eax
invoke KLoadPEFile,pFile,pImage
.if eax!=STATUS_SUCCESS
invoke ExFreePool,pImage
and pImage,0
.endif
.endif
.endif
.endif
.endif
invoke ExFreePool,pFile
.endif
.endif
invoke ZwClose,hFile
.endif
@@:
mov eax,pImage
ret
KLoadLibrary endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
GetPEImageBase proc uses edi pBaseAddress:PVOID
local dwReturn:DWORD
and dwReturn,0
mov edi,pBaseAddress
assume edi:ptrIMAGE_DOS_HEADER
.if [edi].e_magic!=IMAGE_DOS_SIGNATURE
jmp @F
.endif
add edi,[edi].e_lfanew
assume edi:ptrIMAGE_NT_HEADERS
.if [edi].Signature!=IMAGE_NT_SIGNATURE
jmp @F
.endif
M2M dwReturn,[edi].OptionalHeader.ImageBase
assume edi:nothing
@@:
mov eax,dwReturn
ret
GetPEImageBase endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
GetFuncInfoByName proc uses esi edi ecx pusFuncName:PUNICODE_STRING,pBaseAddress:PVOID,pOrdinal:PDWORD
local dwAddress
local lpAddressOfNames
local lpAddressOfNameOrdinals
local dwIndex
local usExportFunctionName:UNICODE_STRING
local ansExportFunctionName:ANSI_STRING
xor eax,eax
mov dwAddress,eax
mov esi,pOrdinal
.if esi
mov dwordptr [esi],0
.endif
invoke KeGetCurrentIrql
.if eax!=PASSIVE_LEVEL
jmp _RETURN
.endif
mov esi,pBaseAddress
assume esi:ptrIMAGE_DOS_HEADER
add esi,[esi].e_lfanew
assume esi:ptrIMAGE_NT_HEADERS
mov eax,[esi].OptionalHeader.DataDirectory.VirtualAddress
.if !eax
jmp _RETURN
.endif
add eax,pBaseAddress
mov edi,eax
assume edi:ptrIMAGE_EXPORT_DIRECTORY
mov eax,[edi].AddressOfNames
add eax,pBaseAddress
mov lpAddressOfNames,eax
mov eax,[edi].AddressOfNameOrdinals
add eax,pBaseAddress
mov lpAddressOfNameOrdinals,eax
mov eax,[edi].AddressOfFunctions
add eax,pBaseAddress
mov esi,eax
mov ecx,[edi].NumberOfFunctions
mov dwIndex,0
@@:
pushad
mov eax,dwIndex
push edi
mov ecx,[edi].NumberOfNames
cld
mov edi,lpAddressOfNameOrdinals
repnz scasw
.if ZERO?
sub edi,lpAddressOfNameOrdinals
sub edi,2
shl edi,1
add edi,lpAddressOfNames
mov eax,dwordptr [edi]
add eax,pBaseAddress
invoke RtlInitAnsiString,addransExportFunctionName,eax
invoke RtlAnsiStringToUnicodeString,addrusExportFunctionName,addr ansExportFunctionName,TRUE
invoke RtlCompareUnicodeString,addrusExportFunctionName,pusFuncName,FALSE
push eax
invoke RtlFreeUnicodeString,addrusExportFunctionName
pop eax
.if eax==0
M2M dwAddress,dwordptr [esi]
pop edi
mov esi,pOrdinal
.if esi
mov ecx,dwIndex
add ecx,[edi].nBase
mov dword ptr [esi],ecx
.endif
popad
jmp _RETURN
.endif
.endif
pop edi
popad
add esi,4
inc dwIndex
loop @B
_RETURN:
assume esi:nothing
mov eax,dwAddress
ret
GetFuncInfoByName endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;获取DLL导出函数信息
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
GetFunctionInformation proc uses esi edi pusFuncName:PUNICODE_STRING,pusDllName:PUNICODE_STRING,pFuncInfo:PFUNCTION_INFORMATION
local status:NTSTATUS
local pBaseAddress:PVOID
xor eax,eax
mov pBaseAddress,eax
mov status,STATUS_UNSUCCESSFUL
invoke KeGetCurrentIrql
.if eax!=PASSIVE_LEVEL
mov status,STATUS_INVALID_LEVEL
jmp @F
.endif
invoke KLoadLibrary,pusDllName
.if eax
mov pBaseAddress,eax
mov esi,pFuncInfo
assume esi:ptrFUNCTION_INFORMATION
lea edi,[esi].dwOridnal
invoke GetFuncInfoByName,pusFuncName,pBaseAddress,edi
.if eax
lea edi,[esi].dwAddress
mov dword ptr [edi],eax
lea edi,[esi].byEntryCode
mov esi,[esi].dwAddress
add esi,pBaseAddress
invoke memcpy,edi,esi,16
mov status,STATUS_SUCCESS
.else
mov status,STATUS_UNSUCCESSFUL
.endif
invoke ExFreePool,pBaseAddress
assume esi:nothing
.endif
@@:
mov eax,status
ret
GetFunctionInformation endp
end
;SSDT.asm
.386
.model flat,stdcall
option casemap:none
include ntddk.inc
include native.inc
include ntoskrnl.inc
include hal.inc
include strings.mac
includePEFile.inc
include DynamicHook.inc
include SSDT.inc
.const
CCOUNTED_UNICODE_STRING "\\Systemroot\\System32\\ntdll.dll",g_usntdllFileName,4
CCOUNTED_UNICODE_STRING "KeServiceDescriptorTable",g_usKeServiceDescriptorTable,4
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;获取内核文件名
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
GetKernel proc uses edi pusFileName:PUNICODE_STRING
local dwLength:DWORD
local dwAddress:DWORD
local pBuffer
local szFileName[260]:byte
local ansFileName:ANSI_STRING
local dwBase:DWORD
and dwLength,0
and pBuffer,0
and dwAddress,0
invoke KeGetCurrentIrql
.if eax!=PASSIVE_LEVEL
jmp @F
.endif
invoke ZwQuerySystemInformation,SystemModuleInformation,NULL,0,addrdwLength
.if dwLength
invoke ExAllocatePool,PagedPool,dwLength
.if eax
mov pBuffer,eax
invoke ZwQuerySystemInformation,SystemModuleInformation,pBuffer,dwLength,addrdwLength
NT_SUCCESS eax
.if eax
mov eax,pBuffer
add eax,4
assume eax:ptr SYSTEM_MODULE_INFORMATION
M2M dwBase,[eax].Base
lea edi,[eax].ImageName
movzx eax,[eax].ModuleNameOffset
add edi,eax
assume eax:nothing
invoke RtlZeroMemory,addr szFileName,260
invoke strcpy,addr szFileName,$CTA0("\\Systemroot\\System32\\")
invoke strcat,addr szFileName,edi
invoke RtlInitAnsiString,addr ansFileName,addr szFileName
.if pusFileName!=NULL
invoke RtlAnsiStringToUnicodeString,pusFileName,addransFileName,TRUE
M2M dwAddress,dwBase
.endif
.endif
invoke ExFreePool,pBuffer
.endif
.endif
@@:
mov eax,dwAddress
ret
GetKernel endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
FindKiServiceTableEx proc uses ecx edi esi edx hModule,dwKSDT
local dwReturn:DWORD
local bFirstChunk:BOOL
local dwCount:DWORD
local dwFixups:DWORD
local dwImageBase:DWORD
and dwReturn,0
and dwFixups,0
mov edi,hModule
assume edi:ptrIMAGE_DOS_HEADER
.if [edi].e_magic!=IMAGE_DOS_SIGNATURE
jmp @F
.endif
add edi,[edi].e_lfanew
assume edi:ptrIMAGE_NT_HEADERS
.if [edi].Signature!=IMAGE_NT_SIGNATURE
jmp @F
.endif
push [edi].OptionalHeader.ImageBase
pop dwImageBase
mov eax,[edi].OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC*sizeofIMAGE_DATA_DIRECTORY].VirtualAddress
movzx ecx,[edi].FileHeader.Characteristics
and ecx,IMAGE_FILE_RELOCS_STRIPPED
.if (eax)&& !(ecx)
mov edi,hModule
add edi,eax
assume edi:ptrIMAGE_BASE_RELOCATION
mov bFirstChunk,TRUE
.while bFirstChunk|| [edi].VirtualAddress
mov bFirstChunk,FALSE
mov esi,edi
add esi,sizeof IMAGE_BASE_RELOCATION
assume esi:ptr IMAGE_FIXUP_ENTRY
mov edx,[edi].SizeOfBlock
sub edx,sizeof IMAGE_BASE_RELOCATION
ror edx,1
mov dwCount,edx
xor edx,edx
.while edx<dwCount
push edx
mov ax,word ptr [esi]
and ax,0F000h
ror ax,12
.if ax==IMAGE_REL_BASED_HIGHLOW
inc dwFixups
mov ax,word ptr [esi]
and ax,0FFFh
movzx eax,ax
add eax,[edi].VirtualAddress
add eax,hModule
mov ecx,eax
mov eax,dword ptr [eax]
sub eax,dwImageBase
.if eax==dwKSDT
mov eax,ecx
sub eax,2
mov ax,word ptr [eax]
.if ax==5C7h
mov eax,ecx
add eax,4
mov eax,dword ptr [eax]
sub eax,dwImageBase
mov dwReturn,eax
jmp @F
.endif
.endif
.endif
pop edx
inc edx
add esi,sizeof WORD
.endw
add edi,[edi].SizeOfBlock
assume esi:nothing
.endw
.endif
assume edi:nothing
@@:
mov eax,dwReturn
ret
FindKiServiceTableEx endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;获取SSDT原始值
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
GetSSDTOrigValue proc uses edi dwIndex
local dwReturn:DWORD
local usKernelFileName:UNICODE_STRING
local dwKernelBase:DWORD
local pBaseAddress:PVOID
local dwImageBase:DWORD
xor eax,eax
mov dwReturn,eax
mov pBaseAddress,eax
mov dwKernelBase,eax
invoke KeGetCurrentIrql
.if eax!=PASSIVE_LEVEL
jmp @F
.endif
invoke GetKernel,addrusKernelFileName
.if eax
mov dwKernelBase,eax
;映射PE文件
invoke KLoadLibrary,addrusKernelFileName
.if eax
mov pBaseAddress,eax
invoke GetPEImageBase,pBaseAddress
.if eax
mov dwImageBase,eax
invoke GetFuncInfoByName,addrg_usKeServiceDescriptorTable,pBaseAddress,NULL
.if eax
invoke FindKiServiceTableEx,pBaseAddress,eax
.if eax
add eax,pBaseAddress
mov edi,eax
mov eax,dwIndex
rol eax,2
add edi,eax
mov eax,dword ptr [edi]
sub eax,dwImageBase
add eax,dwKernelBase
mov dwReturn,eax
.endif
.endif
.endif
invoke ExFreePool,pBaseAddress
.endif
invoke RtlFreeUnicodeString,addrusKernelFileName
.endif
@@:
mov eax,dwReturn
ret
GetSSDTOrigValue endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;获取函数的Service Index
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
GetSysCallIndex proc pusFuncName:PUNICODE_STRING
local FuncInfo:FUNCTION_INFORMATION
invoke GetFunctionInformation,pusFuncName,addrg_usntdllFileName,addr FuncInfo
.if eax==STATUS_SUCCESS
lea eax,FuncInfo.byEntryCode
.if byteptr [eax]==0B8h
inc eax
mov eax,dword ptr [eax]
ret
.endif
.endif
xor eax,eax
ret
GetSysCallIndex endp
end
;SSDT.inc
IFNDEF __SSDT_INC__
__SSDT_INC__ equ <1>
ServiceDescriptorEntry STRUCT
pvSSDTBase dd ?
pvServiceCounterTable dd ?
ulNumberOfServices dd ?
pvParamTableBase dd ?
ServiceDescriptorEntry ENDS
GetSysCallIndex proto :PUNICODE_STRING
GetSSDTOrigValue proto :DWORD
GetKernel proto :PUNICODE_STRING
ENDIF
;PEFile.inc
IFNDEF __PEFILE_INC__
__PEFILE_INC__ equ <1>
FUNCTION_INFORMATION STRUCT
byEntryCode db 16 dup (?) ;前16字节
dwOridnal dd ?
dwAddress dd ?
FUNCTION_INFORMATION ENDS
PFUNCTION_INFORMATION typedef ptr FUNCTION_INFORMATION
IMAGE_DOS_SIGNATURE equ 5A4Dh
IMAGE_NT_SIGNATURE equ 00004550h
IMAGE_SIZEOF_SHORT_NAME equ 8
IMAGE_FILE_RELOCS_STRIPPED equ 0001h
IMAGE_REL_BASED_HIGHLOW equ 3
IMAGE_DIRECTORY_ENTRY_EXPORT equ 0
IMAGE_DIRECTORY_ENTRY_BASERELOC equ 5
IMAGE_NUMBEROF_DIRECTORY_ENTRIES equ 16
IMAGE_DOS_HEADERSTRUCT
e_magic WORD ?
e_cblp WORD ?
e_cp WORD ?
e_crlc WORD ?
e_cparhdr WORD ?
e_minalloc WORD ?
e_maxalloc WORD ?
e_ss WORD ?
e_sp WORD ?
e_csum WORD ?
e_ip WORD ?
e_cs WORD ?
e_lfarlc WORD ?
e_ovno WORD ?
e_res WORD 4 dup(?)
e_oemid WORD ?
e_oeminfo WORD ?
e_res2 WORD 10 dup(?)
e_lfanew DWORD ?
IMAGE_DOS_HEADERENDS
PIMAGE_DOS_HEADER typedef ptr IMAGE_DOS_HEADER
IMAGE_DATA_DIRECTORYSTRUCT
VirtualAddress DWORD?
isizeDWORD ?
IMAGE_DATA_DIRECTORYENDS
IMAGE_OPTIONAL_HEADER32STRUCT
Magic WORD ?
MajorLinkerVersion BYTE ?
MinorLinkerVersion BYTE ?
SizeOfCode DWORD ?
SizeOfInitializedData DWORD ?
SizeOfUninitializedData DWORD ?
AddressOfEntryPoint DWORD ?
BaseOfCode DWORD ?
BaseOfData DWORD ?
ImageBase DWORD?
SectionAlignment DWORD ?
FileAlignment DWORD ?
MajorOperatingSystemVersion WORD?
MinorOperatingSystemVersion WORD?
MajorImageVersion WORD ?
MinorImageVersion WORD ?
MajorSubsystemVersion WORD ?
MinorSubsystemVersion WORD ?
Win32VersionValue DWORD ?
SizeOfImage DWORD ?
SizeOfHeaders DWORD ?
CheckSum DWORD ?
Subsystem WORD ?
DllCharacteristics WORD ?
SizeOfStackReserve DWORD ?
SizeOfStackCommit DWORD ?
SizeOfHeapReserve DWORD ?
SizeOfHeapCommit DWORD ?
LoaderFlags DWORD ?
NumberOfRvaAndSizes DWORD ?
DataDirectory IMAGE_DATA_DIRECTORYIMAGE_NUMBEROF_DIRECTORY_ENTRIES dup(<>)
IMAGE_OPTIONAL_HEADER32ENDS
IMAGE_OPTIONAL_HEADER equ<IMAGE_OPTIONAL_HEADER32>
PIMAGE_OPTIONAL_HEADER typedef ptr IMAGE_OPTIONAL_HEADER
IMAGE_EXPORT_DIRECTORYSTRUCT
Characteristics DWORD ?
TimeDateStamp DWORD ?
MajorVersionWORD ?
MinorVersion WORD ?
nName DWORD ?
nBase DWORD ?
NumberOfFunctions DWORD ?
NumberOfNames DWORD ?
AddressOfFunctions DWORD?
AddressOfNames DWORD ?
AddressOfNameOrdinals DWORD?
IMAGE_EXPORT_DIRECTORYENDS
IMAGE_FILE_HEADERSTRUCT
Machine WORD ?
NumberOfSections WORD?
TimeDateStamp DWORD?
PointerToSymbolTable DWORD?
NumberOfSymbols DWORD?
SizeOfOptionalHeader WORD?
Characteristics WORD?
IMAGE_FILE_HEADERENDS
IMAGE_NT_HEADERSSTRUCT
Signature DWORD ?
FileHeader IMAGE_FILE_HEADER <>
OptionalHeader IMAGE_OPTIONAL_HEADER32 <>
IMAGE_NT_HEADERSENDS
PIMAGE_NT_HEADERS typedef ptr IMAGE_NT_HEADERS
IMAGE_SECTION_HEADERSTRUCT
Name1 db IMAGE_SIZEOF_SHORT_NAME dup(?)
union Misc
PhysicalAddress dd ?
VirtualSize dd ?
ends
VirtualAddress dd ?
SizeOfRawData dd ?
PointerToRawData dd ?
PointerToRelocations dd ?
PointerToLinenumbers dd ?
NumberOfRelocations dw ?
NumberOfLinenumbers dw ?
Characteristics dd ?
IMAGE_SECTION_HEADERENDS
PIMAGE_SECTION_HEADER typedef ptr IMAGE_SECTION_HEADER
IMAGE_BASE_RELOCATIONSTRUCT
VirtualAddress dd ?
SizeOfBlock dd ?
IMAGE_BASE_RELOCATIONENDS
PIMAGE_BASE_RELOCATION typedef ptr IMAGE_BASE_RELOCATION
GetFunctionInformation proto :PUNICODE_STRING,:PUNICODE_STRING,:PFUNCTION_INFORMATION
GetFuncInfoByName proto :PUNICODE_STRING,:PVOID,:PDWORD
GetPEImageBase proto :PVOID
GetPEImageLength proto :PUNICODE_STRING
KLoadLibrary proto :PUNICODE_STRING
ENDIF
;SSDT.inc
;DyanmicHook.inc
IFNDEF __DYNAMICHOOK_INC__
__DYNAMICHOOK_INC__ equ <1>
OPEN_EXISTING equ 3
INVALID_HANDLE_VALUE equ -1
FILE_MAP_READ equ SECTION_MAP_READ
MAX_PATH equ 260
M2M macro M1,M2
push M2
pop M1
ENDM
NT_SUCCESS macro status
mov eax,status
and eax,80000000h
.if eax
mov eax,FALSE
.else
mov eax,TRUE
.endif
ENDM
ENDIF
先贴代码,再来讲原理。根据我们刚才对SSDT的认识,要顺利找到SSDT的位置,首先要确定NtCreateThread的服务序号。不幸的是我尚未找到有效准确的获取方式。我唯一的思路是在ntdll.dll中找到ZwCreateThread,判断如果它的第一字节为0xB8(汇编代码MOV EAX,XXXXXXXX)那么它第二字节开始的双字就是其服务序号。用C语言描述应该是:SysCallIndex = *( (WORD*)((DWORD)GetProcAddress(ntdll,ZwCreateThread)+ 1) );
为此,专门写了PEFile.asm这个文件,这个文件提供了一些函数:
GetFunctionInformation proto :PUNICODE_STRING,:PUNICODE_STRING,:PFUNCTION_INFORMATION
GetFuncInfoByName proto :PUNICODE_STRING,:PVOID,:PDWORD
GetPEImageBase proto :PVOID
GetPEImageLength proto :PUNICODE_STRING
KLoadLibrary proto :PUNICODE_STRING
其中,KLoadLibrary将PE文件按加载到内存中(不是读取到内存中,类似PE加载器一样按照PE结构中的描述对其进行加载,以便通过导出表分析导出函数在内存中的位置),其它几个函数就顾名思义了。这些函数的意思我就不再详细解释,因为这也是我3年前的代码,我也记不得太清楚。有了这几个函数,你就能够理解SSDT.asm中的GetSysCallIndex这个函数的原理。当然,我讲了这是我唯一的思路,也希望大家不吝赐教,把你知道的方法告诉我(你别说用0x35硬编码就行)
确定了NtCreateThread的服务序号,下面我们就要在SSDT中寻找NtCreateThread的存放位置以及原始的函数地址。其实这比获取服务序号简单多了,因为一切秘密都在KeDescriptorTable这个导出符号中。需要注意的是前面我们已经说了,并不是所有系统中内核文件名都叫ntoskrnl.exe,所以先用ZwQuerySystemInformation获取一下当前系统中的内核文件名,通过对导出符号KeDescriptorTable的分析,顺利获得NtCreateThread的真实地址。具体的代码在SSDT.asm中,函数比较少,看名字就懂意思,具体我也不详细解释了,再解释又扯得很远,而且这是我3年前的代码。
好了,打开DebugView,用KmdManager加载一下我们生成的内核程序:
与前面LiveKd看到的一样,貌似一切都很顺利。
这一章就讲完了,下一章我们就完成HookSSDT、UnHookSSDT这两个函数,我们把NtCreateThread函数Hook住,监视一下进程的创建,同时我在下一章告诉大家为什么我极不推荐普通人对SSDT使用Inline Hook,虽然它的功能更加强大!顺便希望大家检查下我的代码,谢谢。