简单的 SST Unhooker






4.92/5 (13投票s)
本文介绍了一个简单的 unhooker,它可以恢复被未知 rootkit 钩住的原始 SST,这些 rootkit 会隐藏某些服务和进程。
目录
- SST:参考文献
- 算法
- 内存映射文件
- 实现
- 演示
- 如何构建
- 历史
1. SST:参考文献
本文是 Ivan Romananko 的文章“隐藏进程和文件的驱动程序”的逻辑延续。你可以在其中找到所有关于系统服务表 (SST) 及其钩取操作的必要信息。
在本文中,我想介绍如何编写自己的 unhooker,它可以恢复被 Ivan 那样的驱动程序钩住的原始 SST。
2. 算法
我的目标是编写一个简单的驱动程序,用于 SST 钩取检测和移除。
这意味着我们的驱动程序不应使用各种 Zw
函数和 SST 表,因为我假设 SST 表已被未知 rootkit 损坏。
目前我不会考虑过滤驱动程序和函数代码拼接器,但也许以后会回来研究它们。
检测和移除钩取的最简单方法是将内存中的 SST 与 ntoskernel.exe 文件中的初始 SST 进行比较。
所以目标是:
- 在内存中找到 ntoskernel 模块
- 找到 ntoskernel 中存放 SST 的部分,并计算 SST 在该部分中的相对偏移量
- 在 ntoskernel.exe 文件中找到该部分
- 计算文件中 SST 的实际地址
- 从文件中读取值并与 SST 进行比较
但在实现之前,我想提供一些额外的信息。
3. 内核模式下的内存映射文件
“内存映射文件是虚拟内存的一个段,它被赋予了与文件或类文件资源的部分直接的字节对字节关联。” (c) Wiki
是的,我们要解析 PE 文件,而内存映射文件对此任务非常有用。
而且,从内核模式下使用内存映射文件 API 非常简单,因为它与 Win32 API 非常相似。在内核模式驱动程序中,不应使用 CreateFileMapping
和 MapViewOfSection
函数,而是应该访问
NTSTATUS
ZwCreateSection(
OUT PHANDLE SectionHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN PLARGE_INTEGER MaximumSize OPTIONAL,
IN ULONG SectionPageProtection,
IN ULONG AllocationAttributes,
IN HANDLE FileHandle OPTIONAL
);
和
NTSTATUS
ZwMapViewOfSection(
IN HANDLE SectionHandle,
IN HANDLE ProcessHandle,
IN OUT PVOID *BaseAddress,
IN ULONG_PTR ZeroBits,
IN SIZE_T CommitSize,
IN OUT PLARGE_INTEGER SectionOffset OPTIONAL,
IN OUT PSIZE_T ViewSize,
IN SECTION_INHERIT InheritDisposition,
IN ULONG AllocationType,
IN ULONG Win32Protect
);
函数。
但是,如果我们使用这些函数,我们将违反不使用 SST 的规则。而且,对于反 rootkit 来说,使用极低级别的函数以期望对可能的 rootkit 隐形是很好的。
有鉴于此,我们可以使用内存管理器 (Mm) 的未公开函数,当然是以我们自己的风险。
NTSTATUS
MmCreateSection (
OUT PVOID *SectionObject,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN PLARGE_INTEGER MaximumSize,
IN ULONG SectionPageProtection,
IN ULONG AllocationAttributes,
IN HANDLE FileHandle OPTIONAL,
IN PFILE_OBJECT File OPTIONAL
);
NTSTATUS
MmMapViewOfSection(
IN PVOID SectionToMap,
IN PEPROCESS Process,
IN OUT PVOID *CapturedBase,
IN ULONG_PTR ZeroBits,
IN SIZE_T CommitSize,
IN OUT PLARGE_INTEGER SectionOffset,
IN OUT PSIZE_T CapturedViewSize,
IN SECTION_INHERIT InheritDisposition,
IN ULONG AllocationType,
IN ULONG Protect
);
NTSTATUS
MmUnmapViewOfSection(
IN PEPROCESS Process,
IN PVOID BaseAddress
);
NTSTATUS drv_MapAllFileEx(HANDLE hFile OPTIONAL,
drv_MappedFile * pMappedFile,
LARGE_INTEGER * pFileSize,
ULONG Protect)
{
NTSTATUS status = STATUS_SUCCESS;
PVOID section = 0;
PCHAR pData=0;
LARGE_INTEGER offset;
offset.QuadPart = 0;
// check zero results
if (!pFileSize->QuadPart)
goto calc_exit;
status = MmCreateSection (§ion,
SECTION_MAP_READ,
0, // OBJECT ATTRIBUTES
pFileSize, // MAXIMUM SIZE
Protect,
0x8000000,
hFile,
0
);
if (status!= STATUS_SUCCESS)
goto calc_exit;
status = MmMapViewOfSection(section,
PsGetCurrentProcess(),
(PVOID*)&pData,
0,
0,
&offset,
&pFileSize->LowPart,
ViewUnmap,
0,
Protect);
if (status!= STATUS_SUCCESS)
goto calc_exit;
calc_exit:
if (NT_SUCCESS(status))
{
pMappedFile->fileSize.QuadPart = pFileSize->QuadPart;
pMappedFile->pData = pData;
pMappedFile->section = section;
}
else
{
if (pData)
MmUnmapViewOfSection(PsGetCurrentProcess(),
pData);
if (section)
{
ObMakeTemporaryObject(section);
ObDereferenceObject(section);
}
}
return status;
}
此示例演示了通过 MmCreateSection
/MmMapViewOfSection
函数使用内存映射文件的替代方法。
所提出的方法相当不错,因为它不使用 Zw*
函数,甚至不使用句柄,但有一个限制。如果你从 DriverEntry
启动此示例,它会正常工作,但如果你从 IRP_MJ_DEVICE_CONTROL
处理程序启动它,你会发现 MmCreateSection
函数会因 STATUS_ACCESS_DENIED
而失败。为什么?
答案是:Zw*
函数有一个好处,那就是它们将 Previous Mode 设置为 KernelMode
,这允许将内核模式指针和句柄用作它们的参数(有关更多信息,请参阅Nt vs. Zw - Clearing Confusion On The Native API article)。
因此,上面介绍的函数只能从 DriverEntry
或系统线程调用。
4. 算法实现
我设计了以下结构来保存所有 ntoskernel
解析结果。
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _Drv_VirginityContext
{
drv_MappedFile m_mapped;
HANDLE m_hFile;
UCHAR m_SectionName[IMAGE_SIZEOF_SHORT_NAME+1];
ULONG m_sstOffsetInSection;
char * m_mappedSST;
ULONG m_imageBase;
char * m_pSectionStart;
char * m_pMappedSectionStart;
char * m_pLoadedNtAddress;
}Drv_VirginityContext;
我按照以下方式实现了选定的算法。
static NTSTATUS ResolveSST(Drv_VirginityContext * pContext,
SYSTEM_MODULE * pNtOsInfo)
{
PIMAGE_SECTION_HEADER pSection = 0;
PIMAGE_SECTION_HEADER pMappedSection = 0;
NTSTATUS status = 0;
PNTPROC pStartSST = KeServiceDescriptorTable->ntoskrnl.ServiceTable;
char * pSectionStart = 0;
char * pMappedSectionStart = 0;
// Drv_ResolveSectionAddress function detects
// to which section pStartSST belongs
// pSection will contain the section of ntoskernel.exe that contains SST
pContext->m_pLoadedNtAddress = (char*)pNtOsInfo->pAddress;
status = Drv_ResolveSectionAddress(pNtOsInfo->pAddress, pStartSST, &pSection);
if (!NT_SUCCESS(status))
goto clean;
// save section name to context
memcpy(pContext->m_SectionName, pSection->Name, IMAGE_SIZEOF_SHORT_NAME);
// calculate m_sstOffsetInSection - offset of SST in section
pSectionStart = (char *)pNtOsInfo->pAddress + pSection->VirtualAddress;
pContext->m_sstOffsetInSection = (char*)pStartSST - pSectionStart;
// find section in mapped file - on disk!
status = Drv_FindSection(pContext->m_mapped.pData,
pSection->Name,
&pMappedSection);
if (!NT_SUCCESS(status))
goto clean;
pMappedSectionStart = (char *)pContext->m_mapped.pData +
pMappedSection->PointerToRawData;
pContext->m_mappedSST = pMappedSectionStart + pContext->m_sstOffsetInSection;
{ // don´t forget to save ImageBase
PIMAGE_DOS_HEADER dosHeader =
(PIMAGE_DOS_HEADER)pContext->m_mapped.pData;
PIMAGE_NT_HEADERS pNTHeader =
(PIMAGE_NT_HEADERS)((char*)dosHeader + dosHeader->e_lfanew);
pContext->m_imageBase = pNTHeader->OptionalHeader.ImageBase;
}
pContext->m_pSectionStart = pSectionStart;
pContext->m_pMappedSectionStart = pMappedSectionStart;
clean:
return status;
}
这是返回 SST 实际值的函数。
void Drv_GetRealSSTValue(Drv_VirginityContext * pContext, long index, void ** ppValue)
{
char * pSST = pContext->m_mappedSST;
ULONG * pValue = ((ULONG *) pSST) + index;
// now pValue points to the mapped SST entry
// but entry contains offset from the beginning of ntoskernel file,
// so correct it
*ppValue = (void*)(*pValue + (ULONG)pContext->m_pLoadedNtAddress –
pContext->m_imageBase);
}
之后,实现主要功能就相当简单了。
virtual NTSTATUS ExecuteReal()
{
CAutoVirginity initer;
NT_CHECK(initer.Init(&m_virginityContext));
// now we are ready to scan :)
for(int i = 0, sstSize = Drv_GetSizeOfNtosSST();
i < sstSize;
++i)
{
void ** pCurrentHandler = Drv_GetNtosSSTEntry(i);
void * pRealHandler = 0;
Drv_GetRealSSTValue(&m_virginityContext, i, &pRealHandler);
if (pRealHandler != *pCurrentHandler)
{
// oops, we found the difference!
// unhook this entry
Drv_HookSST(pCurrentHandler, pRealHandler);
}
}
return NT_OK;
}
这个小循环完全移除了所有 SST 钩取,并将 SST 恢复到其初始状态。
6. 演示
为了测试,我开发了一个名为 unhooker.exe 的简单控制台实用程序。此实用程序可以不带参数启动;在这种情况下,它会显示有关其功能的信息。
- “
stat
”命令显示 SST 钩取统计信息。 - “
unhook
”命令清除 SST。
此示例演示了如何使用该实用程序来检测和清除钩取。
玩得开心!
6. 如何构建
构建步骤与“隐藏驱动程序”文章相同。它们是:
- 安装 Windows Driver Developer Kit 2003 - http://www.microsoft.com/whdc/devtools/ddk/default.mspx
- 将全局环境变量“
BASEDIR
”设置为已安装 DDK 的路径。转到:计算机 -> 属性 -> 高级 -> 环境变量 -> 系统变量 -> 新建
并将其设置为:BASEDIR
-> c:\winddk\3790
(之后您需要重新启动计算机)。
如果您选择 Visual Studio 2003,则可以简单地打开 UnhookerMain.sln 并生成所有内容。
7. 历史
- 2010年3月17日:初始发布
- 这篇文章在 Apriorit 网站上 链接。