[开源]SSDT

it2022-05-05  151

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 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 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..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() 的逆过程而已。

转载于:https://www.cnblogs.com/bits/archive/2009/03/03/Delphi-SSDT_Helper_con_v1.html

相关资源:各显卡算力对照表!

最新回复(0)