Simple In-game Trainer Menus ---------------------------- Author: Orr NOTE! This tutorial is _Definetly_ NOT for newbies. Best read with NOTEPAD. Introduction ------------ Say you made a trainer with 10 or more options. The game you trained uses routines to protect against Alt+Tabbing outside the game. Say that the gamer using your trainer forgot what are the trainers options, or he forgot what keys to use. He can do two things now. Quit the game, or try to press all the keys and see how it affects him. Is there a solution for this kind of situation? Well, There is! The method I will suggest in this tutorial was not invented by me. As far as I know, it was already done in the old DOS days. That method is called Ingame Trainer Menus. The purpose, is to show the trainer option menu, inside the game, when the user hits a hotkey. What I will show in the tutorial, is how to make a trainer with one hotkey, that when pressed, will pop up a Message Box from inside my favorite target game: (pam pam pam!!) "The House of the Dead 2". The tools I will be using are SoftICE, IDA, HIEW, and Visual C++ (not a must). Theory ------ Basically the idea I will suggest works like that: 1. The user presses the menu hotkey. 2. The Trainer injects some code to a free space in the game's memory. 3. The Trainer overwrites a place in the gameloop. 4. The code injected will be run (the code is a messagebox) by the game. 5. The Trainer fixes the place it overwritten in the gameloop. In case you don't know how to perform in-mem code injections, I suggest you go read alittle about it in [SHEEP]'s tutorial, or keep reading on, as I will explain some fundumentals of injections. Finding a Game Loop ------------------- A Problem arises with what we want to do. We want to inject code to the game, and have the game run it by itself. Injecting the code isn't much of a problem, it is actually quite easy. But how do we make the GAME run OUR code? The solution is quite simple. If we find a place that is getting executed ALL the time (like, in a huge loop :), we could locate a good place to patch there, and inject our code to am empty space. The question is, how do we find a place, a loop that runs all the time? Basically, what I did was, whiile playing that game alittle, I poped SoftICE for several times, until I saw I am in the correct process (look in the bottom-right corner of SoftICE for the CLSID), in my case it is HOD2. I poped SoftICE in times that I wasn't doing much, so I would see what code is executed even when I am IDLE. After poping up in a place that looked OK, I began tracing alot. Some cases I did it manually, and some cases I did it using the T command (T 100 will trace 100 instructions). After a while, I saw that some loop is being executed alot of times, so I wanted to check it out. I disassembled the file with IDA, and got to that location. I am terribly sorry to tell you I really forgot what location it was... What I did though, was seeing what function that location was, and then seeing who calls it... for example: .text:004A4EA0 sub_4A4EA0 proc near ; CODE XREF: sub_4A41C0+142p .text:004A4EA0 push ebx .text:004A4EA1 push ebp : : : .text:004A4F47 test ah, 41h ; Say, this location :) : : : .text:004A4FDA pop ebp .text:004A4FDB pop ebx .text:004A4FDC retn .text:004A4FDC sub_4A4EA0 endp This function is called from the location sub_4A41C0. So I went there. and there I found a cool place that executed many API's. Anyway, I chose this location: .text:004A5967 push ecx ; lpMsg .text:004A5968 call ds:GetMessageA .text:004A596E test eax, eax Why? Because GetMessage is a Windows API that gets executed all the time. I checked for my suspicion by simply setting a breakpoint at that location, and I saw that I couldn't even play for one sec, because SoftICE kept popping after I pressed F11. Also, I am a fan of overwriting API calls. Why? a) They have a sufficient amount of OPCodes (6, in many cases). b) They can be executed later on from anywhere in the program, with the same opcodes. c) Because Micro$oft coders did them (and because I couldn't find another reason :) OK, So we found a place to overwrite with our jump... now we actually need a place to jump to! To summarize: You can do 2 things in order to find a place that is getting executed all the time: - Find a place that loops alot manually, by simply stopping the game in an IDLE point, and trace from there. Tracing can be done manually, or it can be done using the T command in SoftICE. - Breakpoint on known API's that are likely to get executed all the time: GetMessage, GetTickCount. Finding a Place to Inject ------------------------- OK, This is my favorite part. Windows EXE's are based on the PE (Portable Executable) file format. The PE format supports sectioning of the file. Every section of the file has a different job. There are Code/Data/Resource/Import/Export/etc sections. Most sections are different in size, so they all must be aligned. Because of that nice feature compilers leave us, there are often alot of 'dead' NULL bytes, that are simply wasted just to align the section size. There is a difference between those code caves, because these are mapped to memory, while there may be cases you will see empty places, but they won't be mapped into memory, thus you cannot jump to them. There are few ways to find the alignments in the exe: 1. You simply scroll through the entire EXE looking for those alignments. Once you see empty spaces, you check if they are indeed code caves (later on how to check). 2. In a hex editor, look for a binary string that contains nothing but 00's. It will bring you to many odd places, but eventually to a cave. 3. You do the "proffesional" way. In IDA (or any other PE editor), we get some information about the sections. We go to the beginning of the code (generally on top), and we see this: .text:00401000 ; File Name : D:\Games\THoTD2\Hod2.exe .text:00401000 ; Format : Portable executable for IBM PC (PE) .text:00401000 ! ; Section 1. (virtual address 00001000) .text:00401000 ! ; Virtual size : 000C1940 ( 792896.) .text:00401000 ; Section size in file : 000C2000 ( 794624.) .text:00401000 ; Offset to raw data for section: 00001000 .text:00401000 ; Flags 60000020: Text Executable Readable .text:00401000 ; Alignment : 16 bytes ? .text:00401000 ; OS type : MS Windows .text:00401000 ; Application type: Executable 32bit You see that the Virtual Address is 1000, and that the Virtual Size is C1940? You add those up together, and you get C2940. You go to that OFFSET in the file, and you will see a nice and clean code cave. :) In order to check if this is indeed a CODE cave, and not just a dead cave, in HIEW, you notice if there is a dot (.) before the address of not. The address will be 4C2940, if you see this: .004C2940, then this is code... if you see just 004C2940 then it won't work. Trust me on this one, I spent a lot of hours figuring why it didn't work on some injection I was working on a while ago. What to Inject? --------------- OK, So we know where to jump from, and where to jump to. But all of this is useles unless we inject something! In this tutorial, I will show how to inject a simple MessageBox. A standard MessageBoxA call looks like that: push Style push Caption push Text push WindowHandle call MessageBoxA But remember, that we have overwritten an API... so we need to execute it again, and jump to the location we came from (actually we jump to the instruction after the API call): call GetMessage jmp 4A596E After that, we need to actually inject the text for MessageBox caption and text. So I chose location 4C2970 for the caption, and 4C2980 for the actual text. The style will be left 0. So we have this code now: push 0 push 4C2970 push 4C2980 push 0 call MessageBoxA call GetMessage jmp 4A596E But this code won't work... because we push 0 as the window handle. If we use 0 as window handle, then the MessageBox will be shown OUTSIDE the game, and sometimes cause it to crash. So how do we solve this? We need to find the window handle, and push it. Basically the Window Handle is saved in a variable after the window was created. So we go back to IDA, and we track down the CreateWindowExA function. We see where it is referenced from, and we find this location: .text:004A5885 call ds:CreateWindowExA .text:004A588B test eax, eax .text:004A588D mov dword_7DC8AC, eax So we see that the hWnd is saved in a global variable which is located inside the memory address 7DC8AC. So now, we have to get that value, and push it, instead of push 0: push 0 push 4C2970 push 4C2980 mov eax, [7DC8AC] push eax call MessageBoxA call GetMessage jmp 4A596E OK, now we get a little practical and summarize what we have: 1. A place to jump from : 4A5968 2. A place to jump to : 4C2949 (not 4C2940, because I want to leave some space) 3. A place to put strings: 4C2970 4. A place to return to : 4A596E Make a copy of hod2.exe, and name if hod2_.exe or whatever. We will be doing all the changes to this one. Open up HIEW, select HOD2_.exe, and then press F4 and go to DECODE mode. Press F5, and type ".4A5968" (Without quotes ofcourse). Now you are in this loc: .text:004A5968 call ds:GetMessageA Press F3 (to be in EDIT mode), and then press F2 (to be in ASSEMBLE mode), and type this: jmp 4C2949 [Enter] nop [Enter] [Escape] Press F9 to update. now go to the cave (.4C2949), and assemble the code block we wanted to add (see above). For API's you cannot assemble, so you will have to leave 6 nop's for each API. how to add an API? Simply. Do you remember that I told you that it can be executed from anywhere in the program, and also with the same opcodes all the time? Simply go to a location that calls GetMessage (4A5968), and a location that calls MessageBoxA (49E148), and copy the opcodes (FF 15 xx xx xx xx). After that go to add the strings (F3 and then TAB), and simply write them. Remember! In order to separate strings there has to be a 00 between them: .004C2970: 54 72 61 69-6E 65 72 20-4F 70 74 69-6F 6E 73 00 Trainer Options .004C2980: 48 69 21 Hi! OK, I believe we are all set. The Trainer ----------- This is how the source of the trainer looks like: #include #include "resource.h" // Define sizes for writing into memory. #define OPT1 6 #define OPT2 58 char wname[]="The House of the Dead 2"; // Window Name BYTE opt1[OPT1]={0x90,0xE9,0xDC,0xCF,0x01,0x00}; // Jump my_injection BYTE opt1_off[OPT1]={0xFF,0x15,0x34,0x32,0x4C,0x00}; // Call GetMessage(); // The rest of the injection BYTE opt2[OPT2]={0x90,0x6A,0x00,0x68,0x70,0x29,0x4C,0x00,0x68,0x80,0x29,0x4C,0x00,\ 0xA1,0xAC,0xC8,0x7D,0x00,0x50,0xFF,0x15,0xFC,0x31,0x4C,0x00,0xFF,\ 0x15,0x34,0x32,0x4C,0x00,0xE9,0x01,0x30,0xFE,0xFF,0x00,0x00,0x00,\ 0x54,0x72,0x61,0x69,0x6E,0x65,0x72,0x20,0x4F,0x70,0x74,0x69,0x6F,\ 0x6E,0x73,0x00,0x48,0x69,0x21}; LRESULT CALLBACK main (HWND, UINT, WPARAM, LPARAM); HINSTANCE hInst; HWND g; int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { // Basic Windows API main entry hInst = hInstance; DialogBox(hInst, MAKEINTRESOURCE( IDD_DIALOG1 ), 0, (DLGPROC)main); return(0); } int Write(char windowname[], long address, BYTE *writearr, long sizeofarr) { // My WRITE engine HWND hwnd; HANDLE phandle; DWORD pid,bytesw; hwnd = FindWindow(0, windowname); if (hwnd == 0) { SetDlgItemText(g, ID_EDIT1, "Error: Cannot Find Game Window!"); goto quit; } GetWindowThreadProcessId(hwnd, (unsigned long *)&pid); phandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); if (phandle == 0) { SetDlgItemText(g, ID_EDIT1, "Error: Cannot Open Game Process!"); goto quit; } WriteProcessMemory(phandle, (void *)(address), &writearr[0],sizeofarr, &bytesw); return(1); quit: return(0); } BOOL GetKeyPress(int key) { // HotKey handling routine return ((GetAsyncKeyState(key) & 1)==1); } VOID CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime) // This is the actual thing! { if (GetKeyPress(VK_F4)) { // If F4 was pressed Write(wname, 0x4C2949, opt2, OPT2); // Write The injection Write(wname, 0x4A5968, opt1, OPT1); // Patch the call Sleep(2000); // Wait a little (2 seconds) Write(wname, 0x4A5968, opt1_off, OPT1); // Change back the call } } LRESULT CALLBACK main(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { // Main Message function g = hDlg; switch (uMsg) { case WM_INITDIALOG: SetTimer(hDlg,0, 100, TimerProc); break; case WM_COMMAND: if (wParam == ID_QUIT) ExitProcess(0x31337); break; default : return(FALSE); } return(TRUE); } Explanation: What I did, was writing all the injected bytes into memory. First I wrote the injection, then I Patched the place to jump from, then waited for 2 seconds, and then patched it back again. I didn't bother to Zero out all the injection, because it won't get executed anyway. Ending ------ Phew! That was a lot to write! I have been working 2 days on this menu + another day actually WRITING this. Looking back, it wasn't very tough, I just had to reboot alot due to blue screens and other sorts of page faults and weird exceptions. What you have to keep in mind while doing an ingame menu, is: 1. Finding a place that runs all the time. 2. Finding a code cave. 3. Adding your code, followed by doing the instructions you overwritten. 4. Finally jumping back to the place. There can be many problems and diffuculties that can occur. For example, what happens if you don't find a global variable that contains the Window Handle? For that case you can simply use FindWindow(0,"Window Name"). But what happens if you don't have FindWindow imported in the EXE? You can import it like this: push offset "User32.dll" ; Find the address of User32.DLL in memory call GetModuleHandle ; eax contains the address push offset "FindWindowA" ; Function to find push eax ; what DLL? (the one we found eariler) call GetProcAddress ; eax contains the address of the function push offset "Window Name" ; What window name? push 0 ; What CLSID? call eax ; call FindWindowA Finally, my little advice, BE CREATIVE. Think of more ideas, like adding DIALOG BOXES, with many more options, or perhaps even injecting the ENTIRE trainer to the game, so you wouldn't even have to use an external program besides a memory patcher. Attached to this package is the trainer source, and a nice pic hehe :) Thanks: [SHEEP]: Encouragement, and for telling me the Window Handle thing Duelist: For telling me where to find that Window Handle thing :) Greets: iCARUS, Have_No_Mercy, calligula, jmp_fce4, Keyboard Junky, ddh, MacDeath, Dang, and all the other dudes in #gamehacking which I forgot to add. Please bitch me if I forgot :) Orr in July 2002. OrrAghion@hotmail.com LONG LIVE ISRAEL! /\ ____/__\____ \ / \ / \/ \/ /\ /\ /__\____/__\ \ / \/1