Game Hacking #1: AssaultCube Infinite Ammo and DLL Injection

n1njasec
11 min readNov 12, 2022

--

Neste post, falarei diretamente sobre Game Hacking e Injeção de DLL em processos de computador.

- Categorias

  • Conhecendo o Cheat Engine
  • Memory View com Cheat Engine
  • O que é uma DLL? DLL Injection?
  • Criando o Injetor da DLL
  • Criando a DLL
  • Pegando os endereços do AssaultCube
  • Carregando a DLL em um processo

- Ferramentas utilizadas:

- Cheat Engine (Windows x86_64)
- Assault Cube (Windows x86_64)
- Visual Studio 2022 (Windows x86_64)
- Process Hacker (Windows x86_64)

- Conhecendo o Cheat Engine

  • Bom, o Cheat Engine é um software de código aberto (open-source) que modifica valores de programas/jogos com finalidade de ganhar mais pontos, obter vida infinita, poderes infinitos e etc. Estamos falando de uma ferramenta perfeita para trabalhar com Game Hacking, portanto, ao longo de diversos artigos que estarei compartilhando aqui no medium, estarei ensinando diversas técnicas utilizando o CE (Cheat Engine). (Acostume-se com a sigla CE! Muito utilizada em fóruns de Game Hacking para citar o Cheat Engine.)

- Memory View com Cheat Engine

  • O que é Memory View e o que podemos conseguir com essa técnica? Memory View contém várias ferramentas e funções para hackers de jogos avançados, como o mecanismo de autoassembler, um scanner de codecave, um gerador de threads, um alocador de memória, um dissector de código, um injetor de DLL, opções de depuração, um desmontador, um montador, um visualizador hexadecimal e mais

- O que é uma DLL? DLL Injection?

  • Uma DLL, também conhecida como (Dynamic-Link Library), é uma biblioteca que contém códigos e dados que podem ser utilizado por mais de um programa ao mesmo tempo. Por exemplo, em sistemas operacionais Windows, a DLL Comdlg32 executa funções comuns relacionadas à caixa de diálogo.
  • DLL Injection é uma técnica utilizada para injetar uma DLL maliciosa dentro de do espaço de endereçamento de determinado(s) processo(s) carregando uma biblioteca dinâmica externa. Um exemplo bastante comum, é utilizado por Malwares que utilizam desse método para fazer qualquer coisa dentro do sistema operacional.
  • Em Game Hacking, essa técnica também é bastante utilizada para fazer injeção dos módulos de um cheat, como wallhack, aimbot, esp, fly mode e etc.

- Criando o Injetor da DLL

  • Vamos dar início a criação do nosso loader de DLL ou (DLL Injector). Vocês precisarão ter instalado em vossa máquina o Visual Studio 2022 (recomendável). Agora vamos fazer a configuração do nosso Visual Studio 2022 para que a compilação do nosso loader seja feita corretamente.
  • Vá até a aba “Projeto” e logo em seguida, clique em “Propriedades de NomeDoSeuProjeto”.
  • Agora será exibido uma caixa de configuração. O seu passo agora é seguir as seguintes instruções: “Propriedades de Configuração -> Avançado -> Conjunto de Caracteres”. O que você fará agora é modificar de “Usar Conjunto de Caracteres Unicode” para “User Conjunto de Caracteres Multibyte” e clique em Aplicar e depois clique em OK.
  • Próximo passo é começar a escrever o nosso loader. Siga meus passos e estarei explicando toda a estrutura do código abaixo.
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <TlHelp32.h>
#include <iostream>
#include <fstream>
#include <string>

char evilDLL[] = "C:\\Loader.dll";
unsigned int evilLen = sizeof(evilDLL) + 1;

int main(int argc, char* argv[]) {

HANDLE ph; // process handle - ph
HANDLE rt; // remote thread - rt
LPVOID rb; // remote buffer - rb

HMODULE hKernel32 = GetModuleHandle("Kernel32");
VOID* lb = GetProcAddress(hKernel32, "LoadLibraryA");

// parse process ID
if (atoi(argv[1]) == 0) {
printf("PID não encontrado...\n");
return -1;
}

printf("PID: %i", atoi(argv[1]));
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));
// Alocar um buffer de memória para o processo remoto
rb = VirtualAllocEx(ph, NULL, evilLen, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
// "copiar" DLL entre os processos que iremos injetar
WriteProcessMemory(ph, rb, evilDLL, evilLen, NULL);

// Nosso processo inicia um novo thread
rt = CreateRemoteThread(ph, NULL, 0, (LPTHREAD_START_ROUTINE)lb, rb, 0, NULL);
CloseHandle(ph);
return 0;
}
  • Vamos focar diretamente nas partes mais interessantes do nosso código.
