专栏首页Eureka伽罗的技术时光轴恢复在WIN64上的SSDT钩子

恢复在WIN64上的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的地址

毫无疑问,这个必须在驱动里实现了。首先看一个结构体:

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项目已经没异常了:

至此,全文完。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 一次线上服务器磁盘耗尽的问题排查处理

    发现很多文件是删除的状态,但是空间还未释放,应该是有进程还在使用这些文件,导致这些以被删除的文件一致被占用,无法释放磁盘空间。

    IT云清
  • Flink Forward 2019--实战相关(11)--Pulasr与Flink整合的弹性数据处理

    Elastic Data Processing with Apache Flink and Apache Pulsar -- Sijie Guo(Apache ...

    阿泽
  • 最强解析:支付宝系统架构内部剖析

    Metamorphosis (MetaQ) 是一个高性能、高可用、可扩展的分布式消息中间件,类似于LinkedIn的Kafka,具有消息存储顺序写、吞吐量大和支...

    Java团长
  • 解读年度数据库PostgreSQL:如何巧妙地实现缓冲区管理器

    原文:http://www.enmotech.com/web/detail/1/752/1.html

    数据和云01
  • 蛋蛋读NVMe之二: 吉祥三宝

    Host往SQ中写入命令, SSD往CQ中写入命令完成结果。SQ与CQ的关系,可以是一对一的关系,也可以是多对一的关系,但不管怎样,他们是成对的:有因就有果,有...

    Linux阅码场
  • Windows Server 2012 Hyper-V群集图文教程

    之前已经测试了Windows Server 2012系统群集,接下来将测试Windows Server 2012 Hyper-V群集功能,实现虚机高可用

    习惯说一说
  • 7.5 变量的存储方式和生存期

    4、全局变量全部存放在静态存储区中,在程序开始执行时给全局变量分配存储区,程序执行完毕就释放。

    闫小林
  • #建站必选#腾讯云秒杀活动:国内1G套餐99元/年

    腾讯云的秒杀活动已经进行了一个月最,目前仅剩下一天的时间,不过后期199元每年的香港云服务器已经不出现了,目前只有新加坡的套餐,不过如果是国内用户做站有备案的建...

    勤劳的小蜜蜂
  • Flutter进阶篇(6)-- PageStorageKey、PageStorageBucket和PageStorage使用详解

    本文所讲到的文件名称为:page_storage.dart,源码存放在本地的路径为:Flutter安装路径/packages/flutter/lib/src/w...

    AWeiLoveAndroid
  • 图解B+树的插入过程

    B+ 树在现代数据库中很常见,如果我们了解它,在工作中可能对性能优化会有更好的帮助!

    业余草

扫码关注云+社区

领取腾讯云代金券