|
| 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