《深入浅出Windows API程序设计:核心编程篇》这本书介绍了Shadow API技术,此技术可以起到隐藏api调用的作用,从而显著增加调试和逆向的难度。
但是里面给的示例代码由于严重依赖于操作系统版本,不要说Win11,在部分Win10上都会出现崩溃,这很不利于学习。
所以我在Win11上重新进行了实现,分析崩溃原因时发现Win11上的MessageBoxW已经不再是直接call MessageBoxTimeOutW,而是直接call了一个位置:
760B8840 8B FF mov edi,edi
760B8842 55 push ebp
760B8843 8B EC mov ebp,esp
760B8845 83 3D 84 8C 0E 76 00 cmp dword ptr ds:[760E8C84h],0
760B884C 74 22 je 760B8870
760B884E 64 A1 18 00 00 00 mov eax,dword ptr fs:[00000018h]
760B8854 BA 00 93 0E 76 mov edx,760E9300h
760B8859 8B 48 24 mov ecx,dword ptr [eax+24h]
760B885C 33 C0 xor eax,eax
760B885E F0 0F B1 0A lock cmpxchg dword ptr [edx],ecx
760B8862 85 C0 test eax,eax
760B8864 75 0A jne 760B8870
760B8866 C7 05 20 8D 0E 76 01 00 00 00 mov dword ptr ds:[760E8D20h],1
760B8870 6A FF push 0FFFFFFFFh
760B8872 6A 00 push 0
760B8874 FF 75 14 push dword ptr [ebp+14h]
760B8877 FF 75 10 push dword ptr [ebp+10h]
760B887A FF 75 0C push dword ptr [ebp+0Ch]
760B887D FF 75 08 push dword ptr [ebp+8]
760B8880 E8 0B FE FF FF call 760B8690 ; 重点
760B8885 5D pop ebp
760B8886 C2 10 00 ret 10h
所以按照书中原来的思路是肯定不行的,需要自己手动修正新代码的偏移。
本人修复后的例子如下,同时也对代码风格进行了部分现代化(如积极使用using、nullptr、增加返回值检查):
cpp
#include <windows.h>
#include "resource.h"
using PFN_MESSAGEBOXW = int (WINAPI *)(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType);
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
}
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HMODULE hUser32 = NULL;
PFN_MESSAGEBOXW pfnMessageBoxW = nullptr;
static PFN_MESSAGEBOXW pfnNewMessageBoxW = nullptr; // 分配内存空间存放MessageBoxW函数机器码
BYTE bArr[74] = { 0 }; // 存放MessageBoxW函数实现代码的缓冲区
PBYTE pMessageBoxInnerCall = nullptr;
PBYTE pMyMessageBoxInnerCall = nullptr;
UINT_PTR uOffset = 0;
switch (uMsg)
{
case WM_INITDIALOG:
// 获取MessageBoxW函数的地址,并读取函数数据
hUser32 = GetModuleHandle(TEXT("User32.dll"));
if (!hUser32)
{
return TRUE;
}
pfnMessageBoxW = (PFN_MESSAGEBOXW)GetProcAddress(hUser32,
"MessageBoxW");
if (!pfnMessageBoxW)
{
return TRUE;
}
if (!ReadProcessMemory(GetCurrentProcess(), pfnMessageBoxW, bArr, sizeof(bArr), NULL))
{
return TRUE;
}
// 分配内存空间存放MessageBoxW函数,可读可写可执行
pfnNewMessageBoxW = (PFN_MESSAGEBOXW)VirtualAlloc(NULL, sizeof(bArr), MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
if (!pfnNewMessageBoxW)
{
return TRUE;
}
if (!WriteProcessMemory(GetCurrentProcess(), pfnNewMessageBoxW, bArr, sizeof(bArr), NULL))
{
return TRUE;
}
// 获取MessageBoxW中Call的位置,获取call指令中的偏移
pMessageBoxInnerCall = (PBYTE)pfnMessageBoxW + 0x40;
if (!ReadProcessMemory(GetCurrentProcess(), pMessageBoxInnerCall + 1, &uOffset, sizeof(uOffset), nullptr))
{
return TRUE;
}
uOffset += (UINT_PTR)pMessageBoxInnerCall;
// 获取NewMessageBoxW中Call的位置,修正call指令中的偏移
pMyMessageBoxInnerCall = (PBYTE)pfnNewMessageBoxW + 0x40;
uOffset = uOffset - (UINT)pMyMessageBoxInnerCall;
WriteProcessMemory(GetCurrentProcess(), pMyMessageBoxInnerCall + 1, &uOffset, sizeof(uOffset), nullptr);
return TRUE;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDC_BTN_OK:
// 如果在调试器中对MessageBoxW函数下了int3断点,有可能第一个字节被修改为0xCC
if (*(LPBYTE)pfnNewMessageBoxW == 0xCC)
*(LPBYTE)pfnNewMessageBoxW = 0x8B;
pfnNewMessageBoxW(hwndDlg, TEXT("内容"), TEXT("标题"), MB_OK);
break;
case IDCANCEL:
EndDialog(hwndDlg, 0);
break;
}
return TRUE;
}
return FALSE;
}
上面的代码在Win11 22H2自测通过,Debug版和Release版都可以,Debug版的区别就是MessageBoxW函数短一点,但是关键的call偏移没有变。