SSDT_Helper for Delphi v1(Ring3下查看SSDT HOOK的Delphi版)

来源:互联网 发布:现在做淘宝玩吗 编辑:程序博客网 时间:2024/05/20 05:55

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这个是因为装了金山清理专家,忽略之...

 

代码稍微有点长,具体部分都有注释,我就不多啰唆了:


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 12and $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 = 0then
  
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 = 0then
  
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 KeServiceDescriptorTable
var
  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) or

end;

{****************************************************************
 *
 *  从引出表获取函数名称
 *
 ****************************************************************
}
procedure GetExportName(MoudleName: String);
type
  PDwordArray 
= ^TDwordArray;
  TDwordArray 
= array[0..8192of 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 <> 0then
  
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), nil0, @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() <> 0then
  
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 = 0then
  
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 = 0then
        
begin
          WriteLn(
'no SSDT Hook.');
        
end;
      
end
    
else if (Pos('-a', ParamStr(1)) > 0then
      
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 = 0then
        
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() 的逆过程而已。