【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
要恢复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的截图:
![](https://www.weixianmanbu.com/zb_users/upload/2016/07/201607031467531101665159.png)
可见,从文件中的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的地址
毫无疑问,这个必须在驱动里实现了。首先看一个结构体:
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的异常项)。
![](https://www.weixianmanbu.com/zb_users/upload/2016/07/201607031467531237318589.png)
检测出了异常的项目就需要恢复。其实恢复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):
![](https://www.weixianmanbu.com/zb_users/upload/2016/07/201607031467531272714851.png)
再次运行这个枚举SSDT的程序,发现NtTerminateProcess项目已经没异常了:
![](https://www.weixianmanbu.com/zb_users/upload/2016/07/201607031467531278969368.png)
至此,全文完。
来源:oschina
链接:https://my.oschina.net/u/1777508/blog/1930685