if (atoi(argv[1]) == 0) {
printf("PID não encontrado...\n");
return -1;
}
  • Nesta parte do código, estamos informando que deveremos passar um argumento junto com o nosso loader, nesse caso, o argumento será o PID de um programa que esteja rodando. Caso o PID estiver incorreto ou não existir, simplesmente exibirá um erro dizendo que esse PID não foi encontrado. (PID é o número que identifica um determinado processo. Você verá isso mais abaixo)
printf("PID: %i", atoi(argv[1]));
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));
// Alocar um buffer de memória para o processo remoto
rb = VirtualAllocEx(ph, NULL, evilLen, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
// "copiar" DLL entre os processos que iremos injetar
WriteProcessMemory(ph, rb, evilDLL, evilLen, NULL);
  • Temos aqui a parte principal do nosso loader, é aqui que toda brincadeira acontecerá. Bom, caso o PID existir e tudo for passado de forma correta, o loader exibirá o número do PID que foi digitado e logo após ele alocará um buffer de memória no processo. Após isso acontecer, o loader escreverá o processo com a nossa DLL.
// Nosso processo inicia um novo thread
rt = CreateRemoteThread(ph, NULL, 0, (LPTHREAD_START_ROUTINE)lb, rb, 0, NULL);
CloseHandle(ph);
  • E por final, o nosso loader criará um novo thread da nossa DLL.
  • Compile o loader clicando em “Depurador Local do Windows”. É provável que um erro seja exibido mas não se preocupe, é normal, já que a nossa DLL não foi criada.

- Criando a DLL

Bom, precisamos ter em mente como funciona a estrutura de uma DLL em C++. Para isso, em seu Visual Studio 2022, crie um novo projeto com o Template de Dynamic-Link Librarie. Será criado alguns arquivo, dentre eles a “dllmain.cpp” e os arquivos de cabeçalho “pch.h” e “framework.h”.

O Visual Studio estará te forçando que você utilize essas bibliotecas e para desabilitar essa opção, clique em “Projeto -> Propriedade do Projeto -> Propriedades de Configuração -> C/C++ -> Cabeçalhos Pré-compilados” e mude para “Não usar Cabeçalho Pré-Compilado”.

  • Clique em Aplicar e depois em OK.
  • Agora vamos ao que nos interessa. Precisamos entender como funciona a estrutura de uma DLL em C++. Diferente da estrutura de um código que será compilado e se tornará um .exe, a .dll ela possuí a sua “main” nomeada de “Dllmain” e o compilador consegue distinguir o que será uma .dll e o que será um .exe!
#include <Windows.h>
#include <TlHelp32.h>
#include <stdlib.h>
#include <tchar.h>
#include <iostream>
#include <string>
#include <vector>

using namespace std;

DWORD GetModuleBaseAddress(TCHAR* lpszModuleName, DWORD pID) {
DWORD dwModuleBaseAddress = 0;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pID); // make snapshot of all modules within process
MODULEENTRY32 ModuleEntry32 = { 0 };
ModuleEntry32.dwSize = sizeof(MODULEENTRY32);

if (Module32First(hSnapshot, &ModuleEntry32)) //store first Module in ModuleEntry32
{
do {
if (_tcscmp(ModuleEntry32.szModule, lpszModuleName) == 0) // if Found Module matches Module we look for -> done!
{
dwModuleBaseAddress = (DWORD)ModuleEntry32.modBaseAddr;
break;
}
} while (Module32Next(hSnapshot, &ModuleEntry32)); // go through Module entries in Snapshot and store in ModuleEntry32


}
CloseHandle(hSnapshot);
return dwModuleBaseAddress;
}

DWORD GetPointerAddress(HWND hwnd, DWORD gameBaseAddr, DWORD address, vector<DWORD> offsets)
{
DWORD pID = NULL; // Game process ID
GetWindowThreadProcessId(hwnd, &pID);
HANDLE phandle = NULL;
phandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pID);
if (phandle == INVALID_HANDLE_VALUE || phandle == NULL);

