12558网页游戏私服论坛

 找回密码
 立即注册
游戏开服表 申请开服
游戏名称 游戏描述 开服状态 游戏福利 运营商 游戏链接
攻城掠地-仿官 全新玩法,觉醒武将,觉醒技能 每周新区 经典复古版本,长久稳定 进入游戏
巅峰新版攻 攻城掠地公益服 攻城掠地SF 新兵种、新武将(兵种) 进入游戏
攻城掠地公 散人玩家的天堂 新开 进入游戏
改版攻城掠 上线即可国战PK 稳定新区 全新改版,功能强大 进入游戏
少年江山 高福利高爆率 刚开一秒 江湖水落潜蛟龙 进入游戏
太古封魔录 开服送10亿钻石 福利多多 不用充钱也可升级 进入游戏
神魔之道 签到送元宝 稳定开新区 送豪华签到奖励 进入游戏
神奇三国 统帅三军,招揽名将 免费玩新区 激情国战,征战四方 进入游戏
龙符 三日豪礼领到爽 天天开新区 助你征战无双 进入游戏
王者之师 免费领豪华奖励 免费玩新区 6元送6888元宝 进入游戏
三国霸业 战车-珍宝-觉醒-攻城掠地SF-全新玩法 免费玩新区 攻城掠地私服 进入游戏
手游私服盒子 各类免费游戏 0.1折送海量资源 各类手游私服 进入游戏
皇家MU2 《奇迹 2:传奇》韩国网禅公司《奇迹》正统续作。 3D锁视角Mmrpg 暗黑3+传奇+流放之路+奇迹 进入游戏
查看: 241|回复: 0

DLL巧妙的绕过被VMP壳HOOK的ZwProtectVirtualMemory

[复制链接]

315

主题

315

帖子

640

积分

实习版主

Rank: 7Rank: 7Rank: 7

积分
640
发表于 2021-2-19 10:34:09 | 显示全部楼层 |阅读模式
被VMP HOOK的ZwProtectVirtualMemory介绍

ZwProtectVirtualMemory,是一个修改内存输入的API函数,VirtualProtect和VirtualProtectEx修改内存属性都会通过ZwProtectVirtualMemory这个API函数.
在VMP壳的步伐中,注入DLL进行肯定的内存修改,但会发现,对WINAPI或者步伐的代码段等进行WriteProcessMemory操作,会发现修改错误,错误提示是没有修改权限.
而WriteProcessMemory在调用更底层的ZwWriteVirtualMemory前会先调用ZwProtectVirtualMemory修改原有步伐的内存属性.
但VMP HOOK了ZwProtectVirtualMemory,就导致了无法随意修改内存属性
778D0068     E9 A2FFA088     jmp 002E000F                             ; 被HOOK的ZwProtectVirtualMemory778D006D     33C9            xor ecx,ecx778D006F     8D5424 04       lea edx,dword ptr ss:[esp+0x4]778D0073     64:FF15 C000000>call dword ptr fs:[0xC0]778D007A     83C4 04         add esp,0x4778D007D     C2 1400         retn 0x14778D0068     B8 4D000000     mov eax,0x4D                         ; 没被HOOK前的ZwProtectVirtualMemory778D006D     33C9            xor ecx,ecx778D006F     8D5424 04       lea edx,dword ptr ss:[esp+0x4]778D0073     64:FF15 C000000>call dword ptr fs:[0xC0]                778D007A     83C4 04         add esp,0x4778D007D     C2 1400         retn 0x14从上面的代码可以看出,被修改的5字节是SSDT操作码,我们需要精准的知道原操作码是多少,但在WIN7下ZwProtectVirtualMemory的操作码是0x4D,而WIN10以及XP,WIN8又是不同的操作码,怎么样可以准确的获取到当前系统的操作码,并且在不修改HOOK的前提下调用ZwProtectVirtualMemory修改内存属性就是本贴的主题.
思路

