cambiar memoria de un juego y escribir en memoria By Sacha

Iniciado por EL PRINTERO, 24 Agosto 2007, 23:36 PM

0 Miembros y 1 Visitante están viendo este tema.

EL PRINTERO

Changing Game Memory

lo primero es saber qué es lo que hace cambiar la memoria ?
el juego almacena variables en memoria, tales como: que team sos, que arma estás usando, que mensage estás por enviar y demás.. cambiando la memoria durante el juego te permite automatizar acciones o cambiar lo que se muestra.

las acciones que puedo automatizar, para half life tenemos estos ejemplos:

_ enviar una cadena de texto a la caja de texto en la consola automáticamente (cambiar de arma, saltar, decir un mensage (say), etc)
_automáticamente elegir un team, arma, mover/strafe, lo que sea.
_crear un bot (que juegue por vos)
_leer la memoria del juego entonces sabés cuando hacer esas acciones. ejemplo: leer mensajes OnConnect, OnNewround.


los segundo es tener un objetivo, osea qué queremos cambiar ?
para el siguiente ejemplo vamos a elegir la consola de texto unicode. elegimos esta porque es más difícil de cambiar. pero pueden elegir lo que quieran.


lo tercero, es encontrar los offsets que necesitás para el cambio.
esto lo veremos en otro tutorial para no complicar este. usen un programa como Tsearch u Ollydbggr para encontrar los offsets. pueden encontrar la variable haciendola cambiar y buscando para que esta variable debería estar.. continuando, hasta que el único offset que quede, ese es el que se necesita.

para el ejemplo, hemos tipeado "denvest" en la consola, attachado olly, buscado en memoria por "denvest" (en unicode porque este usa unicode) y entonces... el offset --> 0x189ECE6

tambien queremos saber cuando alguien se unió al juego. esto es complicado y no es para explicar por ahora, será en otro tutorial. se usa olly para encontrar nombres dentro grandes dll y mirar por algo similar a 'join' (unirse). una vez encontrado esto, buscamos através de la función en ensamblador y encontramos el offset el cual es actualizado cuando un nuevo jugador se une. este offset era:
0x1970028

lo cuarto es codear un poco..
yo use dev-cpp pero deberían andar en cualquier compilador.

INCLUDES y DEFINES

#include <windows.h>
#include <stdio.h>

#define GAMENAME "Name of Game"
#define MAXNAME 15
#define ConsoleTextOffset 0x189ECE6
#define JoinOffset 0x1970028


sólo windows.h y el standard de entrada/salida para esto. el define GAMENAME debe ser cambiado con el nombre que aparece en la ventana (como se ve en tu taskbar). el define MAXNAME es la cantidad máxima de caracteres para un nombre de player. los otros dos defines son para los dos offsets que encontramos antes.

VARIABLES GLOBALES

CitarDWORD pId;
HANDLE pHandle;
HANDLE h;
HWND hWnd;
char LastPlayer[MAXNAME];

sólo handles y cosas que vas a necesitar para cambiar la memoria del proceso luego. Es de ayuda para tener esto para las funciones que crearas luego. abajo de todo tenemos el último jugador (lastplayer). la forma en que funciona este juego es que cuando un nuevo player se une, su nombre es guardado en JoinOffset, entonces checkeamos de acuerdo a si hay actualizaciones.

CUERPO DEL PROGRAMA

Citarint main() {
  cout<<"Sacha's Magnificent Memory Modifier"<<endl;
  hWnd=FindWindow(0,GAMENAME);
  if (hWnd!=0) {
    GetWindowThreadProcessId(hWnd, &pId);
    cout<<"Found game process "<<GAMENAME<<" with ID "<<pId<<endl;
    pHandle = OpenProcess(PROCESS_ALL_ACCESS,0,pId);
    while(true) {
      ...
    }
  } else cout<<"Could not find process. Please run GAMENAME before running this program"<<endl;
}

primero tenemos nuestro pequeño mensaje explicando lo que es nuestro programa es. luego usamos FindWindow para obtener el HWND de el juego. lo que sigue es checkear si esta encontró la ventana y, sino, le decimos al usuario lo ocurrido.
si la tenemos, continuamos para encontrar el PID y decirle al usuario que lo encontramos. por último, creamos un handle al proceso con acceso completo y entramos un bucle.

antes de pasar por el bucle, vamos a explicar ReadProcessMemory().



CitarBOOL ReadProcessMemory(
  HANDLE hProcess,
  LPCVOID lpBaseAddress,
  LPVOID lpBuffer,
  SIZE_T nSize,
  SIZE_T* lpNumberOfBytesRead
);

