恢復在WIN64上的SSDT鉤子

要恢復SSDT,首先要得到SSDT各個函數的原始地址,而SSDT各個函數的原始地址,天然是存儲在內核文件裏的。因而,有了如下思路:函數

1.得到內核裏KiServiceTable的地址(變量名稱:KiServiceTable)測試

2.得到內核文件在內核裏的加載地址(變量名稱:NtosBase)指針

3.得到內核文件在PE32+結構體裏的映像基址(變量名稱:NtosImageBase)orm

4.在自身進程里加載內核文件並取得映射地址(變量名稱:NtosInProcess)進程

5.計算出KiServiceTable和NtosBase之間的「距離」(變量名稱:RVA)ip

6.得到指定INDEX函數的地址(計算公式:*(PULONGLONG)(NtosInProcess+RVA+8*index)-NtosImageBase+NtosBase)it

思路和WIN32下得到SSDT函數原始地址差別不大,接下來解釋一下第六步的計算公式是怎麼得來的。首先看一張IDA的截圖:io

 

可見,從文件中的KiServiceTable地址開始,每8個字節,存儲一個函數的「理想地址」(之因此說是理想地址,是由於這個地址是基於『內核文件的映像基址NtosImageBase』的,而不是基於『內核文件的加載基址NtosBase』的)。所以,獲得8*index。因爲已經得到了KiServiceTable和NtosBase之間的「距離」(RVA=KiServiceTable-NtosBase),也已知內核文件在自身進程裏的映射地址(NtosInProcess),因此就能算出文件中的KiServiceTable的地址(NtosInProcess+RVA)。因此,存儲各個函數原始地址的文件地址就是:NtosInProcess+RVA+8*index。把這個地址的值取出來(長度爲8),就是:form

*(PULONGLONG)(NtosInProcess+RVA+8*index)。前面說了,因爲獲得的這個函數地址是理想地址,由於它假設的加載基址是PE32+結構體裏的成員ImageBase(映像基址)的值。而實際上,內核文件的加載基址確定不多是這個值,因此還要減去內核文件的映像基址(NtosImageBase)再加上內核文件的實際加載基址(NtosBase)。接下來,給出每一步的具體實現過程的代碼。變量

1.得到KiServiceTable的地址

毫無疑問,這個必須在驅動裏實現了。首先看一個結構體:

 

typedefstruct_System_Service_Table{
PVOIDServiceTableBase;
PVOIDServiceCounterTableBase;
ULONG64NumberOfServices;
PVOIDParamTableBase;
}SYSTEM_SERVICE_TABLE,*PSYSTEM_SERVICE_TABLE;

 

這個結構體你們都很熟悉吧,只不過在WIN64下這個結構體胖了一倍,從16字節變成了32字節。但不少性質都沒變,得到KeServiceDescriptorTable的地址後,把KeServiceDescriptorTable的地址強制轉化爲此結構體的結構體指針,則此結構體的第一項ServiceTableBase就是KiServiceTable的地址。實際上寫代碼比描述得還簡單,僅僅兩行(GetKeServiceDescriptorTable64的代碼已經在2011年的期刊上解釋過,這裏再也不贅述):

 

ULONGLONGGetKeServiceDescriptorTable64()
{
char
KiSystemServiceStart_pattern[13]
=
\x8B\xF8\xC1\xEF\x07\x83\xE7\x20\x25\xFF\x0F\x00\x00;
ULONGLONGCodeScanStart=(ULONGLONG)_strnicmp;
ULONGLONGCodeScanEnd=(ULONGLONG)KdDebuggerNotPresent;
UNICODE_STRINGSymbol;
ULONGLONGi,tbl_address,b;
for(i=0;iCodeScanEnd-CodeScanStart;i++)
{
if
(!memcmp((char*)(ULONGLONG)CodeScanStart
+i,
(char*)KiSystemServiceStart_pattern,13))
{
for(b=0;b50;b++)
{
tbl_address=((ULONGLONG)CodeScanStart+i+b);
if(*(USHORT*)((ULONGLONG)tbl_address)==(USHORT)0x8d4c)
return((LONGLONG)tbl_address+7)+*(LONG*)(tbl_address
+3);
}
}
}
return0;
}
ULONG64ssdt_base_aadress=GetKeServiceDescriptorTable64();
KiServiceTable=*(PULONGLONG)ssdt_base_aadress;

 

得到內核文件在內核裏的加載地址

這個本質上屬於枚舉內核模塊,使用ZwQuerySystemInformation的SystemModuleInformation功能號實現。因爲第一個加載的老是內核文件,因此直接得到0號模塊的基址便可。另外,還要得到內核文件的名稱,由於根據CPU核心數目等硬件條件的不一樣,內核文件的名稱也是不盡相同的。

 

