windows10 记事本进程 键盘消息钩子 dll注入

article/2025/9/24 17:52:54

看了很多文档,垮了很多坎,终于完成了这个demo;

有很多个人理解,可能不完全正确,见谅;

先上实现的图片:

如图,我通过SetWindowsHookEx()函数向记事本进程中当前窗口线程注入了自己写的dll,dll中设置的回调函数使,当键盘按了1,那么就会触发一个MessageBox。

工具:VS 2015, PCHunter(用于查看是否成功注入了dll,其实看能否实现功能就信,非必须的)

思路:先写一个dll(就是要被注入的dll),再写一个windows控制台程序(用于将dll注入到我们想要注入的进程)

接下来我们一步步实现看看:

一、DLL编写

1、打开VS新建一个名为DLL的Win32 项目:

2、在应用程序向导中选中DLL、空项目(空项目比较干净,没有多余的东西):

3、创完了项目,先别急着写代码,还有很多必要的动西要改,右键点击项目->属性。将MFC的使用改为“在共享DLL中使用MFC”,原因是dll中会用到CString类型,要加入#include <afx.h>这个头文件,如果不设置MFC的话,之后编译会报错;将字符集改为“使用多字节字符集”,及ANSI,原因是在ANSI和Unicode下,CSting的存储结构是不同的,前者是char *,后者是wchar_t *,而且字符集不同,有些函数的参数也会跟着变,这个后面会说。

4、如图点击配置管理器:

5、将Debug配置的平台改为64位,原因是:我的windows是64位的,记事本软件也是64位的(虽然它的执行文件在System32文件夹下,但是用PCHunter可以看到它是64位的程序),而我们最重要的注入函数SetWindowsHookEx()的官网文档说了,这个函数只能用于64位程序将64位dll注入64位程序,或32位程序将32位dll注入32位程序,如果我们编写的dll是32位的,那么到时候注入时程序就会卡死(别问我为什么知道),也就是注入失败了,再给个官方文档地址点击打开链接。

6、在源文件目录下新建一个名为DLL的cpp文件:

7、现在我们可以写代码了:

#include <afx.h> //CString的头文件
#include "stdio.h"
#include "windows.h" //要调用的很多windows api函数的头文件HHOOK g_hHook = NULL; //HHOOK是钩子句柄,如果想搭建钩子链,也可把下一个需要传给的钩子句柄放在这。CString IsNumber(WPARAM wParam)
{CString message;switch (wParam) {case 0x30: message.Format("按了0"); break;case 0x31: message.Format("按了1"); break;case 0x32: message.Format("按了2"); break;case 0x33: message.Format("按了3"); break;case 0x34: message.Format("按了4"); break;case 0x35: message.Format("按了5"); break;case 0x36: message.Format("按了6"); break;case 0x37: message.Format("按了7"); break;case 0x38: message.Format("按了8"); break;case 0x39: message.Format("按了9"); break;default: message.Format("未定义的按键"); break;}return message;
}
//获取到的wparam是16位的int(也可能是long,这个无所谓),用于标识键盘截取到的消息是哪个键,我简单的
//识别了键盘上的数组键(不是小键盘的数组键),返回CString对象。LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
//这是一个键盘钩子消息的回调函数,当设置钩子成功,dll被注入到目标线程,该回调函数会在每次有键盘消息
//传递给目标线程时被调用,第二个参数在这个类型的钩子中放回的是虚拟键盘的信息,其他两个参数我不太清楚
{MessageBox(NULL, IsNumber(wParam), _T("Message"), 0);return CallNextHookEx(g_hHook, nCode, wParam, lParam);//我理解这句代码意思是,如果g_hHook非空,就把消息传给这个句柄,否则就传给应用程序。
}

KeyboardProc,官方文档有解释:点击打开链接,关于KeyboardProc中wparam参数返回的信息:点击打开链接

我可能也有很多地方没理解对,有能力尽量看官方文档。

8、在源文件目录下新建一个名为DLL的def文件:

9、添加如下代码,可以将KeyboardProc函数导出:

LIBRARY DLLEXPORTS
KeyboardProc

 整个项目下只有“源文件”下的两个文件:

10、点击最上方的生成->生成解决方案,成功的话,找到DLL->x64->Debug这个文件夹,看下有没有DLL.dll这个文件,注意:不是DLL->DLL->x64->Debug这个文件夹,不要问我为什么会知道。

