其他
一种枚举系统热键的思路及代码实现(Win7&Win10)
本文为看雪论坛优秀文章
看雪论坛作者ID:深山修行之人
前言
思路分析
BOOL RegisterHotKey(
HWND hWnd,
int id,
UINT fsModifiers,
UINT vk
);
hWnd 窗口句柄
id 热键ID
fsModifiers 控制位,如Ctrl/Alt/Shift...
vk 虚拟键码 Virtual Key Codes
BOOL APIENTRY NtUserRegisterHotKey(HWND hWnd,
int id,
UINT fsModifiers,
UINT vk
);
mov r9d, edi
mov dword ptr [rsp+48h+BugCheckParameter2], ebp ; BugCheckParameter2
mov r8d, r14d
xor edx, edx
mov rcx, rax ; struct tagWND *
call _RegisterHotKey
v27 = *(struct tagTHREADINFO **)gptiCurrent;
...
*(_QWORD *)v19 = v27; // wndinfo
*(_DWORD *)(v19 + 32) = v30; // id
*(_WORD *)(v19 + 26) = v12 | v22; // modifiers2
*(_WORD *)(v19 + 24) = v11; // modifiers1
*(_DWORD *)(v19 + 28) = BugCheckParameter2; // 这里是vk
*(_QWORD *)(v19 + 8) = v29; // callback
v24 = *(_BYTE *)(v19 + 28) & 0x7F; // Hash表,0x80个Buckets,Index是vk取模7f
*(_QWORD *)(v19 + 40) = gphkHashTable[v24]; // 将之前的节点插入单链表
gphkHashTable[v24] = (struct tagHOTKEY * near *)v19; // 将节点插入Hash表
typedef struct _THREADINFO {
PETHREAD thread;
//..省略其它字段..
} *PTHREADINFO;
typedef struct _WNDINFO {
HWND wnd;
//..省略其它字段..
} *PWNDINFO;
typedef struct _HOT_KEY {
PTHREADINFO thdinfo;
PVOID callback;
PWNDINFO wndinfo;
UINT16 modifiers1; //eg:MOD_CONTROL(0x0002)
UINT16 modifiers2; //eg:MOD_NOREPEAT(0x4000)
UINT32 vk;
UINT32 id;
#ifdef _AMD64_
PADDING32 pad;
#endif
struct _HOT_KEY *slist;
//..省略其它字段..
} HOT_KEY, * PHOT_KEY;
通过解析PDB符号定位。然而Windows符号服务器,它配拥有mirror吗。
通过代码特征码搜索,想到要兼容Win10各种版本,头大。
代码及实现
找到当前session的csrss,插内核APC,实现切换到GUI线程。
遍历找Win32k模块基址(Win7 win32k.sys,Win10 win32kfull.sys) 解析得到.data段地址区域 获取NtUser*Hotkey函数地址,Win7从Shadow SSDT表中查,Win10从win32kfull.sys导出表中获取。 注册热键,1~0x80的vk值,记录注册成功的值。 搜索data段,首先过滤内核地址,搜索满足条件的0x80个区域,再校验是否满足HotKey Hash表条件。 取消注册成功的热键。 递归枚举热键列表,解析对应结构:hWnd、hk、ID、fsModifiers。
NTSTATUS DriverEntry(PDRIVER_OBJECT drvobj, PUNICODE_STRING registry)
{
NTSTATUS status;
UNREFERENCED_PARAMETER(registry);
KdPrint(("OpsHotkey Running..."));
DoEnumHotkeys();
return STATUS_SUCCESS;
}
NTSTATUS DoEnumHotkeys()
{
NTSTATUS Status;
PETHREAD Thread;
PKAPC Apc = NULL;
BOOLEAN Inserted;
// 获取csrss进程ID
ULONG csrss_pid = GetSessionProcessId();
// 得到进程第一个线程
Status = GetProcessFirstThread((ULONG)csrss_pid, &Thread);
if (!NT_SUCCESS(Status)) {
return Status;
}
// 分配内存
Apc = (PKAPC)ExAllocatePoolWithTag(NonPagedPool, sizeof(KAPC), 'cpak');
if (Apc == NULL) {
ObDereferenceObject(Thread);
return STATUS_UNSUCCESSFUL;
}
// 初始化APC对象
KeInitializeApc(Apc,
(PKTHREAD)Thread,
OriginalApcEnvironment,
&KernelApcRoutine,
NULL,
DoEnumHotkeysApc,
KernelMode,
NULL);
// 插APC
Inserted = KeInsertQueueApc(Apc, NULL, NULL, IO_NO_INCREMENT);
if (Inserted) {
KdPrint(("[%s] KeInsertQueueApc ok.", __FUNCTION__));
} else {
KdPrint(("[%s] KeInsertQueueApc failed.", __FUNCTION__));
ExFreePool(Apc);
}
ObDereferenceObject(Thread);
return Status;
}
BOOLEAN SearchHotkeyTable(PUCHAR* &htable)
{
htable = NULL;
//找到Hash表位于的模块基址(Win7 win32k.sys,Win10 win32kfull.sys)
PUCHAR win32k;
ULONG win32ksize = 0;
RTL_OSVERSIONINFOEXW info;
OsGetVersionInfo(info); if (info.dwMajorVersion == 10) {
win32k = (PUCHAR)GetSystemModuleBase("win32kfull.sys", &win32ksize);
} else {
win32k = (PUCHAR)GetSystemModuleBase("win32k.sys", &win32ksize);
}
if (!win32k) {
return FALSE;
}
KdPrint(("win32k:%p, win32ksize:%x\n", win32k, win32ksize));
//得到.data段区域(全局Hash表所在区域)
NTSTATUS status;
PUCHAR start;
ULONG size;
status = GetSectionRegion(win32k, ".data", start, size);
if (!NT_SUCCESS(status)) {
return FALSE;
}
KdPrint(("win32k-data start:%p, size:%x\n", start, size));
//注册一遍热键,为了填充Hash表
__NtUserRegisterHotKey pNtUserRegisterHotKey = NULL;
__NtUserUnregisterHotKey pNtUserUnregisterHotKey = NULL;
if (!GetHotkeyFunctions(win32k, pNtUserRegisterHotKey, pNtUserUnregisterHotKey)) {
return FALSE;
}
int hkmarks[MAX_VK] = { 0 };
for (int i = 1; i <= MAX_VK; i++) {
if (pNtUserRegisterHotKey(NULL, ~i, MOD_ALT | MOD_NOREPEAT, i)) {
hkmarks[i] = ~i;
}
}
//开始搜索Hash表
PUCHAR *ptr = (PUCHAR*)start;
for (int i = 0, j = 0; i < size/sizeof(ptr); i++) {
if (j == 0x80) {
//得到起始位置
i -= j;
//校验特定Hotkey
INT vks[] = { 5, 10 ,15, 20, 25, 30, 35, 40, 45};
for (INT ck = 0; ck < RTL_NUMBER_OF_V2(vks); ck++) {
INT vk = vks[ck];
if (!CheckHotkeyValid(ptr[i + vk], vk)) {
j = 0;
break;
}
}
//找到HashTable
if (j != 0) {
htable = &ptr[i];
break;
}
continue;
}
//初步过滤内核地址
if (ptr[i] > MmSystemRangeStart) {
j++;
continue;
}
j = 0;
}
//取消注册成功的热键
for (int i = 1; i <= MAX_VK; i++) {
if (hkmarks[i]) {
pNtUserUnregisterHotKey(NULL, hkmarks[i]);
}
}
return 1;
}
VOID DumpHotkeyNode(PHOT_KEY hk)
{
// 链表下一个节点存在
if (MmIsAddressValid(hk->slist)) {
// 递归调用
DumpHotkeyNode(hk->slist);
}
PETHREAD thread = hk->thdinfo->thread;
PEPROCESS process = NULL;
HANDLE pid = NULL;
HANDLE tid = NULL;
if (thread != NULL) {
process = IoThreadToProcess(thread);
pid = PsGetProcessId(process);
tid = PsGetThreadId(thread);
}
HWND wnd = NULL;
if (hk->wndinfo && MmIsAddressValid(hk->wndinfo))
wnd = hk->wndinfo->wnd;
// Dump系统热键
DbgPrint("HK:%x NAME:%s PROCESS:%d THREAD:%d HWND:%x MOD:%d VK:%d \n",
hk, PsGetProcessImageFileName(process), pid, tid, wnd, hk->modifiers1, hk->vk);
}
VOID DumpHotkeyTable(PUCHAR* table)
{
// 遍历Hash表
for (INT i = 0; i < 0x7f; i++)
{
PHOT_KEY hk = (PHOT_KEY)table[i];
if (hk)
DumpHotkeyNode(hk);
}
}
结束语
ps. 操作热键附件可点击“阅读原文”下载~
看雪ID:深山修行之人
https://bbs.pediy.com/user-857678.htm
*本文由看雪论坛 深山修行之人 原创,转载请注明来自看雪社区。
推荐文章++++
好书推荐