首先想获取操作码有很多方法.
1.写驱动在驱动层获取SSDT操作码.
首先驱动就不写了.......复杂...而且x64驱动没签名...放弃....
2.读取其他进程的前5字节.
从其他进程读前5字节是可以思量的,但电脑安装了某些安全软件,他们的防护逻辑是全局DLL注入HOOK....所以你读出来的也可能是被HOOK后的opcode........这个方法也不思量....放弃
3.从ntdll.dll文件内获取操作码.
这个方法相对而言是最好的...因为你电脑的ntdll.dll被修改的机率还是非常非常低的(反正我没见过),就这个方法了吧,盘他.
详解

1.获取文件数据

首先把ntdll.dll读入到内存中,然后才可以进行下一步操作,直接贴代码好了,没什么难度
        HANDLE hFile = CreateFile(_T("C:\\Windows\\System32\\ntdll.dll"), GENERIC_READ, FILE_SHARE_READ, NULL,                                                   OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL);        if (hFile == INVALID_HANDLE_VALUE)        {                MessageBox(0, _T("CreateFileError"), _T("error"), MB_OK);                return;        }        SIZE_T ntdllAddr = (SIZE_T)LoadLibrary(_T("ntdll.dll"));        SIZE_T apiAddr = (SIZE_T)GetProcAddress((HMODULE)ntdllAddr, "ZwProtectVirtualMemory");        if (apiAddr == NULL || ntdllAddr == NULL)        {                MessageBox(0, _T("LoadLibrary|GetProcAddress,Error"), _T("error"), MB_OK);                CloseHandle(hFile);                return;        }        //把Ntdll读入进内存        DWORD dwHighSize = 0;        DWORD dwLowSize = GetFileSize(hFile, &dwHighSize);        BYTE* pBuff = new BYTE[dwLowSize + dwHighSize]();        DWORD  dwFileSize = 0;        ReadFile(hFile, pBuff, dwLowSize + dwHighSize, &dwFileSize, NULL);2.获取ZwProtectVirtualMemory在文件中的位置

ZwProtectVirtualMemory地址 - ntdll地址 = RVA
先通过RVA找到所在区段,再用RVA - 当前区段RVA + 当前区段文件偏移 = F0A
最后用F0A找到相对的opcode
//寻找所在区段        PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBuff;        PIMAGE_NT_HEADERS32 pNtHeader = (PIMAGE_NT_HEADERS32)(pDosHeader->e_lfanew + ((SIZE_T)pDosHeader));        PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)(((SIZE_T)(pNtHeader)) + sizeof(IMAGE_NT_HEADERS32));        SIZE_T Deviation = 0;        //ZwProtectVirtualMemory 地址 - ntdll地址 = RVA        SIZE_T Rva = apiAddr - ntdllAddr;        // 先通过RVA找到所在区段,再用RVA-当前区段RVA+当前区段文件偏移 = F0A        // 最后用F0A找到相对的opcode,复制到新内存执行再跳转        for (WORD i = 0; i < pNtHeader->FileHeader.NumberOfSections; ++i)        {                if (Rva > pSectionHeader.VirtualAddress && Rva < pSectionHeader.VirtualAddress + pSectionHeader.SizeOfRawData)                {//通过RVA找到所在区段,再用RVA-当前区段RVA+文件偏移 = F0A                        Deviation = Rva - pSectionHeader.VirtualAddress + pSectionHeader.PointerToRawData;                        break;                }        }        if (Deviation == 0)        {                MessageBoxA(0, "计算段内偏移错误", "error", MB_OK);                delete[] pBuff;                CloseHandle(hFile);                return;        }3.绕过HOOK

