Skip to content

Commit 15076b2

Browse files
Create DexRepair.py
1 parent 8650a63 commit 15076b2

File tree

1 file changed

+188
-0
lines changed

1 file changed

+188
-0
lines changed

DexRepair.py

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
#!/usr/bin/env python3
2+
3+
# This file is part of RevEngi - @RevEngiBot (Telegram Bot)
4+
# Copyright (C) 2023-present RevEngiSquad - Organization
5+
#
6+
# This program is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU General Public License as published by
8+
# the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
11+
# This program is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU General Public License for more details.
15+
16+
# You should have received a copy of the GNU General Public License
17+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
19+
import os
20+
import argparse
21+
import struct
22+
import zlib
23+
import hashlib
24+
25+
# ref: https://source.android.com/docs/core/runtime/dex-format#embedded-in-header_item
26+
DEX_MAGIC_035 = b"dex\n035\0"
27+
DEX_MAGIC_037 = b"dex\n037\0"
28+
DEX_MAGIC_038 = b"dex\n038\0"
29+
DEX_MAGIC_039 = b"dex\n039\0"
30+
DEX_MAGIC_VERSIONS = [DEX_MAGIC_035, DEX_MAGIC_037, DEX_MAGIC_038, DEX_MAGIC_039]
31+
32+
33+
class DexRepairError(Exception):
34+
pass
35+
36+
37+
def is_valid_dex_magic(dex_file):
38+
"""
39+
This function checks if the DEX magic number in the given bytearray is valid.
40+
41+
Parameters:
42+
dex_file (bytearray): The bytearray containing the dex data.
43+
44+
Returns:
45+
bool: True if the magic number is valid, False otherwise.
46+
47+
Note:
48+
The DEX magic number is the first 8 bytes of the dex data. The valid magic numbers are defined in the DEX_MAGIC_VERSIONS list.
49+
This function returns True if the magic number is in the list of valid magic numbers, and False otherwise.
50+
"""
51+
magic = dex_file[:8]
52+
return magic in DEX_MAGIC_VERSIONS
53+
54+
55+
def repair_dex_magic(dex_data: bytearray):
56+
"""
57+
This function checks if the DEX magic number in the given bytearray is valid. If it is not valid, it replaces the magic number with a valid one (DEX_MAGIC_035).
58+
59+
Parameters:
60+
dex_data (bytearray): The bytearray containing the dex data.
61+
62+
Returns:
63+
bytearray: The bytearray containing the updated dex data.
64+
65+
Note:
66+
The DEX magic number is the first 8 bytes of the dex data. The valid magic number for this function is DEX_MAGIC_035.
67+
If the magic number is not valid, it is updated with the valid magic number.
68+
"""
69+
70+
if not is_valid_dex_magic(dex_data):
71+
dex_data[:8] = DEX_MAGIC_035
72+
return dex_data
73+
74+
75+
def update_dex_hashes(dex_data: bytearray):
76+
"""
77+
This function updates the checksum and signature in the DEX header of a given bytearray containing dex data.
78+
79+
Parameters:
80+
dex_data (bytearray): The bytearray containing the dex data.
81+
82+
Returns:
83+
bytearray: The bytearray containing the updated dex data.
84+
85+
Note:
86+
The checksum is calculated using the zlib.adler32 function, starting from the 13th byte of the dex data.
87+
The signature is calculated using the hashlib.sha1 function, starting from the 33rd byte of the dex data.
88+
The updated checksum is then packed into a 4-byte little-endian integer and written back into the dex data, starting from the 9th byte.
89+
The updated signature is then written back into the dex data, starting from the 13th byte.
90+
"""
91+
checksum = zlib.adler32(dex_data[12:])
92+
signature = hashlib.sha1(dex_data[32:]).digest()
93+
print(f"Checksum: {checksum:#x}")
94+
print(f"Signature: {signature.hex()}")
95+
96+
dex_data[8:12] = struct.pack("<I", checksum)
97+
dex_data[12:32] = signature
98+
return dex_data
99+
100+
101+
def repair_dex(
102+
dex_path: str,
103+
output_dex_path: str = None,
104+
):
105+
"""
106+
This function repairs dex files in the given path. If the path is a directory, it will repair all dex files within that directory. If the path is a file, it will repair that specific dex file.
107+
108+
Parameters:
109+
dex_path (str): The path to the dex file or directory containing dex files.
110+
output_dex_path (str, optional): The output path for the repaired dex files. If not provided, the repaired dex files will be overwritten in the original location.
111+
112+
Returns:
113+
None
114+
115+
Raises:
116+
DexRepairError: If the provided dex_path is not a valid directory or file.
117+
"""
118+
if os.path.isdir(dex_path):
119+
for filename in os.listdir(dex_path):
120+
if filename.endswith(".dex"):
121+
file_path = os.path.join(dex_path, filename)
122+
output_file_path = (
123+
os.path.join(output_dex_path, filename) if output_dex_path else None
124+
)
125+
if not os.path.isdir(output_dex_path):
126+
raise DexRepairError(f"{output_dex_path} not a directory!")
127+
print(f"Repairing {file_path}...")
128+
repair_dex_file(file_path, output_file_path)
129+
elif os.path.isfile(dex_path):
130+
repair_dex_file(dex_path, output_dex_path)
131+
else:
132+
raise DexRepairError(f"Path not found: {dex_path}")
133+
134+
135+
def repair_dex_file(dex_file_path: str, output_dex_path: str = None):
136+
"""
137+
This function repairs a single dex file by fixing the DEX magic number and updating the checksum and signature in the DEX header. The repaired dex file is then written to the output path if it is provided, or to the original path if it is not.
138+
139+
Parameters:
140+
dex_file_path (str): The path to the dex file to be repaired.
141+
output_dex_path (str, optional): The output path for the repaired dex file. If not provided, the repaired dex file will be overwritten in the original location.
142+
143+
Returns:
144+
None
145+
146+
Raises:
147+
DexRepairError: If the provided dex_file_path is not a valid file.
148+
"""
149+
try:
150+
with open(dex_file_path, "rb") as f:
151+
dex_data = bytearray(f.read())
152+
except FileNotFoundError:
153+
raise DexRepairError(f"DEX file not found: {dex_file_path}")
154+
155+
dex_data = repair_dex_magic(dex_data)
156+
dex_data = update_dex_hashes(dex_data)
157+
158+
if output_dex_path:
159+
with open(output_dex_path, "wb") as f:
160+
f.write(dex_data)
161+
else:
162+
with open(dex_file_path, "wb") as f:
163+
f.write(dex_data)
164+
165+
166+
def main():
167+
epilog = "A command-line tool for repairing DEX files. It fixes the DEX magic number and updates the checksum and signature in the DEX header."
168+
parser = argparse.ArgumentParser(description="DEX Repair Tool", epilog=epilog)
169+
parser.add_argument("dex_file", help="Path to the DEX file")
170+
parser.add_argument("-o", "--output", help="Path to the output DEX file (optional)")
171+
172+
args = parser.parse_args()
173+
174+
if args.output:
175+
output = args.output
176+
else:
177+
output = args.dex_file.replace(".dex", "_repaired.dex")
178+
179+
try:
180+
repair_dex(args.dex_file, output)
181+
print("DEX repair completed successfully.")
182+
183+
except DexRepairError as e:
184+
print(f"Error during DEX repair: {e}")
185+
186+
187+
if __name__ == "__main__":
188+
main()

0 commit comments

Comments
 (0)