Skip to content

Commit 0520ff6

Browse files
authored
Add statistics and introduce new hook format (#4)
1 parent 18aa6fa commit 0520ff6

File tree

15 files changed

+70040
-14
lines changed

15 files changed

+70040
-14
lines changed

Debug.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,21 @@ def load_sect_map(map_path: Path) -> list[tuple[int, str]]:
6767
return map
6868

6969

70+
def can_convert_to_hex(num: str):
71+
try:
72+
int(num, 16)
73+
return True
74+
except ValueError:
75+
return False
76+
77+
7078
def get_stack_trace(data: list[str]) -> list[int]:
7179
stack_trace = []
7280
for line in data:
7381
if line.startswith("Stacktrace:"):
7482
_, * trace = line.split(" ")
75-
stack_trace.extend((int(i, 16) for i in trace))
83+
stack_trace.extend((int(i, 16)
84+
for i in trace if can_convert_to_hex(i)))
7685
break
7786
return stack_trace
7887

@@ -118,7 +127,7 @@ def main(patches_folder_path, args):
118127
else:
119128
break
120129

121-
exe_map = load_exe_map(Path("./exe.map"))
130+
exe_map = load_exe_map(Path("./statistics/exe.map"))
122131
sect = load_sect_map(Path(patches_folder_path)/"build"/"sectmap.txt")
123132
exe_map.extend(sect)
124133

Patterns.md

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
During REing you will notice certain patterns.
2+
3+
## Object ctor and dtor
4+
5+
These patterns will help with noticing certain structures when REing code.
6+
7+
### std::vector
8+
9+
In ctor you won't notice it at first glance. However if you look closely:
10+
11+
```cpp
12+
...
13+
*(_DWORD *)(a + 1) = 0;
14+
*(_DWORD *)(a + 2) = 0;
15+
*(_DWORD *)(a + 3) = 0;
16+
...
17+
```
18+
You'll notice that `*a` wasn't set to anything, and if you find dtor:
19+
```cpp
20+
...
21+
if(*(a + 1))
22+
{
23+
...
24+
free(*(_DWORD *)(a + 1)); // or operator delete(*(a + 1));
25+
}
26+
*(_DWORD *)(a + 1) = 0;
27+
*(_DWORD *)(a + 2) = 0;
28+
*(_DWORD *)(a + 3) = 0;
29+
...
30+
```
31+
This is a definitely std::vector and function before free (if it presents) may suggest you the type of vector.
32+
33+
```cpp
34+
...
35+
if(a->v.begin)
36+
{
37+
...
38+
free(a->v.begin); // or operator delete(*(a + 1));
39+
}
40+
a->v.begin = 0;
41+
a->v.end = 0;
42+
a->v.capacity_end = 0;
43+
...
44+
```
45+
46+
**Note: offsets may be different, but overall idea stays**
47+
48+
An example:
49+
50+
```cpp
51+
v2 = *(std::string **)(a1 + 8);
52+
if ( v2 )
53+
{
54+
func_FreeStringsRange(v2, *(std::string **)(a1 + 12));
55+
operator delete(*(void **)(a1 + 8));
56+
}
57+
*(_DWORD *)(a1 + 8) = 0;
58+
*(_DWORD *)(a1 + 12) = 0;
59+
*(_DWORD *)(a1 + 16) = 0;
60+
```
61+
62+
This is a vector of std::string.
63+
64+
### linked list
65+
66+
ctor
67+
```cpp
68+
*(_DWORD *)(a1 + 4) = a1 + 4;
69+
*(_DWORD *)(a1 + 8) = a1 + 4;
70+
```
71+
72+
dtor
73+
```cpp
74+
*(_DWORD *)(*(_DWORD *)(a1 + 4) + 4) = *(_DWORD *)(a1 + 8);
75+
**(_DWORD **)(a1 + 8) = *(_DWORD *)(a1 + 4);
76+
*(_DWORD *)(a1 + 4) = a1 + 4;
77+
*(_DWORD *)(a1 + 8) = a1 + 4;
78+
```
79+
80+
This is a linked list
81+
82+
```cpp
83+
a1->l.prev = &a1->l;
84+
a1->l.next = &a1->l;
85+
```
86+
87+
```cpp
88+
a1->l.next->prev = a1->l.prev;
89+
a1->l.prev->next = a1->l.next;
90+
a1->l.prev = &a1->l;
91+
a1->l.next = &a1->l;
92+
```
93+
94+
### std::map (binary tree)
95+
96+
```cpp
97+
v1 = sub_465480(); // this function has weird stuff and call to *new* with size we'll use later
98+
// no first field set
99+
*((_DWORD *)this + 1) = v1; // doing some stuff with the second field
100+
*(_BYTE *)(v1 + 45/*any offset*/) = 1; // setting some value to 1
101+
*(_DWORD *)(*((_DWORD *)this + 1) + 4) = *((_DWORD *)this + 1);
102+
**((_DWORD **)this + 1) = *((_DWORD *)this + 1);
103+
*(_DWORD *)(*((_DWORD *)this + 1) + 8) = *((_DWORD *)this + 1);
104+
*((_DWORD *)this + 2) = 0; // setting third field to zero
105+
```
106+
107+
This is a map based on binary tree.
108+
109+
```cpp
110+
node = create_node();
111+
this->m.root = node;
112+
node->is_leaf = 1;
113+
this->m.root->parent = this->m.root;
114+
this->m.root->left = this->m.root;
115+
this->m.root->right = this->m.root;
116+
this->m.size = 0;
117+
```
118+
119+
Where dtor will look very fancy and you'll guess it very fast
120+
121+
```cpp
122+
... // maybe an iteration over map before
123+
some_function(&this->m, &node, this->m.root->left, this->m.root);
124+
operator delete(this->m.root);
125+
this->m.root = 0;
126+
this->m.size = 0;
127+
...
128+
```
129+
130+
### std::shared_ptr
131+
132+
```cpp
133+
if ( v10 )
134+
{
135+
if ( !_InterlockedExchangeAdd(v10 + 1, 0xFFFFFFFF) )
136+
{
137+
(*(void (__thiscall **)(volatile signed __int32 *))(*v10 + 4))(v10);
138+
if ( !_InterlockedExchangeAdd(v10 + 2, 0xFFFFFFFF) )
139+
(*(void (__thiscall **)(volatile signed __int32 *))(*v10 + 8))(v10);
140+
}
141+
}
142+
```
143+
You'll see this very frequently. It is inlined dtor of shared pointer. So if `v10` is a counter block, then field before it is pointer to data associated with it.
144+
145+
```cpp
146+
if ( pi )
147+
{
148+
if ( !_InterlockedExchangeAdd(&pi->use_count_, 0xFFFFFFFF) )
149+
{
150+
pi->vtable->dispose(pi);
151+
if ( !_InterlockedExchangeAdd(&pi->weak_count_, 0xFFFFFFFF) )
152+
pi->vtable->destroy(pi);
153+
}
154+
}
155+
```

RE.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
We are looking for experienced reverse engineers to improve SCFA's engine and expand it's functions.
2+
Right now we are having goal to improve and expose some of the UI's functionality to have more control over it in Lua.
3+
Specifically user's input and what user can see in UI.
4+
5+
There are 2 main fields where we need help:
6+
7+
* Reverse engineering: revesing
8+
* structures and classes of engine and their names
9+
* functions' logic and their names
10+
* Engine recreation: with given decompiled parts of engine
11+
* recreate functions in C/C++
12+
* recreate structures and classes in C/C++
13+
* recreate generic structures and algorithms in C/C++
14+
15+
Tooling:
16+
17+
* C/C++
18+
* GCC inline x86 asm
19+
* IDA + x64dbg
20+
* a bit of Lua + its API
21+
22+
Dedicated repositories:
23+
24+
* [C++ patcher](https://github.com/FAForever/FA_Patcher)
25+
* [Python patcher](https://github.com/4z0t/SCFA-python-patcher)
26+
* [Game's executable patches](https://github.com/FAForever/FA-Binary-Patches)

main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import sys
22
import time
3-
import Patcher
3+
import patcher
44

55
if __name__ == "__main__":
66
start = time.time()
7-
Patcher.patch(*sys.argv)
7+
patcher.patch(*sys.argv)
88
end = time.time()
99
print(f"Patched in {end-start:.2f}s")
File renamed without changes.

COFFData.py renamed to patcher/COFFData.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from dataclasses import dataclass
22
from typing import Optional, Any
33
import struct
4-
from BasicBinaryParser import BasicBinaryParser
4+
from .BasicBinaryParser import BasicBinaryParser
55

66

77
@dataclass

patcher/Hook.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import re
2+
from pathlib import Path
3+
4+
ADDRESS_RE = re.compile(r"^(0[xX][0-9A-Fa-f]{6,8})\:$")
5+
FUNCTION_NAME_RE = re.compile(r"@([a-zA-Z\_][a-zA-Z0-9\_]+)")
6+
7+
8+
class Section:
9+
10+
def __init__(self, address: str, lines: list[str]) -> None:
11+
self._address: str = address
12+
self._lines: list[str] = lines
13+
14+
def lines_to_cpp(self):
15+
s = ""
16+
for line in self._lines:
17+
line = line.translate(str.maketrans({"\"": r"\"", "\\": r"\\", }))
18+
19+
if FUNCTION_NAME_RE.findall(line):
20+
line = FUNCTION_NAME_RE.subn(r'"QU(\1)"', line)[0]
21+
22+
s += f'"{line};"\n'
23+
return s
24+
25+
def to_cpp(self, index: int) -> str:
26+
if self._address is None:
27+
return self.lines_to_cpp()
28+
return f'SECTION({index:X}, {self._address})\n{self.lines_to_cpp()}'
29+
30+
31+
class Hook:
32+
def __init__(self, sections: list[Section]) -> None:
33+
self._sections: list[Section] = sections
34+
35+
def to_cpp(self):
36+
s = '#include "../asm.h"\n#include "../define.h"\n'
37+
if len(self._sections) > 0:
38+
sections_lines = (section.to_cpp(i).split("\n")
39+
for i, section in enumerate(self._sections))
40+
s += f"asm(\n{''.join((f" {line}\n" for lines in sections_lines for line in lines))});"
41+
return s
42+
43+
44+
def load_hook(file_path: Path) -> Hook:
45+
sections: list[Section] = []
46+
lines = []
47+
address = None
48+
with open(file_path) as f:
49+
for line in f.readlines():
50+
# remove comments
51+
line = line.split("//")[0]
52+
line = line.split("#")[0]
53+
line = line.strip()
54+
if not line:
55+
continue
56+
57+
if match := ADDRESS_RE.match(line):
58+
if len(lines) > 0:
59+
sections.append(Section(address, lines))
60+
lines = []
61+
address = match.group(1)
62+
continue
63+
lines.append(line)
64+
if len(lines) > 0:
65+
sections.append(Section(address, lines))
66+
return Hook(sections)

PEData.py renamed to patcher/PEData.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from dataclasses import dataclass
22
from typing import Optional, Any
33
import struct
4-
from BasicBinaryParser import BasicBinaryParser
4+
from .BasicBinaryParser import BasicBinaryParser
55

66

77
@dataclass

Patcher.py renamed to patcher/Patcher.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1-
from PEData import PEData, PESect
2-
from COFFData import COFFData, COFFSect
1+
from .PEData import PEData, PESect
2+
from .COFFData import COFFData, COFFSect
33
import os
44
from pathlib import Path
55
import re
66
from typing import Optional
77
import struct
88
import itertools
9+
from patcher import Hook
910

10-
11-
CLANG_FLAGS = " ".join(["-pipe -m32 -Os -nostdlib -nostartfiles -w -masm=intel -std=c++20 -march=core2 -c",
11+
CLANG_FLAGS = " ".join(["-pipe -m32 -Os -nostdlib -Werror -masm=intel -std=c++20 -march=core2 -c",
1212
])
1313

14-
GCC_FLAGS = " ".join(["-pipe -m32 -Os -fno-exceptions -nostdlib -nostartfiles -w -fpermissive -masm=intel -std=c++20 -march=core2 -mfpmath=both",
14+
GCC_FLAGS = " ".join(["-pipe -m32 -Os -fno-exceptions -nostdlib -nostartfiles -fpermissive -masm=intel -std=c++20 -march=core2 -mfpmath=both",
1515
])
16+
17+
GCC_FLAGS_ASM = " ".join(["-pipe -m32 -Os -fno-exceptions -nostdlib -nostartfiles -w -fpermissive -masm=intel -std=c++20 -march=core2 -mfpmath=both",
18+
])
1619
SECT_SIZE = 0x80000
1720

1821
ASM_RE = re.compile(r"(asm\(\"(0[xX][0-9a-fA-F]{1,8})\"\);)", re.IGNORECASE)
@@ -380,9 +383,19 @@ def create_defines_file(path: Path, addresses: dict[str, str]):
380383
f.write(f"#define {name} {address}\n")
381384
create_defines_file(target_path / "define.h", addresses)
382385

386+
def generate_hook_files(folder_path: Path):
387+
for file_path in list_files_at(folder_path, "**/*.hook"):
388+
hook = Hook. load_hook(folder_path/file_path)
389+
hook_path = file_path.replace(os.sep, "_") + ".cpp"
390+
print(f"Generating {hook_path}")
391+
with open(folder_path/hook_path, "w") as f:
392+
f.write(hook.to_cpp())
393+
394+
generate_hook_files(target_path/"hooks")
395+
383396
if run_system(
384397
f"""cd {build_folder_path} &
385-
{gcc_compiler_path} -c {GCC_FLAGS} ../hooks/*.cpp"""):
398+
{gcc_compiler_path} -c {GCC_FLAGS_ASM} ../hooks/*.cpp"""):
386399
raise Exception("Errors occurred during building of hooks files")
387400

388401
hooks: list[COFFData] = []
@@ -417,7 +430,7 @@ def create_defines_file(path: Path, addresses: dict[str, str]):
417430
for hook in hooks:
418431
for sect in hook.sects:
419432
pld.writelines([
420-
f" .h{hi} 0x{sect.offset:x} : SUBALIGN(1) {{\n",
433+
f" .h{hi:X} 0x{sect.offset:x} : SUBALIGN(1) {{\n",
421434
f" {hook.name}({sect.name})\n",
422435
" }\n",
423436
])
@@ -457,7 +470,7 @@ def replace_data(new_data, offset):
457470
print(f"No hooks in {hook.name}")
458471
continue
459472
for sect in hook.sects:
460-
psect = patch_pe.find_sect(f".h{hi}")
473+
psect = patch_pe.find_sect(f".h{hi:X}")
461474
size = sect.size
462475
replace_data(
463476
patch_pe.data[psect.f_offset:psect.f_offset + size], psect.v_offset)
@@ -507,3 +520,4 @@ def save_new_base_data(data: bytearray):
507520
save_new_base_data(base_file_data)
508521

509522
remove_files_at(build_folder_path, "**/*.o")
523+
remove_files_at(target_path/"hooks", "*.hook.cpp")

patcher/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .Patcher import patch

0 commit comments

Comments
 (0)