得到函数在文件内的位置后,可以获取原opcode进行调用,然后再跳转回没被HOOK的位置,还是来个图片好表明
...应该

        // 最后用段内偏移找到相对的opcode        g_pAddr = (BYTE*)VirtualAlloc(0, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);//申请一段堆空间执行被修改的汇编代码        if (g_pAddr == NULL)        {                MessageBoxA(0, "VirtualAlloc Error", "error", MB_OK);                delete[] pBuff;                CloseHandle(hFile);                return;        }        memcpy(g_pAddr, pBuff + Deviation, 5);//x32下VMP修改了前5字节,把文件内的原5字节拷贝过去        g_pAddr[5] = 0x68;//push        apiAddr += 5;        memcpy(g_pAddr + 6, &apiAddr, 4);//push的地址是ZwProtectVirtualMemory+5的地方        g_pAddr[10] = 0xc3;//使用retn过去        delete[] pBuff;        CloseHandle(hFile);代码

上面的代码都是一个函数被拆分上传了.x64的也有,和x32不同的地方就只有x64不能直接push太长的地址,所以用了下面的方式来跳转
        mov rax ,地址   48 b8 00 00 00 00 00 00 00 00   len == 9        push rax        50                              len == 10        mov r10,rcx        mov eax,操作码        retn下面是x32和x64完备的代码
