吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 915|回复: 13
收起左侧

[C&C++ 原创] 通过Hook Wow64获取64位系统中32位程序的内核调用信息

  [复制链接]
苏紫方璇 发表于 2024-12-16 21:06

一、前言

最近翻找以前的代码,发现了这个竟然还处于弃坑状态,正好这两天比较闲,回坑补充一下,毕竟远古的技术啦,现在写来应该问题不大。

二、原理分析

1、wow64的调用流程

众所周知,64位系统的内核是64位的,所以32位程序跑的时候,进内核就需要转换一下,wow64就是做这个工作的。
简单的调用流程
32位ntdll.Zw/Ntxxxx --> Wow64Transition --> 64位wow64处理 --> 64位ntdll.Zw/Ntxxxx -->进入内核
具体分析可参看oxygen1a1大佬的帖子

Wow64系统调用分析以及ServiceTableHook
https://www.52pojie.cn/thread-1711323-1-1.html
(出处: 吾爱破解论坛)

2、hook点位查找

根据上面的调用流程分析,可以知道,在32位的终点是通过Wow64Transition处的天堂之门jmp far 0033:xxxxxxx进入64位运行。
所以找到Wow64Transition的位置便是关键。
通过逆向分析,可以看到,在windows10上,ntdll经过多次跳转才来到了Wow64Transition

win10 ntdll.ZwAccessCheck:

77DE2FB0      B8 00000000     mov eax,0x0
77DE2FB5      BA 408FDF77     mov edx,ntdll.77DF8F40
77DE2FBA      FFD2            call edx
77DE2FBC      C2 2000         retn 0x20
77DF8F40  - FF25 2892E977   jmp dword ptr ds:[Wow64Transition]

77D67000      EA 0970D677 330>jmp far 0033:77D67009  ;Wow64Transition

而windows7就比较简单了,通过fs:[0xC0]既可以获取地址。

win7 ntdll.ZwAccessCheck:

7DE90218    B861000000  mov eax, 00000061h              
7DE9021D    33C9        xor ecx, ecx                    
7DE9021F    8D542404    lea edx, dword ptr ss:[esp+04h] 
7DE90223    64FF15C     call dword ptr fs:[000000C0h]   
7DE9022A    83C404      add esp, 04h                    
7DE9022D    C22000      retn 0020h   

经过测试,win10也是可以通过fs:[0xC0]获得地址。
那么,问题就简单了,一句__readfsdword(0xC0)就可以解决。

三、代码编写

1、SSDT和Shadow SSDT序号对应的函数名

通过查看上面的反汇编可以知道,序号在经过hook点时,是存放在eax中的。在写hook函数时,可以获取eax的值来判断调用的是哪个API。
那么,通过遍历ntdll的导出表,将Zw或者Nt开头的函数挑出来,读取mov eax,xxx中的xxx即可将函数名和序号一一对应起来。

void listNtdllExport()
{
    char* funcName;
    DWORD addr, nIndex;
    // 获取导出表地址
    IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)GetModuleHandle(L"ntdll.dll");
    IMAGE_NT_HEADERS* ntHeaders = (IMAGE_NT_HEADERS*)((BYTE*)dosHeader + dosHeader->e_lfanew);
    IMAGE_EXPORT_DIRECTORY* exportDir = (IMAGE_EXPORT_DIRECTORY*)((BYTE*)dosHeader +
        ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

    // 获取导出表中函数名称和地址
    DWORD* nameRVAs = (DWORD*)((BYTE*)dosHeader + exportDir->AddressOfNames);
    DWORD* funcRVAs = (DWORD*)((BYTE*)dosHeader + exportDir->AddressOfFunctions);
    WORD* ordinals = (WORD*)((BYTE*)dosHeader + exportDir->AddressOfNameOrdinals);

    for (DWORD i = 0; i < exportDir->NumberOfNames; i++)
    {
        funcName = (char*)((BYTE*)dosHeader + nameRVAs[i]);
        addr = (DWORD)((BYTE*)dosHeader + funcRVAs[ordinals[i]]);
        //确保地址不是0,并且只检查Zw开头的函数
        if (addr != 0 && *funcName == 'Z')
        {
            //77DE3260 >  B8 29000000     mov eax,0x29
            if (*(BYTE*)addr == 0xB8)
            {
                nIndex = *(DWORD*)(addr + 1);
                if (nIndex < 0xFFFFFF)
                {
                    nIndex &= 0xFFF;
                    strcpy_s(ntList[nIndex], funcName);
                }

            }
        }
    }
}

