内核态下基于动态感染技术的应用程序执行保护(三 获取SSDT)
3886 点击·0 回帖
![]() | ![]() | |
![]() | 前面我们经常提到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 "SystemrootSystem32ntdll.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("SystemrootSystem32") 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,虽然它的功能更加强大!顺便希望大家检查下我的代码,谢谢。 | |
![]() | ![]() |