├── .github ├── FUNDING.yml └── workflows │ └── python-app.yml ├── .gitignore ├── .gitkeep ├── .gitmodules ├── API_README.md ├── Drivers ├── 50-android.rules ├── 51-edl.rules ├── 69-libmtp.rules ├── Windows │ ├── Install_Windows.bat │ ├── libusb-1.0.26-binaries.7z │ └── zadig-2.8.exe └── blacklist-qcserial.conf ├── Example ├── Library │ ├── __init__.py │ └── tcpclient.py ├── api_example.py ├── benchmark.xml └── tcpclient.py ├── LICENSE ├── MANIFEST.in ├── README.md ├── autoinstall.sh ├── boottodownload ├── edl ├── edl.bat ├── edlclient ├── Config │ ├── __init__.py │ ├── nvitems.xml │ ├── qualcomm_config.py │ └── usb_ids.py ├── Library │ ├── Connection │ │ ├── __init__.py │ │ ├── devicehandler.py │ │ ├── seriallib.py │ │ ├── usblib.py │ │ └── usbscsi.py │ ├── Modules │ │ ├── __init__.py │ │ ├── generic.py │ │ ├── init.py │ │ ├── nothing.py │ │ ├── oneplus.py │ │ ├── oneplus_param.py │ │ └── xiaomi.py │ ├── TestFiles │ │ └── gpt_sm8180x.bin │ ├── __init__.py │ ├── api.py │ ├── asmtools.py │ ├── cryptutils.py │ ├── firehose.py │ ├── firehose_client.py │ ├── gpt.py │ ├── hdlc.py │ ├── loader_db.py │ ├── memparse.py │ ├── nand_config.py │ ├── pt.py │ ├── pt64.py │ ├── sahara.py │ ├── sahara_defs.py │ ├── sparse.py │ ├── streaming.py │ ├── streaming_client.py │ ├── streaming_defs.py │ ├── utils.py │ └── xmlparser.py ├── Tools │ ├── Config │ ├── Library │ ├── __init__.py │ ├── beagle_to_loader │ ├── boottodwnload │ ├── enableadb │ ├── fhloaderparse │ ├── qc_diag │ ├── qc_diag.py │ ├── sierrakeygen │ ├── sierrakeygen.py │ └── txt_to_loader.py ├── Windows │ ├── libusb-1.0.dll │ └── libusb32-1.0.dll ├── __init__.py └── edl.py ├── enableadb ├── fastpwn ├── fastpwn.exe ├── fhloaderparse ├── install-linux-edl-drivers.sh ├── install_edl_win10_win11.ps1 ├── pyproject.toml ├── qc_diag ├── requirements.txt ├── setup.cfg ├── setup.py ├── sierrakeygen └── sierrakeygen_README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://paypal.me/viperbjk'] 2 | -------------------------------------------------------------------------------- /.github/workflows/python-app.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python application 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | build: 17 | 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Set up Python 3.10 23 | uses: actions/setup-python@v3 24 | with: 25 | python-version: "3.10" 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install flake8 pytest 30 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 31 | - name: Lint with flake8 32 | run: | 33 | # stop the build if there are Python syntax errors or undefined names 34 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 35 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 36 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | .idea 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | .pytest_cache/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | db.sqlite3 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # Environments 86 | .env 87 | .venv 88 | env/ 89 | venv/ 90 | ENV/ 91 | env.bak/ 92 | venv.bak/ 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | .spyproject 97 | 98 | # Rope project settings 99 | .ropeproject 100 | 101 | # mkdocs documentation 102 | /site 103 | 104 | # mypy 105 | .mypy_cache/ 106 | 107 | edl_config.json -------------------------------------------------------------------------------- /.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkerler/edl/bc1534496c83571b199b30b64774abc495744418/.gitkeep -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Loaders"] 2 | path = Loaders 3 | url = https://github.com/bkerler/Loaders 4 | branch = main 5 | -------------------------------------------------------------------------------- /API_README.md: -------------------------------------------------------------------------------- 1 | # API usage guide 2 | This API was developed to provide a native Python integration with all EDL features. 3 | 4 | ## API setup 5 | Before anything, you should import it and instance the API with: 6 | 7 | ```python 8 | from edlclient.Library.api import * 9 | 10 | e = edl_api() 11 | ``` 12 | 13 | After that, you should set your desired EDL options (those who starts with `--`), e.g. `--debugmode`. For this, you should use the `set_arg()` method, like this: 14 | 15 | ```python 16 | e.set_arg("--debugmode", True) 17 | ``` 18 | 19 | Be aware that, by default, the `set_arg()` method returns the full arguments dictionary, but, if the passed option doesn't exist, it'll return `Invalid key!`. This should be useful to avoid future errors. 20 | 21 | Also, you can use the `reset_arg()` method to set back any option to its default value. This method's return is the same as the `set_arg()`, because it's actually just a wrapper to `set_arg(key, None, True)`. 22 | 23 | That said, now you should initialize the API with the `init()` method. However, note that: 24 | 25 | 1. The API initialization should be done after you set the `--` options 26 | 27 | 2. To change any `--` option **after** the API initialization, you should reinitialize the API with the `reinit()` method (which is equivalent to `deinit()` + `init()`) 28 | 29 | 3. You can use the `deinit()` method to "free" the API. This will happen automatically when the API instance (i.e. the Python object) gets destroyed or the program exits 30 | 31 | 4. Every `init()`/`reinit()`/`deinit()` calls will return the API `status` attribute, which stores a code that can be useful to check if any errors occurred. The stored value will be 1 for error or 0 for success 32 | 33 | And, finally, the API is ready to be used! 34 | 35 | ## EDL commands 36 | In summary, **all EDL commands are API methods** and they follow this same structure: `command([arg1[, arg2[, arg3]]])`. To a better understanding, we're going to take the `peek` command as an example: we know that `peek` requires ``, `` and `` as its arguments, respectively. This way, considering the `` as `0x100000`, `` as `80` and `` as `output.bin`, we should have this API call: 37 | 38 | ```python 39 | e.peek(0x100000, 80, "output.bin") 40 | ``` 41 | 42 | And that's it for all the EDL commands! Simple, isn't it? 43 | 44 | > For the full command list and their arguments, refer to EDL's documentation (i.e. `edl --help` or `README.md`) 45 | 46 | ## Basic usage example 47 | This example covers the basic (and probably most common) API usage, so check it out: 48 | 49 | ```python 50 | from edlclient.Library.api import * 51 | 52 | # Step 1 53 | e = edl_api() 54 | e.set_arg("--debugmode", True) 55 | if (e.init() == 1): 56 | exit(1) 57 | 58 | # Step 2 59 | e.peek(0x100000, 80, "peek1.bin") 60 | 61 | # Step 3 62 | e.reset_arg("--debugmode") 63 | if (e.reinit() == 1): 64 | exit(1) 65 | 66 | # Step 4 67 | e.peek(0x100080, 80, "peek2.bin") 68 | 69 | # Step 5 70 | e.reset() 71 | ``` 72 | 73 | Steps explanation: 74 | 75 | 1. The API is instanced and initialized with the `--debugmode` option enabled 76 | 77 | 2. A `peek` command is executed 78 | 79 | 3. The `--debugmode` option is disabled and, in order to take effect, the API is reinitialized 80 | 81 | 4. Another `peek` command is executed 82 | 83 | 5. The Android device is restarted with the `reset` command 84 | 85 | For a more contextualized and robust example, check the `Examples/api_example.py` 86 | -------------------------------------------------------------------------------- /Drivers/51-edl.rules: -------------------------------------------------------------------------------- 1 | 2 | # Qualcomm EDL 3 | SUBSYSTEMS=="usb", ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9008", TAG+="uaccess" 4 | 5 | # Sony EDL 6 | SUBSYSTEMS=="usb", ATTRS{idVendor}=="0fce", ATTRS{idProduct}=="9dde", TAG+="uaccess" 7 | SUBSYSTEMS=="usb", ATTRS{idVendor}=="0fce", ATTRS{idProduct}=="ade5", TAG+="uaccess" 8 | SUBSYSTEMS=="usb", ATTRS{idVendor}=="0fce", ATTRS{idProduct}=="aded", TAG+="uaccess" 9 | 10 | # Qualcomm Memory Debug 11 | SUBSYSTEMS=="usb", ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9006", TAG+="uaccess" 12 | 13 | # Qualcomm Memory Debug 14 | SUBSYSTEMS=="usb", ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="900e", TAG+="uaccess" 15 | 16 | # LG Memory Debug 17 | SUBSYSTEMS=="usb", ATTRS{idVendor}=="1004", ATTRS{idProduct}=="61a1", TAG+="uaccess" 18 | 19 | # Sierra Wireless 20 | SUBSYSTEMS=="usb", ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9071", TAG+="uaccess" 21 | 22 | # ZTE 23 | SUBSYSTEMS=="usb", ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0076", TAG+="uaccess" 24 | -------------------------------------------------------------------------------- /Drivers/69-libmtp.rules: -------------------------------------------------------------------------------- 1 | # UDEV-style hotplug map for libmtp 2 | # Put this file in /etc/udev/rules.d 3 | 4 | ACTION!="add", ACTION!="bind", GOTO="libmtp_rules_end" 5 | ENV{MAJOR}!="?*", GOTO="libmtp_rules_end" 6 | SUBSYSTEM=="usb", GOTO="libmtp_usb_rules" 7 | GOTO="libmtp_rules_end" 8 | 9 | LABEL="libmtp_usb_rules" 10 | 11 | # If we have a hwdb entry for this device, act immediately! 12 | ENV{ID_MTP_DEVICE}=="1", SYMLINK+="libmtp-%k", GOTO="libmtp_rules_end" 13 | 14 | # Fall back to probing. 15 | # Some sensitive devices we surely don't wanna probe 16 | # Color instruments 17 | ATTR{idVendor}=="0670", GOTO="libmtp_rules_end" 18 | ATTR{idVendor}=="0765", GOTO="libmtp_rules_end" 19 | ATTR{idVendor}=="085c", GOTO="libmtp_rules_end" 20 | ATTR{idVendor}=="0971", GOTO="libmtp_rules_end" 21 | # Canon scanners that look like MTP devices (PID 0x22nn) 22 | ATTR{idVendor}=="04a9", ATTR{idProduct}=="22*", GOTO="libmtp_rules_end" 23 | ATTR{idVendor}=="05c6", ATTR{idProduct}=="9008", GOTO="libmtp_rules_end" 24 | # Canon digital camera (EOS 3D) that looks like MTP device (PID 0x3113) 25 | ATTR{idVendor}=="04a9", ATTR{idProduct}=="3113", GOTO="libmtp_rules_end" 26 | # Sensitive Atheros devices that look like MTP devices 27 | ATTR{idVendor}=="0cf3", GOTO="libmtp_rules_end" 28 | # Sensitive Atmel JTAG programmers 29 | ATTR{idVendor}=="03eb", GOTO="libmtp_rules_end" 30 | # Sensitive Philips device 31 | ATTR{idVendor}=="0471", ATTR{idProduct}=="083f", GOTO="libmtp_rules_end" 32 | # DUALi NFC readers 33 | ATTR{idVendor}=="1db2", ATTR{idProduct}=="060*", GOTO="libmtp_rules_end" 34 | # HP printers 35 | ATTR{idVendor}=="03f0", ENV{ID_USB_INTERFACES}=="*:0701??:*|*:ffcc00:", GOTO="libmtp_rules_end" 36 | # Printers 37 | ENV{ID_USB_INTERFACES}=="*:0701??:*", GOTO="libmtp_rules_end" 38 | 39 | # Autoprobe vendor-specific, communication and PTP devices 40 | ENV{ID_MTP_DEVICE}!="1", ENV{MTP_NO_PROBE}!="1", ENV{COLOR_MEASUREMENT_DEVICE}!="1", ENV{ID_GPHOTO}!="1", ENV{libsane_matched}!="yes", ATTR{bDeviceClass}=="00|02|06|ef|ff", PROGRAM="/usr/lib/udev/mtp-probe /sys$env{DEVPATH} $attr{busnum} $attr{devnum}", RESULT=="1", SYMLINK+="libmtp-%k", ENV{ID_MTP_DEVICE}="1", ENV{ID_MEDIA_PLAYER}="1" 41 | 42 | LABEL="libmtp_rules_end" 43 | -------------------------------------------------------------------------------- /Drivers/Windows/Install_Windows.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cd Oneplus_9008_Driver 3 | dpinst64.exe /SW /U 4 | -------------------------------------------------------------------------------- /Drivers/Windows/libusb-1.0.26-binaries.7z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkerler/edl/bc1534496c83571b199b30b64774abc495744418/Drivers/Windows/libusb-1.0.26-binaries.7z -------------------------------------------------------------------------------- /Drivers/Windows/zadig-2.8.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkerler/edl/bc1534496c83571b199b30b64774abc495744418/Drivers/Windows/zadig-2.8.exe -------------------------------------------------------------------------------- /Drivers/blacklist-qcserial.conf: -------------------------------------------------------------------------------- 1 | blacklist qcserial 2 | blacklist cdc_acm 3 | -------------------------------------------------------------------------------- /Example/Library/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkerler/edl/bc1534496c83571b199b30b64774abc495744418/Example/Library/__init__.py -------------------------------------------------------------------------------- /Example/Library/tcpclient.py: -------------------------------------------------------------------------------- 1 | import socket 2 | from binascii import hexlify 3 | 4 | 5 | class tcpclient: 6 | def __init__(self, port): 7 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 8 | server_address = ("localhost", port) 9 | print("connecting to %s port %s" % server_address) 10 | self.sock.connect(server_address) 11 | 12 | def sendcommands(self, commands): 13 | try: 14 | for command in commands: 15 | self.sock.sendall(bytes(command, 'utf-8')) 16 | data = "" 17 | while "" not in data and "" not in data: 18 | tmp = self.sock.recv(4096) 19 | if tmp == b"": 20 | continue 21 | try: 22 | data += tmp.decode('utf-8') 23 | except: 24 | data += hexlify(tmp) 25 | print(data) 26 | finally: 27 | print("closing socket") 28 | self.sock.close() 29 | -------------------------------------------------------------------------------- /Example/api_example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from edlclient.Library.api import * 5 | import os 6 | 7 | LOADER = os.path.join(os.path.dirname(os.path.abspath(__file__)), "path/to/programmer.mbn") 8 | PEEK_OUTPUT = "peek_output.bin" 9 | 10 | def dump(): 11 | return os.system(f"cat {PEEK_OUTPUT} | xxd") 12 | 13 | def main(): 14 | e = edl_api() 15 | e.set_arg("--loader", LOADER) 16 | e.set_arg("--debugmode", True) 17 | 18 | if (e.init() == 1): 19 | return 1 20 | 21 | e.peek(0x100000, 80, PEEK_OUTPUT) 22 | dump() 23 | 24 | e.reset_arg("--debugmode") 25 | if (e.reinit() == 1): 26 | return 1 27 | 28 | e.printgpt() 29 | 30 | e.pbl("pbl.bin") 31 | 32 | e.reset() 33 | return e.deinit() 34 | 35 | if (__name__ == "__main__"): 36 | main() 37 | -------------------------------------------------------------------------------- /Example/benchmark.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Example/tcpclient.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from edl.Library.tcpclient import tcpclient 3 | 4 | 5 | class client: 6 | def __init__(self): 7 | self.commands = [] 8 | 9 | def send(self): 10 | self.tcp = tcpclient(1340) 11 | self.tcp.sendcommands(self.commands) 12 | 13 | def read(self, src): 14 | self.commands.append(f"peekqword:{hex(src)}") 15 | 16 | def write(self, dest, value): 17 | self.commands.append(f"pokeqword:{hex(dest)},{hex(value)}") 18 | 19 | def memcpy(self, dest, src, size): 20 | self.commands.append(f"memcpy:{hex(dest)},{hex(src)},{hex(size)}") 21 | 22 | 23 | def main(): 24 | exp = client() 25 | exp.commands = [ 26 | "send:nop", 27 | "r:boot,boot.img", 28 | "peek:0x00100000,0x8,qfp.bin" 29 | #"pokehex:0x1402C2CC,1f2003d5", 30 | #"peek:0x14084840,0xC00,uart.bin" 31 | ] 32 | exp.send() 33 | 34 | 35 | if __name__ == "__main__": 36 | main() 37 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | include fastpwn 4 | recursive-include Loaders * 5 | include edlclient/Windows/*.dll 6 | include edlclient/Config/nvitems.xml 7 | recursive-include edlclient/Library/TestFiles * 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Qualcomm Sahara / Firehose Attack Client / Diag Tools 2 | (c) B. Kerler 2018-2025 3 | Licensed under GPLv3 license. 4 | 5 | # Be aware that if you use anything from this repository in any (including) compiled form, you need to opensource your code as well ! 6 | # Violating against the GPLv3 license will enforce me to stop developing these opensource tools. 7 | 8 | ## Why 9 | 10 | - Because we'd like to flexible dump smartphones 11 | - Because attacking firehose is kewl 12 | - Because memory dumping helps to find issues :) 13 | 14 | ## QC Sahara V3 additional information for newer QC devices 15 | - For newer qc phones, loader autodetection doesn't work anymore as the sahara loader doesn't offer a way to read the pkhash anymore 16 | - Thus, for Sahara V3, you need to give a valid loader via --loader option ! 17 | 18 | ### Use LiveDVD (everything ready to go, based on Ubuntu): 19 | User: user, Password:user (based on Ubuntu 22.04 LTS) 20 | 21 | [Live DVD V4](https://www.androidfilehost.com/?fid=15664248565197184488) 22 | 23 | [Live DVD V4 Mirror](https://drive.google.com/file/d/10OEw1d-Ul_96MuT3WxQ3iAHoPC4NhM_X/view?usp=sharing) 24 | 25 | ## Installation 26 | 27 | #### Grab files and install 28 | ``` 29 | git clone https://github.com/bkerler/edl 30 | cd edl 31 | git submodule update --init --recursive 32 | pip3 install -r requirements.txt 33 | ``` 34 | 35 | ### Linux (Debian/Ubuntu/Mint/etc): 36 | ```bash 37 | # Debian/Ubuntu/Mint/etc 38 | sudo apt install adb fastboot python3-dev python3-pip liblzma-dev git 39 | sudo apt purge modemmanager 40 | # Fedora/CentOS/etc 41 | sudo dnf install adb fastboot python3-devel python3-pip xz-devel git 42 | # Arch/Manjaro/etc 43 | sudo pacman -S android-tools python python-pip git xz 44 | sudo pacman -R modemmanager 45 | # Gentoo (run as root!) 46 | emerge -aq dev-util/android-tools dev-vcs/git dev-python/pip 47 | 48 | # For systemd distros 49 | sudo systemctl stop ModemManager 50 | sudo systemctl disable ModemManager 51 | # For OpenRC distros (run as root!) 52 | rc-update del modemmanager default boot sysinit 53 | rc-service modemmanager stop 54 | 55 | git clone https://github.com/bkerler/edl.git # do NOT use --recurse-submodules 56 | cd edl 57 | git submodule update --init --recursive 58 | 59 | # Autoinstall (run as root!) 60 | ./autoinstall.sh 61 | 62 | # Manual install 63 | chmod +x ./install-linux-edl-drivers.sh 64 | bash ./install-linux-edl-drivers.sh 65 | python3 setup.py build 66 | sudo python3 setup.py install 67 | ``` 68 | 69 | If you have SELinux enabled, you may need to set it to permissive mode temporarily to prevent permission issues. SELinux is commonly used by RedHat-like distros (for example, RHEL, Fedora, and CentOS). You can set it to permissive run-time until next boot with `sudo setenforce 0`. 70 | 71 | ### macOS: 72 | ```bash 73 | brew install libusb git 74 | 75 | git clone https://github.com/bkerler/edl.git 76 | cd edl 77 | git submodule update --init --recursive 78 | python3 setup.py build 79 | sudo python3 setup.py install 80 | ``` 81 | 82 | ### Windows: 83 | 84 | #### Method 1 - Automatic with PowerShell (Windows 10 and later) 85 | 86 | 1. Open PowerShell (Not CMD). To do that, right-click on the Windows start menu and select PowerShell or Terminal. 87 | 2. Copy and paste the code below and press enter 88 | ``` 89 | curl.exe -O https://raw.githubusercontent.com/bkerler/edl/master/install_edl_win10_win11.ps1; .\install_edl_win10_win11.ps1 90 | ``` 91 | 92 | #### Method 2 - Manual 93 | #### Install python + git 94 | - Install python 3.9 and git 95 | - If you install python from microsoft store, "python setup.py install" will fail, but that step isn't required. 96 | - WIN+R ```cmd``` 97 | 98 | #### Get latest UsbDk 64-Bit 99 | - Install normal QC 9008 Serial Port driver (or use default Windows COM Port one, make sure no exclamation is seen) 100 | - Get usbdk installer (.msi) from [here](https://github.com/daynix/UsbDk/releases/) and install it 101 | - Test on device connect using "UsbDkController -n" if you see a device with pid 0x9008 102 | - Works fine under Windows 10 and 11 :D 103 | 104 | #### Using serial port instead of usb 105 | With Port autodetection 106 | ```bash 107 | edl --serial 108 | ``` 109 | 110 | or Port name 111 | ```bash 112 | edl --portname \\.\COM1 113 | ``` 114 | 115 | ------------------------------------------------------------------------------------------------------------------------------------ 116 | ## Get Loaders 117 | You should get these automatically if you do a ``` git submodule update --init --recursive ``` 118 | or from [here](https://github.com/bkerler/Loaders) 119 | 120 | ## Convert own EDL loaders for automatic usage 121 | 122 | - Make a subdirectory "newstuff", copy your edl loaders to this subdirectory 123 | - ```fhloaderparse newstuff Loaders``` 124 | 125 | - or sniff existing edl tools using Totalphase Beagle 480, set filter to ```filter({'inputs': False, 'usb3': False, 'chirps': False, 'dev': 26, 'usb2resets': False, 'sofs': False, 'ep': 1})```, export to binary file as "sniffeddata.bin" and then use ```beagle_to_loader sniffeddata.bin``` 126 | 127 | 128 | ## Install EDL loaders 129 | 130 | - ```mkdir examples``` 131 | - Copy all your loaders into the examples directory 132 | - ```fhloaderparse examples Loaders``` -> will autodetect and rename loader structure and copy them to the "Loaders" directory 133 | - Or rename Loaders manually as "msmid_pkhash[8 bytes].bin" and put them into the Loaders directory 134 | 135 | ------------------------------------------------------------------------------------------------------------------------------------ 136 | 137 | ## Run EDL (examples) 138 | 139 | Your device needs to have a usb pid of 0x9008 in order to make the edl tool work. 140 | If your device is semi bricked and entered the usb pid 0x900E, there are several options 141 | to get back the 0x9008 mode : 142 | 143 | 1. Use a edl cable (Short D+ with GND) and force reboot the phone (either vol up + power pressing for more than 20 seconds or disconnect battery), works with emmc + ufs flash (this will only work if XBL/SBL isn't broken) 144 | 145 | 2. If emmc flash is used, remove battery, short DAT0 with gnd, connect battery, then remove short. 146 | 147 | 3. If a ufs flash is used, things are very much more complicated. You will need to open the ufs die and short the clk line on boot, some boards have special test points for that. 148 | 149 | 4. Some devices have boot config resistors, if you find the right ones you may enforce booting to sdcard instead of flash. 150 | 151 | 152 | ### Generic 153 | 154 | - ```edl -h``` -> to see help with all options 155 | - ```edl server --memory=ufs --tcpport=1340``` -> Run TCP/IP server on port 1340, see tcpclient.py for an example client 156 | - ```edl xml run.xml``` -> To send a xml file run.xml via firehose 157 | - ```edl reset``` -> To reboot the phone 158 | - ```edl rawxml ``` -> To send own xml string, example : 159 | ```edl rawxml "``` 160 | - ```edl [anycommand] --debugmode``` -> enables Verbose. Do that only when REALLY needed as it will print out everything happening! 161 | 162 | ### For EMMC Flash 163 | 164 | - ```edl printgpt``` -> to print gpt on device with emmc 165 | - ```edl rf flash.bin``` -> to dump whole flash for device with emmc 166 | - ```edl rl dumps --skip=userdata --genxml``` -> to dump all partitions to directory dumps for device with emmc and skipping userdata partition, write rawprogram0.xml 167 | - ```edl rs 0 15 data.bin``` -> to dump 15 sectors from starting sector 0 to file data.bin for device with emmc 168 | - ```edl rs 0 15 data.bin --skipresponse``` -> to dump 15 sectors from starting sector 0 to file data.bin for device with emmc, ignores missing ACK from phones 169 | - ```edl r boot_a boot.img``` -> to dump the partition "boot_a" to the filename boot.img for device with emmc 170 | - ```edl r boot_a,boot_b boot_a.img,boot_b.img``` -> to dump multiple partitions to multiple filenames 171 | - ```edl footer footer.bin``` -> to dump the crypto footer for Androids with emmc flash 172 | - ```edl w boot_a boot.img``` -> to write boot.img to the "boot" partition on lun 0 on the device with emmc flash 173 | - ```edl w gpt gpt.img``` -> to write gpt partition table from gpt.img to the first sector on the device with emmc flash 174 | - ```edl wl dumps``` -> to write all files from "dumps" folder to according partitions to flash 175 | - ```edl wf dump.bin``` -> to write the rawimage dump.bin to flash 176 | - ```edl e misc``` -> to erase the partition misc on emmc flash 177 | - ```edl gpt . --genxml``` -> dump gpt_main0.bin/gpt_backup0.bin and write rawprogram0.xml to current directory (".") 178 | 179 | 180 | ### For UFS Flash 181 | 182 | - ```edl printgpt --memory=ufs --lun=0``` -> to print gpt on lun 0 183 | - ```edl printgpt --memory=ufs``` -> to print gpt of all lun 184 | - ```edl rf lun0.bin --memory=ufs --lun=0``` -> to dump whole lun 0 185 | - ```edl rf flash.bin --memory=ufs``` -> to dump all luns as lun0_flash.bin, lun1_flash.bin, ... 186 | - ```edl rl dumps --memory=ufs --lun=0 --skip=userdata,vendor_a``` -> to dump all partitions from lun0 to directory dumps for device with ufs and skip userdata and vendor_a partition 187 | - ```edl rl dumps --memory=ufs --genxml``` -> to dump all partitions from all lun to directory dumps and write rawprogram[lun].xml 188 | - ```edl rs 0 15 data.bin --memory=ufs --lun=0``` -> to dump 15 sectors from starting sector 0 from lun 0 to file data.bin 189 | - ```edl r boot_a boot.img --memory=ufs --lun=4``` -> to dump the partition "boot_a" from lun 4 to the filename boot.img 190 | - ```edl r boot_a boot.img --memory=ufs``` -> to dump the partition "boot_a" to the filename boot.img using lun autodetection 191 | - ```edl r boot_a,boot_b boot_a.img,boot_b.img --memory=ufs``` -> to dump multiple partitions to multiple filenames 192 | - ```edl footer footer.bin --memory=ufs``` -> to dump the crypto footer 193 | - ```edl w boot boot.img --memory=ufs --lun=4``` -> to write boot.img to the "boot" partition on lun 4 on the device with ufs flash 194 | - ```edl w gpt gpt.img --memory=ufs --lun=4``` -> to write gpt partition table from gpt.img to the lun 4 on the device with ufs flash 195 | - ```edl wl dumps --memory=ufs --lun=0``` -> to write all files from "dumps" folder to according partitions to flash lun 0 196 | - ```edl wl dumps --memory=ufs``` -> to write all files from "dumps" folder to according partitions to flash and try to autodetect lun 197 | - ```edl wf dump.bin --memory=ufs --lun=0``` -> to write the rawimage dump.bin to flash lun 0 198 | - ```edl e misc --memory=ufs --lun=0``` -> to erase the partition misc on lun 0 199 | - ```edl gpt . --genxml --memory=ufs``` -> dump gpt_main[lun].bin/gpt_backup[lun].bin and write rawprogram[lun].xml to current directory (".") 200 | 201 | ### QFIL emulation (credits to LyuOnLine): 202 | 203 | - For flashing full image: 204 | ``` 205 | edl qfil rawprogram0.xml patch0.xml image_dir 206 | ``` 207 | ------------------------------------------------------------------------------------------------------------------------------------ 208 | 209 | ### For devices with peek/poke command 210 | 211 | - ```edl peek 0x200000 0x10 mem.bin``` -> To dump 0x10 bytes from offset 0x200000 to file mem.bin from memory 212 | - ```edl peekhex 0x200000 0x10``` -> To dump 0x10 bytes from offset 0x200000 as hex string from memory 213 | - ```edl peekqword 0x200000``` -> To display a qword (8-bytes) at offset 0x200000 from memory 214 | - ```edl pokeqword 0x200000 0x400000``` -> To write the q-word value 0x400000 to offset 0x200000 in memory 215 | - ```edl poke 0x200000 mem.bin``` -> To write the binary file mem.bin to offset 0x200000 in memory 216 | - ```edl secureboot``` -> To display secureboot fuses (only on EL3 loaders) 217 | - ```edl pbl pbl.bin``` -> To dump pbl (only on EL3 loaders) 218 | - ```edl qfp qfp.bin``` -> To dump qfprom fuses (only on EL3 loaders) 219 | 220 | ------------------------------------------------------------------------------------------------------------------------------------ 221 | 222 | ### For generic unlocking 223 | - ```edl modules oemunlock enable``` -> Unlocks OEM if partition "config" exists, fastboot oem unlock is still needed afterwards 224 | 225 | #### Dump memory (0x900E mode) 226 | - ```edl memorydump``` 227 | - 228 | ------------------------------------------------------------------------------------------------------------------------------------ 229 | ### Streaming mode (credits to forth32) 230 | 231 | #### Enter streaming mode 232 | 233 | ##### Sierra Wireless Modem 234 | - Send AT!BOOTHOLD and AT!QPSTDLOAD to modem port or use ```modem/boottodwnload.py``` script 235 | - Send AT!ENTERCND="A710" and then AT!EROPTION=0 for memory dump 236 | - ```edl --vid 1199 --pid 9070 --loader=loaders/NPRG9x35p.bin printgpt``` -> To show the partition table 237 | 238 | ##### Netgear MR1100 239 | - run ```boottodownload```, device will enter download mode (0x900E pid) 240 | - ```edl printgpt --loader=Loaders/qualcomm/patched/mdm9x5x/NPRG9x55p.bin```, device will reboot to 0x9008 241 | - now use edl regulary such as ```edl printgpt``` (do not use loader option) 242 | 243 | ##### ZTE MF920V, Quectel, Telit, etc.. Modem 244 | - run ```enableadb```, or send to at port "AT+ZCDRUN=E", or send via ```qc_diag -sahara``` 245 | - ```adb reboot edl``` 246 | - ```edl printgpt``` -> To show the partition table 247 | 248 | 249 | ## Run Diag port tools (examples) 250 | 251 | For Oneplus 6T, enter *#801#* on dialpad, set Engineer Mode and Serial to on and try : 252 | 253 | - ```qc_diag -vid 0x05c6 -pid 0x676c -interface 0 -info``` 254 | 255 | ### Usage 256 | 257 | - ```qc_diag -vid 0x1234 -pid 0x5678 -interface 0 -info``` -> Send cmd "00" and return info 258 | - ```qc_diag -vid 0x1234 -pid 0x5678 -interface 0 -spc 303030303030``` -> Send spc "303030303030" 259 | - ```qc_diag -vid 0x1234 -pid 0x5678 -interface 0 -cmd 00``` -> Send cmd "00" (hexstring) 260 | - ```qc_diag -vid 0x1234 -pid 0x5678 -interface 0 -nvread 0x55``` -> Display nvitem 0x55 261 | - ```qc_diag -vid 0x1234 -pid 0x5678 -interface 0 -nvbackup backup.json``` -> Backup all nvitems to a json structured file 262 | - ```qc_diag -vid 0x1234 -pid 0x5678 -interface 0 -efsread efs.bin``` -> Dump the EFS Modem partition to file efs.bin 263 | - ```qc_diag -vid 0x1234 -pid 0x5678 -interface 0 -efslistdir /``` -> Display / directory listing of EFS 264 | 265 | 266 | ## Issues 267 | 268 | - Secure loader with SDM660 on Xiaomi not yet supported (EDL authentification) 269 | - VIP Programming not supported (Contributions are welcome !) 270 | - EFS directory write and file read has to be added (Contributions are welcome !) 271 | 272 | 273 | ## Tested with 274 | 275 | - Oneplus 3T/5/6T/7T/8/8t/9/Nord CE/N10/N100 (Read-Only), BQ X, BQ X5, BQ X2, Gigaset ME Pure, ZTE MF210, ZTE MF920V, Sierra Wireless EM7455, Netgear MR1100-10EUS, Netgear MR5100 276 | - SIMCOM SIM8905E 277 | 278 | Published under GPLv3 license 279 | Additional license limitations: No use in commercial products without prior permit. 280 | 281 | Enjoy ! 282 | -------------------------------------------------------------------------------- /autoinstall.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | PATH_SCRIPT=$(dirname "${0}") 4 | 5 | [ "$(id -u)" != 0 ] && { printf "\nYou must run this script as root!\n" && exit 1; } 6 | 7 | if [ "$(uname -s)" = "Linux" ]; then 8 | if ! "${PATH_SCRIPT}/install-linux-edl-drivers.sh"; then 9 | printf "\nFailed to install the needed drivers!\n" && exit 1 10 | fi 11 | fi 12 | 13 | if ! pip3 install -r "${PATH_SCRIPT}/requirements.txt" --break-system-packages; then 14 | printf "\nFailed to install the dependencies!\n" && exit 1 15 | fi 16 | 17 | # The CFLAGS below is needed if your GCC version is >= 14 18 | if ! CFLAGS="-Wno-int-conversion" pip3 install -U "${PATH_SCRIPT}" --break-system-packages; then 19 | printf "\nFailed to install this program!\n" && exit 1 20 | fi 21 | 22 | printf "\nInstallation complete! Now rebuild your initramfs and reboot\n" 23 | -------------------------------------------------------------------------------- /boottodownload: -------------------------------------------------------------------------------- 1 | edlclient/Tools/boottodwnload -------------------------------------------------------------------------------- /edl.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | title EDLClient 3 | python "%~dp0edl" %* 4 | -------------------------------------------------------------------------------- /edlclient/Config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkerler/edl/bc1534496c83571b199b30b64774abc495744418/edlclient/Config/__init__.py -------------------------------------------------------------------------------- /edlclient/Config/usb_ids.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # (c) B.Kerler 2018-2024 under GPLv3 license 4 | # If you use my code, make sure you refer to my name 5 | # 6 | # !!!!! If you use this code in commercial products, your product is automatically 7 | # GPLv3 and has to be open sourced under GPLv3 as well. !!!!! 8 | 9 | default_ids = [ 10 | [0x05c6, 0x9008, -1], 11 | [0x0fce, 0x9dde, -1], 12 | [0x0fce, 0xade5, -1], 13 | [0x0fce, 0xaded, -1], 14 | [0x05c6, 0x900e, -1], 15 | [0x05c6, 0x9025, -1], 16 | [0x1199, 0x9062, -1], 17 | [0x1199, 0x9070, -1], 18 | [0x1199, 0x9090, -1], 19 | [0x0846, 0x68e0, -1], 20 | [0x19d2, 0x0076, -1] 21 | ] 22 | 23 | default_diag_vid_pid = [ 24 | [0x2c7c, 0x0125, -1], # Quectel EC25 25 | [0x1199, 0x9071, -1], # Sierra Wireless 26 | [0x1199, 0x9091, -1], # Sierra Wireless 27 | [0x0846, 0x68e2, 2], # Netgear 28 | [0x05C6, 0x9008, -1], # QC EDL 29 | [0x0fce, 0x9dde, -1], # SONY EDL 30 | [0x0fce, 0xade5, -1], # SONY EDL 31 | [0x0fce, 0xaded, -1], # SONY EDL 32 | [0x05C6, 0x676C, 0], # QC Handset 33 | [0x05c6, 0x901d, 0], # QC Android "setprop sys.usb.config diag,adb" 34 | [0x19d2, 0x0016, -1], # ZTE Diag 35 | [0x19d2, 0x0076, -1], # ZTE Download 36 | [0x19d2, 0x0500, -1], # ZTE Android 37 | [0x19d2, 0x1404, 2], # ZTE ADB Modem 38 | [0x12d1, 0x1506, -1], 39 | [0x413c, 0x81d7, 5], # Telit LN940/T77W968 40 | [0x1bc7, 0x1040, 0], # Telit LM960A18 USBCFG 1 QMI 41 | [0x1bc7, 0x1041, 0], # Telit LM960A18 USBCFG 2 MBIM 42 | [0x1bc7, 0x1201, 0], # Telit LE910C4-NF 43 | [0x05c6, 0x9091, 0], 44 | [0x05c6, 0x9092, 0] 45 | ] 46 | -------------------------------------------------------------------------------- /edlclient/Library/Connection/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkerler/edl/bc1534496c83571b199b30b64774abc495744418/edlclient/Library/Connection/__init__.py -------------------------------------------------------------------------------- /edlclient/Library/Connection/devicehandler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # (c) B.Kerler 2018-2024 under GPLv3 license 4 | # If you use my code, make sure you refer to my name 5 | # 6 | # !!!!! If you use this code in commercial products, your product is automatically 7 | # GPLv3 and has to be open sourced under GPLv3 as well. !!!!! 8 | import inspect 9 | import traceback 10 | from binascii import hexlify 11 | 12 | try: 13 | from edlclient.Library.utils import * 14 | except: 15 | from Library.utils import * 16 | 17 | 18 | class DeviceClass(metaclass=LogBase): 19 | 20 | def __init__(self, loglevel=logging.INFO, portconfig=None, devclass=-1): 21 | self.connected = False 22 | self.timeout = 1000 23 | self.maxsize = 512 24 | self.vid = None 25 | self.pid = None 26 | self.stopbits = None 27 | self.databits = None 28 | self.parity = None 29 | self.baudrate = None 30 | self.configuration = None 31 | self.device = None 32 | self.devclass = -1 33 | self.loglevel = loglevel 34 | self.xmlread = True 35 | self.portconfig = portconfig 36 | self.__logger = self.__logger 37 | self.info = self.__logger.info 38 | self.error = self.__logger.error 39 | self.warning = self.__logger.warning 40 | self.debug = self.__logger.debug 41 | self.__logger.setLevel(loglevel) 42 | if loglevel == logging.DEBUG: 43 | logfilename = os.path.join("logs", "log.txt") 44 | fh = logging.FileHandler(logfilename, encoding='utf-8') 45 | self.__logger.addHandler(fh) 46 | 47 | def connect(self, options): 48 | raise NotImplementedError() 49 | 50 | def close(self, reset=False): 51 | raise NotImplementedError() 52 | 53 | def flush(self): 54 | raise NotImplementedError() 55 | 56 | def detectdevices(self): 57 | raise NotImplementedError() 58 | 59 | def getInterfaceCount(self): 60 | raise NotImplementedError() 61 | 62 | def setLineCoding(self, baudrate=None, parity=0, databits=8, stopbits=1): 63 | raise NotImplementedError() 64 | 65 | def setbreak(self): 66 | raise NotImplementedError() 67 | 68 | def setcontrollinestate(self, RTS=None, DTR=None, isFTDI=False): 69 | raise NotImplementedError() 70 | 71 | def write(self, command, pktsize=None): 72 | raise NotImplementedError() 73 | 74 | def usbwrite(self, data, pktsize=None): 75 | raise NotImplementedError() 76 | 77 | def usbread(self, resplen=None, timeout=0): 78 | raise NotImplementedError() 79 | 80 | def ctrl_transfer(self, bmRequestType, bRequest, wValue, wIndex, data_or_wLength): 81 | raise NotImplementedError() 82 | 83 | def usbreadwrite(self, data, resplen): 84 | raise NotImplementedError() 85 | 86 | def read(self, length=None, timeout=-1): 87 | if timeout == -1: 88 | timeout = self.timeout 89 | if length is None: 90 | length = self.maxsize 91 | return self.usbread(length, timeout) 92 | 93 | def rdword(self, count=1, little=False): 94 | rev = "<" if little else ">" 95 | value = self.usbread(4 * count) 96 | data = unpack(rev + "I" * count, value) 97 | if count == 1: 98 | return data[0] 99 | return data 100 | 101 | def rword(self, count=1, little=False): 102 | rev = "<" if little else ">" 103 | data = [] 104 | for _ in range(count): 105 | v = self.usbread(2) 106 | if len(v) == 0: 107 | return data 108 | data.append(unpack(rev + "H", v)[0]) 109 | if count == 1: 110 | return data[0] 111 | return data 112 | 113 | def rbyte(self, count=1): 114 | return self.usbread(count) 115 | 116 | def verify_data(self, data, pre="RX:"): 117 | if self.__logger.level == logging.DEBUG: 118 | frame = inspect.currentframe() 119 | stack_trace = traceback.format_stack(frame) 120 | td = [] 121 | for trace in stack_trace: 122 | if "verify_data" not in trace and "Port" not in trace: 123 | td.append(trace) 124 | self.debug(td[:-1]) 125 | 126 | if isinstance(data, bytes) or isinstance(data, bytearray): 127 | if data[:5] == b"= self.__logger.level: 142 | self.debug(pre + hexlify(data).decode('utf-8')) 143 | else: 144 | if logging.DEBUG >= self.__logger.level: 145 | self.debug(pre + hexlify(data).decode('utf-8')) 146 | return data 147 | -------------------------------------------------------------------------------- /edlclient/Library/Connection/seriallib.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # (c) B.Kerler 2018-2024 under GPLv3 license 4 | # If you use my code, make sure you refer to my name 5 | # 6 | # !!!!! If you use this code in commercial products, your product is automatically 7 | # GPLv3 and has to be open sourced under GPLv3 as well. !!!!! 8 | import sys 9 | 10 | if not sys.platform.startswith('win32'): 11 | import termios 12 | 13 | 14 | def _reset_input_buffer(): 15 | return 16 | 17 | 18 | def _reset_input_buffer_org(self): 19 | if not sys.platform.startswith('win32'): 20 | return termios.tcflush(self.fd, termios.TCIFLUSH) 21 | 22 | 23 | import serial 24 | import serial.tools.list_ports 25 | import inspect 26 | 27 | try: 28 | from edlclient.Library.utils import * 29 | from edlclient.Library.Connection.devicehandler import DeviceClass 30 | except: 31 | from Library.utils import * 32 | from Library.Connection.devicehandler import DeviceClass 33 | 34 | 35 | class serial_class(DeviceClass): 36 | 37 | def __init__(self, loglevel=logging.INFO, portconfig=None, devclass=-1): 38 | super().__init__(loglevel, portconfig, devclass) 39 | self.is_serial = True 40 | 41 | def connect(self, EP_IN=-1, EP_OUT=-1, portname: str = ""): 42 | if self.connected: 43 | self.close() 44 | self.connected = False 45 | if portname == "": 46 | devices = self.detectdevices() 47 | if len(devices) > 0: 48 | portname = devices[0] 49 | if portname != "": 50 | self.device = serial.Serial(baudrate=115200, bytesize=serial.EIGHTBITS, 51 | parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, 52 | timeout=50, 53 | xonxoff=False, dsrdtr=True, rtscts=True) 54 | self.device._reset_input_buffer = _reset_input_buffer 55 | self.device.setPort(port=portname) 56 | self.device.open() 57 | self.device._reset_input_buffer = _reset_input_buffer_org 58 | self.connected = self.device.is_open 59 | if self.connected: 60 | return True 61 | return False 62 | 63 | def close(self, reset=False): 64 | if self.connected: 65 | self.device.close() 66 | del self.device 67 | self.connected = False 68 | 69 | def detectdevices(self): 70 | ids = [] 71 | for port in serial.tools.list_ports.comports(): 72 | for usbid in self.portconfig: 73 | if port.pid == usbid[1] and port.vid == usbid[0]: 74 | portid = port.location[-1:] 75 | print(f"Detected {hex(port.vid)}:{hex(port.pid)} device at: " + port.device) 76 | ids.append(port.device) 77 | return sorted(ids) 78 | 79 | def setLineCoding(self, baudrate=None, parity=0, databits=8, stopbits=1): 80 | self.device.baudrate = baudrate 81 | self.device.parity = parity 82 | self.device.stopbbits = stopbits 83 | self.device.bytesize = databits 84 | self.debug("Linecoding set") 85 | 86 | def setbreak(self): 87 | self.device.send_break() 88 | self.debug("Break set") 89 | 90 | def setcontrollinestate(self, RTS=None, DTR=None, isFTDI=False): 91 | if RTS == 1: 92 | self.device.setRTS(RTS) 93 | if DTR == 1: 94 | self.device.setDTR(DTR) 95 | self.debug("Linecoding set") 96 | 97 | def write(self, command, pktsize=None): 98 | if pktsize is None: 99 | pktsize = 512 100 | if isinstance(command, str): 101 | command = bytes(command, 'utf-8') 102 | pos = 0 103 | if command == b'': 104 | try: 105 | self.device.write(b'') 106 | except Exception as err: 107 | error = str(err.strerror) 108 | if "timeout" in error: 109 | # time.sleep(0.01) 110 | try: 111 | self.device.write(b'') 112 | except Exception as err: 113 | self.debug(str(err)) 114 | return False 115 | return True 116 | else: 117 | i = 0 118 | while pos < len(command): 119 | try: 120 | ctr = self.device.write(command[pos:pos + pktsize]) 121 | if ctr <= 0: 122 | self.info(ctr) 123 | pos += pktsize 124 | except Exception as err: 125 | self.debug(str(err)) 126 | # print("Error while writing") 127 | # time.sleep(0.01) 128 | i += 1 129 | if i == 3: 130 | return False 131 | pass 132 | self.verify_data(bytearray(command), "TX:") 133 | self.device.flushOutput() 134 | timeout = 0 135 | time.sleep(0.005) 136 | """ 137 | while self.device.in_waiting == 0: 138 | time.sleep(0.005) 139 | timeout+=1 140 | if timeout==10: 141 | break 142 | """ 143 | return True 144 | 145 | def read(self, length=None, timeout=-1): 146 | if timeout == -1: 147 | timeout = self.timeout 148 | if length is None: 149 | length = self.device.in_waiting 150 | if length == 0: 151 | return b"" 152 | if self.xmlread: 153 | if length > self.device.in_waiting: 154 | length = self.device.in_waiting 155 | return self.usbread(length, timeout) 156 | 157 | def flush(self): 158 | return self.device.flush() 159 | 160 | def usbread(self, resplen=None, timeout=0): 161 | if resplen is None: 162 | resplen = self.device.in_waiting 163 | if resplen <= 0: 164 | self.info("Warning !") 165 | res = bytearray() 166 | loglevel = self.loglevel 167 | self.device.timeout = timeout 168 | epr = self.device.read 169 | extend = res.extend 170 | if self.xmlread: 171 | info = self.device.read(6) 172 | bytestoread = resplen - len(info) 173 | extend(info) 174 | if b"": 176 | extend(epr(1)) 177 | return res 178 | bytestoread = resplen 179 | while len(res) < bytestoread: 180 | try: 181 | val = epr(bytestoread) 182 | if len(val) == 0: 183 | break 184 | extend(val) 185 | except Exception as e: 186 | error = str(e) 187 | if "timed out" in error: 188 | if timeout is None: 189 | return b"" 190 | self.debug("Timed out") 191 | if timeout == 10: 192 | return b"" 193 | timeout += 1 194 | pass 195 | elif "Overflow" in error: 196 | self.error("USB Overflow") 197 | return b"" 198 | else: 199 | self.info(repr(e)) 200 | return b"" 201 | 202 | if loglevel == logging.DEBUG: 203 | self.debug(inspect.currentframe().f_back.f_code.co_name + ":" + hex(resplen)) 204 | if self.loglevel == logging.DEBUG: 205 | self.verify_data(res[:resplen], "RX:") 206 | return res[:resplen] 207 | 208 | def usbwrite(self, data, pktsize=None): 209 | if pktsize is None: 210 | pktsize = len(data) 211 | res = self.write(data, pktsize) 212 | self.device.flush() 213 | return res 214 | 215 | def usbreadwrite(self, data, resplen): 216 | self.usbwrite(data) # size 217 | self.device.flush() 218 | res = self.usbread(resplen) 219 | return res 220 | -------------------------------------------------------------------------------- /edlclient/Library/Connection/usbscsi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # (c) B.Kerler 2018-2024 under GPLv3 license 4 | # If you use my code, make sure you refer to my name 5 | # 6 | # !!!!! If you use this code in commercial products, your product is automatically 7 | # GPLv3 and has to be open sourced under GPLv3 as well. !!!!! 8 | import argparse 9 | from edlclient.Library.Connection.usblib import * 10 | 11 | 12 | def main(): 13 | info = 'MassStorageBackdoor (c) B.Kerler 2019.' 14 | parser = argparse.ArgumentParser(description=info) 15 | print("\n" + info + "\n\n") 16 | parser.add_argument('-vid', metavar="", help='[Option] Specify vid, default=0x2e04)', default="0x2e04") 17 | parser.add_argument('-pid', metavar="", help='[Option] Specify pid, default=0xc025)', default="0xc025") 18 | parser.add_argument('-interface', metavar="", help='[Option] Specify interface number)', default="") 19 | parser.add_argument('-nokia', help='[Option] Enable Nokia adb backdoor', action='store_true') 20 | parser.add_argument('-alcatel', help='[Option] Enable alcatel adb backdoor', action='store_true') 21 | parser.add_argument('-zte', help='[Option] Enable zte adb backdoor', action='store_true') 22 | parser.add_argument('-htc', help='[Option] Enable htc adb backdoor', action='store_true') 23 | parser.add_argument('-htcums', help='[Option] Enable htc ums adb backdoor', action='store_true') 24 | args = parser.parse_args() 25 | vid = None 26 | pid = None 27 | if args.vid != "": 28 | vid = int(args.vid, 16) 29 | if args.pid != "": 30 | pid = int(args.pid, 16) 31 | if args.interface != "": 32 | interface = int(args.interface, 16) 33 | else: 34 | interface = -1 35 | 36 | usbscsi = Scsi(vid, pid, interface) 37 | if usbscsi.connect(): 38 | if args.nokia: 39 | usbscsi.send_fih_adbenable() 40 | usbscsi.send_fih_root() 41 | elif args.zte: 42 | usbscsi.send_zte_adbenable() 43 | elif args.htc: 44 | usbscsi.send_htc_adbenable() 45 | elif args.htcums: 46 | usbscsi.send_htc_ums_adbenable() 47 | elif args.alcatel: 48 | usbscsi.send_alcatel_adbenable() 49 | else: 50 | print("A command is required. Use -h to see options.") 51 | exit(0) 52 | usbscsi.close() 53 | 54 | 55 | if __name__ == '__main__': 56 | main() 57 | -------------------------------------------------------------------------------- /edlclient/Library/Modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkerler/edl/bc1534496c83571b199b30b64774abc495744418/edlclient/Library/Modules/__init__.py -------------------------------------------------------------------------------- /edlclient/Library/Modules/generic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # (c) B.Kerler 2018-2024 under GPLv3 license 4 | # If you use my code, make sure you refer to my name 5 | # 6 | # !!!!! If you use this code in commercial products, your product is automatically 7 | # GPLv3 and has to be open sourced under GPLv3 as well. !!!!! 8 | 9 | import logging 10 | from edlclient.Library.utils import LogBase 11 | 12 | 13 | class generic(metaclass=LogBase): 14 | def __init__(self, fh, serial, args, loglevel): 15 | self.fh = fh 16 | self.serial = serial 17 | self.args = args 18 | self.__logger.setLevel(loglevel) 19 | self.error = self.__logger.error 20 | if loglevel == logging.DEBUG: 21 | logfilename = "log.txt" 22 | fh = logging.FileHandler(logfilename) 23 | self.__logger.addHandler(fh) 24 | 25 | def oem_unlock(self, enable): 26 | res = self.fh.detect_partition(self.args, "config") 27 | if res[0]: 28 | lun = res[1] 29 | rpartition = res[2] 30 | if rpartition.sectors <= (0x8000 // self.fh.cfg.SECTOR_SIZE_IN_BYTES): 31 | offsettopatch = 0x7FFF 32 | sector, offset = self.fh.calc_offset(rpartition.sector, offsettopatch) 33 | else: 34 | offsettopatch = 0x7FFFF 35 | sector, offset = self.fh.calc_offset(rpartition.sector, offsettopatch) 36 | if enable: 37 | value = 0x1 38 | else: 39 | value = 0x0 40 | size_in_bytes = 1 41 | if self.fh.cmd_patch(lun, sector, offset, value, size_in_bytes, True): 42 | print(f"Patched sector {str(rpartition.sector)}, offset {str(offset)} with value {value}, " + 43 | f"size in bytes {size_in_bytes}.") 44 | else: 45 | print(f"Error on writing sector {str(rpartition.sector)}, offset {str(offset)} with value {value}, " + 46 | f"size in bytes {size_in_bytes}.") 47 | else: 48 | """ 49 | #define DEVICE_MAGIC "ANDROID-BOOT!" 50 | #define DEVICE_MAGIC_SIZE 13 51 | #define MAX_PANEL_ID_LEN 64 52 | #define MAX_VERSION_LEN 64 53 | #if VBOOT_MOTA 54 | struct device_info 55 | { 56 | unsigned char magic[DEVICE_MAGIC_SIZE]; 57 | bool is_unlocked; 58 | bool is_tampered; 59 | bool is_verified; 60 | bool charger_screen_enabled; 61 | char display_panel[MAX_PANEL_ID_LEN]; 62 | char bootloader_version[MAX_VERSION_LEN]; 63 | char radio_version[MAX_VERSION_LEN]; 64 | bool is_unlock_critical; 65 | }; 66 | #else 67 | struct device_info 68 | { 69 | unsigned char magic[DEVICE_MAGIC_SIZE]; 70 | bool is_unlocked; #0x10 71 | bool is_tampered; #0x14 72 | bool charger_screen_enabled; #0x18 73 | char display_panel[MAX_PANEL_ID_LEN]; 74 | char bootloader_version[MAX_VERSION_LEN]; 75 | char radio_version[MAX_VERSION_LEN]; 76 | bool verity_mode; // 1 = enforcing, 0 = logging 77 | bool is_unlock_critical; 78 | }; 79 | #endif 80 | """ 81 | res = self.fh.detect_partition(self.args, "devinfo") 82 | if res[0]: 83 | lun = res[1] 84 | rpartition = res[2] 85 | offsettopatch1 = 0x10 # is_unlocked 86 | offsettopatch2 = 0x18 # is_critical_unlocked 87 | offsettopatch3 = 0x7FFE10 # zte 88 | offsettopatch4 = 0x7FFE18 # zte 89 | sector1, offset1 = self.fh.calc_offset(rpartition.sector, offsettopatch1) 90 | sector2, offset2 = self.fh.calc_offset(rpartition.sector, offsettopatch2) 91 | sector3, offset3 = self.fh.calc_offset(rpartition.sector, offsettopatch3) 92 | sector4, offset4 = self.fh.calc_offset(rpartition.sector, offsettopatch4) 93 | value = 0x1 94 | size_in_bytes = 1 95 | if self.fh.cmd_patch(lun, sector1, offset1, 0x1, size_in_bytes, True): 96 | if self.fh.cmd_patch(lun, sector2, offset2, 0x1, size_in_bytes, True): 97 | print( 98 | f"Patched sector {str(rpartition.sector)}, offset {str(offset1)} with value {value}, " + 99 | f"size in bytes {size_in_bytes}.") 100 | data = self.fh.cmd_read_buffer(lun, rpartition.sector, rpartition.sectors) 101 | if (len(data) > 0x7FFE20) and data[0x7FFE00:0x7FFE10] == b"ANDROID-BOOT!\x00\x00\x00": 102 | if self.fh.cmd_patch(lun, sector3, offset3, value, size_in_bytes, True): 103 | if self.fh.cmd_patch(lun, sector4, offset4, value, size_in_bytes, True): 104 | print( 105 | f"Patched sector {str(rpartition.sector)}, offset {str(offset1)} with " + 106 | f"value {value}, size in bytes {size_in_bytes}.") 107 | return True 108 | print( 109 | f"Error on writing sector {str(rpartition.sector)}, offset {str(offset1)} with value {value}, " + 110 | f"size in bytes {size_in_bytes}.") 111 | return False 112 | else: 113 | fpartitions = res[1] 114 | self.error(f"Error: Couldn't detect partition: \"devinfo\"\nAvailable partitions:") 115 | for lun in fpartitions: 116 | for rpartition in fpartitions[lun]: 117 | if self.args["--memory"].lower() == "emmc": 118 | self.error("\t" + rpartition) 119 | else: 120 | self.error(lun + ":\t" + rpartition) 121 | -------------------------------------------------------------------------------- /edlclient/Library/Modules/init.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # (c) B.Kerler 2018-2024 under GPLv3 license 4 | # If you use my code, make sure you refer to my name 5 | # 6 | # !!!!! If you use this code in commercial products, your product is automatically 7 | # GPLv3 and has to be open sourced under GPLv3 as well. !!!!! 8 | 9 | import logging 10 | from functools import cached_property 11 | 12 | from edlclient.Library.utils import LogBase 13 | 14 | try: 15 | from edlclient.Library.Modules.generic import generic 16 | except ImportError as e: 17 | print(e) 18 | generic = None 19 | pass 20 | 21 | try: 22 | from edlclient.Library.Modules.oneplus import oneplus 23 | except ImportError as e: 24 | print(e) 25 | oneplus = None 26 | pass 27 | 28 | try: 29 | from edlclient.Library.Modules.xiaomi import xiaomi 30 | except ImportError as e: 31 | print(e) 32 | xiaomi = None 33 | pass 34 | 35 | try: 36 | from edlclient.Library.Modules.nothing import nothing 37 | except ImportError as e: 38 | nothing = None 39 | pass 40 | 41 | 42 | class modules(metaclass=LogBase): 43 | def __init__(self, fh, serial: int, supported_functions, loglevel, devicemodel: str, args): 44 | self.fh = fh 45 | self.args = args 46 | self.serial = serial 47 | self.error = self.__logger.error 48 | self.info = self.__logger.info 49 | self.supported_functions = supported_functions 50 | self.__logger.setLevel(loglevel) 51 | if loglevel == logging.DEBUG: 52 | logfilename = "log.txt" 53 | fh = logging.FileHandler(logfilename) 54 | self.__logger.addHandler(fh) 55 | self.options = {} 56 | self.devicemodel = devicemodel 57 | 58 | @cached_property 59 | def generic(self): 60 | try: 61 | return generic(fh=self.fh, serial=self.serial, args=self.args, loglevel=self.__logger.loglevel) 62 | except Exception as e: 63 | self.error(e) 64 | return None 65 | 66 | @cached_property 67 | def ops(self): 68 | try: 69 | return oneplus(fh=self.fh, projid=self.devicemodel, serial=self.serial, loglevel=self.__logger.loglevel, 70 | supported_functions=self.supported_functions, args=self.args) 71 | except Exception as e: 72 | self.error(e) 73 | return None 74 | 75 | @cached_property 76 | def xiaomi(self): 77 | try: 78 | return xiaomi(fh=self.fh) 79 | except Exception as e: 80 | self.error(e) 81 | return None 82 | 83 | def addpatch(self): 84 | if self.ops is not None: 85 | return self.ops.addpatch() 86 | return "" 87 | 88 | def addprogram(self): 89 | if self.ops is not None: 90 | return self.ops.addprogram() 91 | return "" 92 | 93 | def edlauth(self): 94 | if self.xiaomi is not None: 95 | return self.xiaomi.edl_auth() 96 | return True 97 | 98 | def writeprepare(self): 99 | if self.ops is not None: 100 | return self.ops.run() 101 | return True 102 | 103 | def run(self, command, args): 104 | args = args.split(",") 105 | options = {} 106 | for i in range(len(args)): 107 | if "=" in args[i]: 108 | option = args[i].split("=") 109 | if len(option) > 1: 110 | options[option[0]] = option[1] 111 | else: 112 | options[args[i]] = True 113 | if command == "": 114 | print("Valid commands are:\noemunlock, ops\n") 115 | return False 116 | if self.generic is not None and command == "oemunlock": 117 | if "enable" in options: 118 | enable = True 119 | elif "disable" in options: 120 | enable = False 121 | else: 122 | self.error("Unknown mode given. Available are: enable, disable.") 123 | return False 124 | return self.generic.oem_unlock(enable) 125 | elif self.ops is not None and command == "ops": 126 | if self.devicemodel is not None: 127 | partition = "param" 128 | if "enable" in options: 129 | enable = True 130 | elif "disable" in options: 131 | enable = False 132 | else: 133 | self.error("Unknown mode given. Available are: enable, disable.") 134 | return False 135 | res = self.fh.detect_partition(self.args, partition) 136 | if res[0]: 137 | lun = res[1] 138 | rpartition = res[2] 139 | paramdata = self.fh.cmd_read_buffer(lun, rpartition.sector, rpartition.sectors, False) 140 | if paramdata.data == b"": 141 | self.error("Error on reading param partition.") 142 | return False 143 | wdata = self.ops.enable_ops(paramdata.data, enable, self.devicemodel, self.serial) 144 | if wdata is not None: 145 | self.ops.run() 146 | if self.fh.cmd_program_buffer(lun, rpartition.sector, wdata, False): 147 | self.info("Successfully set mode") 148 | return True 149 | else: 150 | self.error("Error on writing param partition") 151 | return False 152 | else: 153 | self.error("No param info generated, did you provide the devicemodel ?") 154 | return False 155 | else: 156 | fpartitions = res[1] 157 | self.error(f"Error: Couldn't detect partition: {partition}\nAvailable partitions:") 158 | for lun in fpartitions: 159 | for rpartition in fpartitions[lun]: 160 | if self.args["--memory"].lower() == "emmc": 161 | self.error("\t" + rpartition) 162 | else: 163 | self.error(lun + ":\t" + rpartition) 164 | else: 165 | self.error("A devicemodel is needed for this command") 166 | return False 167 | -------------------------------------------------------------------------------- /edlclient/Library/Modules/nothing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # (c) B.Kerler 2018-2024 under GPLv3 license 4 | # If you use my code, make sure you refer to my name 5 | # 6 | # !!!!! If you use this code in commercial products, your product is automatically 7 | # GPLv3 and has to be open sourced under GPLv3 as well. !!!!! 8 | import hashlib 9 | import logging 10 | import random 11 | 12 | from edlclient.Library.utils import LogBase 13 | 14 | 15 | class nothing(metaclass=LogBase): 16 | def __init__(self, fh, projid="22111", serial=123456, ATOBuild=0, Flash_Mode=0, cf=0, supported_functions=None, 17 | loglevel=logging.INFO): 18 | self.fh = fh 19 | self.projid = projid 20 | # self.projid == "22111": 21 | self.hashverify = "16386b4035411a770b12507b2e30297c0c5471230b213e6a1e1e701c6a425150" 22 | self.serial = serial 23 | self.supported_functions = supported_functions 24 | self.__logger.setLevel(loglevel) 25 | if loglevel == logging.DEBUG: 26 | logfilename = "log.txt" 27 | fh = logging.FileHandler(logfilename) 28 | self.__logger.addHandler(fh) 29 | 30 | def generatetoken(self, token1: str = None): 31 | if token1 is None: 32 | token1 = random.randbytes(32).hex() 33 | authresp = token1 + self.projid + ("%x" % self.serial) + self.hashverify 34 | token2 = hashlib.sha256(bytes(authresp, 'utf-8')).hexdigest()[:64] 35 | token3 = self.hashverify 36 | return bytes( 37 | f"\n \n\n", 38 | 'utf-8') 39 | 40 | def ntprojectverify(self): 41 | """ 42 | Nothing Phone 2 43 | """ 44 | authcmd = b"\n \n\n" 45 | rsp = self.fh.xmlsend(authcmd) 46 | if rsp.resp: 47 | authresp = self.generatetoken() 48 | rsp = self.fh.xmlsend(authresp) 49 | if rsp.resp: 50 | if b"ACK" in rsp.data: 51 | return True 52 | if "value" in rsp.resp: 53 | if rsp.resp["value"] == "ACK": 54 | if 'authenticated' in rsp.log[0].lower() and 'true' in rsp.log[0].lower(): 55 | return True 56 | return False 57 | 58 | 59 | if __name__ == "__main__": 60 | nt = nothing(fh=None, projid="22111", serial=1729931115) 61 | res = nt.generatetoken(token1="512034500a07154561661e0f371f4a712a0b76074605724c640e301d632b3671") 62 | org = b"\n \n\n" 63 | if res != org: 64 | print("Error !") 65 | print(res) 66 | print(nt.generatetoken()) 67 | -------------------------------------------------------------------------------- /edlclient/Library/Modules/xiaomi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # (c) B.Kerler 2018-2024 under GPLv3 license 4 | # If you use my code, make sure you refer to my name 5 | # 6 | # !!!!! If you use this code in commercial products, your product is automatically 7 | # GPLv3 and has to be open sourced under GPLv3 as well. !!!!! 8 | 9 | import base64 10 | import logging 11 | from edlclient.Library.utils import LogBase 12 | 13 | 14 | class xiaomi(metaclass=LogBase): 15 | def __init__(self, fh, projid="18825", serial=123456, ATOBuild=0, Flash_Mode=0, cf=0, supported_functions=None, 16 | loglevel=logging.INFO): 17 | self.fh = fh 18 | self.xiaomi_authdata = [ 19 | # "QlJORVVnSXVRSTJscjhrU1dDQ3E1dWM3ZnpoRw==" 20 | "k246jlc8rQfBZ2RLYSF4Ndha1P3bfYQKK3IlQy/NoTp8GSz6l57RZRfmlwsbB99sUW/sgfaWj89//dvDl6Fiwso" 21 | "+XXYSSqF2nxshZLObdpMLTMZ1GffzOYd2d/ToryWChoK8v05ZOlfn4wUyaZJT4LHMXZ0NVUryvUbVbxjW5SkLpKDKwkMfnxnEwaOddmT" 22 | "/q0ip4RpVk4aBmDW4TfVnXnDSX9tRI+ewQP4hEI8K5tfZ0mfyycYa0FTGhJPcTTP3TQzy1Krc1DAVLbZ8IqGBrW13YWN" 23 | "/cMvaiEzcETNyA4N3kOaEXKWodnkwucJv2nEnJWTKNHY9NS9f5Cq3OPs4pQ==", 24 | 25 | # "\0" 26 | "vzXWATo51hZr4Dh+a5sA/Q4JYoP4Ee3oFZSGbPZ2tBsaMupn" 27 | "+6tPbZDkXJRLUzAqHaMtlPMKaOHrEWZysCkgCJqpOPkUZNaSbEKpPQ6uiOVJpJwA" 28 | "/PmxuJ72inzSPevriMAdhQrNUqgyu4ATTEsOKnoUIuJTDBmzCeuh/34SOjTdO4Pc+s3ORfMD0TX+WImeUx4c9xVdSL/xirPl" 29 | "/BouhfuwFd4qPPyO5RqkU/fevEoJWGHaFjfI302c9k7EpfRUhq1z+wNpZblOHuj0B3/7VOkK8KtSvwLkmVF" 30 | "/t9ECiry6G5iVGEOyqMlktNlIAbr2MMYXn6b4Y3GDCkhPJ5LUkQ==" 31 | ] 32 | self.__logger.setLevel(loglevel) 33 | if loglevel == logging.DEBUG: 34 | logfilename = "log.txt" 35 | fh = logging.FileHandler(logfilename) 36 | self.__logger.addHandler(fh) 37 | 38 | def edl_auth(self): 39 | """ 40 | Redmi A1, Poco F1, Redmi 5 Pro, 6 Pro, 7 Pro, 7A, 8, 8A, 8A Dual, 8A Pro, Y2, S2 41 | """ 42 | authcmd = b" " 43 | for authdata in self.xiaomi_authdata: 44 | rsp = self.fh.xmlsend(authcmd) 45 | if not rsp.resp: 46 | continue 47 | 48 | rsp = self.fh.xmlsend(base64.b64decode(authdata)) 49 | if not rsp.resp: 50 | continue 51 | 52 | if 'authenticated' in rsp.log[0].lower(): 53 | return True 54 | 55 | return False 56 | -------------------------------------------------------------------------------- /edlclient/Library/TestFiles/gpt_sm8180x.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkerler/edl/bc1534496c83571b199b30b64774abc495744418/edlclient/Library/TestFiles/gpt_sm8180x.bin -------------------------------------------------------------------------------- /edlclient/Library/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkerler/edl/bc1534496c83571b199b30b64774abc495744418/edlclient/Library/__init__.py -------------------------------------------------------------------------------- /edlclient/Library/api.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from edlclient.edl import main as Edl 5 | 6 | EDL_ARGS = { 7 | "--debugmode": False, 8 | "--devicemodel": None, 9 | "--genxml": False, 10 | "--gpt-num-part-entries": "0", 11 | "--gpt-part-entry-size": "0", 12 | "--gpt-part-entry-start-lba": "0", 13 | "--loader": "None", 14 | "--lun": None, 15 | "--maxpayload": "0x100000", 16 | "--memory": None, 17 | "--partitionfilename": None, 18 | "--partitions": None, 19 | "--pid": "-1", 20 | "--portname": None, 21 | "--resetmode": None, 22 | "--sectorsize": None, 23 | "--serial": False, 24 | "--serial_number": None, 25 | "--skip": None, 26 | "--skipresponse": False, 27 | "--skipstorageinit": False, 28 | "--skipwrite": False, 29 | "--tcpport": "1340", 30 | "--vid": "-1", 31 | 32 | "": None, 33 | "": None, 34 | "": None, 35 | "": None, 36 | "": None, 37 | "": None, 38 | "": None, 39 | "": None, 40 | "": None, 41 | "": None, 42 | "": None, 43 | "": None, 44 | "": None, 45 | "": None, 46 | "": None, 47 | "": None, 48 | "": None, 49 | "": None, 50 | } 51 | 52 | class edl_api(): 53 | def __init__(self, args: dict = EDL_ARGS): 54 | self.edl = None 55 | self.status = 0 56 | self.args = {**args} 57 | return 58 | 59 | def init(self) -> int: 60 | self.edl = Edl(self.args) 61 | self.status = self.edl.run() 62 | return self.status 63 | 64 | def deinit(self) -> int: 65 | if (self.edl != None): 66 | self.status = self.edl.exit() 67 | self.edl = None 68 | return self.status 69 | 70 | def reinit(self) -> int: 71 | if (self.deinit() == 1): 72 | return self.status 73 | return self.init() 74 | 75 | def set_arg(self, key: str, value, reset: bool = False): 76 | if (not key in self.args): 77 | return "Invalid key!" 78 | 79 | if (reset): 80 | value = EDL_ARGS[key] 81 | 82 | self.args[key] = value 83 | if (self.edl != None): 84 | self.edl.args = self.args 85 | return self.args 86 | 87 | def reset_arg(self, key: str): 88 | return self.set_arg(key, None, True) 89 | 90 | def __del__(self) -> int: 91 | return self.deinit() 92 | 93 | # ----- Actual API ----- 94 | 95 | def server(self): 96 | return self.edl.fh.handle_firehose("server", self.edl.args) 97 | 98 | def memorydump(self): 99 | return self.edl.fh.handle_firehose("memorydump", self.edl.args) 100 | 101 | def printgpt(self): 102 | return self.edl.fh.handle_firehose("printgpt", self.edl.args) 103 | 104 | def gpt(self, directory: str): 105 | self.set_arg("", directory) 106 | self.edl.fh.handle_firehose("printgpt", self.edl.args) 107 | return self.edl.fh.handle_firehose("gpt", self.edl.args) 108 | 109 | def r(self, partitionname: str, filename: str): 110 | self.set_arg("", partitionname) 111 | self.set_arg("", filename) 112 | return self.edl.fh.handle_firehose('r', self.edl.args) 113 | 114 | def rl(self, directory: str): 115 | self.set_arg("", directory) 116 | return self.edl.fh.handle_firehose("rl", self.edl.args) 117 | 118 | def rf(self, filename: str): 119 | self.set_arg("", filename) 120 | return self.edl.fh.handle_firehose("rf", self.edl.args) 121 | 122 | def rs(self, start_sector: str, sectors: str, filename: str): 123 | self.set_arg("", start_sector) 124 | self.set_arg("", sectors) 125 | self.set_arg("", filename) 126 | return self.edl.fh.handle_firehose("rs", self.edl.args) 127 | 128 | def w(self, partitionname: str, filename: str): 129 | self.set_arg("", partitionname) 130 | self.set_arg("", filename) 131 | return self.edl.fh.handle_firehose('w', self.edl.args) 132 | 133 | def wl(self, directory: str): 134 | self.set_arg("", directory) 135 | return self.edl.fh.handle_firehose("wl", self.edl.args) 136 | 137 | def wf(self, filename: str): 138 | self.set_arg("", filename) 139 | return self.edl.fh.handle_firehose("wf", self.edl.args) 140 | 141 | def ws(self, start_sector: str, filename: str): 142 | self.set_arg("", start_sector) 143 | self.set_arg("", filename) 144 | return self.edl.fh.handle_firehose("ws", self.edl.args) 145 | 146 | def e(self, partitionname: str): 147 | self.set_arg("", partitionname) 148 | return self.edl.fh.handle_firehose('e', self.edl.args) 149 | 150 | def es(self, start_sector: str, sectors: str): 151 | self.set_arg("", start_sector) 152 | self.set_arg("", sectors) 153 | return self.edl.fh.handle_firehose("es", self.edl.args) 154 | 155 | def ep(self, partitionname: str, sectors: str): 156 | self.set_arg("", partitionname) 157 | self.set_arg("", sectors) 158 | return self.edl.fh.handle_firehose("ep", self.edl.args) 159 | 160 | def footer(self, filename: str): 161 | self.set_arg("", filename) 162 | return self.edl.fh.handle_firehose("footer", self.edl.args) 163 | 164 | def peek(self, offset: int, length: int, filename: str): 165 | self.set_arg("", offset) 166 | self.set_arg("", length) 167 | self.set_arg("", filename) 168 | return self.edl.fh.handle_firehose("peek", self.edl.args) 169 | 170 | def peekhex(self, offset: int, length: int): 171 | self.set_arg("", offset) 172 | self.set_arg("", length) 173 | return self.edl.fh.handle_firehose("peekhex", self.edl.args) 174 | 175 | def peekdword(self, offset: int): 176 | self.set_arg("", offset) 177 | return self.edl.fh.handle_firehose("peekdword", self.edl.args) 178 | 179 | def peekqword(self, offset: int): 180 | self.set_arg("", offset) 181 | return self.edl.fh.handle_firehose("peekqword", self.edl.args) 182 | 183 | def memtbl(self, filename: str): 184 | self.set_arg("", filename) 185 | return self.edl.fh.handle_firehose("memtbl", self.edl.args) 186 | 187 | def poke(self, offset: int, filename: str): 188 | self.set_arg("", offset) 189 | self.set_arg("", filename) 190 | return self.edl.fh.handle_firehose("poke", self.edl.args) 191 | 192 | def pokehex(self, offset: int, data: str): 193 | self.set_arg("", offset) 194 | self.set_arg("", data) 195 | return self.edl.fh.handle_firehose("pokehex", self.edl.args) 196 | 197 | def pokedword(self, offset: int, data: str): 198 | self.set_arg("", offset) 199 | self.set_arg("", data) 200 | return self.edl.fh.handle_firehose("pokedword", self.edl.args) 201 | 202 | def pokeqword(self, offset: int, data: str): 203 | self.set_arg("", offset) 204 | self.set_arg("", data) 205 | return self.edl.fh.handle_firehose("pokeqword", self.edl.args) 206 | 207 | def memcpy(self, offset: int, size: int): 208 | self.set_arg("", offset) 209 | self.set_arg("", size) 210 | return self.edl.fh.handle_firehose("memcpy", self.edl.args) 211 | 212 | def secureboot(self): 213 | return self.edl.fh.handle_firehose("server", self.edl.args) 214 | 215 | def pbl(self, filename: str): 216 | self.set_arg("", filename) 217 | return self.edl.fh.handle_firehose("pbl", self.edl.args) 218 | 219 | def qfp(self, filename: str): 220 | self.set_arg("", filename) 221 | return self.edl.fh.handle_firehose("qfp", self.edl.args) 222 | 223 | def getstorageinfo(self): 224 | return self.edl.fh.handle_firehose("getstorageinfo", self.edl.args) 225 | 226 | def setbootablestoragedrive(self, lun: str): 227 | self.set_arg("", lun) 228 | return self.edl.fh.handle_firehose("setbootablestoragedrive", self.edl.args) 229 | 230 | def getactiveslot(self): 231 | return self.edl.fh.handle_firehose("getactiveslot", self.edl.args) 232 | 233 | def setactiveslot(self, slot: str): 234 | self.set_arg("", slot) 235 | return self.edl.fh.handle_firehose("setactiveslot", self.edl.args) 236 | 237 | def send(self, command: str): 238 | self.set_arg("", command) 239 | return self.edl.fh.handle_firehose("send", self.edl.args) 240 | 241 | def xml(self, xmlfile: str): 242 | self.set_arg("", xmlfile) 243 | return self.edl.fh.handle_firehose("xml", self.edl.args) 244 | 245 | def rawxml(self, xmlstring: str): 246 | self.set_arg("", xmlstring) 247 | return self.edl.fh.handle_firehose("rawxml", self.edl.args) 248 | 249 | def reset(self): 250 | return self.edl.fh.handle_firehose("reset", self.edl.args) 251 | 252 | def nop(self): 253 | return self.edl.fh.handle_firehose("nop", self.edl.args) 254 | 255 | def modules(self, command: str, options: str): 256 | self.set_arg("", command) 257 | self.set_arg("", options) 258 | return self.edl.fh.handle_firehose("modules", self.edl.args) 259 | 260 | def provision(self, xmlfile: str): 261 | self.set_arg("", xmlfile) 262 | return self.edl.fh.handle_firehose("provision", self.edl.args) 263 | 264 | def qfil(self, rawprogram: str, patch: str, imagedir: str): 265 | self.set_arg("", rawprogram) 266 | self.set_arg("", patch) 267 | self.set_arg("", imagedir) 268 | return self.edl.fh.handle_firehose("qfil", self.edl.args) 269 | -------------------------------------------------------------------------------- /edlclient/Library/asmtools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # (c) B.Kerler 2018-2024 under GPLv3 license 4 | # If you use my code, make sure you refer to my name 5 | # 6 | # !!!!! If you use this code in commercial products, your product is automatically 7 | # GPLv3 and has to be open sourced under GPLv3 as well. !!!!! 8 | from capstone import * 9 | from keystone import * 10 | from binascii import unhexlify 11 | import argparse 12 | 13 | 14 | def asm(code, cpu, mode, bigendian): 15 | if bigendian: 16 | little = KS_MODE_BIG_ENDIAN # big-endian mode 17 | else: 18 | little = KS_MODE_LITTLE_ENDIAN # little-endian mode (default mode) 19 | print("CPU: %s, MODE: %s" % (cpu, mode)) 20 | ks = None 21 | if cpu == "arm": 22 | # ARM architecture (including Thumb, Thumb-2) 23 | if mode == "arm": 24 | ks = Ks(KS_ARCH_ARM, KS_MODE_ARM + little) # ARM mode 25 | elif mode == "thumb": 26 | ks = Ks(KS_ARCH_ARM, KS_MODE_THUMB + little) # THUMB mode (including Thumb-2) 27 | elif mode == "mclass": 28 | ks = Ks(KS_ARCH_ARM, KS_MODE_THUMB + little) 29 | elif mode == "v8": 30 | ks = Ks(KS_ARCH_ARM, KS_MODE_ARM + KS_MODE_V8 + little) 31 | elif cpu == "arm64": 32 | # ARM-64, also called AArch64 33 | ks = Ks(KS_ARCH_ARM64, little) # ARM mode 34 | elif cpu == "mips": 35 | # Mips architecture 36 | if mode == "micro": 37 | ks = Ks(KS_ARCH_MIPS, KS_MODE_MICRO + little) # MicroMips mode 38 | elif mode == "3": 39 | ks = Ks(KS_ARCH_MIPS, KS_MODE_MIPS3 + little) # Mips III ISA 40 | elif mode == "32R6": 41 | ks = Ks(KS_ARCH_MIPS, KS_MODE_MIPS32R6 + little) # Mips32r6 ISA 42 | elif mode == "32": 43 | ks = Ks(KS_ARCH_MIPS, KS_MODE_MIPS32 + little) # Mips32 ISA 44 | elif mode == "64": 45 | ks = Ks(KS_ARCH_MIPS, KS_MODE_MIPS64 + little) # Mips64 ISA 46 | elif cpu == "x86": 47 | # X86 architecture (including x86 & x86-64) 48 | if mode == "16": 49 | ks = Ks(KS_ARCH_X86, KS_MODE_16 + little) # 16-bit mode 50 | elif mode == "32": 51 | ks = Ks(KS_ARCH_X86, KS_MODE_32 + little) # 32-bit mode 52 | elif mode == "64": 53 | ks = Ks(KS_ARCH_X86, KS_MODE_64 + little) # 64-bit mode 54 | elif cpu == "ppc": 55 | # PowerPC architecture (currently unsupported) 56 | if mode == "32": 57 | ks = Ks(KS_ARCH_PPC, KS_MODE_PPC32 + little) # 32-bit mode 58 | elif mode == "64": 59 | ks = Ks(KS_ARCH_PPC, KS_MODE_PPC64 + little) # 64-bit mode 60 | elif mode == "qpx": 61 | ks = Ks(KS_ARCH_PPC, KS_MODE_QPX + little) # Quad Processing eXtensions mode 62 | elif cpu == "sparc": 63 | # Sparc architecture 64 | if mode == "32": 65 | ks = Ks(KS_ARCH_SPARC, KS_MODE_SPARC32 + little) # 32-bit mode 66 | elif mode == "64": 67 | ks = Ks(KS_ARCH_SPARC, KS_MODE_SPARC64 + little) # 64-bit mode 68 | elif mode == "v9": 69 | ks = Ks(KS_ARCH_SPARC, KS_MODE_V9 + little) # SparcV9 mode 70 | elif cpu == "systemz": 71 | ks = Ks(KS_ARCH_SYSTEMZ, KS_MODE_BIG_ENDIAN) # SystemZ architecture (S390X) 72 | elif cpu == "hexagon": 73 | ks = Ks(KS_ARCH_HEXAGON, KS_MODE_LITTLE_ENDIAN) # QDSP6 Hexagon Qualcomm 74 | 75 | if ks is None: 76 | print("CPU and/or Mode not supported!") 77 | exit(0) 78 | 79 | encoding, count = ks.asm(code) 80 | return encoding 81 | 82 | 83 | def disasm(code, cpu, mode, bigendian, size): 84 | cs = None 85 | if bigendian: 86 | little = CS_MODE_BIG_ENDIAN # big-endian mode 87 | else: 88 | little = CS_MODE_LITTLE_ENDIAN # little-endian mode (default mode) 89 | 90 | if cpu == "arm": 91 | if mode == "arm": 92 | cs = Cs(CS_ARCH_ARM, CS_MODE_ARM + little) # ARM mode 93 | elif mode == "thumb": 94 | cs = Cs(CS_ARCH_ARM, CS_MODE_THUMB + little) # THUMB mode (including Thumb-2) 95 | elif mode == "mclass": 96 | cs = Cs(CS_ARCH_ARM, CS_MODE_THUMB + CS_MODE_MCLASS + little) # ARM Cortex-M 97 | elif mode == "v8": 98 | cs = Cs(CS_ARCH_ARM, CS_MODE_ARM + CS_MODE_V8 + little) # ARMv8 A32 encodings for ARM 99 | elif cpu == "arm64": 100 | cs = Cs(CS_ARCH_ARM64, CS_MODE_ARM + little) 101 | elif cpu == "mips": 102 | if mode == "micro": 103 | cs = Cs(CS_ARCH_MIPS, CS_MODE_MICRO + little) # MicroMips mode 104 | elif mode == "32": 105 | cs = Cs(CS_ARCH_MIPS, CS_MODE_MIPS32 + little) # Mips III ISA 106 | elif mode == "64": 107 | cs = Cs(CS_ARCH_MIPS, CS_MODE_MIPS64 + little) # Mips III ISA 108 | elif mode == "32R6-Micro": 109 | cs = Cs(CS_ARCH_MIPS, CS_MODE_MIPS32R6 + CS_MODE_MICRO + little) # Mips32r6 ISA 110 | elif mode == "32R6": 111 | cs = Cs(CS_ARCH_MIPS, CS_MODE_MIPS32R6 + little) # General Purpose Registers are 64bit wide 112 | elif cpu == "x86": 113 | # X86 architecture (including x86 & x86-64) 114 | if mode == "16": 115 | cs = Cs(CS_ARCH_X86, CS_MODE_16 + little) # 16-bit mode 116 | elif mode == "32": 117 | cs = Cs(CS_ARCH_X86, CS_MODE_32 + little) # 32-bit mode 118 | elif mode == "64": 119 | cs = Cs(CS_ARCH_X86, CS_MODE_64 + little) # 64-bit mode 120 | elif cpu == "ppc": 121 | # PowerPC architecture (currently unsupported) 122 | if mode == "64": 123 | cs = Cs(CS_ARCH_PPC, little) # 64-bit mode 124 | elif cpu == "sparc": 125 | # Sparc architecture 126 | if mode == "None": 127 | cs = Cs(CS_ARCH_SPARC, CS_MODE_BIG_ENDIAN) # 32-bit mode 128 | elif mode == "v9": 129 | cs = Cs(CS_ARCH_SPARC, CS_MODE_BIG_ENDIAN + CS_MODE_V9) # SparcV9 mode 130 | elif cpu == "systemz": 131 | cs = Cs(CS_ARCH_SYSZ, 0) # SystemZ architecture (S390X) 132 | elif cpu == "xcore": 133 | cs = Cs(CS_ARCH_XCORE, 0) # XCore architecture 134 | 135 | if cs is not None: 136 | print("CPU and/or mode not supported!") 137 | exit(0) 138 | 139 | instr = [] 140 | for i in cs.disasm(code, size): 141 | # print("0x%x:\t%s\t%s" % (i.address, i.mnemonic, i.op_str)) 142 | instr.append("%s\t%s" % (i.mnemonic, i.op_str)) 143 | return instr 144 | 145 | 146 | def main(): 147 | parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, 148 | description='Disasm/Asm Tool (c) B. Kerler 2018') 149 | 150 | parser.add_argument( 151 | '--infile', '-in', 152 | help='Input File', 153 | default='') 154 | parser.add_argument( 155 | '--outfile', '-out', 156 | help='Output File', 157 | default='') 158 | 159 | parser.add_argument( 160 | '--cstyle', '-cstyle', 161 | help='Print in c style', 162 | action="store_true") 163 | 164 | parser.add_argument( 165 | '--bigendian', '-bigendian', 166 | help='Big endian', 167 | action="store_true") 168 | 169 | parser.add_argument( 170 | '--disasm', '-disasm', 171 | help='Disasm: arm[arm,thumb,mclass,v8],arm64[arm],mips[micro,3,32R6,GP64],x86[16,32,64],ppc[64],sparc[32,64,' 172 | 'v9],systemz,xcore', 173 | default='') 174 | 175 | parser.add_argument( 176 | '--asm', '-asm', 177 | help='Asm: arm[arm,thumb,mclass,v8],arm64[arm],mips[micro,32,64,32R6,32R6-Micro],x86[16,32,64],ppc[32,64,' 178 | 'qpx],sparc[None,v9],systemz,hexagon', 179 | default='') 180 | 181 | parser.add_argument( 182 | '--inp', '-input', 183 | help='Disasm: hexstring, Asm: instruction string input ', 184 | default='') 185 | 186 | args = parser.parse_args() 187 | 188 | if args.asm == '' and args.disasm == '': 189 | print("[asmtools] Usage: -asm cpu,mode or -disasm cpu,mode") 190 | exit(0) 191 | 192 | if args.infile == '' and args.inp == '': 193 | print("[asmtools] I must have an infile to work on (-in)") 194 | exit(0) 195 | 196 | if args.asm != "": 197 | cpu, mode = args.asm.split(",") 198 | else: 199 | cpu, mode = args.disasm.split(",") 200 | 201 | if args.inp != "": 202 | args.inp = args.inp.replace("\\n", "\n") 203 | if args.asm != "": 204 | aa = asm(args.inp, cpu, mode, args.bigendian) 205 | else: 206 | aa = disasm(unhexlify(args.inp), cpu, mode, args.bigendian, len(args.inp)) 207 | else: 208 | with open(args.infile, "rb") as rf: 209 | code = rf.read() 210 | if args.asm != "": 211 | aa = asm(code, cpu, mode, args.bigendian) 212 | else: 213 | aa = disasm(code, cpu, mode, args.bigendian, len(code)) 214 | 215 | if args.outfile != "": 216 | with open(args.outfile, "wb") as wf: 217 | if args.asm != "": 218 | ba = bytearray() 219 | for i in aa: 220 | ba.append(i) 221 | wf.write(ba) 222 | else: 223 | wf.write(aa) 224 | else: 225 | if args.asm != "": 226 | sc = "" 227 | count = 0 228 | out = "" 229 | for i in aa: 230 | if args.cstyle: 231 | out += ("\\x%02x" % i) 232 | else: 233 | out += ("%02x" % i) 234 | sc += "%02x" % i 235 | count += 1 236 | print(out) 237 | else: 238 | print(aa) 239 | ''' 240 | segment=bytearray(code[0x01C97C:0x01C990]) 241 | segment[7]=0xE1 242 | segment[11]=0xE1 243 | segment[15]=0xE5 244 | segment[19]=0xEA 245 | print(hex(segment[7])) 246 | print(disasm(bytes(segment),len(segment))) 247 | ''' 248 | 249 | 250 | main() 251 | -------------------------------------------------------------------------------- /edlclient/Library/cryptutils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # (c) B.Kerler 2018-2024 under GPLv3 license 4 | # If you use my code, make sure you refer to my name 5 | # 6 | # !!!!! If you use this code in commercial products, your product is automatically 7 | # GPLv3 and has to be open sourced under GPLv3 as well. !!!!! 8 | 9 | import hashlib 10 | from Cryptodome.Cipher import AES 11 | from Cryptodome.Util import Counter 12 | from Cryptodome.Hash import CMAC 13 | from Cryptodome.Util.number import long_to_bytes, bytes_to_long 14 | from binascii import hexlify, unhexlify 15 | 16 | 17 | class PKCS1BaseException(Exception): 18 | pass 19 | 20 | 21 | class DecryptionError(PKCS1BaseException): 22 | pass 23 | 24 | 25 | class MessageTooLong(PKCS1BaseException): 26 | pass 27 | 28 | 29 | class WrongLength(PKCS1BaseException): 30 | pass 31 | 32 | 33 | class MessageTooShort(PKCS1BaseException): 34 | pass 35 | 36 | 37 | class InvalidSignature(PKCS1BaseException): 38 | pass 39 | 40 | 41 | class RSAModulusTooShort(PKCS1BaseException): 42 | pass 43 | 44 | 45 | class IntegerTooLarge(PKCS1BaseException): 46 | pass 47 | 48 | 49 | class MessageRepresentativeOutOfRange(PKCS1BaseException): 50 | pass 51 | 52 | 53 | class CiphertextRepresentativeOutOfRange(PKCS1BaseException): 54 | pass 55 | 56 | 57 | class SignatureRepresentativeOutOfRange(PKCS1BaseException): 58 | pass 59 | 60 | 61 | class EncodingError(PKCS1BaseException): 62 | pass 63 | 64 | 65 | class InvalidInputException(Exception): 66 | def __init__(self, msg): 67 | self.msg = msg 68 | 69 | def __str__(self): 70 | return str(self.msg) 71 | 72 | 73 | class InvalidTagException(Exception): 74 | def __str__(self): 75 | return 'The authentication tag is invalid.' 76 | 77 | 78 | class cryptutils: 79 | class aes: 80 | class AES_GCM: 81 | # Galois/Counter Mode with AES-128 and 96-bit IV 82 | """ 83 | Example: 84 | master_key = 0x0ADAABC70895E008147A48C27791F654 #54F69177C2487A1408E09508C7ABDA0A 85 | init_value = 0x2883B4173F9A838437C1CD86CCFAA5ED #EDA5FACC86CDC13784839A3F17B48328 86 | auth_tag = 46D1FA806ADA1A916E6D0D0B55A40C1F94D7820D110F3DFC984AA3EEC9D67521 87 | ciphertext = b"\x8A\x40\x9D\xF8\x76\x09\xCA\x10\x36\xB3\xFA\x86\x20\xC5\x85\xA3"+ \ 88 | b"\xE3\x8E\x17\x14\x40\xBD\x6B\xA7\x26\x1F\x0B\xFE\xC5\x0A\xB0\xF0"+\ 89 | b"\xCF\x69\x2E\x76\x18\x6D\x96\x9E\x83\x87\x63\xC7\x15\x7C\x1F\x28"+\ 90 | b"\xEE\xE8\xF1\xD6\x1F\x02\x2A\xF1\xA2\x43\x8A\xCF\x7C\xF2\x66\x37"+\ 91 | b"\x8B\x49\x1D\xC5\xDC\xE2\x54\x77\xED\x2F\x17\x5B\xA9\xFC\x8A\x81"+\ 92 | b"\x60\xF6\x5A\x22\x39\xCA\x79\x32\x9B\xDB\x49\x50\xCE\x74\x2C\x56"+\ 93 | b"\xDB\x97\xCA\x13\xDD\x25\xA3\x3C\x0F\x53\xDD\x38\xBF\x7B\x8B\xDA"+\ 94 | b"\xD6\x74\x38\x87\x96\xA8\x10\x5A\x96\x38\x39\x7F\xFD\xEC\xC7\x62"+\ 95 | b"\x06\x44\xF4\x0F\x78\xD6\x3D\x1A\xC5\x40\x4B\x3B\x8C\xBE\xE6\x76"+\ 96 | b"\x65\xFA\x40\xDA\xD3\xF0\xF2\x19\x35\xB7\xB2\x91\xFC\x18\x2C\x53"+\ 97 | b"\xA2\x3F\x1A\xA7\x4F\xFC\x42\xAE\xC1\x97\x89\xAB\x7E\x9B\xA1\x5C"+\ 98 | b"\x3A\x3B\x2F\x01\x60\xB1\xC5\x30\x7C\xB7\x2B\xD5\xAF\x27\xA0\x4C"+\ 99 | b"\xE9\x80\xC5\xB4\xEC\xFB\xD7\x59\xE8\x5D\xEE\xB5\x6F\x3B\xA7\xDE"+\ 100 | b"\xDA\xD8\x55\x09\x7A\x5A\xAD\x6C\x13\x2D\xD1\x23\x7C\x13\x5F\x84"+\ 101 | b"\x35\x29\x51\x55\xF4\x53\x12\x9C\x86\x7A\x77\x2B\xE2\x7B\x01\xA2"+\ 102 | b"\x6B\xC8\x5D\xD8\xCA\x92\xFB\x32\x0A\x09\xAE\xB3\x45\x8D\x0B\x60"+\ 103 | b"\x9D\xEB\xB7\x02\x07\xAB\x4A\x24\xF6\xA1\xE7\x59\xA0\xC4\xB1\xFB"+\ 104 | b"\x44\xAD\x32\xC7\xD4\x8F\xC6\x0C\x33\xD5\x88\x82\xF4\x9A\xA2\x7C"+\ 105 | b"\xDC\x56\x90\x96\x3C\xBC\xCF\x95\x17\x22\x55\x64\x67\x62\x52\x86"+\ 106 | b"\xFA\x3B\xFC\xAA\xC7\x1B\xDE\x7F\x01\xB3\x61\x8C\x28\xAE\x64\x7E"+\ 107 | b"\x43\xF0\x5A\x50\x60\x50\x85\xD4\xC4\xA6\x92\xC7\x8B\xE5\x04\x80"+\ 108 | b"\x74\x0F\xBA\xEB\x7C\x2C\x81\x07\x99\x22\x51\xD1\x9E\xE1\x59\xEE"+\ 109 | b"\x77\xC2\x13\x2C\x46\x16\x92\x9A\x69\xD9\x01\x75\x31\xA6\x20\xB9"+\ 110 | b"\x13\x46\x55\xF7\x8C\xC6\xB8\x7C\x8F\xAC\x00\x1A\x58\x68\xC7\xAD"+\ 111 | b"\x4E\x34\xB9\xEF\x5F\xCD\x87\x12\x0E\x8A\xEA\xD2\x4D\x66\x5E\x40"+\ 112 | b"\xBD\x1D\x30\x8A\x83\xB8\x4F\xC2\xAB\x28\x58\x6C\xEA\xDB\xF5\x87"+\ 113 | b"\xA0\x62\x9E\xF9\xF4\xE7\xE8\x65" 114 | my_gcm = AES_GCM(master_key) 115 | decrypted = my_gcm.decrypt(init_value, ciphertext, auth_tag) 116 | """ 117 | 118 | def __init__(self, master_key): 119 | self.change_key(master_key) 120 | 121 | # GF(2^128) defined by 1 + a + a^2 + a^7 + a^128 122 | # Please note the MSB is x0 and LSB is x127 123 | def gf_2_128_mul(self, x, y): 124 | assert x < (1 << 128) 125 | assert y < (1 << 128) 126 | res = 0 127 | for i in range(127, -1, -1): 128 | res ^= x * ((y >> i) & 1) # branchless 129 | x = (x >> 1) ^ ((x & 1) * 0xE1000000000000000000000000000000) 130 | assert res < 1 << 128 131 | return res 132 | 133 | def change_key(self, master_key): 134 | if master_key >= (1 << 128): 135 | raise InvalidInputException('Master key should be 128-bit') 136 | 137 | self.__master_key = long_to_bytes(master_key, 16) 138 | self.__aes_ecb = AES.new(self.__master_key, AES.MODE_ECB) 139 | self.__auth_key = bytes_to_long(self.__aes_ecb.encrypt(b'\x00' * 16)) 140 | 141 | # precompute the table for multiplication in finite field 142 | table = [] # for 8-bit 143 | for i in range(16): 144 | row = [] 145 | for j in range(256): 146 | row.append(self.gf_2_128_mul(self.__auth_key, j << (8 * i))) 147 | table.append(tuple(row)) 148 | self.__pre_table = tuple(table) 149 | 150 | self.prev_init_value = None # reset 151 | 152 | def __times_auth_key(self, val): 153 | res = 0 154 | for i in range(16): 155 | res ^= self.__pre_table[i][val & 0xFF] 156 | val >>= 8 157 | return res 158 | 159 | def __ghash(self, aad, txt): 160 | len_aad = len(aad) 161 | len_txt = len(txt) 162 | 163 | # padding 164 | if 0 == len_aad % 16: 165 | data = aad 166 | else: 167 | data = aad + b'\x00' * (16 - len_aad % 16) 168 | if 0 == len_txt % 16: 169 | data += txt 170 | else: 171 | data += txt + b'\x00' * (16 - len_txt % 16) 172 | 173 | tag = 0 174 | assert len(data) % 16 == 0 175 | for i in range(len(data) // 16): 176 | tag ^= bytes_to_long(data[i * 16: (i + 1) * 16]) 177 | tag = self.__times_auth_key(tag) 178 | # print 'X\t', hex(tag) 179 | tag ^= ((8 * len_aad) << 64) | (8 * len_txt) 180 | tag = self.__times_auth_key(tag) 181 | 182 | return tag 183 | 184 | def encrypt(self, init_value, plaintext, auth_data=b''): 185 | if init_value >= (1 << 96): 186 | raise InvalidInputException('IV should be 96-bit') 187 | # a naive checking for IV reuse 188 | if init_value == self.prev_init_value: 189 | raise InvalidInputException('IV must not be reused!') 190 | self.prev_init_value = init_value 191 | 192 | len_plaintext = len(plaintext) 193 | # len_auth_data = len(auth_data) 194 | 195 | if len_plaintext > 0: 196 | counter = Counter.new( 197 | nbits=32, 198 | prefix=long_to_bytes(init_value, 12), 199 | initial_value=2, # notice this 200 | allow_wraparound=False) 201 | aes_ctr = AES.new(self.__master_key, AES.MODE_CTR, counter=counter) 202 | 203 | if 0 != len_plaintext % 16: 204 | padded_plaintext = plaintext + \ 205 | b'\x00' * (16 - len_plaintext % 16) 206 | else: 207 | padded_plaintext = plaintext 208 | ciphertext = aes_ctr.encrypt(padded_plaintext)[:len_plaintext] 209 | 210 | else: 211 | ciphertext = b'' 212 | 213 | auth_tag = self.__ghash(auth_data, ciphertext) 214 | # print 'GHASH\t', hex(auth_tag) 215 | auth_tag ^= bytes_to_long(self.__aes_ecb.encrypt( 216 | long_to_bytes((init_value << 32) | 1, 16))) 217 | 218 | # assert len(ciphertext) == len(plaintext) 219 | assert auth_tag < (1 << 128) 220 | return ciphertext, auth_tag 221 | 222 | def decrypt(self, init_value, ciphertext, auth_tag, auth_data=b''): 223 | # if init_value >= (1 << 96): 224 | # raise InvalidInputException('IV should be 96-bit') 225 | # if auth_tag >= (1 << 128): 226 | # raise InvalidInputException('Tag should be 128-bit') 227 | 228 | if auth_tag != self.__ghash(auth_data, ciphertext) ^ \ 229 | bytes_to_long(self.__aes_ecb.encrypt( 230 | long_to_bytes((init_value << 32) | 1, 16))): 231 | raise InvalidTagException 232 | 233 | len_ciphertext = len(ciphertext) 234 | if len_ciphertext > 0: 235 | counter = Counter.new( 236 | nbits=32, 237 | prefix=long_to_bytes(init_value, 12), 238 | initial_value=2, 239 | allow_wraparound=True) 240 | aes_ctr = AES.new(self.__master_key, AES.MODE_CTR, counter=counter) 241 | 242 | if 0 != len_ciphertext % 16: 243 | padded_ciphertext = ciphertext + \ 244 | b'\x00' * (16 - len_ciphertext % 16) 245 | else: 246 | padded_ciphertext = ciphertext 247 | plaintext = aes_ctr.decrypt(padded_ciphertext)[:len_ciphertext] 248 | 249 | else: 250 | plaintext = b'' 251 | 252 | return plaintext 253 | 254 | def aes_gcm(self, ciphertext, nounce, aes_key, hdr, tag_auth, decrypt=True): 255 | cipher = AES.new(aes_key, AES.MODE_GCM, nounce) 256 | if hdr is not None: 257 | cipher.update(hdr) 258 | if decrypt: 259 | plaintext = cipher.decrypt(ciphertext) 260 | try: 261 | cipher.verify(tag_auth) 262 | return plaintext 263 | except ValueError: 264 | return None 265 | return None 266 | 267 | def aes_cbc(self, key, iv, data, decrypt=True): 268 | if decrypt: 269 | return AES.new(key, AES.MODE_CBC, IV=iv).decrypt(data) 270 | else: 271 | return AES.new(key, AES.MODE_CBC, IV=iv).encrypt(data) 272 | 273 | def aes_ecb(self, key, data, decrypt=True): 274 | if decrypt: 275 | return AES.new(key, AES.MODE_ECB).decrypt(data) 276 | else: 277 | return AES.new(key, AES.MODE_ECB).encrypt(data) 278 | 279 | def aes_ctr(self, key, counter, enc_data, decrypt=True): 280 | ctr = Counter.new(128, initial_value=counter) 281 | # Create the AES cipher object and decrypt the ciphertext, basically this here is just aes ctr 256 :) 282 | cipher = AES.new(key, AES.MODE_CTR, counter=ctr) 283 | if decrypt: 284 | data = cipher.decrypt(enc_data) 285 | else: 286 | data = cipher.encrypt(enc_data) 287 | return data 288 | 289 | def aes_ccm(self, key, nounce, tag_auth, data, decrypt=True): 290 | cipher = AES.new(key, AES.MODE_CCM, nounce) 291 | if decrypt: 292 | plaintext = cipher.decrypt(data) 293 | try: 294 | cipher.verify(tag_auth) 295 | return plaintext 296 | except ValueError: 297 | return None 298 | else: 299 | ciphertext = cipher.encrypt(data) 300 | return ciphertext 301 | 302 | def aes_cmac_verify(self, key, plain, compare): 303 | ctx = CMAC.new(key, ciphermod=AES) 304 | ctx.update(plain) 305 | result = ctx.hexdigest() 306 | if result != compare: 307 | print("AES-CMAC failed !") 308 | else: 309 | print("AES-CMAC ok !") 310 | 311 | class rsa: # RFC8017 312 | def __init__(self, hashtype="SHA256"): 313 | if hashtype == "SHA1": 314 | self.hash = hashlib.sha1 315 | self.digestLen = 0x14 316 | elif hashtype == "SHA256": 317 | self.hash = hashlib.sha256 318 | self.digestLen = 0x20 319 | 320 | def pss_test(self): 321 | N = "a2ba40ee07e3b2bd2f02ce227f36a195024486e49c19cb41bbbdfbba98b22b0e577c2eeaffa20d883a76e65e394c69d4b3c05a1e8fadda27edb2a42bc000fe888b9b32c22d15add0cd76b3e7936e19955b220dd17d4ea904b1ec102b2e4de7751222aa99151024c7cb41cc5ea21d00eeb41f7c800834d2c6e06bce3bce7ea9a5" 322 | e = "010001" 323 | D = "050e2c3e38d886110288dfc68a9533e7e12e27d2aa56d2cdb3fb6efa990bcff29e1d2987fb711962860e7391b1ce01ebadb9e812d2fbdfaf25df4ae26110a6d7a26f0b810f54875e17dd5c9fb6d641761245b81e79f8c88f0e55a6dcd5f133abd35f8f4ec80adf1bf86277a582894cb6ebcd2162f1c7534f1f4947b129151b71" 324 | MSG = "859eef2fd78aca00308bdc471193bf55bf9d78db8f8a672b484634f3c9c26e6478ae10260fe0dd8c082e53a5293af2173cd50c6d5d354febf78b26021c25c02712e78cd4694c9f469777e451e7f8e9e04cd3739c6bbfedae487fb55644e9ca74ff77a53cb729802f6ed4a5ffa8ba159890fc " 325 | salt = "e3b5d5d002c1bce50c2b65ef88a188d83bce7e61" 326 | 327 | N = int(N, 16) 328 | e = int(e, 16) 329 | D = int(D, 16) 330 | MSG = unhexlify(MSG) 331 | salt = unhexlify(salt) 332 | signature = self.pss_sign(D, N, self.hash(MSG), salt, 1024) # pkcs_1_pss_encode_sha256 333 | isvalid = self.pss_verify(e, N, self.hash(MSG), signature, 1024) 334 | if isvalid: 335 | print("Test passed.") 336 | else: 337 | print("Test failed.") 338 | 339 | def i2osp(self, x, x_len): 340 | """Converts the integer x to its big-endian representation of length 341 | x_len. 342 | """ 343 | if x > 256 ** x_len: 344 | raise IntegerTooLarge 345 | h = hex(x)[2:] 346 | if h[-1] == 'L': 347 | h = h[:-1] 348 | if len(h) & 1 == 1: 349 | h = '0%s' % h 350 | x = unhexlify(h) 351 | return b'\x00' * int(x_len - len(x)) + x 352 | 353 | def os2ip(self, x): 354 | """Converts the byte string x representing an integer reprented using the 355 | big-endian convient to an integer. 356 | """ 357 | h = hexlify(x) 358 | return int(h, 16) 359 | 360 | # def os2ip(self, X): 361 | # return int.from_bytes(X, byteorder='big') 362 | 363 | def mgf1(self, input, length): 364 | counter = 0 365 | output = b'' 366 | while len(output) < length: 367 | C = self.i2osp(counter, 4) 368 | output += self.hash(input + C) 369 | counter += 1 370 | return output[:length] 371 | 372 | def assert_int(self, var: int, name: str): 373 | if isinstance(var, int): 374 | return 375 | raise TypeError('%s should be an integer, not %s' % (name, var.__class__)) 376 | 377 | def sign(self, tosign, D, N, emBits=1024): 378 | self.assert_int(tosign, 'message') 379 | self.assert_int(D, 'D') 380 | self.assert_int(N, 'n') 381 | 382 | if tosign < 0: 383 | raise ValueError('Only non-negative numbers are supported') 384 | 385 | if tosign > N: 386 | tosign1 = divmod(tosign, N)[1] 387 | signature = pow(tosign1, D, N) 388 | raise OverflowError("The message %i is too long for n=%i" % (tosign, N)) 389 | 390 | signature = pow(tosign, D, N) 391 | hexsign = self.i2osp(signature, emBits // 8) 392 | return hexsign 393 | 394 | def pss_sign(self, D, N, msghash, salt, emBits=1024): 395 | if isinstance(D, str): 396 | D = unhexlify(D) 397 | D = self.os2ip(D) 398 | if isinstance(N, str): 399 | N = unhexlify(N) 400 | N = self.os2ip(N) 401 | slen = len(salt) 402 | emLen = self.ceil_div(emBits, 8) 403 | inBlock = b"\x00" * 8 + msghash + salt 404 | hhash = self.hash(inBlock) 405 | PSlen = emLen - self.digestLen - slen - 1 - 1 406 | DB = (PSlen * b"\x00") + b"\x01" + salt 407 | rlen = emLen - len(hhash) - 1 408 | dbMask = self.mgf1(hhash, rlen) 409 | maskedDB = bytearray() 410 | for i in range(0, len(dbMask)): 411 | maskedDB.append(dbMask[i] ^ DB[i]) 412 | maskedDB[0] = maskedDB[0] & 0x7F 413 | EM = maskedDB + hhash + b"\xbc" 414 | tosign = self.os2ip(EM) 415 | # EM=hexlify(EM).decode('utf-8') 416 | # tosign = int(EM,16) 417 | return self.sign(tosign, D, N, emBits) 418 | # 6B1EAA2042A5C8DA8B1B4A8320111A70A0CBA65233D1C6E418EF8156E82A8F96BD843F047FF25AB9702A6582C8387298753E628F23448B4580E09CBD2A483C623B888F47C4BD2C5EFF09013C6DFF67DB59BAB3037F0BEE05D5660264D28CC6251631FE75CE106D931A04FA032FEA31259715CE0FAB1AE0E2F8130807AF4019A61B9C060ECE59104F22156FEE8108F17DC80D7C2F8397AFB9780994F7C5A0652F93D1B48010B0B248AB9711235787D797FBA4D10A29BCF09628585D405640A866B15EE9D7526A2703E72A19811EF447F6E5C43F915B3808EBC79EA4BCF78903DBDE32E47E239CFB5F2B5986D0CBBFBE6BACDC29B2ADE006D23D0B90775B1AE4DD 419 | 420 | def ceil_div(self, a, b): 421 | (q, r) = divmod(a, b) 422 | if r: 423 | return q + 1 424 | else: 425 | return q 426 | 427 | def pss_verify(self, e, N, msghash, signature, emBits=1024, salt=None): 428 | if salt is None: 429 | slen = self.digestLen 430 | else: 431 | slen = len(salt) 432 | sig = self.os2ip(signature) 433 | 434 | EM = pow(sig, e, N) 435 | # EM = unhexlify(hex(EM)[2:]) 436 | EM = self.i2osp(EM, emBits // 8) 437 | 438 | emLen = len(signature) 439 | 440 | valBC = EM[-1] 441 | if valBC != 0xbc: 442 | print("[rsa_pss] : 0xbc check failed") 443 | return False 444 | mhash = EM[emLen - self.digestLen - 1:-1] 445 | maskedDB = EM[:emLen - self.digestLen - 1] 446 | 447 | lmask = ~(0xFF >> (8 * emLen + 1 - emBits)) 448 | if EM[0] & lmask: 449 | print("[rsa_pss] : lmask check failed") 450 | return False 451 | 452 | dbMask = self.mgf1(mhash, emLen - self.digestLen - 1) 453 | 454 | DB = bytearray() 455 | for i in range(0, len(dbMask)): 456 | DB.append(dbMask[i] ^ maskedDB[i]) 457 | 458 | TS = bytearray() 459 | TS.append(DB[0] & ~lmask) 460 | TS.extend(DB[1:]) 461 | 462 | PS = (b"\x00" * (emLen - self.digestLen - slen - 2)) + b"\x01" 463 | if TS[:len(PS)] != PS: 464 | print(TS[:len(PS)]) 465 | print(PS) 466 | print("[rsa_pss] : 0x01 check failed") 467 | return False 468 | 469 | if salt is not None: 470 | inBlock = b"\x00" * 8 + msghash + salt 471 | mhash = self.hash(inBlock) 472 | return mhash == mhash 473 | else: 474 | salt = TS[-self.digestLen:] 475 | inBlock = b"\x00" * 8 + msghash + salt 476 | mhash = self.hash(inBlock) 477 | return mhash == mhash 478 | 479 | class hash: 480 | def __init__(self, hashtype="SHA256"): 481 | if hashtype == "SHA1": 482 | self.hash = self.sha1 483 | self.digestLen = 0x14 484 | elif hashtype == "SHA256": 485 | self.hash = self.sha256 486 | self.digestLen = 0x20 487 | elif hashtype == "MD5": 488 | self.hash = self.md5 489 | self.digestLen = 0x10 490 | 491 | def sha1(self, msg): 492 | return hashlib.sha1(msg).digest() 493 | 494 | def sha256(self, msg): 495 | return hashlib.sha256(msg).digest() 496 | 497 | def md5(self, msg): 498 | return hashlib.md5(msg).digest() 499 | -------------------------------------------------------------------------------- /edlclient/Library/hdlc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # (c) B.Kerler 2018-2024 under GPLv3 license 4 | # If you use my code, make sure you refer to my name 5 | # 6 | # !!!!! If you use this code in commercial products, your product is automatically 7 | # GPLv3 and has to be open sourced under GPLv3 as well. !!!!! 8 | 9 | import logging 10 | import time 11 | from struct import unpack 12 | 13 | MAX_PACKET_LEN = 4096 14 | 15 | crcTbl = ( 16 | 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 17 | 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, 18 | 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, 19 | 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 20 | 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, 21 | 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, 22 | 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, 23 | 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, 24 | 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, 25 | 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 26 | 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, 27 | 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, 28 | 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 29 | 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 30 | 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, 31 | 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, 32 | 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, 33 | 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, 34 | 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 35 | 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 36 | 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, 37 | 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 38 | 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, 39 | 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, 40 | 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, 41 | 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, 42 | 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, 43 | 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 44 | 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, 45 | 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, 46 | 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 47 | 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78) 48 | 49 | 50 | def serial16le(data): 51 | out = bytearray() 52 | out.append(data & 0xFF) 53 | out.append((data >> 8) & 0xFF) 54 | return out 55 | 56 | 57 | def serial16(data): 58 | out = bytearray() 59 | out.append((data >> 8) & 0xFF) 60 | out.append(data & 0xFF) 61 | return out 62 | 63 | 64 | def serial32le(data): 65 | out = bytearray() 66 | out += serial16le(data & 0xFFFF) 67 | out += serial16le((data >> 16) & 0xFFFF) 68 | return out 69 | 70 | 71 | def crc16(iv, data): 72 | for byte in data: 73 | iv = ((iv >> 8) & 0xFFFF) ^ crcTbl[(iv ^ byte) & 0xFF] 74 | return ~iv & 0xFFFF 75 | 76 | 77 | def serial32(data): 78 | out = bytearray() 79 | out += serial16((data >> 16) & 0xFFFF) 80 | out += serial16(data & 0xFFFF) 81 | return out 82 | 83 | 84 | def escape(indata): 85 | outdata = bytearray() 86 | for i in range(0, len(indata)): 87 | buf = indata[i] 88 | if buf == 0x7e: 89 | outdata.append(0x7d) 90 | outdata.append(0x5e) 91 | elif buf == 0x7d: 92 | outdata.append(0x7d) 93 | outdata.append(0x5d) 94 | else: 95 | outdata.append(buf) 96 | return outdata 97 | 98 | 99 | def unescape(indata): 100 | mescape = False 101 | out = bytearray() 102 | for buf in indata: 103 | if mescape: 104 | if buf == 0x5e: 105 | out.append(0x7e) 106 | elif buf == 0x5d: 107 | out.append(0x7d) 108 | else: 109 | logging.error("Fatal error unescaping buffer!") 110 | return None 111 | mescape = False 112 | else: 113 | if buf == 0x7d: 114 | mescape = True 115 | else: 116 | out.append(buf) 117 | if len(out) == 0: 118 | return None 119 | return out 120 | 121 | 122 | def convert_cmdbuf(indata): 123 | crc16val = crc16(0xFFFF, indata) 124 | indata.extend(bytearray(serial16le(crc16val))) 125 | outdata = escape(indata) 126 | outdata.append(0x7E) 127 | return outdata 128 | 129 | 130 | class hdlc: 131 | def __init__(self, cdc): 132 | self.cdc = cdc 133 | self.programmer = None 134 | self.timeout = 1500 135 | 136 | def receive_reply(self, timeout=None): 137 | replybuf = bytearray() 138 | if timeout is None: 139 | timeout = self.timeout 140 | tmp = self.cdc.read(timeout=timeout) 141 | if tmp == bytearray(): 142 | return b"" 143 | if tmp == b"": 144 | return b"" 145 | retry = 0 146 | while tmp[-1] != 0x7E: 147 | time.sleep(0.01) 148 | tmp += self.cdc.read(timeout=timeout) 149 | retry += 1 150 | if retry > 5: 151 | break 152 | replybuf.extend(tmp) 153 | data = unescape(replybuf) 154 | # print(hexlify(data)) 155 | if len(data) > 3: 156 | if data[0] == 0x7E: 157 | data = data[1:] 158 | crc16val = crc16(0xFFFF, data[:-3]) 159 | reccrc = int(data[-3]) + (int(data[-2]) << 8) 160 | if crc16val != reccrc: 161 | return -1 162 | else: 163 | time.sleep(0.01) 164 | data = self.cdc.read(timeout=timeout) 165 | if len(data) > 3: 166 | crc16val = crc16(0xFFFF, data[:-3]) 167 | reccrc = int(data[-3]) + (int(data[-2]) << 8) 168 | if crc16val != reccrc: 169 | return -1 170 | return data 171 | return data[:-3] 172 | 173 | def receive_reply_nocrc(self, timeout=None): 174 | replybuf = bytearray() 175 | if timeout is None: 176 | timeout = self.timeout 177 | tmp = self.cdc.read(timeout=timeout) 178 | if tmp == bytearray(): 179 | return b"" 180 | if tmp == b"": 181 | return b"" 182 | retry = 0 183 | while tmp[-1] != 0x7E: 184 | # time.sleep(0.05) 185 | tmp += self.cdc.read(timeout=timeout) 186 | retry += 1 187 | if retry > 5: 188 | break 189 | replybuf.extend(tmp) 190 | data = unescape(replybuf) 191 | # print(hexlify(data)) 192 | if len(data) > 3: 193 | # crc16val = self.crc16(0xFFFF, data[:-3]) 194 | # reccrc = int(data[-3]) + (int(data[-2]) << 8) 195 | return data[:-3] 196 | else: 197 | time.sleep(0.5) 198 | data = self.cdc.read(timeout=timeout) 199 | if len(data) > 3: 200 | # crc16val = self.crc16(0xFFFF, data[:-3]) 201 | # reccrc = int(data[-3]) + (int(data[-2]) << 8) 202 | return data[:-3] 203 | else: 204 | return data 205 | 206 | def send_unframed_buf(self, outdata, prefixflag): 207 | # ttyflush() 208 | if prefixflag: 209 | tmp = bytearray() 210 | tmp.append(0x7E) 211 | tmp.extend(outdata) 212 | outdata = tmp 213 | return self.cdc.write(outdata[:MAX_PACKET_LEN]) 214 | # FlushFileBuffers(ser) 215 | 216 | def send_cmd_base(self, outdata, prefixflag, nocrc=False): 217 | if isinstance(outdata, str): 218 | outdata = bytes(outdata, 'utf-8') 219 | packet = convert_cmdbuf(bytearray(outdata)) 220 | self.cdc.flush() 221 | if self.send_unframed_buf(packet, prefixflag): 222 | if nocrc: 223 | return self.receive_reply_nocrc() 224 | else: 225 | return self.receive_reply() 226 | return b"" 227 | 228 | def send_cmd(self, outdata, nocrc=False): 229 | return self.send_cmd_base(outdata, 1, nocrc) 230 | 231 | def send_cmd_np(self, outdata, nocrc=False): 232 | return self.send_cmd_base(outdata, 0, nocrc) 233 | 234 | def show_errpacket(self, descr, pktbuf): 235 | if len(pktbuf) == 0: 236 | return 237 | logging.error("Error: %s " % descr) 238 | if pktbuf[1] == 0x0e: 239 | pktbuf[-4] = 0 240 | # puts(pktbuf+2) 241 | ret = self.receive_reply() 242 | errorcode = unpack(" {str(e)}") 72 | continue 73 | return self.loaderdb 74 | 75 | def convertmsmid(self, msmid): 76 | msmiddb = [] 77 | if int(msmid, 16) & 0xFF == 0xe1 or msmid == '00000000': 78 | return [msmid] 79 | socid = int(msmid, 16) >> 16 80 | if socid in sochw: 81 | names = sochw[socid].split(",") 82 | for name in names: 83 | for ids in msmids: 84 | if msmids[ids] == name: 85 | rmsmid = hex(ids)[2:].lower() 86 | while len(rmsmid) < 8: 87 | rmsmid = '0' + rmsmid 88 | msmiddb.append(rmsmid) 89 | return msmiddb 90 | -------------------------------------------------------------------------------- /edlclient/Library/memparse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # (c) B.Kerler 2018-2024 under GPLv3 license 4 | # If you use my code, make sure you refer to my name 5 | # 6 | # !!!!! If you use this code in commercial products, your product is automatically 7 | # GPLv3 and has to be open sourced under GPLv3 as well. !!!!! 8 | 9 | import argparse 10 | 11 | import pt 12 | import pt64 13 | 14 | 15 | def pt64_walk(data, ttbr, tnsz, levels=3): 16 | print("Dumping page tables (levels=%d)" % levels) 17 | print("First level (ptbase = %016x)" % ttbr) 18 | print("---------------------------------------------") 19 | fl = data[ttbr - ttbr:ttbr - ttbr + 0x1000] 20 | 21 | if levels <= 1: 22 | return 23 | 24 | for (va, fle) in pt64.parse_pt(fl, 0, tnsz, 1): 25 | if "TABLE" in str(fle): 26 | print("Second level (ptbase = %016x)" % fle.output) 27 | print("---------------------------------------------") 28 | 29 | sl = data[fle.output - ttbr:fle.output - ttbr + 0x4000] 30 | sl = pt64.parse_pt(sl, va, tnsz, 2) 31 | 32 | if levels <= 2: 33 | continue 34 | 35 | for (mva, sle) in sl: 36 | if "TABLE" in str(sle): 37 | print("Third level (ptbase = %016x)" % sle.output) 38 | print("---------------------------------------------") 39 | tl = data[sle.output - ttbr:sle.output - ttbr + 0x1000] 40 | pt64.parse_pt(tl, mva, tnsz, 3) 41 | 42 | 43 | def pt32_walk(data, ttbr, skip): 44 | print("First level (va = %08x)" % ttbr) 45 | print("---------------------------------------------") 46 | fl = data[ttbr - ttbr:ttbr - ttbr + 0x4000] 47 | 48 | i = 0 49 | for (va, fl) in pt.parse_pt(fl): 50 | i += 1 51 | if i <= skip: 52 | continue 53 | if isinstance(fl, pt.pt_desc): 54 | print("") 55 | print("Second level (va = %08x)" % va) 56 | print("---------------------------------------------") 57 | sldata = data[fl.coarse_base - ttbr:fl.coarse_base - ttbr + 0x400] 58 | pt.parse_spt(sldata, va) 59 | 60 | 61 | def main(): 62 | parser = argparse.ArgumentParser( 63 | prog="memparse", 64 | usage="python memparse.py -arch <32,64> -in -mem ", 65 | formatter_class=argparse.RawTextHelpFormatter) 66 | parser.add_argument('-in', '--in', dest='infile', help='memory dump', default="") 67 | parser.add_argument('-arch', '--arch', dest='arch', help='architecture=32,64', default="32") 68 | parser.add_argument('-mem', '--mem', dest='mem', help='memoryoffset', default="0x200000") 69 | args = parser.parse_args() 70 | if args.infile == "": 71 | print("You need to add an -in [memorydump filename]") 72 | return 73 | 74 | with open(args.infile, "rb") as rf: 75 | data = rf.read() 76 | if args.arch == "32": 77 | pt32_walk(data, int(args.mem, 16), False) 78 | else: 79 | pt64_walk(data, int(args.mem, 16), 0, 3) 80 | 81 | 82 | main() 83 | -------------------------------------------------------------------------------- /edlclient/Library/pt.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | 4 | def get_n(x): 5 | return int(x[6:8] + x[4:6] + x[2:4] + x[0:2], 16) 6 | 7 | 8 | def parse_pt(data): 9 | va = 0 10 | entries = [] 11 | while va < len(data): 12 | entry = struct.unpack(" 1: 61 | return sld_xsp(msld) 62 | 63 | return "UNSUPPORTED" 64 | 65 | 66 | class descriptor(object): 67 | def __init__(self, mfld): 68 | pass 69 | 70 | def get_name(self): 71 | pass 72 | 73 | def __repr__(self): 74 | s = "%8s " % self.get_name() 75 | for attr, value in self.__dict__.items(): 76 | try: 77 | s += "%s=%s, " % (attr, hex(value)) 78 | except: 79 | s += "%s=%s, " % (attr, value) 80 | 81 | return s 82 | 83 | 84 | class fld(descriptor): 85 | pass 86 | 87 | 88 | class fault_desc(fld): 89 | 90 | def get_name(self): 91 | return "FAULT" 92 | 93 | 94 | class reserved_desc(fld): 95 | 96 | def get_name(self): 97 | return "RESERVED" 98 | 99 | 100 | class pt_desc(fld): 101 | 102 | def __init__(self, desc): 103 | self.coarse_base = (desc >> 10) << 10 104 | self.p = (desc >> 9) & 1 105 | self.domain = (desc >> 5) & 15 106 | self.sbz1 = (desc >> 4) & 1 107 | self.ns = (desc >> 3) & 1 108 | self.sbz2 = (desc >> 2) & 1 109 | 110 | def get_name(self): 111 | return "PT" 112 | 113 | 114 | class section_desc(fld): 115 | def __init__(self, desc): 116 | self.section_base = (desc >> 20) << 20 117 | self.ns = (desc >> 19) & 1 118 | self.zero = ns = (desc >> 18) & 1 119 | self.ng = (desc >> 17) & 1 120 | self.s = (desc >> 16) & 1 121 | self.apx = (desc >> 15) & 1 122 | self.tex = (desc >> 12) & 7 123 | self.ap = (desc >> 10) & 3 124 | self.p = (desc >> 9) & 1 125 | self.domain = (desc >> 5) & 15 126 | self.nx = (desc >> 4) & 1 127 | self.c = (desc >> 3) & 1 128 | self.b = (desc >> 2) & 1 129 | 130 | def get_name(self): 131 | return "SECTION" 132 | 133 | 134 | class sld(descriptor): 135 | pass 136 | 137 | 138 | class sld_lp(sld): 139 | 140 | def __init__(self, desc): 141 | self.page_base = (desc >> 16) << 16 142 | self.nx = (desc >> 15) & 1 143 | self.tex = (desc >> 12) & 7 144 | self.ng = (desc >> 11) & 1 145 | self.s = (desc >> 10) & 1 146 | self.apx = (desc >> 9) & 1 147 | self.sbz = (desc >> 6) & 7 148 | self.ap = (desc >> 4) & 3 149 | self.c = (desc >> 3) & 1 150 | self.b = (desc >> 2) & 1 151 | 152 | def get_name(self): 153 | return "LARGEPAGE" 154 | 155 | 156 | class sld_xsp(sld): 157 | 158 | def __init__(self, desc): 159 | self.desc = desc 160 | self.page_base = (desc >> 12) << 12 161 | self.ng = (desc >> 11) & 1 162 | self.s = (desc >> 10) & 1 163 | self.apx = (desc >> 9) & 1 164 | self.tex = (desc >> 6) & 7 165 | self.ap = (desc >> 4) & 3 166 | self.c = (desc >> 3) & 1 167 | self.b = (desc >> 2) & 1 168 | self.nx = desc & 1 169 | 170 | def get_name(self): 171 | return "XSMALLPAGE" 172 | -------------------------------------------------------------------------------- /edlclient/Library/pt64.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | """ 4 | only supports 4KB granule w/ 25<=TnSZ<=33 5 | https://armv8-ref.codingbelief.com/en/chapter_d4/d42_7_the_algorithm_for_finding_the_translation_table_entries.html 6 | 7 | """ 8 | 9 | 10 | def get_level_index(va, level): 11 | if level == 1: 12 | return (va >> 30) & 0x3F 13 | 14 | if level == 2: 15 | return (va >> 21) & 0x1FF 16 | 17 | if level == 3: 18 | return (va >> 12) & 0x1FF 19 | 20 | raise NotImplementedError() 21 | 22 | 23 | def get_level_bits(level, tnsz): 24 | if level == 1: 25 | return 37 - tnsz + 26 + 1 - 30 26 | 27 | if level == 2: 28 | return 9 29 | 30 | if level == 3: 31 | return 9 32 | 33 | raise NotImplementedError() 34 | 35 | 36 | def get_level_size(tnsz, level): 37 | return 2 ** get_level_bits(level, tnsz) * 8 38 | 39 | 40 | def get_va_for_level(va, index, level): 41 | if level == 1: 42 | return va + (index << 30) 43 | 44 | if level == 2: 45 | return va + (index << 21) 46 | 47 | if level == 3: 48 | return va + (index << 12) 49 | 50 | return va 51 | 52 | 53 | def parse_pt(data, base, tnsz, level=1): 54 | i = 0 55 | entries = [] 56 | while i < min(len(data), get_level_size(tnsz, level)): 57 | mentry = struct.unpack("> 63 114 | self.apx = (desc >> 61) & 3 115 | self.xn = (desc >> 60) & 1 116 | self.pxn = (desc >> 59) & 1 117 | self.attrindex = (desc >> 2) & 7 118 | self.ns = (desc >> 5) & 1 119 | self.ap = (desc >> 6) & 3 120 | self.sh = (desc >> 8) & 3 121 | self.af = (desc >> 10) & 1 122 | self.nG = (desc >> 11) & 1 123 | 124 | 125 | class entry4k(entry): 126 | def __init__(self, desc, level): 127 | entry.__init__(self, desc, level) 128 | self.output = ((desc & 0xFFFFFFFFFFFF) >> 12) << 12 129 | 130 | 131 | class fault_entry(fld): 132 | 133 | def get_name(self): 134 | return "FAULT" 135 | 136 | 137 | class block_entry4k(entry4k): 138 | 139 | def __init__(self, desc, level): 140 | entry4k.__init__(self, desc, level) 141 | # shift = 39-9*level 142 | # self.output = ((desc & 0xFFFFFFFFFFFFL) >> shift) << shift 143 | 144 | def get_name(self): 145 | return "BLOCK4" 146 | 147 | 148 | class table_entry4k(entry4k): 149 | 150 | def __init__(self, desc, level): 151 | entry4k.__init__(self, desc, level) 152 | 153 | def get_name(self): 154 | return "TABLE4" 155 | -------------------------------------------------------------------------------- /edlclient/Library/sahara_defs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # (c) B.Kerler 2018-2024 under GPLv3 license 4 | # If you use my code, make sure you refer to my name 5 | # 6 | # !!!!! If you use this code in commercial products, your product is automatically 7 | # GPLv3 and has to be open sourced under GPLv3 as well. !!!!! 8 | 9 | from edlclient.Library.utils import structhelper_io 10 | from io import BytesIO 11 | 12 | SAHARA_VERSION = 2 13 | SAHARA_MIN_VERSION = 1 14 | 15 | 16 | class DataError(Exception): 17 | pass 18 | 19 | 20 | class cmd_t: 21 | SAHARA_HELLO_REQ = 0x1 22 | SAHARA_HELLO_RSP = 0x2 23 | SAHARA_READ_DATA = 0x3 24 | SAHARA_END_TRANSFER = 0x4 25 | SAHARA_DONE_REQ = 0x5 26 | SAHARA_DONE_RSP = 0x6 27 | SAHARA_RESET_REQ = 0x7 28 | SAHARA_RESET_RSP = 0x8 29 | SAHARA_MEMORY_DEBUG = 0x9 30 | SAHARA_MEMORY_READ = 0xA 31 | SAHARA_CMD_READY = 0xB 32 | SAHARA_SWITCH_MODE = 0xC 33 | SAHARA_EXECUTE_REQ = 0xD 34 | SAHARA_EXECUTE_RSP = 0xE 35 | SAHARA_EXECUTE_DATA = 0xF 36 | SAHARA_64BIT_MEMORY_DEBUG = 0x10 37 | SAHARA_64BIT_MEMORY_READ = 0x11 38 | SAHARA_64BIT_MEMORY_READ_DATA = 0x12 39 | SAHARA_RESET_STATE_MACHINE_ID = 0x13 40 | 41 | 42 | class cmd_t_version: 43 | SAHARA_HELLO_REQ = 0x1 44 | SAHARA_HELLO_RSP = 1 45 | SAHARA_READ_DATA = 1 46 | SAHARA_END_TRANSFER = 1 47 | SAHARA_DONE_REQ = 1 48 | SAHARA_DONE_RSP = 1 49 | SAHARA_RESET_REQ = 1 50 | SAHARA_RESET_RSP = 1 51 | SAHARA_MEMORY_DEBUG = 2 52 | SAHARA_MEMORY_READ = 2 53 | SAHARA_CMD_READY = 2 54 | SAHARA_SWITCH_MODE = 2 55 | SAHARA_EXECUTE_REQ = 2 56 | SAHARA_EXECUTE_RSP = 2 57 | SAHARA_EXECUTE_DATA = 2 58 | SAHARA_64BIT_MEMORY_DEBUG = 2 59 | SAHARA_64BIT_MEMORY_READ = 2 60 | SAHARA_64BIT_MEMORY_READ_DATA = 2 61 | SAHARA_RESET_STATE_MACHINE_ID = 2 62 | 63 | 64 | class exec_cmd_t: 65 | SAHARA_EXEC_CMD_NOP = 0x00 66 | SAHARA_EXEC_CMD_SERIAL_NUM_READ = 0x01 67 | SAHARA_EXEC_CMD_MSM_HW_ID_READ = 0x02 68 | SAHARA_EXEC_CMD_OEM_PK_HASH_READ = 0x03 69 | SAHARA_EXEC_CMD_SWITCH_TO_DMSS_DLOAD = 0x04 70 | SAHARA_EXEC_CMD_SWITCH_TO_STREAM_DLOAD = 0x05 71 | SAHARA_EXEC_CMD_READ_DEBUG_DATA = 0x06 72 | SAHARA_EXEC_CMD_GET_SOFTWARE_VERSION_SBL = 0x07 73 | SAHARA_EXEC_CMD_GET_COMMAND_ID_LIST = 0x08 74 | SAHARA_EXEC_CMD_GET_TRAINING_DATA = 0x09 75 | 76 | 77 | class sahara_mode_t: 78 | SAHARA_MODE_IMAGE_TX_PENDING = 0x0 79 | SAHARA_MODE_IMAGE_TX_COMPLETE = 0x1 80 | SAHARA_MODE_MEMORY_DEBUG = 0x2 81 | SAHARA_MODE_COMMAND = 0x3 82 | 83 | 84 | class status_t: 85 | SAHARA_STATUS_SUCCESS = 0x00 # Invalid command received in current state 86 | SAHARA_NAK_INVALID_CMD = 0x01 # Protocol mismatch between host and target 87 | SAHARA_NAK_PROTOCOL_MISMATCH = 0x02 # Invalid target protocol version 88 | SAHARA_NAK_INVALID_TARGET_PROTOCOL = 0x03 # Invalid host protocol version 89 | SAHARA_NAK_INVALID_HOST_PROTOCOL = 0x04 # Invalid packet size received 90 | SAHARA_NAK_INVALID_PACKET_SIZE = 0x05 # Unexpected image ID received 91 | SAHARA_NAK_UNEXPECTED_IMAGE_ID = 0x06 # Invalid image header size received 92 | SAHARA_NAK_INVALID_HEADER_SIZE = 0x07 # Invalid image data size received 93 | SAHARA_NAK_INVALID_DATA_SIZE = 0x08 # Invalid image type received 94 | SAHARA_NAK_INVALID_IMAGE_TYPE = 0x09 # Invalid tranmission length 95 | SAHARA_NAK_INVALID_TX_LENGTH = 0x0A # Invalid reception length 96 | SAHARA_NAK_INVALID_RX_LENGTH = 0x0B # General transmission or reception error 97 | SAHARA_NAK_GENERAL_TX_RX_ERROR = 0x0C # Error while transmitting READ_DATA packet 98 | SAHARA_NAK_READ_DATA_ERROR = 0x0D # Cannot receive specified number of program headers 99 | SAHARA_NAK_UNSUPPORTED_NUM_PHDRS = 0x0E # Invalid data length received for program headers 100 | SAHARA_NAK_INVALID_PDHR_SIZE = 0x0F # Multiple shared segments found in ELF image 101 | SAHARA_NAK_MULTIPLE_SHARED_SEG = 0x10 # Uninitialized program header location 102 | SAHARA_NAK_UNINIT_PHDR_LOC = 0x11 # Invalid destination address 103 | SAHARA_NAK_INVALID_DEST_ADDR = 0x12 # Invalid data size received in image header 104 | SAHARA_NAK_INVALID_IMG_HDR_DATA_SIZE = 0x13 # Invalid ELF header received 105 | SAHARA_NAK_INVALID_ELF_HDR = 0x14 # Unknown host error received in HELLO_RESP 106 | SAHARA_NAK_UNKNOWN_HOST_ERROR = 0x15 # Timeout while receiving data 107 | SAHARA_NAK_TIMEOUT_RX = 0x16 # Timeout while transmitting data 108 | SAHARA_NAK_TIMEOUT_TX = 0x17 # Invalid mode received from host 109 | SAHARA_NAK_INVALID_HOST_MODE = 0x18 # Invalid memory read access 110 | SAHARA_NAK_INVALID_MEMORY_READ = 0x19 # Host cannot handle read data size requested 111 | SAHARA_NAK_INVALID_DATA_SIZE_REQUEST = 0x1A # Memory debug not supported 112 | SAHARA_NAK_MEMORY_DEBUG_NOT_SUPPORTED = 0x1B # Invalid mode switch 113 | SAHARA_NAK_INVALID_MODE_SWITCH = 0x1C # Failed to execute command 114 | SAHARA_NAK_CMD_EXEC_FAILURE = 0x1D # Invalid parameter passed to command execution 115 | SAHARA_NAK_EXEC_CMD_INVALID_PARAM = 0x1E # Unsupported client command received 116 | SAHARA_NAK_EXEC_CMD_UNSUPPORTED = 0x1F # Invalid client command received for data response 117 | SAHARA_NAK_EXEC_DATA_INVALID_CLIENT_CMD = 0x20 # Failed to authenticate hash table 118 | SAHARA_NAK_HASH_TABLE_AUTH_FAILURE = 0x21 # Failed to verify hash for a given segment of ELF image 119 | SAHARA_NAK_HASH_VERIFICATION_FAILURE = 0x22 # Failed to find hash table in ELF image 120 | SAHARA_NAK_HASH_TABLE_NOT_FOUND = 0x23 # Target failed to initialize 121 | SAHARA_NAK_TARGET_INIT_FAILURE = 0x24 # Failed to authenticate generic image 122 | SAHARA_NAK_IMAGE_AUTH_FAILURE = 0x25 # Invalid ELF hash table size. Too bit or small. 123 | SAHARA_NAK_INVALID_IMG_HASH_TABLE_SIZE = 0x26 124 | SAHARA_NAK_ENUMERATION_FAILURE = 0x27 125 | SAHARA_NAK_HW_BULK_TRANSFER_ERROR = 0x28 126 | SAHARA_NAK_MAX_CODE = 0x7FFFFFFF # To ensure 32-bits wide */ 127 | 128 | 129 | ErrorDesc = { 130 | 0x00: "Invalid command received in current state", 131 | 0x01: "Protocol mismatch between host and target", 132 | 0x02: "Invalid target protocol version", 133 | 0x03: "Invalid host protocol version", 134 | 0x04: "Invalid packet size received", 135 | 0x05: "Unexpected image ID received", 136 | 0x06: "Invalid image header size received", 137 | 0x07: "Invalid image data size received", 138 | 0x08: "Invalid image type received", 139 | 0x09: "Invalid tranmission length", 140 | 0x0A: "Invalid reception length", 141 | 0x0B: "General transmission or reception error", 142 | 0x0C: "Error while transmitting READ_DATA packet", 143 | 0x0D: "Cannot receive specified number of program headers", 144 | 0x0E: "Invalid data length received for program headers", 145 | 0x0F: "Multiple shared segments found in ELF image", 146 | 0x10: "Uninitialized program header location", 147 | 0x11: "Invalid destination address", 148 | 0x12: "Invalid data size received in image header", 149 | 0x13: "Invalid ELF header received", 150 | 0x14: "Unknown host error received in HELLO_RESP", 151 | 0x15: "Timeout while receiving data", 152 | 0x16: "Timeout while transmitting data", 153 | 0x17: "Invalid mode received from host", 154 | 0x18: "Invalid memory read access", 155 | 0x19: "Host cannot handle read data size requested", 156 | 0x1A: "Memory debug not supported", 157 | 0x1B: "Invalid mode switch", 158 | 0x1C: "Failed to execute command", 159 | 0x1D: "Invalid parameter passed to command execution", 160 | 0x1E: "Unsupported client command received", 161 | 0x1F: "Invalid client command received for data response", 162 | 0x20: "Failed to authenticate hash table", 163 | 0x21: "Failed to verify hash for a given segment of ELF image", 164 | 0x22: "Failed to find hash table in ELF image", 165 | 0x23: "Target failed to initialize", 166 | 0x24: "Failed to authenticate generic image", 167 | 0x25: "Invalid ELF hash table size. Too bit or small.", 168 | 0x26: "Invalid IMG Hash Table Size", 169 | 0x27: "Enumeration failed", 170 | 0x28: "Hardware Bulk transfer error" 171 | } 172 | 173 | 174 | class CommandHandler: 175 | 176 | def pkt_hello_req(self, data): 177 | if len(data) < 0xC * 0x4: 178 | raise DataError 179 | st = structhelper_io(BytesIO(data)) 180 | 181 | class req: 182 | cmd = st.dword() 183 | len = st.dword() 184 | version = st.dword() 185 | version_supported = st.dword() 186 | cmd_packet_length = st.dword() 187 | mode = st.dword() 188 | reserved1 = st.dword() 189 | reserved2 = st.dword() 190 | reserved3 = st.dword() 191 | reserved4 = st.dword() 192 | reserved5 = st.dword() 193 | reserved6 = st.dword() 194 | 195 | return req 196 | 197 | def pkt_cmd_hdr(self, data): 198 | if len(data) < 2 * 4: 199 | raise DataError 200 | st = structhelper_io(BytesIO(data)) 201 | 202 | class req: 203 | cmd = st.dword() 204 | len = st.dword() 205 | 206 | return req 207 | 208 | def pkt_read_data(self, data): 209 | if len(data) < 0x5 * 0x4: 210 | raise DataError 211 | st = structhelper_io(BytesIO(data)) 212 | 213 | class req: 214 | cmd = st.dword() 215 | len = st.dword() 216 | image_id = st.dword() 217 | data_offset = st.dword() 218 | data_len = st.dword() 219 | 220 | return req 221 | 222 | def pkt_read_data_64(self, data): 223 | if len(data) < 0x8 + 0x3 * 0x8: 224 | raise DataError 225 | st = structhelper_io(BytesIO(data)) 226 | 227 | class req: 228 | cmd = st.dword() 229 | len = st.dword() 230 | image_id = st.qword() 231 | data_offset = st.qword() 232 | data_len = st.qword() 233 | 234 | return req 235 | 236 | def pkt_memory_debug(self, data): 237 | if len(data) < 0x8 + 0x2 * 0x4: 238 | raise DataError 239 | st = structhelper_io(BytesIO(data)) 240 | 241 | class req: 242 | cmd = st.dword() 243 | len = st.dword() 244 | memory_table_addr = st.dword() 245 | memory_table_length = st.dword() 246 | 247 | return req 248 | 249 | def pkt_memory_debug_64(self, data): 250 | if len(data) < 0x8 + 0x2 * 0x8: 251 | raise DataError 252 | st = structhelper_io(BytesIO(data)) 253 | 254 | class req: 255 | cmd = st.dword() 256 | len = st.dword() 257 | memory_table_addr = st.qword() 258 | memory_table_length = st.qword() 259 | 260 | return req 261 | 262 | def pkt_execute_rsp_cmd(self, data): 263 | if len(data) < 0x4 * 0x4: 264 | raise DataError 265 | st = structhelper_io(BytesIO(data)) 266 | 267 | class req: 268 | cmd = st.dword() 269 | len = st.dword() 270 | client_cmd = st.dword() 271 | data_len = st.dword() 272 | 273 | return req 274 | 275 | def pkt_image_end(self, data): 276 | if len(data) < 0x4 * 0x4: 277 | raise DataError 278 | st = structhelper_io(BytesIO(data)) 279 | 280 | class req: 281 | cmd = st.dword() 282 | len = st.dword() 283 | image_id = st.dword() 284 | image_tx_status = st.dword() 285 | 286 | return req 287 | 288 | def pkt_done(self, data): 289 | if len(data) < 0x3 * 4: 290 | raise DataError 291 | st = structhelper_io(BytesIO(data)) 292 | 293 | class req: 294 | cmd = st.dword() 295 | len = st.dword() 296 | image_tx_status = st.dword() 297 | 298 | return req 299 | 300 | def pkt_info(self, data): 301 | if len(data) < 0x3 * 4 + 0x20: 302 | raise DataError 303 | st = structhelper_io(BytesIO(data)) 304 | 305 | class req: 306 | serial = st.dword() 307 | msm_id = st.dword() 308 | pk_hash = st.bytes(32) 309 | pbl_sw = st.dword() 310 | 311 | return req 312 | 313 | def parttbl(self, data): 314 | if len(data) < (0x3 * 4) + 20 + 20: 315 | raise DataError 316 | st = structhelper_io(BytesIO(data)) 317 | 318 | class req: 319 | save_pref = st.dword() 320 | mem_base = st.dword() 321 | length = st.dword() 322 | desc = st.string(20) 323 | filename = st.string(20) 324 | 325 | return req 326 | 327 | def parttbl_64bit(self, data): 328 | if len(data) < (0x3 * 8) + 20 + 20: 329 | raise DataError 330 | st = structhelper_io(BytesIO(data)) 331 | 332 | class req: 333 | save_pref = st.qword() 334 | mem_base = st.qword() 335 | length = st.qword() 336 | desc = st.string(20) 337 | filename = st.string(20) 338 | 339 | return req 340 | -------------------------------------------------------------------------------- /edlclient/Library/sparse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # (c) B.Kerler 2018-2024 under GPLv3 license 4 | # If you use my code, make sure you refer to my name 5 | # 6 | # !!!!! If you use this code in commercial products, your product is automatically 7 | # GPLv3 and has to be open sourced under GPLv3 as well. !!!!! 8 | import inspect 9 | import logging 10 | import os 11 | import sys 12 | from queue import Queue 13 | from struct import unpack 14 | 15 | current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) 16 | parent_dir = os.path.dirname(current_dir) 17 | sys.path.insert(0, parent_dir) 18 | from edlclient.Library.utils import LogBase, print_progress 19 | 20 | MAX_STORE_SIZE = 1024 * 1024 * 1024 * 2 # 2 GBs 21 | 22 | 23 | class QCSparse(metaclass=LogBase): 24 | def __init__(self, filename, loglevel): 25 | self.rf = open(filename, 'rb') 26 | self.data = Queue() 27 | self.__logger = self.__logger 28 | self.offset = 0 29 | self.tmpdata = bytearray() 30 | self.__logger.setLevel(loglevel) 31 | 32 | self.major_version = None 33 | self.minor_version = None 34 | self.file_hdr_sz = None 35 | self.chunk_hdr_sz = None 36 | self.blk_sz = None 37 | self.total_blks = None 38 | self.total_chunks = None 39 | self.image_checksum = None 40 | 41 | self.tmp_offset = 0 42 | 43 | self.info = self.__logger.info 44 | self.debug = self.__logger.debug 45 | self.error = self.__logger.error 46 | self.warning = self.__logger.warning 47 | 48 | if loglevel == logging.DEBUG: 49 | logfilename = "log.txt" 50 | fh = logging.FileHandler(logfilename) 51 | self.__logger.addHandler(fh) 52 | 53 | def readheader(self): 54 | buf = self.rf.read(0x1C) 55 | if len(buf) != 28: 56 | return False 57 | header = unpack("= MAX_STORE_SIZE: 180 | self.tmpdata = self.tmpdata[self.tmp_offset:] 181 | self.tmp_offset = 0 182 | if length is None: 183 | return self.unsparse() 184 | if (self.tmp_offset + length) <= len(self.tmpdata): 185 | tdata = self.tmpdata[self.tmp_offset: self.tmp_offset + length] 186 | self.tmp_offset += length 187 | return tdata 188 | while (self.tmp_offset + length) > len(self.tmpdata): 189 | self.tmpdata.extend(self.unsparse()) 190 | if (self.tmp_offset + length) <= len(self.tmpdata): 191 | tdata = self.tmpdata[self.tmp_offset: self.tmp_offset + length] 192 | self.tmp_offset += length 193 | return tdata 194 | 195 | 196 | if __name__ == "__main__": 197 | if len(sys.argv) < 3: 198 | print("./sparse.py ") 199 | sys.exit() 200 | sp = QCSparse(sys.argv[1], logging.INFO) 201 | if sp.readheader(): 202 | print("Extracting sectors to " + sys.argv[2]) 203 | with open(sys.argv[2], "wb") as wf: 204 | """ 205 | old=0 206 | while sp.offsetold: 209 | print_progress(prog, 100, prefix='Progress:', suffix='Complete', bar_length=50) 210 | old=prog 211 | data=sp.read() 212 | if data==b"" or data==-1: 213 | break 214 | wf.write(data) 215 | if len(sp.tmpdata)>0: 216 | wf.write(sp.tmpdata) 217 | sp.tmpdata=bytearray() 218 | print_progress(100, 100, prefix='Progress:', suffix='Complete', bar_length=50) 219 | """ 220 | 221 | fsize = sp.getsize() 222 | SECTOR_SIZE_IN_BYTES = 4096 223 | num_partition_sectors = 5469709 224 | MaxPayloadSizeToTargetInBytes = 0x200000 225 | bytesToWrite = SECTOR_SIZE_IN_BYTES * num_partition_sectors 226 | total = SECTOR_SIZE_IN_BYTES * num_partition_sectors 227 | old = 0 228 | pos = 0 229 | while fsize > 0: 230 | wlen = MaxPayloadSizeToTargetInBytes // SECTOR_SIZE_IN_BYTES * SECTOR_SIZE_IN_BYTES 231 | if fsize < wlen: 232 | wlen = fsize 233 | wdata = sp.read(wlen) 234 | bytesToWrite -= wlen 235 | fsize -= wlen 236 | pos += wlen 237 | pv = wlen % SECTOR_SIZE_IN_BYTES 238 | if pv != 0: 239 | filllen = (wlen // SECTOR_SIZE_IN_BYTES * SECTOR_SIZE_IN_BYTES) + \ 240 | SECTOR_SIZE_IN_BYTES 241 | wdata += b"\x00" * (filllen - wlen) 242 | wlen = len(wdata) 243 | 244 | wf.write(wdata) 245 | 246 | prog = round(float(pos) / float(total) * float(100), 1) 247 | if prog > old: 248 | print_progress(prog, 100, prefix='Progress:', suffix='Complete (Sector %d)' 249 | % (pos // SECTOR_SIZE_IN_BYTES), 250 | bar_length=50) 251 | 252 | print("Done.") 253 | -------------------------------------------------------------------------------- /edlclient/Library/streaming_defs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # (c) B.Kerler 2018-2024 under GPLv3 license 4 | # If you use my code, make sure you refer to my name 5 | # 6 | # !!!!! If you use this code in commercial products, your product is automatically 7 | # GPLv3 and has to be open sourced under GPLv3 as well. !!!!! 8 | class open_mode_type: 9 | OPEN_MODE_NONE = 0x00 # Not opened yet 10 | OPEN_BOOTLOADER = 0x01 # Bootloader Image 11 | OPEN_BOOTABLE = 0x02 # Bootable Image 12 | OPEN_CEFS = 0x03 # CEFS Image 13 | OPEN_MODE_FACTORY = 0x04 # Factory Image 14 | 15 | 16 | class open_multi_mode_type: 17 | OPEN_MULTI_MODE_NONE = 0x00 # Not opened yet 18 | OPEN_MULTI_MODE_PBL = 0x01 # Primary Boot Loader 19 | OPEN_MULTI_MODE_QCSBLHDCFG = 0x02 # QC 2ndary Boot Loader Header and Config Data 20 | OPEN_MULTI_MODE_QCSBL = 0x03 # QC 2ndary Boot Loader 21 | OPEN_MULTI_MODE_OEMSBL = 0x04 # OEM 2ndary Boot Loader 22 | OPEN_MULTI_MODE_AMSS = 0x05 # AMSS modem executable 23 | OPEN_MULTI_MODE_APPS = 0x06 # APPS executable 24 | OPEN_MULTI_MODE_OBL = 0x07 # OTP Boot Loader 25 | OPEN_MULTI_MODE_FOTAUI = 0x08 # FOTA UI binarh 26 | OPEN_MULTI_MODE_CEFS = 0x09 # Modem CEFS image 27 | OPEN_MULTI_MODE_APPSBL = 0x0A # APPS Boot Loader 28 | OPEN_MULTI_MODE_APPS_CEFS = 0x0B # APPS CEFS image 29 | OPEN_MULTI_MODE_FLASH_BIN = 0x0C # Flash.bin image for Windows mobile 30 | OPEN_MULTI_MODE_DSP1 = 0x0D # DSP1 runtime image 31 | OPEN_MULTI_MODE_CUSTOM = 0x0E # Image for user defined partition 32 | OPEN_MULTI_MODE_DBL = 0x0F # DBL Image for SB Architecture 2.0 33 | OPEN_MULTI_MODE_OSBL = 0x10 # OSBL Image for SB Architecture 2.0 34 | OPEN_MULTI_MODE_FSBL = 0x11 # FSBL Image for SB Architecture 2.0 35 | OPEN_MULTI_MODE_DSP2 = 0x12 # DSP2 executable 36 | OPEN_MULTI_MODE_RAW = 0x13 # APPS EFS2 RAW image 37 | OPEN_MULTI_MODE_EMMC_USER = 0x21 # EMMC USER partition 38 | OPEN_MULTI_MODE_EMMC_BOOT0 = 0x22 # EMMC BOOT partition 0 39 | OPEN_MULTI_MODE_EMMC_BOOT1 = 0x23 # EMMC BOOT partition 1 40 | OPEN_MULTI_MODE_EMMC_RPMB = 0x24 # EMMC BOOT partition 1 41 | OPEN_MULTI_MODE_EMMC_GPP1 = 0x25 # EMMC GPP partition 1 42 | OPEN_MULTI_MODE_EMMC_GPP2 = 0x26 # EMMC GPP partition 2 43 | OPEN_MULTI_MODE_EMMC_GPP3 = 0x27 # EMMC GPP partition 3 44 | OPEN_MULTI_MODE_EMMC_GPP4 = 0x28 # EMMC GPP partition 4 45 | 46 | 47 | class response_code_type: 48 | ACK = 0x00 # Successful 49 | RESERVED_1 = 0x01 # Reserved 50 | NAK_INVALID_DEST = 0x02 # Failure: destination address is invalid. 51 | NAK_INVALID_LEN = 0x03 # Failure: operation length is invalid. 52 | NAK_EARLY_END = 0x04 # Failure: packet was too short for this cmd. 53 | NAK_INVALID_CMD = 0x05 # Failure: invalid command 54 | RESERVED_6 = 0x06 # Reserved 55 | NAK_FAILED = 0x07 # Failure: operation did not succeed. 56 | NAK_WRONG_IID = 0x08 # Failure: intelligent ID code was wrong. 57 | NAK_BAD_VPP = 0x09 # Failure: programming voltage out of spec 58 | NAK_VERIFY_FAILED = 0x0A # Failure: readback verify did not match 59 | RESERVED_0xB = 0x0B # Reserved 60 | NAK_INVALID_SEC_CODE = 0x0C # Failure: Incorrect security code 61 | NAK_CANT_POWER_DOWN = 0x0D # Failure: Cannot power down phone 62 | NAK_NAND_NOT_SUPP = 0x0E # Failure: Download to NAND not supported 63 | NAK_CMD_OUT_SEQ = 0x0F # Failure: Command out of sequence 64 | NAK_CLOSE_FAILED = 0x10 # Failure: Close command failed 65 | NAK_BAD_FEATURE_BITS = 0x11 # Failure: Incompatible Feature Bits 66 | NAK_NO_SPACE = 0x12 # Failure: Out of space 67 | NAK_INVALID_SEC_MODE = 0x13 # Failure: Multi-Image invalid security mode 68 | NAK_MIBOOT_NOT_SUPP = 0x14 # Failure: Multi-Image boot not supported 69 | NAK_PWROFF_NOT_SUPP = 0x15 # Failure: Power off not supported 70 | -------------------------------------------------------------------------------- /edlclient/Library/xmlparser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # (c) B.Kerler 2018-2024 under GPLv3 license 4 | # If you use my code, make sure you refer to my name 5 | # 6 | # !!!!! If you use this code in commercial products, your product is automatically 7 | # GPLv3 and has to be open sourced under GPLv3 as well. !!!!! 8 | import xml.etree.ElementTree as ET 9 | 10 | 11 | class xmlparser: 12 | def getresponse(self, input): 13 | lines = input.split(b" 0: 57 | if data[j] == 0: 58 | break 59 | text += chr(data[j]) 60 | j += 1 61 | i -= 1 62 | return text 63 | 64 | 65 | def extract_hdr(memsection, version, sign_info, mem_section, code_size, signature_size, hdr1, hdr2, hdr3, hdr4): 66 | try: 67 | md_size = \ 68 | unpack("> 0) & 1 84 | in_use_soc_hw_version=(v >> 1) & 1 85 | use_serial_number_in_signing=(v >> 2) & 1 86 | oem_id_independent=(v >> 3) & 1 87 | root_revoke_activate_enable=(v >> 4) & 0b11 88 | uie_key_switch_enable=(v >> 6) & 0b11 89 | debug=(v >> 8) & 0b11 90 | md_offset+=4 91 | soc_vers=hexlify(mm[md_offset:md_offset + (12*4)]) 92 | md_offset+=12*4 93 | multi_serial_numbers=hexlify(mm[md_offset:md_offset + (8*4)]) 94 | md_offset += 8 * 4 95 | mrc_index=unpack("H", mem_section[signatureoffset + 2:signatureoffset + 4])[0] + 4 114 | casignature2offset = signatureoffset + len1 115 | len2 = unpack(">H", mem_section[casignature2offset + 2:casignature2offset + 4])[0] + 4 116 | rootsignature3 = mem_section[(casignature2offset + len2):(casignature2offset + len2) + 999999999].split( 117 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff')[0] 118 | 119 | idx = signatureoffset 120 | signature = {} 121 | while idx != -1: 122 | if idx >= len(mem_section): 123 | break 124 | idx = mem_section.find('\x04\x0B'.encode(), idx) 125 | if idx == -1: 126 | break 127 | length = mem_section[idx + 3] 128 | if length > 60: 129 | idx += 1 130 | continue 131 | try: 132 | text = mem_section[idx + 4:idx + 4 + length].decode().split(' ') 133 | signature[text[2]] = text[1] 134 | except: 135 | text = "" 136 | idx += 1 137 | idx = mem_section.find('QC_IMAGE_VERSION_STRING='.encode(), 0) 138 | if idx != -1: 139 | sign_info.qc_version = grabtext(mem_section[idx + len("QC_IMAGE_VERSION_STRING="):]) 140 | idx = mem_section.find('OEM_IMAGE_VERSION_STRING='.encode(), 0) 141 | if idx != -1: 142 | sign_info.oem_version = grabtext(mem_section[idx + len("OEM_IMAGE_VERSION_STRING="):]) 143 | idx = mem_section.find('IMAGE_VARIANT_STRING='.encode(), 0) 144 | if idx != -1: 145 | sign_info.image_variant = grabtext(mem_section[idx + len("IMAGE_VARIANT_STRING="):]) 146 | if "MODEL_ID" in signature: 147 | sign_info.model_id = signature["MODEL_ID"] 148 | if "OEM_ID" in signature: 149 | sign_info.oem_id = signature["OEM_ID"] 150 | if "HW_ID" in signature: 151 | sign_info.hw_id = signature["HW_ID"] 152 | if "SW_ID" in signature: 153 | sign_info.sw_id = signature["SW_ID"] 154 | if "SW_SIZE" in signature: 155 | sign_info.sw_size = signature["SW_SIZE"] 156 | if "SHA256" in signature: 157 | sign_info.pk_hash = hashlib.sha256(rootsignature3).hexdigest() 158 | elif "SHA384" in signature: 159 | sign_info.pk_hash = hashlib.sha384( 160 | rootsignature3).hexdigest() 161 | else: 162 | sign_info.pk_hash = hashlib.sha384( 163 | rootsignature3).hexdigest() 164 | 165 | except: 166 | return None 167 | return sign_info 168 | 169 | 170 | def extract_old_hdr(signatureoffset, sign_info, mem_section, code_size, signature_size): 171 | signature = {} 172 | if mem_section[signatureoffset] != 0x30: 173 | print("Error on " + sign_info.filename + ", unknown signaturelength") 174 | return None 175 | if signatureoffset != -1: 176 | if len(mem_section) < signatureoffset + 4: 177 | print("Signature error on " + sign_info.filename) 178 | return None 179 | len1 = unpack(">H", mem_section[signatureoffset + 2:signatureoffset + 4])[0] + 4 180 | casignature2offset = signatureoffset + len1 181 | len2 = unpack(">H", mem_section[casignature2offset + 2:casignature2offset + 4])[0] + 4 182 | rootsignature3 = mem_section[(casignature2offset + len2):(casignature2offset + len2) + 999999999].split( 183 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff')[0] 184 | sign_info.pk_hash = hashlib.sha256(rootsignature3).hexdigest() 185 | idx = signatureoffset 186 | 187 | while idx != -1: 188 | if idx >= len(mem_section): 189 | break 190 | idx = mem_section.find('\x04\x0B'.encode(), idx) 191 | if idx == -1: 192 | break 193 | length = mem_section[idx + 3] 194 | if length > 60: 195 | idx += 1 196 | continue 197 | try: 198 | text = mem_section[idx + 4:idx + 4 + length].decode().split(' ') 199 | signature[text[2]] = text[1] 200 | except: 201 | text = "" 202 | idx += 1 203 | idx = mem_section.find('QC_IMAGE_VERSION_STRING='.encode(), 0) 204 | if idx != -1: 205 | sign_info.qc_version = grabtext(mem_section[idx + len("QC_IMAGE_VERSION_STRING="):]) 206 | idx = mem_section.find('OEM_IMAGE_VERSION_STRING='.encode(), 0) 207 | if idx != -1: 208 | sign_info.oem_version = grabtext(mem_section[idx + len("OEM_IMAGE_VERSION_STRING="):]) 209 | idx = mem_section.find('IMAGE_VARIANT_STRING='.encode(), 0) 210 | if idx != -1: 211 | sign_info.image_variant = grabtext(mem_section[idx + len("IMAGE_VARIANT_STRING="):]) 212 | if "MODEL_ID" in signature: 213 | sign_info.model_id = signature["MODEL_ID"] 214 | if "OEM_ID" in signature: 215 | sign_info.oem_id = signature["OEM_ID"] 216 | if "HW_ID" in signature: 217 | sign_info.hw_id = signature["HW_ID"] 218 | if "SW_ID" in signature: 219 | sign_info.sw_id = signature["SW_ID"] 220 | if "SW_SIZE" in signature: 221 | sign_info.sw_size = signature["SW_SIZE"] 222 | if "SHA256" in signature: 223 | sign_info.pk_hash = hashlib.sha256(rootsignature3).hexdigest() 224 | elif "SHA384" in signature: 225 | sign_info.pk_hash = hashlib.sha384( 226 | rootsignature3).hexdigest() 227 | return sign_info 228 | 229 | 230 | def init_loader_db(): 231 | loaderdb = {} 232 | loaders = os.path.join(current_dir, "..", "..", "Loaders") 233 | if not os.path.exists(loaders): 234 | loaders = os.path.join(current_dir, "Loaders") 235 | if not os.path.exists(loaders): 236 | print("Couldn't find Loaders directory") 237 | return loaderdb 238 | 239 | for (dirpath, dirnames, filenames) in os.walk(loaders): 240 | for filename in filenames: 241 | file_name = os.path.join(dirpath, filename) 242 | found = False 243 | for ext in [".bin", ".mbn", ".elf", ""]: 244 | if ext in filename[-4:]: 245 | found = True 246 | break 247 | if not found: 248 | continue 249 | try: 250 | hwid = filename.split("_")[0].lower() 251 | msmid = hwid[:8] 252 | devid = hwid[8:] 253 | pkhash = filename.split("_")[1].lower() 254 | msmdb = lu.convertmsmid(msmid) 255 | for msmid in msmdb: 256 | mhwid = (msmid + devid).lower() 257 | if mhwid not in loaderdb: 258 | loaderdb[mhwid] = {} 259 | if pkhash not in loaderdb[mhwid]: 260 | loaderdb[mhwid][pkhash] = file_name 261 | else: 262 | loaderdb[mhwid][pkhash].append(file_name) 263 | except: 264 | continue 265 | return loaderdb 266 | 267 | 268 | def is_duplicate(loaderdb, sign_info): 269 | lhash = sign_info.pk_hash[:16].lower() 270 | msmid = sign_info.hw_id[:8].lower() 271 | devid = sign_info.hw_id[8:].lower() 272 | hwid = sign_info.hw_id.lower() 273 | for msmid in lu.convertmsmid(msmid): 274 | rid = (msmid + devid).lower() 275 | if hwid in loaderdb: 276 | loader = loaderdb[hwid] 277 | if lhash in loader: 278 | return True 279 | if rid in loaderdb: 280 | loader = loaderdb[rid] 281 | if lhash in loader: 282 | return True 283 | return False 284 | 285 | 286 | def main(argv): 287 | file_list = [] 288 | path = "" 289 | if len(argv) < 3: 290 | print("Usage: fhloaderparse [FHLoaderDir] [OutputDir]") 291 | exit(0) 292 | 293 | path = argv[1] 294 | outputdir = argv[2] 295 | if not os.path.exists(outputdir): 296 | os.mkdir(outputdir) 297 | 298 | # First hash all loaders in Loader directory 299 | hashes = {} 300 | loaderdb = init_loader_db() 301 | for mhwid in loaderdb: 302 | for pkhash in loaderdb[mhwid]: 303 | fname = loaderdb[mhwid][pkhash] 304 | with open(fname, 'rb') as rhandle: 305 | data = rhandle.read() 306 | sha256 = hashlib.sha256() 307 | sha256.update(data) 308 | hashes[sha256.digest()] = fname 309 | 310 | # Now lets hash all files in the output directory 311 | for (dirpath, dirnames, filenames) in walk(outputdir): 312 | for filename in filenames: 313 | fname = os.path.join(dirpath, filename) 314 | with open(fname, 'rb') as rhandle: 315 | data = rhandle.read() 316 | sha256 = hashlib.sha256() 317 | sha256.update(data) 318 | hashes[sha256.digest()] = fname 319 | 320 | # Now lets search the input path for loaders 321 | extensions = ["txt", "idb", "i64", "py"] 322 | for (dirpath, dirnames, filenames) in walk(path): 323 | for filename in filenames: 324 | basename = os.path.basename(filename).lower() 325 | ext = basename[basename.rfind(".") + 1:] 326 | if ext not in extensions: 327 | file_list.append(os.path.join(dirpath, filename)) 328 | 329 | if not os.path.exists(os.path.join(outputdir, "Unknown")): 330 | os.makedirs(os.path.join(outputdir, "Unknown")) 331 | if not os.path.exists(os.path.join(outputdir, "Duplicate")): 332 | os.mkdir(os.path.join(outputdir, "Duplicate")) 333 | 334 | # Lets hash all the input files and extract the signature 335 | filelist = [] 336 | rt = open(os.path.join(outputdir, argv[1] + ".log"), "w") 337 | for filename in file_list: 338 | filesize = os.stat(filename).st_size 339 | elfpos = 0 340 | with open(filename, 'rb') as rhandle: 341 | data = rhandle.read() 342 | if len(data) < 4: 343 | continue 344 | signinfo = Signed() 345 | sha256 = hashlib.sha256() 346 | sha256.update(data) 347 | signinfo.hash = sha256.digest() 348 | signinfo.filename = filename 349 | signinfo.filesize = os.stat(filename).st_size 350 | 351 | while elfpos < filesize: 352 | if elfpos == -1: 353 | break 354 | mem_section = data[elfpos:] 355 | elfheader = elf(mem_section, signinfo.filename) 356 | if len(elfheader.pentry) < 4: 357 | elfpos = data.find(b"\x7FELF", elfpos + 1) 358 | continue 359 | idx = 0 360 | for entry in elfheader.pentry: 361 | if entry.p_type == 0 and entry.p_flags & 0xF000000 == 0x2000000: 362 | break 363 | idx += 1 364 | if 'memorylayout' in dir(elfheader): 365 | memsection = elfheader.memorylayout[idx] 366 | try: 367 | sect = BytesIO(mem_section[memsection.file_start_addr + 0x4:]) 368 | version = int.from_bytes(sect.read(4), 'little') 369 | hdr1 = int.from_bytes(sect.read(4), 'little') 370 | hdr2 = int.from_bytes(sect.read(4), 'little') 371 | hdr3 = int.from_bytes(sect.read(4), 'little') 372 | code_size = int.from_bytes(sect.read(4), 'little') 373 | hdr4 = int.from_bytes(sect.read(4), 'little') 374 | signature_size = int.from_bytes(sect.read(4), 'little') 375 | # cert_chain_size=unpack("= 6: # SDM 393 | signinfo = extract_hdr(memsection, version, signinfo, mem_section, code_size, signature_size, 394 | hdr1, 395 | hdr2, hdr3, hdr4) 396 | if signinfo is None: 397 | continue 398 | filelist.append(signinfo) 399 | break 400 | else: 401 | print("Unknown version for " + filename) 402 | continue 403 | if elfpos == -1 and int.from_bytes(data[:4], 'little') == 0x844BDCD1: 404 | mbn = MBN(mem_section) 405 | if mbn.sigsz == 0: 406 | print("%s has no signature." % filename) 407 | copyfile(filename, os.path.join(outputdir, "Unknown", filename[filename.rfind("/") + 1:].lower())) 408 | continue 409 | signatureoffset = mbn.imagesrc + mbn.codesz + mbn.sigsz 410 | signinfo = extract_old_hdr(signatureoffset, signinfo, mem_section, mbn.codesz, mbn.sigsz) 411 | if signinfo is None: 412 | continue 413 | filelist.append(signinfo) 414 | elif elfpos == -1: 415 | print("Error on " + filename) 416 | continue 417 | 418 | sorted_x = sorted(filelist, key=lambda x: (x.hw_id, -x.filesize)) 419 | 420 | class loaderinfo: 421 | hw_id = '' 422 | item = '' 423 | 424 | loaderlists = {} 425 | for item in sorted_x: 426 | if item.oem_id != '': 427 | oemid = int(item.oem_id, 16) 428 | if oemid in vendor: 429 | oeminfo = vendor[oemid] 430 | else: 431 | oeminfo = item.oem_id 432 | if len(item.sw_id) < 16: 433 | item.sw_id = "0" * (16 - len(item.sw_id)) + item.sw_id 434 | info = f"OEM:{oeminfo}\tMODEL:{item.model_id}\tHWID:{item.hw_id}\tSWID:{item.sw_id}\tSWSIZE:{item.sw_size}\tPK_HASH:{item.pk_hash}\t{item.filename}\t{str(item.filesize)}" 435 | if item.oem_version != '': 436 | info += "\tOEMVER:" + item.oem_version + "\tQCVER:" + item.qc_version + "\tVAR:" + item.image_variant 437 | loader_info = loaderinfo() 438 | loader_info.hw_id = item.hw_id 439 | loader_info.pk_hash = item.pk_hash 440 | if item.hash not in hashes: 441 | if loader_info not in loaderlists: 442 | if not is_duplicate(loaderdb, item): 443 | loaderlists[loader_info] = item.filename 444 | print(info) 445 | msmid = loader_info.hw_id[:8] 446 | devid = loader_info.hw_id[8:] 447 | for msmid in lu.convertmsmid(msmid): 448 | hwid = (msmid + devid).lower() 449 | auth = "" 450 | with open(item.filename, "rb") as rf: 451 | data = rf.read() 452 | if b"sig tag can" in data: 453 | auth = "_EDLAuth" 454 | if b"peek\x00" in data: 455 | auth += "_peek" 456 | fna = os.path.join(outputdir, ( 457 | hwid + "_" + loader_info.pk_hash[0:16] + "_FHPRG" + auth + ".bin").lower()) 458 | if not os.path.exists(fna): 459 | copyfile(item.filename, 460 | os.path.join(outputdir, hwid + "_" + ( 461 | loader_info.pk_hash[0:16] + "_FHPRG" + auth + ".bin").lower())) 462 | elif item.filesize > os.stat(fna).st_size: 463 | copyfile(item.filename, os.path.join(outputdir, 464 | (hwid + "_" + loader_info.pk_hash[ 465 | 0:16] + "_FHPRG" + auth + ".bin").lower())) 466 | else: 467 | print("Duplicate: " + info) 468 | copyfile(item.filename, os.path.join(outputdir, "Duplicate", 469 | (loader_info.hw_id + "_" + loader_info.pk_hash[ 470 | 0:16] + "_FHPRG.bin").lower())) 471 | else: 472 | copyfile(item.filename, os.path.join(outputdir, "Unknown", os.path.basename(item.filename).lower())) 473 | else: 474 | copyfile(item.filename, 475 | os.path.join(outputdir, "Duplicate", 476 | (loader_info.hw_id + "_" + loader_info.pk_hash[0:16] + "_FHPRG.bin").lower())) 477 | print(item.filename + f" is duplicate of {hashes[item.hash]}. Skipping") 478 | try: 479 | rt.write(info + "\n") 480 | except: 481 | continue 482 | else: 483 | print("Unknown :" + item.filename) 484 | copyfile(item.filename, os.path.join(outputdir, "Unknown", os.path.basename(item.filename).lower())) 485 | 486 | for item in filelist: 487 | if item.oem_id == '' and (".bin" in item.filename or ".mbn" in item.filename or ".hex" in item.filename): 488 | info = "Unsigned:" + item.filename + "\t" + str(item.filesize) 489 | if item.oem_version != '': 490 | info += "\tOEMVER:" + item.oem_version + "\tQCVER:" + item.qc_version + "\tVAR:" + item.image_variant 491 | print(info) 492 | rt.write(info + "\n") 493 | if not os.path.exists(os.path.join(outputdir, "Unknown", item.filename)): 494 | copyfile(item.filename, 495 | os.path.join(outputdir, "Unknown", os.path.basename(item.filename).lower())) 496 | 497 | rt.close() 498 | 499 | 500 | main(sys.argv) 501 | -------------------------------------------------------------------------------- /edlclient/Tools/qc_diag: -------------------------------------------------------------------------------- 1 | qc_diag.py -------------------------------------------------------------------------------- /edlclient/Tools/sierrakeygen: -------------------------------------------------------------------------------- 1 | sierrakeygen.py -------------------------------------------------------------------------------- /edlclient/Tools/txt_to_loader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # (c) B.Kerler 2019-2023 under GPLv3 license 4 | # If you use my code, make sure you refer to my name 5 | # 6 | # !!!!! If you use this code in commercial products, your product is automatically 7 | # GPLv3 and has to be open sourced under GPLv3 as well. !!!!! 8 | # TXT to EDL Loader (c) B.Kerler 2023 9 | 10 | import sys 11 | from struct import unpack 12 | 13 | 14 | def main(): 15 | if len(sys.argv) < 2: 16 | print("Usage: ./txt_to_loader.py [log.txt] [loader.elf]") 17 | sys.exit(0) 18 | with open(sys.argv[1], "rb") as rf: 19 | data = bytearray() 20 | for line in rf.readlines(): 21 | if line[0] == 0x20: 22 | tt = line.split(b" ")[:-1] 23 | tt = tt[1:17] 24 | xx = b"".join(tt) 25 | data.extend(bytes.fromhex(xx.decode('utf-8'))) 26 | 27 | outdata = bytearray() 28 | i = 0 29 | seq = b"\x03\x00\x00\x00\x14\x00\x00\x00\x0D\x00\x00\x00" 30 | with open(sys.argv[2], "wb") as wf: 31 | while True: 32 | idx = data.find(seq) 33 | if idx == -1: 34 | if i == 0: 35 | seq = b"\x12\x00\x00\x00\x20\x00\x00\x00\x0D\x00\x00\x00\x00\x00\x00\x00" 36 | i += 1 37 | continue 38 | else: 39 | break 40 | else: 41 | cmd = unpack("=42", 4 | "wheel", 5 | "pyusb", 6 | "pyserial", 7 | "lxml", 8 | "docopt", 9 | "pycryptodome", 10 | "colorama", 11 | "Exscript", 12 | "requests", 13 | "passlib" 14 | ] 15 | build-backend = "setuptools.build_meta" 16 | -------------------------------------------------------------------------------- /qc_diag: -------------------------------------------------------------------------------- 1 | edlclient/Tools/qc_diag -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | wheel 2 | pyusb>=1.1.0 3 | pyserial>=3.4 4 | docopt>=0.6.2 5 | pycryptodome 6 | pycryptodomex 7 | lxml>=4.6.1 8 | colorama 9 | capstone 10 | keystone-engine 11 | qrcode 12 | requests 13 | passlib 14 | Exscript 15 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | license_file = LICENSE 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from setuptools import setup, find_packages 3 | import os 4 | 5 | setup( 6 | name='edlclient', 7 | version='3.62', 8 | packages=find_packages(), 9 | long_description=open("README.md").read(), 10 | scripts=['edl','edlclient/Tools/qc_diag.py','edlclient/Tools/sierrakeygen.py','edlclient/Tools/boottodwnload','edlclient/Tools/enableadb','edlclient/Tools/fhloaderparse','edlclient/Tools/beagle_to_loader'], 11 | data_files = ['LICENSE','README.md'], 12 | long_description_content_type="text/markdown", 13 | url='https://github.com/bkerler/edl', 14 | project_urls={ 15 | "Bug Tracker": "https://github.com/bkerler/edl/issues", 16 | }, 17 | classifiers=[ 18 | "Programming Language :: Python :: 3", 19 | "License :: OSI Approved :: GPlv3 License", 20 | "Operating System :: OS Independent", 21 | ], 22 | license='GPLv3 License', 23 | install_requires=[ 24 | 'colorama', 25 | 'docopt', 26 | 'usb', 27 | 'pyusb', 28 | 'pyserial', 29 | 'lxml', 30 | 'pylzma', 31 | 'pycryptodome', 32 | 'wheel', 33 | 'Exscript', 34 | 'requests', 35 | 'passlib' 36 | ], 37 | author='B. Kerler', 38 | author_email='info@revskills.de', 39 | description='Qualcomm reverse engineering and flashing tools', 40 | python_requires=">=3.7", 41 | include_package_data=True, 42 | zip_safe=False 43 | ) 44 | -------------------------------------------------------------------------------- /sierrakeygen: -------------------------------------------------------------------------------- 1 | edlclient/Tools/sierrakeygen.py -------------------------------------------------------------------------------- /sierrakeygen_README.md: -------------------------------------------------------------------------------- 1 | # Challenge/Response Generator for Sierra Wireless Cards V1.2 2 | (c) B. Kerler 2019-2024 3 | GPLv3 License 4 | 5 | ## Why 6 | 7 | - For enabling development mode and tests 8 | - For setting band options if locked 9 | 10 | ## Supported devices 11 | "MDM8200": ["M81A", "M81B", "AC880", "AC881", "MC8780", "MC8781", "AC880E", "AC881E", "EM8780", "EM8781", 12 | "MC8780V", "MC8781V", "MC8700", "AC308U"], 13 | "MDM9200": ["AC710", "MC8775", "MC8775V", "AC875", "MC8700", "AC313U", "MC8801", "MC7700", "MC7750", "MC7710", 14 | "EM7700"], 15 | "MDM9200_V1": ["AC710", "MC8775", "MC8775V", "AC875", "MC8700", "AC313U", "MC8801", "MC7700", "MC7750", 16 | "MC7710", "EM7700"], 17 | "MDM9200_V2": ["AC775", "PC7200"], 18 | "MDM9x15": ["SWI9X15C", "AR7550", "AR7552", "AR7554", "EM7355", "EM7655", "MC7354", "WP7100", "WP7102", "WP7104", 19 | "MC7305", "EM7305", "MC8805", "EM8805", "MC7350", "MC7350-L", "MC7802", "MC7304", "AR7556", "AR7558", 20 | "WP75xx", "WP85xx", "WP8548", "WP8548G", "AC340U"], 21 | "MDM9x30": ["EM7455", "MC7455", "EM7430", "MC7430"], 22 | "MDM9x30_V1": ["Netgear AC790S/AC791L"], 23 | "MDM9x40": ["MR1100", "AC815s", "AC785s"], 24 | "MDM9x50": ["EM7565", "EM7565-9", "EM7511"], 25 | "MDM9x06": ["WP77xx"], 26 | "MDM9x07": ["SWI9X07Y", "WP76xx"], 27 | "SDX65": ["MR6400", "MR6500", "MR6110", "MR6150", "MR6450", "MR6550"] 28 | 29 | ## Installation 30 | 31 | - Get python >=3.6 64-Bit 32 | 33 | ```bash 34 | ~> pip3 install -r requirements.txt 35 | ``` 36 | 37 | ## Usage 38 | 39 | - Get a specific challenge for your task from the modem 40 | ``` 41 | AT!OPENLOCK? 42 | ``` 43 | 44 | or 45 | 46 | ``` 47 | AT!OPENMEP? 48 | ``` 49 | 50 | or 51 | 52 | ``` 53 | AT!OPENCND? 54 | ``` 55 | 56 | - Run generator: 57 | For automatic unlock, use -u: 58 | ```bash 59 | ~> sierrakeygen -u 60 | ``` 61 | 62 | For AT!OPENLOCK use -l, for AT!OPENMEP use -m and for AT!OPENCND use -c accordingly 63 | (here challenge is BE96CBBEE0829BCA and device generation is MDM9200) 64 | ```bash 65 | ~> sierrakeygen -l BE96CBBEE0829BCA -d MDM9200 66 | ``` 67 | 68 | - Send generated response back to the modem 69 | 70 | ``` 71 | AT!OPENLOCK=[response from generator] 72 | ``` 73 | 74 | or 75 | 76 | ``` 77 | AT!OPENMEP=[response from generator] 78 | ``` 79 | 80 | or 81 | 82 | ``` 83 | AT!OPENCND=[response from generator] 84 | ``` 85 | 86 | - Open up a terminal and enable enhanced commands (generic pwd is "A710") 87 | 88 | ``` 89 | AT!ENTERCND=A710 90 | ``` 91 | 92 | Other known pwds are (thx to 4PDA): 93 | 94 | ``` 95 | AC815s: "fallow" 96 | MR1100: “lindeman” 97 | AC790-Telstra: "sunflower" 98 | LB1111: "granville" 99 | AC810-100EUS: "whistler" 100 | AC810S-1P1PLS: "seymour" 101 | AC810S-1TLAUS: "grouse" 102 | AC810S-1RDQAS: "cypress" 103 | AC790-100EUS: "lavender" 104 | AC790S-1SPSUS : "bluebell" 105 | ``` 106 | 107 | After unlocking via AT!OPENLOCK, you can also set a new password via AT!SETCND="pwd", 108 | in case the password isn't known 109 | 110 | ## Help 111 | 112 | ```bash 113 | ~> sierrakeygen -h 114 | ``` 115 | 116 | ## Remarks 117 | 118 | - MDM9200/MDM9x15/MDM9x30/MDM9x40/MDM9x50 confirmed to work 119 | 120 | - For AC785/AC790/AC810/MR1100, you can access the serial port via tcp: 121 | 122 | ```bash 123 | HostName: 192.168.1.1 124 | Port: 5510 125 | ConnectionType: Telnet 126 | ``` 127 | 128 | - Get firmware details : 129 | 130 | ``` 131 | ATI 132 | AT!PACKAGE? 133 | ``` 134 | 135 | - Get flash memory info : 136 | 137 | ``` 138 | AT!FMBADBLOCKS? 139 | AT!BSINFO 140 | ``` 141 | 142 | - Set password for opencnd: 143 | 144 | ``` 145 | AT!SETCND="[pwd]" 146 | ``` 147 | 148 | Example: 149 | 150 | ``` 151 | AT!SETCND="A710" 152 | ``` 153 | 154 | - For band selection, see possible bands via : 155 | 156 | ``` 157 | AT!BAND=? 158 | ``` 159 | 160 | - Set Modem to use all bands : 161 | 162 | ``` 163 | AT!BAND=00 164 | ``` 165 | 166 | - Set Modem to only use LTE : 167 | 168 | ``` 169 | AT!SELRAT=06 170 | ``` 171 | 172 | - Reboot modem and save settings : 173 | 174 | ``` 175 | AT!RESET 176 | ``` 177 | 178 | - To add a new band : 179 | 180 | ``` 181 | AT!BAND=[index],"[name]",0,8000000 182 | ``` 183 | 184 | Examples: 185 | 186 | ``` 187 | AT!BAND=03,"LTE B28 700",0,8000000 188 | AT!BAND=04,"LTE B1 2100",0,1 189 | AT!BAND=05,"LTE B3 1800",0,4 190 | AT!BAND=06,"LTE B7 2600",0,40 191 | AT!BAND=07,"LTE B8 900",0,80 192 | ``` 193 | 194 | - To remove a band : 195 | 196 | ``` 197 | AT!BAND=[index],"",0,0 198 | ``` 199 | 200 | Example: 201 | 202 | ``` 203 | AT!BAND=03,"",0,0 204 | ``` 205 | 206 | - Get signal info : 207 | 208 | ``` 209 | AT!GSTATUS? 210 | ``` 211 | 212 | - Get partition info : 213 | 214 | ``` 215 | AT!PARTINFO? 216 | ``` 217 | 218 | - Switch to qc download mode : 219 | 220 | ``` 221 | AT!BOOTHOLD 222 | AT!QPSTDLOAD 223 | ``` 224 | 225 | - Show Secure Boot info : 226 | 227 | ``` 228 | AT!SECBOOTCFG? Show Secure Boot config 229 | AT!SECBOOTPKHASH? Show Secure Boot PKHASH 230 | ``` 231 | 232 | - Show Product Info : 233 | 234 | ``` 235 | AT!USBPRODUCT? 236 | Sierra Wireless EM7565 Qualcomm® Snapdragon™ X16 LTE-A 237 | 238 | AT!USBMANUFACTURER? 239 | Sierra Wireless, Incorporated 240 | ``` 241 | 242 | - Set vid and pid : 243 | 244 | ``` 245 | AT!USBVID=1199 Set usb vid of 0x1199 246 | AT!USBPID=9091,9090 Set usb pid (app=0x9091, boot=0x9090) 247 | ``` 248 | 249 | - Set product identifier : 250 | 251 | ``` 252 | AT!PRIID? Show product identifier 253 | PRI Part Number: 9907344 254 | Revision: 002.001 255 | Customer: Generic-M2M 256 | Carrier PRI: 9999999_9907259_SWI9X50C_01.08.04.00_00_GENERIC_002.012_000 257 | 258 | 259 | AT!USBPID="9907344","002.001","Generic-M2M" Set PartNr, Revision and Customer 260 | ``` 261 | 262 | - Set preferred modem image : 263 | 264 | ``` 265 | AT!IMPREF="GENERIC" 266 | 267 | AT!IMAGE=? 268 | AT!IMAGE=[,[,[,"",""]]] 269 | op - 0:delete 1:list 2:get max num images 270 | type - 0:FW 1:CONFIG 271 | slot - FW slot index - none implies all slots 272 | AT!IMAGE?[[,]] 273 | 274 | AT!IMAGE=0,0,1 Op=0 (Delete), Type=0 (FW), Slot Index=1 275 | ``` 276 | 277 | - Reset to factor settings : 278 | 279 | ``` 280 | AT!RMARESET=1 281 | ``` 282 | 283 | - Lenovo laptop whitelist bypass : 284 | 285 | ``` 286 | AT!ENTERCND="A710" 287 | AT!CUSTOM="FASTENUMEN",2 Disable fast enumeration and only show up after init 288 | AT!PCOFFEN=2 Ignore W_DISABLE pin 289 | AT!USBSPEED=0 Force usb2 mode 290 | AT!RESET 291 | ``` 292 | 293 | - Set usb composition (diag, nmea, modem, mbim, same as USBCOMP=8): 294 | 295 | ``` 296 | AT!USBCOMP=1,3,0000100D 297 | ``` 298 | 299 | - List custom settings : 300 | 301 | ``` 302 | AT!CUSTOM? 303 | ``` 304 | 305 | - Enable telnet (after sending valid openlock request) 306 | 307 | ``` 308 | at!custom="TELNETENABLE",1 309 | ``` 310 | 311 | - Enable adb (after sending valid openlock request, here: MC7304/AC810) 312 | 313 | ``` 314 | AT!CUSTOM="ADBENABLE", 1 315 | ``` 316 | 317 | Regulary, tcp port 5555 is used for adb 318 | 319 | ``` 320 | adb tcpip 5555 321 | adb connect 192.168.1.1 322 | ``` 323 | 324 | - Enable telnet (after sending valid openlock request, here: MR1100) 325 | 326 | ``` 327 | AT!TELEN=1 328 | AT!CUSTOM="RDENABLE", 1 329 | AT!CUSTOM="TELNETENABLE", 1 330 | ``` 331 | then reboot the device. Afterwards, telnet should be available on MR1100 via 192.168.1.1:23 332 | 333 | - Flash firmware : 334 | 335 | ```bash 336 | ~ > sudo apt install libqmi-glib5 libqmi-proxy libqmi-utils -y 337 | ~ > qmi-firmware-update --update -d 1199:9091 firmware.cwe firmware.nvu 1199:9091 is usb vid/pid 338 | ``` 339 | 340 | ## Other useful links 341 | 342 | - https://github.com/danielewood/sierra-wireless-modems/blob/master/README.md 343 | 344 | ## ToDo 345 | 346 | - Nothing :) 347 | 348 | ## License 349 | 350 | Published under GPLv3 license 351 | Additional license limitations: No use in commercial products without prior permit by me. 352 | 353 | Enjoy ! 354 | --------------------------------------------------------------------------------