至此第一部分就算完成了,我们得到了DLL.dll这个文件。

二、CPP编写

1、打开VS新建一个名为CPP的Win 32控制台应用程序:

2、之后的设置都是默认的(之前写dll选了空项目,写cpp就不用了)。

3、4、5、全部参考第一部分。

6、在源文件目录下的CPP.cpp文件添加代码:

#include "stdafx.h"
#include "windows.h"
#include "Psapi.h" //连接了库后引用头文件,EnumProcesses及GetModuleFileNameEx都需要引入这个头文件
#pragma comment(lib,"Psapi.lib") //预编译指令,连接psapi.lib库DWORD FindProcessByEnumProcess(CString TargetProcessName)
//参数是目标程序名,如notepad.exe
//返回值类型DWORD,是32位的long型,值是找到的目标进程的进程id, 如果打开了多个同名程序,找到的是最后打开的那个进程的进程id
{DWORD TargetProcessId = 0; //目标进程初始值是0,没找到时就返回0DWORD ProcessesId[1024] = { 0 }; //进程id数组,在之后EnumProcesses函数调用会将当前所有进程id放入数组DWORD NeededProcessesId = 0; //在之后EnumProcesses函数调用后会将实际需要的进程数组的大小赋值给它LPSTR ProcessName = (LPSTR)malloc((sizeof(char)) * 1024);//LPSTR定义是typedef LPSTR char * ,LPSTR被定义成是一个指向以NULL(‘\0’)结尾的32位ANSI字符数组指针//用于存储返回到的进程名EnumProcesses(ProcessesId, sizeof(ProcessesId), &NeededProcessesId);//查询所有当前进程//第一个参数是输出参数,返回进程数组存储到ProcessesId[1024]中//第二个参数的输入参数,输入需要返回的进程数组的存储大小//第三个参数的输出参数,返回实际需要的进程数组的存储大小DWORD ProcessNumber = NeededProcessesId / sizeof(DWORD);//得到进程个数,用于遍历for (unsigned int i = 0; i < ProcessNumber; i++){if (ProcessesId[i] != 0){//对每个进程id执行下面操作HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, ProcessesId[i]);//HANDLE是进程句柄类型,hProcess存储的就是进程句柄了//OpenProcess函数通过进程id获取进程句柄//第一个参数是输入参数,标识需要获取的权限,这里我们获取PROCESS_QUERY_INFORMATION和PROCESS_VM_READ权限//因为下面的GetModuleFileNameEx函数指定要这两个权限//第二个参数是输入参数,用来标识该句柄是否希望被子进程继承,不过不考虑子进程的继承权限则直接赋值为FALSE//第三个参数是输入参数,输入需要打开进程的进程id//返回值就是得到的句柄了if (hProcess != NULL){GetModuleFileNameEx(hProcess, NULL, ProcessName, 1024);//根据进程句柄获取到进程完整的名称,如C:\Windows\System32\notepad.exe//第一个参数是输入参数,输入需要获取进程名的进程句柄//第二个参数是输入参数,输入需要获取的模块的模块句柄,为NULL表示获取进程主模块//第三个参数是输出参数,输出进程模块完整的名称//第四个参数是输入参数,表明ProcessName的存储大小CString ProcessFullPathName = (CString)ProcessName;//把LPSTR类型转为CString类型,便于进行字符处理//CString在ANSI字符集下以存储char数组,在Unicode字符集下以存储wchar_t数组,后者的长度是前者的两倍//CString a,则a可作为指向存储的char数组的头部的指针,和LPSTR类型是一样的,所以我用了强转//我百度到的转化方法是这么写的:CString ProcessFullPathName(ProcessName); 也可以CString ProcessBaseName = ProcessFullPathName.Right(ProcessFullPathName.GetLength() - ProcessFullPathName.ReverseFind('\\') - 1);//把路径去掉,留下一个基础名称及C:\Windows\System32\notepad.exe转为notepad.exeif (ProcessBaseName == TargetProcessName)//如果该进程名与目标进程名相同,那么该进程id就是目标进程id{TargetProcessId = ProcessesId[i];}CloseHandle(hProcess);//关闭句柄hProcess = NULL;}}}return TargetProcessId;
}void DoInject(DWORD TargetWindowThreadId)
{HMODULE hDll = LoadLibrary(_T("DLL.dll"));//HMODULE是模块句柄类型//LoadLibrary可以显示加载dll//这里我没有加路径,所有执行前要将dll放到exe文件同目录下if (hDll == NULL) {printf("将dll加载到自身进程失败\n");exit(0);}else {printf("将dll加载到自身进程成功\n");}FARPROC KeyboardProc = (FARPROC)GetProcAddress(hDll, "KeyboardProc");//通过GetProcAddress函数获取到hDll句柄中的KeyboardProc函数的地址if (KeyboardProc == NULL) {printf("获取到回调函数地址失败\n");exit(0);}else {printf("获取到回调函数地址成功\n");}HHOOK g_hHook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KeyboardProc, hDll, TargetWindowThreadId);//将dll注入目标线程,设置函数指针指向写好的键盘消息回调函数//第一个参数输入钩子类型//第二个参数根据不同的钩子类型,要输入不同类型的回调函数地址//第三个参数输入dll句柄//第四个参数输入目标线程idif (g_hHook) {printf("向目标线程添加钩子并注入dll成功\n");}printf("输入q卸载钩子:");while (getchar() != 'q');if (g_hHook){UnhookWindowsHookEx(g_hHook);g_hHook = NULL;}//卸载钩子FreeLibrary(hDll);//释放dll
}BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
//EnumWindows设置的回调函数,系统每发现一个窗口都会调用该回调函数
//HWND是窗口句柄类型
//第一个参数返回的是当前窗口句柄,第二个参数类型可以自己定,我传入的是目标进程id
{DWORD CurrentWindowProcessId; //当前窗口进程idDWORD CurrentWindowThreadId; //当前窗口线程idCurrentWindowThreadId = GetWindowThreadProcessId(hwnd, &CurrentWindowProcessId);//GetWindowThreadProcessId()可以通过窗口句柄,获取该窗口的所在的进程及线程//第一个参数是输入参数,输入目标窗口句柄//第二个参数是输出参数,类型是LPDWORD,及指向DWORD的指针,所以要取地址,函数执行成功后CurrentWindowProcessId值就是返回的当前窗口进程id//返回值是值传递的,DWORD类型,直接赋值给DWORD类型就行了,值是当前窗口线程idif (CurrentWindowProcessId == lParam) {//如果当前窗口进程id等于目标进程的进程id//则得到的当前窗口线程id就是目标窗口线程idDoInject(CurrentWindowThreadId);//得到了线程id后就可以注入了return false; //当找到后就返回false,这样才会终止遍历}return true; //不是当前窗口,返回true,继续遍历
}int main()
{CString TargetProcessName(_T("notepad.exe"));DWORD TargetProcessId = FindProcessByEnumProcess(TargetProcessName); //先找到目标进程idEnumWindows(EnumWindowsProc, TargetProcessId); //根据目标进程id进行遍历,找到目标线程,并注入dll
}

