文章目录
- 内存相关
- InlineHook
- 完整实现代码(dll):
- InlineHook测试:
内存相关
-
内存信息
头文件:#include <Psapi.h>
//检索有关系统当前使用物理内存和虚拟内存的信息 MEMORYSTATUSEX mst; GlobalMemoryStatusEx(&mst); //检索有关当前系统的信息 SYSTEM_INFO SysInfo; GetSystemInfo(&SysInfo); //检索当前进程的伪句柄 HANDLE hProcess = GetCurrentProcess(); //检索调用进程的进程标识符。 DWORD dwProcessId = GetCurrentProcessId(); //检索有关指定进程的内存使用情况的信息 PROCESS_MEMORY_COUNTERS pmc; GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(PROCESS_MEMORY_COUNTERS));
这里需要注意一点,上述API,后面带EX的通常可以跨进程获取信息,不带Ex的版本通常用来查询自身信息
-
常用:
查询虚拟内存属性,申请虚拟内存,修改属性
本地内存读写:
//保留、提交或更改调用进程的虚拟地址空间中页面区域的状态。 此函数分配的内存会自动初始化为零。 LPVOID lpAddr = VirtualAlloc( NULL, //要分配的内存的起始地址,如果为NULL,则由系统自动确定地址 0x100, //要分配的内存大小 MEM_COMMIT, //内存分配的类型,这里MEM_COMMIT基本上是通配了 PAGE_READONLY //分配内存的保护属性,这里PAGE_READONLY是只读属性,可以调用API修改的 ); //检索有关指定进程的虚拟地址空间中的页面范围的信息 MEMORY_BASIC_INFORMATION mbi; VirtualQueryEx( GetCurrentProcess(), //查询内存信息的进程句柄 lpAddr, //指向要查询的页面区域的基址指针 &mbi, //指向接收返回信息的MEMORY_BASIC_INFORMATION指针 sizeof(MEMORY_BASIC_INFORMATION) //lpBuffer指向的缓冲区大小 ); //更改指定进程的虚拟地址空间中已提交的页面区域的保护 DWORD OldProtect = 0; VirtualProtectEx( GetCurrentProcess(), //要修改内存保护的进程句柄 lpAddr, //要修改保护属性基址指针 0x100, //要修改保护属性的内存大小 PAGE_READWRITE, //修改的新的保护属性 &OldProtect //这里会写入原来的保护属性,为了避免被检测,通常修改之后还会修改回去 ); //给内存赋值,如果内存是只读属性,那么就会报错0x5(拒绝访问),需要修改内存属性 memcpy(lpAddr, L"123456789", sizeof(L"123456789")); //写完内存之后,我们修改回去 VirtualProtectEx(GetCurrentProcess(), lpAddr, 0x100, OldProtect, &OldProtect); memcpy(lpAddr, L"123456789", sizeof(L"123456789")); //释放虚拟内存 VirtualFreeEx( GetCurrentProcess(), //进程句柄 lpAddr, //指向要释放内存的起始指针 0, //要释放内存的大小,如果ddwFreeType属性为MEM_RELEASE,则必须为0 MEM_RELEASE //免费操作的类型 );
还是一样,这里所有API,Ex版本通常可以跨进程读写虚拟内存,不带Ex版本可以读写自身进程虚拟内存
-
远程读写内存:
ReadProcessMemory(); WriteProcessMemory();
-
堆
堆实际上是由一种数据结构进行管理的内存
我们用的new,malloc等都是在默认堆上申请,堆会随着我们申请空间而变大
默认堆,我们也可以自己创建堆结构:
//创建可由调用进程使用的专用堆对象 HANDLE hHeap = HeapCreate( HEAP_NO_SERIALIZE, //堆分配选项 0, //堆的起始大小 1024 //堆的最大大小 ); //从堆中分配内存块。分配的内存不可移动。 LPVOID lpHeapAddr = HeapAlloc( hHeap, //堆的句柄 HEAP_ZERO_MEMORY, //堆分配选项,这里是初始化为0 MAX_PATH //要分配的字节数 ); memcpy(lpHeapAddr, L"123456", sizeof(L"123456")); //释放从堆中申请的内存 HeapFree( hHeap, //堆的句柄 HEAP_NO_SERIALIZE, //堆免费选项,这里是不会使用序列化访问 lpHeapAddr //指向要释放内存的指针 ); //销毁指定堆对象 HeapDestroy(hHeap);
InlineHook
我们要HOOK的目标程序源码:
int main()
{
MessageBox(NULL, L"WdIg111", L"提示", NULL);
system("pause");
MessageBox(NULL, L"WdIg222", L"提示", NULL);
}
程序拖进x32dbg:
0030181E | 6A 00 | push 0 |
00301820 | 68 307B3000 | push <目标程序.L"\xFFD0:"> | 307B30:"衏:y"
00301825 | 68 F87B3000 | push <目标程序.L"WdIg222"> | 307BF8:L"WdIg222"
0030182A | 6A 00 | push 0 |
0030182C | FF15 98B03000 | call dword ptr ds:[<&MessageBoxW>] |
这是调用MessageBox的部分,有四个push是压入参数,我们来看看MessageBox实现部分:
770A8E20 | 8BFF | mov edi,edi |
770A8E22 | 55 | push ebp |
770A8E23 | 8BEC | mov ebp,esp |
770A8E25 | 833D 8C8C0D77 00 | cmp dword ptr ds:[770D8C8C],0 |
770A8E2C | 74 22 | je user32.770A8E50 |
770A8E2E | 64:A1 18000000 | mov eax,dword ptr fs:[18] |
770A8E34 | BA 18930D77 | mov edx,user32.770D9318 | edx:"榍\f"
770A8E39 | 8B48 24 | mov ecx,dword ptr ds:[eax+24] | ecx:"榍\f"
770A8E3C | 33C0 | xor eax,eax |
770A8E3E | F0:0FB10A | lock cmpxchg dword ptr ds:[edx],ecx | edx:"榍\f", ecx:"榍\f"
770A8E42 | 85C0 | test eax,eax |
770A8E44 | 75 0A | jne user32.770A8E50 |
770A8E46 | C705 288D0D77 01000000 | mov dword ptr ds:[770D8D28],1 |
770A8E50 | 6A FF | push FFFFFFFF |
770A8E52 | 6A 00 | push 0 |
770A8E54 | FF75 14 | push dword ptr ss:[ebp+14] |
770A8E57 | FF75 10 | push dword ptr ss:[ebp+10] |
770A8E5A | FF75 0C | push dword ptr ss:[ebp+C] |
770A8E5D | FF75 08 | push dword ptr ss:[ebp+8] |
770A8E60 | E8 0BFEFFFF | call <user32.MessageBoxTimeoutW> |
770A8E65 | 5D | pop ebp |
770A8E66 | C2 1000 | ret 10 |
这里就是MessageBox的实现了,那么我们要Hook它的实现,那么我们就要在这里做劫持,实际上就是修改前五个字节,做到jmp到我们构造的函数,
也就是:修改指令
770A8E20 | 8BFF | mov edi,edi |
770A8E22 | 55 | push ebp |
770A8E23 | 8BEC | mov ebp,esp |
那么为什么是修改5个字节呢?因为我们要做到劫持,jmp到我们的地址上去,jmp指令(E9)占据一个字节,而在32位环境下,地址是四个字节长度,所以我们只需要修改五个字节就可以了
-
实现:
主要思想,就是发布一个dll,注入到目标程序,而我们前一讲刚学过,dll里面case DLL_PROCESS_ATTACH,实在进程加载dll的时候,就会触发,我们将Hook在触发事件里完成
完整实现代码(dll):
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
//全局变量
//目标函数地址(被Hook的函数)
PROC m_FuncAddr = NULL;
//保存被我们修改的5个字节数据
BYTE m_OldByte[5] = { 0 };
//我们修改的5个字节数据
BYTE m_NewByte[5] = { 0 };
//Hook(这里我们需要:目标函数所在模块名称,目标函数名,劫持流程的函数地址)
BOOL InlineHook(const WCHAR* pszModuleName, const char* pszFuncName, PROC pfnHookFunc) {
//获取模块句柄
HMODULE hModule = GetModuleHandleW(pszModuleName);
//从指定的动态链接库 (DLL) 检索导出函数 (也称为过程) 或变量,返回地址
m_FuncAddr = GetProcAddress(hModule, pszFuncName);
if (m_FuncAddr == NULL) {
return FALSE;
}
//读取指定进程内存
SIZE_T dwReadSize = 0;
BOOL bRet = ReadProcessMemory(
GetCurrentProcess(), //由于我们注入到了目标进程,这里直接获取自身的进程句柄就可以了
m_FuncAddr, //要读取内存的起始地址
m_OldByte, //接收读取的内容指针
5, //指定读取大小
&dwReadSize //接收实际读取大小指针
);
//判断是否读取成功
if (!bRet||dwReadSize != 5) {
return FALSE;
}
//构造新的5字节
m_NewByte[0] = '\xE9';
//这里是要跳转的地址,注意计算公式:要跳转的地址 = 要执行的地址 - 原来执行地址 -指令长度
*(DWORD*)(m_NewByte + 1) = (DWORD)pfnHookFunc - (DWORD)m_FuncAddr - 5;
SIZE_T dwWrittenByte = 0;
bRet = WriteProcessMemory(
GetCurrentProcess(),
m_FuncAddr,
m_NewByte,
5,
&dwWrittenByte
);
if (!bRet || dwWrittenByte != 5) {
return FALSE;
}
else {
return TRUE;
}
}
//修改完执行后,我们还需要改回去,不然就进入了死循环
VOID UnHook() {
if (m_FuncAddr != NULL) {
DWORD dwWrittenByte = 0;
WriteProcessMemory(GetCurrentProcess(), m_FuncAddr, m_OldByte, 5, &dwWrittenByte);
}
}
VOID ReHook() {
if (m_FuncAddr != NULL) {
DWORD dwWrittenByte = 0;
WriteProcessMemory(GetCurrentProcess(), m_FuncAddr, m_NewByte, 5, &dwWrittenByte);
}
}
//这里构造我们自己的MessageBox(做函数转发)
int
WINAPI
MyMessageBox(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType) {
//恢复
UnHook();
int bRet = MessageBox(hWnd, L"Hook", L"InlineHook", uType);
//重新挂钩
ReHook();
return bRet;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
InlineHook(L"User32.dll", "MessageBoxW", (PROC)MyMessageBox);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
InlineHook测试:
-
- 执行目标程序:
- 执行目标程序:
-
- 注入dll:
- 注入dll:
- Hook成功: