Postado Sex Set 02, 2016 10:39 pm
Memory Hacking.
Os códigos de programação deste tutorial destinam-se, exclusivamente, para fins de estudos e pesquisas. Sua utilização para outras finalidades é de responsabilidade de seus usuários.
Denomina-se memory hacking o ato de mexer, de forma interna, em uma porção da memória principal do computador que é restrita a um determinado programa .
Com isso é possível, por exemplo, fazer um processo que mude o valor de uma variável de outro processo.
A Windows API será utilizada nos códigos, portanto, estes só funcionarão em ambiente Windows .
Também trabalharemos com o jogo Grand Theft Auto: San Andreas 1.0, software de código fechado que permite modificações de acordo com sua licença.
1 - Requerimentos.
Consideremos os seguintes tipos de endereços de memória :
Físico - O endereço físico é o utilizado pelo processador para manipular a memória principal. Na presença de endereços virtuais, ele é obtido pelo núcleo do sistema operacional com uma conversão.
Lógico - O lógico é aquele que é trabalhado nos programas, em linguagens como C e Assembly. Ele pode ser igual ao endereço físico ou igual ao endereço virtual, dependendo do sistema operacional.
Virtual - É um endereço especial. Em alguns casos, como no Windows, os processos são forçados a mexerem em endereços dessa categoria para torná-los isolados uns dos outros, evitar que o sistema seja derrubado por seus erros de execução, etc .
Base - Endereço que serve como referencial a todo um bloco de dados.
Estático - Esse endereço sempre é representado pelo mesmo número e aponta para o mesmo dado na memória.
Dinâmico - É o endereço de uma informação o qual muda.
Atualmente, o Windows e muitos sistemas operacionais fazem com que os programas executados neles considerem "endereços virtuais" como endereços de memória.
Por isso, seria impossível construir um executável que, só com ponteiros, efetuasse um memory hacking em outro .
Contudo, no Windows, com privilégios administrativos, pode-se elaborar técnicas para o memory hacking com a Windows API. Algumas destas serão abordadas a seguir.
2 - WriteProcessMemory.
O nome dessa função significa "escrever memória de processo" .
Ela serve basicamente para alterar o valor de uma variável de um processo específico.
Veremos agora como ficaria um código que mudasse a quantidade de dinheiro do personagem do jogo GTA pelo nome de seu executável:
Código:
- Código:
#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>
int main(void)
{
puts("Valor em dado do processo modificado.");
getchar();
return 0;
}
Os arquivos de cabeçalho stdio.h, windows.h e tlhelp32.h definiriam as funções puts e getchar, muitas funções da Windows API, e, a funçãoCreateToolhelp32Snapshot, respectivamente.
A partir daqui, os códigos mostrados ficariam no main.
Código:
- Código:
HANDLE ProcessoHandle;
DWORD Valor = 500;
LPVOID Endereco = (LPVOID) 0xB7CE50;
De modo mais abstrato, a variável Valor conteria o total de dinheiro no jogo GTA: San Andreas e a variável Endereco, o seu endereço estático (pois sempre é 0xB7CE50) .
Sendo gta_sa.exe o nome do executável que originaria o processo do GTA, o próximo código seria:
Código:
- Código:
HANDLE SnapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
PROCESSENTRY32 Lista;
Lista.dwSize = sizeof(PROCESSENTRY32);
Process32First(SnapshotHandle, &Lista);
do
{
if(!strcmp(Lista.szExeFile, "gta_sa.exe"))
{
ProcessoHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Lista.th32ProcessID);
}
}
while(Process32Next(SnapshotHandle, &Lista));
CloseHandle(SnapshotHandle);
Em seguida declaramos uma struct de nome Lista para guardar as informações dos processos do snapshot, um de cada vez.
A chamada do Process32First grava as informações do primeiro processo em Lista, permitindo que o nome de seu executável seja acessado na construção if que é lida logo depois .
Nesse if, verifica-se se aquele nome é igual a gta_sa.exe. Caso sim, a função OpenProcess gera um handle para o processo (pelo seu ID, também obtido de Lista), que é armazenado na variável ProcessoHandle (lembra dela ?).
Os outros processos são analisados devido às execuções do Process32Next, que ficou no while justamente para percorrer todos eles.
Por fim, CloseHandle torna inválido o handle do snapshot que utilizamos, uma vez que não precisamos mais dele.
Agora, lembra do primeiro argumento do OpenProcess ? Ele era PROCESS_ALL_ACCESS, constante que oferece, entre outros, o direito de escrever na memória do processo indicado.
Com isso, já podemos usar a função WriteProcessMemory, e efetuar o memory hacking :
Código:
- Código:
WriteProcessMemory(ProcessoHandle, Endereco, &Valor, sizeof(Valor), NULL);
A quantidade de dinheiro no jogo seria modificada também.
Só faltaria então, invalir o handle do processo que criamos:
Código:
- Código:
CloseHandle(ProcessoHandle);
O código final seria :
Código:
- Código:
#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>
int main(void)
{
HANDLE ProcessoHandle;
DWORD Valor = 500;
LPVOID Endereco = (LPVOID) 0xB7CE50;
HANDLE SnapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
PROCESSENTRY32 Lista;
Lista.dwSize = sizeof(PROCESSENTRY32);
Process32First(SnapshotHandle, &Lista);
do
{
if(!strcmp(Lista.szExeFile, "gta_sa.exe"))
{
ProcessoHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Lista.th32ProcessID);
}
}
while(Process32Next(SnapshotHandle, &Lista));
CloseHandle(SnapshotHandle);
WriteProcessMemory(ProcessoHandle, Endereco, &Valor, sizeof(Valor), NULL);
CloseHandle(ProcessoHandle);
puts("Valor em dado do processo modificado.");
getchar();
return 0;
}
3 - ReadProcessMemory.
O nome dessa função, por sua vez, significa "ler memória de processo".
Em termos de código, a aplicação dela difere do WriteProcessMemory principalmente em sua chamada :
Código:
- Código:
ReadProcessMemory(ProcessoHandle, Endereco, &Valor, sizeof(Valor), NULL);
Se efetuada no GTA, por exemplo, essa função serviria para obter dados do jogo, como a quantidade de vida do personagem.
Exemplo em código completo de executável :
Código:
- Código:
#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>
int main(void)
{
HANDLE ProcessoHandle;
DWORD Valor;
LPVOID Endereco = (LPVOID) 0xB7CE50;
HANDLE SnapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
PROCESSENTRY32 Lista;
Lista.dwSize = sizeof(PROCESSENTRY32);
Process32First(SnapshotHandle, &Lista);
do
{
if(!strcmp(Lista.szExeFile, "gta_sa.exe"))
{
ProcessoHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Lista.th32ProcessID);
}
}
while(Process32Next(SnapshotHandle, &Lista));
CloseHandle(SnapshotHandle);
ReadProcessMemory(ProcessoHandle, Endereco, &Valor, sizeof(Valor), NULL);
CloseHandle(ProcessoHandle);
printf("Valor em dado do processo obtido: %d.", Valor);
getchar();
return 0;
}
4 - Injeção de DLL.
Essa é a mais complexa das técnicas, porém fornece um controle melhor.
Fundamenta-se em forçar um processo a carregar uma DLL (biblioteca dinâmica) com códigos de memory hacking.
O que é acontece é que, com a DLL carregada, as memórias dela e do processo ficam unidas, permitindo que um modifique o outro diretamente.
A maneira mais simples de lidar com o processo através da DLL é com a keyword __asm do Microsoft C .
Isso quer dizer que, mexeremos, dentro de códigos C, com um pouco de Assembly nesta seção do tutorial.
Vamos, antes disso, construir a função para injetar a DLL:
Código:
- Código:
VOID InjetarDLL(HANDLE ProcessoHandle, PCHAR CaminhoDLL)
{
}
A partir daqui, os códigos citados ficariam nessa função .
Código:
- Código:
INT TamanhoCaminho = strlen(CaminhoDLL) + 1;
LPVOID EnderecoFoco = VirtualAllocEx(ProcessoHandle, NULL, TamanhoCaminho, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(ProcessoHandle, EnderecoFoco, CaminhoDLL, TamanhoCaminho, NULL);
O VirtualAllocEx reservaria memória no espaço do processo para colocar o caminho da DLL lá. O ponteiro EnderecoFoco guardaria o valor retornado: o endereço base do conjunto de dados .
E então, WriteProcessMemory preencheria aquela porção de memória com o caminho da DLL.
Código:
- Código:
HMODULE Kernel32Handle = GetModuleHandle("kernel32.dll");
LPTHREAD_START_ROUTINE Kernel32Funcao = (LPTHREAD_START_ROUTINE) GetProcAddress(Kernel32Handle, "LoadLibraryA");
HANDLE ThreadHandle = CreateRemoteThread(ProcessoHandle, NULL, 0, Kernel32Funcao, EnderecoFoco, 0, NULL);
WaitForSingleObject(ThreadHandle, INFINITE);
Depois, cria-se a variável Kernel32Funcao, que armazena o endereço da DLL correspondente à função LoadLibraryA .
Poteriormente a função CreateRemoteThread é executada com algumas das variáveis que declaramos. Isso resulta em uma nova thread (sub-processo) para o processo no qual queremos fazer o memory hacking.
Acontece que a nova thread acaba tendo o LoadLibraryA como sua função principal e o endereço do caminho da DLL (que reservamos anteriormente) como o argumento dela.
Isso já faz com que a DLL seja carregada na memória do processo .
O WaitForSingleObject espera a execução da thread terminar, para que possamos remover tudo que agora é desnecessário:
Código:
- Código:
DWORD DLLHandle;
GetExitCodeThread(ThreadHandle, &DLLHandle);
CloseHandle(ThreadHandle);
VirtualFreeEx(ProcessoHandle, EnderecoFoco, TamanhoCaminho, MEM_RELEASE);
Em seguida, o CloseHandle destrói a thread e invalida seu handle .
O VirtualFreeEx libera a string do caminho da DLL.
Código:
- Código:
Kernel32Funcao = (LPTHREAD_START_ROUTINE) GetProcAddress(Kernel32Handle, "FreeLibrary");
ThreadHandle = CreateRemoteThread(ProcessoHandle, NULL, 0, Kernel32Funcao, (LPVOID) DLLHandle, 0, NULL);
WaitForSingleObject(ThreadHandle, INFINITE);
CloseHandle(ThreadHandle);
O WaitForSingleObject espera o processamento da thread terminar, e, por fim, o CloseHandle destrói-a e invalida seu handle.
O código final:
Código:
- Código:
VOID InjetarDLL(HANDLE ProcessoHandle, PCHAR CaminhoDLL)
{
INT TamanhoCaminho = strlen(CaminhoDLL) + 1;
LPVOID EnderecoFoco = VirtualAllocEx(ProcessoHandle, NULL, TamanhoCaminho, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(ProcessoHandle, EnderecoFoco, CaminhoDLL, TamanhoCaminho, NULL);
HMODULE Kernel32Handle = GetModuleHandle("kernel32.dll");
LPTHREAD_START_ROUTINE Kernel32Funcao = (LPTHREAD_START_ROUTINE) GetProcAddress(Kernel32Handle, "LoadLibraryA");
HANDLE ThreadHandle = CreateRemoteThread(ProcessoHandle, NULL, 0, Kernel32Funcao, EnderecoFoco, 0, NULL);
WaitForSingleObject(ThreadHandle, INFINITE);
DWORD DLLHandle;
GetExitCodeThread(ThreadHandle, &DLLHandle);
CloseHandle(ThreadHandle);
VirtualFreeEx(ProcessoHandle, EnderecoFoco, TamanhoCaminho, MEM_RELEASE);
Kernel32Funcao = (LPTHREAD_START_ROUTINE) GetProcAddress(Kernel32Handle, "FreeLibrary");
ThreadHandle = CreateRemoteThread(ProcessoHandle, NULL, 0, Kernel32Funcao, (LPVOID) DLLHandle, 0, NULL);
WaitForSingleObject(ThreadHandle, INFINITE);
CloseHandle(ThreadHandle);
}
A função de injetar DLL deveria ser chamada em locais como aquele, onde há um handle de processo válido, etc.
Vejamos como ficaria o source da DLL :
Código:
- Código:
#include <windows.h>
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void *lpReserved)
{
return 1;
}
Código:
- Código:
#define Endereco 0x863984
FLOAT Valor = 0.0005;
__asm
{
mov eax, Valor
mov [Endereco], eax
}
Código:
- Código:
#define Endereco 0x863984
FLOAT Valor;
__asm
{
mov eax, [Endereco]
mov Valor, eax
}
E a melhor parte, subprogramas :
Código:
- Código:
#define Endereco 0x439600
__asm
{
mov eax, Endereco
call eax
}
O resultado no jogo seria a obtenção de uma jetpack (mochila a jato).
5 - Outros.
Os endereços do GTA trabalhados até o momento eram estáticos: apontavam sempre para os mesmos elementos do jogo.
Quando um endereço de um mesmo dado varia, ele é dinâmico .
Seria preciso outro dado de endereço fixo que atuasse como referencial, se quiséssemos obter um valor em um endereço dinâmico.
No caso do GTA, por exemplo, há a vida do jogador.
Para conseguir seu endereço, deve-se realizar este cálculo:
(Valor no endereço 0xB6F5F0) + (0x540)
Programas mapeadores de memória, como o Cheat Engine, ajudam a encontrar endereços de dados específicos .
Espero ter ajudado .