Domanda Reversing Una mano per il Reversing di un gioco

Ultima modifica:
Visto che il topic sembra interessante e ci sono un sacco di informazioni utili .... volevo chiederti/vi alcune delucidazioni:

- come si trova un base address ?
- essendo che ASRL randomizza tutte le volte l'address, come è possiile effettuare il calcolo di cui tu parli ( l'indirizzo base + l'indirizzo randomizzato dall'ASRL)?
- se dovessi conoscere il prototipo di una funzione, sarebbe utile? In che modo ?

L'ASLR randomizza l'address space, ti serve trovare il base address proprio durante l'esecuzione, fino alla chiusura del programma rimarra' costante. Su Windows devi usare le API di Windows, un metodo potrebbe essere usare: OpenProcess, EnumProcessModules e soprattutto GetModuleInformation che ti da il base address attuale del programma in esecuzione scelto.
Questo per trovarlo da un processo remoto senza nemmeno iniettarti, se pero' hai in mente di fare DLL Injection allora una volta caricata la tua DLL potresti non avere bisogno di nessuna di queste chiamate in quanto sarai gia' dentro il contesto del processo e ti basta leggere la struttura del PEB per ottenere il base address.

Conoscere il prototipo e' essenziale se vuoi chiamare una funzione, non tanto per il valore di ritorno quanto per i parametri (e la calling convention) che devono essere esattamente quelli richiesti, faccio alcuni esempi:

C:
int __cdecl foo(const char* bar)
{
    printf("%s", bar);
    return 0;
}

// Chiamata con calling convention errata
typedef int (__stdcall* foo_ptr)(const char* bar);
foo_ptr fnptr = (foo_ptr)foo;
fnptr("abc"); // Fa print correttamente, poi crash: foo usando cdecl ha gia' pulito lo stack prima di ritornare.
// Credendo che usi stdcall, il programma provera' a farlo dopo che ha ritornato ma distruggera' lo stack.

// Chiamata con meno parametri
typedef int (__cdecl* foo_ptr)();
foo_ptr fnptr = (foo_ptr)foo;
fnptr(); // crash: foo, facendo pop dallo stack non ottiene il parametro che si aspettava ma qualcos'altro.
// Se questo non bastasse a causare un crash ci sono comunque altre conseguenze e reazioni a catena
// che molto probabilmente lo causeranno comunque anche in seguito.

// Chiamata con tipo parametri sbagliato
typedef int (__cdecl* foo_ptr)(int* bar);
foo_ptr fnptr = (foo_ptr)foo;
int x = 1;
fnptr(&x); // Nessun crash, printa un carattere invalido. (x in memoria e' 01 00 ... )

// Altra con tipo sbagliato
typedef int (__cdecl* foo_ptr)(int bar);
foo_ptr fnptr = (foo_ptr)foo;
fnptr(1); // Crash nella printf, dereferenziera' 0x00000001 che non e' un puntatore valido.
 
Ultima modifica:
Da quanto ho capito non si è obbligati ad eseguire l'injection di una dll, per modificare i valori interni di una funzione, ma si può tranquillamente sovrascrivere il tutto da "remoto". Qual è allora l'utilità di una dll injection? Perchè si usa, piuttosto che eseguire le call direttamente da remoto?
Gestire il tutto con una dll è molto più performante rispetto all'uso di un programma esterno che sicuramente utilizzerà l'API WriteProcessMemory.
Per cambiare un byte all'interno di un programma usando una dll ti basterà fare:
C++:
size_t* address = 0x00400000;
BYTE new_byte = 0x90;

*address = new_byte; // <-- fase di scrittura
per il semplice fatto che la dll sarà mappata nello spazio degli indirizzi virtuali del processo vittima.

Nel codice qui sopra, ho usato la variabile "DWORD address = 0x22A62C0E308;" che come puoi vedere contiene l'indirizzo di memoria della variabile da modificare. Il problema è che questo indirizzo è stato trovato per mezzo di CheatEngine, e solo dopo averlo trovato l ho aggiunto al programma. Tutte le volte che avvio il gioco, ovviamente l'indirizzo cambia e mi tocca andare a ritrovarlo tutte le volte. Avnedo utilizzato openprocess, dovrei, quindi, già avere il base-address?
DWORD address = 0x22A62C0E308; stai evidentemente salvando un valore le cui dimensioni superano i 32-bit in una DWORD, quindi il valore 0x22A00000000 verrà del tutto estromesso, per ovviare a questo problema ti basterà utilizzare una variabile più grande come: UINT64, __int64, ecc.