我把cpp的框架写出来

FindProcessByEnumProcess()函数是输入进程名,返回进程id

DoInject()函数是执行注入的过程,需要知道被注入的线程的id

EnumWindowsProc()函数是回调函数,对于每个已存在的窗口,判断其进程id是否与目标进程id相同,如果是,就锁定了目标线程id,再调用DoInject()函数执行注入的过程

int main()

{

    1、得到目标进程id

    2、设置回调函数,等待其执行

}

再来说说我的思路:我们目标是要找到计算本程序线程id,因为注入函数SetWindowsHookEx的最后一个参数是目标线程id,进程id是 不行的,其实有两种实现方法:

思路1:找到记事本进程id,根据进程id找到其所有的线程id,但是一个记事本进程有很多子线程,我不知道是否都要注入还是只要注入一个,而且列出所有子线程那个方法我没弄懂,于是没这么做;

思路2:找到找到记事本进程id,枚举当前所有窗口参看窗口的进程id以及线程id,对比记事本进程id,相同的话就锁定了记事本窗口所在线程id;

思路3:其实最开始我们的源头就是记事本的进程名notepad.exe,我们有没有办法绕过进程id,找到线程id呢,FindWindow()这个函数可以通过窗口名找到窗口句柄,再GetWindowThreadProcessId()根据窗口句柄找到窗口线程id不就行了吗,但是可惜的是这个窗口名并不是notepad.exe,而是“新建文本文档.txt - 记事本”,根本不好锁定,所以此路不通。

