Endianess
Read/Write/Arithmetic for hexadecimal/Binary
Process Context
Thread Context
Windows Process Layout
User Mode vs Kernel Mode
C calling conventions
InterProcessCommunication
Windows Functions(VirtualAllocEx, ReadProcessMemory, WriteProcessMemory, CreateRemoteThread,LoadLibrary)
Tools:
Dynamic Analysis Tools: Cheat Engine, x32dbg, Immunity Debugger, ollydbg, ReClass,etc
Static Analysis Tools: Ghidra, IDA Pro, either one with a variation of binary diff plugins
My preferred Tools: Cheat Engine, ReClass, Immunity Debugger, and Ghidra.
What is GWA2?
It is a Application Programming Interface(API) for Guild Wars 1. What this really means is that it gives a way to interact with the Guild Wars clients through code and in this case autoit.
How does it do this magic?
GWA2 on the simplest level has a basic x86-asm compiler that converts mnemonics of assembly code written in autoit to functioning executable code. It then uses various windows functions to pattern scan for specific locations to place said payload for actions such as function hooks. Once desired locations are found for whatever features are needed the API uses windows messages and Read/WriteProcessMemory to act aka InterProcessCommunication. That is the high-level overview of how it allows you to write autoit but act within the game client.
So, what are we going to look at today?
Pattern Scanning seems to be the main topic people are posting repeatedly about.
What is pattern scanning from a bird’s eye view?
It is taking the process memory map pictured below and searching it byte by byte to find a pattern in that specific program. Generally, most patterns are in the .text section which is really the program instructions that are generated when you write code like If statements or loops.
Image Courtesy of

What do we do with a pattern once it is found?
Usually one or more offsets are added to it to get to the desired address in the game client. Once we are there we can do things like read the list of agents to find our own agent and then get hit points or the location for a function hook such as sendpacket so we can send network packets directly.
Pattern of interest today: ScanTraderFunction
The goal today is to show quick and dirty tricks for fixing common but simple pattern breaks.
Tips and Tricks:
Keep a folder of gw.exe from every patch, keep an associated listing of gwa2 patterns as they change along with offsets. In your static analysis tool of choice set bookmarks of pattern locations when they are known good to compare to an updated gw.exe.
Shorter patterns are generally better as there are less bytes to break but too short and you get too many false positives.
GWA2 Pattern Scanning Peculiarities:
The address returned by the pattern in most pattern scanners is at the very byte the pattern started. In GWA2 it is that location minus one. The scanner in GWA2 doesn’t support wild cards.
How does a pattern break?
One or more bytes change in the game executable after an update or the pattern now matches 2 or more results. The other possible situation is code outside the pattern changes slightly so that the offset must be adjusted even thought the pattern still works. Anticheat is another one but doesn’t apply for our current client.
Code:
Starting patterns:
_('ScanTraderFunction:')
AddPattern('83FF10761468AC210000') ; Old version of scantrader
SetValue('TraderFunction', '0x' & Hex(GetScannedAddress('ScanTraderFunction', -0x1E), 8));offset
Start chopping 1 byte off the end at a time searching until you get a few possible patterns.
Example:
Start: 83FF10761468AC210000
Chop 1: 83FF10761468AC2100 ; not found
Chop 2: 83FF10761468AC21 ; not found
Chop 3: 83FF10761468AC ; not found
Chop 4: 83FF10761468 found 2 patterns
Now lets look at the memory regions. I highlight in red in the lower window the interesting part.
Well look at that, looks identical to the old pattern but one byte changed. AC -> D2. We can confirm this by plugging it into gwa2 and running a test script or bot. We can also check it in Cheat Engine.
One result and I know for a fact that is a current working pattern. Something to note is in this case the start of our scan at 83 FF didn’t change so offset didn’t change. This could also be confirmed in your static and dynamic debuggers of choice outside cheat engine.
Dirty Trick #2:
Same exact technique but start chopping from the front starting with 83 instead of the end being 00.
Dirty Trick #3:
History repeats itself and thus lets us abuse wild cards in patterns that don’t change heavily.
Original Pattern: 83FF10761468AC210000
Fix 1: 83FF10761468AE210000
Fix 2: Fix 1: 83FF10761468D2210000
Notice how historically its just the one byte changing over and over? That does break our pattern since gwa2 doesn’t support wild cards but we can get a quick fix from cheat engine.
Boom got em:
Dirty Trick 4:
Binary Comparison using static analysis. This is my personal favorite to take one of the many saved gw.exe and do a diff in ghidra between the newest gw.exe. I am currently working on some old code to port so this isn’t the latest gw.exe but two older ones being compared to find the pattern update.
Notice nicely in green it showed us where the bytes changed. Specifically the update that went from:
83FF10761468AE210000 to 83FF10761468D2210000, image below:
Dirty Trick 5:
Assertion strings can be an easy way to find a pattern after a major break. So just do a string search for some of these in the picture: = "p:\\code\\gw\\char\\charmsg.cpp", = "giveItemCount <= CHAR_MAX_TRANSACT_ITEM_COUNT"
Parting Thoughts On Dirty Tricks:
Save old patterns and gw.exe to make finding them again simple.
How the hell did they find these originally?
Nearly an infinite number of ways but the most common techniques are mapping call trees based on assertion strings. My personal favorite is tracing back ws32_send until you get the pre encryption function for sendpacket. Then set a breakpoint on send packet and when the packet header matches the action of interest trace the call stack back through the functions and then map out the function for intended use. I could write books on various ways but those are the 2 most common I know of and that are openly used by people.
In summary:
ScanTraderFunction Currently equals 83FF10761468D2210000 or a variation of said pattern/offset. If fixing the pattern doesn’t work that may mean you have a broken assembly stub or offset.
Code:
ASM stub:
_('CommandTraderBuy:')
_('push 0')
_('push TraderCostID')
_('push 1')
_('push 0')
_('push 0')
_('push 0')
_('push 0')
_('mov edx,dword[TraderCostValue]')
_('push edx')
_('push C')
_('mov ecx,C')
_('call TraderFunction')
_('add esp,24')
_('mov dword[TraderCostID],0')
_('mov dword[TraderCostValue],0')
_('ljmp CommandReturn')






