您现在的位置是:首页 >其他 >API Hook技术实现记事本程序保存时添加水印网站首页其他

API Hook技术实现记事本程序保存时添加水印

神使墨丘利 2024-06-15 00:01:02
简介API Hook技术实现记事本程序保存时添加水印

API Hook技术原理

API Hook技术是一种在运行时修改API(应用程序接口)的行为方式。API是软件开发中常用的接口,它定义了不同模块之间的交互方式和数据格式等。通过API Hook技术,攻击者可以拦截并篡改目标程序使用的API函数调用,实现对程序行为的控制。常见的方法包括插入Hook代码注入到目标程序中、实现DLL劫持或直接修改内存中的函数指针等。该技术常用于恶意代码编写和各类辅助工具开发等领域。
由于API Hook技术具有一定的危险性,可以用于病毒、恶意软件等攻击手段,因此在使用API Hook技术时需要谨慎使用。尽管API Hook技术常常被用于恶意目的,但它也有着广泛的用途,例如反病毒软件、系统信息收集和监测、游戏内存分析等都会涉及到此类技术。

API Hook技术实现模版

  1. 设置一个钩子函数。
  2. 循环监视系统消息。
  3. 设置回调函数,主要用于处理指定事件到来是所进行的操作。
  4. 释放钩子

记事本添加水印思路

  1. 实时监控键盘事件。
  2. 如果用户同时按下ctrl+s,表示有保存事件发生。
  3. 判断此时是否有记事本开启。
  4. 如果有,则向记事本中写入一些“水印”数据。

注:本项目使用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的分析:

  1. 使用SetWindowsHookEx方法创建一个键盘钩子,由于是监控键盘事件的,所以函数第一个参数设置为WH_KEYBOARD_LL,第二个参数是我们自己写的钩子回调函数,第三个参数固定写法,第四个参数指定钩子监视的进程,如果设置为0(NULL),则表示监视所有进程。
  2. 判断钩子设置是否成功。
  3. 使用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)
具体参数见官方网站

  1. 使用string 字符流记录按键记录
  2. 创建一个键盘钩子结构体KBDLLHOOKSTRUCT* p = (KBDLLHOOKSTRUCT*)lParam;
  3. 判断参数nCode是否接受了系统消息,如果有消息传来,则获取当前窗口,与上一次打开的窗口进行比对,更新窗口值。
  4. 有了系统消息还不够,我们还需要判断系统消息是否是键盘消息,即判断p->vkCode,如果有键盘消息,则使用InputCode函数获取键盘的输入。
  5. kbinput数组记录了上一次输入于本次的输入,如果上一次输入是ctrl,本次输入是s,则表示用户正在进行保存操作。执行AddWaterMark函数
  6. 记住需要形成钩子链,否则这一个钩子一直占用资源,因此该函数返回值一般都是固定写法。
    该部分代码:
    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);

如果成功找到,则返回记事本窗口的句柄,反之返回空。

向记事本中添加水印

到这一步,我们已经成功判断是记事本进程在进行文件保存操作。现在只需要向记事本中写入东西就可以啦。

  1. 使用SetForegroundWindow方法将记事本设置为活动窗口
  2. 使用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!”水印。
在这里插入图片描述

完整代码下载

完整项目代码:https://gitee.com/codestorebase/hook.git

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。