I'm using the term "hack" very loosely here. Essentially, I'm going to show you how to make external memory based C++ "trainers" for Advanced Warfare.
Requirements:
- Basic knowledge of C++ and Pointers
- Basic knowledge of Windows ( Process IDs etc. )
- Basic assembly knowledge
- A decompiler or Cheat Engine ( It's up to you )
- A brain. ( This is the most important part )
Step 1: You'll need to do a memory dump of Advanced Warfare. I described how to do it in the first 2 paragraphs in this post.
Step 2: Now that you have your memory dump, you'll need to open it in your debugger. If you're using cheat engine, a memory dump isn't required since you can just find offsets the old fashioned way. For this tutorial however, I'm going to show you how you can create a trainer to edit dvars by name from an external application.
Step 3: Since every CoD game, dvar-related functions can always be found by a string reference. The particular string is:
"Can't create dvar '%s': %i dvars already exist"
So let's do a string search for this. I'm using IDA.
The cross reference to this string is always present in the RegisterDvar function.
Let's go to that. We'll see the below code.
Code:
.text:00000001404A3AC3 lea rdx, aCanTCreateDvar ; "Can't create dvar '%s': %i dvars alread"... - Our error string
.text:00000001404A3ACA mov r9d, 2000h ; _QWORD ; Maximum number of dvars the game can use
.text:00000001404A3AD0 mov r8, rsi ; _QWORD ; Dvar name
.text:00000001404A3AD3 xor ecx, ecx ; type ; Type = 0 = Fatal error
.text:00000001404A3AD5 call Com_ErrorMessage ; Calls the error function message to alert the user
Right so this function registers dvars but that doesn't really help us, does it? We want to know how to edit dvars, not create them. So for that, we'll need to find out what functions call this "RegisterDvar" function. So let's go to the top of our subroutine:
Now select a cross reference. ( X button in IDA while the cursor is on "sub_xxxx" )
As we can see, there's several cross references. Let's try the first function. Ideally, we're looking for a short function that functions as a "wrapper" ( I.E. It mainly checks if a dvar has been registered with a name identical to one in the array of registered dvars. )
Scrolling up or down we can see that the first function well exceeds over 300 bytes. That's way too much for a simple "If dvar already exists" function.
Let's try the second function:
Code:
.text:00000001404A34B0 ; int __fastcall Dvar_RegisterBool(char *dvarName, QWORD defaultValue, QWORD maxValue)
.text:00000001404A34B0 Dvar_RegisterBool proc near ; CODE XREF: sub_14000EDF0+80p
.text:00000001404A34B0 ; sub_14000EDF0+9Fp ...
.text:00000001404A34B0
.text:00000001404A34B0 var_38 = qword ptr -38h
.text:00000001404A34B0 var_30 = qword ptr -30h
.text:00000001404A34B0 var_28 = xmmword ptr -28h
.text:00000001404A34B0 var_18 = xmmword ptr -18h
.text:00000001404A34B0 arg_0 = qword ptr 8
.text:00000001404A34B0 arg_8 = qword ptr 10h
.text:00000001404A34B0
.text:00000001404A34B0 mov [rsp+arg_0], rbx
.text:00000001404A34B5 mov [rsp+arg_8], rsi
.text:00000001404A34BA push rdi
.text:00000001404A34BB sub rsp, 50h
.text:00000001404A34BF xor eax, eax
.text:00000001404A34C1 mov rdi, rcx
.text:00000001404A34C4 mov ecx, 34h
.text:00000001404A34C9 mov qword ptr [rsp+58h+var_28], rax
.text:00000001404A34CE mov qword ptr [rsp+58h+var_28+8], rax
.text:00000001404A34D3 mov rax, gs:58h
.text:00000001404A34DC mov ebx, r8d
.text:00000001404A34DF mov dword ptr [rsp+58h+var_18], 0
.text:00000001404A34E7 mov byte ptr [rsp+58h+var_18], dl
.text:00000001404A34EB mov edx, cs:TlsIndex
.text:00000001404A34F1 mov rax, [rax+rdx*8]
.text:00000001404A34F5 or [rax+rcx], ebx
.text:00000001404A34F8 mov rcx, rdi
.text:00000001404A34FB call Dvar_FindByString
.text:00000001404A3500 mov rsi, rax
.text:00000001404A3503 test rax, rax
.text:00000001404A3506 lea rax, [rsp+58h+var_28]
.text:00000001404A350B movaps xmm0, [rsp+58h+var_28]
.text:00000001404A3510 movaps xmm1, [rsp+58h+var_18]
.text:00000001404A3515 movdqa [rsp+58h+var_28], xmm0
.text:00000001404A351B movdqa [rsp+58h+var_18], xmm1
.text:00000001404A3521 jnz short loc_1404A354A
.text:00000001404A3523 lea r9, [rsp+58h+var_18]
.text:00000001404A3528 mov r8d, ebx
.text:00000001404A352B xor edx, edx
.text:00000001404A352D mov rcx, rdi
.text:00000001404A3530 mov [rsp+58h+var_38], rax
.text:00000001404A3535 call RegisterDvar
.text:00000001404A353A mov rbx, [rsp+58h+arg_0]
.text:00000001404A353F mov rsi, [rsp+58h+arg_8]
.text:00000001404A3544 add rsp, 50h
.text:00000001404A3548 pop rdi
.text:00000001404A3549 retn
.text:00000001404A354A ; ---------------------------------------------------------------------------
.text:00000001404A354A
.text:00000001404A354A loc_1404A354A: ; CODE XREF: Dvar_RegisterBool+71j
.text:00000001404A354A mov [rsp+58h+var_30], rax
.text:00000001404A354F lea rax, [rsp+58h+var_18]
.text:00000001404A3554 mov r9d, ebx
.text:00000001404A3557 xor r8d, r8d
.text:00000001404A355A mov rdx, rdi
.text:00000001404A355D mov rcx, rsi
.text:00000001404A3560 mov [rsp+58h+var_38], rax
.text:00000001404A3565 call sub_1404A41D0
.text:00000001404A356A mov rbx, [rsp+58h+arg_0]
.text:00000001404A356F mov rax, rsi
.text:00000001404A3572 mov rsi, [rsp+58h+arg_8]
.text:00000001404A3577 add rsp, 50h
.text:00000001404A357B pop rdi
.text:00000001404A357C retn
.text:00000001404A357C Dvar_RegisterBool endp
This seems much shorter.
The interesting part in this function is the following code:
Code:
.text:00000001404A34FB call Dvar_FindByString ; Calls Dvar_FindByString ( I've reversed this. It'll appear as sub_xxx to you )
.text:00000001404A3500 mov rsi, rax ; Dvar_FindByString returns a pointer to the string passed
.text:00000001404A3503 test rax, rax ; If it returns NULL ( 0 ) no dvar was found. This checks for that.
.text:00000001404A3506 lea rax, [rsp+58h+var_28] ; Below prepares for registering the dvar, ignore these if you want
.text:00000001404A350B movaps xmm0, [rsp+58h+var_28]
.text:00000001404A3510 movaps xmm1, [rsp+58h+var_18]
.text:00000001404A3515 movdqa [rsp+58h+var_28], xmm0
.text:00000001404A351B movdqa [rsp+58h+var_18], xmm1
.text:00000001404A3521 jnz short loc_1404A354A ; If the test rax,rax above returns 1 ( Aka the dvar exists ) it leaves the function
So we basically have our function we want. Jump to Dvar_FindByString
Code:
.text:00000001404A20E0 Dvar_FindByString proc near ; CODE XREF: sub_1404A0DC0+83p
.text:00000001404A20E0 ; sub_1404A1770+33p ...
.text:00000001404A20E0
.text:00000001404A20E0 arg_0 = qword ptr 8 ; This is the dvar name. Eg. "com_maxfps"
.text:00000001404A20E0
.text:00000001404A20E0 mov [rsp+arg_0], rbx
.text:00000001404A20E5 push rdi ; Prologue function. Allocate 0x20 bytes on the stack for local variables.
.text:00000001404A20E6 sub rsp, 20h
.text:00000001404A20EA mov rdi, rcx ; The below is to avoid deadlocks ( google ) during dvar registration. Ignore this.
.text:00000001404A20ED lock inc cs:DvarLock
.text:00000001404A20F4 mov eax, cs:dword_14A90E1B4
.text:00000001404A20FA test eax, eax
.text:00000001404A20FC jz short loc_1404A2111 ; This is where the fun begins
.text:00000001404A20FE xchg ax, ax
.text:00000001404A2100
.text:00000001404A2100 loc_1404A2100: ; CODE XREF: Dvar_FindByString+2Fj
.text:00000001404A2100 xor ecx, ecx ; dwMilliseconds
.text:00000001404A2102 call j_Sleep
.text:00000001404A2107 mov eax, cs:dword_14A90E1B4
.text:00000001404A210D test eax, eax
.text:00000001404A210F jnz short loc_1404A2100
.text:00000001404A2111
.text:00000001404A2111 loc_1404A2111: ; CODE XREF: Dvar_FindByString+1Cj - Aka "Fun"
.text:00000001404A2111 mov rcx, rdi ; Move the dvar name pointer into RCX
.text:00000001404A2114 call HashString ; Calls a function that hashes the string
.text:00000001404A2119 movsxd rbx, eax ; The result is a DWORD stored in EAX
.text:00000001404A211C lea rax, DvarArrayPtr ; This is an array pointer of hashes strings
.text:00000001404A2123 mov rbx, [rax+rbx*8] ; The dvar pointer is stored at ( HashArray[ HashValue ] )
.text:00000001404A2127 test rbx, rbx ; Check if a dvar exists at this index
.text:00000001404A212A jz short loc_1404A2148 ; If no dvar exists, return 0
.text:00000001404A212C nop dword ptr [rax+00h] ; Ignore this. Activision brainfarted here.
.text:00000001404A2130
.text:00000001404A2130 loc_1404A2130: ; CODE XREF: Dvar_FindByString+66j
.text:00000001404A2130 mov rdx, [rbx] ; Compare the dvar name stored at the hash value array index
.text:00000001404A2133 mov rcx, rdi ; to our provided name ( rcx )
.text:00000001404A2136 call stricmp ; String compare function
.text:00000001404A213B test eax, eax ; If it returns NULL ( Aka EAX == 0 )
.text:00000001404A213D jz short loc_1404A215C ; Then we found our dvar -> Return the pointer
.text:00000001404A213F mov rbx, [rbx+58h] ; The bottom of the dvar struct contains a single linked-list pointing to the next dvar
.text:00000001404A2143 test rbx, rbx ; If this value is not zero, it iterates to the next dvar.
.text:00000001404A2146 jnz short loc_1404A2130 ; And performs the stricmp() again
.text:00000001404A2148
.text:00000001404A2148 loc_1404A2148: ; CODE XREF: Dvar_FindByString+4Aj
.text:00000001404A2148 lock dec cs:DvarLock
.text:00000001404A214F xor eax, eax ; This returns 0
.text:00000001404A2151 mov rbx, [rsp+28h+arg_0]
.text:00000001404A2156 add rsp, 20h
.text:00000001404A215A pop rdi
.text:00000001404A215B retn
.text:00000001404A215C ; ---------------------------------------------------------------------------
.text:00000001404A215C
.text:00000001404A215C loc_1404A215C: ; CODE XREF: Dvar_FindByString+5Dj
.text:00000001404A215C lock dec cs:DvarLock
.text:00000001404A2163 mov rax, rbx ; This returns our found dvar pointer.
.text:00000001404A2166 mov rbx, [rsp+28h+arg_0]
.text:00000001404A216B add rsp, 20h
.text:00000001404A216F pop rdi
.text:00000001404A2170 retn
.text:00000001404A2170 Dvar_FindByString endp
So from the above code, we can gather that our dvar structure looks like this:
Code:
struct dvar_s{
char* dvarName; // 0x0000 - Pointer to the dvar name
struct dvar_s* next; // 0x0058
}; // Size: [ 0x60 ]
From common knowledge, we know for AW, the value is stored at dvar_s + 0x10 ( 16 )
So basically:
Code:
struct dvar_s{
char* dvarName; // 0x0000 - Pointer to the dvar name
char _unk[ 8 ]; // 0x0008
enum{ // 0x0010
int iValue;
float fValue;
double dblValue;
float flVec2[2];
float flVec3[3];
float flVec4[4];
}
char _unk2[0x38]; // 0x0020
struct dvar_s* next; // 0x0058
}; // Size: [ 0x60 ]
So how do we edit dvars? Well we have the dvar structure and the HashValue function.
All we need to do is emulate the above Dvar_FindByString() and we can edit the dvars with the structure.