A manipulação de memória de processos externos é uma técnica comum em engenharia reversa e no desenvolvimento de ferramentas de automação ou modificação de software (como trainers de jogos). No ecossistema .NET, essa tarefa é realizada através de chamadas de plataforma (P/Invoke) para interagir com a API nativa do Windows (kernel32.dll).
1. Importação de APIs do Windows
Para ler e escrever na memória de outro processo, precisamos das funções OpenProcess, ReadProcessMemory e WriteProcessMemory. Abaixo, apresentamos uma implementação encaspulada em uma classe utilitária.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace GestorMemoria
{
public static class OperacoesMemoria
{
// Permissões de acesso ao processo
private const int PROCESS_ALL_ACCESS = 0x1F0FFF;
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll")]
public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);
[DllImport("kernel32.dll")]
public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, out IntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll")]
public static extern bool CloseHandle(IntPtr hObject);
public static int ObterIdPeloNome(string nomeProcesso)
{
Process[] processos = Process.GetProcessesByName(nomeProcesso);
return processos.Length > 0 ? processos[0].Id : 0;
}
public static int LerInteiro(IntPtr handle, int endereco)
{
byte[] buffer = new byte[4];
ReadProcessMemory(handle, (IntPtr)endereco, buffer, buffer.Length, out _);
return BitConverter.ToInt32(buffer, 0);
}
public static void EscreverInteiro(IntPtr handle, int endereco, int valor)
{
byte[] dados = BitConverter.GetBytes(valor);
WriteProcessMemory(handle, (IntPtr)endereco, dados, dados.Length, out _);
}
}
}
2. Implementação da Lógica de Negócio
Abaixo, demonstramos como aplicar essa lógica para modificar valores em um jogo, utilizando cálculos de endereços base e offsets (deslocamentos). Neste exemplo, tratamos da modificação de recursos como "Sol" e "Dinheiro" em um processo específico.
using System;
using System.Windows.Forms;
namespace GestorMemoria
{
public partial class FormPrincipal : Form
{
private string nomeProcesso = "PlantsVsZombies";
private int enderecoBaseGlobal = 0x006A9EC0;
public FormPrincipal()
{
InitializeComponent();
}
private void AtualizarRecursos()
{
int pid = OperacoesMemoria.ObterIdPeloNome(nomeProcesso);
if (pid == 0) return;
IntPtr handle = OperacoesMemoria.OpenProcess(0x1F0FFF, false, pid);
if (handle == IntPtr.Zero) return;
try
{
// Lógica para Sol (Ponteiro Multinível)
int nivel1 = OperacoesMemoria.LerInteiro(handle, enderecoBaseGlobal);
int enderecoSol = OperacoesMemoria.LerInteiro(handle, nivel1 + 0x768) + 0x5560;
OperacoesMemoria.EscreverInteiro(handle, enderecoSol, 9999);
// Lógica para Dinheiro
int nivelDinheiro = OperacoesMemoria.LerInteiro(handle, enderecoBaseGlobal);
int enderecoDinheiro = OperacoesMemoria.LerInteiro(handle, nivelDinheiro + 0x82C) + 0x28;
OperacoesMemoria.EscreverInteiro(handle, enderecoDinheiro, 150000);
}
finally
{
OperacoesMemoria.CloseHandle(handle);
}
}
private void btnAtivar_Click(object sender, EventArgs e)
{
if (OperacoesMemoria.ObterIdPeloNome(nomeProcesso) != 0)
{
timerModificacao.Start();
btnAtivar.Text = "Modificando...";
}
else
{
MessageBox.Show("O processo alvo não foi encontrado.");
}
}
private void timerModificacao_Tick(object sender, EventArgs e)
{
AtualizarRecursos();
}
private void btnAlterarFase_Click(object sender, EventArgs e)
{
int pid = OperacoesMemoria.ObterIdPeloNome(nomeProcesso);
if (pid == 0) return;
IntPtr handle = OperacoesMemoria.OpenProcess(0x1F0FFF, false, pid);
// Cálculo de endereço para nível do jogo
int basePointer = OperacoesMemoria.LerInteiro(handle, enderecoBaseGlobal);
int enderecoNivel = OperacoesMemoria.LerInteiro(handle, basePointer + 0x82C) + 0x24;
if (int.TryParse(txtNovaFase.Text, out int novaFase))
{
OperacoesMemoria.EscreverInteiro(handle, enderecoNivel, novaFase);
}
OperacoesMemoria.CloseHandle(handle);
}
}
}
3. Considerações sobre Endereçamento
Os endereços de memória em jogos modernos raramente são estáticos devido a mecanismos como o ASLR (Address Space Layout Randomization). Por isso, o uso de Endereços Base somados a Offsets é fundamental. O processo geralmente envolve:
- Localizar o endereço base do módulo principal.
- Seguir a cadeia de ponteiros até o valor final desejado.
- Garantir que a aplicação possua privilégios administrativos para abrir o handle do processo alvo com permissões de escrita.