Last active
November 22, 2025 10:36
-
-
Save CypherpunkSamurai/8976137c2e70c8e1db557718ef72b89b to your computer and use it in GitHub Desktop.
ZTE F50 nr_modem_a.bin patcher
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| import argparse | |
| import sys | |
| try: | |
| from colorama import Fore, Back, Style, init | |
| except: | |
| print("colorama module not found! install colorama module!") | |
| print("type: pip3 install colorama") | |
| sys.exit(1) | |
| # Initialize colorama to auto-reset styles after each print | |
| init(autoreset=True) | |
| # --- Configuration --- | |
| # The sequence of bytes to find in the file | |
| SEARCH_BYTES = bytes([0x05, 0x24, 0x0B, 0xE0, 0x30, 0x68, 0x03]) | |
| # The byte to replace the first byte of the sequence with | |
| PATCH_BYTE = 0x01 | |
| # The resulting sequence after the patch (for display purposes) | |
| PATCHED_BYTES = bytes([PATCH_BYTE, 0x24, 0x0B, 0xE0, 0x30, 0x68, 0x03]) | |
| def print_hex_view(data, start_address, highlight_offset, highlight_len): | |
| """ | |
| Prints a section of binary data in a hex editor-like format with highlighting. | |
| Args: | |
| data (bytes): The data to print. | |
| start_address (int): The absolute memory address of the first byte in 'data'. | |
| highlight_offset (int): The offset within 'data' to start highlighting. | |
| highlight_len (int): The number of bytes to highlight. | |
| """ | |
| print(f"\n{Fore.CYAN}--- Hex View ---") | |
| print(f"{Fore.CYAN}Address Hex Data ASCII") | |
| print(f"{Fore.CYAN}---------- ------------------------------------------- ----------------") | |
| for i in range(0, len(data), 16): | |
| # The part of the data for this line | |
| chunk = data[i:i+16] | |
| # Format the address | |
| hex_addr = f"{start_address + i:08X}" | |
| # Format the hex bytes | |
| hex_parts = [] | |
| for j, byte in enumerate(chunk): | |
| # Determine if this byte should be highlighted | |
| is_highlighted = (i + j) >= highlight_offset and (i + j) < (highlight_offset + highlight_len) | |
| if is_highlighted: | |
| # Color for the bytes we are about to change | |
| hex_parts.append(f"{Fore.LIGHTYELLOW_EX}{Back.RED}{byte:02X}{Style.RESET_ALL}") | |
| else: | |
| # Default color for other bytes | |
| hex_parts.append(f"{Fore.WHITE}{byte:02X}") | |
| # Pad the hex section to align properly if the line is short | |
| hex_str = " ".join(hex_parts) | |
| hex_str = f"{hex_str:<48}" # 16 bytes * 2 chars + 15 spaces = 47, so 48 is safe | |
| # Format the ASCII representation | |
| ascii_parts = [] | |
| for j, byte in enumerate(chunk): | |
| is_highlighted = (i + j) >= highlight_offset and (i + j) < (highlight_offset + highlight_len) | |
| char = chr(byte) if 32 <= byte <= 126 else '.' | |
| if is_highlighted: | |
| ascii_parts.append(f"{Fore.LIGHTYELLOW_EX}{Back.RED}{char}{Style.RESET_ALL}") | |
| else: | |
| ascii_parts.append(f"{Fore.LIGHTBLACK_EX}{char}") | |
| ascii_str = "".join(ascii_parts) | |
| print(f"{hex_addr} {hex_str} {ascii_str}") | |
| print(f"{Fore.CYAN}---------------------------------------------------------\n") | |
| def main(): | |
| """Main function to run the patcher.""" | |
| parser = argparse.ArgumentParser( | |
| description='Colorful hex patcher for modem binary files to enable IMEI writing.', | |
| formatter_class=argparse.RawTextHelpFormatter | |
| ) | |
| parser.add_argument('infile', help='Input binary file (e.g., nr_modem_a.bin)') | |
| parser.add_argument( | |
| 'outfile', | |
| nargs='?', | |
| default='nr_modem_a.imei_patch.bin', | |
| help='Output binary file (default: nr_modem_a.imei_patch.bin)' | |
| ) | |
| args = parser.parse_args() | |
| try: | |
| with open(args.infile, 'rb') as f: | |
| # Use bytearray to allow in-place modification | |
| content = bytearray(f.read()) | |
| except FileNotFoundError: | |
| print(f"{Fore.RED}Error: Input file not found at '{args.infile}'") | |
| sys.exit(1) | |
| print(f"{Fore.GREEN}[*] Searching for byte sequence: {' '.join(f'{b:02X}' for b in SEARCH_BYTES)}") | |
| offset = content.find(SEARCH_BYTES) | |
| if offset == -1: | |
| print(f"{Fore.RED}Error: Byte sequence not found in '{args.infile}'") | |
| sys.exit(1) | |
| print(f"{Fore.GREEN}[+] Sequence found at offset: {offset:#06x} ({offset} decimal)") | |
| # Prepare context for the hex view (32 bytes before and after) | |
| context_start = max(0, offset - 32) | |
| context_end = min(len(content), offset + len(SEARCH_BYTES) + 32) | |
| context_data = content[context_start:context_end] | |
| # The highlight offset is relative to the start of the context_data | |
| highlight_rel_offset = offset - context_start | |
| print(f"\n{Fore.YELLOW}Current bytes to be patched (highlighted in red):") | |
| print_hex_view(context_data, context_start, highlight_rel_offset, len(SEARCH_BYTES)) | |
| print(f"{Fore.CYAN}Planned change:") | |
| print(f" From: {Fore.RED}{' '.join(f'{b:02X}' for b in SEARCH_BYTES)}") | |
| print(f" To: {Fore.GREEN}{' '.join(f'{b:02X}' for b in PATCHED_BYTES)}") | |
| # --- User Confirmation --- | |
| while True: | |
| choice = input(f"{Fore.YELLOW}Do you want to apply this patch? (y/n): ").lower().strip() | |
| if choice == 'y': | |
| break | |
| elif choice == 'n': | |
| print(f"{Fore.LIGHTBLACK_EX}Patch cancelled by user. Exiting.") | |
| sys.exit(0) | |
| else: | |
| print(f"{Fore.RED}Invalid input. Please enter 'y' or 'n'.") | |
| # --- Apply Patch --- | |
| print(f"\n{Fore.GREEN}[*] Applying patch...") | |
| content[offset] = PATCH_BYTE | |
| try: | |
| with open(args.outfile, 'wb') as f: | |
| f.write(content) | |
| print(f"{Fore.GREEN}[+] Successfully patched!") | |
| print(f"{Fore.GREEN}[+] Patched file saved to: {args.outfile}") | |
| except IOError as e: | |
| print(f"{Fore.RED}Error writing to output file '{args.outfile}': {e}") | |
| sys.exit(1) | |
| # --- Final Instructions --- | |
| print("\n" + "="*60) | |
| print(f"{Fore.CYAN}{Style.BRIGHT}PATCHING COMPLETE!") | |
| print("="*60) | |
| print(f""" | |
| You can now flash the patched file ({args.outfile}) to your device. | |
| After flashing, you can open engineer mode using the dialer code: | |
| {Fore.YELLOW}*#*#83781#*#*{Style.RESET_ALL} | |
| Then, navigate to the AT Command mode and type one of the following: | |
| {Fore.GREEN}AT+SPIMEI=0,"NEW_IMEI"{Style.RESET_ALL} | |
| (to write a new IMEI) | |
| {Fore.LIGHTBLACK_EX}AT+SPIMEI=0,"OLD_IMEI"{Style.RESET_ALL} | |
| (to reset the IMEI to the original value) | |
| Replace {Fore.YELLOW}"NEW_IMEI"{Style.RESET_ALL} with your desired 15-digit IMEI. | |
| """) | |
| print("="*60) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment