-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpatcher.py
executable file
·158 lines (129 loc) · 5.26 KB
/
patcher.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/usr/bin/env python
"""
A script for patching binary files based on configurations provided in JSON format.
Supports direct offset patching, method name-based patching, and wildcard pattern patching.
"""
import json
import os
import re
import sys
# ANSI escape codes for colors
RESET = "\033[0m"
RED = "\033[31m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
CYAN = "\033[36m"
DEFAULT_CONFIG_PATH = "config.json" # Default configuration file name
def load_config(config_path):
"""Load the JSON configuration file."""
if not os.path.exists(config_path):
print(f"{RED}Error: Configuration file '{
config_path}' not found.{RESET}")
sys.exit(1)
with open(config_path, "r", encoding="utf-8") as file:
return json.load(file)
def replace_hex_at_offset(data, offset, repl):
"""Replace hex bytes at a specified offset in the binary data."""
repl_bytes = bytes.fromhex(repl.replace(" ", ""))
data[offset: offset + len(repl_bytes)] = repl_bytes
def wildcard_pattern_scan(data, pattern):
"""Search for a byte pattern with wildcards in the given data."""
pattern_bytes = pattern.split(" ")
pattern_length = len(pattern_bytes)
# Convert pattern into a list of bytes (integers) or None for wildcards
pattern_bytes = [int(b, 16) if b != "??" else None for b in pattern_bytes]
for i, _ in enumerate(data[: -pattern_length + 1]):
if all(p_b is None or p_b == data[i + j]
for j, p_b in enumerate(pattern_bytes)):
return i
return -1
def patch_code(input_filename, output_filename, patch_list, dump_path):
"""Patch the input binary file based on the configuration."""
if not os.path.exists(input_filename):
print(f"{RED}Error: Input file '{input_filename}' not found.{RESET}")
return
try:
with open(input_filename, "rb") as file:
data = bytearray(file.read())
except OSError as e:
print(f"{RED}Error reading input file: {e}{RESET}")
return
# Check if any patch uses a wildcard, and print a single note if so
if any("wildcard" in patch for patch in patch_list):
print(
f"{CYAN}Note: Scanning with wildcards; this may take longer on larger files...{
RESET}"
)
for patch in patch_list:
# Determine whether to use method_name, offset, or wildcard
if "method_name" in patch:
offset = find_offset_by_method_name(
patch["method_name"], dump_path)
if offset is None:
print(
f"{YELLOW}Warning: Method '{
patch['method_name']}' not found. Skipping patch.{RESET}")
continue
elif "offset" in patch:
offset = int(patch["offset"], 16) # Convert hex string to integer
elif "wildcard" in patch:
offset = wildcard_pattern_scan(data, patch["wildcard"])
if offset == -1:
print(
f"{YELLOW}Warning: No match found for wildcard pattern '{
patch['wildcard']}'. Skipping patch.{RESET}")
continue
else:
print(
f"{YELLOW}Warning: No valid patch definition found. Skipping.{RESET}")
continue
if offset >= len(data):
print(
f"{RED}Error: Offset 0x{
offset:X} is out of range for the input file.{RESET}")
continue
print(f"{GREEN}Patching at Offset: 0x{offset:X}{RESET}")
replace_hex_at_offset(data, offset, patch["hex_code"])
try:
with open(output_filename, "wb") as file:
file.write(data)
except OSError as e:
print(f"{RED}Error writing output file: {e}{RESET}")
return
print(f"{CYAN}Patching completed. Output written to '{
output_filename}'.{RESET}")
def find_offset_by_method_name(method_name, dump_path):
"""Find the offset of a method name in the dump file."""
if not os.path.exists(dump_path):
print(f"{RED}Error: '{dump_path}' file not found.{RESET}")
return None
try:
with open(dump_path, "r", encoding="utf-8") as file:
lines = file.readlines()
except OSError as e:
print(f"{RED}Error reading dump file: {e}{RESET}")
return None
for i, line in enumerate(lines):
if method_name in line:
# Look at the previous line for the offset
if i > 0:
match = re.search(r"Offset: 0x([0-9A-Fa-f]+)", lines[i - 1])
if match:
offset = int(match.group(1), 16)
print(
f"{GREEN}Found {method_name} at Offset: 0x{
offset:X}{RESET}")
return offset
print(
f"{YELLOW}Warning: No offset found for {method_name}.{RESET}")
return None
# Argument parsing
fileconfig = DEFAULT_CONFIG_PATH if len(sys.argv) < 2 else sys.argv[1]
config = load_config(fileconfig)
# Extract necessary details
InputFile = config["Patcher"]["input_file"]
DumpPath = config["Patcher"]["dump_file"]
OutputFile = config["Patcher"]["output_file"]
PatchList = config["Patcher"]["patches"]
# Apply patches to binary
patch_code(InputFile, OutputFile, PatchList, DumpPath)