DWORD offset_null = NULL;
ReadProcessMemory(phandle, (LPVOID*)(gameBaseAddr + address), &offset_null, sizeof(offset_null), 0);
DWORD pointeraddress = offset_null; // the address we need
for (int i = 0; i < offsets.size() - 1; i++) // we dont want to change the last offset value so we do -1
{
ReadProcessMemory(phandle, (LPVOID*)(pointeraddress + offsets.at(i)), &pointeraddress, sizeof(pointeraddress), 0);
}
return pointeraddress += offsets.at(offsets.size() - 1); // adding the last offset
}


bool WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID lpReserved) {
if (dwReason == DLL_PROCESS_ATTACH) {
HWND hwnd_AC = FindWindowA(NULL, "AssaultCube");

if (hwnd_AC != FALSE);
DWORD pID = NULL;
GetWindowThreadProcessId(hwnd_AC, &pID);
HANDLE phandle = NULL;
phandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pID);
if (phandle == INVALID_HANDLE_VALUE || phandle == NULL);

char GameModule1[] = "ac_client.exe";
DWORD GameBaseAddress1 = GetModuleBaseAddress(_T(GameModule1), pID);

//Ammo
DWORD AmmoAddr = 0x018083C;
vector<DWORD> AmmoOffsets{ 0x8, 0x614, 0x64, 0x30, 0x30, 0x30, 0x6DC };
DWORD AmmoPtrAddr = GetPointerAddress(hwnd_AC, GameBaseAddress1, AmmoAddr, AmmoOffsets);

// Writing Memory
while (true) {
int ammo = 999;
WriteProcessMemory(phandle, (LPVOID*)(AmmoPtrAddr), &ammo, 4, 0);
}
}
return true;
}
  • Bom, vou explicar o código e mostrar que não há nada de complexo.
if (dwReason == DLL_PROCESS_ATTACH) {
HWND hwnd_AC = FindWindowA(NULL, "AssaultCube");

if (hwnd_AC != FALSE);
DWORD pID = NULL;
GetWindowThreadProcessId(hwnd_AC, &pID);
HANDLE phandle = NULL;
phandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pID);
if (phandle == INVALID_HANDLE_VALUE || phandle == NULL);

char GameModule1[] = "ac_client.exe";
DWORD GameBaseAddress1 = GetModuleBaseAddress(_T(GameModule1), pID);

//Ammo
DWORD AmmoAddr = 0x018083C;
vector<DWORD> AmmoOffsets{ 0x8, 0x614, 0x64, 0x30, 0x30, 0x30, 0x6DC };
DWORD AmmoPtrAddr = GetPointerAddress(hwnd_AC, GameBaseAddress1, AmmoAddr, AmmoOffsets);

// Writing Memory
while (true) {
int ammo = 999;
WriteProcessMemory(phandle, (LPVOID*)(AmmoPtrAddr), &ammo, 4, 0);
}
}
  • Aqui temos o corpo da nossa DLL, é aqui que toda injeção ocorrerá, no caso, toda essa função será injetada. Resumidamente explicando, a DLL irá procurar por uma janela chamada “AssaultCube” e caso seja verdadeiro (true), ela continuará executando a DLL. A DLL estará procurando pelo processo “ac_client.exe” e se ela for encontrada, a memória do processo será meapeado até encontrar o endereço “0x018083C” e setará os Offsets { 0x8, 0x614, 0x64, 0x30, 0x30, 0x30, 0x6DC } no ponteiro do endereço encontrado na memória. Em seguida caso todo esse processo for 100% realizado, a DLL entrará em um loop infinito na qual estará escrevendo o processo da memória injetando o valor “999” que está sendo definido na variável “ammo”. Para que o processo da DLL seja interrompido, será necessário fechar o programa na qual foi o alvo de injeção da DLL.

