Any text editor (Notepad++ recommended)
Step 1 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.
Code: Select all
005E339F |. 8B45 00 MOV EAX,DWORD PTR SS:[EBP]
005E33A2 |. 8B0D EC579C00 MOV ECX,DWORD PTR DS:[9C57EC] ; <--- THIS LINE HERE
005E33A8 |. 50 PUSH EAX ; /Arg1
005E33A9 |. E8 32520400 CALL Client.006285E0 ; \Client.006285E0
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.
Code: Select all
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:
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.
Step 4 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.
Code: Select all
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.
Code: Select all
local foundAddress = 0;
foundAddress = findPatternInProcess(process, myUpdatePattern, myUpdateMask, 0x5E0000, 0xA0000);
-- We expect foundAddress to be 0x5E339F - the start of the pattern
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().
Code: Select all
local address = memoryReadInt(proc, foundAddress + myUpdateOffset);
And that's it. 'address' should now contain 0x9C57EC.