不在浮沙筑高台-demons
C++/OS/disassembly/Anti-virus
posts - 5,  comments - 5,  trackbacks - 0
 
  风格:为了让国外朋友也能看懂,代码中的注释全部用英文,而分析部分我用中文做详细解释, 如果您不习惯这种表达风格,或者有什么意见,欢迎向我反馈.


    前言:本文主要讨论现在网传很广的单线程函数hook 类CULHook 挂钩代码的致命漏洞以及设计缺陷,并给出我的解决方案.


   知识前提:您应该有windows 程序设计和C++的基本知识,以及对操作系统原理稍微了解.

     -----------人因相互学习而进步,若您发现文中有错误,请不吝赐教,能够告诉我,感恩您.

    once, I saw a cpp class that realized the API hook by inline assembly  in user mode. Maybe some of you have known this class that named "CULHook" too.
However, there are some problems existed by now. One problem is that  hook method doesn't work well on multiple threads condition. And another  problem
seems not to be clear but fatal !

  This is the original code of the class CULHook:             代码来自 <windows 程序设计> -王艳平         注: 注解见下面,可参考我的CSTAPIHook类的代码


#include <windows.h>

class CULHook
{
public:
    CULHook(LPSTR pszModName, LPSTR pszFuncName, PROC pfnHook);
    ~CULHook();
    void Unhook();
    void Rehook();
protected:
    PROC m_pfnOrig;        
    BYTE m_btNewBytes[8];
    BYTE m_btOldBytes[8];   
    HMODULE m_hModule;
};

CULHook::CULHook(LPSTR pszModName, LPSTR pszFuncName, PROC pfnHook)
{
    // jmp eax == 0xFF, 0xE0
    BYTE btNewBytes[8] = { 0xB8, 0x00, 0x00, 0x40, 0x00, 0xFF, 0xE0, 0x00 };
    memcpy(m_btNewBytes, btNewBytes, 8);
    *(DWORD *)(m_btNewBytes + 1) = (DWORD)pfnHook;
    m_hModule = ::LoadLibrary(pszModName);

    if(m_hModule == NULL)
    {
        m_pfnOrig = NULL;
        return;
    }
    m_pfnOrig = ::GetProcAddress(m_hModule, pszFuncName);
    if(m_pfnOrig != NULL)
    {
        DWORD dwOldProtect;
        MEMORY_BASIC_INFORMATION    mbi;                                         //1
        ::VirtualQuery( m_pfnOrig, &mbi, sizeof(mbi) );
        ::VirtualProtect(m_pfnOrig, 8, PAGE_READWRITE, &dwOldProtect);   //2
        memcpy(m_btOldBytes, m_pfnOrig, 8);
        ::WriteProcessMemory(::GetCurrentProcess(), (void *)m_pfnOrig, m_btNewBytes, sizeof(DWORD)*2, NULL);
        ::VirtualProtect(m_pfnOrig, 8, mbi.Protect, 0);
    }
}

CULHook::~CULHook()
{
    Unhook();
    if(m_hModule != NULL)
        ::FreeLibrary(m_hModule);
}

void CULHook::Unhook()
{
    if(m_pfnOrig != NULL)
    {
        DWORD dwOldProtect;
        MEMORY_BASIC_INFORMATION    mbi;
        ::VirtualQuery(m_pfnOrig, &mbi, sizeof(mbi));                               //1
        ::VirtualProtect(m_pfnOrig, 8, PAGE_READWRITE, &dwOldProtect);  //2

        ::WriteProcessMemory(::GetCurrentProcess(), (void *)m_pfnOrig,
            m_btOldBytes, sizeof(DWORD)*2, NULL);

        ::VirtualProtect(m_pfnOrig, 8, mbi.Protect, 0);
    }
}