- Pegando os endereços do AssaultCube

  • Abra o AssaultCube e o Cheat Engine (Link para instalação dos dois itens está no topo desse post.) Vá até o Cheat Engine e selecione o processo do AssaultCube.
  • Perfeito! Depois de ter feito esses passos, vamos agora procurar pelo endereço de memória da munição. Para isso, será preciso que você faça alguns testes dentro do jogo e o CE. Siga os passos do Gif abaixo!
  • Repita o mesmo processo para obter o endereço correto. Caso surja alguns outros endereços, procure modificar o valor para cada um deles, não ponha o mesmo valor para todo tipo de endereço, isso dificulta descobrir qual é o endereço correto.
  • Tenha em mente que esse endereço não é estático, portanto, a cada vez que você fechar o jogo e abrir novamente, ele mudará. Então toda vez que eu abrir o jogo eu terei que repetir todo esse processo? Para a sua felicidade, não! Existe uma forma bastante legal e que vai ajudar nesse processo. Existem ponteiro que apontam para o mesmo endereços diferentes toda vez em que o jogo é reaberto e para conseguirmos pegar esse ponteiro é necessário que alguns testes sejam feitos! Sigam os passos do mestre.
  • Aqui estamos buscando os ponteiros. Observeque eu selecionei mais de um ponteiro e o nome do Base Address de todos eles era “ac_client.exe”, pois estamos buscando especificamente os ponteiros do processo do AssaultCube. Para descobrir qual é o ponteiro correto, basta fechar o jogo e abrir novamente, caso algum ponteiro exiba resultados como “???” ou mais de um ponteiro retorne o mesmo endereço de memória, delete todos e apenas deixe um ponteiro.
  • Agora, vamos procurar pelo nosso endereço estático e pelos Offsets. Para isso, em cima do ponteiro que você encontrou, clique duas vezes com o botão esquerdo do mouse.
  • Uma caixa como essa aparecerá e talvez esteja diferente do meu, pois pode ser que aparecera alguns Offsets a mais ou então apenas um. O que está sendo exibido é apenas uma soma de “Offset + hex” e isso gera um endereço que o nosso ponteiro estará apontando. Lembra do que eu disse um pouco acima? Os Offsets que serão setados “{ 0x0, 0x0, 0x0 }” são os “hex” que estão somando com algum endereço. Pegue estes Offsets, de baixo para cima, e coloque dentro do código em “AmmoOffsets”. O nosso endereço estático está sendo somado com o nome do processo do AssaultCube, no meu caso é o “00187C0C”. Obviamente este endereço será diferente, portanto, pegue o endereço que será exibido na sua máquina e coloque-o em “AmmoAddr” no seu código.
  • Perfeito! Depois de ter feito esse processo, é hora de compilar a DLL. Para isso, clique em “Depurador Local do Windows” e será exibido um erro. É normal! Pois tentar executar uma DLL igual executamos um arquivo .exe é totalmente diferente, portanto, vá até o diretório do seu projeto, copie a DLL que foi compilada e coloque no diretório raiz do Windows (C:\).

- Carregando a DLL em um processo

  • Chegou a hora de injetar a nossa DLL! Para isso, procure por um processo ou então abra o notepad e vamos utilizá-lo como alvo. Agora abra o Process Hacker, pois ele é um gerenciador de processo bem mais completo e você observará por ele que a DLL foi carregada.
  • Copie o PID do notepad e agora vamos executar o nosso loader. (Vale ressaltar que, o PID é diferente em cada máquina/vezes em que você abre determinado programa.)
  • Preste bem atenção, pois dá para perceber que você injetou a DLL quando o notepad começa a utilizar a CPU sem estar utilizando o notepad. Vamos conferir na memória se ela realmente foi carregada, para isso, clique com o botão direito do mouse em cima do processo e vá em “propriedades”. Uma caixa será exibido, clique em “memória” e desça com o scroll do mouse e fique de olho na coluna “tipo”, pois uma DLL é carregada como tipo “imagem”.
  • Veja! DLL injetada! E claro, confira o AssaultCube e você observará que a quantidade de munição estará como “999”. Esse valor não mudará, pois lembre-se que a DLL entra em um loop infinito de recarga, então a cada tiro que você der não será decrementado nenhuma munição da sua arma.

“de-me seis horas para derrubar uma árvore e passarei as quatro primeiras afiando o machado.”
- Abraham Lincoln

BOM, ISSO É TUDO, PESSOAL! ESPERO VOCÊS NO PRÓXIMO POST! ATÉ BREVE :)

-# L1za left…

--

--

n1njasec
n1njasec

Written by n1njasec

Apenas um brasileiro apaixonado por hacking

No responses yet