目录
前言
一、概述 Winlogon 的 RPC 例程
二、Handler 函数的消息处理机制
三、讨论如何安全执行 HOOK 代码
3.1 挂钩例程解决方案
3.2 脱钩例程解决方案
3.3 钩子例程解决方案
3.4 地址信息查询函数
3.5 简单保护加载的模块
四、模块如何安全地注入?
4.1 多进程处理
4.2 模块重载问题
4.3 关键注入代码
4.4 提升进程权限
4.5 处理句柄和环境检测
五、编译代码和测试运行
5.1 钩子模块代码
5.2 注入工具代码
5.3 测试结果展示
总结&后记
[正文来源: https://blog.csdn.net/qq_59075481/article/details/135980850,转载请注明出处]
前言
在第一篇文章中,我们主要分析了系统热键处理的关键接口以及如何通过选取特征码精准定位接口函数的入口点。在这一篇中,我们将进一步分析 Winlogon 相关例程的机制,并给出挂钩处理的解决方案。需要注意的是“屏蔽 Ctrl + Alt + Del 、Ctrl + Shift + Esc 等热键”系列是 Windows 系统热键拦截技术分析专栏的一个分支,主要复现 heiheiabcd 的旧文给出的拦截思路。该方案并不是最推荐的方案,如果你想要继续了解,可以查阅同专栏的其他文章。
本系列文章目录:
编号 | 文章标题 | ID 号 |
1 | 实现屏蔽 Ctrl + Alt + Del 、Ctrl + Shift + Esc 等热键(一) | 135899525 |
2 | 实现屏蔽 Ctrl + Alt + Del 、Ctrl + Shift + Esc 等热键(二) | 135980850 |
一、概述 Winlogon 的 RPC 例程
Winlogon 进程通过 SignalManagerWaitForSignal 函数循环等待系统快捷键。而关键的消息回调是通过 RPC 完成的。
Winlogon 首先会在主线程的 WinMain 函数中调用 WlStateMachineInitialize、WMsgClntInitialize 初始化全部的回调函数和事务处理线程(注意 RPC 信息的响应处理是在新线程中完成的,不是主线程);随后进入临界区并调用 StateMachineRun 开始监听事件。StateMachineRun 函数内部实际通过 SignalManagerWaitForSignal 循环等待系统事件。
我想有必要先简单介绍一下初始化后创建的事务线程,我打算在 RPC Hook 路线的第二篇中进一步讲解,我想大家先了解这部分的理论,会对本篇挂钩过程的原理有一个更好的理解,也许看完了图示流程,就会发现除了本篇的方法还有更多的拦截方法,这也是为什么我文章“兵分两路”讲解的原因。
以接收到热键消息(有 K 的函数)为例,处理流程可以用下面的简化版理论来总结。
备注:提权等操作过程类似,只不过通过的函数没有 K 字样,在第一篇说过两个函数的功能区别。整理经 WinDbg 和 IDA Pro 的逆向分析结果,并参考了 heiheiabcd 的工作。
首先,客户端的请求通过 Ndr64AsyncClientCall 最终传递进入服务例程,服务例程通过调用 Ndr64AsyncServerCallAll 来完成所有响应过程。
而该过程是通过 rpcrt4!Invoke 函数来派发的间接调用链(了解过 MS-RPC 的应该都知道 Invoke )。
随后,进入关键调用过程:
所有任务都通过 I_WMsgkSendMessage 实现,因为此时需要调用的远程过程函数的参数已经全部在堆栈或寄存器上了。我们将过程划分为三个阶段:(1)测试远程过程,验证客户端信息来确定是否取消后续的调用;(2)验证通过后调用 WMsgKMessageHandler 也就是正真的事务处理例程,在该例程中通过 WlStateMachineSetSignal 设置事件信号,该事件会通知主线程;(3)I_WMsgkSendMessage 进行最后的处理,通过 RpcAsyncCompleteCall 通知客户端完成请求。
总的来说,整个多进程跨线程的异步机制为:客户端(调用方进程)请求某个操作时,服务器(Winlogon)通过特殊的事务处理线程接收消息并验证身份,然后通过设置事件(SetEvent)释放正在等待的 WinMain 主线程,最后事务线程通知客户端请求的操作已经完成,客户端(如果有)等待到消息后类似服务器,释放相关执行过程的线程(阻滞/非阻滞过程)。
这就是为什么我们提出,可以通过拦截 WMsgKMessageHandler 函数来拦截热键的响应过程。因为该函数负责设置事件的过程,拦截它就可以及时中断 RPC 事务处理。
二、Handler 函数的消息处理机制
WMsgKMessageHandler 和 WMsgMessageHandler 函数是两个消息处理回调。也就是第一篇谈论如何定位的两个函数。
经动态调试他们的主要功能总结如下表所示:
- WMsgKMessageHandler 主要负责处理由 Csrss 注册的部分 winlogon 进程的快捷键,如:Ctrl + Shift + Esc。此外,他还负责开始菜单的注销,资源管理器上 Alt+F4 的注销/关机/重启。
- WMsgMessageHandler 主要负责处理用户层发起的大部分会话请求的处理(不包括注销),如:开始菜单发起的重启、关机、切换用户。此外,它还联合 AppInfo Service 服务,一同处理进程的提升管理员权限会话。
下面就是在第一篇也展示过的 Handler 函数的声明,不过我现在要小小纠正一下我对部分参数的解析以及名称:
int __fastcall WMsgKMessageHandler( // also WMsgMessageHandler
unsigned int uMachineState,
unsigned int uMsgWLGenericKey,
PRPC_ASYNC_STATE pAsync,
LPDWORD lpStatus
)
备注:主要修正了第四个参数的类型,以及第一个参数的猜测名称。
前三个参数都和设置事件有关系:其中,前两个和操作类型有关;第三个参数实际上指向 StateMachineSignalData 结构体,用于传递额外的数据(如果有),第四个参数是在使用异步注销会话时返回额外的状态信息,其他情况下返回值为 NULL。
通过测试,我们也初步总结了在不同操作下的消息参数值(部分),如下表1,表2 所示。
(1) WMsgMessageHandler 回调参数
uMachineState | uMsgWLGenericKey | MajorAction |
0x0001 | 0x04002009 | 开始菜单的关机按钮 |
0x0001 | 0x04002003 | 开始菜单的重启按钮 |
0x0500 | 0x1E88 | 请求提升管理员权限 |
0x0501 | 0x06B8 | 已经提升管理员权限 |
0x0403 | 0x0000 | 切换用户(初始) |
0x0202 | 0x0000 | 切换用户(恢复) |
0x0205 | 0x0000 | 切换用户(恢复) |
(2) WMsgKMessageHandler 回调参数
uMachineState | uMsgWLGenericKey | MajorAction |
0x0404 | 0x04 | Ctrl+Shift+Esc, 任务管理器 |
0x0404 | 0x00 | Ctrl+Alt+Delete, 安全桌面 |
0x0404 | 0x05 | Win+L, 锁屏, LogonUI Windows |
0x0404 | 0x07 | Win+P, 投影屏幕 |
0x0404 | 0x06 | Win+U, 设置/辅助功能 |
0x0404 | 0x0C | Win+Plus, 放大镜 |
0x0404 | 0x0D | Win+Ctrl+Enter, 讲述人 |
0x0404 | 0x0E | Win+Enter, 未知 |
0x0402 | 0x05 | 左 Alt+LShift+Print Screen, 高对比度主题 |
0x0402 | 0x01 | 连续按下五次左 Shift,滞粘键 |
0x0402 | 0x02 | 按住右 Shift 键 8 秒,筛选键 |
0x0401 | 0x03 | Alt+F4 资源管理器,重启计算机 |
0x0001 | 0x4009 | Alt+F4 资源管理器,关闭计算机 |
0x0001 | 0x0000 | Alt+F4 资源管理器,注销计算机/开始菜单注销计算机 |
在分析时 WMsgKMessageHandler 的参数研究的相对多一些,所以它的参数基本上全了。
还有就是上表均是在较新版本的 Win10/11 上测试的,低版本系统 uMachineState 是否相同并未进行测试。这些参数的测试仅是通过挂钩后对照微软的快捷键表查表得到的,官网链接为:Windows 的键盘快捷方式 - Microsoft 支持。
三、讨论如何安全执行 HOOK 代码
安全执行代码对于此类键盘钩子来说非常重要,对 Winlogon 进程的代码注入常常会导致系统不稳定,如果 Winlogon 进程崩溃或者死锁,则会导致一系列的致命错误。
3.1 挂钩例程解决方案
为了不占用系统过多资源,我们采用 API Inline Hook 这一经典的技术。 Inline Hook 通常修改函数入口的第一条指令为 jmp 指令(蹦床)来实现将函数的控制流程导向到自己编写的钩子函数。在钩子函数中处理自己的逻辑,并且在需要调用原始函数的时候,提前恢复入口处被修改过的字节(脱钩),随后通过 Call 或者 jmp 或者 ret 修改 RIP 指向原函数地址即可,调用完成后恢复挂钩。
在 x64 下,一种蹦床指令序列利用了将钩子函数的地址写入 r11 通用寄存器,并利用 jmp 无条件跳转到 r11 指向的地址来实现挂钩过程。下列报告显示了它的汇编和机器码:
Disassembly Report
Raw Hex (zero bytes in bold):
49BB000000000000000041FFE3
String Literal:
"\x49\xBB\x00\x00\x00\x00\x00\x00\x00\x00\x41\xFF\xE3"
Array Literal:
{ 0x49, 0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0xFF, 0xE3 }
Disassembly:
0: 49 bb 00 00 00 00 00 movabs r11,0x0 ; 这里填充钩子函数地址
7: 00 00 00
a: 41 ff e3 jmp r11 ; 实现跳转
该指令序列的做法只适用于 x64 进程。
我们通过 BYTE 数组存储蹦床函数的指令,随后通过写入钩子函数的地址,得到完整的蹦床函数。
随后,修改原始代码通过 memcpy 来实现,这是因为我们是在注入后 Winlogon 进程内进行修改的。修改前后,通过 VirtualProtect 修改内存保护, memcpy 覆盖原始字节后,通过 FlushInstructionCache 函数刷新 Cache 中的缓存,这是为了确保在 Cache 上的代码和内存中的一致。如果使用 WriteProcessMemory 则函数内部会调用 FlushInstructionCache 函数而不需要我们再去干预。
void HookHandler()
{
DWORD oldProtect = 0;
LPVOID hHookAddress = lpgHandlerInfo.lpHookAddress;
LPVOID hWMsgHandler = &HookedWMsgKMessageHandler;
SIZE_T bufferSize = 0;
// our trampoline
unsigned char boing[] = {
0x49, 0xbb, 0xde, 0xad,
0xc0, 0xde, 0xde, 0xad,
0xc0, 0xde, 0x41, 0xff,
0xe3 };
// add in the address of our hook
*(LPVOID*)(boing + 2) = hWMsgHandler;
bufferSize = sizeof(boing);
// disable write protect
VirtualProtect(hHookAddress, 13, PAGE_EXECUTE_READWRITE, &oldProtect);
// save the original bytes
memcpy_s(pOriginalBytes, 13, hHookAddress, 13);
// write the hook
memcpy_s(hHookAddress, bufferSize, boing, bufferSize);
// Flush Cache to make code work
FlushInstructionCache(GetCurrentProcess(), hHookAddress, bufferSize);
// enable write protect
VirtualProtect(hHookAddress, 13, oldProtect, &oldProtect);
}
3.2 脱钩例程解决方案
脱钩是必要的过程,因为我们修改了原始函数的代码,要想执行原始函数,就必须恢复原始代码。其次,卸载模块时也要恢复原始代码,否则程序就会出错。
我们通过全局变量在挂钩例程中已经备份了原始的 13 个字节,脱钩时候只需要恢复这 13 个字节即可。
参考代码如下:
void UnhookHandler()
{
DWORD bResponse = FALSE;
DWORD oldProtect = 0;
PVOID hookAddress = lpgHandlerInfo.lpHookAddress;
// disable write protect
VirtualProtect(hookAddress, 13, PAGE_EXECUTE_READWRITE, &oldProtect);
// recover original bytes
memcpy_s(hookAddress, 13, pOriginalBytes, 13);
// Flush Cache to make code work
FlushInstructionCache(GetCurrentProcess(), hookAddress, 13);
// enable write protect
VirtualProtect(hookAddress, 13, oldProtect, &oldProtect);
}
这样,是不是很类似于挂钩的过程?
3.3 钩子例程解决方案
钩子例程就是我们需要做处理的函数,由于 WMsgMessageHandler 和 WMsgKMessageHandler 是相同参数的函数,所以我们的钩子例程可以做成通配函数。
首先定义函数的声明:
typedef int(__fastcall* __WMsgKMessageHandler)(
unsigned int uMachineState,
unsigned int uMsgWLGenericKey,
PRPC_ASYNC_STATE pAsync,
LPDWORD lpStatus // int* pReserved
);
随后,指向原函数地址的指针 LPVOID 用于函数的指针调用。
在钩子例程中设置对感兴趣的参数的筛选即可,不过要注意:如果不执行任何操作,则 Handler 函数必须返回 1 代表结束调用,否则在部分操作中 Winlogon 会持续等待直至死锁,这一点可以通过 IDA Pro 的逆向代码确认。此外,不应该挂钩全部的参数,因为有参数超出我们目前已知的稳定参数列表,拦截这部分操作会造成系统死锁,比如切换用户的操作就不推荐拦截。
一个可供参考的钩子例程如下:
int __fastcall HookedWMsgHandler(
unsigned int uMachineState,
unsigned int uMsgWLGenericKey,
PRPC_ASYNC_STATE pAsync,
LPDWORD lpStatus // int* pReserved
)
{
int dwFunResponse = 0;
/*
* ///
*
* WMsgMessageHandler 控制 账户状态/UAC 相关的功能
* uMachineState uMsgWLGenericKey MajorAction
* 0001 04002009 关闭本地计算机
* 0001 04002003 重启本地计算机
* 0500 1E88 请求提升管理员权限
* 0501 06B8 已经提升管理员权限
* 0403 0000 切换用户进行时(建议窗口在 Winlogon 下创建,并置前端)
* 0202 0000 切换用户恢复时(具体操作未知)
* 0205 0000 切换用户恢复时(具体操作未知)
* 注意:(1)对于非已知代码的情况,不要使用阻滞过程,否则会导致死锁。
* (2)如果不需要执行指定的过程,函数返回值必须是 1,如果为 0 可能会陷入等待。
*
*
*
* WMsgKMessageHandler 控制 系统热键/注销 相关的功能
* uMachineState uMsgWLGenericKey MajorAction
* 0404 4 Ctrl+Shift+Esc, 任务管理器
* 0404 0 Ctrl+Alt+Delete, 安全桌面
* 0404 5 Win+L, 锁屏, LogonUI Windows
* 0404 7 Win+P, 投影屏幕
* 0404 6 Win+U, 设置/辅助功能
* 0404 C Win+Plus, 放大镜
* 0404 D Win+Ctrl+Enter, 讲述人
* 0404 E Win+Enter, 未知
* 0402 5 左Alt+LShift+Print Screen, 高对比度主题
* 0402 1 连续按下五次左侧 Shift,滞粘键
* 0402 2 按住右侧 Shift 键 8 秒,筛选键
* 0001 3 Alt+F4 资源管理器,重启计算机
* 0001 4009 Alt+F4 资源管理器,关闭计算机
* 0001 0 Alt+F4 资源管理器,注销计算机
*
*
*/
// 样例参数过滤代码
if (uMachineState == 0x404)
{
switch(uMsgWLGenericKey){
case ID_1:
{
// do something
}
break;
case ID_2:
{
// do something
}
break;
default:
break;
}
// UnHook
UnhookHandler(NULL);
auto WMsgKMessageHandler = (__WMsgKMessageHandler)lpgHandlerInfo.lpHookAddress;
// call original func
dwFunResponse = WMsgKMessageHandler(uMachineState, uMsgWLGenericKey, pAsync, lpStatus);
// Re-hook
HookHandler(NULL);
return dwFunResponse;
}
3.4 地址信息查询函数
由于钩子代码通过 Dll 模块实现,上面的函数可以设置为由一个可以查询地址信息的函数来实现,在外部进程中,只需要知道地址,就可以使用远程线程调用位于 winlogon 进程中 Hook 模块的挂钩/脱钩例程。
我们使用一个结构体来记录所需要的所有信息:
typedef struct HANDLER_INFO_STRUCT
{
DWORD cbSize = 0;
DWORD dwMainThread = 0;
HMODULE hHookModule = nullptr;
LPVOID lpHookHandler = nullptr;
LPVOID lpUnhookHandler = nullptr;
LPVOID lpSafeFreeLib = nullptr;
LPVOID lpWinlogonBase = nullptr;
LPVOID lpHookAddress = nullptr;
//LPVOID lpEPLFuncAddress = nullptr;
}HANDLER_INFO_STRUCT, * LPHANDLER_INFO_STRUCT;
随后我们创建共享节,以便同步一些信息,里面为部分需要共享的全局变量进行初始化。
#pragma data_seg("WMsgHookData")
CHAR pOriginalBytes[13] = { 0 };
HANDLER_INFO_STRUCT lpgHandlerInfo = { 0 };
#pragma data_seg()
#pragma comment(linker,"/SECTION:WMsgHookData,RWS")
使用下面的函数来拷贝模块共享节中的全局变量 HANDLER_INFO_STRUCT,来使得同样加载了 Hook 模块的挂钩工具,其在外部就可以获得 winlogon 信息的拷贝副本。
BOOL WINAPI GetHandlerAddress(LPHANDLER_INFO_STRUCT lpHandlerInfo)
{
// 结构体指针不为空
if (lpHandlerInfo == nullptr)
{
OutputDebugStringW(L"Er:[GetHandlerAddress] status:[Invalid nullptr].\n");
return FALSE;
}
// 计算 cbSize 成员的地址
SIZE_T cbSizeOff = offsetof(HANDLER_INFO_STRUCT, cbSize);
PDWORD lpcbSize = reinterpret_cast<PDWORD>(lpHandlerInfo + cbSizeOff);
// 保证传入结构体大小等于预期的大小
if (*lpcbSize != sizeof(HANDLER_INFO_STRUCT))
{
OutputDebugStringW(L"Er:[GetHandlerAddress] status:[Invalid HANDLER_INFO_STRUCT Size].\n");
return FALSE;
}
// 将传入结构体指针中的各个字段赋值给lpHandlerInfo结构体中对应的字段,并返回TRUE
lpHandlerInfo->lpHookHandler = lpgHandlerInfo.lpHookHandler;
lpHandlerInfo->lpUnhookHandler = lpgHandlerInfo.lpUnhookHandler;
lpHandlerInfo->lpHookAddress = lpgHandlerInfo.lpHookAddress;
lpHandlerInfo->lpWinlogonBase = lpgHandlerInfo.lpWinlogonBase;
lpHandlerInfo->lpSafeFreeLib = lpgHandlerInfo.lpSafeFreeLib;
lpHandlerInfo->dwMainThread = lpgHandlerInfo.dwMainThread;
lpHandlerInfo->hHookModule = lpgHandlerInfo.hHookModule;
return TRUE;
}
现在,我们只需要导出这个副本查询函数,即可在任何加载了该模块的进程获取相同的信息拷贝。这里的操作基于假设采用随机化加载基址时,也能安全远程控制挂钩状态;否则,我们只需要固定函数地址就直接远程线程调用了,而不需要传递远程进程中的模块地址。
3.5 简单保护加载的模块
以上代码均在 Dll 模块中实现,并注入到 winlogon 进程。但是,为了防止一些第三方软件卸载模块,我们简单采取一点防护机制。在 R3 下防护动态加载的模块不被意外卸载需要很多的策略,比如:LDR 断链、VAD 记录擦除、PE 头擦除、修改入口函数、内存注入等。这里我们只使用了模块静态化技术这一项技术。
进程中初次加载模块时会将模块映射到地址空间中,当之后多次加载同一个模块时,并不会重新加载这个模块,而是只返回相同的地址。此时,例程会增加一个由进程维护的模块的引用计数这样子的一个变量。当使用 FreeLibrary 卸载模块时,引用计数会减小,只有引用计数减到 0,才会正真释放占用的资源。并且规定,静态加载的模块,其引用计数为 -1,并且不能增加计数或通过 FreeLibrary / LdrUnLoadDll 等卸载模块。模块静态化是一个很常见的模块保护技术,它通过修改模块的引用计数为 -1,来使得模块不可被标准的 API 成功卸载。
另一种类似的技术是通过 uFlags 位域覆盖修改标志位,系统会认为模块是静态的进而阻止模块的卸载。
参考文献:(1)The covert way to find the Reference Count of DLL - www.SecurityXploded.com
(2)Make Your Dynamic Module Unfreeable (Anti-FreeLibrary) | secrary[dot]com
这两个技术都通过 LDR_DATA_TABLE_ENTRY 中的成员来完成,前者通过修改 DdagNode 结构中的 LoadCount 以及 LDR 中废弃的(旧系统用到) ObsoleteLoadCount 成员,将他们赋值为 -1,即可修改模块属性为静态:
pLdrDataEntry->DdagNode->LoadCount = 0xffffffff;
pLdrDataEntry->ObsoleteLoadCount = 0xffff;
修改前,动态加载的模块可以被 RemoteDll 等工具卸载:
修改后,按钮灰显,表明这个模块不可以被卸载:
这种修改不需要针对 R3 下哪种工具,只需要在 Winlogon 进程内修改即可。
后者通过修改位域 ProcessStaticImport 为 TRUE(1) 来实现的,该结构体如下:
//0x120 bytes (sizeof)
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks; //0x0
LIST_ENTRY InMemoryOrderLinks; //0x10
LIST_ENTRY InInitializationOrderLinks; //0x20
VOID* DllBase; //0x30
VOID* EntryPoint; //0x38
ULONG SizeOfImage; //0x40
UNICODE_STRING FullDllName; //0x48
UNICODE_STRING BaseDllName; //0x58
union
{
UCHAR FlagGroup[4]; //0x68
ULONG Flags; //0x68
struct
{
ULONG PackagedBinary : 1; //0x68
ULONG MarkedForRemoval : 1; //0x68
ULONG ImageDll : 1; //0x68
ULONG LoadNotificationsSent : 1; //0x68
ULONG TelemetryEntryProcessed : 1; //0x68
ULONG ProcessStaticImport : 1; //0x68
ULONG InLegacyLists : 1; //0x68
ULONG InIndexes : 1; //0x68
ULONG ShimDll : 1; //0x68
ULONG InExceptionTable : 1; //0x68
ULONG ReservedFlags1 : 2; //0x68
ULONG LoadInProgress : 1; //0x68
ULONG LoadConfigProcessed : 1; //0x68
ULONG EntryProcessed : 1; //0x68
ULONG ProtectDelayLoad : 1; //0x68
ULONG ReservedFlags3 : 2; //0x68
ULONG DontCallForThreads : 1; //0x68
ULONG ProcessAttachCalled : 1; //0x68
ULONG ProcessAttachFailed : 1; //0x68
ULONG CorDeferredValidate : 1; //0x68
ULONG CorImage : 1; //0x68
ULONG DontRelocate : 1; //0x68
ULONG CorILOnly : 1; //0x68
ULONG ChpeImage : 1; //0x68
ULONG ReservedFlags5 : 2; //0x68
ULONG Redirected : 1; //0x68
ULONG ReservedFlags6 : 2; //0x68
ULONG CompatDatabaseProcessed : 1; //0x68
}uFlags;
};
USHORT ObsoleteLoadCount; //0x6c
USHORT TlsIndex; //0x6e
LIST_ENTRY HashLinks; //0x70
ULONG TimeDateStamp; //0x80
struct ACTIVATION_CONTEXT* EntryPointActivationContext; //0x88
VOID* Lock; //0x90
LDR_DDAG_NODE* DdagNode; //0x98
LIST_ENTRY NodeModuleLink; //0xa0
struct LDRP_LOAD_CONTEXT* LoadContext; //0xb0
VOID* ParentDllBase; //0xb8
VOID* SwitchBackContext; //0xc0
RTL_BALANCED_NODE BaseAddressIndexNode; //0xc8
RTL_BALANCED_NODE MappingInfoIndexNode; //0xe0
ULONGLONG OriginalBase; //0xf8
LARGE_INTEGER LoadTime; //0x100
ULONG BaseNameHashValue; //0x108
LDR_DLL_LOAD_REASON LoadReason; //0x10c
ULONG ImplicitPathOptions; //0x110
ULONG ReferenceCount; //0x114
ULONG DependentLoadFlags; //0x118
UCHAR SigningLevel; //0x11c
}LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
注意:位域成员需要按位修改取值。
其实,开启这个标志位保护模块的官方的方法是调用一次 GetModuleHandleEx 并指定 GET_MODULE_HANDLE_EX_FLAG_PIN 标志,但似乎并没法取消掉。
HMODULE hTestModule = 0;
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, L"模块名称", &hTestModule);
开启该标志位后,无论使用多少次 FreeLibrary 都不会真正卸载模块。
下面的代码实现上述所有功能,需要注意是,有三个链表都需要枚举并修改,关于这个结构枚举的原理可以看我的另一篇文章“利用 LDR_DATA_TABLE 枚举进程模块信息”,此外需要注意如果包含 winternl 头文件,它里面有 ldrp.h 部分结构的重复声明(微软给的结构体不完整)。
ldrp.h:
#pragma once
#include <winnt.h>
#include <WTypesbase.h>
//#include <winternl.h>
// Kernels | x64 | Windows 10 | 2016 | 2210 22H2(May 2023 Update)
//0x18 bytes (sizeof)
typedef struct _RTL_BALANCED_NODE
{
union
{
_RTL_BALANCED_NODE* Children[2]; //0x0
struct
{
_RTL_BALANCED_NODE* Left; //0x0
_RTL_BALANCED_NODE* Right; //0x8
};
};
union
{
struct
{
UCHAR Red : 1; //0x10
UCHAR Balance : 2; //0x10
};
ULONGLONG ParentValue; //0x10
};
}RTL_BALANCED_NODE, * PRTL_BALANCED_NODE, * LPRTL_BALANCED_NODE;
//0x4 bytes (sizeof)
enum _LDR_DLL_LOAD_REASON
{
LoadReasonStaticDependency = 0,
LoadReasonStaticForwarderDependency = 1,
LoadReasonDynamicForwarderDependency = 2,
LoadReasonDelayloadDependency = 3,
LoadReasonDynamicLoad = 4,
LoadReasonAsImageLoad = 5,
LoadReasonAsDataLoad = 6,
LoadReasonEnclavePrimary = 7,
LoadReasonEnclaveDependency = 8,
LoadReasonUnknown = -1
};
typedef _LDR_DLL_LOAD_REASON LDR_DLL_LOAD_REASON;
//0x10 bytes (sizeof)
typedef struct _LDR_SERVICE_TAG_RECORD
{
_LDR_SERVICE_TAG_RECORD* Next; //0x0
ULONG ServiceTag; //0x8
}LDR_SERVICE_TAG_RECORD, * PLDR_SERVICE_TAG_RECORD;
//0x8 bytes (sizeof)
typedef struct _LDRP_CSLIST
{
SINGLE_LIST_ENTRY* Tail; //0x0
}LDRP_CSLIST;
//0x4 bytes (sizeof)
enum _LDR_DDAG_STATE
{
LdrModulesMerged = -5,
LdrModulesInitError = -4,
LdrModulesSnapError = -3,
LdrModulesUnloaded = -2,
LdrModulesUnloading = -1,
LdrModulesPlaceHolder = 0,
LdrModulesMapping = 1,
LdrModulesMapped = 2,
LdrModulesWaitingForDependencies = 3,
LdrModulesSnapping = 4,
LdrModulesSnapped = 5,
LdrModulesCondensed = 6,
LdrModulesReadyToInit = 7,
LdrModulesInitializing = 8,
LdrModulesReadyToRun = 9
};
typedef _LDR_DDAG_STATE LDR_DDAG_STATE;
//0x50 bytes (sizeof)
typedef struct _LDR_DDAG_NODE
{
LIST_ENTRY Modules; //0x0
PLDR_SERVICE_TAG_RECORD ServiceTagList; //0x10
ULONG LoadCount; //0x18
ULONG LoadWhileUnloadingCount; //0x1c
ULONG LowestLink; //0x20
LDRP_CSLIST Dependencies; //0x28
LDRP_CSLIST IncomingDependencies; //0x30
LDR_DDAG_STATE State; //0x38
SINGLE_LIST_ENTRY CondenseLink; //0x40
ULONG PreorderNumber; //0x48
}LDR_DDAG_NODE, * PLDR_DDAG_NODE;
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING;
typedef UNICODE_STRING* PUNICODE_STRING;
typedef const UNICODE_STRING* PCUNICODE_STRING;
//0x120 bytes (sizeof)
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks; //0x0
LIST_ENTRY InMemoryOrderLinks; //0x10
LIST_ENTRY InInitializationOrderLinks; //0x20
VOID* DllBase; //0x30
VOID* EntryPoint; //0x38
ULONG SizeOfImage; //0x40
UNICODE_STRING FullDllName; //0x48
UNICODE_STRING BaseDllName; //0x58
union
{
UCHAR FlagGroup[4]; //0x68
ULONG Flags; //0x68
struct
{
ULONG PackagedBinary : 1; //0x68
ULONG MarkedForRemoval : 1; //0x68
ULONG ImageDll : 1; //0x68
ULONG LoadNotificationsSent : 1; //0x68
ULONG TelemetryEntryProcessed : 1; //0x68
ULONG ProcessStaticImport : 1; //0x68
ULONG InLegacyLists : 1; //0x68
ULONG InIndexes : 1; //0x68
ULONG ShimDll : 1; //0x68
ULONG InExceptionTable : 1; //0x68
ULONG ReservedFlags1 : 2; //0x68
ULONG LoadInProgress : 1; //0x68
ULONG LoadConfigProcessed : 1; //0x68
ULONG EntryProcessed : 1; //0x68
ULONG ProtectDelayLoad : 1; //0x68
ULONG ReservedFlags3 : 2; //0x68
ULONG DontCallForThreads : 1; //0x68
ULONG ProcessAttachCalled : 1; //0x68
ULONG ProcessAttachFailed : 1; //0x68
ULONG CorDeferredValidate : 1; //0x68
ULONG CorImage : 1; //0x68
ULONG DontRelocate : 1; //0x68
ULONG CorILOnly : 1; //0x68
ULONG ChpeImage : 1; //0x68
ULONG ReservedFlags5 : 2; //0x68
ULONG Redirected : 1; //0x68
ULONG ReservedFlags6 : 2; //0x68
ULONG CompatDatabaseProcessed : 1; //0x68
}uFlags;
};
USHORT ObsoleteLoadCount; //0x6c
USHORT TlsIndex; //0x6e
LIST_ENTRY HashLinks; //0x70
ULONG TimeDateStamp; //0x80
struct ACTIVATION_CONTEXT* EntryPointActivationContext; //0x88
VOID* Lock; //0x90
LDR_DDAG_NODE* DdagNode; //0x98
LIST_ENTRY NodeModuleLink; //0xa0
struct LDRP_LOAD_CONTEXT* LoadContext; //0xb0
VOID* ParentDllBase; //0xb8
VOID* SwitchBackContext; //0xc0
RTL_BALANCED_NODE BaseAddressIndexNode; //0xc8
RTL_BALANCED_NODE MappingInfoIndexNode; //0xe0
ULONGLONG OriginalBase; //0xf8
LARGE_INTEGER LoadTime; //0x100
ULONG BaseNameHashValue; //0x108
LDR_DLL_LOAD_REASON LoadReason; //0x10c
ULONG ImplicitPathOptions; //0x110
ULONG ReferenceCount; //0x114
ULONG DependentLoadFlags; //0x118
UCHAR SigningLevel; //0x11c
}LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
//0x58 bytes (sizeof)
typedef struct _PEB_LDR_DATA32
{
ULONG Length; // +0x00
BOOLEAN Initialized; // +0x04
PVOID SsHandle; // +0x08
LIST_ENTRY InLoadOrderModuleList; // +0x0c
LIST_ENTRY InMemoryOrderModuleList; // +0x14
LIST_ENTRY InInitializationOrderModuleList;// +0x1c
} PEB_LDR_DATA32, * PPEB_LDR_DATA32; // +0x24
typedef struct _PEB32
{
UCHAR InheritedAddressSpace; //0x0
UCHAR ReadImageFileExecOptions; //0x1
UCHAR BeingDebugged; //0x2
union
{
UCHAR BitField; //0x3
struct
{
UCHAR ImageUsesLargePages : 1; //0x3
UCHAR IsProtectedProcess : 1; //0x3
UCHAR IsImageDynamicallyRelocated : 1; //0x3
UCHAR SkipPatchingUser32Forwarders : 1; //0x3
UCHAR IsPackagedProcess : 1; //0x3
UCHAR IsAppContainer : 1; //0x3
UCHAR IsProtectedProcessLight : 1; //0x3
UCHAR IsLongPathAwareProcess : 1; //0x3
};
};
PVOID Mutant; //0x4
PVOID ImageBaseAddress; //0x8
PEB_LDR_DATA32* Ldr; //0xc
struct RTL_USER_PROCESS_PARAMETERS* ProcessParameters; //0x10
PVOID SubSystemData; //0x14
PVOID ProcessHeap; //0x18
RTL_CRITICAL_SECTION* FastPebLock; //0x1c
SLIST_HEADER* volatile AtlThunkSListPtr; //0x20
PVOID IFEOKey; //0x24
} PEB32, * PPEB32;
typedef struct _STRING64
{
USHORT Length; //0x0
USHORT MaximumLength; //0x2
ULONGLONG Buffer; //0x8
}STRING64, * LPSTRING64;
typedef struct _PEB_LDR_DATA64
{
ULONG Length; //0x0
UCHAR Initialized; //0x4
PVOID SsHandle; //0x8
LIST_ENTRY InLoadOrderModuleList; //0x10
LIST_ENTRY InMemoryOrderModuleList; //0x20
LIST_ENTRY InInitializationOrderModuleList; //0x30
PVOID EntryInProgress; //0x40
UCHAR ShutdownInProgress; //0x48
PVOID ShutdownThreadId; //0x50
}PEB_LDR_DATA64, * PPEB_LDR_DATA64;
typedef struct _PEB64
{
UCHAR InheritedAddressSpace; //0x0
UCHAR ReadImageFileExecOptions; //0x1
UCHAR BeingDebugged; //0x2
union
{
UCHAR BitField; //0x3
struct
{
UCHAR ImageUsesLargePages : 1; //0x3
UCHAR IsProtectedProcess : 1; //0x3
UCHAR IsImageDynamicallyRelocated : 1; //0x3
UCHAR SkipPatchingUser32Forwarders : 1; //0x3
UCHAR IsPackagedProcess : 1; //0x3
UCHAR IsAppContainer : 1; //0x3
UCHAR IsProtectedProcessLight : 1; //0x3
UCHAR IsLongPathAwareProcess : 1; //0x3
};
};
UCHAR Padding0[4]; //0x4
ULONGLONG Mutant; //0x8
ULONGLONG ImageBaseAddress; //0x10
PEB_LDR_DATA64* Ldr; //0x18
ULONGLONG ProcessParameters; //0x20
ULONGLONG SubSystemData; //0x28
ULONGLONG ProcessHeap; //0x30
ULONGLONG FastPebLock; //0x38
ULONGLONG AtlThunkSListPtr; //0x40
ULONGLONG IFEOKey; //0x48
}PEB64, * PPEB64;
#ifdef _WIN64
typedef PEB64 PEB;
typedef PPEB64 PPEB;
typedef PEB_LDR_DATA64 PEB_LDR_DATA;
typedef PPEB_LDR_DATA64 PPEB_LDR_DATA;
#else
typedef PEB32 PEB;
typedef PPEB32 PPEB;
typedef PEB_LDR_DATA32 PEB_LDR_DATA;
typedef PPEB_LDR_DATA32 PPEB_LDR_DATA;
#endif
main.cpp:
DWORD WINAPI EasyProtectLibrary(LPVOID lpThreadParameter)
{
auto ldrpt = (LPLDR_PROTECT_STRUCT)lpThreadParameter;
if (ldrpt == nullptr) return 0;
BOOL bNewValue = ldrpt->bEnableProtect;
BOOL bOldProtect = 0;
DWORD index = 0;
DWORD bResponse = 0;
const WCHAR lpFileName[] = HOOK_MODULE_NAME;
PPEB_LDR_DATA pPebLdrData = nullptr;
PLDR_DATA_TABLE_ENTRY pLdrDataEntry = nullptr;
PLIST_ENTRY pListEntryStart = nullptr;
PLIST_ENTRY pListEntryEnd = nullptr;
SIZE_T ulTestSize = 0;
SIZE_T ulRealSize = wcsnlen_s(lpFileName, MAX_PATH);
#ifdef _WIN64
ULONGLONG ModuleSum = NULL;
PPEB peb = (PPEB)__readgsqword(0x60);
#else
ULONG ModuleSum = NULL;
PPEB32 peb = (PPEB32)__readfsdword(0x30);
#endif
__try {
pPebLdrData = peb->Ldr;
// 以模块加载顺序排列的链表
pListEntryStart = pPebLdrData->InLoadOrderModuleList.Flink;
pListEntryEnd = pPebLdrData->InLoadOrderModuleList.Blink;
for (index = 0; pListEntryStart != pListEntryEnd; index++)
{
pLdrDataEntry = CONTAINING_RECORD(pListEntryStart,
LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
ulTestSize = wcsnlen_s(pLdrDataEntry->BaseDllName.Buffer, MAX_PATH);
if (ulTestSize != ulRealSize || ulTestSize == MAX_PATH)
{
pListEntryStart = pListEntryStart->Flink;
continue;
}
if (!_wcsicmp(pLdrDataEntry->BaseDllName.Buffer, lpFileName))
{
if (bNewValue == TRUE)
{
// 引用计数主要有两个成员,引用计数为 -1 表示静态加载的模块,
// 并且不允许卸载
pLdrDataEntry->DdagNode->LoadCount = 0xffffffff;
pLdrDataEntry->ObsoleteLoadCount = 0xffff;
pLdrDataEntry->Flags |= (1 << 5); // 将第六位置为 1
}
else {
// 引用计数主要有两个成员,引用计数为 -1 表示静态加载的模块,
// 并且不允许卸载
pLdrDataEntry->DdagNode->LoadCount = 1;
pLdrDataEntry->ObsoleteLoadCount = 1;
// ProcessStaticImport 位域如果为 1, 则任何卸载调用都将直接返回 TRUE
// 而不做任何资源释放操作
pLdrDataEntry->Flags &= ~(1 << 5); // 将第六位清零
}
//pLdrDataEntry->uFlags.ProcessStaticImport = bNewValue;
bResponse |= 0x1;
break;
}
pListEntryStart = pListEntryStart->Flink;
}
// 以内存位置排列的模块链表
pListEntryStart = pPebLdrData->InMemoryOrderModuleList.Flink;
pListEntryEnd = pPebLdrData->InMemoryOrderModuleList.Blink;
for (index = 0; pListEntryStart != pListEntryEnd; index++)
{
pLdrDataEntry = CONTAINING_RECORD(pListEntryStart,
LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
ulTestSize = wcsnlen_s(pLdrDataEntry->BaseDllName.Buffer, MAX_PATH);
if (ulTestSize != ulRealSize || ulTestSize == MAX_PATH)
{
pListEntryStart = pListEntryStart->Flink;
continue;
}
if (!_wcsicmp(pLdrDataEntry->BaseDllName.Buffer, lpFileName))
{
if (bNewValue == TRUE)
{
pLdrDataEntry->DdagNode->LoadCount = 0xffffffff;
pLdrDataEntry->ObsoleteLoadCount = 0xffff;
pLdrDataEntry->Flags |= (1 << 5);
}
else {
pLdrDataEntry->DdagNode->LoadCount = 1;
pLdrDataEntry->ObsoleteLoadCount = 1;
pLdrDataEntry->Flags &= ~(1 << 5);
}
//pLdrDataEntry->uFlags.ProcessStaticImport = bNewValue;
bResponse |= 0x2;
break;
}
pListEntryStart = pListEntryStart->Flink;
}
// 以初始化顺序加载的模块列表
pListEntryStart = pPebLdrData->InInitializationOrderModuleList.Flink;
pListEntryEnd = pPebLdrData->InInitializationOrderModuleList.Blink;
for (index = 0; pListEntryStart != pListEntryEnd; index++)
{
pLdrDataEntry = CONTAINING_RECORD(pListEntryStart,
LDR_DATA_TABLE_ENTRY, InInitializationOrderLinks);
ulTestSize = wcsnlen_s(pLdrDataEntry->BaseDllName.Buffer, MAX_PATH);
if (ulTestSize != ulRealSize || ulTestSize == MAX_PATH)
{
pListEntryStart = pListEntryStart->Flink;
continue;
}
if (!_wcsicmp(pLdrDataEntry->BaseDllName.Buffer, lpFileName))
{
if (bNewValue == TRUE)
{
pLdrDataEntry->DdagNode->LoadCount = 0xffffffff;
pLdrDataEntry->ObsoleteLoadCount = 0xffff;
pLdrDataEntry->Flags |= (1 << 5);
}
else {
pLdrDataEntry->DdagNode->LoadCount = 1;
pLdrDataEntry->ObsoleteLoadCount = 1;
pLdrDataEntry->Flags &= ~(1 << 5);
}
bResponse |= 0x4;
break;
}
pListEntryStart = pListEntryStart->Flink;
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
OutputDebugStringW(L"Er:Exception occurred while accessing memory.\n");
return FALSE;
}
return bResponse;
}
这样,我们只需要在模块初始化和脱钩时,调用该函数就可以在 winlogon 中简单地保护/脱保护钩子模块。
四、模块如何安全地注入?
为什么要考虑模块的安全注入?
4.1 多进程处理
考虑到系统可能因为登陆多个账户而存在多个 winlogon.exe 进程。所以,在注入时,需要额外考虑一些信息。
WTSGetActiveConsoleSessionId 函数检索控制台会话的会话标识符。 控制台会话是当前附加到物理控制台的会话。 将检索到的 Winlogon.exe 的 PID 通过 ProcessIdToSessionId 函数转换为所属的控制台会话 ID,如果控制台会话 ID 相等,则说明该 winlogon.exe 进程属于当前会话。
我们通过下面的代码实现搜索当前用户会话对应的 winlogon.exe 进程的 PID:
DWORD WINAPI GetActiveConsoleSessionId() {
return WTSGetActiveConsoleSessionId();
}
BOOL WINAPI IsProcessInSession(DWORD processId, DWORD sessionId) {
DWORD session;
if (!ProcessIdToSessionId(processId, &session)) {
printf("Error: ProcessIdToSessionId failed.\n");
return FALSE;
}
return session == sessionId;
}
DWORD WINAPI FindWinlogonProcessId() {
DWORD dwProcessId = 0;
DWORD activeSessionId = GetActiveConsoleSessionId();
if (activeSessionId == 0xFFFFFFFF) {
printf("Error: Unable to retrieve active console session ID.\n");
return 0;
}
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot == INVALID_HANDLE_VALUE) {
printf("Error: CreateToolhelp32Snapshot failed.\n");
return 0;
}
PROCESSENTRY32 entry;
entry.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(snapshot, &entry)) {
printf("Error: Process32First failed.\n");
SafeCloseHandle(snapshot);
return 0;
}
do {
if (entry.cntThreads <= 1u) continue; // 跳过僵尸进程
if (_wcsicmp(entry.szExeFile, L"winlogon.exe") == 0) {
if (IsProcessInSession(entry.th32ProcessID, activeSessionId)) {
dwProcessId = entry.th32ProcessID;
break;
}
}
} while (Process32Next(snapshot, &entry));
SafeCloseHandle(snapshot);
return dwProcessId;
}
4.2 模块重载问题
不允许重复加载模块,可能产生冲突。通过检索模块是否已经被加载来校验,下面是使用官方支持的检索过程的示例代码:
BOOL WINAPI CheckProcessHasLoadModule(DWORD dwProcessId, LPCWSTR wsFileName) {
/*
* 参数为TH32CS_SNAPMODULE 或 TH32CS_SNAPMODULE32时,
* 如果函数失败并返回ERROR_BAD_LENGTH,则重试该函数直至成功
* 进程创建未初始化完成时,CreateToolhelp32Snapshot会返回error 299,但其它情况下不会。
*/
HANDLE hSnapshot = CreateToolhelp32Snapshot(
TH32CS_SNAPMODULE |
TH32CS_SNAPMODULE32,
dwProcessId);
while (INVALID_HANDLE_VALUE == hSnapshot) {
DWORD dwError = GetLastError();
if (dwError == ERROR_BAD_LENGTH) {
hSnapshot = CreateToolhelp32Snapshot(
TH32CS_SNAPMODULE |
TH32CS_SNAPMODULE32,
dwProcessId);
continue;
}
else {
printf("CreateToolhelp32Snapshot failed: %d, targetProcessId:%d.\n",
dwError, dwProcessId);
return FALSE;
}
}
MODULEENTRY32W mi = { 0 };
mi.dwSize = sizeof(MODULEENTRY32W); // 第一次使用必须初始化成员
BOOL bRet = Module32FirstW(hSnapshot, &mi);
while (bRet) {
// mi.szModule 是短路径
if (wcsstr(wsFileName, mi.szModule) ||
wcsstr(mi.szModule, wsFileName) ) {
if (hSnapshot != NULL) CloseHandle(hSnapshot);
return TRUE;
}
mi.dwSize = sizeof(MODULEENTRY32W);
bRet = Module32NextW(hSnapshot, &mi);
}
if (hSnapshot != NULL) SafeCloseHandle(hSnapshot);
return FALSE;
}
你可以决定添加重载时允许卸载旧的模块的操作,这在我代码中暂未考虑实现。
4.3 关键注入代码
我们通过 NTCreateThreadEx 注入和实现远程控制挂钩(可以考虑通过进程通信实现)。但是要注意 NTCreateThreadEx 函数在 XP 上不可用,需要在 Win 7 及以上系统才支持。并且 XP 不使用异步 RPC 过程处理 winlogon 信息。
下面的代码分别展示了通用注入代码和钩子控制代码,运用的原理相同。
a. 模块注入代码:
BOOL WINAPI InjectMouldeHandler(
HANDLE hProcess,
LPCWSTR pszDllFileName
)
{
// 1.目标进程句柄
if (hProcess == NULL || pszDllFileName == nullptr)
{
wprintf(L"Error: InvalidSyntax error from InjectMouldeHandler.\n");
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
size_t pathSize = (wcslen(pszDllFileName) + 1) * sizeof(wchar_t);
SetLastError(0);
// 2.在目标进程中申请空间
LPVOID lpPathAddr = VirtualAllocEx( hProcess, 0, pathSize,
MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (NULL == lpPathAddr)
{
wprintf(L"Error[%d]: Failed to apply memory in the target process!\n", GetLastError());
return FALSE;
}
SetLastError(0);
// 3.在目标进程中写入 Dll 路径
if (FALSE == WriteProcessMemory( hProcess, lpPathAddr,
pszDllFileName, pathSize, NULL) )
{
wprintf(L"Error[%d]: Failed to write module path in target process!\n", GetLastError());
return FALSE;
}
SetLastError(0);
// 4.加载 ntdll.dll
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
if (NULL == hNtdll)
{
wprintf(L"Error[%d]: Failed to load NTDLL.DLL!\n", GetLastError());
VirtualFreeEx(hProcess, lpPathAddr, 0, MEM_RELEASE);
return FALSE;
}
SetLastError(0);
// 5.获取 LoadLibraryW 的函数地址, FARPROC 可以自适应 32 位与 64 位
FARPROC pFuncProcAddr = MyGetProcAddress64(GetModuleHandleW(L"KernelBase.dll"),
"LoadLibraryW");
if (NULL == pFuncProcAddr)
{
wprintf(L"Error[%d]: Failed to obtain the address of the LoadLibrary function!\n",
GetLastError());
VirtualFreeEx(hProcess, lpPathAddr, 0, MEM_RELEASE);
return FALSE;
}
// 6.获取 NtCreateThreadEx 函数地址,该函数在32位与64位下原型不同
// _WIN64 用来判断编译环境 ,_WIN32用来判断是否是 Windows 系统
#ifdef _WIN64
typedef DWORD(WINAPI* __NtCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
ULONG CreateThreadFlags,
SIZE_T ZeroBits,
SIZE_T StackSize,
SIZE_T MaximumStackSize,
LPVOID pUnkown
);
#else
typedef DWORD(WINAPI* __NtCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
BOOL CreateSuspended,
DWORD dwStackSize,
DWORD dw1,
DWORD dw2,
LPVOID pUnkown
);
#endif
SetLastError(0);
__NtCreateThreadEx NtCreateThreadEx =
(__NtCreateThreadEx)MyGetProcAddress64(hNtdll, "NtCreateThreadEx");
if (NULL == NtCreateThreadEx)
{
wprintf(L"Error[%d]: Failed to obtain NtCreateThreadEx function address!\n",
GetLastError());
VirtualFreeEx(hProcess, lpPathAddr, 0, MEM_RELEASE);
return FALSE;
}
SetLastError(0);
// 7.在目标进程中创建远线程
HANDLE hRemoteThread = NULL;
DWORD lpExitCode = 0;
DWORD dwStatus = NtCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL,
hProcess,
(LPTHREAD_START_ROUTINE)pFuncProcAddr, lpPathAddr, 0, 0, 0, 0, NULL);
if (NULL == hRemoteThread)
{
wprintf(L"Error[%d]: Failed to create thread in target process!\n", GetLastError());
VirtualFreeEx(hProcess, lpPathAddr, 0, MEM_RELEASE);
return FALSE;
}
SetLastError(0);
// 8.等待线程结束
if (WAIT_TIMEOUT == WaitForSingleObject(hRemoteThread, 2000))
{
wprintf(L"Error[%d]: Remote thread not responding.\n", GetLastError());
VirtualFreeEx(hProcess, lpPathAddr, 0, MEM_RELEASE);
return FALSE;
}
GetExitCodeThread(hRemoteThread, &lpExitCode);
if (lpExitCode == 0)
{
wprintf(L"Error: Injection module failed in the target process.\n");
VirtualFreeEx(hProcess, lpPathAddr, 0, MEM_RELEASE);
return FALSE;
}
// 9.清理环境
VirtualFreeEx(hProcess, lpPathAddr, 0, MEM_RELEASE);
SafeCloseHandle(hRemoteThread);
return TRUE;
}
b. 钩子控制代码:
BOOL WINAPI RemoteHookingHandler(
HANDLE hProcess,
PVOID lpProcAddress,
LPVOID lpParameter
)
{
// 1.目标进程句柄
if (hProcess == NULL || lpProcAddress == nullptr)
{
wprintf(L"Error: InvalidSyntax error from RemoteHookingHandler.\n");
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
SetLastError(0);
// 4.加载 ntdll.dll
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
if (NULL == hNtdll)
{
wprintf(L"Error[%d]: Failed to load NTDLL.DLL!\n", GetLastError());
return FALSE;
}
// 6.获取 NtCreateThreadEx 函数地址,该函数在 32 位与 64 位下原型不同
// _WIN64 用来判断编译环境 ,_WIN32用来判断是否是 Windows 系统
#ifdef _WIN64
typedef DWORD(WINAPI* __NtCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
ULONG CreateThreadFlags,
SIZE_T ZeroBits,
SIZE_T StackSize,
SIZE_T MaximumStackSize,
LPVOID pUnkown
);
#else
typedef DWORD(WINAPI* __NtCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
BOOL CreateSuspended,
DWORD dwStackSize,
DWORD dw1,
DWORD dw2,
LPVOID pUnkown
);
#endif
SetLastError(0);
__NtCreateThreadEx NtCreateThreadEx =
(__NtCreateThreadEx)MyGetProcAddress64(hNtdll, "NtCreateThreadEx");
if (NULL == NtCreateThreadEx)
{
wprintf(L"Error[%d]: Failed to obtain NtCreateThreadEx function address!\n",
GetLastError());
return FALSE;
}
SetLastError(0);
// 7.在目标进程中创建远线程
HANDLE hRemoteThread = NULL;
DWORD lpExitCode = 0;
DWORD dwStatus = NtCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL,
hProcess, (LPTHREAD_START_ROUTINE)lpProcAddress, lpParameter, 0, 0, 0, 0, NULL);
if (NULL == hRemoteThread)
{
wprintf(L"Error[%d]: Failed to create thread in target process!\n", GetLastError());
return FALSE;
}
SetLastError(0);
// 8.等待线程结束
if (WAIT_TIMEOUT == WaitForSingleObject(hRemoteThread, 15000))
{
wprintf(L"Error[%d]: Remote thread not responding.\n", GetLastError());
return FALSE;
}
GetExitCodeThread(hRemoteThread, &lpExitCode);
if (lpExitCode == 0)
{
wprintf(L"Error: Control HOOK routine failed in the target process.\n");
return FALSE;
}
// 9.清理环境
SafeCloseHandle(hRemoteThread);
return TRUE;
}
4.4 提升进程权限
进程需要获取足够的权限。可以通过程序清单文件或者以编程的方式来实现获取管理员模拟令牌和调试权限。
下面的代码通过 IsRunAsAdministrator 判断进程是否以管理员身份运行,如果没有,则可以通过 ElevateCurrentProcess 尝试重启进程并请求以管理员身份运行。
BOOL WINAPI IsRunAsAdministrator() // 判断是否以管理员身份运行
{
BOOL bIsElevated = FALSE;
HANDLE hToken = NULL;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
TOKEN_ELEVATION te = { 0 };
DWORD dwReturnLength = 0;
if (GetTokenInformation(hToken, TokenElevation,
&te, sizeof(te), &dwReturnLength))
{
if (dwReturnLength == sizeof(te))
bIsElevated = te.TokenIsElevated;
}
SafeCloseHandle(hToken);
}
return bIsElevated;
}
BOOL WINAPI ElevateCurrentProcess(LPCWSTR wsFilePath) // 尝试运行管理员进程
{
TCHAR szPath[MAX_PATH] = { 0 };
if (GetModuleFileNameW(NULL, szPath, MAX_PATH) != 0)
{
// Launch itself as administrator.
SHELLEXECUTEINFO sei = { 0 };
sei.cbSize = sizeof(SHELLEXECUTEINFO);
sei.lpVerb = L"runas";
sei.lpFile = szPath;
sei.lpParameters = (LPCTSTR)wsFilePath;
sei.nShow = SW_SHOWNORMAL;
if (!ShellExecuteEx(&sei))
{
DWORD dwStatus = GetLastError();
if (dwStatus == ERROR_CANCELLED)
{
// The user refused to allow privileges elevation.
printf("The user refused to allow privileges elevation.\n");
return FALSE;
}
else if (dwStatus == ERROR_FILE_NOT_FOUND)
{
// The file defined by lpFile was not found and
// an error message popped up.
printf("Error Cannot Access Files.\n");
return FALSE;
}
return FALSE;
}
return TRUE;
}
return FALSE;
}
在成功以管理员身份运行后,就可以启用 SE_DEBUG 模拟令牌访问权限:
BOOL WINAPI EnableDebugPrivilege()
{
HANDLE handleToken = NULL;
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &handleToken))
{
printf("Error OpenProcessToken.\n");
return FALSE;
}
LUID debugNameValue = { 0 };
if (!LookupPrivilegeValueW(nullptr, SE_DEBUG_NAME, &debugNameValue))
{
SafeCloseHandle(handleToken);
printf("Error LookupPrivilegeValue.\n");
return FALSE;
}
TOKEN_PRIVILEGES tokenPri = { 0 };
tokenPri.PrivilegeCount = 1;
tokenPri.Privileges[0].Luid = debugNameValue;
tokenPri.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(handleToken,
FALSE, &tokenPri, sizeof(tokenPri), nullptr, nullptr))
{
SafeCloseHandle(handleToken);
printf("Error AdjustTokenPrivileges.\n");
return FALSE;
}
SafeCloseHandle(handleToken);
return TRUE;
}
4.5 处理句柄和环境检测
全部的空闲句柄通过及时地 CloseHandle 来完成,以避免句柄泄露:
BOOL WINAPI SafeCloseHandle(HANDLE handle)
{
BOOL bResponse = TRUE;
if (handle != nullptr) {
bResponse = CloseHandle(handle);
handle = nullptr;
}
return bResponse;
}
随着微软缓解策略不断地实施,在未来可能为 Winlogon 进程升级严格的安全策略,通过下面的检测代码判断是否可以执行代码注入:
BOOL WINAPI IsProcessStrictMitigativeProtectionOff(HANDLE hProcess) {
PROCESS_MITIGATION_DYNAMIC_CODE_POLICY dynamicCodePolicy = { 0 };
PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY signaturePolicy = { 0 };
PROCESS_MITIGATION_CONTROL_FLOW_GUARD_POLICY cfgPolicy = { 0 };
// Actually retrieve the mitigation policy for ACG
if (!GetProcessMitigationPolicy(hProcess,
ProcessDynamicCodePolicy, &dynamicCodePolicy, sizeof(dynamicCodePolicy))) {
printf("[!] Could not GetProcessMitigationPolicy. [%d]\n", GetLastError());
return FALSE;
}
if (dynamicCodePolicy.ProhibitDynamicCode ||
dynamicCodePolicy.AllowRemoteDowngrade ||
dynamicCodePolicy.AllowThreadOptOut) {
printf("Detect[0]: ProcessDynamicCodePolicy on.\n");
return FALSE;
}
// Retrieve mitigation policy for loading arbitrary DLLs
if (!GetProcessMitigationPolicy(hProcess,
ProcessSignaturePolicy, &signaturePolicy, sizeof(signaturePolicy))) {
printf("Could not GetProcessMitigationPolicy. [%d]\n", GetLastError());
return FALSE;
}
if (signaturePolicy.AuditMicrosoftSignedOnly ||
signaturePolicy.AuditStoreSignedOnly ||
signaturePolicy.MicrosoftSignedOnly ||
signaturePolicy.MitigationOptIn ||
signaturePolicy.StoreSignedOnly) {
printf("Detect[1]: ProcessSignaturePolicy on.\n");
return FALSE;
}
// Retrieve mitigation policy for strict Control Flow Guards
if (!GetProcessMitigationPolicy(hProcess,
ProcessControlFlowGuardPolicy, &cfgPolicy, sizeof(cfgPolicy))) {
printf("Could not GetProcessMitigationPolicy. [%d]\n", GetLastError());
return FALSE;
}
if (cfgPolicy.EnableXfg || cfgPolicy.StrictMode)
{
printf("Detect[2]: ProcessControlFlowGuardPolicy on.\n");
return FALSE;
}
return TRUE;
}
该例程检测进程的部分安全缓解策略(包括 ACG、DSG、CFG 等)的等级,当达到无法注入代码的级别时,函数返回 FALSE,表示不可以继续执行远程代码注入,以防止严重错误。当然,部分策略表示允许远程关闭缓解,可以进一步完善代码实现在可以关闭时候关闭防护。
五、编译代码和测试运行
测试功能的代码调用了挂钩例程、脱钩例程和卸载例程,中间需要回车确认继续。
void RunHookingAndCleanup(HANDLE hWinlogonProc, const HANDLER_INFO_STRUCT& lpHandleInfo) {
getchar();
if (TRUE == RemoteHookingHandler(hWinlogonProc, lpHandleInfo.lpHookHandler, NULL)) {
printf("Enable Hooks in winlogon SUCCESSFULLY.\n");
}
getchar();
if (TRUE == RemoteHookingHandler(hWinlogonProc, lpHandleInfo.lpUnhookHandler, NULL)) {
printf("Disable Hooks in winlogon SUCCESSFULLY.\n");
}
getchar();
if (TRUE == RemoteHookingHandler(hWinlogonProc, lpHandleInfo.lpSafeFreeLib, NULL)) {
printf("UnLoadLibrary in winlogon SUCCESSFULLY.\n");
}
SafeCloseHandle(hWinlogonProc);
}
完整的代码我也展示给大家。只作为一个参考,代码还不够完善。
5.1 钩子模块代码
ldrp.h:
#pragma once
#include <winnt.h>
#include <WTypesbase.h>
//#include <winternl.h>
// Kernels | x64 | Windows 10 | 2016 | 2210 22H2(May 2023 Update)
//0x18 bytes (sizeof)
typedef struct _RTL_BALANCED_NODE
{
union
{
_RTL_BALANCED_NODE* Children[2]; //0x0
struct
{
_RTL_BALANCED_NODE* Left; //0x0
_RTL_BALANCED_NODE* Right; //0x8
};
};
union
{
struct
{
UCHAR Red : 1; //0x10
UCHAR Balance : 2; //0x10
};
ULONGLONG ParentValue; //0x10
};
}RTL_BALANCED_NODE, * PRTL_BALANCED_NODE, * LPRTL_BALANCED_NODE;
//0x4 bytes (sizeof)
enum _LDR_DLL_LOAD_REASON
{
LoadReasonStaticDependency = 0,
LoadReasonStaticForwarderDependency = 1,
LoadReasonDynamicForwarderDependency = 2,
LoadReasonDelayloadDependency = 3,
LoadReasonDynamicLoad = 4,
LoadReasonAsImageLoad = 5,
LoadReasonAsDataLoad = 6,
LoadReasonEnclavePrimary = 7,
LoadReasonEnclaveDependency = 8,
LoadReasonUnknown = -1
};
typedef _LDR_DLL_LOAD_REASON LDR_DLL_LOAD_REASON;
//0x10 bytes (sizeof)
typedef struct _LDR_SERVICE_TAG_RECORD
{
_LDR_SERVICE_TAG_RECORD* Next; //0x0
ULONG ServiceTag; //0x8
}LDR_SERVICE_TAG_RECORD, * PLDR_SERVICE_TAG_RECORD;
//0x8 bytes (sizeof)
typedef struct _LDRP_CSLIST
{
SINGLE_LIST_ENTRY* Tail; //0x0
}LDRP_CSLIST;
//0x4 bytes (sizeof)
enum _LDR_DDAG_STATE
{
LdrModulesMerged = -5,
LdrModulesInitError = -4,
LdrModulesSnapError = -3,
LdrModulesUnloaded = -2,
LdrModulesUnloading = -1,
LdrModulesPlaceHolder = 0,
LdrModulesMapping = 1,
LdrModulesMapped = 2,
LdrModulesWaitingForDependencies = 3,
LdrModulesSnapping = 4,
LdrModulesSnapped = 5,
LdrModulesCondensed = 6,
LdrModulesReadyToInit = 7,
LdrModulesInitializing = 8,
LdrModulesReadyToRun = 9
};
typedef _LDR_DDAG_STATE LDR_DDAG_STATE;
//0x50 bytes (sizeof)
typedef struct _LDR_DDAG_NODE
{
LIST_ENTRY Modules; //0x0
PLDR_SERVICE_TAG_RECORD ServiceTagList; //0x10
ULONG LoadCount; //0x18
ULONG LoadWhileUnloadingCount; //0x1c
ULONG LowestLink; //0x20
LDRP_CSLIST Dependencies; //0x28
LDRP_CSLIST IncomingDependencies; //0x30
LDR_DDAG_STATE State; //0x38
SINGLE_LIST_ENTRY CondenseLink; //0x40
ULONG PreorderNumber; //0x48
}LDR_DDAG_NODE, * PLDR_DDAG_NODE;
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING;
typedef UNICODE_STRING* PUNICODE_STRING;
typedef const UNICODE_STRING* PCUNICODE_STRING;
//0x120 bytes (sizeof)
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks; //0x0
LIST_ENTRY InMemoryOrderLinks; //0x10
LIST_ENTRY InInitializationOrderLinks; //0x20
VOID* DllBase; //0x30
VOID* EntryPoint; //0x38
ULONG SizeOfImage; //0x40
UNICODE_STRING FullDllName; //0x48
UNICODE_STRING BaseDllName; //0x58
union
{
UCHAR FlagGroup[4]; //0x68
ULONG Flags; //0x68
struct
{
ULONG PackagedBinary : 1; //0x68
ULONG MarkedForRemoval : 1; //0x68
ULONG ImageDll : 1; //0x68
ULONG LoadNotificationsSent : 1; //0x68
ULONG TelemetryEntryProcessed : 1; //0x68
ULONG ProcessStaticImport : 1; //0x68
ULONG InLegacyLists : 1; //0x68
ULONG InIndexes : 1; //0x68
ULONG ShimDll : 1; //0x68
ULONG InExceptionTable : 1; //0x68
ULONG ReservedFlags1 : 2; //0x68
ULONG LoadInProgress : 1; //0x68
ULONG LoadConfigProcessed : 1; //0x68
ULONG EntryProcessed : 1; //0x68
ULONG ProtectDelayLoad : 1; //0x68
ULONG ReservedFlags3 : 2; //0x68
ULONG DontCallForThreads : 1; //0x68
ULONG ProcessAttachCalled : 1; //0x68
ULONG ProcessAttachFailed : 1; //0x68
ULONG CorDeferredValidate : 1; //0x68
ULONG CorImage : 1; //0x68
ULONG DontRelocate : 1; //0x68
ULONG CorILOnly : 1; //0x68
ULONG ChpeImage : 1; //0x68
ULONG ReservedFlags5 : 2; //0x68
ULONG Redirected : 1; //0x68
ULONG ReservedFlags6 : 2; //0x68
ULONG CompatDatabaseProcessed : 1; //0x68
}uFlags;
};
USHORT ObsoleteLoadCount; //0x6c
USHORT TlsIndex; //0x6e
LIST_ENTRY HashLinks; //0x70
ULONG TimeDateStamp; //0x80
struct ACTIVATION_CONTEXT* EntryPointActivationContext; //0x88
VOID* Lock; //0x90
LDR_DDAG_NODE* DdagNode; //0x98
LIST_ENTRY NodeModuleLink; //0xa0
struct LDRP_LOAD_CONTEXT* LoadContext; //0xb0
VOID* ParentDllBase; //0xb8
VOID* SwitchBackContext; //0xc0
RTL_BALANCED_NODE BaseAddressIndexNode; //0xc8
RTL_BALANCED_NODE MappingInfoIndexNode; //0xe0
ULONGLONG OriginalBase; //0xf8
LARGE_INTEGER LoadTime; //0x100
ULONG BaseNameHashValue; //0x108
LDR_DLL_LOAD_REASON LoadReason; //0x10c
ULONG ImplicitPathOptions; //0x110
ULONG ReferenceCount; //0x114
ULONG DependentLoadFlags; //0x118
UCHAR SigningLevel; //0x11c
}LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
//0x58 bytes (sizeof)
typedef struct _PEB_LDR_DATA32
{
ULONG Length; // +0x00
BOOLEAN Initialized; // +0x04
PVOID SsHandle; // +0x08
LIST_ENTRY InLoadOrderModuleList; // +0x0c
LIST_ENTRY InMemoryOrderModuleList; // +0x14
LIST_ENTRY InInitializationOrderModuleList;// +0x1c
} PEB_LDR_DATA32, * PPEB_LDR_DATA32; // +0x24
typedef struct _PEB32
{
UCHAR InheritedAddressSpace; //0x0
UCHAR ReadImageFileExecOptions; //0x1
UCHAR BeingDebugged; //0x2
union
{
UCHAR BitField; //0x3
struct
{
UCHAR ImageUsesLargePages : 1; //0x3
UCHAR IsProtectedProcess : 1; //0x3
UCHAR IsImageDynamicallyRelocated : 1; //0x3
UCHAR SkipPatchingUser32Forwarders : 1; //0x3
UCHAR IsPackagedProcess : 1; //0x3
UCHAR IsAppContainer : 1; //0x3
UCHAR IsProtectedProcessLight : 1; //0x3
UCHAR IsLongPathAwareProcess : 1; //0x3
};
};
PVOID Mutant; //0x4
PVOID ImageBaseAddress; //0x8
PEB_LDR_DATA32* Ldr; //0xc
struct RTL_USER_PROCESS_PARAMETERS* ProcessParameters; //0x10
PVOID SubSystemData; //0x14
PVOID ProcessHeap; //0x18
RTL_CRITICAL_SECTION* FastPebLock; //0x1c
SLIST_HEADER* volatile AtlThunkSListPtr; //0x20
PVOID IFEOKey; //0x24
} PEB32, * PPEB32;
typedef struct _STRING64
{
USHORT Length; //0x0
USHORT MaximumLength; //0x2
ULONGLONG Buffer; //0x8
}STRING64, * LPSTRING64;
typedef struct _PEB_LDR_DATA64
{
ULONG Length; //0x0
UCHAR Initialized; //0x4
PVOID SsHandle; //0x8
LIST_ENTRY InLoadOrderModuleList; //0x10
LIST_ENTRY InMemoryOrderModuleList; //0x20
LIST_ENTRY InInitializationOrderModuleList; //0x30
PVOID EntryInProgress; //0x40
UCHAR ShutdownInProgress; //0x48
PVOID ShutdownThreadId; //0x50
}PEB_LDR_DATA64, * PPEB_LDR_DATA64;
typedef struct _PEB64
{
UCHAR InheritedAddressSpace; //0x0
UCHAR ReadImageFileExecOptions; //0x1
UCHAR BeingDebugged; //0x2
union
{
UCHAR BitField; //0x3
struct
{
UCHAR ImageUsesLargePages : 1; //0x3
UCHAR IsProtectedProcess : 1; //0x3
UCHAR IsImageDynamicallyRelocated : 1; //0x3
UCHAR SkipPatchingUser32Forwarders : 1; //0x3
UCHAR IsPackagedProcess : 1; //0x3
UCHAR IsAppContainer : 1; //0x3
UCHAR IsProtectedProcessLight : 1; //0x3
UCHAR IsLongPathAwareProcess : 1; //0x3
};
};
UCHAR Padding0[4]; //0x4
ULONGLONG Mutant; //0x8
ULONGLONG ImageBaseAddress; //0x10
PEB_LDR_DATA64* Ldr; //0x18
ULONGLONG ProcessParameters; //0x20
ULONGLONG SubSystemData; //0x28
ULONGLONG ProcessHeap; //0x30
ULONGLONG FastPebLock; //0x38
ULONGLONG AtlThunkSListPtr; //0x40
ULONGLONG IFEOKey; //0x48
}PEB64, * PPEB64;
#ifdef _WIN64
typedef PEB64 PEB;
typedef PPEB64 PPEB;
typedef PEB_LDR_DATA64 PEB_LDR_DATA;
typedef PPEB_LDR_DATA64 PPEB_LDR_DATA;
#else
typedef PEB32 PEB;
typedef PPEB32 PPEB;
typedef PEB_LDR_DATA32 PEB_LDR_DATA;
typedef PPEB_LDR_DATA32 PPEB_LDR_DATA;
#endif
main.cpp
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <rpc.h>
#include <WtsApi32.h>
#include <corecrt_wstdio.h>
#include <Psapi.h>
#include "ldrp.h"
#include <cstddef>
#pragma comment(lib, "WtsApi32.lib")
#define HOOK_MODULE_NAME L"WMsgKMsgHookCore.dll"
typedef int(__fastcall* __WMsgKMessageHandler)(
unsigned int uMachineState,
unsigned int uMsgWLGenericKey,
PRPC_ASYNC_STATE pAsync,
LPDWORD lpStatus // int* pReserved
);
typedef struct HANDLER_INFO_STRUCT
{
DWORD cbSize = 0;
DWORD dwMainThread = 0;
HMODULE hHookModule = nullptr;
LPVOID lpHookHandler = nullptr;
LPVOID lpUnhookHandler = nullptr;
LPVOID lpSafeFreeLib = nullptr;
LPVOID lpWinlogonBase = nullptr;
LPVOID lpHookAddress = nullptr;
//LPVOID lpEPLFuncAddress = nullptr;
}HANDLER_INFO_STRUCT, * LPHANDLER_INFO_STRUCT;
typedef struct _LDR_PROTECT_STRUCT
{
BOOL bEnableProtect;
}LDR_PROTECT_STRUCT, * LPLDR_PROTECT_STRUCT;
#pragma data_seg("WMsgHookData")
CHAR pOriginalBytes[13] = { 0 };
HANDLER_INFO_STRUCT lpgHandlerInfo = { 0 };
#pragma data_seg()
#pragma comment(linker,"/SECTION:WMsgHookData,RWS")
extern "C" {
__declspec(dllexport) BOOL WINAPI RemoteSetHookBaseAddress(DWORD lpBaseOffest);
__declspec(dllexport) BOOL WINAPI GetHandlerAddress(
LPHANDLER_INFO_STRUCT lpHandlerInfo);
}
BOOL WINAPI InitHandlerAddress();
BOOL WINAPI IsHookBaseAddressValidInternal();
DWORD WINAPI HookWMsgKMessageExceptionHandler(LPVOID lpThreadParameter);
DWORD WINAPI UnhookWMsgKMessageExceptionHandler(LPVOID lpThreadParameter);
DWORD WINAPI SafeFreeLibrary(LPVOID lpThreadParameter);
DWORD WINAPI EasyProtectLibrary(LPVOID lpThreadParameter);
BOOL WINAPI SvcMessageBox(LPWSTR lpCap, LPWSTR lpMsg, DWORD style, BOOL bWait, DWORD& result);
int __fastcall HookedWMsgKMessageHandler(
unsigned int uMachineState,
unsigned int uMsgWLGenericKey,
PRPC_ASYNC_STATE pAsync,
LPDWORD lpStatus // int* pReserved
)
{
int dwFunResponse = 0;
DWORD dwMsgBoxResponse = 0;
WCHAR wsTitle[] = L"TestHookingHandler";
WCHAR wsSvcMsg[100] = { 0 };
/*
* ///
*
* WMsgMessageHandler 控制 账户状态/UAC 相关的功能
* uMsgCSessionKey uMsgWLGenericKey MajorAction
* 0001 04002009 关闭本地计算机
* 0001 04002003 重启本地计算机
* 0500 1E88 请求提升管理员权限
* 0501 06B8 已经提升管理员权限
* 0403 0000 切换用户进行时(建议窗口在 Winlogon 下创建,并置前端)
* 0202 0000 切换用户恢复时(具体操作未知)
* 0205 0000 切换用户恢复时(具体操作未知)
* 注意:(1)对于非已知代码的情况,不要使用阻滞过程,否则会导致死锁。
* (2)如果不需要执行指定的过程,函数返回值必须是 1,如果为 0 可能会陷入等待。
*
*
*
* WMsgKMessageHandler 控制 系统热键/注销 相关的功能
* uMsgCSessionKey uMsgWLGenericKey MajorAction
* 0404 4 Ctrl+Shift+Esc, 任务管理器
* 0404 0 Ctrl+Alt+Delete, 安全桌面
* 0404 5 Win+L, 锁屏, LogonUI Windows
* 0404 7 Win+P, 投影屏幕
* 0404 6 Win+U, 设置/辅助功能
* 0404 C Win+Plus, 放大镜
* 0404 D Win+Ctrl+Enter, 讲述人
* 0404 E Win+Enter, 未知
* 0402 5 左Alt+LShift+Print Screen, 高对比度主题
* 0402 1 连续按下五次左侧 Shift,滞粘键
* 0402 2 按住右侧 Shift 键 8 秒,筛选键
* 0001 3 Alt+F4 资源管理器,重启计算机
* 0001 4009 Alt+F4 资源管理器,关闭计算机
* 0001 0 Alt+F4 资源管理器,注销计算机
*
*
*/
//if (uMsgCSessionKey == 0x404)
//{
swprintf_s(wsSvcMsg,
L"Intercepted procedure call message: \nuMsgCSessionKey: %X, uMsgWLGenericKey: %X.\n ",
uMachineState, uMsgWLGenericKey);
if (SvcMessageBox(wsTitle, wsSvcMsg,
MB_YESNO | MB_ICONINFORMATION | MB_SYSTEMMODAL,
TRUE, dwMsgBoxResponse))
{
if (dwMsgBoxResponse == IDNO)
{
return 1;
}
}
//}
// UnHook
UnhookWMsgKMessageExceptionHandler(NULL);
auto WMsgKMessageHandler = (__WMsgKMessageHandler)lpgHandlerInfo.lpHookAddress;
dwFunResponse = WMsgKMessageHandler(uMachineState, uMsgWLGenericKey, pAsync, lpStatus);
// Re-hook
HookWMsgKMessageExceptionHandler(NULL);
return dwFunResponse;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
DisableThreadLibraryCalls(hModule);
WCHAR wsFileName[MAX_PATH] = { 0 };
WCHAR wsDebugPrint[56] = { 0 };
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
GetModuleFileNameW(NULL, wsFileName, MAX_PATH);
if (!wcsstr(wsFileName, L"winlogon.exe"))
{
swprintf_s(wsDebugPrint, L"This thread is not in winlogon, TID: [%d].\n",
GetCurrentThreadId());
OutputDebugStringW(wsDebugPrint);
return TRUE;
}
else {
swprintf_s(wsDebugPrint, L"Init Hook in winlogon, TID: [%d].\n",
GetCurrentThreadId());
OutputDebugStringW(wsDebugPrint);
lpgHandlerInfo.hHookModule = hModule;
return InitHandlerAddress();
}
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
if (!wcsstr(wsFileName, L"winlogon.exe"))
{
return TRUE;
}
UnhookWMsgKMessageExceptionHandler(NULL);
break;
}
return TRUE;
}
BOOL WINAPI SvcMessageBox(LPWSTR lpCap, LPWSTR lpMsg, DWORD style, BOOL bWait, DWORD& result)
{
if (NULL == lpMsg || NULL == lpCap)
return FALSE;
result = 0;
DWORD sessionXId = WTSGetActiveConsoleSessionId();
return WTSSendMessageW(WTS_CURRENT_SERVER_HANDLE, sessionXId,
lpCap, (DWORD)wcslen(lpCap) * sizeof(DWORD),
lpMsg, (DWORD)wcslen(lpMsg) * sizeof(DWORD),
style, 0, &result, bWait);
}
BOOL WINAPI InitHandlerAddress()
{
WCHAR wsDbPrint[155] = { 0 };
swprintf_s(wsDbPrint, L"Op:[GetModuleHandleW] ArgList:[HOOK_MODULE_NAME].\n");
OutputDebugStringW(wsDbPrint);
SetLastError(0);
HMODULE hHookCore = GetModuleHandleW(HOOK_MODULE_NAME);
if (hHookCore == NULL)
{
swprintf_s(wsDbPrint, L"Er:[GetModuleHandleW, HOOK_MODULE_NAME] Status:[%d].\n",
GetLastError());
OutputDebugStringW(wsDbPrint);
return FALSE;
}
swprintf_s(wsDbPrint, L"Op:[GetModuleHandleW] ArgList:[Current].\n");
OutputDebugStringW(wsDbPrint);
SetLastError(0);
HMODULE hModuleBase = GetModuleHandleW(NULL);
if (hModuleBase == NULL)
{
swprintf_s(wsDbPrint, L"Er:[GetModuleHandleW, NULL] Status:[%d].\n",
GetLastError());
OutputDebugStringW(wsDbPrint);
return FALSE;
}
swprintf_s(wsDbPrint, L"Op:[InitHandlerAddress] ArgList:[].\n");
OutputDebugStringW(wsDbPrint);
// 将函数指针等写入全局变量
lpgHandlerInfo.lpWinlogonBase = hModuleBase; // Winlogon 进程加载基址
lpgHandlerInfo.dwMainThread = GetCurrentThreadId(); // 保存初始化例程所在线程TID
lpgHandlerInfo.lpHookHandler =
&HookWMsgKMessageExceptionHandler;
lpgHandlerInfo.lpUnhookHandler =
&UnhookWMsgKMessageExceptionHandler;
lpgHandlerInfo.lpSafeFreeLib = &SafeFreeLibrary;
//lpgHandlerInfo.lpEPLFuncAddress = &EasyProtectLibrary;
if (lpgHandlerInfo.lpHookHandler == NULL ||
lpgHandlerInfo.lpUnhookHandler == NULL ||
lpgHandlerInfo.lpSafeFreeLib == NULL)
{
swprintf_s(wsDbPrint, L"Er:[CheckGlobalParameter] Status:[nullpointer].\n");
OutputDebugStringW(wsDbPrint);
return FALSE;
}
swprintf_s(wsDbPrint, L"Op:[InitHandlerAddress], pt:[0x%I64X].\n",
reinterpret_cast<UINT64>(&lpgHandlerInfo));
OutputDebugStringW(wsDbPrint);
return TRUE;
}
// 用于检测虚拟地址是否有效,且对当前进程可以读写访问
BOOL IsExecutableAddress(LPVOID VirtualAddress)
{
BOOL IsOk = FALSE;
MEMORY_BASIC_INFORMATION MemoryBasicInfo = { 0 };
if (!VirtualQuery(VirtualAddress, &MemoryBasicInfo, sizeof(MEMORY_BASIC_INFORMATION))) {
return IsOk;
}
if ( (MemoryBasicInfo.State == MEM_COMMIT) &&
( (MemoryBasicInfo.Protect & PAGE_READONLY) ||
(MemoryBasicInfo.Protect & PAGE_READWRITE) ||
(MemoryBasicInfo.Protect & PAGE_EXECUTE_READ) ||
(MemoryBasicInfo.Protect & PAGE_EXECUTE_READWRITE) ) )
{
IsOk = TRUE;
}
return IsOk;
}
// 检测地址指针是否有效
BOOL CheckPointerValidity(LPVOID ptr) {
// 获取指针的值
uintptr_t pointerValue = reinterpret_cast<uintptr_t>(ptr);
WCHAR errString[56] = { 0 };
__try {
// 额外的检查条件
if ((pointerValue >> 56) == 0xFF || // 高位是 FF
!IsExecutableAddress(ptr) // 进一步检查指针地址是否可读可写
) {
throw L"Invalid pointer";
}
// 如果上面的检查通过,表示指针是有效且可写的
return TRUE;
}
__except (EXCEPTION_EXECUTE_HANDLER) {
// 捕获异常,指针无效或不可写
swprintf_s(errString, L"Read access is not allowed at 0x%I64X.\n", pointerValue);
OutputDebugStringW(errString);
return FALSE;
}
}
BOOL WINAPI RemoteSetHookBaseAddress(DWORD lpBaseOffest)
{
if (lpgHandlerInfo.lpWinlogonBase == nullptr ||
lpBaseOffest == NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
PVOID lpHookAddressTemp = reinterpret_cast<PVOID>(lpBaseOffest +
reinterpret_cast<UINT64>(lpgHandlerInfo.lpWinlogonBase));
// 设置全局指针地址
lpgHandlerInfo.lpHookAddress = lpHookAddressTemp;
return TRUE;
}
BOOL WINAPI IsHookBaseAddressValidInternal()
{
PVOID lpWinlogonBaseTemp = lpgHandlerInfo.lpWinlogonBase;
PVOID lpHookAddressTemp = lpgHandlerInfo.lpHookAddress;
HMODULE hWinlogonHandle =
reinterpret_cast<HMODULE>(lpWinlogonBaseTemp);
MODULEINFO moduleInfo = { 0 };
UINT64 ulModuleBaseLb = 0, ulModuleBaseUp = 0;
UINT64 ulHookAddress =
reinterpret_cast<UINT64>(lpHookAddressTemp);
if (lpWinlogonBaseTemp == nullptr ||
lpHookAddressTemp == nullptr)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
if (!CheckPointerValidity(lpWinlogonBaseTemp))
{
SetLastError(ERROR_INVALID_ADDRESS);
return FALSE;
}
if (!GetModuleInformation(GetCurrentProcess(),
hWinlogonHandle, &moduleInfo, sizeof(moduleInfo))) {
SetLastError(ERROR_INVALID_DLL);
return FALSE;
}
ulModuleBaseLb =
reinterpret_cast<UINT64>(lpWinlogonBaseTemp);
ulModuleBaseUp =
reinterpret_cast<UINT64>(lpWinlogonBaseTemp)
+ moduleInfo.SizeOfImage;
if (ulHookAddress >= ulModuleBaseUp ||
ulHookAddress <= ulModuleBaseLb)
{
SetLastError(ERROR_INVALID_DATA);
return FALSE;
}
if (!CheckPointerValidity(lpHookAddressTemp))
{
SetLastError(ERROR_INVALID_ADDRESS);
return FALSE;
}
return TRUE;
}
BOOL WINAPI GetHandlerAddress(LPHANDLER_INFO_STRUCT lpHandlerInfo)
{
// 结构体指针不为空
if (lpHandlerInfo == nullptr)
{
OutputDebugStringW(L"Er:[GetHandlerAddress] status:[Invalid nullptr].\n");
return FALSE;
}
// 计算 cbSize 成员的地址
SIZE_T cbSizeOff = offsetof(HANDLER_INFO_STRUCT, cbSize);
PDWORD lpcbSize = reinterpret_cast<PDWORD>(lpHandlerInfo + cbSizeOff);
// 保证传入结构体大小等于预期的大小
if (*lpcbSize != sizeof(HANDLER_INFO_STRUCT))
{
OutputDebugStringW(L"Er:[GetHandlerAddress] status:[Invalid HANDLER_INFO_STRUCT Size].\n");
return FALSE;
}
// 将传入结构体指针中的各个字段赋值给lpHandlerInfo结构体中对应的字段,并返回TRUE
lpHandlerInfo->lpHookHandler = lpgHandlerInfo.lpHookHandler;
lpHandlerInfo->lpUnhookHandler = lpgHandlerInfo.lpUnhookHandler;
lpHandlerInfo->lpHookAddress = lpgHandlerInfo.lpHookAddress;
lpHandlerInfo->lpWinlogonBase = lpgHandlerInfo.lpWinlogonBase;
lpHandlerInfo->lpSafeFreeLib = lpgHandlerInfo.lpSafeFreeLib;
lpHandlerInfo->dwMainThread = lpgHandlerInfo.dwMainThread;
lpHandlerInfo->hHookModule = lpgHandlerInfo.hHookModule;
return TRUE;
}
DWORD WINAPI HookWMsgKMessageExceptionHandler(LPVOID lpThreadParameter)
{
// 首先进行必要的安全检查
if (IsHookBaseAddressValidInternal() == FALSE)
{
OutputDebugStringW(L"Er:[HookHandler] status:[Invalid Address].\n");
return GetLastError();
}
DWORD bResponse = FALSE;
DWORD oldProtect = 0;
LPVOID hHookAddress = lpgHandlerInfo.lpHookAddress;
LPVOID hWMsgHandler = &HookedWMsgKMessageHandler;
SIZE_T bufferSize = 0;
LDR_PROTECT_STRUCT ldrpt = { TRUE };
// our trampoline
unsigned char boing[] = {
0x49, 0xbb, 0xde, 0xad,
0xc0, 0xde, 0xde, 0xad,
0xc0, 0xde, 0x41, 0xff,
0xe3 };
// add in the address of our hook
*(LPVOID*)(boing + 2) = hWMsgHandler;
bufferSize = sizeof(boing);
// Enable FreeLibrary Protect
ldrpt.bEnableProtect = TRUE;
if (EasyProtectLibrary(&ldrpt) == 0)
{
OutputDebugStringW(L"Er:[HookHandler] status:[EasyProtectLibrary failed].\n");
return FALSE;
}
// disable write protect
SetLastError(0);
bResponse = VirtualProtect(hHookAddress, bufferSize, PAGE_EXECUTE_READWRITE, &oldProtect);
if (!bResponse)
{
OutputDebugStringW(L"Er:[HookHandler] status:[VirtualProect failed, EXECUTE_READWRITE].\n");
return GetLastError();
}
// save the original bytes
if (memcpy_s(pOriginalBytes, bufferSize, hHookAddress, bufferSize) != 0)
{
OutputDebugStringW(L"Er:[HookHandler] status:[memcpy failed, 1:2].\n");
return GetLastError();
}
SetLastError(0);
// write the hook
if (memcpy_s(hHookAddress, bufferSize, boing, bufferSize) != 0)
{
OutputDebugStringW(L"Er:[HookHandler] status:[memcpy failed, 2:2].\n");
return GetLastError();
}
// Flush Cache to make code work
bResponse = FlushInstructionCache(GetCurrentProcess(), hHookAddress, bufferSize);
if (!bResponse)
{
OutputDebugStringW(L"Er:[HookHandler] status:[FlushInstructionCache failed].\n");
return GetLastError();
}
// enable write protect
bResponse = VirtualProtect(hHookAddress, bufferSize, oldProtect, &oldProtect);
if (!bResponse)
{
OutputDebugStringW(L"Er:[HookHandler] status:[VirtualProect failed].\n");
return GetLastError();
}
return TRUE;
}
DWORD WINAPI UnhookWMsgKMessageExceptionHandler(LPVOID lpThreadParameter)
{
DWORD bResponse = FALSE;
DWORD oldProtect = 0;
PVOID hookAddress = lpgHandlerInfo.lpHookAddress;
// disable write protect
bResponse = VirtualProtect(hookAddress, 13, PAGE_EXECUTE_READWRITE, &oldProtect);
if (!bResponse)
{
OutputDebugStringW(L"Er:[UnHookHandler] status:[VirtualProect failed, EXECUTE_READWRITE].\n");
return bResponse;
}
// recover original bytes
if (memcpy_s(hookAddress, 13, pOriginalBytes, 13) != 0)
{
OutputDebugStringW(L"Er:[UnHookHandler] status:[memcpy failed].\n");
return bResponse;
}
// Flush Cache to make code work
bResponse = FlushInstructionCache(GetCurrentProcess(), hookAddress, 13);
if (!bResponse)
{
OutputDebugStringW(L"Er:[HookHandler] status:[FlushInstructionCache failed].\n");
return GetLastError();
}
// enable write protect
bResponse = VirtualProtect(hookAddress, 13, oldProtect, &oldProtect);
if (!bResponse)
{
OutputDebugStringW(L"Er:[UnHookHandler] status:[VirtualProect failed].\n");
return bResponse;
}
return TRUE;
}
DWORD WINAPI EasyProtectLibrary(LPVOID lpThreadParameter)
{
auto ldrpt = (LPLDR_PROTECT_STRUCT)lpThreadParameter;
if (ldrpt == nullptr) return 0;
BOOL bNewValue = ldrpt->bEnableProtect;
BOOL bOldProtect = 0;
DWORD index = 0;
DWORD bResponse = 0;
const WCHAR lpFileName[] = HOOK_MODULE_NAME;
PPEB_LDR_DATA pPebLdrData = nullptr;
PLDR_DATA_TABLE_ENTRY pLdrDataEntry = nullptr;
PLIST_ENTRY pListEntryStart = nullptr;
PLIST_ENTRY pListEntryEnd = nullptr;
SIZE_T ulTestSize = 0;
SIZE_T ulRealSize = wcsnlen_s(lpFileName, MAX_PATH);
#ifdef _WIN64
ULONGLONG ModuleSum = NULL;
PPEB peb = (PPEB)__readgsqword(0x60);
#else
ULONG ModuleSum = NULL;
PPEB32 peb = (PPEB32)__readfsdword(0x30);
#endif
__try {
pPebLdrData = peb->Ldr;
// 以模块加载顺序排列的链表
pListEntryStart = pPebLdrData->InLoadOrderModuleList.Flink;
pListEntryEnd = pPebLdrData->InLoadOrderModuleList.Blink;
for (index = 0; pListEntryStart != pListEntryEnd; index++)
{
pLdrDataEntry = CONTAINING_RECORD(pListEntryStart,
LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
ulTestSize = wcsnlen_s(pLdrDataEntry->BaseDllName.Buffer, MAX_PATH);
if (ulTestSize != ulRealSize || ulTestSize == MAX_PATH)
{
pListEntryStart = pListEntryStart->Flink;
continue;
}
if (!_wcsicmp(pLdrDataEntry->BaseDllName.Buffer, lpFileName))
{
if (bNewValue == TRUE)
{
// 引用计数主要有两个成员,引用计数为 -1 表示静态加载的模块,
// 并且不允许卸载
pLdrDataEntry->DdagNode->LoadCount = 0xffffffff;
pLdrDataEntry->ObsoleteLoadCount = 0xffff;
}
else {
// 引用计数主要有两个成员,引用计数为 -1 表示静态加载的模块,
// 并且不允许卸载
pLdrDataEntry->DdagNode->LoadCount = 1;
pLdrDataEntry->ObsoleteLoadCount = 1;
}
// ProcessStaticImport 位域如果为 1, 则任何卸载调用都将直接返回 TRUE
// 而不做任何资源释放操作
pLdrDataEntry->uFlags.ProcessStaticImport = bNewValue;
bResponse |= 0x1;
break;
}
pListEntryStart = pListEntryStart->Flink;
}
// 以内存位置排列的模块链表
pListEntryStart = pPebLdrData->InMemoryOrderModuleList.Flink;
pListEntryEnd = pPebLdrData->InMemoryOrderModuleList.Blink;
for (index = 0; pListEntryStart != pListEntryEnd; index++)
{
pLdrDataEntry = CONTAINING_RECORD(pListEntryStart,
LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
ulTestSize = wcsnlen_s(pLdrDataEntry->BaseDllName.Buffer, MAX_PATH);
if (ulTestSize != ulRealSize || ulTestSize == MAX_PATH)
{
pListEntryStart = pListEntryStart->Flink;
continue;
}
if (!_wcsicmp(pLdrDataEntry->BaseDllName.Buffer, lpFileName))
{
if (bNewValue == TRUE)
{
pLdrDataEntry->DdagNode->LoadCount = 0xffffffff;
pLdrDataEntry->ObsoleteLoadCount = 0xffff;
}
else {
pLdrDataEntry->DdagNode->LoadCount = 1;
pLdrDataEntry->ObsoleteLoadCount = 1;
}
pLdrDataEntry->uFlags.ProcessStaticImport = bNewValue;
bResponse |= 0x2;
break;
}
pListEntryStart = pListEntryStart->Flink;
}
// 以初始化顺序加载的模块列表
pListEntryStart = pPebLdrData->InInitializationOrderModuleList.Flink;
pListEntryEnd = pPebLdrData->InInitializationOrderModuleList.Blink;
for (index = 0; pListEntryStart != pListEntryEnd; index++)
{
pLdrDataEntry = CONTAINING_RECORD(pListEntryStart,
LDR_DATA_TABLE_ENTRY, InInitializationOrderLinks);
ulTestSize = wcsnlen_s(pLdrDataEntry->BaseDllName.Buffer, MAX_PATH);
if (ulTestSize != ulRealSize || ulTestSize == MAX_PATH)
{
pListEntryStart = pListEntryStart->Flink;
continue;
}
if (!_wcsicmp(pLdrDataEntry->BaseDllName.Buffer, lpFileName))
{
if (bNewValue == TRUE)
{
pLdrDataEntry->DdagNode->LoadCount = 0xffffffff;
pLdrDataEntry->ObsoleteLoadCount = 0xffff;
}
else {
pLdrDataEntry->DdagNode->LoadCount = 1;
pLdrDataEntry->ObsoleteLoadCount = 1;
}
pLdrDataEntry->uFlags.ProcessStaticImport = bNewValue;
bResponse |= 0x4;
break;
}
pListEntryStart = pListEntryStart->Flink;
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
OutputDebugStringW(L"Er:Exception occurred while accessing memory.\n");
return FALSE;
}
return bResponse;
}
DWORD WINAPI SafeFreeLibrary(LPVOID lpThreadParameter)
{
DWORD dwMainThread = lpgHandlerInfo.dwMainThread;
HMODULE hThisModule = lpgHandlerInfo.hHookModule;
// Safely free module
if (dwMainThread != GetCurrentThreadId())
{
DWORD dwExitCode = TRUE;
LDR_PROTECT_STRUCT ldrpt = { FALSE };
EasyProtectLibrary(&ldrpt); // 解除保护
// 卸载模块
FreeLibraryAndExitThread(hThisModule, dwExitCode);
return TRUE;
}
return FALSE;
}
5.2 注入工具代码
测试注入代码如下,由于时间仓促,定位代码直接写在注入过程中的,通过在 BFTracePatternInModule 函数中调整 BYTE pattern 和对应的特征码长度字段来完成不同的挂钩,这需要进一步完善代码。
// WinlogonInject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <stdio.h>
#include <Windows.h>
#include <Tlhelp32.h>
#include <Shlwapi.h>
#include <new>
#include <locale.h>
#pragma comment(lib, "Shlwapi.lib")
#define HOOK_MODULE_NAME L"WMsgKMsgHookCore.dll"
#define WINLOGON_NAME L"winlogon.exe"
typedef struct HANDLER_INFO_STRUCT
{
DWORD cbSize = 0;
DWORD dwMainThread = 0;
HMODULE hHookModule = nullptr;
LPVOID lpHookHandler = nullptr;
LPVOID lpUnhookHandler = nullptr;
LPVOID lpSafeFreeLib = nullptr;
LPVOID lpWinlogonBase = nullptr;
LPVOID lpHookAddress = nullptr;
//LPVOID lpEPLFuncAddress = nullptr;
}HANDLER_INFO_STRUCT, * LPHANDLER_INFO_STRUCT;
typedef struct MAPVIEW_INFO_STRUCT
{
HANDLE hFile = NULL;
HANDLE hMapping = NULL;
LPVOID lpBaseMapView = nullptr;
}MAPVIEW_INFO_STRUCT, * LPMAPVIEW_INFO_STRUCT;
typedef BOOL (WINAPI* __GetHandlerAddress)(LPHANDLER_INFO_STRUCT lpHandlerInfo);
typedef BOOL (WINAPI* __SetBaseAddress)(DWORD lpBaseOffest);
FARPROC WINAPI MyGetProcAddress64(PVOID lpBaseAddress, LPCSTR FunName);
BOOL WINAPI InjectMouldeHandler(HANDLE hProcess, LPCWSTR pszDllFileName);
BOOL WINAPI SafeCloseHandle(HANDLE handle);
DWORD WINAPI GetActiveConsoleSessionId();
BOOL WINAPI IsProcessInSession(DWORD processId, DWORD sessionId);
DWORD WINAPI FindWinlogonProcessId();
BOOL WINAPI CheckProcessHasLoadModule(DWORD dwProcessId, LPCWSTR wsFileName);
BOOL WINAPI IsProcessStrictMitigativeProtectionOff(HANDLE hProcess);
BOOL WINAPI IsRunAsAdministrator();
BOOL WINAPI ElevateCurrentProcess(LPCWSTR wsFilePath);
BOOL WINAPI EnableDebugPrivilege();
BOOL WINAPI RemoteHookingHandler(HANDLE hProcess, PVOID lpProcAddress, LPVOID lpParameter);
DWORD WINAPI BFTracePatternInModule(LPCWSTR moduleName, PBYTE pattern, SIZE_T patternSize);
BYTE pattern1[] =
{ 0x48u, 0x8Bu, 0x0Du, 0, 0, 0, 0, 0x49u,
0x3Bu, 0xCCu, 0x74u , 0, 0x44, 0x84,
0x79, 0x1C , 0x74
};// ETW Trace 特征码 1
// ETW Trace 特征码 2
BYTE pattern2[] =
{
0x8Bu, 0x0, 0x8Bu, 0x0,
0x48, 0x8Bu, 0x0Du, 0x0,
0x0, 0x0, 0x0, 0x0,
0x8D, 0x0, 0x0, 0x0,
0x0, 0x0, 0x41u, 0x0, 0x05u
};
void SetConsoleCodePageToUTF8() {
_wsetlocale(LC_ALL, L".UTF8");
}
BOOL GetExecutablePath(
PWCHAR* binPathBuffer,
DWORD& bufferLen) {
while (true) {
SetLastError(0);
DWORD dwwcharLen = bufferLen * sizeof(WCHAR) + sizeof(WCHAR);
*binPathBuffer = new WCHAR[dwwcharLen];
if (*binPathBuffer == nullptr) {
printf("Error Alloc Memory for search.\n");
SetLastError(ERROR_OUTOFMEMORY);
return FALSE;
}
DWORD exePathLen = GetModuleFileNameW(nullptr, *binPathBuffer, bufferLen);
if (exePathLen == 0) {
printf("Error Get Module File Name.\n");
return FALSE;
}
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
break;
delete[] *binPathBuffer;
bufferLen += 10;
}
return TRUE;
}
BOOL InjectModuleToWinlogon(const WCHAR* fullModulePath, HANDLE hWinlogonProc) {
if (!IsProcessStrictMitigativeProtectionOff(hWinlogonProc)) {
printf("Incompatible injection while enabling mitigation policies.\n");
return FALSE;
}
if (TRUE == InjectMouldeHandler(hWinlogonProc, fullModulePath)) {
printf("Inject Module to winlogon SUCCESSFULLY.\n");
return TRUE;
}
else {
printf("Inject Module to winlogon Failed.\n");
return FALSE;
}
}
BOOL LoadHookCoreAndGetSymbols(
const WCHAR* fullDllPath,
HANDLER_INFO_STRUCT& lpHandleInfo,
DWORD& dwHookBaseOffest
) {
HMODULE hHookCore = LoadLibraryW(fullDllPath);
if (hHookCore == NULL) {
printf("Error[%d]: Failed in Loading Library.\n", GetLastError());
return FALSE;
}
FARPROC pFunGetHandler = MyGetProcAddress64(hHookCore, "GetHandlerAddress");
FARPROC pFunSetBaseAddr = MyGetProcAddress64(hHookCore, "RemoteSetHookBaseAddress");
if (pFunGetHandler == NULL || pFunSetBaseAddr == NULL) {
printf("Error[%d]: Failed in Loading Library.\n", GetLastError());
FreeLibrary(hHookCore);
return FALSE;
}
__GetHandlerAddress GetHandlerAddress = (__GetHandlerAddress)pFunGetHandler;
__SetBaseAddress RemoteSetHookBaseAddress = (__SetBaseAddress)pFunSetBaseAddr;
WCHAR SystemDirtory[MAX_PATH] = { 0 };
WCHAR winlogonPath[500] = { 0 };
GetSystemDirectoryW(SystemDirtory, sizeof(SystemDirtory) / sizeof(TCHAR));
swprintf_s(winlogonPath, sizeof(winlogonPath) / sizeof(TCHAR),
L"%s\\%s", SystemDirtory, WINLOGON_NAME);
dwHookBaseOffest = BFTracePatternInModule(winlogonPath, pattern1, 17);
if (dwHookBaseOffest == 0) {
printf("Error: Failed to Search Special FunctionAddress.\n");
FreeLibrary(hHookCore);
return FALSE;
}
if (!RemoteSetHookBaseAddress(dwHookBaseOffest)) {
printf("Error[%d]: Failed to SetHookBaseAddress.\n", GetLastError());
FreeLibrary(hHookCore);
return FALSE;
}
lpHandleInfo.cbSize = sizeof(HANDLER_INFO_STRUCT);
if (!GetHandlerAddress(&lpHandleInfo)) {
printf("Error: Failed GetHandlerAddressInfo.\n");
FreeLibrary(hHookCore);
return FALSE;
}
wprintf(L"lpHookHandler: [0x%I64X], lpUnhookHandler: [0x%I64X], lpHook: [0x%I64X].\n",
reinterpret_cast<uint64_t>(lpHandleInfo.lpHookHandler),
reinterpret_cast<uint64_t>(lpHandleInfo.lpUnhookHandler),
reinterpret_cast<uint64_t>(lpHandleInfo.lpHookAddress));
return TRUE;
}
void RunHookingAndCleanup(HANDLE hWinlogonProc, const HANDLER_INFO_STRUCT& lpHandleInfo) {
getchar();
if (TRUE == RemoteHookingHandler(hWinlogonProc, lpHandleInfo.lpHookHandler, NULL)) {
printf("Enable Hooks in winlogon SUCCESSFULLY.\n");
}
getchar();
if (TRUE == RemoteHookingHandler(hWinlogonProc, lpHandleInfo.lpUnhookHandler, NULL)) {
printf("Disable Hooks in winlogon SUCCESSFULLY.\n");
}
getchar();
if (TRUE == RemoteHookingHandler(hWinlogonProc, lpHandleInfo.lpSafeFreeLib, NULL)) {
printf("UnLoadLibrary in winlogon SUCCESSFULLY.\n");
}
SafeCloseHandle(hWinlogonProc);
}
BOOL RunAsAdministratorHandler(LPCWSTR lpBinPathName) {
// 检查是否以管理员权限运行
if (IsRunAsAdministrator() == FALSE)
{
printf("Error require Administrator Token.\n");
// 尝试动态提权
if (!ElevateCurrentProcess(lpBinPathName))
{
printf("Failed Auto-Elevate.\n");
return FALSE;
}
else { // 退出进程
exit(0);
}
}
// 启用管理员令牌
if (!EnableDebugPrivilege())
{
printf("Failed to Enable Debug Priviledge.\n");
return FALSE;
}
return TRUE;
}
// 主函数
int main() {
SetConsoleCodePageToUTF8();
//WCHAR binPathBuffer = L'\0';
PWCHAR lpbinPathBuffer = 0;
DWORD bufferLen = MAX_PATH;
if (!GetExecutablePath(&lpbinPathBuffer, bufferLen))
return -1;
if(!RunAsAdministratorHandler(lpbinPathBuffer))
{
delete[] lpbinPathBuffer;
return -1;
}
// 删除程序文件名部分
if (PathRemoveFileSpecW(lpbinPathBuffer) == 0)
{
delete[] lpbinPathBuffer;
printf("Error Remove File Spec.\n");
return -1;
}
wprintf(L"Current WorkingDirectury: %s.\n", lpbinPathBuffer);
WCHAR fullDllPath[MAX_PATH] = { 0 };
swprintf_s(fullDllPath, MAX_PATH, L"%s\\%s", lpbinPathBuffer, HOOK_MODULE_NAME);
DWORD dwWinlogonId = FindWinlogonProcessId();
printf("CurrentSession winlogon PID: %d.\n", dwWinlogonId);
if (CheckProcessHasLoadModule(dwWinlogonId, HOOK_MODULE_NAME))
{
printf("Error: Process already Loaded module.\n");
return -1;
}
HANDLE hWinlogonProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwWinlogonId);
if (hWinlogonProc == NULL) {
delete[] lpbinPathBuffer;
printf("Error OpenProcess(PID: %d, error: %d)\n", dwWinlogonId, GetLastError());
return -1;
}
if (!InjectModuleToWinlogon(fullDllPath, hWinlogonProc))
{
delete[] lpbinPathBuffer;
SafeCloseHandle(hWinlogonProc);
return -1;
}
HANDLER_INFO_STRUCT lpHandleInfo = { 0 };
DWORD dwHookBaseOffest = 0;
if (!LoadHookCoreAndGetSymbols(fullDllPath, lpHandleInfo, dwHookBaseOffest))
{
delete[] lpbinPathBuffer;
SafeCloseHandle(hWinlogonProc);
return -1;
}
// 测试样例
RunHookingAndCleanup(hWinlogonProc, lpHandleInfo);
system("pause");
return 0;
}
// 映射模块到内存中
BYTE* NtMapViewofModule(LPCWSTR lpFileName, LPMAPVIEW_INFO_STRUCT lpMapViewInfo)
{
HANDLE hFile = NULL;
HANDLE hMapping = NULL;
LPVOID lpBaseMapView = nullptr;
PIMAGE_DOS_HEADER dosHeader = nullptr;
PIMAGE_NT_HEADERS ntHeaders = nullptr;
PBYTE BaseAddress = nullptr;
hFile = CreateFileW(lpFileName, GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("Failed to open file.\n");
return FALSE;
}
hMapping = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (hMapping == NULL) {
printf("Failed to create file mapping.\n");
CloseHandle(hFile);
return FALSE;
}
lpBaseMapView = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
if (lpBaseMapView == NULL) {
printf("Failed to map view of file.\n");
goto ErrorEndFunction;
}
dosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(lpBaseMapView);
if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
UnmapViewOfFile(lpBaseMapView);
printf("Not a valid DOS executable.\n");
goto ErrorEndFunction;
}
BaseAddress = reinterpret_cast<BYTE*>(lpBaseMapView);
ntHeaders = reinterpret_cast<PIMAGE_NT_HEADERS>(
BaseAddress + dosHeader->e_lfanew);
if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) {
UnmapViewOfFile(lpBaseMapView);
printf("Not a valid NT executable.\n");
goto ErrorEndFunction;
}
lpMapViewInfo->hFile = hFile;
lpMapViewInfo->hMapping = hMapping;
lpMapViewInfo->lpBaseMapView = lpBaseMapView;
return BaseAddress;
ErrorEndFunction:
CloseHandle(hMapping);
CloseHandle(hFile);
return FALSE;
}
// 释放映射到内存中的模块
BOOL NtUnMapViewModule(LPMAPVIEW_INFO_STRUCT lpMapViewInfo)
{
if (lpMapViewInfo == nullptr)
{
return FALSE;
}
BOOL bResponse = FALSE;
HANDLE hFile = lpMapViewInfo->hFile;
HANDLE hMapping = lpMapViewInfo->hMapping;
LPVOID lpBaseMapView = lpMapViewInfo->lpBaseMapView;
if (hFile)
bResponse = CloseHandle(hFile);
if (hMapping)
bResponse = CloseHandle(hMapping);
if (lpBaseMapView)
bResponse = UnmapViewOfFile(lpBaseMapView);
lpMapViewInfo->hFile = NULL;
lpMapViewInfo->hMapping = NULL;
lpMapViewInfo->lpBaseMapView = nullptr;
return bResponse;
}
// 遍历 pdata 节段信息,搜索指定函数结束地址,并返回下一个函数入口地址
DWORD TraversePDATASectionInfo(PBYTE lpBaseAddress, DWORD dwLastEndAddress, PDWORD dwRealAddress)
{
if (dwLastEndAddress == 0) return 0;
PIMAGE_DOS_HEADER dosHeader =
reinterpret_cast<PIMAGE_DOS_HEADER>(lpBaseAddress);
PIMAGE_NT_HEADERS ntHeaders =
reinterpret_cast<PIMAGE_NT_HEADERS>(lpBaseAddress + dosHeader->e_lfanew);
PIMAGE_SECTION_HEADER sectionHeader = IMAGE_FIRST_SECTION(ntHeaders);
for (int i = 0; i < ntHeaders->FileHeader.NumberOfSections; ++i) {
if (strcmp(reinterpret_cast<char*>(sectionHeader[i].Name), ".pdata") == 0) {
DWORD pdataVirtualAddress = sectionHeader[i].VirtualAddress;
DWORD pdataSize = sectionHeader[i].SizeOfRawData;
DWORD pdataOffset = pdataVirtualAddress
- sectionHeader[i].VirtualAddress
+ sectionHeader[i].PointerToRawData;
PIMAGE_RUNTIME_FUNCTION_ENTRY pdata =
reinterpret_cast<PIMAGE_RUNTIME_FUNCTION_ENTRY>(lpBaseAddress + pdataOffset);
DWORD numEntries = pdataSize / sizeof(IMAGE_RUNTIME_FUNCTION_ENTRY);
for (DWORD j = 0; j < numEntries; ++j) {
long distance = static_cast<long>(dwLastEndAddress) -
static_cast<long>(pdata[j].EndAddress); // 转为有符号数,以便于比较绝对值
if (abs(distance) <= 15) // 容错性,编译器优化可能存在折叠函数
{
memcpy_s(dwRealAddress, sizeof(DWORD), &pdata[j].EndAddress, sizeof(DWORD));
return pdata[j + 1].BeginAddress;
}
}
break;
}
}
return FALSE;
}
// 暴力搜索函数
DWORD WINAPI BFTracePatternInModule(
LPCWSTR moduleName,
PBYTE pattern,
SIZE_T patternSize
)
{
if (pattern == 0 || moduleName == 0 || patternSize == 0)
{
printf("Error Invalid parameter.\n");
return 0;
}
int cwStartTime = 0, cwEndTime = 0; // 计时器存储时间
PBYTE lpBaseAddress = 0;
MAPVIEW_INFO_STRUCT MapViewInfo = { 0 };
// 将模块映射到内存中
lpBaseAddress = NtMapViewofModule(moduleName, &MapViewInfo);
if (lpBaseAddress == nullptr) {
printf("Failed to load module: %ws.\n", moduleName);
return 0;
}
PIMAGE_DOS_HEADER dosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(lpBaseAddress);
PIMAGE_NT_HEADERS ntHeader = reinterpret_cast<PIMAGE_NT_HEADERS>(
(reinterpret_cast<BYTE*>(lpBaseAddress)) + dosHeader->e_lfanew);
DWORD moduleSize = ntHeader->OptionalHeader.SizeOfImage;
wprintf(L"MapView Winlogon Base:0x%I64X.\n", reinterpret_cast<uint64_t>(lpBaseAddress));
wprintf(L"Winlogon ModuleSize:%ld Bytes.\n", moduleSize);
// 模块大小不能为 0
if (moduleSize == 0)
{
printf("Failed to get module information.\n");
NtUnMapViewModule(&MapViewInfo);
return 0;
}
DWORD vcMachOffest = 0; // 用于记录查找的特征码偏移
for (DWORD i = 0; i < moduleSize; i++)
{
vcMachOffest = 0;
DWORD j = 0;
for (j; j < patternSize - 1; j++)
{
if (lpBaseAddress[i + j] != pattern[j] && pattern[j] != 0u)
{
break;
}
}
if (j == patternSize - 1)
{
if (lpBaseAddress[i + j] == pattern[j] || pattern[j] == 0u)
{
vcMachOffest = i;
break;
}
}
}
if (vcMachOffest == 0)
{
NtUnMapViewModule(&MapViewInfo);
return 0;
}
/*
* 关键算法:向上搜索跳过 HotPatch 以及 BreakSwap 代码段
* Created By LianYou 516 at 2024.01.29.
*/
DWORD baseStart = vcMachOffest; // 存储偏移量
DWORD basePatch = vcMachOffest - 1;
DWORD baseCCByte = 0, baseNopByte = 0;
DWORD baseEnd = 0; // 上下文的偏移
DWORD pDataFunEntry = 0; // 正真的入口点偏移
const uint8_t ccByte = 0xCC;
const uint8_t nopByte = 0x90;
// 第一个循环找到连续的 0xCC 或者 0x90 首次出现位置
while (baseStart - basePatch <= 0x3E8u) // 搜索域限界条件
{
// 检测到连续的 0xCC 标记偏移量到 baseCCByte 变量
if (lpBaseAddress[basePatch] == ccByte
&& lpBaseAddress[basePatch - 1] == ccByte)
{
baseCCByte = basePatch - 1;
break;
}
// 检测到连续的 0x90 标记偏移量到 baseNopByte 变量
if (lpBaseAddress[basePatch] == nopByte &&
lpBaseAddress[basePatch - 1] == nopByte)
{
baseNopByte = basePatch - 1;
break;
}
--basePatch;// 递减循环
if (basePatch == 0) break; // 防止数组越界
}
// 判断是否找到第一个连续 0xCC 字节的位置
if (baseCCByte != 0)
{
// 循环检索 0xCC 软件断点指令,实现越过入口点软件断点区域
while (baseStart - baseCCByte <= 0x3E8u)
{
if (lpBaseAddress[baseCCByte] != ccByte)
{
baseEnd = baseCCByte + 1; // 找到 0xCC 最早出现的位置(低地址)
break;
}
if (baseCCByte == 0) break;
--baseCCByte;
}
}
// 判断是否找到第一个连续 0x90 字节的位置
if (baseNopByte != 0)
{
// 循环检索 0x90 NOP 指令,实现越过入口点热补丁区域
while (baseStart - baseNopByte <= 0x3E8u)
{
if (lpBaseAddress[baseNopByte] != nopByte)
{
baseEnd = baseNopByte + 1; // 找到 0x90 最早出现的位置(低地址)
break;
}
if (baseNopByte == 0) break;
--baseNopByte;
}
}
// 依据 text 节映射信息,修正地址
DWORD vcFixedOffest = 0;
DWORD dwRealAddress = 0;
PIMAGE_SECTION_HEADER sectionHeader = IMAGE_FIRST_SECTION(ntHeader);
for (int i = 0; i < ntHeader->FileHeader.NumberOfSections; ++i) {
if (strcmp(reinterpret_cast<char*>(sectionHeader[i].Name), ".text") == 0) {
vcFixedOffest = baseEnd
- sectionHeader[i].PointerToRawData
+ sectionHeader[i].VirtualAddress;
break;
}
}
wprintf(L"End address of the previous function in the entry (estimated): [0x%lX].\n", vcFixedOffest);
// 如果正确定位,则返回地址
pDataFunEntry = TraversePDATASectionInfo(lpBaseAddress, vcFixedOffest, &dwRealAddress);
if (pDataFunEntry != 0)
{
wprintf(L"End address of the previous function in the entry (actual): [0x%lX].\n", dwRealAddress);
wprintf(L"The matched function entry point is located at offset: [0x%lX].\n", pDataFunEntry);
return pDataFunEntry;
}
NtUnMapViewModule(&MapViewInfo);
return 0;
}
BOOL WINAPI SafeCloseHandle(HANDLE handle)
{
BOOL bResponse = TRUE;
if (handle != nullptr) {
bResponse = CloseHandle(handle);
handle = nullptr;
}
return bResponse;
}
BOOL WINAPI IsProcessStrictMitigativeProtectionOff(HANDLE hProcess) {
PROCESS_MITIGATION_DYNAMIC_CODE_POLICY dynamicCodePolicy = { 0 };
PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY signaturePolicy = { 0 };
PROCESS_MITIGATION_CONTROL_FLOW_GUARD_POLICY cfgPolicy = { 0 };
// Actually retrieve the mitigation policy for ACG
if (!GetProcessMitigationPolicy(hProcess,
ProcessDynamicCodePolicy, &dynamicCodePolicy, sizeof(dynamicCodePolicy))) {
printf("[!] Could not GetProcessMitigationPolicy. [%d]\n", GetLastError());
return FALSE;
}
if (dynamicCodePolicy.ProhibitDynamicCode ||
dynamicCodePolicy.AllowRemoteDowngrade ||
dynamicCodePolicy.AllowThreadOptOut) {
printf("Detect[0]: ProcessDynamicCodePolicy on.\n");
return FALSE;
}
// Retrieve mitigation policy for loading arbitrary DLLs
if (!GetProcessMitigationPolicy(hProcess,
ProcessSignaturePolicy, &signaturePolicy, sizeof(signaturePolicy))) {
printf("Could not GetProcessMitigationPolicy. [%d]\n", GetLastError());
return FALSE;
}
if (signaturePolicy.AuditMicrosoftSignedOnly ||
signaturePolicy.AuditStoreSignedOnly ||
signaturePolicy.MicrosoftSignedOnly ||
signaturePolicy.MitigationOptIn ||
signaturePolicy.StoreSignedOnly) {
printf("Detect[1]: ProcessSignaturePolicy on.\n");
return FALSE;
}
// Retrieve mitigation policy for strict Control Flow Guards
if (!GetProcessMitigationPolicy(hProcess,
ProcessControlFlowGuardPolicy, &cfgPolicy, sizeof(cfgPolicy))) {
printf("Could not GetProcessMitigationPolicy. [%d]\n", GetLastError());
return FALSE;
}
if (cfgPolicy.EnableXfg || cfgPolicy.StrictMode)
{
printf("Detect[2]: ProcessControlFlowGuardPolicy on.\n");
return FALSE;
}
return TRUE;
}
BOOL WINAPI IsRunAsAdministrator() // 判断是否以管理员身份运行
{
BOOL bIsElevated = FALSE;
HANDLE hToken = NULL;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
TOKEN_ELEVATION te = { 0 };
DWORD dwReturnLength = 0;
if (GetTokenInformation(hToken, TokenElevation,
&te, sizeof(te), &dwReturnLength))
{
if (dwReturnLength == sizeof(te))
bIsElevated = te.TokenIsElevated;
}
SafeCloseHandle(hToken);
}
return bIsElevated;
}
BOOL WINAPI ElevateCurrentProcess(LPCWSTR wsFilePath)
{
TCHAR szPath[MAX_PATH] = { 0 };
if (GetModuleFileNameW(NULL, szPath, MAX_PATH) != 0)
{
// Launch itself as administrator.
SHELLEXECUTEINFO sei = { 0 };
sei.cbSize = sizeof(SHELLEXECUTEINFO);
sei.lpVerb = L"runas";
sei.lpFile = szPath;
sei.lpParameters = (LPCTSTR)wsFilePath;
sei.nShow = SW_SHOWNORMAL;
if (!ShellExecuteEx(&sei))
{
DWORD dwStatus = GetLastError();
if (dwStatus == ERROR_CANCELLED)
{
// The user refused to allow privileges elevation.
printf("The user refused to allow privileges elevation.\n");
return FALSE;
}
else if (dwStatus == ERROR_FILE_NOT_FOUND)
{
// The file defined by lpFile was not found and
// an error message popped up.
printf("Error Cannot Access Files.\n");
return FALSE;
}
return FALSE;
}
return TRUE;
}
return FALSE;
}
BOOL WINAPI EnableDebugPrivilege()
{
HANDLE handleToken = NULL;
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &handleToken))
{
printf("Error OpenProcessToken.\n");
return FALSE;
}
LUID debugNameValue = { 0 };
if (!LookupPrivilegeValueW(nullptr, SE_DEBUG_NAME, &debugNameValue))
{
SafeCloseHandle(handleToken);
printf("Error LookupPrivilegeValue.\n");
return FALSE;
}
TOKEN_PRIVILEGES tokenPri = { 0 };
tokenPri.PrivilegeCount = 1;
tokenPri.Privileges[0].Luid = debugNameValue;
tokenPri.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(handleToken,
FALSE, &tokenPri, sizeof(tokenPri), nullptr, nullptr))
{
SafeCloseHandle(handleToken);
printf("Error AdjustTokenPrivileges.\n");
return FALSE;
}
SafeCloseHandle(handleToken);
return TRUE;
}
DWORD WINAPI GetActiveConsoleSessionId() {
return WTSGetActiveConsoleSessionId();
}
BOOL WINAPI IsProcessInSession(DWORD processId, DWORD sessionId) {
DWORD session;
if (!ProcessIdToSessionId(processId, &session)) {
printf("Error: ProcessIdToSessionId failed.\n");
return FALSE;
}
return session == sessionId;
}
DWORD WINAPI FindWinlogonProcessId() {
DWORD dwProcessId = 0;
DWORD activeSessionId = GetActiveConsoleSessionId();
if (activeSessionId == 0xFFFFFFFF) {
printf("Error: Unable to retrieve active console session ID.\n");
return 0;
}
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot == INVALID_HANDLE_VALUE) {
printf("Error: CreateToolhelp32Snapshot failed.\n");
return 0;
}
PROCESSENTRY32 entry;
entry.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(snapshot, &entry)) {
printf("Error: Process32First failed.\n");
SafeCloseHandle(snapshot);
return 0;
}
do {
if (entry.cntThreads <= 1u) continue; // 跳过僵尸进程
if (_wcsicmp(entry.szExeFile, L"winlogon.exe") == 0) {
if (IsProcessInSession(entry.th32ProcessID, activeSessionId)) {
dwProcessId = entry.th32ProcessID;
break;
}
}
} while (Process32Next(snapshot, &entry));
SafeCloseHandle(snapshot);
return dwProcessId;
}
/// <summary>
/// MyGetProcAddress64 is a function to replace GetProcAddress for 64-bit
/// architecture, used to retrieve the entry address of a function within a module.
/// </summary>
/// <param name="lpBaseAddress">The base address of the module.</param>
/// <param name="FunName">The name of the function to retrieve.</param>
/// <returns>The entry address of the function if found, or 0 if not found.</returns>
FARPROC WINAPI MyGetProcAddress64(PVOID lpBaseAddress, LPCSTR FunName) {
if (lpBaseAddress == nullptr || FunName == nullptr)
{
SetLastError(ERROR_INVALID_PARAMETER);
return 0;
}
__try {
// Get DOS header
PIMAGE_DOS_HEADER dosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(lpBaseAddress);
if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
SetLastError(ERROR_IMAGE_MACHINE_TYPE_MISMATCH);
return 0;
}
// Get NT header
PIMAGE_NT_HEADERS ntHeader = reinterpret_cast<PIMAGE_NT_HEADERS>(
(reinterpret_cast<BYTE*>(lpBaseAddress)) + dosHeader->e_lfanew);
if (ntHeader->Signature != IMAGE_NT_SIGNATURE) {
SetLastError(ERROR_IMAGE_MACHINE_TYPE_MISMATCH);
return 0;
}
// Get export directory
PIMAGE_EXPORT_DIRECTORY exports =
reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>((reinterpret_cast<BYTE*>(lpBaseAddress))
+ ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
// Get export tables
DWORD* nameTable = reinterpret_cast<DWORD*>(
(reinterpret_cast<BYTE*>(lpBaseAddress)) + exports->AddressOfNames);
DWORD* addressTable = reinterpret_cast<DWORD*>(
(reinterpret_cast<BYTE*>(lpBaseAddress)) + exports->AddressOfFunctions);
WORD* ordinalTable = reinterpret_cast<WORD*>(
(reinterpret_cast<BYTE*>(lpBaseAddress)) + exports->AddressOfNameOrdinals);
DWORD low = 0;
DWORD high = exports->NumberOfNames - 1;
// Binary search in export table
while (low <= high) {
DWORD mid = low + (high - low) / 2;
PCHAR pFuncName = reinterpret_cast<PCHAR>(
(reinterpret_cast<BYTE*>(lpBaseAddress)) + nameTable[mid]);
if (pFuncName != nullptr) {
// Copy function name and ensure null-termination
size_t strLength = strnlen_s(pFuncName, MAX_PATH);
char* buffer = new (std::nothrow) char[strLength + 1];
if (buffer != nullptr) {
memcpy_s(buffer, strLength + 1, pFuncName, strLength);
buffer[strLength] = '\0';
// Compare function names
int compareResult = _stricmp(buffer, FunName);
delete[] buffer; // Free allocated memory
if (compareResult == 0) {
// Function name found, return function address
DWORD functionRVA = addressTable[ordinalTable[mid]];
FARPROC pfunAddress = reinterpret_cast<FARPROC>(
reinterpret_cast<UINT64>(lpBaseAddress) + functionRVA );
SetLastError(0);
return pfunAddress;
}
// Update search range based on comparison result
if (compareResult < 0) {
low = mid + 1;
}
else {
high = mid - 1;
}
}
else {
printf("Error out of memory.\n");
SetLastError(ERROR_OUTOFMEMORY);
}
}
else {
printf("Error GetFunctionNameTable.\n");
SetLastError(ERROR_ACCESS_DENIED);
}
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
printf("Exception occurred while accessing memory.\n");
return 0;
}
return 0; // Function not found
}
BOOL WINAPI CheckProcessHasLoadModule(DWORD dwProcessId, LPCWSTR wsFileName) {
/*
* 参数为TH32CS_SNAPMODULE 或 TH32CS_SNAPMODULE32时,
* 如果函数失败并返回ERROR_BAD_LENGTH,则重试该函数直至成功
* 进程创建未初始化完成时,CreateToolhelp32Snapshot会返回error 299,但其它情况下不会。
*/
HANDLE hSnapshot = CreateToolhelp32Snapshot(
TH32CS_SNAPMODULE |
TH32CS_SNAPMODULE32,
dwProcessId);
while (INVALID_HANDLE_VALUE == hSnapshot) {
DWORD dwError = GetLastError();
if (dwError == ERROR_BAD_LENGTH) {
hSnapshot = CreateToolhelp32Snapshot(
TH32CS_SNAPMODULE |
TH32CS_SNAPMODULE32,
dwProcessId);
continue;
}
else {
printf("CreateToolhelp32Snapshot failed: %d, targetProcessId:%d.\n",
dwError, dwProcessId);
return FALSE;
}
}
MODULEENTRY32W mi = { 0 };
mi.dwSize = sizeof(MODULEENTRY32W); // 第一次使用必须初始化成员
BOOL bRet = Module32FirstW(hSnapshot, &mi);
while (bRet) {
// mi.szModule 是短路径
if (wcsstr(wsFileName, mi.szModule) ||
wcsstr(mi.szModule, wsFileName) ) {
if (hSnapshot != NULL) CloseHandle(hSnapshot);
return TRUE;
}
mi.dwSize = sizeof(MODULEENTRY32W);
bRet = Module32NextW(hSnapshot, &mi);
}
if (hSnapshot != NULL) SafeCloseHandle(hSnapshot);
return FALSE;
}
BOOL WINAPI InjectMouldeHandler(
HANDLE hProcess,
LPCWSTR pszDllFileName
)
{
// 1.目标进程句柄
if (hProcess == NULL || pszDllFileName == nullptr)
{
wprintf(L"Error: InvalidSyntax error from InjectMouldeHandler.\n");
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
size_t pathSize = (wcslen(pszDllFileName) + 1) * sizeof(wchar_t);
SetLastError(0);
// 2.在目标进程中申请空间
LPVOID lpPathAddr = VirtualAllocEx( hProcess, 0, pathSize,
MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (NULL == lpPathAddr)
{
wprintf(L"Error[%d]: Failed to apply memory in the target process!\n", GetLastError());
return FALSE;
}
SetLastError(0);
// 3.在目标进程中写入 Dll 路径
if (FALSE == WriteProcessMemory( hProcess, lpPathAddr,
pszDllFileName, pathSize, NULL) )
{
wprintf(L"Error[%d]: Failed to write module path in target process!\n", GetLastError());
return FALSE;
}
SetLastError(0);
// 4.加载 ntdll.dll
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
if (NULL == hNtdll)
{
wprintf(L"Error[%d]: Failed to load NTDLL.DLL!\n", GetLastError());
VirtualFreeEx(hProcess, lpPathAddr, 0, MEM_RELEASE);
return FALSE;
}
SetLastError(0);
// 5.获取 LoadLibraryW 的函数地址, FARPROC 可以自适应 32 位与 64 位
FARPROC pFuncProcAddr = MyGetProcAddress64(GetModuleHandleW(L"KernelBase.dll"),
"LoadLibraryW");
if (NULL == pFuncProcAddr)
{
wprintf(L"Error[%d]: Failed to obtain the address of the LoadLibrary function!\n",
GetLastError());
VirtualFreeEx(hProcess, lpPathAddr, 0, MEM_RELEASE);
return FALSE;
}
// 6.获取 NtCreateThreadEx 函数地址,该函数在32位与64位下原型不同
// _WIN64 用来判断编译环境 ,_WIN32用来判断是否是 Windows 系统
#ifdef _WIN64
typedef DWORD(WINAPI* __NtCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
ULONG CreateThreadFlags,
SIZE_T ZeroBits,
SIZE_T StackSize,
SIZE_T MaximumStackSize,
LPVOID pUnkown
);
#else
typedef DWORD(WINAPI* __NtCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
BOOL CreateSuspended,
DWORD dwStackSize,
DWORD dw1,
DWORD dw2,
LPVOID pUnkown
);
#endif
SetLastError(0);
__NtCreateThreadEx NtCreateThreadEx =
(__NtCreateThreadEx)MyGetProcAddress64(hNtdll, "NtCreateThreadEx");
if (NULL == NtCreateThreadEx)
{
wprintf(L"Error[%d]: Failed to obtain NtCreateThreadEx function address!\n",
GetLastError());
VirtualFreeEx(hProcess, lpPathAddr, 0, MEM_RELEASE);
return FALSE;
}
SetLastError(0);
// 7.在目标进程中创建远线程
HANDLE hRemoteThread = NULL;
DWORD lpExitCode = 0;
DWORD dwStatus = NtCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL,
hProcess,
(LPTHREAD_START_ROUTINE)pFuncProcAddr, lpPathAddr, 0, 0, 0, 0, NULL);
if (NULL == hRemoteThread)
{
wprintf(L"Error[%d]: Failed to create thread in target process!\n", GetLastError());
VirtualFreeEx(hProcess, lpPathAddr, 0, MEM_RELEASE);
return FALSE;
}
SetLastError(0);
// 8.等待线程结束
if (WAIT_TIMEOUT == WaitForSingleObject(hRemoteThread, 2000))
{
wprintf(L"Error[%d]: Remote thread not responding.\n", GetLastError());
VirtualFreeEx(hProcess, lpPathAddr, 0, MEM_RELEASE);
return FALSE;
}
GetExitCodeThread(hRemoteThread, &lpExitCode);
if (lpExitCode == 0)
{
wprintf(L"Error: Injection module failed in the target process.\n");
VirtualFreeEx(hProcess, lpPathAddr, 0, MEM_RELEASE);
return FALSE;
}
// 9.清理环境
VirtualFreeEx(hProcess, lpPathAddr, 0, MEM_RELEASE);
SafeCloseHandle(hRemoteThread);
return TRUE;
}
BOOL WINAPI RemoteHookingHandler(
HANDLE hProcess,
PVOID lpProcAddress,
LPVOID lpParameter
)
{
// 1.目标进程句柄
if (hProcess == NULL || lpProcAddress == nullptr)
{
wprintf(L"Error: InvalidSyntax error from RemoteHookingHandler.\n");
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
SetLastError(0);
// 4.加载 ntdll.dll
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
if (NULL == hNtdll)
{
wprintf(L"Error[%d]: Failed to load NTDLL.DLL!\n", GetLastError());
return FALSE;
}
// 6.获取 NtCreateThreadEx 函数地址,该函数在 32 位与 64 位下原型不同
// _WIN64 用来判断编译环境 ,_WIN32用来判断是否是 Windows 系统
#ifdef _WIN64
typedef DWORD(WINAPI* __NtCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
ULONG CreateThreadFlags,
SIZE_T ZeroBits,
SIZE_T StackSize,
SIZE_T MaximumStackSize,
LPVOID pUnkown
);
#else
typedef DWORD(WINAPI* __NtCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
BOOL CreateSuspended,
DWORD dwStackSize,
DWORD dw1,
DWORD dw2,
LPVOID pUnkown
);
#endif
SetLastError(0);
__NtCreateThreadEx NtCreateThreadEx =
(__NtCreateThreadEx)MyGetProcAddress64(hNtdll, "NtCreateThreadEx");
if (NULL == NtCreateThreadEx)
{
wprintf(L"Error[%d]: Failed to obtain NtCreateThreadEx function address!\n",
GetLastError());
return FALSE;
}
SetLastError(0);
// 7.在目标进程中创建远线程
HANDLE hRemoteThread = NULL;
DWORD lpExitCode = 0;
DWORD dwStatus = NtCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL,
hProcess, (LPTHREAD_START_ROUTINE)lpProcAddress, lpParameter, 0, 0, 0, 0, NULL);
if (NULL == hRemoteThread)
{
wprintf(L"Error[%d]: Failed to create thread in target process!\n", GetLastError());
return FALSE;
}
SetLastError(0);
// 8.等待线程结束
if (WAIT_TIMEOUT == WaitForSingleObject(hRemoteThread, 15000))
{
wprintf(L"Error[%d]: Remote thread not responding.\n", GetLastError());
return FALSE;
}
GetExitCodeThread(hRemoteThread, &lpExitCode);
if (lpExitCode == 0)
{
wprintf(L"Error: Control HOOK routine failed in the target process.\n");
return FALSE;
}
// 9.清理环境
SafeCloseHandle(hRemoteThread);
return TRUE;
}
5.3 测试结果展示
(1)成功注入钩子模块并启用挂钩:
(2)按下 Ctrl + Shift + Esc 弹出对话框:
(3)安全地远程卸载模块:
至此,测试完成。
总结&后记
在第一篇文章中,我们主要分析了系统热键处理的关键接口以及如何通过选取特征码精准定位接口函数的入口点。在这一篇中,我们进一步分析了 Winlogon 相关例程的机制,并给出挂钩处理的解决方案。
更新内容:暂无
已知兼容性问题:暂无
发布于:2024.02.03,更新于:2024.02.04