Ci sono differenze nel dover trovare l'indirizzo di memoria di una variabile e di una funzione:
  • Nel caso di una funzione ti basterà il nome del modulo in cui è alloggiata e l'offset, quindi dovrai enumerare i moduli del processo, trovare quello corispondente, ottenerne l'indirizzo base e sommarci l'offset, che sarà uguale a: offset = indirizzo (virtuale) assoluto della funzione - indirizzo base del modulo.
  • Nel caso di una variabile il processo è più astruso: potresti trovare un puntatore statico tramite il pointer scanner di Cheat Engine ed usarlo per rintracciare la variabile, oppure sempre tramite Cheat Engine intercetti una qualsiasi istruzione che faccia uso di tale variabile, e con una tecnica chiamata "copy injection" vai a modificare la funzione in modo che scriva il valore del puntatore della variabile in una regione di memoria da te conosciuta, un pò come un hook.
Avnedo utilizzato openprocess
Il fatto che usi l'API OpenProcess è irrilevante, il motivo per cui si chiama quella funzione è per ottenere un handle sul processo, nel caso tu non comprenda a pieno cosa sia un handle, consideralo come una 'chiave' che ti permette di interagire con il processo 'aperto'.
In più non c'entra tanto col topic ma per buona pratica anche se un programma sta per terminare si tende a chiudere gli handle in modo esplicito usando la funzione CloseHandle, ricorda che queste sono variabili paticolari (ed anche molto importanti).

Per quanto riguarda la chiamata a funzione, come sopracitato, mi prendo qualche ora domani per studiarmi il codice, la mia domanda è: quando vado a fare un reverse, come posso capire qual è la funzione che effettivamente sto cercando ?
Una convenzione di chiamata sta ad indicare come una funzione gestirà i parametri e restituirà il valore di ritorno, puoi giungere alla convenzione adoperata analizzando il codice antecedente alla chimamata di funzione, in particolare l'ordine in cui vengono effettuate le operazioni di push e mov.
Potrebbe esserti più chiaro dando un'occhiata al paragrafo "Caller clean-up" di x86 calling conventions - Wikipedia.

UPDATE: nel codice che hai riportato, in particolare questa parte:
C++:
if (std::wstring(entry.szExeFile) == targetProcessName)
puoi semplicemente sostituirla con:
C++:
if (entry.szExeFile == targetProcessName)
 
  • Mi piace
Reazioni: Hastro

Hastro

Utente Emerald
14 Febbraio 2012
423
60
169
361
Salve,

Sto facendo il reversing di un piccolo gioco, ma non essendo esperto in reversing di videogiochi ed approcciando per la prima volta windows in questo, c'è qualcosa che non mi torna:

Ho fatto un dump della memoria, e dentro ho trovato un sacco di funzioni interessanti, esempio:
  • Game/Game/interiors/club¬XXX_DEV_addMoney_Cl/Game/Game/maps_resources
Il problema è che non esiste alcun percorso simile all'interno della cartella di gioco ... qualcuno sa darmi una dritta ?

P.s. Il gioco è basato su UnrealEngine4. L'unico file che ho è un eseguibile... non ci sono texture ne altre dll (oltre a quelle per l'audio / video)

P.s. Spero di aver azzeccato la sezione :/
 
E' difficile poter dire qualcosa senza avere il gioco e senza sapere cosa vuoi ottenere. Quella stringa inoltre puo' significare qualunque cosa cosi' decontestualizzata. Puo' essere di tutto meno che una funzione: nel compilato i nomi delle funzioni non vengono salvati eccetto per le RTTI e simili che comunque sono riconoscibili da prefissi e suffissi che li' non ci sono.
 
Confermo, se non ci dai più informazioni non potremo aiutarti.
Inoltre, il dump della memoria fatto così molto spesso non è di aiuto, e sicuramente non è il modo migliore di approcciare il problema.
 
Avete ragione, colpa mia che mi son espresso male. Diciamo che più per un gioco in particolare, volevo sapere, se ci fosse un modo per scoprire le funzioni interne di un programma, qual'ora questo non sia paccato, da li vorrei poi eseguire una dll injection per sovrascrivere la funzione standard con una mia custom.

