Internal Hack
这是一个“内部”的hack,内部的意思就是通过dll注入的方法将可执行代码直接注入到目标进程”内部“,并且新开始一个线程执行这部分代码。
内部代码具有隐蔽,高效的特点,但是注入部分其实是更难的部分,并且如果代码写的有问题,会更容易被检测。
下边是一个简单的实例代码,重要的部分或者API使用地方会被更加详细的说明。
实验程序为AssaultCube
DLL 模板
#include <iostream>
#include <Windows.h>
#include "mem.h"
#include "proc.h"
DWORD WINAPI HackThread(HMODULE hModule)
{
}
BOOL APIENTRY DLLMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
CloseHandle(CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)HackThread, hModule, 0, nullptr));
break;
case DLL_THREAD_ATTACH:
;
case DLL_THREAD_DETACH:
;
case DLL_PROCESS_DETACH:
break;
}
}
这里就是一个dll的模板,dllmain是一个swtich结构,其中四个case就是dll触发的原因,其实也挺清楚的,就是线程和进程的加载和分离。将要执行的事件写在case里面即可。
CreateThread
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
lpThreadAttributes
:安全属性,通常传nullptr
(默认安全)。dwStackSize
:线程栈大小,0
代表默认大小。lpStartAddress
:线程函数的指针(这里是HackThread
)。lpParameter
:传递给线程的参数(这里是hModule
)。dwCreationFlags
:控制线程创建状态,0
表示立即运行。lpThreadId
:返回线程 ID,传nullptr
忽略。
接下来分析下边代码就很容易了,默认安全属性
,默认大小
,第三个参数要写需要执行代码的位置,这里HackThread
是线程函数,必须是 DWORD WINAPI ThreadFunction(LPVOID lpParam)
这样的格式,第四个参数传递的是线程函数参数
,立即运行
,不需要知道线程的id
所以最后一个参数写零。
最外面套一个CloseHandle,表明关闭这个线程的句柄,不需要进一步操作了,但是线程其实已经在运行了。
CloseHandle(CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)HackThread, hModule, 0, nullptr));
线程函数
DWORD WINAPI HackThread(HMODULE hModule)
{
AllocConsole();
FILE* f;
freopen_s(&f, "CONOUT$", "w", stdout);
std::cout << "my internal\n";
uintptr_t moduleBase = (uintptr_t)GetModuleHandle(L"ac_client.exe");
bool bHealth = false, bAmmo = false, bRecoil = false;
while (true)
{
if (GetAsyncKeyState(VK_END) & 1)
{
break;
}
if (GetAsyncKeyState(VK_NUMPAD1) & 1)
{
bHealth = !bHealth;
}
if (GetAsyncKeyState(VK_NUMPAD2) & 1)
{
bAmmo = !bAmmo;
}
if (GetAsyncKeyState(VK_NUMPAD3) & 1)
{
bRecoil = !bRecoil;
if (bRecoil)
{
mem::Nop((BYTE*)moduleBase + 0x63786, 10);
}
else
{
mem::Patch((BYTE*)(moduleBase + 0x63786), (BYTE*)"\x50\x8D\x4C\x24\x1C\x51\x8B\xCE\xFF\xD2", 10);
}
}
uintptr_t* loaclPlayerPtr = (uintptr_t*)(moduleBase + 0x10f4f4);
if (loaclPlayerPtr)
{
if (bHealth)
{
*(int*)(*loaclPlayerPtr + 0xF8) = 1337;
}
if (bAmmo)
{
uintptr_t AmmoAddr = 0;
AmmoAddr = mem::FindDMAAddy((uintptr_t)loaclPlayerPtr, { 0x374, 0x14,0x0 });
*(int*)AmmoAddr = 1337;
}
}
Sleep(5);
}
fclose(f);
FreeConsole();
FreeLibraryAndExitThread(hModule, 0);
return 0;
}
线程代码大部分都是之前重复的功能,这里只是添加了一个控制台,让我们的代码注入是否成功更容易看到。
重复功能就不讲了,大概就是通过键盘按键来控制功能的开关。
// 创建新的控制台
AllocConsole();
// 创建新的文件指针 指向freopen_s重定向之后的文件
FILE* f;
// 将标准输出绑定到 CONOUT$ 终端输出,f会指向新的文件,这里就是CONOUT$
freopen_s(&f, "CONOUT$", "w", stdout);
std::cout << "my internal\n";
// 清扫工作 卸载dll
fclose(f);
FreeConsole();
FreeLibraryAndExitThread(hModule, 0);
成功注入之后就是这个样子。