Difference between revisions of "User:Nosoop/Guide/Advanced"

From AlliedModders Wiki
Jump to: navigation, search
(Fixup section name, add links to makesig scripts, elaborate on signature failure case)
Line 1: Line 1:
 
= Finding VTable Offsets =
 
= Finding VTable Offsets =
  
A ''virtual method table'' (shorthand &quot;vtable&quot;) is effectively an array of function pointers. It's intended for inheritance — <code>::DoThing()</code> can be different for different classes.
+
In C++, a ''virtual method table'' (shorthand &quot;vtable&quot;) is effectively an array of function pointers. It's intended for inheritance — a virtual <code>::DoThing()</code> method can be different for different classes, and so the code will look up the correct function for a specific instance based on the table for the instance's class.
  
 
== The Hard Way ==
 
== The Hard Way ==
Line 12: Line 12:
  
 
If the game isn't stripped of debugging symbols, use [https://asherkin.github.io/vtable/ asherkin's VTable Dumper].  It provides correct offsets for Linux binaries (as it's what it works with), and estimates usually correct offsets for Windows.
 
If the game isn't stripped of debugging symbols, use [https://asherkin.github.io/vtable/ asherkin's VTable Dumper].  It provides correct offsets for Linux binaries (as it's what it works with), and estimates usually correct offsets for Windows.
 +
 +
There are instances where the dumper isn't correct (such as for multiple inheritance), so you may need to be careful in those cases.
  
 
= Finding Functions =
 
= Finding Functions =
Line 19: Line 21:
 
* TODO suggest opening IDA's options and enabling opcode bytes
 
* TODO suggest opening IDA's options and enabling opcode bytes
 
* TODO inlined functions
 
* TODO inlined functions
 +
* TODO debugging
  
 
= Creating Signatures =
 
= Creating Signatures =
Line 44: Line 47:
 
As a solution to this, you use wildcards to mask off the bytes you don't care about. The sequence <code>\x2A</code> indicates that particular byte shouldn't be checked and to continue to the next one.
 
As a solution to this, you use wildcards to mask off the bytes you don't care about. The sequence <code>\x2A</code> indicates that particular byte shouldn't be checked and to continue to the next one.
  
Here is what the previous signature with the masked bytes designated as <code>??</code>:
+
Here is what the previous signature looks like with the masked bytes displayed as <code>??</code>:
  
 
<pre>
 
<pre>
Line 74: Line 77:
 
= Finding Addresses =
 
= Finding Addresses =
  
TODO explain what <code>read</code> and <code>offset</code> does
+
Sometimes you have a symbol, but you need an address to work with.  That is what the "Addresses" section of a game configuration file is used for.
 +
 
 +
To find an address, you start from a known location reference (signature).  You may then have to jump to references (that is, dereference locations), then get an offset from the previous reference.
 +
 
 +
<code>read</code> keys indicate an offset to load / dereference relative to the previous address, and <code>offset</code> means to shift the previous address without any dereference.
 +
 
 +
For a C++-like example:
 +
 
 +
<pre class="cpp">// start from an address
 +
uintptr_t addr = FindLocation("some_signature");
 +
addr = *reinterpret_cast<uintptr_t*>(addr + 40); // gameconf: "read" "40"
 +
addr = *reinterpret_cast<uintptr_t*>(addr); // gameconf: "read" "0"
 +
addr += 13; // gameconf: "offset" "13"
 +
</pre>
  
 
{{Note|This section is a work-in-progress.}}
 
{{Note|This section is a work-in-progress.}}

Revision as of 18:14, 11 October 2020

Finding VTable Offsets

In C++, a virtual method table (shorthand "vtable") is effectively an array of function pointers. It's intended for inheritance — a virtual ::DoThing() method can be different for different classes, and so the code will look up the correct function for a specific instance based on the table for the instance's class.

The Hard Way

  • TODO count offsets in IDA
Note:This section is a work-in-progress.

The Easy Way

If the game isn't stripped of debugging symbols, use asherkin's VTable Dumper. It provides correct offsets for Linux binaries (as it's what it works with), and estimates usually correct offsets for Windows.

There are instances where the dumper isn't correct (such as for multiple inheritance), so you may need to be careful in those cases.

Finding Functions

  • TODO refer to public SDK if you don't know what you're looking for
  • TODO explain what to do in a game with symbols
  • TODO suggest opening IDA's options and enabling opcode bytes
  • TODO inlined functions
  • TODO debugging

Creating Signatures

The Hard Way

After you've found a function, you need to tell SourceMod the sequence of bytes unique to it. Those bytes make up a signature.

You could treat just the sequence bytes as the signature directly, but this would break very easily whenever the game is updated. At the machine-code level, the instructions might be the same for "move X to Y", but the data might change — X and Y might be in a different location in the binary altogether. For an example within a longer signature:

; sets esp to the offset aString
; the bytes 3B B3 25 01 are the absolute offset of aString in this binary
C7 04 24 3B B3 25 01    mov     dword ptr [esp], offset aString

; call function, the four bytes after E8 are the location of the function
E8 78 F0 48 00          call    _Z12UTIL_VarArgsPKcz

; sets eax to arg 0
8B 45 08                mov     eax, [ebp+arg_0]

The naive signature for that would be \xC7\x04\x24\x2B\xB3\x25\x01\xE8\x78\xF0\x48\x00\x8B\x45\x08. However, you can't rely on those bytes mentioned to be constant at all; the offsets of aString and UTIL_VarArgs might be located somewhere else after a game update.

As a solution to this, you use wildcards to mask off the bytes you don't care about. The sequence \x2A indicates that particular byte shouldn't be checked and to continue to the next one.

Here is what the previous signature looks like with the masked bytes displayed as ??:

C7 04 24 ?? ?? ?? ??    mov     dword ptr [esp], offset aString
E8 ?? ?? ?? ??          call    _Z12UTIL_VarArgsPKcz
8B 45 08                mov     eax, [ebp+arg_0]

A masked signature would then be \xC7\x04\x24\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\x8B\x45\x08.

Masking is used mainly for offsets, such as for functions and variables. Instructions generally don't change unless the function code itself is modified, at which point you'll want to revisit your binary and update accordingly.

If you're using DHooks with byte signatures (covered later), you may want to also mask out the first five or six bytes, as a detour will patch in an unconditional JMP at the start.

For an extended lesson, you can look at the following material:

The Easy Way

If you're using IDA (including Free), use the makesig.idc script. If you're using Ghidra, use makesig.py.

They generally do pretty well at finding and masking byte signatures, but when it fails or you want a more robust signature, you should understand how to create the signatures manually.

It's exceedingly rare, but possible that the binary has two copies of the exact same short function (for example, when they are typechecked and statically casted to different subclasses). Both scripts will fail in that case. SourceMod's signature scanner will use the first match it finds, so if any match is acceptable, you can still use an appropriately masked signature.

Be sure to look at the disassembly to make sure that the functions are indeed the same.

Finding Addresses

Sometimes you have a symbol, but you need an address to work with. That is what the "Addresses" section of a game configuration file is used for.

To find an address, you start from a known location reference (signature). You may then have to jump to references (that is, dereference locations), then get an offset from the previous reference.

read keys indicate an offset to load / dereference relative to the previous address, and offset means to shift the previous address without any dereference.

For a C++-like example:

// start from an address
uintptr_t addr = FindLocation("some_signature");
addr = *reinterpret_cast<uintptr_t*>(addr + 40); // gameconf: "read" "40"
addr = *reinterpret_cast<uintptr_t*>(addr); // gameconf: "read" "0"
addr += 13; // gameconf: "offset" "13"
Note:This section is a work-in-progress.

Calling Game Functions

Note:This section is a work-in-progress.

Hooking Game Functions (with DHooks)

Note:This section is a work-in-progress.