void CULHook::Rehook()
{
    if(m_pfnOrig != NULL)
    {
        DWORD dwOldProtect;                                        
        MEMORY_BASIC_INFORMATION    mbi;                                         //1                       
        ::VirtualQuery( m_pfnOrig, &mbi, sizeof(mbi) );                               
        ::VirtualProtect(m_pfnOrig, 8, PAGE_READWRITE, &dwOldProtect);    //2
        ::WriteProcessMemory(::GetCurrentProcess(), (void *)m_pfnOrig, m_btNewBytes, sizeof(DWORD)*2, NULL);
        ::VirtualProtect(m_pfnOrig, 8, mbi.Protect, 0);
    }
}

 
一个事实:我用这个类挂钩TerminateProcess和Process32NextW函数时,目标进程崩溃(下面你将会看到在什么情况下会失败)
   凡是我标1的地方都有设计问题,凡是我标2的地方都有严重安全隐患。


   1, 首先我们先看一下这个类,有着很明显的设计问题,我们可以看看构造函数和ReHook成员函数,有着明显的代码重复,一种解决方案是,把挂钩功能放在一个成员函数中,在构造函数中调用即可。
   2, 凡是我标1 的地方完全没有必要,我们知道VirtualProtect 的最后一个参数可以返回原来的权限,写完内存后直接把 dwOldProtect再传给 VirtualProtect 以恢复页面权限属性,而无需
      再大费周章的得到 mbi 结构.
  3, 最不容易被发现的一点,也就是我标2的地方,也就是我们应该获取何种访问权限,代码中是PAGE_READWRITE, 这有问题吗?我们不能给教科书挑错吧,毕竟原书示例程序的却是用这个类   拦截了Ws2_32.dll中的winsock2的 一些函数. 但是,这句有问题,要弄清楚这个问题就必须清楚这个类的原理.


  原理: 通过修把要挂钩函数的前8个字节数据改为跳转到我们函数的机器指令(原来的8字节保存起来),然后在我们的钩子函数中执行我们的操作,我们要干预的动作全部结束后,再把原来       的8字节数据写回去,再调用原函数,为了再次拦截目标函数,原函数调用完后, 要再次把原函数前8个字节写成跳转到我们函数的机器指令,至于为什么是8字节? 我们来看看实现这个跳转的汇   编指令吧:


                mov eax,our_Hookfunction         //把我们函数地址装入到eax寄存器的指令占1字节 ,我们的函数地址占4字节,  即:0xB8, 0x00, 0x00, 0x00, 0x00 
                jmp eax                                  //jmp 指令码1字节, eax 寄存器号1字节 即: 0xFF, 0xE0, 


好了 1+4+2=7 , 按照对齐原则,最后一字节我们以0填充.
问题在哪里,我说过这个类是挂钩单线程函数的,怎么理解, 试想这种情况:当我们修改目标函数前8个字节时,在32位系统中,,要写完8字节(64位)得用两个存储周期,假如第一次写了32位数据后,系统发生线程换,如果新线程也要调用我们要挂钩的函数,因为这个函数我们只写了前4个字节,后4字节还没写,天知道会发生什么事。假如只有一个线程会调用我们的目标函数,则万事大吉。当然这个类本身就是挂钩单线程函数的,我在这里说明是因为有人人问过我这个问题。

好了既然搞清楚了原理,我们就言归正传,分析下面这种情况:假设我们要挂钩的目标进程中有D和W两个线程分别只会调用fun1,fun2两个函数,而fun1和fun2又都是dw.dll的导出函数,且在同一个页面中,现在我们用上面的类挂钩函数fun1,显然fun1是单线程(D)调用的,符合单线程安全,按理说应该会成功. 难道真相就是能成功挂钩吗,如果是,我这篇博客就没必要写了.根据上面类的代码我们来分析运行过程:首先我们把跳转到我们函数的机器指令写到fun1的前8个字节,首先代码中获取了PAGE_READWRITE权限,然后有了权限后开始写,若只写完前4个字节后,系统切换到线程W,假设W线程在这个时间片里没有调用fun2,而线程W不会调用fun1,等线程切回来之后我们写完剩下的4字节,如果是这样则挂钩成功,但是如果在写了前4字节切换到W时,W调用了fun2,会发生什么,这就是我本篇博文主要的焦点了,因为fun1和fun2是在同一个页面中,而VirtualProtect给该页面获取了PAGE_READWRITE权限,也许有人会认为只给fun1获取了PAGE_READWRITE 权限,而fun2权限没变,这是错误的,因为系统权限管理是以页为单位的,VirtualProtect 会给fun1所在的所有页都会获取PAGE_READWRITE 权限(如果fun1的代码横跨两个页面,则这两个页面都会被改为PAGE_READWRITE 权限),也就是说此时fun2的权限是PAGE_READWRITE 即只能读或写,而不能执行,那么fun2调用将会失败,产生非法操作,这时您的目标进程就被您成功的搞崩溃了.这也就解释了我们用这个类挂钩其他某些函数时,一挂钩,目标进程就崩溃. 还有一个问题就是如果目标进程是单线程的,那么挂钩还有没有可能会失败, 请读者思考,我把答案直接告诉你,有可能!如果您实在想不到为什么可以向我反馈,我在这里说出原因主要是想让您能够独立思考,授人以鱼不如授人以渔.