7、在stdafx.h这个头文件中添加代码:

#include <afx.h> //因为我们cpp建的不是空项目,项目是有结构的,引入头文件一定要放在stdafx.h中

8、点击最上方的生成->生成解决方案,将DLL->x64->Debug目录下的DLL.dll文件复制到CPP->x64->Debug目录下

9、先打开一个记事本程序(如果你打开两个,前面也说到了,只能锁定后打开的窗口),点击上方调试->开始执行,这时再打开回到记事本窗口,试试摁下键盘看看有没有效果。

还可以通过PCHunter查看被注入的dll,方法是右击进程->查看进程模块,如下图被标记为红色的dll:

参考文章:1、点击打开链接(腾讯 游戏安全实验室,这个demo只是其中的一个作业)/2、点击打开链接 


http://chatgpt.dhexx.cn/article/G7R2gGpA.shtml

相关文章

消息钩子学习工程

前奏 近来一直在自学Windows Hook相关的知识&#xff0c;已经尝试多种注入方式。尤其对消息钩子方式很感兴趣&#xff0c;因为看到Spy能够截获系统中绝大多数应用的消息流&#xff0c;就很想知道它的工作原理&#xff0c;打算制作属于自己的Spy。 消息钩子简介&#xff1a; 消息…

DLL注入技术之消息钩子注入(HOOK简单的实现)

低头不是认输&#xff0c;是要看清自己的路。仰头不是骄傲&#xff0c;是看见自己的天空。——致自己 Hook&#xff0c;是Windows消息处理机制的一个平台&#xff0c;应用程序可以在上面设置子程序以监视指定窗口的某种消息&#xff0c;而且所监视的窗口可以是其他进程所创建的…

powerdesigner制作数据字典

powerdesigner制作数据字典TOC 配置好数据库连接之后&#xff0c;点击File→New Model 这步是默认的点OK就行。 数据传输完之后点击保存。 选择好保存路径后&#xff0c;关闭软件&#xff08;关闭时注意选择保存&#xff09;&#xff0c;然后再重现打开。 重新打开Po…

设计 - 数据字典

文档分类 写文档目的 你有没有遇到过开晨会、周会的时候某个问题已经讨论的很清晰。 但是几天后或者临近周末的时候再说这个问题的时候&#xff0c;团队中有的童鞋会说:“我不知道&#xff0c;没有说过这个问题或者这个方案”&#xff0c;因此而造成的BB事很伤神费心。 为了避免…

Oracle 数据字典详解

Oracle 数据字典详解 什么叫数据字典&#xff1f; 数据字典指的是描述数据的数据。 举个例子&#xff1a;我们在数据库里面创建了一个表&#xff0c;这个表位于哪个数据文件、这个表有哪些列、这个表的每一个列的数据类型、这个表的约束等等。这些信息都是描述这个表的&#…

数据字典及其使用(方案一)

1 数据字典 1.1 什么是数据字典 将如下这些具有相同类型的配置项&#xff0c;配置到系统的数据字典表中&#xff0c;方便系统维护&#xff0c;由超级管理员统一在后台进行数据字典维护&#xff0c;如果用户需求要增加变更配置项&#xff0c;只需要修改数据字典表记录…

什么是mysql数据字典_数据字典是什么?

展开全部 数据e69da5e6ba9062616964757a686964616f31333366306434字典是指对数据的数据项、数据结构、数据流、数据存储、处理逻辑等进行定义和描述,其目的是对数据流程图中的各个元素做出详细的说明,使用数据字典为简单的建模项目。简而言之,数据字典是描述数据的信息集合,…

matlab新建数据字典及如何导入

一、如何创建 点击model explorer 依次点击file->new->Data dictionary 输入数据字典的名字 创建之后为一下界面&#xff1a; 点击 进行创建 右键save change 进行保存 没保存时候带星号 &#xff0c;保存之后信号消失。 二、如何导入 点击file—>model proper…

数据治理浅谈之数据字典

导读 数据字典作为数据治理整体体系中重要的一环&#xff0c;理解和使用数据字典尤为重要。本文将从数据字典概念、定义、结构、应用、治理等方面进行详细阐述。 01概念 数据字典&#xff08;Data Dictionary&#xff09;是一个容器&#xff0c;用于包含有组织定义和使用的所…

