• 在已有软件加壳保护 下实现 Inline hook


    如写的不好请见谅,本人水平有限。

    个人简历及水平:。 http://www.cnblogs.com/hackdragon/p/3662599.html

    正常情况:

    接到一个项目实现对屏幕输出内容的获取,于是OD载入,发现是XX加壳保护,正常情况写代码采用jmp跳转到自己的代码处 采用前人使用的CHookApi_Jmp类 源码在http://www.cnblogs.com/showna/articles/850279.html。(我自己用的时候把CHookApi_Jmp修改成了CHookApi)当然我是截取目标程序的绘制函数TextOutA,所以代码如下:

    // 原函数声明
    //__gdi_entry WINGDIAPI BOOL  WINAPI TextOutA( __in HDC hdc, __in int x, __in int y, __in_ecount(c) LPCSTR lpString, __in int c);
    //全局初始化类
    CHookApi  m_HookTextOutA;
    //自己的函数
    BOOL  WINAPI FuncJMP_TextOutA(HDC hdc, int x, int y,LPCSTR lpString, int c)
    {
        m_HookTextOutA.SetHookOff();//关闭HOOK  实现自己调用原来的WINAPI
        {
            //做你想做的事情
        }
        int Func_Ret = TextOutA(hdc,x,y,lpString,c);
        m_HookTextOutA.SetHookOn();//开启HOOK   实现拦截下一次函数
        return Func_Ret;
    }
    //适当的位置初始化
    void Func_Init()//
    {
        m_HookTextOutA.Initialize(_T("Gdi32.dll"),"TextOutA",(FARPROC)Hook_FuncTextOutA);
        m_HookTextOutA.SetHookOn();
    }

    正常情况下已经完成了Inline hook,但是由于软件加壳保护了TextOutA函数的前5个字节,我们的jmp转移大法不行了,你修改完 他就给你修改回去,造成程序异常。

    寻找办法:

    于是OD往下跟踪,发现他继续调用的其他DLL也是软件保护下的,只有动态链接才不保护,本人比较懒不爱继续跟踪了,于是想了一下,他是保护前几个字节,他不能全部保护,于是往下看了看汇编代码(win7的GDI32.dll)。

    看到这里调用了下一个函数,那么他这里E8(CALL)后面也是4个字节,于是乎马上我想到自己也可以篡改他的这个地址到咱自己的函数中,鬼使神差的我看了下X版本的GDI32的代码

    发现一个问题 他们在TextOutA函数头处到E8的位置并不一样。具体来说

    WIN7的 089D-0878 = 0x25(偏移)

    XP的      BA7C-BA4F = 0x2D (偏移)

    2个的偏移量明显不一样了,这怎么办呢,如果在有其他版本的GDI32是不是也不一样呢。于是想到如下解决办法。

    解决问题:

    我们要实现inline hook 首先要知道我们要修改的内存地址,其次要知道原来的函数地址。下面这个函数来获取我们的地址。

    DWORD GetGDI32_TextOutAEx(DWORD &dwHook,DWORD &dwCall)
    {
        //取得TextOutA函数地址 也可以直接TextOutA
        DWORD dwGDI32TextOutA = (DWORD)GetProcAddress(GetModuleHandle(_T("Gdi32.dll")),"TextOutA");
        //转换下
        BYTE *lpBuf = (BYTE *)dwGDI32TextOutA;
        //每字节对比
        for(int i=0;i<0x30;i++)
        {
            DWORD dwAddress = (DWORD)(&lpBuf[i]);    //字节地址
            DWORD dwFind = *(DWORD*)(dwAddress);    //转换获取内容
            //为什么是0xE80875FF 这个可以在OD上看下内存内容 
            //由于这个函数的前一部分都是 push dword ptr ss:[ebp+0x8]  紧跟这就是CALL 
            //那么对应的内存二进制就是 0xE80875FF 了
            if(dwFind==0xE80875FF)
            {
                //当前地址+3那就是E8了
                dwHook = dwAddress+3;
                //由于“CALL到哪里”的计算方式是计算2个地址的差
                //所以我们要取CALL 后面的 4个字节的内容
                //当前地址+4那就是E8后面的了
                DWORD dwNow = dwAddress+4;
                //读出来这个值
                DWORD dwOff = *(DWORD*)(dwNow);
                //和当前的地址相加+(这个自己想一下吧为什么是+)
                //在+4是因为本身占用4个字节
                dwCall = dwNow+dwOff+4;
                //得出这个CALL实际的内存地址了。
                return 1;
            }
        }
        return 0;
    }

    给我们的CHookApi增加一个函数

    BOOL CHookApi::InitializeE8(FARPROC lpOldFunc, FARPROC lpNewFunc)
    {
        m_lpHookFunc = lpOldFunc;
        hProc = GetCurrentProcess();
        DWORD dwOldFlag;
        if(VirtualProtectEx(hProc,m_lpHookFunc,5,PAGE_READWRITE,&dwOldFlag)) 
        {  
            memcpy_s(m_OldFunc,5,m_lpHookFunc,5);
            //if(ReadProcessMemory(hProc,m_lpHookFunc,m_OldFunc,5,0))  
            {   
                if(VirtualProtectEx(hProc,m_lpHookFunc,5,dwOldFlag,&dwOldFlag))   
                {    
                    m_NewFunc[0]=0xE8;    
                    DWORD *pNewFuncAddress;    
                    pNewFuncAddress=(DWORD*)&m_NewFunc[1];    
                    *pNewFuncAddress=(DWORD)lpNewFunc-(DWORD)m_lpHookFunc-5;    
                    return TRUE;   
                }  
            }
        }
        return FALSE;
    }
    

    他原来是E9(JMP)那么我们要实现自己的CALL 那么我们用E8 其实我只是偷懒而已。因为原来就是E8 我们只要写入自己的函数的偏移就行了。

    实现过程:

    //全局初始化类
    CHookApi  m_HookTextOutAEx;
    //要挂钩的地址
    DWORD dwHook;
    //原来的CALL的地址
    DWORD dwCall;
    //自己的函数
    int WINAPI FuncJMP_TextOutEx(HDC hdc, int x, int y,int A,int B,LPCSTR lpString, int c,int C,int D)
    {
        //处理函数形式
        typedef int (__stdcall *MyTextOut )(HDC hdc, int x, int y,int A,int B,LPCSTR lpString, int c,int C,int D);
        //函数指针
        MyTextOut TextOutStr = (MyTextOut)dwCall;
        //调用
        int Ret = TextOutStr(hdc,x,y,A,B,lpString,c,C,D);
        //取得返回的地址
        DWORD *Ptr = (DWORD *)&D;
        if(Ptr[2]==0x12345678)//这里你可以加入以提升性能,根据返回的地址来判断是不是你要拦截的输出内容
        {
            //做你想做的事情
        }
        return Ret;
    }
    //适当的位置初始化
    void Func_Init2()//
    {
        GetGDI32_TextOutAEx(dwHook,dwCall);
        m_HookTextOutAEx.InitializeE8((FARPROC)dwHook,(FARPROC)FuncJMP_TextOutEx);
        m_HookTextOutAEx.SetHookOn();
    }

    函数参数的判断可以根据push情况自己来处理下,我随便处理下了。不知道多的几个参数干什么用的。

    最后总结:

    对于有保护的软件,如果不脱壳处理,那么可以曲线实现自己的目的。对于其他有内存校验的软件,则可根据它对应的调用其他未保护的API进行代码插入。算是写完了,也算是我cnblog的第二篇技术类文章吧。写的不对的地方欢迎指正啊。本人QQ:78486367

  • 相关阅读:
    05-java学习-循环结构
    04-java学习-选择结构
    03-java学习-基本数据类型-运算符-键盘接收用户输入
    A02-java学习-classpath配置-标识符-java变量类型
    A01-java学习环境准备
    20190215面试-C#操作外设-多线程-shocket
    装饰者模式
    状态模式
    DllImport学习
    网络编程(一)----基础知识、数据流套接字
  • 原文地址:https://www.cnblogs.com/hackdragon/p/3872883.html
Copyright © 2020-2023  润新知