TitanHide学习总结
驱动代码基本结构
模块名 功能定位_global 全局工具库:封装内核内存分配、随机化、安全拷贝等通用操作,是其他模块的基础依赖。hider 隐藏规则管理:维护进程隐藏条目列表,处理 “隐藏 / 取消隐藏” 指令,联动反调试标记修改。hooklib 挂钩工具库:实现内联Hook(指令替换)的基础逻辑,支持32/64位系统的API挂钩 / 解钩。hooks 核心挂钩实现:挂钩内核调试相关API(如NtQueryInformationProcess),篡改返回结果实现反调试。log 日志模块:将驱动运行信息同时输出到调试器(WinDbg)和磁盘日志文件,用于调试和问题定位。misc 辅助工具:提供 “通过句柄获取PID” 等内核对象操作的辅助函数,依赖未公开内核结构。ntdll NTDLL解析:加载ntdll.dll到内核内存,提取其导出函数对应的SSDT索引,为挂钩提供基础。pe PE文件解析:处理PE文件的RVA / 文件偏移转换、导出函数查找,支持内核中解析NTDLL等模块。ssdt SSDT操作:查找系统服务描述符表(SSDT),实现内核函数的SSDT Hook/ 解钩,是内核层挂钩的核心。threadhidefromdbg 线程反调试处理:动态定位ETHREAD结构体偏移,移除线程的HideFromDebugger反调试标记。TitanHide 驱动主入口:负责驱动初始化(设备创建、符号链接、模块依赖加载)、IRP处理(用户层指令交互)、卸载清理。undocumented 未公开API封装:定义并调用Windows内核未公开的函数 / 结构(如ZwQueryInformationProcess),突破官方接口限制。
TitanHide 驱动程序入口点
TitanHide是Windows内核模式驱动,其入口点是DriverEntry(定义在TitanHide.cpp中),这是Windows内核驱动的标准入口函数(类似用户态程序的main)。
DriverEntry核心初始化流程:
1 | extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) |
- 设备与符号链接初始化:
构造内核设备名(默认\Device\TitanHide)和用户态符号链接名(\DosDevices\TitanHide),用于用户态与内核态通信。 - 注册驱动回调:
注册DriverUnload函数(驱动卸载时的清理逻辑);
初始化IRP(I/O请求包)处理函数(仅处理IRP_MJ_CREATE/CLOSE/WRITE三类请求)。 - 依赖模块初始化:
加载ntdll.dll到内核内存(NTDLL::Initialize),提取其导出函数的SSDT索引;
初始化未公开内核API(Undocumented::UndocumentedInit);
动态查找ETHREAD结构体中CrossThreadFlags偏移(FindCrossThreadFlagsOffset)。 - 创建内核设备与符号链接:
调用IoCreateDevice创建内核设备对象;
调用IoCreateSymbolicLink创建用户态可访问的符号链接(用户态通过\\.\TitanHide访问)。 - 挂钩内核函数:
调用Hooks::Initialize挂钩调试相关内核API(如NtQueryInformationProcess、NtSetInformationThread)。
TitanHide 驱动工作流程
用户态指令接收:
用户态(GUI/ 插件)通过WriteFile向驱动设备(\\.\TitanHide)写入HIDE_INFO结构体(包含指令、目标PID、隐藏类型);
驱动通过IRP_MJ_WRITE处理请求,解析HIDE_INFO数据。隐藏规则维护:
调用Hider::ProcessData处理指令:
HidePid:将目标PID及隐藏类型加入隐藏条目列表,并调用UndoHideFromDebuggerInRunningThreads移除线程的反调试标记;
UnhidePid:从隐藏条目列表中移除目标PID;
UnhideAll:清空隐藏条目列表。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60//usable functions
bool Hider::ProcessData(PVOID Buffer, ULONG Size)
{
// 校验数据合法性:数据长度必须是HIDE_INFO的整数倍
if(Size % sizeof(HIDE_INFO))
return false;
size_t HideInfoCount = Size / sizeof(HIDE_INFO);
HIDE_INFO* HideInfo = (HIDE_INFO*)Buffer;// 解析用户态传来的指令数组
for(size_t i = 0; i < HideInfoCount; i++)
{
switch(HideInfo[i].Command)
{
case HidePid: // 隐藏指定PID的进程
{
int FoundEntry = EntryFind(HideInfo[i].Pid);
if(FoundEntry == -1)// 该进程未被隐藏,新增条目
{
HIDE_ENTRY HideEntry;
HideEntry.Pid = HideInfo[i].Pid;
HideEntry.Type = HideInfo[i].Type;
EntryAdd(&HideEntry);
}
else// 该进程已被隐藏,叠加隐藏类型
{
EntrySet(FoundEntry, HideInfo[i].Type);
}
// Use DKOM to disable HideThreadHideFromDebugger in any threads in the target process that already have this flag set
if((HideInfo[i].Type & (ULONG)HideThreadHideFromDebugger) != 0 && CrossThreadFlagsOffset != 0)
{
const NTSTATUS Status = UndoHideFromDebuggerInRunningThreads(HideInfo[i].Pid);// 通过DKOM清理目标进程已有线程的HideThreadHideFromDebugger标志
if(!NT_SUCCESS(Status))
{
Log("[TITANHIDE] Failed to undo HideThreadHideFromDebugger in running threads! Status = 0x%08lX\n", Status);
}
}
}
break;
case UnhidePid:// 取消指定PID的部分/全部隐藏类型
{
int FoundEntry = EntryFind(HideInfo[i].Pid);
if(FoundEntry != -1)
{
EntryUnset(FoundEntry, HideInfo[i].Type);
if(!EntryGet(FoundEntry)) //nothing left to hide for PID, 若该进程已无任何隐藏类型,删除条目
EntryDel(FoundEntry);
}
}
break;
case UnhideAll:// 取消所有进程的隐藏
{
EntryClear();
}
break;
}
}
return true;
}内核
API挂钩拦截:
被挂钩的内核API(如NtQueryInformationProcess)被调用时,先检查当前进程PID是否在隐藏列表中;
若处于隐藏状态,篡改API返回结果(如伪装调试端口为空、隐藏调试对象、清空调试寄存器),使VMP等反调试无法获取真实的调试状态。
插件与驱动的通信机制
TitanHide插件(x64dbg/x32dbg插件)是用户态工具,通过Windows设备I/O接口 与内核驱动通信,核心流程如下:
- 通信前提
驱动已加载:插件需先确认TitanHide驱动已通过sc start TitanHide等方式加载;
设备路径一致:插件与驱动约定设备名(默认TitanHide),插件通过\\.\TitanHide访问驱动设备。 - 通信流程(以
x64dbg插件为例)
(1)插件获取目标进程PID
插件通过x64dbg的回调(CBCREATEPROCESS/CBATTACH)捕获当前调试进程的PID;
(2)插件构造指令结构体
插件构造HIDE_INFO结构体(定义在TitanHide.h中):(3)插件打开驱动设备1
2
3
4
5struct HIDE_INFO {
HIDE_COMMAND Command; // 指令:HidePid/UnhidePid/UnhideAll
ULONG Type; // 隐藏类型(位掩码,如 HideProcessDebugPort)
ULONG Pid; // 目标进程 PID
};
调用CreateFileA打开驱动设备(用户态访问内核设备的标准方式):(4)插件发送指令1
HANDLE hDevice = CreateFileA("\\\\.\\TitanHide", GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
调用WriteFile将HIDE_INFO结构体写入驱动设备,完成指令传递:(5)驱动处理指令1
WriteFile(hDevice, &HideInfo, sizeof(HIDE_INFO), &written, 0);
驱动通过IRP_MJ_WRITE接收HIDE_INFO数据,执行 “隐藏 / 取消隐藏” 逻辑。 - 通信特点
同步通信:插件调用WriteFile是同步操作,直到驱动处理完请求才返回;
无返回值:插件仅发送指令,驱动执行结果通过日志(log.cpp)记录,插件需通过调试器日志或驱动日志查看状态;
权限要求:插件需以管理员权限运行(否则CreateFileA无法打开内核设备)。
具体示例
VMP通过查询ProcessDebugPort(7)和ProcessDebugObjectHandle(31)两个核心特征判定调试器是否附加,TitanHide则通过内核层挂钩篡改查询结果 + 维护隐藏规则,从内核层彻底屏蔽这两个特征,实现反调试绕过。
用户态注册隐藏规则(插件 /
GUI触发)
当用户通过x64dbg插件 /GUI选择 “隐藏目标PID” 时,会向TitanHide驱动发送指令:
构造指令结构体:1
2
3
4
5HIDE_INFO hideInfo = {
.Command = HidePid, // 隐藏指定PID指令
.Type = HideProcessDebugPort | HideProcessDebugObjectHandle, // 需隐藏的特征类型
.Pid = 目标进程PID(如VMP保护的进程)
};发送指令到驱动:
插件 /GUI通过CreateFileA(“\\\\.\\TitanHide“)打开驱动设备;
调用WriteFile将hideInfo写入驱动,驱动通过IRP_MJ_WRITE接收指令;
驱动调用Hider::ProcessData将该PID和隐藏类型(DebugPort+DebugObjectHandle)加入HideEntries隐藏列表。内核层挂钩
NtQueryInformationProcessTitanHide在初始化时(DriverEntry)会调用Hooks::Initialize(),通过SSDT Hook替换内核原生的NtQueryInformationProcess为自定义的HookNtQueryInformationProcess函数,当VMP调用该API时,会触发以下篡改逻辑:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98static NTSTATUS NTAPI HookNtQueryInformationProcess(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength)
{
// 获取目标进程 PID
ULONG pid = Misc::GetProcessIDFromProcessHandle(ProcessHandle);
// Handle ProcessDebugObjectHandle early,优先拦截 ProcessDebugObjectHandle
if(ProcessInformationClass == ProcessDebugObjectHandle &&
ProcessInformation != nullptr &&
ProcessInformationLength == sizeof(HANDLE) &&
Hider::IsHidden(pid, HideProcessDebugObjectHandle))//检查该PID是否在隐藏列表中
{
// 验证进程句柄,获取EPROCESS内核对象
PEPROCESS Process;
NTSTATUS Status = ObReferenceObjectByHandle(ProcessHandle,
PROCESS_QUERY_INFORMATION,
*PsProcessType,
ExGetPreviousMode(),
(PVOID*)&Process,
nullptr);
if(!NT_SUCCESS(Status))
return Status;
// (The kernel calls DbgkOpenProcessDebugPort here)
ObDereferenceObject(Process);
__try
{
ProbeForWrite(ProcessInformation, sizeof(HANDLE), 4);
if (ReturnLength != nullptr)
ProbeForWrite(ReturnLength, sizeof(ULONG), 1);
//将调试对象句柄强制置为NULL(VMP检测到=0,判定无调试器)
*(PHANDLE)ProcessInformation = nullptr;
if (ReturnLength != nullptr)
*ReturnLength = sizeof(HANDLE);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
return GetExceptionCode();
}
return STATUS_PORT_NOT_SET;
}
// 调用原生 API 获取真实结果,先查真实值再篡改
NTSTATUS ret = Undocumented::NtQueryInformationProcess(ProcessHandle, ProcessInformationClass, ProcessInformation, ProcessInformationLength, ReturnLength);
if(NT_SUCCESS(ret) &&
ProcessInformation &&
ProcessInformationClass != ProcessBasicInformation) //prevent stack overflow
{
if(ProcessInformationClass == ProcessDebugFlags)// 篡改 ProcessDebugFlags
{
if(Hider::IsHidden(pid, HideProcessDebugFlags))//检查该PID是否在隐藏列表中
{
Log("[TITANHIDE] ProcessDebugFlags by %d\r\n", pid);
__try
{
BACKUP_RETURNLENGTH();
*(unsigned int*)ProcessInformation = TRUE;// 篡改:强制置为TRUE
RESTORE_RETURNLENGTH();
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
ret = GetExceptionCode();
}
}
}
else if (ProcessInformationClass == ProcessDebugPort)// 篡改 ProcessDebugPort
{
if(Hider::IsHidden(pid, HideProcessDebugPort))//检查该PID是否在隐藏列表中
{
Log("[TITANHIDE] ProcessDebugPort by %d\r\n", pid);
__try
{
BACKUP_RETURNLENGTH();
*(ULONG_PTR*)ProcessInformation = 0;// 篡改:调试端口强制置0
RESTORE_RETURNLENGTH();
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
ret = GetExceptionCode();
}
}
}
}
return ret;
}