ULONGLONGGetNtosBaseAndPath(char*ModuleName)
{
ULONGNeedSize,i,ModuleCount,BufferSize=0x5000;
PVOIDpBuffer=NULL;
ULONGLONGqwBase=0;
NTSTATUSResult;
PSYSTEM_MODULE_INFORMATIONpSystemModuleInformation;
do
{
pBuffer=malloc(BufferSize);
if(pBuffer==NULL)
{
returnFALSE;
}
Result=ZwQuerySystemInformation(SystemModuleInformation,pBuffer,
BufferSize,NeedSize);
if(Result==STATUS_INFO_LENGTH_MISMATCH)
{
free(pBuffer);
BufferSize*=2;
}
elseif(!NT_SUCCESS(Result))
{
}
free(pBuffer);
returnFALSE;
}
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);
returnqwBase;
}

 

3.得到內核文件的映像基址

這個直接解析PE32+文件的結構便可,關於PE32+格式的詳細內容,請見《初步探索PE32+格式文件》。

 

DWORDFileLen(char*filename)
{
WIN32_FIND_DATAAfileInfo={0};
DWORDfileSize=0;
HANDLEhFind;
hFind=FindFirstFileA(filename,fileInfo);
if(hFind!=INVALID_HANDLE_VALUE)
{
fileSize=fileInfo.nFileSizeLow;
FindClose(hFind);
}
returnfileSize;
}
CHAR*LoadDllContext(char*filename)
{
DWORDdwReadWrite,LenOfFile=FileLen(filename);
HANDLEhFile=CreateFileA(filename,GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,0,OPEN_EXISTING,0,0);
if(hFile!=INVALID_HANDLE_VALUE)
{
PCHARbuffer=(PCHAR)malloc(LenOfFile);
SetFilePointer(hFile,0,0,FILE_BEGIN);
ReadFile(hFile,buffer,LenOfFile,dwReadWrite,0);
CloseHandle(hFile);
returnbuffer;
}
returnNULL;
}
VOIDGetNtosImageBase()
{
PIMAGE_NT_HEADERS64pinths64;
PIMAGE_DOS_HEADERpdih;
char*NtosFileData=NULL;
NtosFileData=LoadDllContext(NtosName);
pdih=(PIMAGE_DOS_HEADER)NtosFileData;
pinths64=(PIMAGE_NT_HEADERS64)(NtosFileData+pdih-e_lfanew);
NtosImageBase=pinths64-OptionalHeader.ImageBase;
printf(ImageBase:%llx\n,NtosImageBase);
}

 

得到SSDT函數的原始地址

原理已經在前面解釋過,這裏直接給出代碼。

 

ULONGLONGGetFunctionOriginalAddress(DWORDindex)
{
if(NtosInProcess==0)
NtosInProcess
=
(ULONGLONG)LoadLibraryExA(NtosName,0,
DONT_RESOLVE_DLL_REFERENCES);
ULONGLONGRVA=KiServiceTable-NtosBase;
ULONGLONGtemp=*(PULONGLONG)(NtosInProcess+RVA+8*(ULONGLONG)index);
ULONGLONGRVA_index=temp-NtosImageBase;
returnRVA_index+NtosBase;
}

 

接下來測試一下效果,在測試前,運行SSDTHOOKNtTerminateProcess的DEMO(檢測出了SSDT的異常項)。

檢測出了異常的項目就須要恢復。其實恢復SSDT本質上和掛鉤SSDT本質上沒有不一樣,都是在KiServiceTable的指定偏移處寫入一個INT32值。代碼以下:

 

LONGGetOffsetAddress(ULONGLONGFuncAddr)
{
LONGdwtmp=0;
PULONGServiceTableBase=NULL;
if(KeServiceDescriptorTable==NULL)
KeServiceDescriptorTable=(PSYSTEM_SERVICE_TABLE)GetKeServiceDescriptorTa
ble64();
ServiceTableBase=(PULONG)KeServiceDescriptorTable-ServiceTableBase;
dwtmp=(LONG)(FuncAddr-(ULONGLONG)ServiceTableBase);
returndwtmp4;
}
VOIDUnHookSSDT(ULONGid,ULONGLONGFuncAddr)
{
//傳入正確的地址
KIRQLirql;
LONGdwtmp;
PULONGServiceTableBase=NULL;
dwtmp=GetOffsetAddress(FuncAddr);
ServiceTableBase=(PULONG)KeServiceDescriptorTable-ServiceTableBase;
irql=WPOFFx64();
ServiceTableBase[id]=dwtmp;
WPONx64(irql);
//核心就這一句
}

 

接下來測試效果(輸入要恢復的函數的Index):

再次運行這個枚舉SSDT的程序,發現NtTerminateProcess項目已經沒異常了:

至此,全文完。

相關文章
相關標籤/搜索