Tjohei. Jeg runda Hitman Bloodmoney her om dagen og fant det for godt å skrive en liten artikkel om hvordan man kan lage en trainer til det.
Egentlig ville jeg vise hvor mye rart man kan gjøre hvis man lærer seg litt assembly. Alt av programkode er utsatt for endring, så man kan endre på programmer til å få den oppførselen _du_ vil at de skal ha. I dette tilfellet skal jeg bare vise kort hvordan man kan gå frem for å få uendelig liv og ammo i nyeste hitman.
Tools:
Tsearch v1.6 - kjekt "gamehacking"-program
IDA pro - Disassembler og Debugger
Hitman Bloodmoney fixed exe av Reloaded, slik at vi slipper beskyttelsen mot debuggere etc. Denne releasen er vel egentlig flawed har jeg hørt, men funker greit nok til at vi kan bruke den til å lage en trainer.
Del 1 - Å finne minneadressene til ammo og liv.
Fyr opp Hitman. Sørg for å kjøre spillet i "windowed mode", så slipper vi trøbbel når vi debugger det. Start på et vilkårlig brett, men sørg for å ta med deg en pistol eller no. Fyr så opp Tsearch. Velg "Open process" og finn HitmanBloodMoney.exe. I spillet: Ta opp våpenet og se hvor mye ammo du har. I mitt tilfelle står det 9/36. Det betyr 9 skudd i clip'et og 36 tilsammen i alle clip'ene. Trykk deretter på forstørrelsesglasset i Tsearch der det står "Init new search". La det stå på "Exact Value" og "4 bytes", men skriv inn 9 som value. Trykk OK og la den søke seg ferdig. Det kan ta litt tid, mye minne å søke igjennom =)
OK, jeg fikk 24481 treff på verdien 9. Det kan variere fra gang til gang så ikke bry dere med det. Cluet her er å få disse treffene ned til så få som mulig, helst 1.
I spillet: Skyt et par skudd for å dekrementere ammo. Jeg har nå 7 skudd i klippet. I Tsearch trykker du nå på forstørrelsesglasset med tre punktum under. "Search next". Skriv inn verdien 7 og søk videre. Nå er det bare 13 treff hos meg =) Gjør det samme en gang til. Skyt litt og søk igjen. Jeg får tre minneadresser som alltid inneholder antall
ammo i clip. Mine adresser er:
289D3314
28A19600
29347BC0
Dere vil ikke få samme adresser, siden spillet allokerer minne forskjellig hver gang et brett starter. Dobbeltklikk på hver av de tre adressene for å få dem over i det andre vinduet. Nå må vi finne ut hvilken av disse tre som er "dominant". De to andre adressene er altså kopier av den dominante og ikke like viktig. Det kan vi gjøre ved å check'e den
checkboksen ved adressen vi vil teste. Du vil få et lite smilefjes i checkboksen. Dette indikerer at minneverdien er "låst" og ikke vil endre seg. Jeg prøvde først på den første av minneverdiene. Gå inn i spillet og skyt et skudd. Jeg mista et skudd, så denne adressen er ikke riktig. Prøv så neste. Wee. Den funka! Vi har nå uendelig ammo.
Men det jeg vil gjøre er å endre på selve programkoden slik at den ikke dekrementerer ammo, isteden for at Tsearch sitter og skriver en verdi til spillets minne hele tiden.
Velg "AutoHack" menyen til Tsearch og trykk på "Enable Debugger". Høyreklikk på adressen vi fant og velg "Autohack". Ta så opp "Autohack"-vinduet ved å gå på menyen.
Gå inn i spillet og skyt et skudd. I autohack-vinduet dukket dette opp hos meg:
5140AB: mov [esi+0x94], ecx
Dette er altså assemblyinstruksjonen som legger inn ny ammoverdi i ammovariabelen. Ved å NOP'e denne instruksjonen vil den aldri bli eksekvert og vi vil aldri miste ammo. NOP står for "No operation" og er en assemblyinstruksjon som ikke gjør noe som helst.
Skriv ned adressen til denne instruksjonen: 5140AB
OK, da har vi uendelig ammo. Nå over til noe mye vanskeligere, å finne adressen for liv. Her har vi ikke bestemte verdier å gå ut ifra, bare en liv-bar. Vi vet heller ikke hvilken
datatype som er brukt til å representere liv. Det kan være en byte, en word, en dword, en float eller en double. Av erfaring vet jeg at det er ganske normalt å bruke float,
men det er ikke alltid tilfellet. En annen ting er at vi ikke vet mengde liv vi har i utgangspunktet, så vi kan ikke søke etter en "exact value", slik som vi gjorde for ammo.
Her har jeg dessverre ingen "oppskrift" på hvordan man skal gå frem. Det jeg gjorde var vel mer eller mindre gjetning. Jeg fant ihvertfall at verdien til å begynne med er 150.0
i float (vanligvis er det 100,150,200 etc), og at koden som endrer verdien er på adresse 005FB617. Instruksjonen på denne adressen er:
fstp dword ptr [esi+0xFA4]
Dette er en floating point instruksjon som lagrer en float på adresse esi+0xFA4. Problemet her er at koden før den her sannsynligvis har en fld-instruksjon eller noe lignende, og den må vi også huske å fjerne hvis vi skal NOP'e ut denne instruksjonen, ellers blir det feil på fpu-stacken. For dere som kan assembly blir det som om vi push'er en verdi, men glemmer å pop'e den. Jeg brukte ihvertfall ida pro til å se på koden rundt den adressen der. Å disassemble spill i ida pro kan ta svært lang tid btw, siden den
er så nøye =)
.text:005FB60D fld dword ptr [esi+0FA4h]
.text:005FB613 fsub dword ptr [esp+8]
.text:005FB617 fstp dword ptr [esi+0FA4h]
Her er ihvertfall koden. Vi vil helst NOP'e alle disse instruksjonene. Det er 17h-0Dh = 10h = 16 bytes. Siden en NOP bare er en byte må vi altså fyre inn 16 NOP's på adresse 005FB60D.
Vel, da har vi alt vi trenger for å lage en trainer.
Vi må bare NOP'e bort disse instruksjonene:
6 bytes:
5140AB: mov [esi+0x94], ecx
16 bytes:
5FB60D: fld dword ptr [esi+0xFA4]
5FB613: fsub dword ptr [esp+8]
5FB617: fstp dword ptr [esi+0xFA4]
Del 2. Kode en trainer.
Her har jeg kun tenkt å ta med koden som er relevant for å NOP'e instruksjonene. Ikke noe fin GUI eller noe sånt. Jeg valgte dessuten å kode traineren C# siden jeg lærte
det for et par dager siden og vil øve meg =) En ordentlig trainer burde selvfølgelig ha muligheten til å fjerne cheaten igjen, men det orker jeg ikke kode inn nå. Det er uansett
en smal sak å skrive de orginale bytene tilbake istedenfor NOP's (0x90).
Her er koden min. Det skal vel bare være å paste inn f.eks
i eventhandleren til en knapp eller no.
Og her er koden min for WIN32API klassen:
Egentlig ville jeg vise hvor mye rart man kan gjøre hvis man lærer seg litt assembly. Alt av programkode er utsatt for endring, så man kan endre på programmer til å få den oppførselen _du_ vil at de skal ha. I dette tilfellet skal jeg bare vise kort hvordan man kan gå frem for å få uendelig liv og ammo i nyeste hitman.
Tools:
Tsearch v1.6 - kjekt "gamehacking"-program
IDA pro - Disassembler og Debugger
Hitman Bloodmoney fixed exe av Reloaded, slik at vi slipper beskyttelsen mot debuggere etc. Denne releasen er vel egentlig flawed har jeg hørt, men funker greit nok til at vi kan bruke den til å lage en trainer.
Del 1 - Å finne minneadressene til ammo og liv.
Fyr opp Hitman. Sørg for å kjøre spillet i "windowed mode", så slipper vi trøbbel når vi debugger det. Start på et vilkårlig brett, men sørg for å ta med deg en pistol eller no. Fyr så opp Tsearch. Velg "Open process" og finn HitmanBloodMoney.exe. I spillet: Ta opp våpenet og se hvor mye ammo du har. I mitt tilfelle står det 9/36. Det betyr 9 skudd i clip'et og 36 tilsammen i alle clip'ene. Trykk deretter på forstørrelsesglasset i Tsearch der det står "Init new search". La det stå på "Exact Value" og "4 bytes", men skriv inn 9 som value. Trykk OK og la den søke seg ferdig. Det kan ta litt tid, mye minne å søke igjennom =)
OK, jeg fikk 24481 treff på verdien 9. Det kan variere fra gang til gang så ikke bry dere med det. Cluet her er å få disse treffene ned til så få som mulig, helst 1.
I spillet: Skyt et par skudd for å dekrementere ammo. Jeg har nå 7 skudd i klippet. I Tsearch trykker du nå på forstørrelsesglasset med tre punktum under. "Search next". Skriv inn verdien 7 og søk videre. Nå er det bare 13 treff hos meg =) Gjør det samme en gang til. Skyt litt og søk igjen. Jeg får tre minneadresser som alltid inneholder antall
ammo i clip. Mine adresser er:
289D3314
28A19600
29347BC0
Dere vil ikke få samme adresser, siden spillet allokerer minne forskjellig hver gang et brett starter. Dobbeltklikk på hver av de tre adressene for å få dem over i det andre vinduet. Nå må vi finne ut hvilken av disse tre som er "dominant". De to andre adressene er altså kopier av den dominante og ikke like viktig. Det kan vi gjøre ved å check'e den
checkboksen ved adressen vi vil teste. Du vil få et lite smilefjes i checkboksen. Dette indikerer at minneverdien er "låst" og ikke vil endre seg. Jeg prøvde først på den første av minneverdiene. Gå inn i spillet og skyt et skudd. Jeg mista et skudd, så denne adressen er ikke riktig. Prøv så neste. Wee. Den funka! Vi har nå uendelig ammo.
Men det jeg vil gjøre er å endre på selve programkoden slik at den ikke dekrementerer ammo, isteden for at Tsearch sitter og skriver en verdi til spillets minne hele tiden.
Velg "AutoHack" menyen til Tsearch og trykk på "Enable Debugger". Høyreklikk på adressen vi fant og velg "Autohack". Ta så opp "Autohack"-vinduet ved å gå på menyen.
Gå inn i spillet og skyt et skudd. I autohack-vinduet dukket dette opp hos meg:
5140AB: mov [esi+0x94], ecx
Dette er altså assemblyinstruksjonen som legger inn ny ammoverdi i ammovariabelen. Ved å NOP'e denne instruksjonen vil den aldri bli eksekvert og vi vil aldri miste ammo. NOP står for "No operation" og er en assemblyinstruksjon som ikke gjør noe som helst.
Skriv ned adressen til denne instruksjonen: 5140AB
OK, da har vi uendelig ammo. Nå over til noe mye vanskeligere, å finne adressen for liv. Her har vi ikke bestemte verdier å gå ut ifra, bare en liv-bar. Vi vet heller ikke hvilken
datatype som er brukt til å representere liv. Det kan være en byte, en word, en dword, en float eller en double. Av erfaring vet jeg at det er ganske normalt å bruke float,
men det er ikke alltid tilfellet. En annen ting er at vi ikke vet mengde liv vi har i utgangspunktet, så vi kan ikke søke etter en "exact value", slik som vi gjorde for ammo.
Her har jeg dessverre ingen "oppskrift" på hvordan man skal gå frem. Det jeg gjorde var vel mer eller mindre gjetning. Jeg fant ihvertfall at verdien til å begynne med er 150.0
i float (vanligvis er det 100,150,200 etc), og at koden som endrer verdien er på adresse 005FB617. Instruksjonen på denne adressen er:
fstp dword ptr [esi+0xFA4]
Dette er en floating point instruksjon som lagrer en float på adresse esi+0xFA4. Problemet her er at koden før den her sannsynligvis har en fld-instruksjon eller noe lignende, og den må vi også huske å fjerne hvis vi skal NOP'e ut denne instruksjonen, ellers blir det feil på fpu-stacken. For dere som kan assembly blir det som om vi push'er en verdi, men glemmer å pop'e den. Jeg brukte ihvertfall ida pro til å se på koden rundt den adressen der. Å disassemble spill i ida pro kan ta svært lang tid btw, siden den
er så nøye =)
.text:005FB60D fld dword ptr [esi+0FA4h]
.text:005FB613 fsub dword ptr [esp+8]
.text:005FB617 fstp dword ptr [esi+0FA4h]
Her er ihvertfall koden. Vi vil helst NOP'e alle disse instruksjonene. Det er 17h-0Dh = 10h = 16 bytes. Siden en NOP bare er en byte må vi altså fyre inn 16 NOP's på adresse 005FB60D.
Vel, da har vi alt vi trenger for å lage en trainer.
Vi må bare NOP'e bort disse instruksjonene:
6 bytes:
5140AB: mov [esi+0x94], ecx
16 bytes:
5FB60D: fld dword ptr [esi+0xFA4]
5FB613: fsub dword ptr [esp+8]
5FB617: fstp dword ptr [esi+0xFA4]
Del 2. Kode en trainer.
Her har jeg kun tenkt å ta med koden som er relevant for å NOP'e instruksjonene. Ikke noe fin GUI eller noe sånt. Jeg valgte dessuten å kode traineren C# siden jeg lærte
det for et par dager siden og vil øve meg =) En ordentlig trainer burde selvfølgelig ha muligheten til å fjerne cheaten igjen, men det orker jeg ikke kode inn nå. Det er uansett
en smal sak å skrive de orginale bytene tilbake istedenfor NOP's (0x90).
Her er koden min. Det skal vel bare være å paste inn f.eks
i eventhandleren til en knapp eller no.
Kode
IntPtr hWnd = WIN32API.FindWindow(null, "Hitman Blood Money"); uint processId; WIN32API.GetWindowThreadProcessId(hWnd, out processId); IntPtr hProcess = WIN32API.OpenProcess(0x1F0FFF, false, processId); //0x1f0fff = PROCESS_ALL_ACCESS IntPtr nBytesWritten; uint oldprotect; //Uendelig Ammo IntPtr lpAddress = new IntPtr(0x5140ab); UIntPtr dwSize = new UIntPtr(6); WIN32API.VirtualProtectEx(hProcess, lpAddress, dwSize, 0x40, out oldprotect); //0x40 = PAGE_EXECUTE_READWRITE byte[] lpBuffer = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 }; WIN32API.WriteProcessMemory(hProcess, lpAddress, lpBuffer, dwSize, out nBytesWritten); //Uendelig liv lpAddress = new IntPtr(0x005fb60d); dwSize = new UIntPtr(16); WIN32API.VirtualProtectEx(hProcess, lpAddress, dwSize, 0x40, out oldprotect);//0x40 = PAGE_EXECUTE_READWRITE byte[] lpBuffer2 = {0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90}; WIN32API.WriteProcessMemory(hProcess, lpAddress, lpBuffer2, dwSize, out nBytesWritten); WIN32API.CloseHandle(hProcess);
Kode
using System; using System.Runtime.InteropServices; using System.Collections.Generic; using System.Text; namespace HitmanTrainer { class WIN32API { public static bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect) { return API.VirtualProtectEx(hProcess, lpAddress, dwSize, flNewProtect,out lpflOldProtect); } public static IntPtr FindWindow(string lpClassName, string lpWindowName) { return API.FindWindow(lpClassName, lpWindowName); } public static uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId) { return API.GetWindowThreadProcessId(hWnd, out lpdwProcessId); } public static bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, UIntPtr nSize, IntPtr lpNumberOfBytesRead) { return API.ReadProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesRead); } public static bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, UIntPtr nSize, out IntPtr lpNumberOfBytesWritten) { return API.WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, out lpNumberOfBytesWritten); } public static IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId) { return API.OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId); } public static Boolean CloseHandle(IntPtr hObject) { return API.CloseHandle(hObject); } internal class API { [DllImport("kernel32.dll")] public static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect); [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); [DllImport("user32.dll", SetLastError = true)] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); [DllImport("kernel32.dll")] public static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId); [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, UIntPtr nSize, IntPtr lpNumberOfBytesRead); [DllImport("kernel32.dll")] public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, UIntPtr nSize, out IntPtr lpNumberOfBytesWritten); [DllImport("kernel32.dll")] public static extern Boolean CloseHandle(IntPtr hObject); } } }
Sist endret av Evinyatar; 31. mai 2006 kl. 15:32.