Although I've posted lots of information about this before, the information is rather fragmented. This post will be a complete A-Z tutorial on how to do update scripts based on pattern recognition. For this tutorial, I will be using Runes of Magic in my examples.Tools required:OllyDbg
Any text editor (Notepad++
is simply opening up OllyDbg and starting the game client (or other target application) in the debugger. If it is a multiplayer online game that you are creating an update pattern for, you should not have it open at the time of doing any of this. Why? Because many online games have gotten into the bad habbit of installing rootkits on your machine to check for anything they deem as inappropriate, meaning that if a debugger is seen, it could raise a flag on your account. Runes of Magic is an exception as it does not come with any (known) malware and does not scan background applications nor check for debugging tools.
You should have OllyDbg open so now go to File->Open
and browse to the location of the game client. You must not just copy the target executable out and use that; Olly will need all of the other files (such as DLLs) to function. Be aware that many online games use a different executable for the actual game than the patcher/launcher. In our case, we'll open Program Files/Runes of Magic/Client.exe.
After a short amount of time, the target program should be loaded into Olly. You should see several windows containing a bunch of gibberish. This means you (hopefully) did something right. Under 64-bit OSes, 32-bit applications are not run directly, but through a compatibility layer of sorts. This means we need to be aware that Olly will not automatically select the proper module, but rather, the wrapper. If you do not already have the Executable modules
window showing in Olly, press ALT-E or go to View->Executable modules
. In this window, check the list for the target application again. In our example, we look for Program Files (x86)/Runes of Magic/Client.exe. When you find it, double-click it. The CPU (main thread) window
should now jump to the foreground.
So far, you should see something like this:Step 2
is centered about finding the information we want. We'll be wanting to find some memory addresses in our example; we will use 'staticbase_char', which resolves to 0x9C57EC
as of writing this tutorial.
Now, we can't simply just go to the address 0x9C57EC and expect anything useful there. Remember, that is a memory address
, where the information we want will, eventually, be loaded. We are looking for code that accesses that address
. If we can find that code, we can use it in a pattern. Then, we can have a script that finds that pattern and extracts the useful information (ie. the memory address). Everything make sense so far? Well, I hope it does, because it is a lot to get much more confusing.Right-click in the CPU (main thread) window
, and go to Search->Constant
. A small window should pop open (with the title "Enter constant to search...") which allows you to put a constant as Hexadecimal, Signed, or Unsigned. Generally, you'll only use Hexadecimal, so ignore the others for now. Since we are looking for the address 0x9C57EC, you'll type 9C57EC
into the Hexadecimal box (note that the '0x' just denotes that it is hexadecimal and doesn't actually mean anything). Click OK.
You should be brought to an area in the program that has a constant reference to the value you searched for. The line that matches the search will be highlighted. If you suspect that the line is not what you want, you can press CTRL+L to continue the search. You should end up with something like the following:
Write down the address (far left column) of the found line.
In Step 3
, you will actually start dissecting the code from the target software and create the pattern needed. Start by pressing CTRL+A
to analyze the program. This will help give us some guidelines. The dark black lines that will show up on the left will help you to distinguish functions (and the ones that show up in the right comment area help with function calls
). It is, generally, a good idea to have your pattern contained within a single function and probably not contain any 0xCC (INT3).
Examine the area round the line containing the information you want to determine what you want to make part of the pattern. A larger pattern is generally more accurate, but is more time consuming to create, slower to check for, and is more likely to contain code that might be changed in future patches that could break the pattern. The more unique lines it contains, the better. Lines such as "PUSH EAX" are going to be repeated throughout the target program hundreds of times and are probably going to be less valuable than something like "MOV BYTE PTR SS:[ESP+F],AL". Jumps (JMP, JE, JNE, etc.) can go either way. A long jump (for example, "0F84 68040000 JE Client.005E3807") largely contains dynamic information that cannot be used (the address it will jump to is subject to change). A short jump ("74 05 JE SHORT Client.005E33FA") works by saying "jump ahead a few bytes" and is unlikely to be changed, so can be very useful.
Now, select an area that you will want to use for your pattern. I'm going to use a short selection for this example to keep it simple, but you should definitely use a larger selection.
Next thing you need to know is how to read the assembly code. That is the hex digits on the left side right after the address. An 'opcode' is simply the instruction that the computer is to execute. In the first line, you see "8B45 00 MOV EAX,DWORD PTR SS:[EBP]". 8B45 is simply the command (a specific MOV or 'move' command) and 00 is an argument being passed to it, to put it simply. It just means we are moving some information into the EAX register. That line is unlikely to be changed in future patches. The opcodes will rarely ever be changed (though might be moved around), however, arguments often can, depending on their meaning.
In the next line, you see "8B0D EC579C00 MOV ECX,DWORD PTR DS:[9C57EC]". It is another MOV command, but it is different. The argument is 4 bytes (we already knew it would be; this contains the memory address we are after, which is a 4 byte integer). Again, the opcode(s) will not change, but we expect the argument to change every patch. For this reason, you need to take note that we will not be using that segment in our pattern matching.
After checking over the code, you can finally start writing your pattern. The first thing is just writing down a list of the bytes (see: assembly code) that are taken from the code segment.
0x8B, 0x45, 0x00,
0x8B, 0x0D, 0xEC, 0x57, 0x9C, 0x00,
0xE8, 0x32, 0x52, 0x04, 0x00
There are two 4-byte long segments here that are subject to change: The second line (containing our memory address), and the last line (a function call -- the address of the function will change).
It is now time to make the pattern's mask. We use this to tell the pattern matching to ignore certain bytes while ensuring that others must match. The mask is simply a string that uses the letter 'x' to denote a match and the '?' to denote a byte that doesn't need to match. The proper mask for the above pattern would be:"xxxxx????xx????"
The only other thing that you need to make note of is the offset
of the information we want and it's size. The memory address we are after starts at the 6th byte
and is 4 bytes long
is all about the actual script. All the hard work is done. We just take all of the information we gathered from above and put it to use. First, create variables to make your life easier and code cleaner.
myUpdatePattern = string.char(0x8B, 0x45, 0x00, 0x8B, 0x0D, 0xEC, 0x57, 0x9C, 0x00, 0x50, 0xE8, 0x32, 0x52, 0x04, 0x00); -- This was taken from the assembly instructions above
myUpdateMask = "xxxxx????xx????" -- The mask we created to match the pattern and ignore dynamic information
myUpdateOffset = 5; -- The position (6) minus one; offsets start at 0 to indicate no change.
MicroMacro offsets a function to scan a process for a specific pattern and mask. All the information you need to use findPatternInProcess
() has already been gathered: we need a process (the target program), pattern, mask, start address, and length (which we can just make up).
The 'start address' is just the location to start scanning for the pattern and length is how long (in bytes) to continue the search before giving up. You should, obviously, place the start address before
the location we are looking for. Since we found the address at 0x5E33A2
, a start address of 0x5E0000
should work nicely. The closer the two are, the quicker a pattern scan will find it, but you may eventually run into problems with the pattern being moved to some location before the start address causing it to not be found. As for length, a value of 0xA0000
usually works. A longer length will not necessarily slow the process down (as the scan will break free as soon as it is found), but can cause considerable delay if the pattern is not found at all. It is fairly unimportant so long as the address that we expect to find is between start address and start address + length.
At this point, it is a good idea to do some testing and make sure you are getting the expected value. If you are getting a value of 0, then that means the pattern was not found. If you are getting a value other than zero that is not the expected value, then chances are your pattern isn't unique enough and you are finding another, similar segment of code instead. You might have to refine your pattern at this point.
Be aware that findPatternInProcess() will return the address of the start of the match, not
the address of the information we are actually looking for (because, how would it know? It can't read your mind). This is where the 'offset' from earlier comes in. We know that the pattern starts 5 bytes before the address
we need, so we can just reverse that and add 5 bytes to the pattern's location.
Now, you should, finally, be able to get the data we are after. All you have to do is read it from memory. We know where the information is at (foundAddress + myUpdateOffset), the size (4 bytes), and type (integer). So, to do this, use memoryReadInt
And that's it. 'address' should now contain 0x9C57EC.