Mi spiego meglio: so dell'esistenza dei debbuger, tuttavia non riesco a trovare le funzioni (anche se ho la certezza che l'applicazione bersaglio non è "paccata"), ho provato a usare IDA PRO, OllyDBG, Ghidra senza successo :/
 
Credo che il problema non sia che non ci sono le funzioni, ma che in realtà tu non sappia cosa cercare nè come farlo.
Ma purtroppo ancora mancano delle informazioni. E' un programma che utilizza un codice interpretato?
 
Credo che il problema non sia che non ci sono le funzioni, ma che in realtà tu non sappia cosa cercare nè come farlo.
Ma purtroppo ancora mancano delle informazioni. E' un programma che utilizza un codice interpretato?
Esatto! Conosco un po' di reverse, ma non so come ricercare le informazioni, ed eventualmente modificarle a mio favore "comunemente" chiamata ddl injection. Hai ragione, mancavano informazioni, parliamo di c++, non di linguaggi interpretati, ma compilati.
 
Se è così allora x64dbg o ollydbg sono gli strumenti più adeguati. Ma dipende da "cosa" vuoi cercare.
In verità mi piacerebbe imparare in generale sull'argomento, e credo che i videogiochi siano un ottimo punto di partenza! Mi piacerebbe capire come triggherare le funzioni, come eseguire correttamente una dll injection e mostrare su un form di terze parti eventuali funzioni che si possono abilitare, fin poi ad arrivare allo studio dei socket e creare magari un pack editor !
 
Se al posto di "triggherare" le funzioni usi il termine chiamare la risposta diventa ovvia: ti basta trovare l'indirizzo dove inizia la funzione e poi fare call indirizzo passando i parametri del tipo che si aspetta. Per fare questo devi studiare assembly e almeno le calling convention piu' comuni (cdecl, stdcall, fastcall...) per poter capire che tipo di parametri si aspetta e come li utilizza.

IDA, come Ghidra ti danno il vantaggio di cercare in automatico i pattern corrispondenti ai preamboli delle calling convention di vari compilatori, sono quindi di solito in grado di poter generare una lista di funzioni presenti nel programma, scansionando tutte le sezioni eseguibili del programma per questi pattern, ovviamente non puo' sapere il nome perche' il dato viene perso, ma spesso possono indicarti anche il numero e la dimensione dei parametri che la funzione si aspetta.
 
Se al posto di "triggherare" le funzioni usi il termine chiamare la risposta diventa ovvia: ti basta trovare l'indirizzo dove inizia la funzione e poi fare call indirizzo passando i parametri del tipo che si aspetta. Per fare questo devi studiare assembly e almeno le calling convention piu' comuni (cdecl, stdcall, fastcall...) per poter capire che tipo di parametri si aspetta e come li utilizza.

IDA, come Ghidra ti danno il vantaggio di cercare in automatico i pattern corrispondenti ai preamboli delle calling convention di vari compilatori, sono quindi di solito in grado di poter generare una lista di funzioni presenti nel programma, scansionando tutte le sezioni eseguibili del programma per questi pattern, ovviamente non puo' sapere il nome perche' il dato viene perso, ma spesso possono indicarti anche il numero e la dimensione dei parametri che la funzione si aspetta.
Ti ringrazio Junk! Hai condiviso ottime informazioni che mi danno un ottima pista di inizio! La mia domanda ora è: se ogni volta che apro un programma, l'allocazione di una funzione cambia ( e quindi il suo indirizzo), come faccio a trovare l'address di quella determinata funzione, ogni qual volta apro il programma?

Ancora, come faccio a capire qual'è la funzione che sto cercando, se non riesco a vedere (giustamente) i nomi delle funzioni ?
 
Ti ringrazio Junk! Hai condiviso ottime informazioni che mi danno un ottima pista di inizio! La mia domanda ora è: se ogni volta che apro un programma, l'allocazione di una funzione cambia ( e quindi il suo indirizzo), come faccio a trovare l'address di quella determinata funzione, ogni qual volta apro il programma?

Ancora, come faccio a capire qual'è la funzione che sto cercando, se non riesco a vedere (giustamente) i nomi delle funzioni ?

A meno di codice che si auto-modifica con allocazioni dinamiche una funzione manterra' la sua posizione, quello che cambia e' il base address dell'immagine intera dell'eseguibile in memoria, per trovarlo dinamicamente devi studiare il metodo usato dal loader di Windows, quindi capire RVA, VA ecc... Per poi calcolare l'indirizzo effettivo sommando l'indirizzo base randomizzato dall'ASRL. Se pero' verra' rilasciata una nuova versione del gioco e' probabile che, ricompilando, anche se la funzione non e' stata modificata venga spostata a un altro indirizzo.

Come trovare la funzione e' tutto un altro paio di maniche, a meno che non vuoi leggere e mappare tutto devi sapere cosa stai cercando.
 
A meno di codice che si auto-modifica con allocazioni dinamiche una funzione manterra' la sua posizione, quello che cambia e' il base address dell'immagine intera dell'eseguibile in memoria, per trovarlo dinamicamente devi studiare il metodo usato dal loader di Windows, quindi capire RVA, VA ecc... Per poi calcolare l'indirizzo effettivo sommando l'indirizzo base randomizzato dall'ASRL. Se pero' verra' rilasciata una nuova versione del gioco e' probabile che, ricompilando, anche se la funzione non e' stata modificata venga spostata a un altro indirizzo.

Come trovare la funzione e' tutto un altro paio di maniche, a meno che non vuoi leggere e mappare tutto devi sapere cosa stai cercando.
Sembra na cosa impossibile
 
A meno di codice che si auto-modifica con allocazioni dinamiche una funzione manterra' la sua posizione, quello che cambia e' il base address dell'immagine intera dell'eseguibile in memoria, per trovarlo dinamicamente devi studiare il metodo usato dal loader di Windows, quindi capire RVA, VA ecc... Per poi calcolare l'indirizzo effettivo sommando l'indirizzo base randomizzato dall'ASRL. Se pero' verra' rilasciata una nuova versione del gioco e' probabile che, ricompilando, anche se la funzione non e' stata modificata venga spostata a un altro indirizzo.

Come trovare la funzione e' tutto un altro paio di maniche, a meno che non vuoi leggere e mappare tutto devi sapere cosa stai cercando.
Visto che il topic sembra interessante e ci sono un sacco di informazioni utili .... volevo chiederti/vi alcune delucidazioni:

- come si trova un base address ?
- essendo che ASRL randomizza tutte le volte l'address, come è possiile effettuare il calcolo di cui tu parli ( l'indirizzo base + l'indirizzo randomizzato dall'ASRL)?
- se dovessi conoscere il prototipo di una funzione, sarebbe utile? In che modo ?
 
ti serve trovare il base address proprio durante l'esecuzione, fino alla chiusura del programma rimarra' costante. Su Windows devi usare le API di Windows, un metodo potrebbe essere u
Ti ringrazio moltissimo per la guida dettagliata, e per la voglia di scrivere un codice molto lungo. Mi prendo qualche giorno per analizzarlo, sicuramente dovrò chiederti altri chiarimenti, sempre se sarai disponibile!

Questo è un codice che ho scritto tempo fa. Mi consentiva di aggiungere "soldi virtuali" all'interno di un vecchio gioco (offline). Sono più esperto di C, che di c++, ho solo cercato di "far funzionare le cose", cercando roba "qua e la".

C++:
// include library
#include <iostream>
#include <windows.h>
#include <tlhelp32.h>
#include <vector>

// Declarations
using namespace std;

//DWORD pid = 19708;
DWORD address = 0x22A62C0E308;
int MyValue = 9000;

int main(int, char* [])
{
    int i;

    std::vector<DWORD> pids;
    std::wstring targetProcessName = L"my_program.exe";

    HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); //all processes

    PROCESSENTRY32W entry; //current process
    entry.dwSize = sizeof entry;

    if (!Process32FirstW(snap, &entry))
    { //start with the first in snapshot
        return 0;
    }

    do
    {
        if (std::wstring(entry.szExeFile) == targetProcessName)
        {
            pids.emplace_back(entry.th32ProcessID); //name matches; add to list
        }
    }
    while (Process32NextW(snap, &entry)); //keep going until end of snapshot

    for (int i(0); i < pids.size(); ++i)
    {
        std::cout << pids[i] << std::endl;
    }

    for (int i(0); i < pids.size(); ++i)
    {
        HANDLE pHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pids[i]);
        printf("My pid: %lu\nMy address: %p\nMy vaule: %d\n\n", pids[i], address, MyValue);
        WriteProcessMemory(pHandle, (LPVOID)address, &MyValue, sizeof(MyValue+3), 0);
    }

    system("pause");
}

