zoukankan      html  css  js  c++  java
  • Windows下对函数打桩,及Linux类似技术

    一个简单的桩实现类:

    #define JMPCODE_LENGTH 5            //x86 平坦内存模式下,绝对跳转指令长度  
    #define JMPCMD_LENGTH  1            //机械码0xe9长度  
    #define JMPCMD         0xe9         //对应汇编的jmp指令  
    
    // 一个简化的打桩类的实现
    class XSimpleStub
    {
    public:
        explicit XSimpleStub(void* pOrigFunc, void* pNewFunc, bool need_lock_other_thread = false);
        ~XSimpleStub();
    
    private:
        // 源函数地址
        void * str_func_addr;
        // 是否打桩成功
        bool is_stub_succ;
        // 是否打桩成功
        bool need_lock_other_thread_;
        // 源指令数据的备份
        unsigned char str_instruct_back[JMPCODE_LENGTH];
    };

    函数就只有两个函数体,分别如下

    #include <tlhelp32.h>
    
    BOOL LockOtherThread()
    {
        DWORD dwCurrPid = GetCurrentProcessId();
        DWORD dwCurrTid = GetCurrentThreadId();
    
        HANDLE hThread = NULL;
        HANDLE hThreadSnap = NULL;
        THREADENTRY32 te32 = { 0 };
        te32.dwSize = sizeof(THREADENTRY32);
    
        // 遍历线程
        if (Thread32First(hThreadSnap, &te32))
        {
            do
            {
                if (te32.th32OwnerProcessID == dwCurrPid) {
                    if (te32.th32ThreadID != dwCurrTid){
                        // 获取句柄
                        hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te32.th32ThreadID);
                        if (NULL != hThread){
                            SuspendThread(hThread);
                        }
                        CloseHandle(hThread);
                    }
                }
            } while (Thread32Next(hThreadSnap, &te32));
        }
        CloseHandle(hThreadSnap);
    
        return TRUE;
    }
    
    BOOL UnlockOtherThread()
    {
        DWORD dwCurrPid = GetCurrentProcessId();
        DWORD dwCurrTid = GetCurrentThreadId();
    
        HANDLE hThread = NULL;
        HANDLE hThreadSnap = NULL;
        THREADENTRY32 te32 = { 0 };
        te32.dwSize = sizeof(THREADENTRY32);
    
        // 遍历线程
        if (Thread32First(hThreadSnap, &te32))
        {
            do
            {
                if (te32.th32OwnerProcessID == dwCurrPid) {
                    if (te32.th32ThreadID != dwCurrTid){
                        // 获取句柄
                        hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te32.th32ThreadID);
                        if (NULL != hThread){
                            ResumeThread(hThread);
                        }
                        CloseHandle(hThread);
                    }
                }
            } while (Thread32Next(hThreadSnap, &te32));
        }
        CloseHandle(hThreadSnap);
    
        return TRUE;
    }
    
    static void __inner_memcpy(unsigned char* pDest, unsigned char* pSrc, unsigned int count)
    {
        while(count > 0) {
            *pDest++ = *pSrc++;
            count --;
        }
    }
    
    XSimpleStub::XSimpleStub(void* pOrigFunc, void* pNewFunc, bool need_lock_other_thread):
        str_func_addr(pOrigFunc), is_stub_succ(false), need_lock_other_thread_(need_lock_other_thread)
    {
        // 源地址、目标地址需要进行一次判定
        if (nullptr != pOrigFunc && nullptr != pNewFunc)
        {
            DWORD   ProtectVar;              // 保护属性变量
            MEMORY_BASIC_INFORMATION MemInfo;    //内存分页属性信息
    
            // 取得对应内存的原始属性
            if (0 != VirtualQuery(pOrigFunc, &MemInfo, sizeof(MEMORY_BASIC_INFORMATION)))
            {
                // 如果需要锁住所有其他线程,则先执行锁定动作
                if (need_lock_other_thread) {
                    LockOtherThread();
                }
    
                // 修改页面为可写
                if(VirtualProtect(MemInfo.BaseAddress, MemInfo.RegionSize, PAGE_READWRITE, &MemInfo.Protect))
                {
                    // 备份原数据,防止自身需要使用memcpy,不能使用类似接口
                    __inner_memcpy((unsigned char*)str_instruct_back, (unsigned char*)pOrigFunc, JMPCODE_LENGTH);
    
                    // 修改目标地址指令为 jmp pDestFunc
                    *(unsigned char*)pOrigFunc = JMPCMD;                                      //拦截API,在函数代码段前面注入jmp xxx  
                    *(DWORD*)((unsigned char*)pOrigFunc + JMPCMD_LENGTH) = (DWORD)pNewFunc  - (DWORD)pOrigFunc - JMPCODE_LENGTH;  
    
                    // 改回原属性
                    VirtualProtect(MemInfo.BaseAddress, MemInfo.RegionSize, MemInfo.Protect, &ProtectVar);  
    
                    // 修改后,还需要刷新cache
                    FlushInstructionCache(GetCurrentProcess(), pOrigFunc, JMPCODE_LENGTH);
    
                    is_stub_succ = true;
                }
    
                // 如果需要锁住所有其他线程,则先执行锁定动作
                if (need_lock_other_thread) {
                    UnlockOtherThread();
                }
            }
        }
    }
    
    XSimpleStub::~XSimpleStub()
    {
        if (is_stub_succ)
        {
            DWORD   TempProtectVar;              //临时保护属性变量  
            MEMORY_BASIC_INFORMATION MemInfo;    //内存分页属性信息
    
            if (0 != VirtualQuery(str_func_addr, &MemInfo, sizeof(MEMORY_BASIC_INFORMATION)))
            {
                // 如果需要锁住所有其他线程,则先执行锁定动作
                if (need_lock_other_thread_) {
                    LockOtherThread();
                }
    
                // 修改页面为可写
                if(VirtualProtect(MemInfo.BaseAddress,MemInfo.RegionSize,  PAGE_READWRITE,&MemInfo.Protect))                            
                {
                    // 恢复代码段
                    __inner_memcpy((unsigned char*)str_func_addr, (unsigned char*)str_instruct_back, JMPCODE_LENGTH);
    
                    //改回原属性
                    VirtualProtect(MemInfo.BaseAddress,MemInfo.RegionSize,  MemInfo.Protect,&TempProtectVar);
    
                    // 修改后,还需要刷新cache
                    FlushInstructionCache(GetCurrentProcess(), str_func_addr, JMPCODE_LENGTH);
                }
    
                // 如果需要锁住所有其他线程,则先执行锁定动作
                if (need_lock_other_thread_) {
                    UnlockOtherThread();
                }
            }
        }
    
    }

    Linux下一样有类似技术,可以参考IBM的一个文档:

    https://www.ibm.com/developerworks/cn/linux/l-knldebug/index.html

    这其中的差别在跳转指针的设计上,Linux上,是使用了7个字节,并不需要计算原函数、新函数的地址距离

    整个替换流程的实现分为如下几个步骤:
    
    (1)    替换指令码: 
    b8 00 00 00 00 /*movl $0, $eax;这里的$0将被具体替换函数的地址所取代*/ 
    ff e0 /*jmp *$eax ;跳转函数*/ 
    将上述7个指令码存放在一个字符数组中: 
    replace_code[7]
    
    (2)    用替换函数的地址覆盖第一条指令中的后面8个0,并保留原来的指令码: 
    memcpy (orig_code, func, 7); /* 保留原函数的指令码 */ 
    *((long*)&replace_code[1])= (long) replace_func; /* 赋替换函数的地址 */ 
    memcpy (func, replace_code, 7); /* 用新的指令码替换原函数指令码 */
    
    (3)    恢复过程用保留的指令码覆盖原函数代码: 
    memcpy (func, orig_code, 7)

    Linux下,实现代码页属性修改使用函数接口:mprotect

     而相应的Linux下线程挂起、恢复使用如下接口:

    #include <signal.h>
    pthread_kill(ThreadID, SIGSTOP);  // suspend
    pthread_kill(ThreadID, SIGCONT);  // resume

    通过查看/proc/pid/task得知一个任务下的所有线程数

  • 相关阅读:
    第025讲:字典:当索引不好用时 | 课后测试题及答案
    第023、024讲:递归:这帮小兔崽子、汉诺塔 | 课后测试题及答案
    第022讲:函数:递归是神马
    第021讲:函数:lambda表达式
    第020讲:函数:内嵌函数和闭包
    第019讲:函数:我的地盘听我的
    第018讲:函数:灵活即强大
    第017讲:函数
    第016讲:序列!序列
    第015讲:字符串:格式化
  • 原文地址:https://www.cnblogs.com/eaglexmw/p/11556275.html
Copyright © 2011-2022 走看看