VER: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/base/readprocessmemory.asp

la función toma 5 parámetros. el primero es el handle de la ventana que nosotros encontramos en el code anterior (pId). el segundo es la dirección de el offset que encontramos en el tercer paso. el tercer parámetro es la dirección de la variable que deseamos para guardar la memoria en la que nosotros estamos leyendo. el cuarto es cuántos bytes queremos para leer y el quinto es donde queremos guardar el valor para cuantos bytes tenemos leídos. (podemos ponerlo NULL).

vamos a hacer una nueva función para esta, sin embargo (con ReadProcessMemory() usada para ello). la razón es que queremos leer unicode en ascii. unicode es equivalente a ascii si solamente removés el segundo caracter, entonces ahí vamos:

FUNCIÓN ReadUnicodeProcessMemory

Citarvoid ReadProcessMemoryUnicode(LONG Address,char (&buffer)[MAXNAME]) {
  char nextchar;
  for (int i=0;i<MAXNAME*2;i++) {
    ReadProcessMemory(pHandle,(LPVOID)(JoinOffset+(LONG)(i*2)),&nextchar,1,NULL);
    buffer=nextchar;
    if (nextchar=='') return;
  }
}

esta simplemente lee cada segundo caracter. la otra que haremos es WriteProcessMemoryUnicode(), pero esta la pueden hacer ustedes hehe,
es casi idéntica. para WriteProcessMemory() simplemente especificamos una búffer de entrada en vez de salida para el tercer parámetro.

nota: esto es sólo para leer/escribir cadenas unicode. si es una cadena ascii u otro tipo de variable pueden ignorar esta función.


VER: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/base/writeprocessmemory.asp

BUCLE del PROGRAMA

Citarwhile(true) {
      char CurrentPlayer[MAXNAME];
      ReadProcessMemoryUnicode(JoinOffset,&CurrentPlayer);
      if (strcmp(CurrentPlayer,LastPlayer)!=0) {
        char ChatMessage[190]; // maximum size of console chat is 190
        sprintf(ChatMessage,"say Hello %s!",CurrentPlayer);
        WriteProcessMemoryUnicode(ConsoleOffset,ChatMessage); // this also sends enter key
        strcpy(LastPlayer,CurrentPlayer);
      }
      Sleep(1000);
    }

esto lee el offset de memoria para el correspondiente nombre de player y, si es un nuevo player, continúa dando un 'say hello' (mensaje de saludo). esto luego escribe el mensaje a la memoria y envía una pulsación enter antes de copiar el nuevo nombre a LastPlayer. esto sólo checkea cada 1 segundo, porque no tiene sentido checkear cada milisegundo. muy simple y funciona..
recuerden que Read y WriteProcessMemory() regresa 0 para un error.

--------------------------------------------------------------------

en este segundo tutorial nos expandiremos a escribir en memoria, vamos a estar inyectando una librería a la memoria.. esto significa que debes tener una librería la que que quieras inyectar.

dll injection
-----------------


las nuevas funciones en este tutorial son:

VirtualAllocEx()
CreateRemoteThread()!
FindWindow()
OpenProcess()
GetWindowThreadProcessID()


fueron revisadas en el tutorial anterior pero igual la MSDN puede ayudar con los argumentos y eso.

ahora explicamos las dos nuevas funciones:

VirtualAllocEx() toma 5 parámetros, como se puede ver:

CitarLPVOID VirtualAllocEx(
  HANDLE hProcess,
  LPVOID lpAddress,
  SIZE_T dwSize,
  DWORD flAllocationType,
  DWORD flProtect
);

VER: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/memory/base/virtualallocex.asp

el primero es el handle para el proceso, el cual es el valor de retorno de OpenProcess().
lpAddress se refiere a la ubicación en memoria es la que deseamos allocar memoria. si nosotros elegimos NULL para este argumento, se decidirá una ubicación por nosotros. lo siguiente, nosotros necesitamos decirle a la función cuánta memoria allocar para dwSize. el tipo de allocación para el cuarto parámetro, dice a la función que tipo de allocación vamos a hacer. poniendo MEM_RESERVE|MEM_COMMIT como el cuarto parámetro, somos capaces de reservar y cometer la página en un paso (vamos a usar esto). el último parámetro setea el tipo de protección que esta memoria va a tener.

CreateRemoteThread() es más pesada con sus 7 parámetros:

CitarHANDLE WINAPI CreateRemoteThread(
  HANDLE hProcess,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  SIZE_T dwStackSize,
  LPTHREAD_START_ROUTINE lpStartAddress,
  LPVOID lpParameter,
  DWORD dwCreationFlags,
  LPDWORD lpThreadId
);