Da quanto ho capito non si è obbligati ad eseguire l'injection di una dll, per modificare i valori interni di una funzione, ma si può tranquillamente sovrascrivere il tutto da "remoto". Qual è allora l'utilità di una dll injection? Perchè si usa, piuttosto che eseguire le call direttamente da remoto?

Nel codice qui sopra, ho usato la variabile "DWORD address = 0x22A62C0E308;" che come puoi vedere contiene l'indirizzo di memoria della variabile da modificare. Il problema è che questo indirizzo è stato trovato per mezzo di CheatEngine, e solo dopo averlo trovato l ho aggiunto al programma. Tutte le volte che avvio il gioco, ovviamente l'indirizzo cambia e mi tocca andare a ritrovarlo tutte le volte. Avnedo utilizzato openprocess, dovrei, quindi, già avere il base-address?

Per quanto riguarda la chiamata a funzione, come sopracitato, mi prendo qualche ora domani per studiarmi il codice, la mia domanda è: quando vado a fare un reverse, come posso capire qual è la funzione che effettivamente sto cercando ?
 
Ti ringrazio dany per la risposta molto esaustiva! Sto cercando di capirci di più e di studiare sia il tuo codice, sia quello di @JunkCoder !
Ci sono differenze nel dover trovare l'indirizzo di memoria di una variabile e di una funzione:
  • Nel caso di una funzione ti basterà il nome del modulo in cui è alloggiata e l'offset, quindi dovrai enumerare i moduli del processo, trovare quello corispondente, ottenerne l'indirizzo base e sommarci l'offset, che sarà uguale a: offset = indirizzo (virtuale) assoluto della funzione - indirizzo base del modulo.
  • Nel caso di una variabile il processo è più astruso: potresti trovare un puntatore statico tramite il pointer scanner di Cheat Engine ed usarlo per rintracciare la variabile, oppure sempre tramite Cheat Engine intercetti una qualsiasi istruzione che faccia uso di tale variabile, e con una tecnica chiamata "copy injection" vai a modificare la funzione in modo che scriva il valore del puntatore della variabile in una regione di memoria da te conosciuta, un pò come un hook
Ho alcune domande da porti su questa sezione:
  • "Nel caso di una funzione ti basterà il nome del modulo", non sembra essere il prototipo di una funzione, ma mi ha più ricordato le sub di VBA. Tuttavia se il codice è compilato, non dovrebbe avere sub, dove sto sbagliando (?)
  • "quindi dovrai enumerare i moduli del processo" in senso più concreto, a cosa corrisponde questa operazione?

  • "Nel caso di una variabile il processo è più astruso: potresti trovare un puntatore statico", il puntatore "statico" sarebbe quello che vienee direttamente allocato quando lancio il programma? l'address di questo puntatore non cambia ad ogni lancio?

Ti/Vi ringrazio per la disponibilità
 
Ultima modifica:
  • "Nel caso di una funzione ti basterà il nome del modulo", non sembra essere il prototipo di una funzione, ma mi ha più ricordato le sub di VBA. Tuttavia se il codice è compilato, non dovrebbe avere sub, dove sto sbagliando (?)
  • "quindi dovrai enumerare i moduli del processo" in senso più concreto, a cosa corrisponde questa operazione?

  • "Nel caso di una variabile il processo è più astruso: potresti trovare un puntatore statico", il puntatore "statico" sarebbe quello che vienee direttamente allocato quando lancio il programma? l'address di questo puntatore non cambia ad ogni lancio?
Mi sono appena informato sul termine "sub di VBA" e ti dico che il termine "modulo" varia molto a seconda del contesto (i miei riferimenti non hanno nulla a che vedere con il Visual Basic).
Partiamo dal fatto che io parlo di un programma scritto in C++, un linguaggio nativo che non ha bisogno di un interprete, in questo caso per modulo intendo il file eseguibile .exe che è stato mappato nello spazio degli indirizzi virtuali del processo.
Per essere più chiaro, considerando il seguente codice:
C++:
#include <iostream>

