• 【Hook技术】实现从"任务管理器"中保护进程不被关闭 + 附带源码 + 进程保护知识扩展


    【Hook技术】实现从"任务管理器"中保护进程不被关闭 + 附带源码 + 进程保护知识扩展

    公司有个监控程序涉及到进程的保护问题,需要避免用户通过任务管理器结束掉监控进程,这里使用了HOOK技术,通过Hook OperProcess来实现进程的保护.

    正常的结束进程的流程是(应用层)

     a.OpenProcess 打开进程,获取进程的句柄.

     b.将a获取的进程句柄传递给TerminateProcess,最后由TermianteProcess来完成进程的关闭.

    ps:TerminateProcess又会调用系统的NtTerminateProcess,然后逐步深入内核层,最终调用内核API完成进程的关闭和进程相关资源的释放.

    (在应用层大多数进程的关闭都是走上面流程的,包括任务管理器,所以我们知道在Opernprocess和TerminateProcess间,我们只要Hook OpenProcess,让程序无法打开进程获取到句柄就可以了,那样TerminateProcess调用自然也就失败了)

    HANDLE WINAPI OpenProcess(
      _In_  DWORD dwDesiredAccess,
      _In_  BOOL bInheritHandle,
      _In_  DWORD dwProcessId      //进程ID,我们所关注的
    );

    由OpenProcess函数原型我们知道,第三个参数dwProcessId标识要打开进程的ID,而我们可以通过拦截OpenProcess,然后判断dwProcess是否为保护的进程ID.

    例如:我们自定义的Hook_OpenProcess

    复制代码
    HANDLE WINAPI Hook_OpenProcess(DWORD dwDesiredAccess,BOOL bInheritHandle,DWORD dwProcessId)
    {
        //判断打开进程的权限和PID,其中lpData->dwProcessId存储我们要保护的进程ID
        if(dwDesiredAccess == PROCESS_TERMINATE && dwProcessId == lpData->dwProcessId)
        { 
                    //为保护的进程直接返回NULL给TerminateProcess
            return NULL;
        }
            
           
        return OpenProcess(dwDesiredAccess,bInheritHandle,dwProcessId);
    }
    复制代码

    定制好了自己的OpenProcess的函数,接下来我们要做的就是如何将原函数(OpenProcess)替换为我们自己的Hook_OpenProcess,只有程序在调用OpenProcess时候先走我们的Hook_OpenProcess那么保护才能顺序的进行. 而替换的主要方法主要有:

    1. 通过Patch  IAT表,要求对PE文件格式熟悉

    2. 通过修改原函数的入口处几个字节,实现跳转到我们定制的函数,这种技术我们称为inline hook,主要是通过修改入口前5个字节或者7个字节,或者patch函数的中部代码,也可以patch尾部.

    我们这里用的是第一种方法,这里HOOK类,使用的《核心编程》中的CAPIHook,例如:

    复制代码
    class QHookSrv
    {
    public:
       // Hook a function in all modules
       QHookSrv(PSTR pszCalleeModName, PSTR pszFuncName, PROC pfnHook, 
          BOOL fExcludeAPIHookMod);
    
       // Unhook a function from all modules
       ~QHookSrv();
    
       // Returns the original address of the hooked function
       operator PROC() { return(m_pfnOrig); }
    
    private:
       static PVOID sm_pvMaxAppAddr; // Maximum private memory address
       static QHookSrv* sm_pHead;    // Address of first object
       QHookSrv* m_pNext;            // Address of next  object
    
       PCSTR m_pszCalleeModName;     // Module containing the function (ANSI)
       PCSTR m_pszFuncName;          // Function name in callee (ANSI)
       PROC  m_pfnOrig;              // Original function address in callee
       PROC  m_pfnHook;              // Hook function address
       BOOL  m_fExcludeAPIHookMod;   // Hook module w/CAPIHook implementation?
    
    private:
       // Replaces a symbol's address in a module's import section
       static void WINAPI ReplaceIATEntryInAllMods(PCSTR pszCalleeModName, 
          PROC pfnOrig, PROC pfnHook, BOOL fExcludeAPIHookMod);
    
       // Replaces a symbol's address in all module's import sections
       static void WINAPI ReplaceIATEntryInOneMod(PCSTR pszCalleeModName, 
          PROC pfnOrig, PROC pfnHook, HMODULE hmodCaller);
    };
    复制代码

    QHookSrv实现代码:

     View Code

    PVOID QHookSrv::sm_pvMaxAppAddr = NULL;
    const BYTE cPushOpCode = 0x68; // The PUSH opcode on x86 platforms

    QHookSrv* QHookSrv::sm_pHead = NULL;

    QHookSrv::QHookSrv(PSTR pszCalleeModName, PSTR pszFuncName, PROC pfnHook, BOOL fExcludeAPIHookMod)
    {
    if(sm_pvMaxAppAddr == NULL)
    {
    SYSTEM_INFO si;
    GetSystemInfo(&si);
    sm_pvMaxAppAddr = si.lpMaximumApplicationAddress;
    }

    m_pNext = sm_pHead; // The next node was at the head
    sm_pHead = this; // This node is now at the head

    // Save information about this hooked function
    m_pszCalleeModName = pszCalleeModName;
    m_pszFuncName = pszFuncName;
    m_pfnHook = pfnHook;
    m_fExcludeAPIHookMod = fExcludeAPIHookMod;
    m_pfnOrig = GetProcAddress(GetModuleHandleA(pszCalleeModName), m_pszFuncName);

    if (m_pfnOrig > sm_pvMaxAppAddr) {
    // The address is in a shared DLL; the address needs fixing up
    PBYTE pb = (PBYTE) m_pfnOrig;
    if (pb[0] == cPushOpCode) {
    // Skip over the PUSH op code and grab the real address
    PVOID pv = * (PVOID*) &pb[1];
    m_pfnOrig = (PROC) pv;
    }
    }

    // Hook this function in all currently loaded modules
    ReplaceIATEntryInAllMods(m_pszCalleeModName, m_pfnOrig, m_pfnHook,
    m_fExcludeAPIHookMod);
    }

    QHookSrv::~QHookSrv()
    {
    // Unhook this function from all modules
    ReplaceIATEntryInAllMods(m_pszCalleeModName, m_pfnHook, m_pfnOrig, m_fExcludeAPIHookMod);

    // Remove this object from the linked list
    QHookSrv* p = sm_pHead;
    if (p == this) { // Removing the head node
    sm_pHead = p->m_pNext;
    } else {

    BOOL fFound = FALSE;

    // Walk list from head and fix pointers
    for (; !fFound && (p->m_pNext != NULL); p = p->m_pNext) {
    if (p->m_pNext == this) {
    // Make the node that points to us point to the our next node
    p->m_pNext = p->m_pNext->m_pNext;
    break;
    }
    }
    }
    }

    static HMODULE ModuleFromAddress(PVOID pv)
    {
    MEMORY_BASIC_INFORMATION mbi;
    return((VirtualQuery(pv, &mbi, sizeof(mbi)) != 0)
    ? (HMODULE) mbi.AllocationBase : NULL);
    }

    void QHookSrv::ReplaceIATEntryInAllMods(PCSTR pszCalleeModName, PROC pfnCurrent, PROC pfnNew, BOOL fExcludeAPIHookMod)
    {
    HMODULE hmodThisMod = fExcludeAPIHookMod ? ModuleFromAddress(ReplaceIATEntryInAllMods) : NULL;
    HANDLE m_hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());
    if(m_hSnapshot != INVALID_HANDLE_VALUE)
    {
    MODULEENTRY32 me = { sizeof(me) };
    for (BOOL fOk = Module32First(m_hSnapshot, &me); fOk; fOk = Module32Next(m_hSnapshot, &me))
    {
    // NOTE: We don't hook functions in our own module
    if (me.hModule != hmodThisMod)
    {
    // Hook this function in this module
    ReplaceIATEntryInOneMod(pszCalleeModName, pfnCurrent, pfnNew, me.hModule);
    }
    }
    CloseHandle(m_hSnapshot);
    }
    }

    void QHookSrv::ReplaceIATEntryInOneMod(PCSTR pszCalleeModName,
    PROC pfnCurrent, PROC pfnNew, HMODULE hmodCaller) {

    // Get the address of the module's import section
    ULONG ulSize;
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(hmodCaller, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize);

    if (pImportDesc == NULL)
    return; // This module has no import section


    // Find the import descriptor containing references to callee's functions
    for (; pImportDesc->Name; pImportDesc++) {
    PSTR pszModName = (PSTR) ((PBYTE) hmodCaller + pImportDesc->Name);
    if (lstrcmpiA(pszModName, pszCalleeModName) == 0)
    break; // Found
    }

    if (pImportDesc->Name == 0)
    return; // This module doesn't import any functions from this callee

    // Get caller's import address table (IAT) for the callee's functions
    PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)
    ((PBYTE) hmodCaller + pImportDesc->FirstThunk);

    // Replace current function address with new function address
    for (; pThunk->u1.Function; pThunk++) {

    // Get the address of the function address
    PROC* ppfn = (PROC*) &pThunk->u1.Function;

    // Is this the function we're looking for?
    BOOL fFound = (*ppfn == pfnCurrent);

    if (!fFound && (*ppfn > sm_pvMaxAppAddr)) {

    // If this is not the function and the address is in a shared DLL,
    // then maybe we're running under a debugger on Windows 98. In this
    // case, this address points to an instruction that may have the
    // correct address.

    PBYTE pbInFunc = (PBYTE) *ppfn;
    if (pbInFunc[0] == cPushOpCode) {
    // We see the PUSH instruction, the real function address follows
    ppfn = (PROC*) &pbInFunc[1];

    // Is this the function we're looking for?
    fFound = (*ppfn == pfnCurrent);
    }
    }

    if (fFound) {
    // The addresses match, change the import section address
    WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew,
    sizeof(pfnNew), NULL);
    return; // We did it, get out
    }
    }
    }

    挂钩OpenProcess :

    //Hook_OpenProcess为定制的拦截函数
    QHookSrv g_OpenProcess("kernel32.dll", "OpenProcess",(PROC)Hook_OpenProcess,TRUE);

    有了IAT hook类,也有了拦截函数,接下来我们要做的就是如何patch所有的进程中duiOpenProcess的调用了,这里我们通过安装全局的WH_SHELL钩子来实现

    应用层消息钩子安装通过API:

    复制代码
    HHOOK WINAPI SetWindowsHookEx(
      _In_  int idHook,             //钩子的类型(消息钩子)
      _In_  HOOKPROC lpfn,    //钩子回调,当前响应消息被触发时候调用
      _In_  HINSTANCE hMod, //实例模块句柄,一般是DLL句柄
      _In_  DWORD dwThreadId //0标识全局钩子,非0标识局部钩子
    );
    复制代码

    消息钩子的卸载:

    //只有一个参数,通过SetWindowsHookEx返回的钩子句柄
    BOOL WINAPI UnhookWindowsHookEx(
      _In_  HHOOK hhk
    );

    : 本Demo中钩子的安装和卸载过程

    复制代码
    //安装钩子 ,由Dll导出
    //参数pid标识保护进程的ID
    BOOL InstallHook(DWORD pid)
    {    
        BOOL bResult=FALSE;
        if(!glhHook)
        {
            glhHook = SetWindowsHookEx(WH_SHELL,ShellHookProc,glhInstance, 0);
            if(glhHook!=NULL)
            {
                //´´½¨ÄÚ´æ¹²Ïí
                hMapping=CreateFileMapping((HANDLE)0xFFFFFFFF,NULL,PAGE_READWRITE,0,0x100,"PCMONITOR."); 
                if(hMapping != NULL) 
                { 
                    lpData=(LPSHWP_STRUCT)MapViewOfFile(hMapping,FILE_MAP_ALL_ACCESS,0,0,0); 
                    lpData->dwProcessId = pid; 
                }
                bResult=TRUE;
            }
        }
        return bResult; 
    }
    
    //卸载钩子,UninstallHook由Dll导出
    BOOL UninstallHook()
    {    
        BOOL bResult=FALSE;
        if(glhHook)
        {
            bResult= UnhookWindowsHookEx(glhHook);
            if(bResult)
            {
                glhHook=NULL;
            }
        }
        return bResult;
    }
    复制代码

    消息回调函数,回调不进行任何操作调用CallNextHookEx将消息传递下一个处理程序

    static LRESULT WINAPI ShellHookProc(int code, WPARAM wParam, LPARAM lParam) 
    {
        return ::CallNextHookEx(glhHook, code, wParam, lParam);
    }

    +

    NET(C#)中的调用:

    复制代码
            // QomoHookSrv.dll 
            [DllImport("QomoHookSrv.dll", CallingConvention=CallingConvention.Cdecl)]
            private extern static bool InstallHook( int processID);
            [DllImport("QomoHookSrv.dll", CallingConvention=CallingConvention.Cdecl)]
            private extern static bool UninstallHook();
    
            private void install_hook_when_app_startup() {
                //获取进程的ID
                int pid = System.Diagnostics.Process.GetCurrentProcess().Id;
                //安装钩子
                InstallHook(pid);
            }
    
            private void uninstall_hook_when_app_closed() {
                UninstallHook();
            }
    复制代码

    源码仅包含HOOK DLL代码(核心代码), 测试工程请自行code,文章也已经提及了,相对简单
    我是Source

    【进程保护知识扩展】

    本文中提及是从应用层的角度来保护进程不被关闭,通过Hook OperProcess 和TerminateProcess并不是安全了,应用程进程的关闭有些不是走该流程的,比如说直接调用NtTerminateProcess或者通过NtSetSystemInformation等

    进程关闭的流程大致为(系统API间的调用关系);

    通过上述流程相应的进程保护方法:

    1.应用层Hook OpenProcess/TerminateProcess ,或者调用RtlSetprocessiscritical

    2.内核层方式实现进程保护相对方式比较多点:

      a. Hook NtTerminaterProcess/ZwTerminateProcess(两个是一样的,在R3是为NtTerminateProcess,内核就有NtTerminateProcess转为ZwTerminateProcess调用),这种拦截可以通过修改内核表SSDT,或者InlineHook实现

      b.Hook ObReferenceObjectByHandle  ,网上有很多关于该API的HoOK源码,也是进程保护中常用的一种手段,因为后续的很多操作都是需要通过调用该函数返回的进程对象体

      c. Hook PspTerminateThreadByPointer/PspTerminateProcess/PspExitThread ,pspXX函数是比较底层的API了,而未导出,获取他们地址需要通过硬编码的方式,所以不同的系统可能存在问题,所以这种方法是相对比较危险的,但是确实很有效的

     d. Hook 插APC函数,例如:KeInitializeApc等这种方法也相对比较实用多,网上也有很多这方面的例子,不过大多数都是通过插APC的方式来强制结束进程的.


    专注于: Net分布式技术,移动服务端架构及系统安全学习及研究  by Andy


     
     
    分类: System Hacker
  • 相关阅读:
    Jedis与Redisson选型对比
    使用rdbtools工具来解析redis rdb文件
    Generating equals/hashCode implementation but without a call to superclass
    解决 Order By 将字符串类型的数字 或 字符串中含数字 按数字排序问题
    File.delete()和Files.delete(Path path)的区别
    file.deleteOnExit()与file.delete()的区别
    java日期中YYYY与yyyy的区别
    IDEA git分支回退指定的历史版本
    Lucene介绍与使用
    Lombok 学习指南
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3311279.html
Copyright © 2020-2023  润新知