被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
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |