您现在的位置是:首页 >其他 >API Hook技术实现记事本程序保存时添加水印网站首页其他
API Hook技术实现记事本程序保存时添加水印
API Hook技术实现记事本程序保存时添加水印
API Hook技术原理
API Hook技术是一种在运行时修改API(应用程序接口)的行为方式。API是软件开发中常用的接口,它定义了不同模块之间的交互方式和数据格式等。通过API Hook技术,攻击者可以拦截并篡改目标程序使用的API函数调用,实现对程序行为的控制。常见的方法包括插入Hook代码注入到目标程序中、实现DLL劫持或直接修改内存中的函数指针等。该技术常用于恶意代码编写和各类辅助工具开发等领域。
由于API Hook技术具有一定的危险性,可以用于病毒、恶意软件等攻击手段,因此在使用API Hook技术时需要谨慎使用。尽管API Hook技术常常被用于恶意目的,但它也有着广泛的用途,例如反病毒软件、系统信息收集和监测、游戏内存分析等都会涉及到此类技术。
API Hook技术实现模版
- 设置一个钩子函数。
- 循环监视系统消息。
- 设置回调函数,主要用于处理指定事件到来是所进行的操作。
- 释放钩子
记事本添加水印思路
- 实时监控键盘事件。
- 如果用户同时按下ctrl+s,表示有保存事件发生。
- 判断此时是否有记事本开启。
- 如果有,则向记事本中写入一些“水印”数据。
注:本项目使用C++ MFC中基于对话框应用程序类型实现
在OnInitDialog函数中添加SetWindowText(_T(“Hook!”));实现对话框添加名字
界面:
第一个APH Hook技术之键盘记录
设置键盘钩子,实时监控键盘事件
创建项目,安放两个button,一个用来建立钩子,一个用来释放钩子。
点开两个button的后台代码:
void CHookDlg::OnBnClickedButton3()
{
// TODO: 在此添加控件通知处理程序代码
}
void CHookDlg::OnBnClickedButton4()
{
// TODO: 在此添加控件通知处理程序代码
}
我们需要再这两个函数中加入实现的功能:
void CHookDlg::OnBnClickedButton3()
{
// TODO: 在此添加控件通知处理程序代码
if (!HookKeyBoard())
{
AfxMessageBox(L"StartHook失败!");
}
}
void CHookDlg::OnBnClickedButton4()
{
// TODO: 在此添加控件通知处理程序代码
UnhookWindowsHookEx(KeyboardHook);
}
由于程序整体功能分块的原因,将钩子函数HookKeyBoard另单独处理。
钩子函数HookKeyBoard的分析:
- 使用SetWindowsHookEx方法创建一个键盘钩子,由于是监控键盘事件的,所以函数第一个参数设置为WH_KEYBOARD_LL,第二个参数是我们自己写的钩子回调函数,第三个参数固定写法,第四个参数指定钩子监视的进程,如果设置为0(NULL),则表示监视所有进程。
- 判断钩子设置是否成功。
- 使用GetMessage方法循环获取系统消息
以上三部分基本上都是固定写法,关键是Hook函数的书写。
这一部分代码如下:
/********************************************************
函数作用:设置键盘钩子
参数:无
返回值:是否hook成功
*********************************************************/
BOOL HookKeyBoard()
{
//设置标志位,用于判断hook是否成功
BOOL flag = FALSE;
KeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL,
Hook, // 回调函数地址
GetModuleHandle(NULL),
NULL
);
// 如果SetWindowsHookEx 失败
if (!KeyboardHook)
AfxMessageBox(L"SetWindowsHookEx() failed!");
else
{
// 统一初始化
MSG Msg{};
//循环获取系统消息
while (GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
flag = TRUE;
}
return flag;
}
在回调函数Hook中判断键盘是否有ctrl+s按下
Hook回调函数固定写法LRESULT CALLBACK Hook(int nCode, WPARAM wParam, LPARAM lParam)
具体参数见官方网站
- 使用string 字符流记录按键记录
- 创建一个键盘钩子结构体KBDLLHOOKSTRUCT* p = (KBDLLHOOKSTRUCT*)lParam;
- 判断参数nCode是否接受了系统消息,如果有消息传来,则获取当前窗口,与上一次打开的窗口进行比对,更新窗口值。
- 有了系统消息还不够,我们还需要判断系统消息是否是键盘消息,即判断p->vkCode,如果有键盘消息,则使用InputCode函数获取键盘的输入。
- kbinput数组记录了上一次输入于本次的输入,如果上一次输入是ctrl,本次输入是s,则表示用户正在进行保存操作。执行AddWaterMark函数
- 记住需要形成钩子链,否则这一个钩子一直占用资源,因此该函数返回值一般都是固定写法。
该部分代码:
Hook函数:
/********************************************************
函数作用:钩子回调
参数:nCode,wParam,lParam
返回值:是否hook成功
*********************************************************/
LRESULT CALLBACK Hook(int nCode, WPARAM wParam, LPARAM lParam)
{
ofstream tofile(fileName, ios::out | ios::app);
string outPut;
stringstream ssTemp; // string 字符流
//固定写法
KBDLLHOOKSTRUCT* p = (KBDLLHOOKSTRUCT*)lParam;
//如果触发了系统消息,即有系统消息产生
if (nCode == HC_ACTION)
{
// 返回前台窗口,获得当前窗口
currentWindow = GetForegroundWindow();
//如果当前有键盘消息的窗口不是我们之前的窗口
if (currentWindow != lastWindow)
{
// 使用GetWindowTextA函数获取当前窗口的名字
int c = GetWindowTextA(GetForegroundWindow(), cWindow, sizeof(cWindow));
ssTemp << "当前窗口是:" << cWindow << endl;
tofile << ssTemp.str();
lastWindow = currentWindow;
}
//如果有按键按下,p->vkCode显示的是ASCII码值
if (p->vkCode)
{
ssTemp.clear();
//input用于记录当前键盘的输入
string input = InputCode(p->vkCode);
//**************处理按键开始******************
if (kbinput[0] == "")
kbinput[0] = kbinput[1] = input;
else
{
kbinput[0] = kbinput[1];
kbinput[1] = input;
}
//如果按下ctrl+s,表示进行保存操作,此时可以添加水印
if (kbinput[0] == "[CTRL]" && kbinput[1] == "S")
{
AddWaterMark();
}
//**************处理按键结束******************
ssTemp << "键盘输入的字母是:" << input << endl;;
tofile << ssTemp.str();
}
}
tofile.close();
// hook链
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
InputCode函数:
/********************************************************
函数作用:将获得键盘消息转换为字符
参数: DWORD code 获得的键盘消息
返回值:转换的字符
*********************************************************/
string InputCode(DWORD code)
{
string key;
switch (code)
{
//S的ASCII码值用十六进制表示是0x53
case 0x53: key="S"; break;
case VK_LCONTROL: key = "[CTRL]"; break;
case VK_RCONTROL: key = "[CTRL]"; break;
default: key = "[UNK-KEY]";
}
return key;
}
判断是否有记事本进程开启
使用FindWindowEx方法即可指定第三个参数是我们需要寻找的进程名字。由于我们需要寻找的进程是记事本,因此名字指定为Notepad。
char name[] = "Notepad";
HWND hWnd = FindWindowEx(NULL, NULL, change(name), NULL);
如果成功找到,则返回记事本窗口的句柄,反之返回空。
向记事本中添加水印
到这一步,我们已经成功判断是记事本进程在进行文件保存操作。现在只需要向记事本中写入东西就可以啦。
- 使用SetForegroundWindow方法将记事本设置为活动窗口
- 使用SendInput方法向记事本中写入我们想要的“水印”,这里写入的“水印”是“The txt has been hooked!”。
该部分代码如下:
/********************************************************
函数作用:在记事本中添加水印
参数:无
返回值:无
*********************************************************/
void AddWaterMark()
{
char name[] = "Notepad";
HWND hWnd = FindWindowEx(NULL, NULL, change(name), NULL);
if (hWnd)
{
//将记事本设置为活动窗口
if (SetForegroundWindow(hWnd))
{
INPUT input[25];
input[0].type = INPUT_KEYBOARD;
input[0].ki.wVk = 0;
input[0].ki.wScan = '
';
input[0].ki.dwFlags = KEYEVENTF_UNICODE;
input[0].ki.time = 0;
input[0].ki.dwExtraInfo = 0;
input[1].type = INPUT_KEYBOARD;
input[1].ki.wVk = 0;
input[1].ki.wScan = 'T';
input[1].ki.dwFlags = KEYEVENTF_UNICODE;
input[1].ki.time = 0;
input[1].ki.dwExtraInfo = 0;
input[2].type = INPUT_KEYBOARD;
input[2].ki.wVk = 0;
input[2].ki.wScan = 'h';
input[2].ki.dwFlags = KEYEVENTF_UNICODE;
input[2].ki.time = 0;
input[2].ki.dwExtraInfo = 0;
input[3].type = INPUT_KEYBOARD;
input[3].ki.wVk = 0;
input[3].ki.wScan = 'e';
input[3].ki.dwFlags = KEYEVENTF_UNICODE;
input[3].ki.time = 0;
input[3].ki.dwExtraInfo = 0;
input[4].type = INPUT_KEYBOARD;
input[4].ki.wVk = 0;
input[4].ki.wScan = ' ';
input[4].ki.dwFlags = KEYEVENTF_UNICODE;
input[4].ki.time = 0;
input[4].ki.dwExtraInfo = 0;
input[5].type = INPUT_KEYBOARD;
input[5].ki.wVk = 0;
input[5].ki.wScan = 't';
input[5].ki.dwFlags = KEYEVENTF_UNICODE;
input[5].ki.time = 0;
input[5].ki.dwExtraInfo = 0;
input[6].type = INPUT_KEYBOARD;
input[6].ki.wVk = 0;
input[6].ki.wScan = 'x';
input[6].ki.dwFlags = KEYEVENTF_UNICODE;
input[6].ki.time = 0;
input[6].ki.dwExtraInfo = 0;
input[7].type = INPUT_KEYBOARD;
input[7].ki.wVk = 0;
input[7].ki.wScan = 't';
input[7].ki.dwFlags = KEYEVENTF_UNICODE;
input[7].ki.time = 0;
input[7].ki.dwExtraInfo = 0;
input[8].type = INPUT_KEYBOARD;
input[8].ki.wVk = 0;
input[8].ki.wScan = ' ';
input[8].ki.dwFlags = KEYEVENTF_UNICODE;
input[8].ki.time = 0;
input[8].ki.dwExtraInfo = 0;
input[9].type = INPUT_KEYBOARD;
input[9].ki.wVk = 0;
input[9].ki.wScan = 'h';
input[9].ki.dwFlags = KEYEVENTF_UNICODE;
input[9].ki.time = 0;
input[10].type = INPUT_KEYBOARD;
input[10].ki.wVk = 0;
input[10].ki.wScan = 'a';
input[10].ki.dwFlags = KEYEVENTF_UNICODE;
input[10].ki.time = 0;
input[10].ki.dwExtraInfo = 0;
input[11].type = INPUT_KEYBOARD;
input[11].ki.wVk = 0;
input[11].ki.wScan = 's';
input[11].ki.dwFlags = KEYEVENTF_UNICODE;
input[11].ki.time = 0;
input[11].ki.dwExtraInfo = 0;
input[12].type = INPUT_KEYBOARD;
input[12].ki.wVk = 0;
input[12].ki.wScan = ' ';
input[12].ki.dwFlags = KEYEVENTF_UNICODE;
input[12].ki.time = 0;
input[12].ki.dwExtraInfo = 0;
input[12].ki.dwExtraInfo = 0;
input[13].type = INPUT_KEYBOARD;
input[13].ki.wVk = 0;
input[13].ki.wScan = 'b';
input[13].ki.dwFlags = KEYEVENTF_UNICODE;
input[13].ki.time = 0;
input[13].ki.dwExtraInfo = 0;
input[14].type = INPUT_KEYBOARD;
input[14].ki.wVk = 0;
input[14].ki.wScan = 'e';
input[14].ki.dwFlags = KEYEVENTF_UNICODE;
input[14].ki.time = 0;
input[14].ki.dwExtraInfo = 0;
input[15].type = INPUT_KEYBOARD;
input[15].ki.wVk = 0;
input[15].ki.wScan = 'e';
input[15].ki.dwFlags = KEYEVENTF_UNICODE;
input[15].ki.time = 0;
input[15].ki.dwExtraInfo = 0;
input[16].type = INPUT_KEYBOARD;
input[16].ki.wVk = 0;
input[16].ki.wScan = 'n';
input[16].ki.dwFlags = KEYEVENTF_UNICODE;
input[16].ki.time = 0;
input[16].ki.dwExtraInfo = 0;
input[17].type = INPUT_KEYBOARD;
input[17].ki.wVk = 0;
input[17].ki.wScan = ' ';
input[17].ki.dwFlags = KEYEVENTF_UNICODE;
input[17].ki.time = 0;
input[17].ki.dwExtraInfo = 0;
input[18].type = INPUT_KEYBOARD;
input[18].ki.wVk = 0;
input[18].ki.wScan = 'h';
input[18].ki.dwFlags = KEYEVENTF_UNICODE;
input[18].ki.time = 0;
input[18].ki.dwExtraInfo = 0;
input[19].type = INPUT_KEYBOARD;
input[19].ki.wVk = 0;
input[19].ki.wScan = 'o';
input[19].ki.dwFlags = KEYEVENTF_UNICODE;
input[19].ki.time = 0;
input[19].ki.dwExtraInfo = 0;
input[20].type = INPUT_KEYBOARD;
input[20].ki.wVk = 0;
input[20].ki.wScan = 'o';
input[20].ki.dwFlags = KEYEVENTF_UNICODE;
input[20].ki.time = 0;
input[20].ki.dwExtraInfo = 0;
input[21].type = INPUT_KEYBOARD;
input[21].ki.wVk = 0;
input[21].ki.wScan = 'k';
input[21].ki.dwFlags = KEYEVENTF_UNICODE;
input[21].ki.time = 0;
input[21].ki.dwExtraInfo = 0;
input[22].type = INPUT_KEYBOARD;
input[22].ki.wVk = 0;
input[22].ki.wScan = 'e';
input[22].ki.dwFlags = KEYEVENTF_UNICODE;
input[22].ki.time = 0;
input[22].ki.dwExtraInfo = 0;
input[23].type = INPUT_KEYBOARD;
input[23].ki.wVk = 0;
input[23].ki.wScan = 'd';
input[23].ki.dwFlags = KEYEVENTF_UNICODE;
input[23].ki.time = 0;
input[23].ki.dwExtraInfo = 0;
input[24].type = INPUT_KEYBOARD;
input[24].ki.wVk = 0;
input[24].ki.wScan = '!';
input[24].ki.dwFlags = KEYEVENTF_UNICODE;
input[24].ki.time = 0;
input[24].ki.dwExtraInfo = 0;
SendInput(ARRAYSIZE(input), input, sizeof(INPUT));
}
else
{
AfxMessageBox(L"将记事本设置为活动窗口失败!");
}
}
else
AfxMessageBox(L"获取记事本句柄失败!");
}
实现效果
运行程序,打开一个记事本,任意输入一些东西,点击ctrl+s进行保存,记事本中出现“The txt has been hooked!”水印。