Ntdll中,Nt开头和Zw开头是同一个函数,函数地址相同,这里使用Z开头判断,是为了和Shadow SSDT函数做区分。
同理,在win10中,Shadow SSDT函数在win32u.dll中做跳转,根据上方代码做少量修改即可。
需要注意的是,Ntdll序号为0x0XXXWin32u中序号为0x1XXX,部分序号高16位还有数据,需要加以剔除。
例如

77DE3660 ntdll.ZwAddAtomEx     B8 69001100     mov eax,0x110069
77DE3665                       BA 408FDF77     mov edx,ntdll.77DF8F40

2、hook代码编写

由于需要保存一个eax,并且最后需要jmp到Wow64Transition,所以需要一个中间函数。

__declspec(naked) void hookProc()
{
    __asm
    {
        pushad
        mov dwEax,eax
        call hookProc2
        popad
        jmp tWow64Transition
    }
}

这里使用一个裸函数,call一个hook处理函数即可保证只记录处理数据,不修改相关寄存器环境。

//处理函数
void WINAPI hookProc2()
{
    DWORD dwCount;
    DWORD dwId;
    //跳过,防止输出信息时重入
    if (bFlag)
    {
        dwCount++;
        return;
    }
    bFlag = TRUE;
    dwCount = dwCount;
    dwCount = 0;
    //取eax低16位,win32u中序号为1xxx,ntdll中为0xxx
    dwId = dwEax & 0xFFFF;
    if ((dwId&0x1000)==0x1000)
    {
        printf("跳过%d  id:%d %s\r\n", dwCount, dwId&0xFFF, w32List[dwId & 0xFFF]);
    }
    else
    {
        printf("跳过%d  id:%d %s\r\n", dwCount, dwId & 0xFFF, ntList[dwId & 0xFFF]);
    }

    bFlag = FALSE;
    return;
}

在上面处理函数中只输出了函数名等信息,但需要注意,即便使用printf,也是需要进入内核的,所以,会引发hook重入,导致堆栈耗尽程序崩溃。本文使用了较为简单的全局标记法,这种方法在多线程时,会漏掉不少调用(通过下方输出信息也可以看出来,标准printf会跳过2条记录,但有时会跳5条或者更多)。至于优化方法,不在本文探讨范围之内。

输出信息
跳过2  id:24 ZwAllocateVirtualMemory
跳过2  id:340 ZwQueryLicenseValue
跳过2  id:5 NtUserCallNoParam
跳过2  id:347 ZwQuerySecurityAttributesToken
跳过2  id:347 ZwQuerySecurityAttributesToken
跳过2  id:34 NtUserGetProcessWindowStation
跳过2  id:106 NtUserGetObjectInformation
跳过2  id:242 NtUserGetGUIThreadInfo
跳过2  id:160 ZwConnectPort
跳过2  id:34 ZwRequestWaitReplyPort
跳过2  id:34 ZwRequestWaitReplyPort
跳过2  id:33 ZwQueryInformationToken
跳过2  id:18 ZwOpenKey
跳过2  id:289 ZwOpenKeyEx
跳过2  id:23 ZwQueryValueKey
跳过2  id:15 ZwClose
跳过2  id:15 ZwClose
跳过2  id:453 ZwTraceControl
跳过2  id:453 ZwTraceControl
跳过2  id:5 ZwCallbackReturn
跳过2  id:99 NtUserGetWindowDC
跳过5  id:80 ZwProtectVirtualMemory
跳过5  id:80 ZwProtectVirtualMemory
跳过2  id:176 NtGdiCreateSolidBrush

3、完整代码

#include <windows.h>
#include "detours/detours.h"
#include <stdio.h>
#include <locale.h>

//地址fs[0xc0]
typedef void (WINAPI *pWow64Transition)();

pWow64Transition tWow64Transition;
//跳过次数
DWORD dwCount = 0;
//跳过标记
BOOL bFlag = FALSE;
//保存的eax的值
DWORD dwEax = 0;
//ntdll中SSDT导出函数列表 
char ntList[600][100];
//win32u中Shadow SSDT导出函数列表
char w32List[1600][100];

//初始化命令行输出映射
BOOL InitConsoleWindow()
{
    FILE * pfile;
    BOOL ret = AllocConsole();
    if (ret)
    {
        //HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
        SetConsoleTitleA("Debug Output");
        setlocale(LC_ALL, "chs");
        freopen_s(&pfile, "CONOUT$", "w", stdout);
        freopen_s(&pfile, "CONOUT$", "w", stderr);
        freopen_s(&pfile, "CONIN$", "r", stdin);
    }
    return ret;
}

