zoukankan      html  css  js  c++  java
  • HOOK大法实现不修改程序代码给程序添加功能


    【文章标题】: HOOK大法实现不修改程序代码给程序添加功能
    【文章作者】: 0x18c0
    【软件名称】: Scylla
    【使用工具】: OD、Stub_PE、ResHacker
    【版权声明】: 本文原创于0x18c0, 转载请注明作者并保持文章的完整, 谢谢!

    菜鸟第一次发帖,写的不好的地方请各位多多包含

    本来是第三次培训的作业,要给Scylla加上弹窗
    @Kido 老师在上课的时候也演示了,没什么难度,但是按照上课的方法来搞,程序一运行就弹个窗口,太粗暴,强迫症完全不能忍好吧,所以我想在窗口上加个按钮,等点击按钮的时候再弹出窗口,这样就感觉友好多了。
    <ignore_js_op> 
    这么做其实也不难,方法就是找到窗口的过程函数,然后改写指令让程序先跳到我们自己写的函数里,最后再跳转回原窗口过程函数
    比如@苏紫方璇 大牛这篇帖子的方法,无源码给程序添加功能-记事本标题添加当前时间
    但是,这么搞太麻烦了,需要自己定位窗口过程,还要各种修改指令实现跳转,完全不适合我这种懒人
    但是,懒人有懒人的方法,俗话说的好,懒是推动科技进步的根本动力——0x18c0
    这里我已经忍不住要高喊一句——HOOK大法好!



    先介绍一下方法,我修改了Scylla的导入表,添加了一个我自己编写的DLL,然后在DLL的DllMain函数里下消息hook,每当按钮被按下时,WM_COMMAND消息就会被hook住,从而弹出窗口。



    基本方法介绍完了,下面介绍一下HOOK和DLL的基本知识
    一、HOOK
            Hook,字面意思就是钩子,是windows系统提供给开发者用来改变windows消息处理流程的编程接口。hook也分好几种,拿下面我要用到的消息hook来举例,每当我们按下一个窗口上的按钮时,系统就会捕获到一个WM_COMMAND消息,消息会被Windows系统传递给软件提前编写好的一个函数,这个函数叫做窗口过程函数,过程函数会根据不同的消息做出不同的处理。但是当我们想在消息被操作系统传递给过程函数处理之前先处理怎么办呢?这个时候就要用到消息hook了,操作系统提供给了开发者改变消息的能力,我们只需要调用相关的API,在相应的消息上设置hook钩子,并且告诉操作系统当消息被钩子钩住的时候因该怎么办,操作系统就会按照我们的意愿来处理消息。
            比如我们hook按钮被按下的消息WM_COMMAND,并且告诉操作系统,当钩子被触发的时候把消息传递给我自己编写的函数HookMSG,于是我们每次按下按钮,钩子都会被触发,并且操作系统会自动
    调用HookMSG函数。
    二、DLL
            DLL,动态链接库,可以导出变量和函数供其他程序调用,也可以包含资源文件,DLL有一个DllMain函数,每当DLL被链接时都会被调用,并且不同的调用原因可以有不同的处理办法。所以我们用Stub_PE将一个DLL添加到Scylla的倒入表里,每次Scylla运行时操作系统都回自动载入我们添加的DLL,并且运行DllMain里的代码,所以我们在DllMain里写上我们的hook代码,那么每次Scylla运行都会被自动hook了,这样就达到了我们的目的。



    方法和基本原理都说了,下面就应该开始实战
    一、添加按钮资源
    首先得给Scylla加一个按钮,用ResHacker打开Scylla,在对话框上要添加按钮的地方右键——>insert control,出现添加控件的窗口
    <ignore_js_op> 
    选择控件类型为BUTTON,caption这里填写需要显示在按钮上的字,最后别忘了给按钮添加ID,ID需要和其他控件不同以免冲突,这里我填写1099
    ,点击ok后界面上了出现我们添加的按钮,接下来不要忘了编译和保存
    <ignore_js_op> 


    二、编写DLL
    接下来我们编写DLL,我电脑上只有vs2013,所以我就用它了,你们也可以用其他编译器,只要可以编写Windows系统上的DLL就可以。
    1.新建工程
    选择win32项目,填写工程名,点击确定
    <ignore_js_op> 
    选择DLL和空项目,点击完成,项目创建完成,然后添加main.cpp到工程
    <ignore_js_op> 

    2.编写代码
    首先编写DllMain函数

    [C++] 纯文本查看 复制代码
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
    {
            switch (ul_reason_for_call)
            {
            case DLL_PROCESS_ATTACH:     //dll载入时执行
            {
                    g_hModule = hModule;     //保存句柄到全局变量
                    hhk = StartHook();
                    break;
            }
            case DLL_PROCESS_DETACH:     //dll卸载时执行
            {
                    EndHook();
                    break;
            }
            case DLL_THREAD_ATTACH:
            case DLL_THREAD_DETACH:
                    break;
            }
            return TRUE;
    }


    DllMain函数的ul_reason_for_call指明了函数被调用的原因,DLL_PROCESS_ATTACH指DLL被调用,DLL_PROCESS_DETACH指DLL被卸载,而case DLL_THREAD_ATTACH和case DLL_THREAD_DETACH分别表示线程被创建和销毁。我们在dll加载时调用StartHook函数,dll卸载时调用EndHook函数,下面我们看看这两个函数

    [C++] 纯文本查看 复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    HHOOK StartHook()
    {
            return SetWindowsHookEx(WH_CALLWNDPROC, HookProc, g_hModule, GetCurrentThreadId());//设置hook,类型为WH_CALLWNDPROC,过程函数为HookProc,hook当前线程
    }
     
    BOOL EndHook()
    {
            return UnhookWindowsHookEx(hhk);     //结束hook
    }


    这两个函数只是很简单的封装了俩个API,重点在于SetWindowsHookEx的参数设置,由于我们hook的是WM_COMMAND消息,所以我们选择hook类型为WH_CALLWNDPROC,表明当窗口过程函数被调用时触发hook,并且我们是hook当前线程,hook过程函数在dll里,所以第三个参数填dll模块句柄或者NULL都行,第四个参赛则直接利用GetCurrentThreadId()获取当前线程id
    这里有一篇文章介绍hook的类型,写的很不错,有兴趣的可以看看http://blog.csdn.net/whatday/article/details/8006225
    重点还在hook过程函数,我们需要在这里过滤出我们需要的消息,并编写功能代码

    [C++] 纯文本查看 复制代码
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam)
    {
            if (nCode >= 0)
            {
                    CWPSTRUCT* cwps = (CWPSTRUCT*)lParam;
     
                    if (WM_COMMAND == cwps->message)
                    {
                            INT wmId = LOWORD(cwps->wParam);
                            if (wmId == 1099)
                                    DialogBoxParam(g_hModule, (LPCTSTR)IDD_DIALOG, NULL, DlgProc, NULL);
                    }
            }
     
            return CallNextHookEx(hhk, nCode, wParam, lParam);
    }


    由于我们只需要添加按钮弹窗功能,所以我们只过滤WM_COMMAND消息,前面我们添加按钮资源的时候id填写的是1099,这里就派上用场了。
    这里我调用了DialogBoxParam函数来弹出对话框,弹出对话框需要添加对话框资源,并且编写过程函数。
    选择vs2013的菜单项->项目->添加资源->Dialog->新建
    <ignore_js_op> 
    vs中出现我们新建的对话框资源
    <ignore_js_op> 
    我就不编辑资源了,直接开始编写对话框的过程函数,这里我没有添加任何功能代码

    [C++] 纯文本查看 复制代码
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    BOOL CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
    {
            switch (msg)
            {
            case WM_CLOSE:
                    EndDialog(hDlg, NULL);
            }
     
            return FALSE;
    }


    到此为止,代码编写工作就完成了,但是这个dll没有一个导出函数,我们没法把它加到Scylla的导入表里,不过我们可以写一个空的导出函数

    [C++] 纯文本查看 复制代码
    1
    2
    3
    4
    __declspec(dllexport) void EmptyFunc()
    {
            //Do nothing
    }


    3.编译dll
    接下来我们编译dll,将工具条上的编译选项设为release,点击编译。
    <ignore_js_op> 
    如果你用的也是vs2013,这里需要修改两个地方的设置,否则编译出来的dll在xp上不能运行
    VS2013菜单->项目->属性->配置属性->常规->平台工具集->Visual Studio 2013 - Windows XP (v120_xp)
    VS2013菜单->项目->属性->配置属性->c/c++->代码生成->运行库->多线程 (/MT)
    在项目文件夹里找到HookMSG.dll,拷贝到Scylla目录下。

    三、修改Scylla导入表
    Stub_PE载入Scylla,选择“函数”选项卡,右键添加函数,选择HookMSG.dll,选择EmptyFunc函数,确定添加并保存。
    <ignore_js_op> 



    运行一下看结果,点击about按钮,对话框成功弹出,大功告成。
    <ignore_js_op> 


    写在最后:
    其实整个过程没有什么技术含量,懂Windows编程的人看一眼就懂,但是作为新手弄这些东西还是有点难,整个过程当中我也是遇到各种问题,不过结果总算是好的
    发这篇帖子的目的是希望与大家共勉,同时分享一下成功的喜悦,写的不好的地方希望大家多多包涵
    最后感谢论坛提供这个学习的机会,也感谢各位讲师@Hmily @Kido 的指导
    同时附上HookMSG.dll的完整源代码,链接:http://pan.baidu.com/s/1c0GvFOW  密码: v7um
    ps:其实Scylla是开源软件,想要汉化或者改界面的可以下载源代码自己编译,https://github.com/NtQuery/Scylla

  • 相关阅读:
    聚簇索引与非聚簇索引(二级索引)的区别
    swoole介绍
    什么是mysql执行计划
    php-fpm浅析
    字段设计规范
    mysql排序规则utf8_genera_ci和utf8_bin的区别
    chrome 麦克风被禁用
    获取地址栏参数
    vue 打包去掉console debugger
    Vue less全局变量预处理加载
  • 原文地址:https://www.cnblogs.com/h2zZhou/p/8601395.html
Copyright © 2011-2022 走看看