#define DefineFuncPtr(name,base) decltype(name) *My_##name = (decltype(name)*)base#include "windows.h"#include "tchar.h"BYTE* g_pAddr = NULL;//ZwProtectVirtualMemory的调用函数BYTE* g_pHookAddr = NULL;typedef SIZE_T(CALLBACK* PZwProtectVirtualMemory)(        HANDLE ProcessHandle,        PVOID* BaseAddress,        SIZE_T* NumberOfBytesToProtect,        ULONG NewAccessProtection,        PULONG OldAccessProtection);PZwProtectVirtualMemory pZwProtectVirtualMemory;void Init64(){        if (g_pAddr != NULL)        {                return;        }        HANDLE hFile = CreateFile(_T("C:\\Windows\\System32\\ntdll.dll"), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL);        if (hFile == INVALID_HANDLE_VALUE)        {                MessageBox(0, _T("CreateFileError"), _T("error"), MB_OK);                return;        }        SIZE_T ntdllAddr = (SIZE_T)LoadLibrary(_T("ntdll.dll"));        SIZE_T apiAddr = (SIZE_T)GetProcAddress((HMODULE)ntdllAddr, "ZwProtectVirtualMemory");        if (apiAddr == NULL || ntdllAddr == NULL)        {                MessageBox(0, _T("LoadLibrary|GetProcAddress,Error"), _T("error"), MB_OK);                CloseHandle(hFile);                return;        }        //把Ntdll读入进内存        DWORD dwHighSize = 0;        DWORD dwLowSize = GetFileSize(hFile, &dwHighSize);        BYTE* pBuff = new BYTE[dwLowSize + dwHighSize]();        DWORD  dwFileSize = 0;        ReadFile(hFile, pBuff, dwLowSize + dwHighSize, &dwFileSize, NULL);        //寻找所在区段        PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBuff;        PIMAGE_NT_HEADERS64 pNtHeader = (PIMAGE_NT_HEADERS64)(pDosHeader->e_lfanew + ((SIZE_T)pDosHeader));        PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)(((SIZE_T)(pNtHeader)) + sizeof(IMAGE_NT_HEADERS64));        SIZE_T Deviation = 0;        //ZwProtectVirtualMemory 地址 - ntdll地址 = RVA        SIZE_T Rva = apiAddr - ntdllAddr;        // 先通过RVA找到所在区段,再用RVA-当前区段RVA+当前区段文件偏移 = F0A        // 最后用F0A找到相对的opcode,复制到新内存执行再跳转        for (WORD i = 0; i < pNtHeader->FileHeader.NumberOfSections; ++i)        {                if (Rva > pSectionHeader.VirtualAddress && Rva < pSectionHeader.VirtualAddress + pSectionHeader.SizeOfRawData)                {//通过RVA找到所在区段,再用RVA-当前区段RVA+文件偏移 = F0A                        Deviation = Rva - pSectionHeader.VirtualAddress + pSectionHeader.PointerToRawData;                        break;                }        }        if (Deviation == 0)        {                MessageBoxA(0, "计算段内偏移错误", "error", MB_OK);                delete[] pBuff;                CloseHandle(hFile);                return;        }        // 最后用段内偏移找到相对的opcode        g_pAddr = (BYTE*)VirtualAlloc(0, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);//申请一段堆空间执行被修改的汇编代码        if (g_pAddr == NULL)        {                MessageBoxA(0, "VirtualAlloc Error", "error", MB_OK);                delete[] pBuff;                CloseHandle(hFile);                return;        }        /*        mov rax ,地址   48 b8 00 00 00 00 00 00 00 00   len == 9        push rax        50                              len == 10        mov r10,rcx        mov eax,操作码        retn        */        g_pAddr[0] = 0x48;        g_pAddr[1] = 0xb8;        apiAddr += 8;        memcpy(g_pAddr + 2, &apiAddr, 8);//地址是ZwProtectVirtualMemory+7        g_pAddr[10] = 0x50;//push rax        memcpy(g_pAddr + 11, pBuff + Deviation, 8);//x64下VMP修改了前8字节,把文件内的原8字节拷贝过去        g_pAddr[19] = 0xc3;//retn过去        delete[] pBuff;        CloseHandle(hFile);}void Init32(){        if (g_pAddr != NULL)        {                return;        }        HANDLE hFile = CreateFile(_T("C:\\Windows\\System32\\ntdll.dll"), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL);        if (hFile == INVALID_HANDLE_VALUE)        {                MessageBox(0, _T("CreateFileError"), _T("error"), MB_OK);                return;        }        SIZE_T ntdllAddr = (SIZE_T)LoadLibrary(_T("ntdll.dll"));        SIZE_T apiAddr = (SIZE_T)GetProcAddress((HMODULE)ntdllAddr, "ZwProtectVirtualMemory");        if (apiAddr == NULL || ntdllAddr == NULL)        {                MessageBox(0, _T("LoadLibrary|GetProcAddress,Error"), _T("error"), MB_OK);                CloseHandle(hFile);                return;        }        //把Ntdll读入进内存        DWORD dwHighSize = 0;        DWORD dwLowSize = GetFileSize(hFile, &dwHighSize);        BYTE* pBuff = new BYTE[dwLowSize + dwHighSize]();        DWORD  dwFileSize = 0;        ReadFile(hFile, pBuff, dwLowSize + dwHighSize, &dwFileSize, NULL);        //寻找所在区段        PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBuff;        PIMAGE_NT_HEADERS32 pNtHeader = (PIMAGE_NT_HEADERS32)(pDosHeader->e_lfanew + ((SIZE_T)pDosHeader));        PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)(((SIZE_T)(pNtHeader)) + sizeof(IMAGE_NT_HEADERS32));        SIZE_T Deviation = 0;        //ZwProtectVirtualMemory 地址 - ntdll地址 = RVA        SIZE_T Rva = apiAddr - ntdllAddr;        // 先通过RVA找到所在区段,再用RVA-当前区段RVA+当前区段文件偏移 = F0A        // 最后用F0A找到相对的opcode,复制到新内存执行再跳转        for (WORD i = 0; i < pNtHeader->FileHeader.NumberOfSections; ++i)        {                if (Rva > pSectionHeader.VirtualAddress && Rva < pSectionHeader.VirtualAddress + pSectionHeader.SizeOfRawData)                {//通过RVA找到所在区段,再用RVA-当前区段RVA+文件偏移 = F0A                        Deviation = Rva - pSectionHeader.VirtualAddress + pSectionHeader.PointerToRawData;                        break;                }        }        if (Deviation == 0)        {                MessageBoxA(0, "计算段内偏移错误", "error", MB_OK);                delete[] pBuff;                CloseHandle(hFile);                return;        }        // 最后用段内偏移找到相对的opcode        g_pAddr = (BYTE*)VirtualAlloc(0, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);//申请一段堆空间执行被修改的汇编代码        if (g_pAddr == NULL)        {                MessageBoxA(0, "VirtualAlloc Error", "error", MB_OK);                delete[] pBuff;                CloseHandle(hFile);                return;        }        memcpy(g_pAddr, pBuff + Deviation, 5);//x32下VMP修改了前5字节,把文件内的原5字节拷贝过去        g_pAddr[5] = 0x68;//push        apiAddr += 5;        memcpy(g_pAddr + 6, &apiAddr, 4);//push的地址是ZwProtectVirtualMemory+5的地方        g_pAddr[10] = 0xc3;//使用retn过去        delete[] pBuff;        CloseHandle(hFile);}SIZE_T MyZwProtectVirtualMemory(        HANDLE ProcessHandle, //进程句柄        PVOID* BaseAddress,//地址的指针        SIZE_T* NumberOfBytesToProtect,//修改的大小,函数调用后会改成成功修改的大小        ULONG NewAccessProtection,//新的内存属性        PULONG OldAccessProtection)//旧的内存属性{#ifdef _WIN64        Init64();#else        Init32();#endif        pZwProtectVirtualMemory = (PZwProtectVirtualMemory)g_pAddr;        return pZwProtectVirtualMemory(ProcessHandle, BaseAddress, NumberOfBytesToProtect, NewAccessProtection, OldAccessProtection);}验证下效果