//处理函数
void WINAPI hookProc2()
{
    DWORD dwCount;
    DWORD dwId;
    //跳过,防止输出信息时重入
    if (bFlag)
    {
        dwCount++;
        return;
    }
    bFlag = TRUE;
    dwCount = dwCount;
    dwCount = 0;
    //取eax低16位,win32u中序号为1xxx,ntdll中为0xxx
    dwId = dwEax & 0xFFFF;
    if ((dwId&0x1000)==0x1000)
    {
        printf("跳过%d  id:%d %s\r\n", dwCount, dwId&0xFFF, w32List[dwId & 0xFFF]);
    }
    else
    {
        printf("跳过%d  id:%d %s\r\n", dwCount, dwId & 0xFFF, ntList[dwId & 0xFFF]);
    }

    bFlag = FALSE;
    return;
}

__declspec(naked) void hookProc()
{
    __asm
    {
        pushad
        mov dwEax,eax
        call hookProc2
        popad
        jmp tWow64Transition
    }
}

void Hook()
{
    tWow64Transition =(pWow64Transition) __readfsdword(0xC0);
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourAttach(&(PVOID&)tWow64Transition, hookProc);
    DetourTransactionCommit();
}

void listNtdllExport()
{
    char* funcName;
    DWORD addr, nIndex;
    // 获取导出表地址
    IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)GetModuleHandle(L"ntdll.dll");
    IMAGE_NT_HEADERS* ntHeaders = (IMAGE_NT_HEADERS*)((BYTE*)dosHeader + dosHeader->e_lfanew);
    IMAGE_EXPORT_DIRECTORY* exportDir = (IMAGE_EXPORT_DIRECTORY*)((BYTE*)dosHeader +
        ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

    // 获取导出表中函数名称和地址
    DWORD* nameRVAs = (DWORD*)((BYTE*)dosHeader + exportDir->AddressOfNames);
    DWORD* funcRVAs = (DWORD*)((BYTE*)dosHeader + exportDir->AddressOfFunctions);
    WORD* ordinals = (WORD*)((BYTE*)dosHeader + exportDir->AddressOfNameOrdinals);

    for (DWORD i = 0; i < exportDir->NumberOfNames; i++)
    {
        funcName = (char*)((BYTE*)dosHeader + nameRVAs[i]);
        addr = (DWORD)((BYTE*)dosHeader + funcRVAs[ordinals[i]]);
        //确保地址不是0,并且只检查Zw开头的函数
        if (addr != 0 && *funcName == 'Z')
        {
            //77DE3260 >  B8 29000000     mov eax,0x29
            if (*(BYTE*)addr == 0xB8)
            {
                nIndex = *(DWORD*)(addr + 1);
                if (nIndex < 0xFFFFFF)
                {
                    nIndex &= 0xFFF;
                    strcpy_s(ntList[nIndex], funcName);
                }
            }
        }
    }
}
void listWin32uExport()
{
    char* funcName;
    DWORD addr, nIndex;
    // 获取导出表地址
    IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)GetModuleHandle(L"win32u.dll");
    //无窗口不存在这个dll
    if (dosHeader == NULL) return;
    IMAGE_NT_HEADERS* ntHeaders = (IMAGE_NT_HEADERS*)((BYTE*)dosHeader + dosHeader->e_lfanew);
    IMAGE_EXPORT_DIRECTORY* exportDir = (IMAGE_EXPORT_DIRECTORY*)((BYTE*)dosHeader +
        ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

    // 获取导出表中函数名称和地址
    DWORD* nameRVAs = (DWORD*)((BYTE*)dosHeader + exportDir->AddressOfNames);
    DWORD* funcRVAs = (DWORD*)((BYTE*)dosHeader + exportDir->AddressOfFunctions);
    WORD* ordinals = (WORD*)((BYTE*)dosHeader + exportDir->AddressOfNameOrdinals);

    for (DWORD i = 0; i < exportDir->NumberOfNames; i++)
    {
        funcName = (char*)((BYTE*)dosHeader + nameRVAs[i]);
        addr = (DWORD)((BYTE*)dosHeader + funcRVAs[ordinals[i]]);
        if (addr != 0 && *funcName == 'N')
        {
            //77092220 w>  B8 22110000     mov eax,0x1122
            if (*(BYTE*)addr == 0xB8)
            {
                nIndex = *(DWORD*)(addr + 1);
                if (nIndex < 0xFFFFFF)
                {
                    nIndex &= 0xFFF;
                    strcpy_s(w32List[nIndex], funcName);
                }
            }
        }
    }
}

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
)
{
    if (ul_reason_for_call == DLL_PROCESS_ATTACH)
    {
        DisableThreadLibraryCalls(hModule);
        InitConsoleWindow();
        printf("DLL加载\r\n");
        listNtdllExport();
        listWin32uExport();
        Hook();
    }
    if (ul_reason_for_call == DLL_PROCESS_DETACH)
    {
        printf("DLL卸载\r\n");
    }
    return TRUE;
}

四、结语

本文代码仅在win10 22H2测试通过,win7由于没有win32u.dll,相关函数是通过user32.dll来中转,且部分函数未导出,故不支持win7获取此部分。当然Ntdll还是可以的。
叠盾时间,本文全部依靠自己理解所写,如有用词不规范或者谬误之处,还请各位大佬指教。
最后小提示,由于hook的32转64进内核的地方,调用会非常非常频繁,添加任何代码都将大幅影响运行速度,加大系统运行开销。



Wow64TransitionHook.7z (95.03 KB, 下载次数: 15)

点评

可以使用`InstrumentationCallback`。  发表于 2024-12-17 09:36

免费评分

参与人数 5吾爱币 +5 热心值 +5 收起 理由
笙若 + 1 + 1 谢谢@Thanks!
wutljs + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
woyucheng + 1 + 1 谢谢@Thanks!
Oo0520 + 1 + 1 用心讨论,共获提升!
lzq20041213 + 1 + 1 用心讨论,共获提升!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

冥界3大法王 发表于 2024-12-16 22:47
一堆术语堆叠,不知是如何摆脱迷雾束缚的? 单词单个全认识,搞在一起就不知咋组合谁对谁搞成代码实现了。。。
周易 发表于 2024-12-17 12:18
本帖最后由 周易 于 2024-12-17 12:41 编辑

避免重入有较为粗糙的解决方案。

#include <windows.h>

extern "C" ULONG __stdcall RtlWalkFrameChain(PVOID *Callers, ULONG Count, ULONG Flags);
#pragma comment(lib, "ntdll.lib")

ULONG CountReentrance(PVOID pReturnAddress)
{
    PVOID pCallers[0x80];
    ULONG ulCount;
    ulCount = RtlWalkFrameChain(pCallers, 0x80, 0);
    ULONG countReentrance = 0;
    for (ULONG i = 0; i != ulCount; i++)
        if (pCallers[i] == pReturnAddress)
            countReentrance++;
    return countReentrance;
}

使用方式如下。

#include <intrin.h>

if (CountReentrance(_ReturnAddress()) >= 2)
    __debugbreak();

对代码稍事修改如下,可以去除全局变量使用,提高线程安全。

// 处理函数
void WINAPI hookProc2(DWORD dwEax)
{
    DWORD dwId;
    // 跳过,防止输出信息时重入
    if (CountReentrance(_ReturnAddress()) >= 2)
        return;
    // 取eax低16位,win32u中序号为1xxx,ntdll中为0xxx
    dwId = dwEax & 0xFFFF;
    if ((dwId & 0x1000) == 0x1000)
    {
        printf("id:%d %s\r\n", dwId & 0xFFF, w32List[dwId & 0xFFF]);
    }
    else
    {
        printf("id:%d %s\r\n", dwId & 0xFFF, ntList[dwId & 0xFFF]);
    }

    return;
}

__declspec(naked) void hookProc()
{
    DWORD dwEax;
    __asm push ebp;
    __asm mov ebp, esp;
    __asm sub esp, __LOCAL_SIZE;
    __asm pushad;
    __asm mov dwEax, eax;
    {
        hookProc2(dwEax);
    }
    __asm popad;
    __asm mov esp, ebp;
    __asm pop ebp;
    __asm jmp tWow64Transition;
}

点评

我也是刚想起来eax的值不可以用全局变量,应该改成作为参数放进去。准备重新编辑,就发现你已经提出来了  详情 回复 发表于 2024-12-17 16:10

免费评分

参与人数 1吾爱币 +3 热心值 +1 收起 理由
苏紫方璇 + 3 + 1 用心讨论,共获提升!

查看全部评分

董督秀 发表于 2024-12-16 21:19
qi1990 发表于 2024-12-16 21:20
谢谢提供学习
wubin61 发表于 2024-12-16 21:56
感谢分享
头像被屏蔽
pomxion 发表于 2024-12-16 23:36
提示: 作者被禁止或删除 内容自动屏蔽
freecat 发表于 2024-12-17 00:16
正好学习理解一下
lforl 发表于 2024-12-17 09:13

不知此知识,能否实践于:为著名20年经典免费软件的拖把更名器在64位系统下续命右键
https://www.52pojie.cn/thread-1909882-1-1.html

点评

你这个当时我看过,难度还是很大的,不太好弄  详情 回复 发表于 2024-12-17 10:42
smile789 发表于 2024-12-17 10:25
感谢分享!
fangxiaolong 发表于 2024-12-17 10:38
谢谢分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-1-7 20:15

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表