【数据库管理】⑩数据字典

1. 数据字典的概述 数据字典&#xff08;Data Dictionary&#xff09;是数据库管理系统中的一个重要组成部分&#xff0c;它是一个存储数据库元数据的集合&#xff0c;包含了数据库中所有对象的定义和描述信息。数据字典可以帮助用户了解数据库中的各种对象和数据结构&#xff…

List中remove()的用法

List remove的三种正确方法 1、倒序循环&#xff0c;因为list删除只会导致当前元素之后的元素位置发生改变&#xff0c;所以采用倒序可以保证前面的元素没有变化&#xff1b; for(int ilist.size()-1;i>0;i--){ list.remove(i); }2、顺序循环时&#xff0c;删除当前位置的…

JSP内置对象Session——setAttribute/getAttibute/removeAttribute

本文章代码使用了request内置对象&#xff0c;可先查看之前的文章学习。 前言 一、Session使用环境 二、常用方法 三.使用示例 1. 设计某系统登陆模块&#xff0c;该模块需满足以下条件&#xff1a; 2.login.jsp登录页面 2.check.jsp校验页面 3. success.jsp成功登录页面…

Iterator remove()详解

转自&#xff1a;http://blog.51cto.com/tianxingzhe/1693218 一、Iterator的API 关于Iterator主要有三个方法&#xff1a;hasNext()、next()、remove() hasNext:没有指针下移操作&#xff0c;只是判断是否存在下一个元素 next&#xff1a;指针下移&#xff…

VLAN命令行配置

VLNA配置任务概览 各配置任务间的逻辑关系&#xff1a; VLAN配置任务概览&#xff1a; 配置任务描述划分VLAN创建并划分VLAN&#xff0c;将没有二层互通需求的用户进行隔离&#xff0c;可增强网络的安全性、减少广播流量&#xff0c;同时也减少了广播风暴的产生。配置通过VLA…

VLAN配置命令

VLAN作用 分割广播域 VLAN分类 静态VLAN 基于端口划分 动态VLAN 基于MAC地址划分 VLAN范围 0-4095 共4096个vlanID 0与4095系统保留 1-4094可用 VLAN配置命令 VLAN基础配置代码 添加vlan&#xff08;2-4094&#xff09; vlan 10 删除vlan undo vlan 10 批量增加 vlan batch …

华为 -VLAN配置

按照实验拓扑图接线。 配置PC1&#xff1a;IP&#xff08;192.168.1.1/24&#xff09;,PC2:IP(192.168.2.1/24)。测试PC1和PC2的连通性&#xff1a;用PC1去PingPC2&#xff0c;查看Ping的结果&#xff0c;并解释原因。 PC1和PC2在不同的网段&#xff0c;不能ping通 配置PC1&…

华为交换机vlan配置

拓扑图&#xff1a; 配置命令&#xff1a; sw1: sys sys sw1 vlan 10 vlan 20 int e0/0/1 port link-type access port default vlan 10 int e0/0/2 port link-type access port default vlan 20 int e0/0/3 port link-type trunk port trunk allow vlan 10 20sw2: sys sy…

ensp交换机vlan配置

交换机 一、VLAN1、VLAN的概念2、优势3、VLAN的种类4、VLAN的范围5、VLAN标识6、IEEE802.1q工作原理帧格式 二、交换机命令配置1、华为交换机链路类型2、添加VLAN 一、VLAN 1、VLAN的概念 VLAN&#xff08;Virtual Local Area Network&#xff09;即虚拟局域网&#xff0c;是…

eNSP VLAN配置

按如上拓扑图进行配置&#xff1a; 1、按拓扑图标记&#xff0c;划分VLAN&#xff0c;分配固定IP 2、开启两个路由器的DHCP服务&#xff0c;分配所有客户机IP地址 3、实现全网互通 一、划分VLAN&#xff0c;分配固定IP 因为两个路由器连接不同的交换机&#xff0c;所以左右两…

华三vlan配置

基于MAC地址划分vlan 配置思路&#xff1a; 创建VLAN 100、VLAN 200。 配置Device A和Device C的上行端口为Trunk端口&#xff0c;并允许VLAN 100和VLAN 200的报文通过。 配置 Device B 的下行端口为Trunk端口&#xff0c;并允许VLAN 100和VLAN 200的报文通过&#xff1b;上行…