1.步伐介绍

一个加了VMP 3.2壳的易语言步伐.

已知步伐是获取CPU序号的步伐,并且这个步伐只调用了CPUID这个汇编代码获取了CPU序号,无法对相关的API进行HOOK,而且是个加了VMP壳的步伐,只能让其代码段解密后修改,但想精准的修改还需在代码没运行前进行拦截,这里可以通过DLL进行API HOOK判断代码段是否解码,然后再HOOK拦截修改EAX或者EDX
2.寻找代码段解码后一个调用的函数


先看连接器信息,10.00,可能是一个vs2010连接器天生的EXE,vs2010连接器天生的代码在运行main函数之前第一个调用的API是GetSystemTimeAsFileTime
通过测试发现,GetSystemTimeAsFileTime 这个API第一次调用的时候,0x0040112B已经解密完成,并且对其下断能正常断下,可以使用这个API进行代码段是否解码完成.
3.HOOK GetSystemTimeAsFileTime

这里就没什么好说的,相信大家都对HOOK很熟悉了,调用上面我写好的MyZwProtectVirtualMemory函数来修改内存属性.
HOOK GetSystemTimeAsFileTime-> 判断代码是否解码->修改内存属性,然后HOOK 0040112B->轻微修改下CPUID调用后的EAX
case DLL_PROCESS_ATTACH:        {        // HOOK GetSystemTimeAsFileTime来判断代码段是否解密        //保存GetSystemTimeAsFileTime前5字节,并且push  retn跳到GetSystemTimeAsFileTime + 5 位置                g_pHookAddr = (BYTE*)VirtualAlloc(0, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);//GetSystemTimeAsFileTime调用接口                PVOID addr = (PVOID)GetProcAddress(LoadLibraryA("KernelBase.dll"), "GetSystemTimeAsFileTime");                memcpy(g_pHookAddr, addr, 5);//拷贝原5字节                g_pHookAddr[5] = 0x68;//psuh  GetSystemTimeAsFileTime + 5                 SIZE_T TempAddr = ((SIZE_T)addr) + 5;//GetSystemTimeAsFileTime + 5                 memcpy(g_pHookAddr + 6, &TempAddr, 4);                g_pHookAddr[10] = 0xc3;//retn                SIZE_T size = 5;//e9 00 00 00 00 size                ULONG OldProtect;                MyZwProtectVirtualMemory((HANDLE)-1, &addr, &size, PAGE_EXECUTE_READWRITE, &OldProtect);//修改内存属性                addr = (PVOID)GetProcAddress(LoadLibraryA("KernelBase.dll"), "GetSystemTimeAsFileTime");                BYTE opcode[8] = {};                opcode[0] = 0xe9;//jmp                SIZE_T MyApiAddr = (((SIZE_T)MyGetSystemTimeAsFileTime) - ((SIZE_T)addr) - 5);                memcpy(opcode + 1, &MyApiAddr, 4);                memcpy(addr, opcode, 5);//HOOK GetSystemTimeAsFileTime这个函数,并且JMP到MyGetSystemTimeAsFileTime                MyZwProtectVirtualMemory((HANDLE)-1, &addr, &size, OldProtect, &OldProtect);//恢复内存属性                break;        }判断代码是否解码
VOID WINAPI MyGetSystemTimeAsFileTime(        _Out_ LPFILETIME lpSystemTimeAsFileTime){        WORD opcode;        memcpy(&opcode, (PVOID)(((SIZE_T)GetModuleHandleA(nullptr)) + 0x112B), 2);        if (opcode == 0xA20F)//EXEtest.vmp.exe + 0x112B        {                PVOID addr = (PVOID)(((SIZE_T)GetModuleHandleA(nullptr)) + 0x112B);                SIZE_T size = 5;                SIZE_T OldProtect;                MyZwProtectVirtualMemory((HANDLE)-1, &addr, &size, PAGE_EXECUTE_READWRITE, &OldProtect);//修改属性                addr = (PVOID)(((SIZE_T)GetModuleHandleA(nullptr)) + 0x112B);                memcpy(addr, new BYTE[5]{ 0xE9, 0x54 , 0x9E , 0x09 , 0x00 }, 5);                size = 5;                MyZwProtectVirtualMemory((HANDLE)-1, &addr, &size, OldProtect, &OldProtect);//恢复属性                addr = (PVOID)(((SIZE_T)GetModuleHandleA(nullptr)) + 0x9AF84);                size = 15;                MyZwProtectVirtualMemory((HANDLE)-1, &addr, &size, PAGE_EXECUTE_READWRITE, &OldProtect);//修改属性                addr = (PVOID)(((SIZE_T)GetModuleHandleA(nullptr)) + 0x9AF84);                size = 15;                memcpy(addr, new BYTE[15]{ 0x0F ,0xA2 ,0x89 ,0x55 ,0xFC ,0xB8 ,0x66 ,0x66 ,0x66 ,0x66 ,0xE9 ,0x9D ,0x61 ,0xF6 ,0xFF }, 15);                MyZwProtectVirtualMemory((HANDLE)-1, &addr, &size, OldProtect, &OldProtect);//恢复属性        }        DefineFuncPtr(GetSystemTimeAsFileTime, g_pHookAddr);        return My_GetSystemTimeAsFileTime(lpSystemTimeAsFileTime);}HOOK后对0x0040112B的处理
0040112B   E9 549E0900     jmp EXEtest_.0049AF840049AF84    0FA2            cpuid0049AF86    8955 FC         mov dword ptr ss:[ebp-0x4],edx0049AF89    B8 66666666     mov eax,0x666666660049AF8E    E9 9D61F6FF     jmp EXEtest_.00401130
完备的DLL代码在附件内,同测试步伐同时上传吧.
WIN10测试图片:



# 附录

文件解压密码:52pojie

DLL源码:dllmain.zip

测试步伐下载:太大了,上传蓝奏吧:https://lanzous.com/ibjcj7i   解压密码:52pojie

提示:测试用的exe是易语言编写,并且关闭了随机机制,还有修改oep的代码来调用LoadLibrary,详情可以去看没加壳前的exe.

DLL用注入器注入到步伐内需要修改下GetSystemTimeAsFileTime的HOOK,因为他只有在开始的时候调用了一次,如果步伐已经运行起来再注入是不会生效的.

加壳的测试步伐可用 +   进行调试观察.
来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
楼主热帖
回复

使用道具 举报

*滑块验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|12558网页游戏私服论坛 |网站地图

GMT+8, 2024-11-28 18:51 , Processed in 0.078125 second(s), 32 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

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