int sum(int a, int b)
{
    return a + b;
}

int main()
{
    std::cout << sum(1, 1);
}
Abbiamo in primis la funzione main e sum, ora agganciando un debugger all'eseguibile prodotto, visualizzando i moduli caricati nel processo:
moduli.png


Il primo modulo (quello in verde) è il file eseguibile stesso che io ho chiamato "progettoesempio.exe", e poi ci sono delle dll, sia il file eseguibile che le dll mappate sono definite con il termine "modulo".
Ora avendo definito il termine modulo, "Perchè nel codice sopra riportato ho aggiunto la funzione sum?" soltanto per dirti che quella funzione si troverà nel modulo "progettoesempio.exe".

in senso più concreto, a cosa corrisponde questa operazione?
Nel codice che hai riportato prima hai usato la funzione [URL='https://docs.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot']CreateToolhelp32Snapshot[/URL] usando la flag "TH32CS_SNAPPROCESS", e se dai un'occhiata alla documentazione vedrai anche la flag "TH32CS_SNAPMODULE", oppure puoi usare la funzione [URL='https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocessmodules']EnumProcessModules[/URL], il resto penso sia intuitivo.

Dettagli aggiuntivi: Per effetto dell ASLR l'indirizzo del modulo dell'exe (0x00E70000) e delle dll cambiera dopo ogni riavvio del sistema, mentre l'indirizzo delle variabili impiegate dal processo cambieranno ogni riavvio del programma stesso, potresti trovare interessante questa mia vecchia domanda sull'ASLR.

il puntatore "statico" sarebbe quello che vienee direttamente allocato quando lancio il programma? l'address di questo puntatore non cambia ad ogni lancio?
Consideralo come un puntatore che si trova sul modulo base del processo in questione, e questo puntatore punti sull'ambiente di gioco, che a sua volta comprende altri puntatori ad altre strutture inerenti al programma stesso.
 
Mi sono appena informato sul termine "sub di VBA" e ti dico che il termine "modulo" varia molto a seconda del contesto (i miei riferimenti non hanno nulla a che vedere con il Visual Basic).
Partiamo dal fatto che io parlo di un programma scritto in C++, un linguaggio nativo che non ha bisogno di un interprete, in questo caso per modulo intendo il file eseguibile .exe che è stato mappato nello spazio degli indirizzi virtuali del processo.
Per essere più chiaro, considerando il seguente codice:
C++:
#include <iostream>

int sum(int a, int b)
{
    return a + b;
}

int main()
{
    std::cout << sum(1, 1);
}
Abbiamo in primis la funzione main e sum, ora agganciando un debugger all'eseguibile prodotto, visualizzando i moduli caricati nel processo:Visualizza allegato 47352

Il primo modulo (quello in verde) è il file eseguibile stesso che io ho chiamato "progettoesempio.exe", e poi ci sono delle dll, sia il file eseguibile che le dll mappate sono definite con il termine "modulo".
Ora avendo definito il termine modulo, "Perchè nel codice sopra riportato ho aggiunto la funzione sum?" soltanto per dirti che quella funzione si troverà nel modulo "progettoesempio.exe".


Nel codice che hai riportato prima hai usato la funzione [URL='https://docs.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot']CreateToolhelp32Snapshot[/URL] usando la flag "TH32CS_SNAPPROCESS", e se dai un'occhiata alla documentazione vedrai anche la flag "TH32CS_SNAPMODULE", oppure puoi usare la funzione [URL='https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocessmodules']EnumProcessModules[/URL], il resto penso sia intuitivo.

Dettagli aggiuntivi: Per effetto dell ASLR l'indirizzo del modulo dell'exe (0x00E70000) e delle dll cambiera dopo ogni riavvio del sistema, mentre l'indirizzo delle variabili impiegate dal processo cambieranno ogni riavvio del programma stesso, potresti trovare interessante questa mia vecchia domanda sull'ASLR.


Consideralo come un puntatore che si trova sul modulo base del processo in questione, e questo puntatore punti sull'ambiente di gioco, che a sua volta comprende altri puntatori ad altre strutture inerenti al programma stesso.
Ti ringrazio moltissimo per la risposta. Più che continuare a chiedere, raccolgo il materiale che tu e @JunkCoder mi avete passato e proverò a raccapezzarci qualcosa questo pomeriggio!