解决方案:在VirtualProtect指定要获取的操作权限时把PAGE_EXECUTE_WRITECOPY 或PAGE_EXECUTE_READWRITE,也就是加上EXECUTE可执行权限,就行了,到此我们的主要问题就解决了,但是别忘了你现在浏览的是Duwen的C++博客,所以我设计了CSTAPIHook类 意思就是 CPP Single Thread API Hook 的意思:


/************************************************************************

  Description :   SINGLE THREAD API Hook Class( CSTAPIHook)
  Notices      :   Copyright (c) Duwen
  TIME         :   4/27/2012
************************************************************************/

#ifndef _STAPIHOOK_
#define _STAPIHOOK_
#include<windows.h>

/*************************************************************
  NOTE: This C++ class is not thread safe. it's only allowed to hook SINGLE thread
  API.  once you using this class to hook multiple threads accessible API, it maybe
  lead to your whole system run abnormally.
************************************************************
*/

// CSTAPIHook (Single Thread API Hook)
class CSTAPIHook
{
public:
    CSTAPIHook(LPSTR pszModName, LPSTR pszFuncName, PROC pfnHook);
    ~CSTAPIHook();
    void Hook();// hook   
    void Unhook();// Cancel hook
private:
    PROC m_pfnOrig;              // Destination API address.
    BYTE m_btNewBytes[8];    // the new start 8B data of the original API .
    BYTE m_btOldBytes[8];        // the old start 8B data of the original API .
    HMODULE m_hModule;      // DLL module handle.
};
#endif 
#include "stdafx.h"
#include "STAPIHOOK.h"

CSTAPIHook::CSTAPIHook(LPSTR pszModName, LPSTR pszFuncName, PROC pfnHook)
{
    // jmp eax == 0xFF, 0xE0
    BYTE btNewBytes[8] = { 0xB8, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00 };
    memcpy(m_btNewBytes, btNewBytes, 8);
    //save our hook function address to btNewBtytes[1]~ btNewBtytes[4]
    *((DWORD *)(m_btNewBytes + 1)) = reinterpret_cast<DWORD>(pfnHook);

    // Loading dll module.
    m_hModule = ::LoadLibraryA(pszModName);
    if(m_hModule == NULL)
    {
        m_pfnOrig = NULL;
        return;
    }
    m_pfnOrig =(PROC)(::GetProcAddress(m_hModule, pszFuncName));

    // Revising the original  8B data of the hook_function start entry.
    if(m_pfnOrig != NULL)
        Hook();
}

void CSTAPIHook:: Hook()
{
    DWORD dwOldProtect=0;
    ::VirtualProtect((void *)m_pfnOrig, 8, PAGE_EXECUTE_WRITECOPY, &dwOldProtect);
     memcpy(m_btOldBytes,(void *) m_pfnOrig, 8);// Save the original 8B data.
   
// Write the new 8B data to the hook_function entry .
    ::WriteProcessMemory(::GetCurrentProcess(), (void *)m_pfnOrig, m_btNewBytes, 8, NULL);
    //Recover the page protection property.
    ::VirtualProtect((void*)m_pfnOrig,8, dwOldProtect, 0);

}


CSTAPIHook::~CSTAPIHook()
{
    Unhook();
    if(m_hModule != NULL)
        ::FreeLibrary(m_hModule);
}

void CSTAPIHook::Unhook()
{
    if(m_pfnOrig != NULL)
    {
        DWORD dwOldProtect;
        ::VirtualProtect(m_pfnOrig, 8, PAGE_EXECUTE_WRITECOPY, &dwOldProtect);
        // write the original 8B data
        ::WriteProcessMemory(::GetCurrentProcess(), (void *)m_pfnOrig, m_btOldBytes, 8, NULL);
        ::VirtualProtect(m_pfnOrig, 8, dwOldProtect , 0);
    }
}

/*********************END FILE******************************/

上面的代码我给您使用以及传播的权利,但我不希望以后有人说我是在那里抄的xx的,所以转载或使用请说明Copyright (c) Duwen,请您尊重原创,支持原创.也希望广大网友支持.
posted on 2012-05-08 20:41 demons 阅读(1706) 评论(0)  编辑 收藏 引用 所属分类: Program in Windows

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理



<2024年4月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

常用链接

留言簿(1)

随笔分类

随笔档案

文章分类

文章档案

搜索

  •  

最新评论

阅读排行榜

评论排行榜