SSDT(System Services Descriptor Table) 这几年被讲烂了,各大论坛、Blog相关文章都是成堆成堆的。检测SSDT HOOK的代码也相当多,只不过放眼望去都是C的。实在不忍自己苦苦捍卫的Delphi就此末落(D2009倒是让人振奋了一把),于是把自己感兴趣的几个代码都给翻成了Delphi的,现在拿来共享。
不会驱动,这方面涉水也比较浅,只能写个查看SSDT的小程序。最初是看到 Ring3下用ZwSystemDebugControl获取和恢复SSDT 这篇文章,感觉不错!于是有了这个SSDT_Helper for Delphi,如图:
这是装了 Daemon Tools 后的SSDT,它HOOK了注册表相关的几个函数。0x011C这个是因为装了金山清理专家,忽略之...
代码稍微有点长,具体部分都有注释,我就不多啰唆了:
SSDT_Helper.dpr program SSDT_Helper;(*************************************************************** * * SSDT_Helper for Delphi (2009) @Version: 1.0 * * 通过搜索 SSDT 并和 ZwSystemDebugControl 获取的内容相比较 * 找出不同的SSDT项。 * * 方法来自《Ring3下用ZwSystemDebugControl获取和恢复SSDT》 * http://blog.csdn.net/DryFisHH/archive/2007/12/29/2002517.aspx * * @Author: 木桩 (2009) ***************************************************************){$APPTYPE CONSOLE}uses SysUtils, Windows, Classes, NtTypes in 'NtTypes.pas';{ Types }type // 用于存储 SSDT 内容的结构 _SSDT_Record = record Address: DWORD; OrignAddress: DWORD; FunctionName: AnsiString; end; TSSDT_Record = _SSDT_Record; PSSDT_Record = ^TSSDT_Record; TSSDT_Array = array of TSSDT_Record;var KernelName: String; // 内核名 KernelBase: DWORD; // 内核基址 RVA_KeServiceDescriptorTable: DWORD; // RVA of KeServiceDescriptorTable RVA_KiServiceTable: DWORD; // RVA of KiServiceTable ServiceCount: DWORD; // 条目总数 SSDT: TSSDT_Array; i, dwCount: Cardinal;{$REGION '- ImageFixupEntry -'}// 用于读取 ImageFixupEntry 数据function _ImageFixupEntry_Type(aFixupEntry: WORD): BYTE;begin Result := (aFixupEntry shr 12) and $0F;end;function _ImageFixupEntry_Offset(aFixupEntry: WORD): WORD;begin Result := aFixupEntry and $0FFF;end;{$ENDREGION}{$REGION '- SSDT Functions -'}// #define RVATOVA(base,offset) ((PVOID)((DWORD)(base)+(DWORD)(offset)))function RVA2VA(base, offset: DWORD): DWORD;begin Result := base + offset;end;{**************************************************************** * * 根据提供的 ModuleBase 设置DOS头、NT头以及重定位节的指针 * ****************************************************************}function SetModuleHeaders(ModuleBase: DWORD; var lpDosHeader: PImageDosHeader; // DOS头 var lpNtHeaders: PImageNtHeaders; // NT头 var lpBaseReloc: PImageBaseRelocation): Boolean;begin Result := False; lpDosHeader := PImageDosHeader(ModuleBase); //如果DOS头无效 if ( lpDosHeader^.e_magic <> IMAGE_DOS_SIGNATURE ) then begin Exit; end; lpNtHeaders := PImageNtHeaders(ModuleBase + DWORD(lpDosHeader^._lfanew)); //如果NT头无效 if ( lpNtHeaders^.Signature <> IMAGE_NT_SIGNATURE ) then begin Exit; end; // 检查重定位节是否存在 if (lpNtHeaders^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress <> 0) and (lpNtHeaders^.FileHeader.Characteristics and IMAGE_FILE_RELOCS_STRIPPED = 0) then begin lpBaseReloc := PImageBaseRelocation(RVA2VA(ModuleBase, lpNtHeaders^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress)); Result := True; end;end;{**************************************************************** * * 通过 ZwQuerySystemInformation(SystemModuleInformation) * 取内核名称与基址 * ****************************************************************}function GetKernelNameAndBase(): NTSTATUS;var uReturn: ULONG; pBuffer: PDWORD; aModule: PSYSTEM_MODULE_INFORMATION; tmpName: PAnsiChar;begin // 取内核名和基址 uReturn := 0; // 第一次取缓冲区长度 Result := ZwQuerySystemInformation(SystemModuleInformation, @uReturn, 0, @uReturn); if (uReturn = 0) then begin Exit; end; pBuffer := AllocMem(uReturn); try // 取内容 Result := ZwQuerySystemInformation(SystemModuleInformation, pBuffer, uReturn, nil); if ( Result <> 0 ) then begin Exit; end; // 第一个就是内核模块 aModule := PSystemModuleInformation(DWORD(pBuffer) + 4); tmpName := @aModule^.ImageName[aModule^.ModuleNameOffset]; KernelName := String(StrPas(tmpName)); // 名字 KernelBase := DWORD(aModule^.Base); // 基址 finally FreeMem(pBuffer); end;end;{**************************************************************** * * 根据 KeServiceDescriptorTable 的RVA查找 KiServiceTable × 照搬 《ring3下用ZwSystemDebugControl获取和恢复SSDT》 * ****************************************************************}function FindKiServiceTable( hModuleBase, ImageBase: DWORD; lpBaseReloc: PImageBaseRelocation; RVA_KSDT: DWORD): DWORD; // RVA of KeServiceDescriptorTablevar bFirstChunk: Boolean; pFixupEntry: PWORD; i: Cardinal; dwPointerRva, dwPointsToRva: DWORD; tmpCode: WORD;begin Result := 0; bFirstChunk := True; // 1st IMAGE_BASE_RELOCATION.VirtualAddress of ntoskrnl is 0 while ((bFirstChunk) or (lpBaseReloc^.VirtualAddress <> 0)) do begin bFirstChunk := False; pFixupEntry := PWORD(DWORD(lpBaseReloc) + SizeOf(IMAGE_BASE_RELOCATION)); // 读入一个重定位表项 i := 0; while ( i < ((lpBaseReloc^.SizeOfBlock-SizeOf(IMAGE_BASE_RELOCATION))shr 1) ) do begin if ( _ImageFixupEntry_Type(pFixupEntry^) = IMAGE_REL_BASED_HIGHLOW ) then begin // 计算RVA dwPointerRva := lpBaseReloc^.VirtualAddress + _ImageFixupEntry_Offset(pFixupEntry^); // DONT_RESOLVE_DLL_REFERENCES flag means relocs aren't fixed dwPointsToRva := PDWORD(hModuleBase+dwPointerRva)^ - ImageBase; // does this reloc point to KeServiceDescriptorTable.Base? if (dwPointsToRva = RVA_KeServiceDescriptorTable) then begin // check for mov [mem32],imm32. we are trying to find // "mov ds:_KeServiceDescriptorTable.Base, offset _KiServiceTable" // from the KiInitSystem. tmpCode := PWORD(hModuleBase + dwPointerRva-2)^; //fLogList.add(Format('[%0.8X] KSDT Code: %0.4X = $05c7', [dwPointerRva, tmpCode])); if (tmpCode = $05C7) then begin // should check for a reloc presence on KiServiceTable here // but forget it Result := PDWORD(hModuleBase + dwPointerRva + 4)^ - ImageBase; Exit; end; end; end; Inc(i); pFixupEntry := PWORD(DWORD(pFixupEntry) + 2); end; // end of while ( i < (pbr^.SizeOfBlock- lpBaseReloc := PImageBaseRelocation(DWORD(lpBaseReloc)+ lpBaseReloc^.SizeOfBlock); end; // end of while ((bFirstChunk) orend;{**************************************************************** * * 从引出表获取函数名称 * ****************************************************************}procedure GetExportName(MoudleName: String);type PDwordArray = ^TDwordArray; TDwordArray = array[0..8192] of DWORD;var hModuleBase: THandle; lpDosHeader: PImageDosHeader; lpNtHeaders: PImageNtHeaders; lpExportDirectory: PImageExportDirectory; arrayOfFunctionNames, arrayOfFunctionAddresses: PDwordArray; arrayOfFunctionOrdinals: PWordArray; functionOrdinal, functionAddress: DWORD; i: Cardinal; funcName: PAnsiChar; Index: Cardinal;begin hModuleBase := GetModuleHandle(PChar(MoudleName)); if (SetModuleHeaders(hModuleBase, lpDosHeader, lpNtHeaders, PImageBaseRelocation(lpExportDirectory))) then if (lpNtHeaders^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress <> 0) then begin lpExportDirectory := PImageExportDirectory(hModuleBase + lpNtHeaders^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); arrayOfFunctionAddresses := PDwordArray(hModuleBase + DWORD(lpExportDirectory^.AddressOfFunctions)); arrayOfFunctionNames := PDwordArray(hModuleBase + DWORD(lpExportDirectory^.AddressOfNames)); arrayOfFunctionOrdinals := PWordArray(hModuleBase + DWORD(lpExportDirectory^.AddressOfNameOrdinals)); for i := 0 to lpExportDirectory^.NumberOfNames - 1 do begin funcName := PAnsiChar(hModuleBase + arrayOfFunctionNames[i]); functionOrdinal := lpExportDirectory^.Base + arrayOfFunctionOrdinals[i] - 1; functionAddress := hModuleBase + arrayOfFunctionAddresses[functionOrdinal]; if (funcName^ = 'N') and (PAnsiChar(funcName+1)^ = 't') then begin Index := PWORD(functionAddress + 1)^; if (Index > lpExportDirectory^.NumberOfNames) then Continue; SSDT[Index].FunctionName := StrPas(funcName); //WriteLn(Format('(%0.4X) %s', [Index, SSDT[Index].FunctionName])); end; end; end;end;{$ENDREGION}{$REGION '- dump SSDT -'}{**************************************************************** * * 从内存地址直接读取 SSDT (可能是被HOOK过的) * ****************************************************************}procedure BlackSSDT();var tmpArray: array of DWORD; QueryBuff: TMEMORY_CHUNKS; i: Cardinal; Ret: NTSTATUS;begin // 读取内存中的 SSDT SetLength(tmpArray, ServiceCount); QueryBuff.Address := KernelBase + RVA_KiServiceTable; QueryBuff.Data := tmpArray; QueryBuff.Length := ServiceCount * 4; Ret := ZwSystemDebugControl(SysDbgReadVirtual, @QueryBuff, SizeOf(TMEMORY_CHUNKS), nil, 0, @i); //WriteLn(Format('ZwSystemDebugControl() = %0.8X, Address(%0.8X), Count(%d)', [Ret, QueryBuff.Address, ServiceCount])); for i := 0 to ServiceCount - 1 do begin SSDT[i].Address := tmpArray[i]; end;end;{**************************************************************** * * 读取 SSDT * ****************************************************************}procedure GetSSDT();var hKernel: THandle; lpDosHeader: PImageDosHeader; lpNtHeaders: PImageNtHeaders; lpBaseReloc: PImageBaseRelocation; ImageBase, ImageEndSize: DWORD; pService: PDWORD;begin // 取内核基址 if (GetKernelNameAndBase() <> 0) then begin WriteLn(Format('ERROR: 取内核基址错误 > [%0.8X] %s', [KernelBase, KernelName])); Exit; end; // DONT_RESOLVE_DLL_REFERENCES方式载入内核准备读取 hKernel := LoadLibraryEx(PChar(KernelName), 0, DONT_RESOLVE_DLL_REFERENCES); if (hKernel = 0) then begin WriteLn(Format('加载 %s 失败,可能是被改了名字。', [KernelName])); Exit; end; try // 取 KeServiceDescriptorTable RVA RVA_KeServiceDescriptorTable := DWORD(GetProcAddress(hKernel, 'KeServiceDescriptorTable')) - hKernel; // 检查文件头 if (SetModuleHeaders(hKernel, lpDosHeader, lpNtHeaders, lpBaseReloc)) then begin ImageBase := lpNtHeaders^.OptionalHeader.ImageBase; // 查找 KiServiceTable RVA_KiServiceTable := FindKiServiceTable(hKernel, ImageBase, lpBaseReloc, RVA_KeServiceDescriptorTable); // 根据文件搜索 SSDT 内容 ServiceCount := 0; SetLength(SSDT, 1000); // 设置一个较大的长度,后面调整 ImageEndSize := ImageBase + lpNtHeaders^.OptionalHeader.SizeOfImage; pService := PDWORD(hKernel + RVA_KiServiceTable); while ( pService^ < ImageEndSize ) do begin // 导出 SSDT 表项 SSDT[ServiceCount].OrignAddress := pService^-ImageBase + KernelBase; //WriteLn(Format('(%0.4X) Ori:0x%0.8X', [ServiceCount, SSDT[ServiceCount].OrignAddress])); Inc(ServiceCount); pService := PDWORD(DWORD(pService) + 4); end; // 确定SSDT大小 SetLength(SSDT, ServiceCount); end; except on E: Exception do WriteLn(Format('ERROR: %s', [E.Message])); end; FreeLibrary(hKernel);end;{$ENDREGION}{$REGION '- 附加函数 -'}// 提升权限function EnabledDebugPrivilege(const bEnabled: Boolean): Boolean;var hToken: THandle; tp: TOKEN_PRIVILEGES; a: DWORD;const SE_DEBUG_NAME = 'SeDebugPrivilege';begin Result := False; if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, hToken)) then begin tp.PrivilegeCount := 1; LookupPrivilegeValue(nil, SE_DEBUG_NAME, tp.Privileges[0].Luid); if bEnabled then tp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED else tp.Privileges[0].Attributes := 0; a := 0; AdjustTokenPrivileges(hToken, False, tp, SizeOf(tp), nil, a); Result := GetLastError = ERROR_SUCCESS; CloseHandle(hToken); end;end;{$ENDREGION}begin try // 提升权限,不然用BlackSSDT()中的ZwSystemDebugControl无法取到数据 if Not EnabledDebugPrivilege(True) then begin WriteLn(Format('SetDebugPrivilege 失败!', [])); end; // 从文件中读取真实的SSDT GetSSDT(); // 读取内存中的SSDT,用于对比 BlackSSDT(); // 从ntdll读取导出函数名 GetExportName('ntdll.dll'); // 输出SSDT WriteLn('SSDT(System Services Descriptor Table)'); WriteLn(Format('KernelBase = 0x%0.8X, KernelName = %s', [KernelBase, KernelName])); WriteLn('------------------------------------------------------------------'); dwCount := 0; // 检查参数 if ParamCount <= 0 then begin // 列出可疑SSDT for i := 0 to ServiceCount - 1 do begin if (SSDT[i].Address <> SSDT[i].OrignAddress) then begin WriteLn(Format('0x%0.4X 0x%0.8X 0x%0.8X %s', [i, SSDT[i].Address, SSDT[i].OrignAddress, SSDT[i].FunctionName])); Inc(dwCount); end; end; if (dwCount = 0) then begin WriteLn('no SSDT Hook.'); end; end else if (Pos('-a', ParamStr(1)) > 0) then begin // -a 有参数,显示完整SSDT for i := 0 to ServiceCount - 1 do begin if (SSDT[i].Address = SSDT[i].OrignAddress) then WriteLn(Format('0x%0.4X 0x%0.8X 0x%0.8X %s', [i, SSDT[i].Address, SSDT[i].OrignAddress, SSDT[i].FunctionName])) else begin WriteLn(Format('0x%0.4X 0x%0.8X [0x%0.8X] %s', [i, SSDT[i].Address, SSDT[i].OrignAddress, SSDT[i].FunctionName])); Inc(dwCount); end; end; if (dwCount = 0) then begin WriteLn('no SSDT Hook.'); end; end; WriteLn('------------------------------------------------------------------'); ReadLn; except on E: Exception do WriteLn('ERROR: ' + E.Message); end;end.
完整工程源码:SSDT_Helper_src.rar 可执行文件:SSDT_Helper.rar
这个程序比较简单,r3下用ZwSystemDebugControl读内存,从文件搜SSDT...都是些常见的办法,而且由于只简单比较了一下SSDT地址,这样是检测不出Inline HOOK的。为了能检测Inline Hook,下一步应该比较每个函数头部的10几字节看有没有变化,等有时间了把这个功能给加上吧。
另外,恢复SSDT可以用ZwSystemDebugControl写回从文件读取的原始SSDT,实际就是上面代码里 BlackSSDT() 的逆过程而已。
转载于:https://www.cnblogs.com/bits/archive/2009/03/03/Delphi-SSDT_Helper_con_v1.html
相关资源:各显卡算力对照表!