恢复在WIN64上的SSDT钩子
在上一篇文章里我描述了如何在 Win64 系统上实现 SSDT HOOK,有 SSDT HOOK,自然也 有UNHOOK SSDT的方法,现在我就来讲一下如何实现UNHOOK SSDT。
要恢复 SSDT,首先要获得 SSDT 各个函数的原始地址,而 SSDT 各个函数的原始地址, 自然是存储在内核文件里的。于是,有了以下思路:
1.获得内核里KiServiceTable的地址(变量名称:KiServiceTable) 2.获得内核文件在内核里的加载地址(变量名称:NtosBase)
3.获得内核文件在PE32+结构体里的映像基址(变量名称:NtosImageBase) 4.在自身进程里加载内核文件并取得映射地址(变量名称:NtosInProcess) 5.计算出KiServiceTable和NtosBase之间的“距离”(变量名称:RVA)
6.获得指定INDEX 函数的地址(计算公式: *(PULONGLONG)(NtosInProcess + RVA + 8 * index) - NtosImageBase + NtosBase)
思路和 WIN32 下获得 SSDT 函数原始地址差异不大,接下来解释一下第六步的计算公式 是怎么得来的。首先看一张IDA的截图:
可见,从文件中的KiServiceTable 地址开始,每8 个字节,存储一个函数的“理想地
址”(之所以说是理想地址,是因为这个地址是基于『内核文件的映像基址NtosImageBase』 的,而不是基于『内核文件的加载基址 NtosBase』的)。因此,得到 8 * index。由于已经 获得了 KiServiceTable和 NtosBase之间的“距离”(RVA = KiServiceTable - NtosBase), 也已知内核文件在自身进程里的映射地址(NtosInProcess),所以就能算出文件中的 KiServiceTable 的地址(NtosInProcess + RVA)。所以,存储各个函数原始地址的文件地 址就是:NtosInProcess + RVA + 8 * index。把这个地址的值取出来(长度为8),就是: *(PULONGLONG)(NtosInProcess + RVA + 8 * index)。前面说了,由于得到的这个函数地
址是理想地址,因为它假设的加载基址是 PE32+结构体里的成员 ImageBase(映像基址)的 值。而实际上,内核文件的加载基址肯定不可能是这个值,所以还要减去内核文件的映像基
址(NtosImageBase)再加上内核文件的实际加载基址(NtosBase)。接下来,给出每一步的 具体实现过程的代码。
1.获得 KiServiceTable的地址
毫无疑问,这个必须在驱动里实现了。首先看一个结构体: typedef struct _System_Service_Table{ PVOID ServiceTableBase;
PVOID ServiceCounterTableBase; ULONG64 NumberOfServices; PVOID ParamTableBase;
} SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE;
这个结构体大家都很熟悉吧,只不过在 WIN64 下这个结构体胖了一倍,从 16 字节变成 了 32 字节。但很多性质都没变,获得 KeServiceDescriptorTable 的地址后,把
KeServiceDescriptorTable 的地址强制转化为此结构体的结构体指针,则此结构体的第一 项ServiceTableBase就是 KiServiceTable的地址。实际上写代码比描述得还简单,仅仅两 行(GetKeServiceDescriptorTable64 的代码已经在以前上解释过,这里不再赘 述):
ULONGLONG GetKeServiceDescriptorTable64() {
char KiSystemServiceStart_pattern[13] =
\ ULONGLONG CodeScanStart = (ULONGLONG)&_strnicmp;
ULONGLONG CodeScanEnd = (ULONGLONG)&KdDebuggerNotPresent; UNICODE_STRING Symbol; ULONGLONG i, tbl_address, b;
for (i = 0; i < CodeScanEnd - CodeScanStart; i++) {
if (!memcmp((char*)(ULONGLONG)CodeScanStart +i, (char*)KiSystemServiceStart_pattern,13)) {
for (b = 0; b < 50; b++) {
tbl_address = ((ULONGLONG)CodeScanStart+i+b);
if (*(USHORT*) ((ULONGLONG)tbl_address ) == (USHORT)0x8d4c) return ((LONGLONG)tbl_address +7) + *(LONG*)(tbl_address +3); }
} }
return 0; }
ULONG64 ssdt_base_aadress=GetKeServiceDescriptorTable64(); KiServiceTable=*(PULONGLONG)ssdt_base_aadress; 2.获得内核文件在内核里的加载地址
这个本质上属于枚举内核模块,使用 ZwQuerySystemInformation 的
SystemModuleInformation 功能号实现。由于第一个加载的总是内核文件,所以直接获得 0 号模块的基址即可。另外,还要获得内核文件的名称,因为根据CPU核心数目等硬件条件的 不同,内核文件的名称也是不尽相同的。
ULONGLONG GetNtosBaseAndPath(char *ModuleName) {
ULONG NeedSize, i, ModuleCount, BufferSize = 0x5000; PVOID pBuffer = NULL; ULONGLONG qwBase = 0; NTSTATUS Result;
PSYSTEM_MODULE_INFORMATION pSystemModuleInformation; do {
pBuffer = malloc( BufferSize ); if( pBuffer == NULL ) {
return FALSE; }
Result = ZwQuerySystemInformation( SystemModuleInformation, pBuffer, BufferSize, &NeedSize );
if( Result == STATUS_INFO_LENGTH_MISMATCH ) {
free( pBuffer ); BufferSize *= 2; }
else if( !NT_SUCCESS(Result) ) {
free( pBuffer ); return FALSE;
} }
while( Result == STATUS_INFO_LENGTH_MISMATCH );
pSystemModuleInformation = (PSYSTEM_MODULE_INFORMATION)pBuffer; if(ModuleName!=NULL)
strcpy(ModuleName,pSystemModuleInformation->Module[0].ImageName+pSystemM oduleInformation->Module[0].ModuleNameOffset);
qwBase=(ULONGLONG)pSystemModuleInformation->Module[0].Base; free(pBuffer); return qwBase; }
3.获得内核文件的映像基址
这个直接解析PE32+文件的结构即可,关于PE32+格式的详细内容,请见《初步探索PE32+ 格式文件》。 DWORD FileLen(char *filename) {
WIN32_FIND_DATAA fileInfo={0}; DWORD fileSize=0; HANDLE hFind;
hFind = FindFirstFileA(filename ,&fileInfo); if(hFind != INVALID_HANDLE_VALUE) {
fileSize = fileInfo.nFileSizeLow; FindClose(hFind); }
return fileSize; }
CHAR *LoadDllContext(char *filename) {
DWORD dwReadWrite, LenOfFile=FileLen(filename);
HANDLE hFile = CreateFileA(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0); if (hFile != INVALID_HANDLE_VALUE) {
PCHAR buffer=(PCHAR)malloc(LenOfFile); SetFilePointer(hFile, 0, 0, FILE_BEGIN);
ReadFile(hFile, buffer, LenOfFile, &dwReadWrite, 0); CloseHandle(hFile); return buffer; }
return NULL; }
VOID GetNtosImageBase() {
PIMAGE_NT_HEADERS64 pinths64; PIMAGE_DOS_HEADER pdih; char *NtosFileData=NULL;
NtosFileData=LoadDllContext(NtosName); pdih=(PIMAGE_DOS_HEADER)NtosFileData;
pinths64=(PIMAGE_NT_HEADERS64)(NtosFileData+pdih->e_lfanew); NtosImageBase=pinths64->OptionalHeader.ImageBase; printf(\}
4/5/6.获得SSDT 函数的原始地址
原理已经在前面解释过,这里直接给出代码。
ULONGLONG GetFunctionOriginalAddress(DWORD index) {
if ( NtosInProcess==0 )
NtosInProcess = (ULONGLONG)LoadLibraryExA(NtosName,0, DONT_RESOLVE_DLL_REFERENCES);
ULONGLONG RVA=KiServiceTable-NtosBase;
ULONGLONG temp=*(PULONGLONG)(NtosInProcess+RVA+8*(ULONGLONG)index); ULONGLONG RVA_index=temp-NtosImageBase; return RVA_index+NtosBase; }
接下来测试一下效果,在测试前,运行 SSDT HOOK NtTerminateProcess 的 DEMO(检测 出了 SSDT的异常项)。
百度搜索“77cn”或“免费范文网”即可找到本站免费阅读全部范文。收藏本站方便下次阅读,免费范文网,提供经典小说教育文库恢复在WIN64上的SSDT钩子在线全文阅读。
相关推荐: