Creating a Simple Hack with MASMIn this tutorial we will cover basic syntax, how to read and write to memory, and how to use a function of the game. The target game in this tutorial is Stacraft with the latest (1.15.2) patch. I will go about doing this by posting the code in small blocks and then telling you what happens line by line.
When doing line counts I -do- count blank lines, so be sure to keep an eye on that if you get confused as to what I'm talking about. Also, this is a fairly long read, so if you're not interested in programming your own hacks, it will probably be a big waste of time.
Prerequisites: The MASM Compiler
First off, create a new text file, name it whatever you like. Open it with notepad. Add the following code:
Line #1: Declares that the DLL is going to use the i386 (x86) architecture.
.model Flat, StdCall
OPTION CASEMAP :NONE
Line #2: Use StdCall when calling things.
Line #3: Make everything in the project case-sensitive.
Lines #5-9: Including these libraries gives us access to the generic windows APIs.
Lines #10-13: Including these makes it so that we can link our .obj file to a .dll
Line #15: Memory.asm will include the majority of our code, but it's nice to have it separate from this file.
These declarations are put in every project that you ever make (of course maybe with more things added or some things changed or taken away.)
Unknown Variable Declarations
Line #1: Declares that this is the beginning of the code section
ThreadID dd ?[/ syntax]
Line #1: Declares that this is the section for the variables that aren’t initialized with a value.
Line #2: ThreadID is the name of our variable that will hold the ID of our new thread that we will create, but since we don’t know the idea yet it’s marked by a ‘?’
DllEntryPoint proc hInstDLL:DWORD, reason:DWORD, unused:DWORD
mov eax, reason
.if eax == DLL_PROCESS_ATTACH
invoke CreateThread, NULL, 0, addr DLLProc, 0, 0, addr ThreadID
Line #3: ‘DLLEntryPoint’ is the function that injectors call after they have written the DLL to the memory
Line #5: We move the reason that the DLL has been loaded into the EAX register
Line #6: If EAX (the reason the DLL was loaded) is because it has attached then…
Line #7: We create a new thread. The first function to be called in this thread is ‘DLLProc’ (a proc which we will be putting in Memory.asm)
Line #10: Declares the end of the ‘DLLEntryPoint’ proc
Line #11: Declares the end of the entry point of our DLL
Part Two – Memory.asm
Line #1: This declares section for the variables that we do already know the values to
szHackOn db "Zerg Underling Limit Hack - ", 07h, "On", 0
szHackOff db "Zerg Underling Limit Hack - ", 04h, "Off", 0
blHackStatus db 0
xxHackAddr dd 004888FAh
BWFXN_PrintText dd 0048CE60h
JmpByte db 0EBh
JeByte db 074h
Lines #2-3: The 'sz' prefix to the variable name indicates to everyone who reads the source (it doesn't matter to MASM whether or not you have it) that this is a string.
Line #4: The 'bl' prefix indicates that it's meant to be used as a boolean (true or false.) Initially the hack is off, so we have it set to 0 (false.)
Line #5: This holds the address that I'm going to patch.
Line #6: This is the address of the function in the game that prints a specified text to the screen. We will be using this to let the user know if the hack is on or off.
Line #7: An unconditional jump (jmp) in hex is shown as 'EB'
Line #8: A jump if equal to jump (je) in hex is shown as '74'
- Please notice the null terminator at the end of all strings (the 0.) Always include that on the end of any string.
- Please notice the difference between the 'db' and the 'dd'. 'db' is used for smaller things, such as strings and booleans; 'dd' is used for larger things, such as memory addresses.
- Please notice the 'h' at the end of the addresses. This signals to MASM that it is a hex value, which is very very important.
Unknown Variable Declarations
Line #1: This declares the proc named 'DLLProc'
HndHook dd ?[/ syntax]
Line #1: Discussed in part 1
Line #2: This was also discussed in part one, but I just wanted to note what we're going to be using this variable for. This variable is going to hold the handle to the hook on our keyboard.
Initializing Our Hook
As noted at the end of part one, after the DLL is injected, the function 'DLLProc' would be started under a new thread, so here's that code now.
invoke FindWindow, CTEXT ("SWarClass"), 0
invoke GetWindowThreadProcessId, eax, 0
invoke SetWindowsHookEx, WH_KEYBOARD, addr HotKeys, NULL, eax
mov HndHook, eax
Line #3: This gets the hWnd of the StarCraft window based on its class name ("SWarClass"). As with every other windows API I have used so far, the result of it is stored in the register 'eax' (will be used in Line #4.)
Line #4: This gets the pID of Starcraft based on the hWnd of its window (stored in eax, as earlier noted.) The output pID is put into the eax register.
Line #6: This creates a hook on the keyboard using the pID that was in eax. So now any time anything is pressed on the keyboard, it will go to our 'HotKeys' function which I will discuss next.
Line #7: This moves the handle of our hook which was stored in eax into our variable HndHook for later access.
Line #9: This makes the thread sleep for an infinite time. Without this, the thread would die and our hack wouldn't work.
Line #11: Signals that the proc 'DLLProc' is done with.
The Actual Hack Code
This is the largest piece of code in this tutorial, but in my opinion, the most simple.
Line #1: Declares the HotKeys proc
HotKeys proc nCode:DWORD, wParam:DWORD, lParam:DWORD
.if nCode != HC_ACTION
mov ebx, lParam
or ebx, 00FFFFFFh
.if ebx == 0C0FFFFFFh
.if wParam == VK_F7
mov blHackStatus, 1
invoke SCTxtOut, addr szHackOn, -1
invoke WriteMem, HackAddr, addr JmpByte, 1
mov blHackStatus, 0
invoke SCTxtOut, addr szHackOff, -1
invoke WriteMem, HackAddr, addr JeByte, 1
invoke CallNextHookEx, HndHook, nCode, wParam, lParam
Lines #3-5: If nothing is being pressed, go to HotKeys_End
Lines #7-11: If the key being pressed is going up instead of down, go to HotKeys_End
Line #13: If the key being pressed is F7...
Line #15: If our hack is off...
Line #17: Change the hack status boolean to on
Line #18: Call the 'SCTxtOut' function to alert the user that the hack is on (the code for this function will be shown later.)
Line #19: Call the WriteMem function to write the unconditional jump to the address of the hack to replace the conditional jump. The 1 at the end of the line indicates how many bytes to be written, and in this case it's one. (The code for this function will be shown later.)
Line #21: If our hack is on...
Line #23: Change the hack status boolean to off
Line #24: Call the 'SCTxtOut' function to alert the user that the hack is off
Line #25: Call the WriteMem function to write the conditional jump to the address of the hack to replace the unconditional jump. The 1 at the end of the line has already been discussed.
Line #31: Simply a label that could be jmp'd to (as shown earlier in this piece of code.)
Line #33: This resets our hook on the keyboard using the handle that we stored in HndHook earlier.
Note: The 'addr' string that some of you may have noticed refers to 'the address of <input>'
The SCTextOut Function
Line #1: Declares the function with the custom arguments 'txt' and 'code'. 'txt' is the actual text to be printed to the screen and 'code' is the format code. In the previous piece of code, we called it with the '-1' for the code argument, which will format it to the center of the screen.
SCTxtOut proc txt:DWORD, code:DWORD
mov eax, code
call dword ptr [BWFXN_PrintText]
Line #3: Push all of the registers on to the stack so we don't mess up the other code that had previously been going on.
Line #4: Move the format code into eax, because that's where Starcraft's function will look for it at (this is found out through reversing with Olly or similar.)
Line #5: Push our text onto the stack so that Starcraft will read that too.
Line #6: Call the actual function in Starcraft.
Line #7: Restore the registers that we earlier pushed into the stack for safe keeping
The WriteMem Function
Line #1: Declare the WriteMem function with our custom arguments. MemOffset is the memory address that we want to write to. DataPtr is the pointer to the data that we're going to write. dataLen is the length (in bytes) of the data that we're going to write.
WriteMem proc MemOffset:DWORD, DataPtr:DWORD, dataLen:DWORD
invoke VirtualProtect, MemOffset, dataLen, PAGE_EXECUTE_READWRITE, addr OldProt
invoke RtlMoveMemory, MemOffset, DataPtr, dataLen
invoke VirtualProtect, MemOffset, dataLen, OldProt, addr OldProt
Line #3: Set up the variable 'OldProt' which will store the old memory access privileges.
Line #5: These changes the privileges on the memory that we want to change so that we can (if we couldn't before.)
Line #6: Copy the memory from DataPtr to MemOffset for the length of dataLen.
Line #7: Restore old memory access privileges.
Congrats, you finished the tutorial.