VER: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/createremotethread.asp

de vuelta, nosotros enviamos el handle a la función como el primero parámetro -- el primero que encontramos para OpenProcess(). esta ves, la función [segundo argumento] quiere saber los atributos de seguridad de este nuevo hilo. esta acepta un puntero a estos atributos o NULL como seguridad default (usemos este). tercero, este pregunta por el tamaño de pila de tu nuevo hilo. sólo ponemos 0 para este para setear un tamaño de pila default. ahora tenemos un lindo e importante parámetro, lpStartAddress. este es la dirección en donde nosotros queremos nuestros nuevo hilo para que comience a ejecutarse (adonde empieza). este quinto parámetro es un puntero a una variable que nosotros queremos enviar a este nuevo hilo -- la función regresa el handle del nuevo hilo. todo listo, ahora es momento de codear.

EL PROGRAMA

-- Includes y Defines

Citar#include <windows.h>
#include <stdio.h>
#include <iostream> //optional: puede reforzar el tamaño de archivo.
#include <fstream> // optional: hay alternativas.
#define GAME "Game Name"
#define DLL "Cheat.dll"

using namespace std; //


bueno, nosotros tenemos windows.h y el standard de E/S, como es normal. Incluímos iostream y fstream para hacer un filechecking aquí, pero se puede quitar fácilmente.
hemos definido el nombre del juego y el nombre de la DLL también. podrías, opcionalmente, tener la DLL nombrada como el inyector. también vas a estar usando el espacio de nombres standard (std namespace) si te decidís a incluír iostresm/fstream.

-- variables globales

CitarHWND hWnd;
DWORD pId;

el handle de la ventana y el PID globalizados (pero no se necesitan en este ejemplo) -- lo facilita si vos terminás haciendo muchas funciones usando esto.

-- La función de inyección

Citarvoid Inject(HWND hWnd, char* strDll ) {
  GetWindowThreadProcessId(hWnd, &pId);
  HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pId);
  LPVOID lpRemoteAddress = VirtualAllocEx(hProcess, NULL, strlen(strDll), MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
  WriteProcessMemory(hProcess, lpRemoteAddress, (LPVOID)strDll, strlen(strDll), NULL);
  CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA"), lpRemoteAddress, 0, NULL);
}

no necesitan más argumentos para este ejemplo y pueden también hacerlo inline, pero la intención es que se expandan en base a esto, usando esta función en el futuro. igualmente aceptamos el handle y el nombre de la DLL como parámetros en este ejemplo.

entonces, el primero busca el PID usando el handle de la ventana. luego, con este PID, la función nos devuelve un handle para el proceso. como explicamos arriba, esta puede allocar memoria en el proceso usando este handle.

para inyectar la DLL, allocamos memoria que es el tamaño del nombre de la DLL dentro de la memoria de los procesos remotos. entonces escribimos en la memoria (ya lo vimos en el otro tut), el nombre de la DLL para esa ubicación ya allocada. siguiendo esto, creamos un hilo (ya explicado) en el comienzo de la función LoadLibraryA() con el parámetro que es la dirección remota del nombre de la DLL que nosotros ya pusimos en la memoria de los procesos. esto es como vos hacés que un proceso cargue tu librería:

-- MAIN

Citarint main() {
  cout<<"Waiting for window to appear... ";
  while(!(hWnd = FindWindow(0,GAME))) Sleep(1000);
  cout<<"Found Window!"<<endl;
  ofstream filecheck;
  filecheck.open(DLL,ios::in);
  if(!filecheck.is_open()) printf("Error: Unable to find %s.n",DLL);
  else { Inject(hWnd,DLL); cout<<"Injected "<<DLL<<" into process!"<<endl;}
  filecheck.close();
  system("PAUSE");
  return 0;
}

esto espera hasta que encuentra una ventana concordando con el nombre ("caption") del juego [definido arriba] y, mientras espera, se duerme por un segundo (sleep 1000). luego dice que encontró una ventana y checkea si tu DLL existe, antes de usar la función de inyección escrita arriba.

ESO ES TODO...

esto es sólo una estructura, lo ideal es que se expandan en base a esto:

_ Error Checking.
_ Convertirlo en una aplicación win32 en ves de una de consola.
_ crearle gráficos, una GUI.
_ agregar un textbox en donde el usuario pueda tipear en la DLL para inyectar, y el proceso en el cual
  inyectar.

_ hacerlo en asm.

------------------------------------------------------------------------
Traducido por mi, si algo no se entiende lo verificaré.
Creado por Sacha en la comunidad planetmayhem.

TIGRE CAPO
CHACA GATO