Advanced Warfare uses a "new" method for storing dvars in that it doesn't directly push the dvar pointer's name during registration. It registers these dvars using dynamic addresses generated from your configuration file meaning a dvar offset for you won't necessarily be the same for someone else. This severely impacts people making trainers as they don't possess the knowledge on how to bypass it. ( Shots fired )
Luckily for us, there is a way to reverse this. I'll show you how to do it.
First, let's get rid of the packing used in Advanced Warfare by dumping the process. We can do this by running the exe then dumping the memory as it's being run. You can use various applications for this. Hell you can even use Task manager.
I have personally used Scylla ( simply google ) to dump my process. Anyways, after you've done that, you should end up with a fairly large file.
Mine is about 800 MB in size.
Now, let's open IDA 64-bit. ( You can use other debuggers but for the same of simplicity... )
Next we'll open our string table ( Shift + F12 )
We'll now search for a simple dvar. Let's try the common dvar "com_maxfps"
Good. We'll just jump to the cross referenced location where this name is used. ( Select "aCom_maxfps" and press X )
Seems there's only one string reference. That makes our work easier. Let's go to that:
What the fuck is this shit? Well, it's the string reference table I was talking about above. Basically, this is an array of string pointers that references each string by an ID number ( Index 0 = "aSnd_erroronmis" etc. )
So for us to actually find and dump these strings, we'll need to find out how this ticks.
It's quite simple. Let's cross-reference where the base of this string array table is used.
Seems there's two functions referencing this. ( N.B. I have reversed these before so your functions will always have a "sub_xxxxxxxxxxxxxxxx" name )
Go to the first one.
Let's break this down. Looking at the overview of x64 calling conventions we can see that parameters are passed in registers RCX, RDX, R8 and R9 for __fastcall calls. We can see RCX is being used and thus only 1 parameter is being used.
Yay. Hopefully you understood the above ( Not my problem if you're stupid. )Code:.text:00000001404A7670 movsxd rax, ecx ; Move the index number into RAX .text:00000001404A7673 lea rcx, stringTableLookup ; Load RCX with the base address of the string table array earlier mentioned .text:00000001404A767A mov rax, [rcx+rax*8] ; Access the pointer of the ID. ( Size of a pointer on x64 processes = 8 bytes ) .text:00000001404A767E retn ; Return the address
Anyways, now that we have that knowledge, we can build a simple application to dump all these string ID numbers
Now that we have these IDs, we can do simple binary strings to find where each string is referenced in your file dump.Code:void DumpFile(){ char** baseStringTable = (char**)0x14099CA00; // The base table used in the function above. for( signed int i=0; i<3205; i++ ){ DumpToFile( "StringDump.txt", "idx: [ %x ] str: [ %s ]", i, baseStringTable[ i ] ); } }
Remember when we searched for this?
Well we found the ID for it. Now we can do a binary search for it.
The general format is:
So let's convert our com_maxfps IDX ( 2605 ) to hex: 00 00 0A 2D ( Big Endian Order )Code:mov ecx, < idx > ; The ID of the string we want -- B9 ?? ?? ?? ?? call LookupString ; Our string finding function
Since I'm using a little endian CPU, I need to reverse the byte order. Thus my format will be:
B9 2D 0A 00 00
Searching for this pattern using IDA's binary search function: ( Alt + B )
Leads me to this:
And a code dump of the following:
So now we have our dvar pointer which will NOT change.Code:.text:00000001403B4C88 mov ecx, 0A2Dh ; com_maxFps IDX .text:00000001403B4C8D mov cs:off_1472458E8, rax ; This is from the previous call to RegisterDvar() .text:00000001403B4C94 call LookupString ; Calls our lookup string function to retrieve the com_maxFPS string .text:00000001403B4C99 lea r9d, [rdi+64h] ; Below here are parameters used for calling RegisterDvar. Ignore these .text:00000001403B4C9D lea edx, [rdi+55h] .text:00000001403B4CA0 xor r8d, r8d .text:00000001403B4CA3 mov rcx, rax .text:00000001403B4CA6 mov [rsp+38h+var_18], edi .text:00000001403B4CAA call sub_1404A3950 ; This is our register dvar function ( Reverse this for more fun ) .text:00000001403B4CAF mov ecx, 0A2Eh ; This is some other dvar .text:00000001403B4CB4 mov cs:off_1472452F8, rax ; This is what we want. RAX contains our pointer for our dvar registered ( Aka com_maxFPS )
How do we use it?
Simple:
0x1472452F8 Points to the base of the dvar structure. + 16 bytes into that structure contains the value for the current dvar.
Aka in C++:
Code:INT64 dvarPointer = 0x1472452F8; INT64 dvarBase = *PINT64( dvarPointer ); // Get the dvar base from the pointer PINT64( dvarBase + 16 ) = 999; // Set the value for com_maxFPS