Restructure

This commit is contained in:
Bjoern Kerler 2021-08-05 09:04:49 +02:00
parent 5db3070d06
commit c11a1bc5a6
32 changed files with 10173 additions and 22 deletions

View file

@ -33,10 +33,24 @@ sudo python3 -m pip install -r requirements.txt
```
Windows:
- Boot device into 9008 mode, install Qualcomm_Diag_QD_Loader_2016_driver.exe from Drivers\Windows
- Use Zadig 2.5 or higher, list all devices, select QUSB_BULK device and replace
driver with libusb >= 1.2.6.0 one (will replace original driver)
- Get latest Zadig release [here] (https://zadig.akeo.ie/)
#### Install python + git
- Install python 3.9 and git
- If you install python from microsoft store, "python setup.py install" will fail, but that step isn't required.
- WIN+R ```cmd```
#### Grab files and install
```
git clone https://github.com/bkerler/edl
cd edl
pip3 install -r requirements.txt
```
#### Get latest UsbDk 64-Bit
- Install normal QC 9008 Serial Port driver (or use default Windows COM Port one, make sure no exclamation is seen)
- Get usbdk installer (.msi) from [here](https://github.com/daynix/UsbDk/releases/) and install it
- Test on device connect using "UsbDkController -n" if you see a device with pid 0x9008
- Works fine under Windows 10 and 11 :D
## Convert EDL loaders for automatic usage

26
edl.py
View file

@ -119,28 +119,18 @@ import logging
import subprocess
import re
from docopt import docopt
from Library.utils import LogBase
from Library.usblib import UsbClass
from Library.sahara import sahara
from Library.streaming_client import streaming_client
from Library.firehose_client import firehose_client
from Library.streaming import Streaming
from edl.Config.usb_ids import default_ids
from edl.Library.utils import LogBase
from edl.Library.usblib import UsbClass
from edl.Library.sahara import sahara
from edl.Library.streaming_client import streaming_client
from edl.Library.firehose_client import firehose_client
from edl.Library.streaming import Streaming
from binascii import hexlify
args = docopt(__doc__, version='3')
default_ids = [
[0x05c6, 0x9008, -1],
[0x05c6, 0x900e, -1],
[0x05c6, 0x9025, -1],
[0x1199, 0x9062, -1],
[0x1199, 0x9070, -1],
[0x1199, 0x9090, -1],
[0x0846, 0x68e0, -1],
[0x19d2, 0x0076, -1]
]
print("Qualcomm Sahara / Firehose Client V3.4 (c) B.Kerler 2018-2021.")
print("Qualcomm Sahara / Firehose Client V3.5 (c) B.Kerler 2018-2021.")
def parse_cmd(rargs):

0
edl/Config/__init__.py Executable file
View file

View file

@ -0,0 +1,677 @@
vendor = {
0x0000: "Qualcomm ",
0x0001: "Foxconn/Sony ",
0x0004: "ZTE ",
0x0011: "Smartisan ",
0x0015: "Huawei ",
0x0017: "Lenovo ",
0x0020: "Samsung ",
0x0029: "Asus ",
0x0030: "Haier ",
0x0031: "LG ",
0x0035: "Foxconn/Nokia",
0x0042: "Alcatel ",
0x0045: "Nokia ",
0x0048: "YuLong ",
0x0051: "Oppo/Oneplus ",
0x0072: "Xiaomi ",
0x0073: "Vivo ",
0x0130: "GlocalMe ",
0x0139: "Lyf ",
0x0168: "Motorola ",
0x01B0: "Motorola ",
0x0208: "Motorola ",
0x0228: "Motorola ",
0x2A96: "Micromax ",
0x02E8: "Lenovo ",
0x0328: "Motorola ",
0x0368: "Motorola ",
0x03C8: "Motorola ",
0x00C8: "Motorola ",
0x0348: "Motorola ",
0x1043: "Asus ",
0x1111: "Asus ",
0x143A: "Asus ",
0x1978: "Blackphone ",
0x2A70: "Oxygen "
}
root_cert_hash = {
"secboot_sha2_pss_subca1" : "afca69d4235117e5bfc21467068b20df85e0115d7413d5821883a6d244961581",
"secboot_sha2_pss_subca2" : "d40eee56f3194665574109a39267724ae7944134cd53cb767e293d3c40497955bc8a4519ff992b031fadc6355015ac87",
"old" : "cc3153a80293939b90d02d3bf8b23e0292e452fef662c74998421adad42a380f",
"new" : "7be49b72f9e4337223ccb84d6eccca4e61ce16e3602ac2008cb18b75babe6d09",
"mdm9x60_tel" : "36c886068d9a6634e9c55185044344e9e756dcc3b5960874942c7a1a1550dee0"
}
msmids = {
# cc3153a80293939b90d02d3bf8b23e0292e452fef662c74998421adad42a380f pkhash/root-cert
# 7be49b72f9e4337223ccb84d6eccca4e61ce16e3602ac2008cb18b75babe6d09 pkhash/root-cert
0x9440E1: "QDF2432",
0x9780E1: "IPQ4018",
0x9790E1: "IPQ4019",
0x0160E1: "QCA4020",
0x9680E1: "APQ8009",
0x7060E1: "APQ8016",
0x8100E1: "APQ806x",
0x9D00E1: "APQ8076",
0x08A0E1: "APQ807x",
0x9000E1: "APQ8084",
0x9630E1: "APQ8092",
0x0940E1: "MSM8905",
0x9600E1: "MSM8909", # SnapDragon 210
0x0510E1: "MSM8909W",
0x7050E1: "MSM8916", # SnapDragon 410
0x0560E1: "MSM8917",
0x0860E1: "MSM8920",
0x91B0E1: "MSM8929", # SnapDragon 415
0x04F0E1: "MSM8937",
0x90B0E1: "MSM8939", # SnapDragon 610
0x90C0E1: "APQ8036",
0x90D0E1: "APQ8039",
0x06B0E1: "MSM8940",
0x9720E1: "MSM8952", # SnapDragon 652
0x0460E1: "MSM8953", # 8053lat
0x0660E1: "APQ8053",
0x9900E1: "MSM8976", # SnapDragon 652
0x9690E1: "MSM8992", # SnapDragon 82x
0x9400E1: "MSM8994", # SnapDragon 808
0x9470E1: "MSM8996", # SnapDragon 820
0x06F0E1: "MSM8996AU",
0x05E0E1: "MSM8998_SDM835",
0x94B0E1: "MSM9055",
0x9730E1: "MDM9206_MDM9607tx",
0x04A0E1: "MDM9607",
0x8090E1: "MDM9916",
0x80B0E1: "MDM9955",
0x9210E1: "MDM9x35",
0x9500E1: "MDM9x40",
0x9540E1: "MDM9x45",
0x03A0E1: "MDM9x50",
0x7F50E1: "MDM9x25",
0x0320E1: "MDM9250", # MDM9x50
0x0340E1: "MDM9255", # MDM9x55
0x0390E1: "MDM9350", # MDM9x50
0x03B0E1: "MDM9x55",
0x07D0E1: "MDM9x60", # SDX20
0x07F0E1: "MDM9x65",
0x1280E1: "fsm100xx",
0x1650E1: "FSM10000",
0x1680E1: "FSM10005",
0x1690E1: "FSM10010",
0x16A0E1: "FSM10051",
0x16B0E1: "FSM10056",
0x1530E1: "ipq5018",
0x1610E1: "olympic_manar",
0x1060E1: "qm215",
0x0BE0E1: "SDM429",
0x0BF0E1: "SDM439",
0x09A0E1: "SDM450",
0x0AC0E1: "SDM630", #0x30070x00
0x0BA0E1: "SDM632",
0x0BB0E1: "SDA632",
0x08C0E1: "SDM660", # 0x30060000 soc_hw_version
0x07B0E1: "SDX50M", # 0x soc_hw_version,
0x0E50E1: "SDX55:CD90-PG591", # 0x600b0100 soc_hw_version, 0x8fff7000 dbgpolicy 32Bit, 0x8FCFD000 sec.elf 64Bit
0x0CF0E1: "SDX55M:CD90-PH809", # 0x600b0100 soc_hw_version, 0x8fff7000 dbgpolicy 32Bit, 0x8FCFD000 sec.elf 64Bit, # Netgear MR5100, sdxprairie
0x1250E1: "SA515M",
# afca69d4235117e5bfc21467068b20df85e0115d7413d5821883a6d244961581
0x0AB0E1: "QCA6290", # 0x40040100 soc_hw_version
0x0D90E1: "QCA6390", # 0x400A0000 soc_hw_version
0x1310E1: "QCA6480",
0x12E0E1: "QCA6481",
0x12D0E1: "QCA6491",
0x0D70E1: "QCA6595", # 0x400B0000 soc_hw_version
0x0D30E1: "QCN7605", # 0x400B0000 soc_hw_version
0x0D50E1: "QCN7606", # 0x400B0000 soc_hw_version
0x0910E1: "SDM670", # 0x60040100 soc_hw_version
0x0DB0E1: "SDM710",
0x0AA0E1: "QCS605",
0x0ED0E1: "SXR1120",
0x0EA0E1: "SXR1130",
0x08E0E1: "SDA845",
# d40eee56f3194665574109a39267724ae7944134cd53cb767e293d3c40497955
# d40eee56f3194665574109a39267724ae7944134cd53cb767e293d3c40497955bc8a4519ff992b031fadc6355015ac87 pk-hash/root-cert
0x1260E1: "IPQ6018",
0x1070E1: "MDM9205", # 0x20130100
0x1450E1: "agatti", # soc_vers 0x9003
0x13F0E1: "bitra_SDM", # soc_vers 0x6012
0x1410E1: "bitra_SDA",
0x1590E1: "cedros", # soc_vers 0x6017
0x1360E1: "kamorta", # soc_vers 0x9002 SnapDragon 662/460 SM4250/SM4350, bengal
0x1350E1: "lahaina", # soc_vers 0x600F sm8350, SDM875
0x1420E1: "lahaina_premier",
0x14A0E1: "makena", # soc_vers 0x6014
0x14B0E1: "SA8295P",
0x14C0E1: "SA8540P",
#0x1610E1: "mannar", # soc_vers 0x9004
0x1470E1: "moselle", # soc_vers 0x4014
0x10A0E1: "nicobar", # 0x90010100 soc_hw_version, 0x45FFF000 sec.elf 64Bit, 0x101FF000 dbgpolicy, 64Bit
0x10B0E1: "qcn90xx", # soc_vers 0x400D
0x10C0E1: "QCN9001",
0x1150E1: "QCN9002",
0x10D0E1: "QCN9003",
0x10E0E1: "QCN9010",
0x10F0E1: "QCN9011",
0x1110E1: "QCN9012",
0x1140E1: "QCN9013",
0x0AF0E1: "qcs405", # 0x20140000 soc_hw_version, 0x863DB000 sec.elf 64Bit, 0x863DE000 dbgpolicy, 64Bit
0x0400E1: "rennell", # soc_vers 0x600E7T A11 CB
0x12A0E1: "rennell",
0x12B0E1: "rennell_premier",
0x1490E1: "rennell_v1.1",
0x1630E1: "sd7250",
0x11E0E1: "saipan", # 0x600D0100 soc_hw_version, 0x808FF000 sec.elf 64Bit, 0x1C000000 dbgpolicy, 64Bit, SM7250 Snapdragon 765G
0x0950E1: "SM6150", # 0x60070100 soc_hw_version, 0x85FFF000 sec.elf 64Bit, 0x1C1FF000 dbgpolicy, 64Bit
0x0EC0E1: "SM6150p", # 0x60070100 soc_hw_version, 0x85FFF000 sec.elf 64Bit, 0x1C1FF000 dbgpolicy, 64Bit
0x0E60E1: "SM7150", # 0x600C0100 soc_hw_version, 0x85FFF000 sec.elf 64Bit, 0x1C1FF000 dbgpolicy, 64Bit
0x0A50E1: "SDM855_SM8150", # Hana 0x60030100 soc_hw_version, 0x85FFF000 sec.elf 64Bit, 0x1C1FF000 dbgpolicy, 64Bit
0x0A60E1: "SDM855p_SM8150p", # Hana 0x60030100 soc_hw_version, 0x85FFF000 sec.elf 64Bit, 0x1C1FF000 dbgpolicy, 64Bit
0x0C30E1: "SM8250:CD90-PH805-1A", # Kona, 0x60080100 soc_hw_version, 0x808FF000 sec.elf 64Bit, 0x1C000000 dbgpolicy, 64Bit
0x0CE0E1: "SM8250:CD90-PH806-1A", # Kona 0x60080100 soc_hw_version, 0x808FF000 sec.elf 64Bit, 0x1C000000 dbgpolicy, 64Bit
0x0B80E1: "sc8180x", # Snapdragon 8CX
0x1560E1: "SM8250", # HDK 8250
# Unknown root hash
0x0B70E1: "SDM850",
0x0960E1: "SDX24", # 0x60020100 soc_hw_version, 0x8fff7000 dbgpolicy 32Bit, 0x8FCFD000 sec.elf 64Bit
0x0970E1: "SDX24M", # 0x60020100 soc_hw_version, 0x8fff7000 dbgpolicy 32Bit, 0x8FCFD000 sec.elf 64Bit
0x0E70E1: "SM7150p", # 0x600C0100 soc_hw_version, 0x85FFF000 sec.elf 64Bit, 0x1C1FF000 dbgpolicy, 64Bit
0x0E80E1: "SA8155", # 0x60030100 soc_hw_version, 0x85FFF000 sec.elf 64Bit, 0x1C1FF000 dbgpolicy, 64Bit
0x0E90E1: "SA8155p", # 0x60030100 soc_hw_version, 0x85FFF000 sec.elf 64Bit, 0x1C1FF000 dbgpolicy, 64Bit
0x0E40E1: "qcs403", # 0x20140000 soc_hw_version, 0x863DB000 sec.elf 64Bit, 0x863DE000 dbgpolicy, 64Bit
0x1440E1: "chitwan", # soc_vers 0x6013
0x1370E1: "kamortap",
0x6220E1: "MSM7227A",
0x8040E1: "APQ8026",
0x0550E1: "APQ8017",
0x90F0E1: "APQ8037",
0x9770E1: "APQ8052",
0x9F00E1: "APQ8056",
0x7190E1: "APQ8064",
0x9300E1: "APQ8092",
0x0620E1: "APQ8098",
0x8110E1: "MSM8210",
0x8140E1: "MSM8212",
0x8120E1: "MSM8610",
0x8150E1: "MSM8612",
0x8010E1: "MSM8626",
0x8050E1: "MSM8926", # SnapDragon 400
0x9180E1: "MSM8928", # SnapDragon 400
0x7210E1: "MSM8930",
0x72C0E1: "MSM8960",
0x9B00E1: "MSM8956", # SnapDragon 652
0x9100E1: "MSM8962",
0x7B00E1: "MSM8974", # Snapdragon 800
0x7B30E1: "MSM8974A",
0x7B40E1: "MSM8974AB",
0x7B80E1: "MSM8974Pro",
0x7BC0E1: "MSM8974ABv3",
0x6B10E1: "MSM8974AC",
0x05F0E1: "MSM8996Pro", # SnapDragon 821
0x0480E1: "MDM9207",
0x0CC0E1: "SDM636",
0x0930E1: "SDA670", # 0x60040100 soc_hw_version
#0x0930E1: "SDA835", # 0x30020000 => HW_ID1 3002000000290022
0x08B0E1: "SDM845", # Napali 0x60000100 => HW_ID1 6000000000010000
#SDM840 NapaliQ ?
#SDM640 Talos ?
}
sochw = {
0x2013: "MDM9205",
0x2014: "qcs405",
0x2017: "IPQ6018",
0x3002: "MSM8998_SDM835,SDA835",
0x3006: "SDM660",
0x3007: "SDM630",
0x4003: "QCA4020",
0x4004: "IPQ8074,QCA6290",
0x400A: "QCA6390",
0x400B: "QCN7605,QCA6595,QCN7606",
0x400D: "qcn90xx",
0x4014: "moselle",
#: "SDM632",
#: "SDA632",
#: "SDM636",
0x6000: "SDM845",
0x6001: "SDA845",
0x6002: "SDX24,SDX24M",
0x6003: "SDM855_SM8150,SDM855p_SM8150p",
0x6004: "SDA670,SDM670,SDM710",
0x6005: "SDM670",
#: "SDX50M",
0x6006: "sc8180x",
0x6007: "SM6150,SM6150p",
0x6008: "SM8250:CD90-PH805-1A,SM8250:CD90-PH806-1A,SM8250",
0x6009: "SDM670",
0x600B: "SDX55:CD90-PG591,SDX55:CD90-PH809",
0x600C: "SM7150,SM7150p",
0x600D: "saipan",
0x600E: "rennell",
0x600F: "lahaina",
0x6012: "bitra_SDM",
0x6013: "chitwan",
0x6014: "makena",
0x6016: "olympic",
0x6017: "cedros",
0x9001: "nicobar",
0x9002: "kamorta",
0x9003: "agatti",
0x9004: "mannar"
}
secgen=[
# BOOT_ROM_BASE_PHYS, SECURITY_CONTROL_BASE_PHYS, MEMORY_MAP
[[], [0x01900000, 0x100000], []],
[[],[0x01e20000,0x1000],[]],
[[0xFC010000, 0x18000], [0xFC4B8000, 0x60F0], [0x200000, 0x24000]],
[[0x100000, 0x1ffb0], [0x70000, 0x6158], [0x200000, 0x24000]],
[[0x100000, 0x1ffb0], [0x00058000, 0x1000], [0x200000, 0x24000]],
[[0x100000, 0x1ffb0], [0x000A0000, 0x6FFF], [0x200000, 0x24000]],
[[0x100000, 0x1ffb0], [0x00700000, 0x6158], [0x200000, 0x24000]],
[[0x300000, 0x3c000], [0x00780000, 0x10000], [0x14009003, 0x24000]],
[[0x300000, 0x3c000], [0x01B40000, 0x10000], []],
]
infotbl = {
"QDF2432": secgen[0],
"QCA6290": secgen[1],
"QCA6390": secgen[1],
"QCA6480": secgen[1],
"QCA6481": secgen[1],
"QCA6490": secgen[1],
"QCA6491": secgen[1],
"APQ8084": secgen[2],
"APQ8092": secgen[2],
"MSM8962": secgen[2],
"MSM8974": secgen[2],
"MSM8974Pro": secgen[2],
"MSM8974AB": secgen[2],
"MSM8974ABv3": secgen[2],
"MSM8974AC": secgen[2],
"MSM8992": secgen[2],
"MSM8994": secgen[2],
"MDM9x25": secgen[2],
"MDM9x35": secgen[2],
"MSM8996": secgen[3],
"MSM8996AU": secgen[3],
"MSM8996Pro": secgen[3],
"IPQ4018": secgen[4],
"IPQ4019": secgen[4],
"APQ8009": secgen[4],
"APQ8016": secgen[4],
"APQ8036": secgen[4],
"APQ8039": secgen[4],
"MSM8905": secgen[4],
"MSM8909": secgen[4],
"MSM8909W": secgen[4],
"MSM8916": secgen[4],
"MSM8929": secgen[4],
"MSM8939": secgen[4],
"MSM8952": secgen[4],
"MDM9x40": secgen[4],
"MDM9x45": secgen[4],
"APQ8017": secgen[5],
"APQ8037": secgen[5],
"APQ8053": secgen[5],
"APQ8056": secgen[5],
"APQ8076": secgen[5],
"MSM8917": secgen[5],
"MSM8920": secgen[5],
"MSM8937": secgen[5],
"MSM8940": secgen[5],
"MSM8953": secgen[5],
"MSM8956": secgen[5],
"MSM8976": secgen[5],
"MSM9206": secgen[5],
"MDM9207": secgen[5],
"MDM9607": secgen[5],
"MDM9x50": secgen[5],
"MDM9x55": secgen[5],
"MDM9x60": secgen[5],
"MDM9x65": secgen[5],
"MDM9250": secgen[5],
"MDM9350": secgen[5],
"MDM9650": secgen[5],
"SDM429": secgen[5],
"SDM439": secgen[5],
"SDM450": secgen[5],
"SDM632": secgen[5],
"SDA632": secgen[5],
"SDX50M": secgen[5],
"qcs403": secgen[5],
"qcs405": secgen[5],
"ipq5018": secgen[5],
"ipq6018": secgen[5],
"qm215": secgen[5],
"APQ806x": secgen[6],
"MSM8930": secgen[6],
"MSM8936": secgen[6],
"APQ8098": secgen[7],
"MSM8998": secgen[7],
"SDM630": secgen[7],
"SDM636": secgen[7],
"SDM660": secgen[7],
"SDM670": secgen[7],
"SDA670": secgen[7],
"SDM710": secgen[7],
"QCS605": secgen[7],
"SXR1120": secgen[7],
"SXR1130": secgen[7],
"SDM845": secgen[7],
"SDA845": secgen[7],
"SDM850": secgen[7],
"SDX24": secgen[7],
"SDX24M": secgen[7],
"SDX55M": secgen[7],
"SDX55:CD90-PG591": secgen[7],
"SDX55:CD90-PH809": secgen[7],
"SA515M": secgen[7],
"SM6150": secgen[7],
"SM6150p": secgen[7],
"SM7150": secgen[7],
"SM7150p": secgen[7],
"SDM855_SM8150": secgen[7],
"SDM855p_SM8150p": secgen[7],
"SM8250": secgen[7],
"SM8250p": secgen[7],
"SM8250:CD90-PH805-1A": secgen[7],
"SM8250:CD90-PH806-1A": secgen[7],
"saipan": secgen[7],
"sc8180x": secgen[7],
"bitra": secgen[7],
"cedros": secgen[7],
"chitwan": secgen[7],
"lahaina": secgen[7],
"lahaina_premier": secgen[7],
"mannar": secgen[7],
"rennell": secgen[7],
"sd7250": secgen[7],
"nicobar": secgen[8],
"agatti": secgen[8],
"kamorta": secgen[8],
"kamortap": secgen[8],
# "MSM7227A": [[], [], []],
# "MSM8210": [[], [0xFC4B8000,0x6FFF], []],
# "MSM8212": [[], [], []],
# "MSM8610": [[], [0xFC4B8000,0x6FFF], []],
# "MSM8226": [[], [0xFC4B8000,0x6FFF], []],
# "MSM8926": [[], [0xFC4B8000,0x6FFF], []],
# "MSM8928": [[], [], []],
}
class memory_type:
nand = 0
emmc = 1
ufs = 2
spinor = 3
preferred_memory = {
"QDF2432": emmc,
"QCA6290": emmc,
"QCA6390": emmc,
"QCA6480": emmc,
"QCA6481": emmc,
"QCA6490": emmc,
"QCA6491": emmc,
"APQ8084": emmc,
"APQ8092": emmc,
"MSM8962": emmc,
"MSM8974": emmc,
"MSM8974Pro": emmc,
"MSM8974AB": emmc,
"MSM8974ABv3": emmc,
"MSM8974AC": emmc,
"MSM8992": emmc,
"MSM8994": emmc,
"MDM9x25": emmc,
"MDM9x35": emmc,
"MSM8996": ufs,
"MSM8996AU": ufs,
"MSM8996Pro": ufs,
"IPQ4018": emmc,
"IPQ4019": emmc,
"APQ8009": emmc,
"APQ8016": emmc,
"APQ8036": emmc,
"APQ8039": emmc,
"MSM8905": emmc,
"MSM8909": emmc,
"MSM8909W": emmc,
"MSM8916": emmc,
"MSM8929": emmc,
"MSM8939": emmc,
"MSM8952": emmc,
"MDM9x40": emmc,
"MDM9x45": emmc,
"APQ8017": emmc,
"APQ8037": emmc,
"APQ8053": emmc,
"APQ8056": emmc,
"APQ8076": emmc,
"MSM8917": emmc,
"MSM8920": emmc,
"MSM8937": emmc,
"MSM8940": emmc,
"MSM8953": emmc,
"MSM8956": emmc,
"MSM8976": emmc,
"MSM9206": emmc,
"MDM9207": nand,
"MDM9607": nand,
"MDM9x50": emmc,
"MDM9x55": emmc,
"MDM9x60": emmc,
"MDM9x65": emmc,
"MDM9250": emmc,
"MDM9350": emmc,
"MDM9650": emmc,
"SDM429": emmc,
"SDM439": emmc,
"SDM450": emmc,
"SDM632": emmc,
"SDA632": emmc,
"SDX50M": emmc,
"qcs403": emmc,
"qcs405": emmc,
"ipq5018": emmc,
"ipq6018": emmc,
"qm215": emmc,
"APQ806x": emmc,
"MSM8930": emmc,
"MSM8936": emmc,
"APQ8098": emmc,
"MSM8998": ufs,
"SDM630": emmc,
"SDM636": emmc,
"SDM660": emmc,
"SDM670": emmc,
"SDA670": emmc,
"SDM710": emmc,
"QCS605": emmc,
"SXR1120": emmc,
"SXR1130": emmc,
"SDM845": ufs,
"SDA845": ufs,
"SDM850": ufs,
"SDX24": emmc,
"SDX24M": emmc,
"SDX55M": ufs,
"SDX55:CD90-PG591": ufs,
"SDX55:CD90-PH809": ufs,
"SA515M": emmc,
"SM6150": emmc,
"SM6150p": emmc,
"SM7150": emmc,
"SM7150p": emmc,
"SDM855_SM8150": ufs,
"SDM855p_SM8150p": ufs,
"SM8250": ufs,
"SM8250p": ufs,
"SM8250:CD90-PH805-1A": ufs,
"SM8250:CD90-PH806-1A": ufs,
"saipan": ufs,
"sc8180x": ufs,
"bitra": ufs,
"cedros": ufs,
"chitwan": ufs,
"lahaina": ufs,
"lahaina_premier": ufs,
"mannar": ufs,
"rennell": ufs,
"sd7250": ufs,
"nicobar": ufs,
"agatti": ufs,
"kamorta": ufs,
"kamortap": ufs,
# "MSM7227A": [[], [], []],
# "MSM8210": [[], [0xFC4B8000,0x6FFF], []],
# "MSM8212": [[], [], []],
# "MSM8610": [[], [0xFC4B8000,0x6FFF], []],
# "MSM8226": [[], [0xFC4B8000,0x6FFF], []],
# "MSM8926": [[], [0xFC4B8000,0x6FFF], []],
# "MSM8928": [[], [], []],
}
secureboottbl = {
"QDF2432": 0x019018c8,
"QCA6290": 0x01e20030,
"QCA6390": 0x01e20010,
"IPQ4018": 0x00058098,
"IPQ4019": 0x00058098,
"APQ8009": 0x00058098,
"APQ8016": 0x00058098,
"APQ8036": 0x00058098,
"APQ8039": 0x00058098,
"APQ8037": 0x000a01d0,
"APQ8053": 0x000a01d0,
"APQ8052": 0x00058098,
"APQ8056": 0x000a01d0,
"APQ8076": 0x000a01d0,
"APQ8084": 0xFC4B83F8,
"APQ8092": 0xFC4B83F8,
"APQ8098": 0x00780350,
"MSM8226": 0xFC4B83E8,
"MSM8610": 0xFC4B83E8,
"MSM8905": 0x00058098,
"MSM8909": 0x00058098,
"MSM8909W": 0x00058098,
"MSM8916": 0x00058098,
"MSM8917": 0x000A01D0,
"MSM8920": 0x000A01D0,
"MSM8929": 0x00058098,
"MSM8930": 0x700310,
"MSM8936": 0x700310,
"MSM8937": 0x000A01D0,
"MSM8939": 0x00058098,
"MSM8940": 0x000A01D0,
"MSM8952": 0x00058098,
"MSM8953": 0x000a01d0,
"MSM8956": 0x000a01d0,
"MSM8974": 0xFC4B83F8,
"MSM8974AB": 0xFC4B83F8,
"MSM8974ABv3": 0xFC4B83F8,
"MSM8974AC": 0xFC4B83F8,
"MSM8976": 0x000a01d0,
"MSM8992": 0xFC4B83F8,
"MSM8994": 0xFC4B83F8,
"MSM8996": 0x00070378,
"MSM8996AU": 0x00070378,
"MSM8996Pro": 0x00070378,
"MSM8998_SDM835": 0x00780350,
"MDM9205": 0x000a0320,
"MDM9206_MDM9207tx": 0x000a01d0,
"MDM9250": 0x000a01d0,
"MDM9350": 0x000a01d0,
"MDM9207": 0x000a01d0,
"MDM9607": 0x000a01d0,
"MDM9x25": 0xFC4B6028,
"MDM9x30": 0xFC4B6028,
"MDM9x35": 0xFC4B6028,
"MDM9x40": 0x00058098,
"MDM9x45": 0x00058098,
"MDM9650": 0x000a01d0,
"MDM9x50": 0x000a01d0,
"MDM9x55": 0x000a01d0,
"MDM9x60": 0x000a01d0,
"MDM9x65": 0x000a01d0,
"SDM429": 0x000a01d0,
"SDM439": 0x000a01d0,
"SDM450": 0x000a01d0,
"SDM630": 0x00780350,
"SDM632": 0x000a01d0,
"SDA632": 0x000a01d0,
"SDM636": 0x00780350,
"SDM660": 0x00780350,
"SDM670": 0x00780350, # Warlock
"SDA670": 0x00780350,
"SDM710": 0x00780350,
"QCS605": 0x00780350,
"SXR1120": 0x00780350,
"SXR1130": 0x00780350,
"SDM845": 0x00780350,
"SDA845": 0x00780350,
"SDX24" : 0x00780390,
"SDX24M": 0x00780390,
"SDX50M": 0x000a01e0,
"SDX55:CD90-PG591": 0x007805E8,
"SDX55:CD90-PH809": 0x007805E8,
"SDX55M" : 0x007804D0,
"SA515M" : 0x007804D0,
"SM6150": 0x00780360,
"SM6150p": 0x00780360,
"SM7150": 0x00780460,
"SM7150p": 0x00780460,
"SDM855_SM8150": 0x007804D0,
"SDM855p_SM8150p": 0x007804D0,
"SM8250:CD90-PH805-1A": 0x007805E8,
"SM8250:CD90-PH806-1A": 0x007805E8,
"agatti": 0x01B40458,
"bitra": 0x007804D8,
"bitra_SDM": 0x007804D8,
"bitra_SDA": 0x007804D8,
"cedros": 0x00780728,
"chitwan": 0x00780668,
"ipq5018": 0x000A01D0,
"ipq6018": 0x000A01D0,
"saipan": 0x007805E8,
"sd7250": 0x007805E8,
"sc8180x": 0x007805E8,
"qcs403": 0x000a0310,
"qcs405": 0x000a0310,
"nicobar": 0x01B40458,
"kamorta": 0x01B40458,
"kamorta_p": 0x01B40458,
"lahaina": 0x780668,
"lahaina_premier": 0x780668,
"mannar": 0x01B40458,
"qm215": 0x000a01d0,
"rennell":0x000780498
# "MSM7227A":[[], [], []],
# "MSM8210": [[], [], []],
# "MSM8212":
# "MSM8926": [[], [], []],
# "MSM8928": [[], [], []],
}

10
edl/Config/usb_ids.py Normal file
View file

@ -0,0 +1,10 @@
default_ids = [
[0x05c6, 0x9008, -1],
[0x05c6, 0x900e, -1],
[0x05c6, 0x9025, -1],
[0x1199, 0x9062, -1],
[0x1199, 0x9070, -1],
[0x1199, 0x9090, -1],
[0x0846, 0x68e0, -1],
[0x19d2, 0x0076, -1]
]

View file

View file

@ -0,0 +1,113 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# (c) B.Kerler 2018-2021
import logging
from edl.Library.utils import LogBase
class generic(metaclass=LogBase):
def __init__(self, fh, serial, args, loglevel):
self.fh = fh
self.serial = serial
self.args = args
self.__logger.setLevel(loglevel)
self.error=self.__logger.error
if loglevel == logging.DEBUG:
logfilename = "log.txt"
fh = logging.FileHandler(logfilename)
self.__logger.addHandler(fh)
def oem_unlock(self, enable):
res = self.fh.detect_partition(self.args, "config")
if res[0]:
lun = res[1]
rpartition = res[2]
offsettopatch = 0x7FFFF
sector = rpartition.sector + (offsettopatch // self.fh.cfg.SECTOR_SIZE_IN_BYTES)
offset = offsettopatch % self.fh.cfg.SECTOR_SIZE_IN_BYTES
if enable:
value = 0x1
else:
value = 0x0
size_in_bytes = 1
if self.fh.cmd_patch(lun, sector, offset, value, size_in_bytes, True):
print(f"Patched sector {str(rpartition.sector)}, offset {str(offset)} with value {value}, " +
f"size in bytes {size_in_bytes}.")
else:
print(f"Error on writing sector {str(rpartition.sector)}, offset {str(offset)} with value {value}, " +
f"size in bytes {size_in_bytes}.")
else:
"""
#define DEVICE_MAGIC "ANDROID-BOOT!"
#define DEVICE_MAGIC_SIZE 13
#define MAX_PANEL_ID_LEN 64
#define MAX_VERSION_LEN 64
#if VBOOT_MOTA
struct device_info
{
unsigned char magic[DEVICE_MAGIC_SIZE];
bool is_unlocked;
bool is_tampered;
bool is_verified;
bool charger_screen_enabled;
char display_panel[MAX_PANEL_ID_LEN];
char bootloader_version[MAX_VERSION_LEN];
char radio_version[MAX_VERSION_LEN];
bool is_unlock_critical;
};
#else
struct device_info
{
unsigned char magic[DEVICE_MAGIC_SIZE];
bool is_unlocked; #0x10
bool is_tampered; #0x14
bool charger_screen_enabled; #0x18
char display_panel[MAX_PANEL_ID_LEN];
char bootloader_version[MAX_VERSION_LEN];
char radio_version[MAX_VERSION_LEN];
bool verity_mode; // 1 = enforcing, 0 = logging
bool is_unlock_critical;
};
#endif
"""
res = self.fh.detect_partition(self.args, "devinfo")
if res[0]:
lun = res[1]
rpartition = res[2]
offsettopatch1 = 0x10 # is_unlocked
offsettopatch2 = 0x18 # is_critical_unlocked
offsettopatch3 = 0x7FFE10 # zte
offsettopatch4 = 0x7FFE18 # zte
sector1, offset1 = self.fh.calc_offset(rpartition.sector, offsettopatch1)
sector2, offset2 = self.fh.calc_offset(rpartition.sector, offsettopatch2)
sector3, offset3 = self.fh.calc_offset(rpartition.sector, offsettopatch3)
sector4, offset4 = self.fh.calc_offset(rpartition.sector, offsettopatch4)
value = 0x1
size_in_bytes = 1
if self.fh.cmd_patch(lun, sector1, offset1, 0x1, size_in_bytes, True):
if self.fh.cmd_patch(lun, sector2, offset2, 0x1, size_in_bytes, True):
print(
f"Patched sector {str(rpartition.sector)}, offset {str(offset1)} with value {value}, " +
f"size in bytes {size_in_bytes}.")
data = self.fh.cmd_read_buffer(lun, rpartition.sector, rpartition.sectors)
if (len(data) > 0x7FFE20) and data[0x7FFE00:0x7FFE10] == b"ANDROID-BOOT!\x00\x00\x00":
if self.fh.cmd_patch(lun, sector3, offset3, value, size_in_bytes, True):
if self.fh.cmd_patch(lun, sector4, offset4, value, size_in_bytes, True):
print(
f"Patched sector {str(rpartition.sector)}, offset {str(offset1)} with " +
f"value {value}, size in bytes {size_in_bytes}.")
return True
print(
f"Error on writing sector {str(rpartition.sector)}, offset {str(offset1)} with value {value}, " +
f"size in bytes {size_in_bytes}.")
return False
else:
fpartitions = res[1]
self.error(f"Error: Couldn't detect partition: \"devinfo\"\nAvailable partitions:")
for lun in fpartitions:
for rpartition in fpartitions[lun]:
if self.args["--memory"].lower() == "emmc":
self.error("\t" + rpartition)
else:
self.error(lun + ":\t" + rpartition)

View file

@ -0,0 +1,99 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# (c) B.Kerler 2018-2021
import logging
from edl.Library.utils import LogBase
try:
from edl.Library.Modules.generic import generic
except ImportError as e:
generic = None
pass
try:
from edl.Library.Modules.oneplus import oneplus
except ImportError as e:
oneplus = None
pass
try:
from edl.Library.Modules.xiaomi import xiaomi
except ImportError as e:
xiaomi = None
pass
class modules(metaclass=LogBase):
def __init__(self, fh, serial, supported_functions, loglevel, devicemodel, args):
self.fh = fh
self.args = args
self.serial = serial
self.error = self.__logger.error
self.supported_functions = supported_functions
self.__logger.setLevel(loglevel)
if loglevel==logging.DEBUG:
logfilename = "log.txt"
fh = logging.FileHandler(logfilename)
self.__logger.addHandler(fh)
self.options = {}
self.devicemodel = devicemodel
self.generic = None
try:
self.generic = generic(fh=self.fh, serial=self.serial, args=self.args, loglevel=loglevel)
except Exception as e:
pass
self.ops = None
try:
self.ops = oneplus(fh=self.fh, projid=self.devicemodel, serial=self.serial,
supported_functions=self.supported_functions, args=self.args,loglevel=loglevel)
except Exception as e:
pass
self.xiaomi=None
try:
self.xiaomi = xiaomi(fh=self.fh)
except Exception as e:
pass
def addpatch(self):
if self.ops is not None:
return self.ops.addpatch()
return ""
def addprogram(self):
if self.ops is not None:
return self.ops.addprogram()
return ""
def edlauth(self):
if self.xiaomi is not None:
return self.xiaomi.edl_auth()
return True
def writeprepare(self):
if self.ops is not None:
return self.ops.run()
return True
def run(self, command, args):
args = args.split(",")
options = {}
for i in range(len(args)):
if "=" in args[i]:
option = args[i].split("=")
if len(option) > 1:
options[option[0]] = option[1]
else:
options[args[i]] = True
if command=="":
print("Valid commands are:\noemunlock\n")
return False
if self.generic is not None and command == "oemunlock":
if "enable" in options:
enable = True
elif "disable" in options:
enable = False
else:
self.error("Unknown mode given. Available are: enable, disable.")
return False
return self.generic.oem_unlock(enable)
return False

602
edl/Library/Modules/oneplus.py Executable file
View file

@ -0,0 +1,602 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# (c) B.Kerler 2018-2021
"""
Usage:
oneplus.py rawxml [--projid=value] [--serial=value]
oneplus.py rawnewxml [--projid=value] [--ts=value] [--serial=value]
oneplus.py setprojmodel_verify <token> <pk> [--projid=value]
oneplus.py setswprojmodel_verify <token> <pk> [--projid=value] [--ts=value]
oneplus.py program_verify <token> <prog_token> <pk> [--projid=value]
Options:
--projid=value Set the appropriate projid [default: 18825]
--serial=value Set the appropriate serial [default: 123456]
--ts=value Set the device timestamp [default: 1604949411]
"""
import time
import random
from struct import pack
import logging
from edl.Library.utils import LogBase
try:
from edl.Library.cryptutils import cryptutils
except Exception as e:
print(e)
from ..cryptutils import cryptutils
from binascii import unhexlify, hexlify
deviceconfig = {
# OP5, cheeseburger
"16859": dict(version=1, cm=None, param_mode=0),
# OP5t, dumpling
"17801": dict(version=1, cm=None, param_mode=0),
# OP6, enchilada
"17819": dict(version=1, cm=None, param_mode=0),
# OP6t, fajita
"18801": dict(version=1, cm=None, param_mode=0),
# OP6t T-Mo, fajitat
"18811": dict(version=1, cm=None, param_mode=0),
# Oneplus 7, guacamoleb
"18857": dict(version=1, cm=None, param_mode=0),
# Oneplus 7 Pro, guacamole
"18821": dict(version=1, cm=None, param_mode=0),
# Oneplus 7 Pro 5G Sprint, guacamoles
"18825": dict(version=1, cm=None, param_mode=0),
# Oneplus 7 Pro 5G EE and Elisa, guacamoleg
"18827": dict(version=1, cm=None, param_mode=0),
# Oneplus 7 Pro T-Mo, guacamolet
"18831": dict(version=1, cm=None, param_mode=0),
# Oneplus 7t, hotdogb
"18865": dict(version=1, cm=None, param_mode=0),
# Oneplus 7t T-Mo, hotdogt
"19863": dict(version=1, cm=None, param_mode=0),
# Oneplus 7t Pro, hotdog
"19801": dict(version=1, cm=None, param_mode=0),
# Oneplus 7t Pro 5G T-Mo, hotdogg
"19861": dict(version=1, cm=None, param_mode=0),
# OP8, instantnoodle
"19821": dict(version=2, cm="0cffee8a", param_mode=0),
# OP8 T-Mo, instantnoodlet
"19855": dict(version=2, cm="6d9215b4", param_mode=0),
# OP8 Verizon, instantnoodlev
"19867": dict(version=2, cm="4107b2d4", param_mode=0),
# OP8 Visible, instantnoodlevis
"19868": dict(version=-1, cm="178d8213", param_mode=0),
# OP8 Pro, instantnoodlep
"19811": dict(version=2, cm="40217c07", param_mode=0),
# OP8t, kebab
"19805": dict(version=2, cm="1a5ec176", param_mode=0),
# OP8t T-Mo, kebabt
"20809": dict(version=2, cm="d6bc8c36", param_mode=0),
# OP Nord, avicii
"20801": dict(version=2, cm="eacf50e7", param_mode=0),
# OP N10 5G Metro, billie8t
"20885": dict(version=3, cm="3a403a71", param_mode=1),
# OP N10 5G Global, billie8
"20886": dict(version=3, cm="b8bd9e39", param_mode=1),
# billie8t, OP N10 5G TMO
"20888": dict(version=3, cm="142f1bd7", param_mode=1),
# OP N10 5G Europe, billie8
"20889": dict(version=3, cm="f2056ae1", param_mode=1),
# OP N100 Metro, billie2t
"20880": dict(version=3, cm="6ccf5913", param_mode=1),
# OP N100 Global, billie2
"20881": dict(version=3, cm="fa9ff378", param_mode=1),
# OP N100 TMO, billie2t
"20882": dict(version=3, cm="4ca1e84e", param_mode=1),
# OP N100 Europe, billie2
"20883": dict(version=3, cm="ad9dba4a", param_mode=1),
# OP9 Pro, lemonadep
"19815": dict(version=2, cm="9c151c7f", param_mode=0),
"20859": dict(version=2, cm="9c151c7f", param_mode=0),
# OP9, lemonade
"19825": dict(version=2, cm="0898dcd6", param_mode=1),
# OP9R, lemonades
"20828": dict(version=2, cm=None, param_mode=1),
# OP9 TMO, lemonadet
"20854": dict(version=2, cm="16225d4e", param_mode=1),
# OP9 Pro TMO, lemonadept
"2085A": dict(version=2, cm="7f19519a", param_mode=1),
# dre8t
"20818": dict(version=2, cm=None, param_mode=1),
# dre8m
"2083C": dict(version=2, cm=None, param_mode=1),
# dre9
"2083D": dict(version=2, cm=None, param_mode=1)
}
class oneplus(metaclass=LogBase):
def __init__(self, fh, projid="18825", serial=123456, ATOBuild=0, Flash_Mode=0, cf=0, supported_functions=None,
args=None, loglevel=logging.INFO):
self.fh = fh
self.__logger=self.__logger
self.args = args
self.ATOBuild = ATOBuild
self.Flash_Mode = Flash_Mode
self.cf = cf # CustFlag
self.supported_functions = supported_functions
self.__logger.setLevel(loglevel)
self.info = self.__logger.info
self.debug = self.__logger.debug
self.error = self.__logger.error
if projid == "":
res = self.fh.detect_partition(arguments=args, partitionname="param")
if res[0]:
lun = res[1]
rpartition = res[2]
data = self.fh.cmd_read_buffer(lun, rpartition.sector, 1, False)
value = data[24:24 + 5]
try:
test = int(value.decode('utf-8'))
self.info("Oneplus protection with prjid %d detected" % test)
projid = value.decode('utf-8')
except:
pass
if loglevel == logging.DEBUG:
logfilename = "log.txt"
filehandler = logging.FileHandler(logfilename)
self.__logger.addHandler(filehandler)
self.ops_parm = None
self.ops = self.convert_projid(fh, projid, serial)
def getprodkey(self, projid):
if projid in ["18825", "18801"]: # key_guacamoles, fajiita
prodkey = "b2fad511325185e5"
else: # key_op7t/op8/N10
prodkey = "7016147d58e8c038"
return prodkey
def convert_projid(self, fh, projid, serial):
prodkey = self.getprodkey(projid)
pk = ""
val = bytearray(b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
for i in range(0, 16):
rand = int(random.randint(0, 0x100))
nr = (rand & 0xFF) % 0x3E
pk += chr(val[nr])
if projid in deviceconfig:
version = deviceconfig[projid]["version"]
cm = deviceconfig[projid]["cm"]
if version == 1:
return oneplus1(fh, projid, serial, pk, prodkey, self.cf)
elif version == 2:
if cm is not None:
return oneplus1(fh, cm, serial, pk, prodkey, self.cf)
else:
assert "Device is not supported"
exit(0)
elif version == 3:
if cm is not None:
oneplus2(fh, cm, serial, pk, prodkey, self.ATOBuild, self.Flash_Mode, self.cf)
else:
assert "Device is not supported"
exit(0)
return None
def run(self):
if self.ops is not None:
if "demacia" in self.supported_functions:
if not self.ops.run("demacia"):
exit(0)
if "SetNetType" in self.supported_functions:
self.fh.cmd_send(f"SetNetType")
elif "setprojmodel" in self.supported_functions:
if not self.ops.run(""):
exit(0)
if "setprocstart" in self.supported_functions:
if not self.ops.run(""):
exit(0)
return True
def setprojmodel_verify(self, pk, token):
if self.ops.setprojmodel_verify:
return self.ops.setprojmodel_verify(pk, token)
def setswprojmodel_verify(self, pk, token, device_timestamp):
if self.ops.setswprojmodel_verify:
return self.ops.setswprojmodel_verify(pk, token, device_timestamp)
def program_verify(self, pk, token, tokendata):
if self.ops.program_verify:
return self.ops.program_verify(pk, token, tokendata)
def generatetoken(self, program=False, device_timestamp="123456789"):
return self.ops.generatetoken(program=program, device_timestamp=device_timestamp)
def demacia(self):
if self.ops.demacia():
return self.ops.demacia()
def enable_ops(self, data, enable, projid, serial):
return None
def addpatch(self):
if "setprojmodel" in self.supported_functions or "setswprojmodel" in self.supported_functions:
pk, token = self.ops.generatetoken(True)
return f"pk=\"{pk}\" token=\"{token}\" "
else:
return ""
def addprogram(self):
if "setprojmodel" in self.supported_functions or "setswprojmodel" in self.supported_functions:
pk, token = self.ops.generatetoken(True)
return f"pk=\"{pk}\" token=\"{token}\" "
else:
return ""
class oneplus1:
def __init__(self, fh, ModelVerifyPrjName="18825", serial=123456, pk="", prodkey="", cf=0):
self.pk = pk
self.prodkey = prodkey
self.ModelVerifyPrjName = ModelVerifyPrjName
self.fh = fh
self.random_postfix = "8MwDdWXZO7sj0PF3"
self.Version = "guacamoles_21_O.22_191107"
self.cf = str(cf)
self.soc_sn = str(serial)
def crypt_token(self, data, pk, decrypt=False, demacia=False):
aes = cryptutils().aes()
if demacia:
aeskey = b"\x01\x63\xA0\xD1\xFD\xE2\x67\x11" + bytes(pk, 'utf-8') + b"\x48\x27\xC2\x08\xFB\xB0\xE6\xF0"
aesiv = b"\x96\xE0\x79\x0C\xAE\x2B\xB4\xAF\x68\x4C\x36\xCB\x0B\xEC\x49\xCE"
else:
aeskey = b"\x10\x45\x63\x87\xE3\x7E\x23\x71" + bytes(pk, 'utf-8') + b"\xA2\xD4\xA0\x74\x0f\xD3\x28\x96"
aesiv = b"\x9D\x61\x4A\x1E\xAC\x81\xC9\xB2\xD3\x76\xD7\x49\x31\x03\x63\x79"
if decrypt:
cdata = unhexlify(data)
result = aes.aes_cbc(aeskey, aesiv, cdata)
result = result.rstrip(b'\x00')
if result[:16] == b"907heavyworkload":
return result
else:
return result.decode('utf-8').split(',')
else:
if not demacia:
while len(data) < 256:
data += "\x00"
pdata = bytes(data, 'utf-8')
else:
while len(data) < 256:
data += b"\x00"
pdata = data
result = aes.aes_cbc(aeskey, aesiv, pdata, False)
rdata = hexlify(result)
return rdata.upper().decode('utf-8')
def cmd_setpro(self):
pk, token = self.generatetoken(False)
data = "<?xml version=\"1.0\" ?>\n<data>\n<setprojmodel token=\"" + token + "\" pk=\"" + pk + "\" />\n</data>"
return data
def cmd_dem(self):
pk, token = self.demacia()
data = "<?xml version=\"1.0\" ?>\n<data>\n<demacia token=\"" + token + "\" pk=\"" + pk + "\" />\n</data>"
return data
def generatetoken(self, program=False, device_timestamp=None):
timestamp = str(int(time.time()))
ha = cryptutils().hash()
h1 = self.prodkey + self.ModelVerifyPrjName + self.random_postfix
ModelVerifyHashToken = hexlify(ha.sha256(bytes(h1, 'utf-8'))).decode('utf-8').upper()
# ModelVerifyPrjName=0x1C [0]
# random_postfix=0x2D [1]
# verify_hash=0x3E [2] Len:0x41
# ver=0x90 [3]
# cf=0x4 [4]
# sn=0x14 [5]
# ts=0x7f [6] Len:0x11
# secret=0xd1 (hash store) [7], len:0x41
# 0x7, Len:0x11
# 0x24, Len:0x41
# 0x1c 0x4 0x14 0x90 0x7f ModelVerifyHashToken
h2 = "c4b95538c57df231" + self.ModelVerifyPrjName + self.cf + self.soc_sn + self.Version + \
timestamp + ModelVerifyHashToken + "5b0217457e49381b"
secret = hexlify(ha.sha256(bytes(h2, 'utf-8'))).decode('utf-8').upper() # 0xd1
if program:
items = [timestamp, secret]
else:
items = [self.ModelVerifyPrjName, self.random_postfix, ModelVerifyHashToken, self.Version, self.cf,
self.soc_sn, timestamp, secret]
data = ""
for item in items:
data += item + ","
data = data[:-1]
token = self.crypt_token(data, self.pk)
return self.pk, token
def setprojmodel_verify(self, pk, token):
self.pk = pk
ha = cryptutils().hash()
items = self.crypt_token(token, pk, True, False)
info = ["Projid", "ModelVerifyHashToken", "Hash1", "FirmwareString", "CustFlag", "SOC_Serial", "Timestamp",
"secret"]
i = 0
print()
if len(info) == len(items):
for item in items:
print(info[i] + "=" + item)
i += 1
# Old
# 0=ModelVerifyPrjName [param+0x1C]
# 1=random_postfix [param+0x2D]
# 2=hash(key+0+1) [ModelVerifyHashToken param+0x3E]
# 3=ver [param_1+0x90]
# 4=cf [param_1+4]
# 5=serial? [param_1+0x14]
# 6=timestamp [param_1+0x7F]
hash1 = self.prodkey + items[0] + items[1]
res1 = hexlify(ha.sha256(bytes(hash1, 'utf-8'))).decode('utf-8').upper()
if items[2] != res1:
print("Hash1 failed !")
return
# ModelVerifyPrjName cf sn ver ts ModelVerifyHashToken
secret = "c4b95538c57df231" + items[0] + items[4] + items[5] + items[3] + items[6] + \
items[2] + "5b0217457e49381b"
res2 = hexlify(ha.sha256(bytes(secret, 'utf-8'))).decode('utf-8').upper()
if items[7] != res2:
print("secret failed !")
return
print("setprojmodel good")
return items
def toSigned32(self, n):
n = n & 0xffffffff
return (n ^ 0x80000000) - 0x80000000
def demacia(self):
"""
return "<?xml version=\"1.0\" ?>\n<data>\n " + \
"<program SECTOR_SIZE_IN_BYTES=\"4096\" filename=\"param.bin\" " + \
"num_partition_sectors=\"256\" partofsingleimage=\"0\" physical_partition_number=\"0\" " + \
"read_back_verify=\"1\" start_sector=\"8456\" token=\""+token+"\" pk=\""+pk+"\" />\n</data>"
"""
ha = cryptutils().hash()
serial = self.soc_sn
while len(serial) < 10:
serial = '0' + serial
hash1 = "2e7006834dafe8ad" + serial + "a6674c6b039707ff"
data = b"907heavyworkload" + ha.sha256(bytes(hash1, 'utf-8'))
token = self.crypt_token(data, self.pk, False, True)
return self.pk, token
def run(self, flag):
if flag == "demacia":
pk, token = self.demacia()
res = self.fh.cmd_send(f"demacia token=\"{token}\" pk=\"{pk}\"")
if b"verify_res=\"0\"" not in res:
print("Demacia failed:")
print(res)
return False
pk, token = self.generatetoken(False)
res = self.fh.cmd_send(f"setprojmodel token=\"{token}\" pk=\"{pk}\"")
if b"model_check=\"0\"" not in res or b"auth_token_verify=\"0\"" not in res:
print("Setprojmodel failed.")
print(res)
return False
return True
def program_verify(self, pk, token, tokendata):
print()
self.pk = pk
items = self.crypt_token(token, pk, True, False)
if len(items) == 2:
print("Timestamp=" + items[0])
print("secret=" + items[1])
if items[0] != tokendata[6] or items[1] != tokendata[7]:
print("Hash failed !")
return
print("program good")
class oneplus2(metaclass=LogBase):
def __init__(self, fh, ModelVerifyPrjName="20889", serial=123456, pk="", prodkey="", ATOBuild=0, Flash_Mode=0,
cf=0, loglevel=logging.INFO):
self.ModelVerifyPrjName = ModelVerifyPrjName
self.pk = pk
self.fh = fh
self.prodkey = prodkey
self.random_postfix = "c75oVnz8yUgLZObh" # ModelVerifyRandom
self.Version = "billie8_14_E.01_201028" # Version
self.device_id = str(int(ModelVerifyPrjName, 16))
self.flash_mode = str(Flash_Mode)
self.ato_build_state = str(ATOBuild)
self.soc_sn = str(serial)
self.__logger.setLevel(loglevel)
if loglevel == logging.DEBUG:
logfilename = "log.txt"
fh = logging.FileHandler(logfilename)
self.__logger.addHandler(fh)
def crypt_token(self, data, pk, device_timestamp, decrypt=False):
aes = cryptutils().aes()
aeskey = b"\x46\xA5\x97\x30\xBB\x0D\x41\xE8" + bytes(pk, 'utf-8') + \
pack("<Q", int(device_timestamp, 10)) # we get this using setprocstart
aesiv = b"\xDC\x91\x0D\x88\xE3\xC6\xEE\x65\xF0\xC7\x44\xB4\x02\x30\xCE\x40"
if decrypt:
cdata = unhexlify(data)
result = aes.aes_cbc(aeskey, aesiv, cdata)
result = result.rstrip(b'\x00')
return result.decode('utf-8').split(',')
else:
while len(data) < 0x200:
data += "\x00"
pdata = bytes(data, 'utf-8')
result = aes.aes_cbc(aeskey, aesiv, pdata, False)
rdata = hexlify(result)
return rdata.upper().decode('utf-8')
def generatetoken(self, program=False, device_timestamp=None): # setswprojmodel
timestamp = str(int(time.time()))
ha = cryptutils().hash()
h1 = self.prodkey + self.ModelVerifyPrjName + self.random_postfix
ModelVerifyHashToken = hexlify(ha.sha256(bytes(h1, 'utf-8'))).decode('utf-8').upper()
# 0x1c[0x10] 0x4 0x90 0x7f 0x3e ModelVerifyHashToken/verify_hash
h2 = self.prodkey + self.ModelVerifyPrjName + self.soc_sn + self.Version + timestamp + \
ModelVerifyHashToken + "8f7359c8a2951e8c"
secret = hexlify(ha.sha256(bytes(h2, 'utf-8'))).decode('utf-8').upper()
if program:
items = [timestamp, secret]
else: # 0x1C[0x10] 0x2D[0x11] 0x3E[0x41] 0x10[0x4]
# 0x14[0x4] 0x90[0x40] 0x4[0x4] 0x114[0x4] 0x7F[0x10] 0xD1[0x41]
items = [self.ModelVerifyPrjName, self.random_postfix, ModelVerifyHashToken, self.ato_build_state,
self.flash_mode, self.Version, self.soc_sn, self.device_id, timestamp, secret]
data = ""
for item in items:
data += item + ","
data = data[:-1]
token = self.crypt_token(data, self.pk, device_timestamp)
return self.pk, token
def run(self, flag):
res = self.fh.cmd_send(f"setprocstart")
if not b"device_timestamp" in res:
print("Setprocstart failed.")
print(res.decode('utf-8'))
return False
data = res.decode('utf-8')
device_timestamp = data[data.rfind("device_timestamp"):].split("\"")[1]
pk, token = self.generatetoken(False, device_timestamp)
res = self.fh.cmd_send(f"setswprojmodel token=\"{token}\" pk=\"{pk}\"")
if not b"model_check=\"0\"" in res or not b"auth_token_verify=\"0\"" in res:
print("Setswprojmodel failed.")
print(res.decode('utf-8'))
return False
return True
def setswprojmodel_verify(self, pk, token, device_timestamp):
self.pk = pk
ha = cryptutils().hash()
items = self.crypt_token(token, pk, device_timestamp, True)
info = ["ModelVerifyPrjName", "random_postfix", "ModelVerifyHashToken", "ato_build_state", "flash_mode",
"Version", "soc_sn", "cf", "timestamp", "secret"]
i = 0
print()
if len(info) == len(items):
for item in items:
print(info[i] + "=" + item)
i += 1
# New
# 0=ModelVerifyPrjName [param+0x1C]
# 1=random_postfix [param+0x2D]
# 2=hash [param_1+0xd1]
# 3=[ATO Build state param+0x3E]
# 4=[flash mode param_1+0x10]
# 5=[img ver param+0x14]
# 6=soc sn [param_1+0x90]
# 7=cf [param_1+4]
# 8=timestamp [param_1+0x114]
# 9=secret [param_1+0x7f]
hash1 = self.prodkey + items[0] + items[1]
res1 = hexlify(ha.sha256(bytes(hash1, 'utf-8'))).decode('utf-8').upper()
if items[2] != res1:
print("Hash1 failed !")
return
# ModelVerifyPrjName sn ver ts ModelVerifyHashToken
secret = self.prodkey + items[0] + items[6] + items[5] + items[8] + items[2] \
+ "8f7359c8a2951e8c"
# h2 = self.prodkey + self.ModelVerifyPrjName + self.soc_sn + self.Version + timestamp +
# ModelVerifyHashToken + "8f7359c8a2951e8c"
res2 = hexlify(ha.sha256(bytes(secret, 'utf-8'))).decode('utf-8').upper()
if items[9] != res2:
print("secret failed !")
return
print("setswprojmodel good")
return items
def main():
from docopt import docopt
args = docopt(__doc__, version='oneplus 1.2')
# filename="param_jacob_7pro.bin"
# filename="chris/param.bin"
if args["rawxml"]:
projid = args["--projid"][0]
serial = args["--serial"]
op = oneplus(None, projid=projid, serial=serial)
# 18831 2799496336,2799496336
# 18825
# 18857 7 Europe
pk, token = op.demacia()
print(
f"./edl.py rawxml \"<?xml version=\\\"1.0\\\" ?><data><demacia " +
f"token=\\\"{token}\\\" pk=\\\"{pk}\\\" /></data>\"")
pk, token = op.generatetoken(False)
print(
f"./edl.py rawxml \"<?xml version=\\\"1.0\\\" ?><data><setprojmodel " +
f"token=\\\"{token}\\\" pk=\\\"{pk}\\\" /></data>\" --debugmode")
elif args["rawnewxml"]:
serial = args["--serial"]
device_timestamp = args["--ts"]
op2 = oneplus(None, projid="20889", serial=serial, ATOBuild=0, Flash_Mode=0, cf=0)
# 20889 OP N10 5G Europe
print(f"./edl.py rawxml \"<?xml version=\\\"1.0\\\" ?><data><setprocstart /></data>\"")
# Response should be : <?xml version="1.0" ?><data><response value=1 device_timestamp="%llu" /></data>
pk, token = op2.generatetoken(False, device_timestamp)
print(
f"./edl.py rawxml \"<?xml version=\\\"1.0\\\" ?><data><setswprojmodel " +
f"token=\\\"{token}\\\" pk=\\\"{pk}\\\" /></data>\" --debugmode")
elif args["setprojmodel_verify"]:
projid = args["--projid"][0]
op = oneplus(None, projid=projid, serial=123456)
token = args["<token>"]
pk = args["<pk>"]
# setprojmodel_verify 2BA77B345812E4E45DDB5E407CF9B0F20BCD3E4F0C504A86A3DAA7D70643D0D86F4F5DAEE99E21093D26FF8A8A
# 7C2CCFED387FA4C7D3BC6D8B8C2CC2D27D398886FC150C98CDC521699568C4A419D7E2F2C1A33F6B57AA7CCB5F
# 39D69BB87463986B2CADDD55A41F0F9404C3FB08B0325BFDFCFDE05D1D8314D22F39979A289505D5050D854092
# CFC9FA3C101A267DD3ECA0442BF89066365ABA6607D43743D86B47B228BAAC5538B622644D74FD4049BE37C520
# 76DE1B4BFE75187A7B0EE88E6C26E106570B8C0541C4693878BE9B23DEB8E4C530CFBFE9F25597FA3A86223711
# 2CAF77F0D1EA4CC41EB201FFAE31036FC9E405BABAE43DE9C7E56FE1DC8E82 KHaJV1TfN45ofeLW 18865
# setprojmodel_verify 633B7E2BBE68BAC392B3E10FC8FEAC09F152853805A6D91FAADDE5A631C7B5A6081C6156F7344BDF407ABF7598
# 0A9E6DA96964D472FE94311FEAADF6A9032C623A1C5D5B9BDD68C5E049F13DF9D893422C1A44047B1AC8E05A0A
# 2A942B15B409A933A06BAB09F41FB0A3A5C8FEB86B98D39739FA4E2ABDF471DE181646F7AA228C6EC81DB3BAF2
# F2C3B5381FC9A722F9D11B6A101CAAE31ACD873B83B39AC07B7603EAA38B13F5D0B5E8F9236FB94B967AECE278
# FEA280E9330636F7C6C72C36A6040F6B8BC3C56AEC9CB0C07360E14EA83D2F6DEC4613FA74D79C325A320B88F2
# BF025CF9CE528E13169BA255E68909D7E902CE494B49514F6F57713D6F46BE Tgu1kbDW3NemNNqn 18831
op.setprojmodel_verify(pk, token)
elif args["program_verify"]:
projid = args["--projid"][0]
op = oneplus(None, projid=projid)
token = args["<token>"]
prog_token = args["<prog_token>"]
pk = args["<pk>"]
items = op.setprojmodel_verify(pk, token)
op.program_verify(pk, prog_token, items)
elif args["setswprojmodel_verify"]:
projid = args["--projid"][0]
device_timestamp = args["--ts"]
op = oneplus(None, projid=projid, serial=123456)
token = args["<token>"]
pk = args["<pk>"]
op.setswprojmodel_verify(pk, token, device_timestamp)
def test_setswprojmodel_verify():
deviceresp = b"RX:<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\nRX:<data>\nRX:<response value=\"ACK\" " + \
b"device_timestamp=\"2507003650\" /></data>\n<?xmlversion=\"1.0\" ? ><data><setprocstart /></data>"
projid = "20889"
op = oneplus(None, projid=projid, serial=123456)
data = deviceresp.decode('utf-8')
device_timestamp = data[data.rfind("device_timestamp"):].split("\"")[1]
pk, token = op.generatetoken(False, device_timestamp)
if not op.setswprojmodel_verify(pk, token, device_timestamp):
assert "Setswprojmodel error"
if __name__ == "__main__":
main()

View file

@ -0,0 +1,45 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# (c) B.Kerler 2018-2021
import logging
from edl.Library.utils import LogBase
class xiaomi(metaclass=LogBase):
def __init__(self, fh, projid="18825", serial=123456, ATOBuild=0, Flash_Mode=0, cf=0, supported_functions=None,
loglevel=logging.INFO):
self.fh = fh
self.xiaomi_authdata = b"\x93\x6E\x3A\x8E\x57\x3C\xAD\x07\xC1\x67\x64\x4B\x61\x21\x78\x35\xD8\x5A\xD4\xFD" + \
b"\xDB\x7D\x84\x0A\x2B\x72\x25\x43\x2F\xCD\xA1\x3A\x7C\x19\x2C\xFA\x97\x9E\xD1\x65" + \
b"\x17\xE6\x97\x0B\x1B\x07\xDF\x6C\x51\x6F\xEC\x81\xF6\x96\x8F\xCF\x7F\xFD\xDB\xC3" + \
b"\x97\xA1\x62\xC2\xCA\x3E\x5D\x76\x12\x4A\xA1\x76\x9F\x1B\x21\x64\xB3\x9B\x76\x93" + \
b"\x0B\x4C\xC6\x75\x19\xF7\xF3\x39\x87\x76\x77\xF4\xE8\xAF\x25\x82\x86\x82\xBC\xBF" + \
b"\x4E\x59\x3A\x57\xE7\xE3\x05\x32\x69\x92\x53\xE0\xB1\xCC\x5D\x9D\x0D\x55\x4A\xF2" + \
b"\xBD\x46\xD5\x6F\x18\xD6\xE5\x29\x0B\xA4\xA0\xCA\xC2\x43\x1F\x9F\x19\xC4\xC1\xA3" + \
b"\x9D\x76\x64\xFF\xAB\x48\xA9\xE1\x1A\x55\x93\x86\x81\x98\x35\xB8\x4D\xF5\x67\x5E" + \
b"\x70\xD2\x5F\xDB\x51\x23\xE7\xB0\x40\xFE\x21\x10\x8F\x0A\xE6\xD7\xD9\xD2\x67\xF2" + \
b"\xC9\xC6\x1A\xD0\x54\xC6\x84\x93\xDC\x4D\x33\xF7\x4D\x0C\xF2\xD4\xAA\xDC\xD4\x30" + \
b"\x15\x2D\xB6\x7C\x22\xA1\x81\xAD\x6D\x77\x61\x63\x7F\x70\xCB\xDA\x88\x4C\xDC\x11" + \
b"\x33\x72\x03\x83\x77\x90\xE6\x84\x5C\xA5\xA8\x76\x79\x30\xB9\xC2\x6F\xDA\x71\x27" + \
b"\x25\x64\xCA\x34\x76\x3D\x35\x2F\x5F\xE4\x2A\xB7\x38\xFB\x38\xA5"
self.__logger.setLevel(loglevel)
if loglevel == logging.DEBUG:
logfilename = "log.txt"
fh = logging.FileHandler(logfilename)
self.__logger.addHandler(fh)
def edl_auth(self):
"""
Poco F1, Redmi 5 Pro, 6 Pro, 7 Pro, 7A, 8, 8A, 8A Dual, 8A Pro, Y2, S2
"""
authcmd = b"<?xml version=\"1.0\" ?><data> <sig TargetName=\"sig\" size_in_bytes=\"256\" verbose=\"1\"/></data>"
rsp = self.fh.xmlsend(authcmd)
if rsp[0]:
rsp = self.fh.xmlsend(self.xiaomi_authdata)
if len(rsp) > 1:
if rsp[0]:
if b"EDL Authenticated" in rsp[2] or b"ACK" in rsp[2]:
return True
return True
return False

Binary file not shown.

0
edl/Library/__init__.py Executable file
View file

247
edl/Library/asmtools.py Executable file
View file

@ -0,0 +1,247 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# (c) B.Kerler 2018-2021
from capstone import *
from keystone import *
from binascii import unhexlify
import argparse
def asm(code, cpu, mode, bigendian):
if bigendian:
little = KS_MODE_BIG_ENDIAN # big-endian mode
else:
little = KS_MODE_LITTLE_ENDIAN # little-endian mode (default mode)
print("CPU: %s, MODE: %s" % (cpu, mode))
ks = None
if cpu == "arm":
# ARM architecture (including Thumb, Thumb-2)
if mode == "arm":
ks = Ks(KS_ARCH_ARM, KS_MODE_ARM + little) # ARM mode
elif mode == "thumb":
ks = Ks(KS_ARCH_ARM, KS_MODE_THUMB + little) # THUMB mode (including Thumb-2)
elif mode == "mclass":
ks = Ks(KS_ARCH_ARM, KS_MODE_THUMB + little)
elif mode == "v8":
ks = Ks(KS_ARCH_ARM, KS_MODE_ARM + KS_MODE_V8 + little)
elif cpu == "arm64":
# ARM-64, also called AArch64
ks = Ks(KS_ARCH_ARM64, little) # ARM mode
elif cpu == "mips":
# Mips architecture
if mode == "micro":
ks = Ks(KS_ARCH_MIPS, KS_MODE_MICRO + little) # MicroMips mode
elif mode == "3":
ks = Ks(KS_ARCH_MIPS, KS_MODE_MIPS3 + little) # Mips III ISA
elif mode == "32R6":
ks = Ks(KS_ARCH_MIPS, KS_MODE_MIPS32R6 + little) # Mips32r6 ISA
elif mode == "32":
ks = Ks(KS_ARCH_MIPS, KS_MODE_MIPS32 + little) # Mips32 ISA
elif mode == "64":
ks = Ks(KS_ARCH_MIPS, KS_MODE_MIPS64 + little) # Mips64 ISA
elif cpu == "x86":
# X86 architecture (including x86 & x86-64)
if mode == "16":
ks = Ks(KS_ARCH_X86, KS_MODE_16 + little) # 16-bit mode
elif mode == "32":
ks = Ks(KS_ARCH_X86, KS_MODE_32 + little) # 32-bit mode
elif mode == "64":
ks = Ks(KS_ARCH_X86, KS_MODE_64 + little) # 64-bit mode
elif cpu == "ppc":
# PowerPC architecture (currently unsupported)
if mode == "32":
ks = Ks(KS_ARCH_PPC, KS_MODE_PPC32 + little) # 32-bit mode
elif mode == "64":
ks = Ks(KS_ARCH_PPC, KS_MODE_PPC64 + little) # 64-bit mode
elif mode == "qpx":
ks = Ks(KS_ARCH_PPC, KS_MODE_QPX + little) # Quad Processing eXtensions mode
elif cpu == "sparc":
# Sparc architecture
if mode == "32":
ks = Ks(KS_ARCH_SPARC, KS_MODE_SPARC32 + little) # 32-bit mode
elif mode == "64":
ks = Ks(KS_ARCH_SPARC, KS_MODE_SPARC64 + little) # 64-bit mode
elif mode == "v9":
ks = Ks(KS_ARCH_SPARC, KS_MODE_V9 + little) # SparcV9 mode
elif cpu == "systemz":
ks = Ks(KS_ARCH_SYSTEMZ, KS_MODE_BIG_ENDIAN) # SystemZ architecture (S390X)
elif cpu == "hexagon":
ks = Ks(KS_ARCH_HEXAGON, KS_MODE_LITTLE_ENDIAN) # QDSP6 Hexagon Qualcomm
if ks is None:
print("CPU and/or Mode not supported!")
exit(0)
encoding, count = ks.asm(code)
return encoding
def disasm(code, cpu, mode, bigendian, size):
cs = None
if bigendian:
little = CS_MODE_BIG_ENDIAN # big-endian mode
else:
little = CS_MODE_LITTLE_ENDIAN # little-endian mode (default mode)
if cpu == "arm":
if mode == "arm":
cs = Cs(CS_ARCH_ARM, CS_MODE_ARM + little) # ARM mode
elif mode == "thumb":
cs = Cs(CS_ARCH_ARM, CS_MODE_THUMB + little) # THUMB mode (including Thumb-2)
elif mode == "mclass":
cs = Cs(CS_ARCH_ARM, CS_MODE_THUMB + CS_MODE_MCLASS + little) # ARM Cortex-M
elif mode == "v8":
cs = Cs(CS_ARCH_ARM, CS_MODE_ARM + CS_MODE_V8 + little) # ARMv8 A32 encodings for ARM
elif cpu == "arm64":
cs = Cs(CS_ARCH_ARM64, CS_MODE_ARM + little)
elif cpu == "mips":
if mode == "micro":
cs = Cs(CS_ARCH_MIPS, CS_MODE_MICRO + little) # MicroMips mode
elif mode == "32":
cs = Cs(CS_ARCH_MIPS, CS_MODE_MIPS32 + little) # Mips III ISA
elif mode == "64":
cs = Cs(CS_ARCH_MIPS, CS_MODE_MIPS64 + little) # Mips III ISA
elif mode == "32R6-Micro":
cs = Cs(CS_ARCH_MIPS, CS_MODE_MIPS32R6 + CS_MODE_MICRO + little) # Mips32r6 ISA
elif mode == "32R6":
cs = Cs(CS_ARCH_MIPS, CS_MODE_MIPS32R6 + little) # General Purpose Registers are 64bit wide
elif cpu == "x86":
# X86 architecture (including x86 & x86-64)
if mode == "16":
cs = Cs(CS_ARCH_X86, CS_MODE_16 + little) # 16-bit mode
elif mode == "32":
cs = Cs(CS_ARCH_X86, CS_MODE_32 + little) # 32-bit mode
elif mode == "64":
cs = Cs(CS_ARCH_X86, CS_MODE_64 + little) # 64-bit mode
elif cpu == "ppc":
# PowerPC architecture (currently unsupported)
if mode == "64":
cs = Cs(CS_ARCH_PPC, little) # 64-bit mode
elif cpu == "sparc":
# Sparc architecture
if mode == "None":
cs = Cs(CS_ARCH_SPARC, CS_MODE_BIG_ENDIAN) # 32-bit mode
elif mode == "v9":
cs = Cs(CS_ARCH_SPARC, CS_MODE_BIG_ENDIAN + CS_MODE_V9) # SparcV9 mode
elif cpu == "systemz":
cs = Cs(CS_ARCH_SYSZ, 0) # SystemZ architecture (S390X)
elif cpu == "xcore":
cs = Cs(CS_ARCH_XCORE, 0) # XCore architecture
if cs is not None:
print("CPU and/or mode not supported!")
exit(0)
instr = []
for i in cs.disasm(code, size):
# print("0x%x:\t%s\t%s" % (i.address, i.mnemonic, i.op_str))
instr.append("%s\t%s" % (i.mnemonic, i.op_str))
return instr
def main():
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,
description='Disasm/Asm Tool (c) B. Kerler 2018')
parser.add_argument(
'--infile', '-in',
help='Input File',
default='')
parser.add_argument(
'--outfile', '-out',
help='Output File',
default='')
parser.add_argument(
'--cstyle', '-cstyle',
help='Print in c style',
action="store_true")
parser.add_argument(
'--bigendian', '-bigendian',
help='Big endian',
action="store_true")
parser.add_argument(
'--disasm', '-disasm',
help='Disasm: arm[arm,thumb,mclass,v8],arm64[arm],mips[micro,3,32R6,GP64],x86[16,32,64],ppc[64],sparc[32,64,'
'v9],systemz,xcore',
default='')
parser.add_argument(
'--asm', '-asm',
help='Asm: arm[arm,thumb,mclass,v8],arm64[arm],mips[micro,32,64,32R6,32R6-Micro],x86[16,32,64],ppc[32,64,'
'qpx],sparc[None,v9],systemz,hexagon',
default='')
parser.add_argument(
'--inp', '-input',
help='Disasm: hexstring, Asm: instruction string input ',
default='')
args = parser.parse_args()
if args.asm == '' and args.disasm == '':
print("[asmtools] Usage: -asm cpu,mode or -disasm cpu,mode")
exit(0)
if args.infile == '' and args.inp == '':
print("[asmtools] I must have an infile to work on (-in)")
exit(0)
if args.asm != "":
cpu, mode = args.asm.split(",")
else:
cpu, mode = args.disasm.split(",")
if args.inp != "":
args.inp = args.inp.replace("\\n", "\n")
if args.asm != "":
aa = asm(args.inp, cpu, mode, args.bigendian)
else:
aa = disasm(unhexlify(args.inp), cpu, mode, args.bigendian, len(args.inp))
else:
with open(args.infile, "rb") as rf:
code = rf.read()
if args.asm != "":
aa = asm(code, cpu, mode, args.bigendian)
else:
aa = disasm(code, cpu, mode, args.bigendian, len(code))
if args.outfile != "":
with open(args.outfile, "wb") as wf:
if args.asm != "":
ba = bytearray()
for i in aa:
ba.append(i)
wf.write(ba)
else:
wf.write(aa)
else:
if args.asm != "":
sc = ""
count = 0
out = ""
for i in aa:
if args.cstyle:
out += ("\\x%02x" % i)
else:
out += ("%02x" % i)
sc += "%02x" % i
count += 1
print(out)
else:
print(aa)
'''
segment=bytearray(code[0x01C97C:0x01C990])
segment[7]=0xE1
segment[11]=0xE1
segment[15]=0xE5
segment[19]=0xEA
print(hex(segment[7]))
print(disasm(bytes(segment),len(segment)))
'''
main()

500
edl/Library/cryptutils.py Executable file
View file

@ -0,0 +1,500 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# (c) B.Kerler 2018-2019
import hashlib
from Crypto.Cipher import AES
from Crypto.Util import Counter
from Crypto.Hash import CMAC
from Crypto.Util.number import long_to_bytes, bytes_to_long
from binascii import hexlify, unhexlify
class PKCS1BaseException(Exception):
pass
class DecryptionError(PKCS1BaseException):
pass
class MessageTooLong(PKCS1BaseException):
pass
class WrongLength(PKCS1BaseException):
pass
class MessageTooShort(PKCS1BaseException):
pass
class InvalidSignature(PKCS1BaseException):
pass
class RSAModulusTooShort(PKCS1BaseException):
pass
class IntegerTooLarge(PKCS1BaseException):
pass
class MessageRepresentativeOutOfRange(PKCS1BaseException):
pass
class CiphertextRepresentativeOutOfRange(PKCS1BaseException):
pass
class SignatureRepresentativeOutOfRange(PKCS1BaseException):
pass
class EncodingError(PKCS1BaseException):
pass
class InvalidInputException(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return str(self.msg)
class InvalidTagException(Exception):
def __str__(self):
return 'The authentication tag is invalid.'
class cryptutils:
class aes:
class AES_GCM:
# Galois/Counter Mode with AES-128 and 96-bit IV
"""
Example:
master_key = 0x0ADAABC70895E008147A48C27791F654 #54F69177C2487A1408E09508C7ABDA0A
init_value = 0x2883B4173F9A838437C1CD86CCFAA5ED #EDA5FACC86CDC13784839A3F17B48328
auth_tag = 46D1FA806ADA1A916E6D0D0B55A40C1F94D7820D110F3DFC984AA3EEC9D67521
ciphertext = b"\x8A\x40\x9D\xF8\x76\x09\xCA\x10\x36\xB3\xFA\x86\x20\xC5\x85\xA3"+ \
b"\xE3\x8E\x17\x14\x40\xBD\x6B\xA7\x26\x1F\x0B\xFE\xC5\x0A\xB0\xF0"+\
b"\xCF\x69\x2E\x76\x18\x6D\x96\x9E\x83\x87\x63\xC7\x15\x7C\x1F\x28"+\
b"\xEE\xE8\xF1\xD6\x1F\x02\x2A\xF1\xA2\x43\x8A\xCF\x7C\xF2\x66\x37"+\
b"\x8B\x49\x1D\xC5\xDC\xE2\x54\x77\xED\x2F\x17\x5B\xA9\xFC\x8A\x81"+\
b"\x60\xF6\x5A\x22\x39\xCA\x79\x32\x9B\xDB\x49\x50\xCE\x74\x2C\x56"+\
b"\xDB\x97\xCA\x13\xDD\x25\xA3\x3C\x0F\x53\xDD\x38\xBF\x7B\x8B\xDA"+\
b"\xD6\x74\x38\x87\x96\xA8\x10\x5A\x96\x38\x39\x7F\xFD\xEC\xC7\x62"+\
b"\x06\x44\xF4\x0F\x78\xD6\x3D\x1A\xC5\x40\x4B\x3B\x8C\xBE\xE6\x76"+\
b"\x65\xFA\x40\xDA\xD3\xF0\xF2\x19\x35\xB7\xB2\x91\xFC\x18\x2C\x53"+\
b"\xA2\x3F\x1A\xA7\x4F\xFC\x42\xAE\xC1\x97\x89\xAB\x7E\x9B\xA1\x5C"+\
b"\x3A\x3B\x2F\x01\x60\xB1\xC5\x30\x7C\xB7\x2B\xD5\xAF\x27\xA0\x4C"+\
b"\xE9\x80\xC5\xB4\xEC\xFB\xD7\x59\xE8\x5D\xEE\xB5\x6F\x3B\xA7\xDE"+\
b"\xDA\xD8\x55\x09\x7A\x5A\xAD\x6C\x13\x2D\xD1\x23\x7C\x13\x5F\x84"+\
b"\x35\x29\x51\x55\xF4\x53\x12\x9C\x86\x7A\x77\x2B\xE2\x7B\x01\xA2"+\
b"\x6B\xC8\x5D\xD8\xCA\x92\xFB\x32\x0A\x09\xAE\xB3\x45\x8D\x0B\x60"+\
b"\x9D\xEB\xB7\x02\x07\xAB\x4A\x24\xF6\xA1\xE7\x59\xA0\xC4\xB1\xFB"+\
b"\x44\xAD\x32\xC7\xD4\x8F\xC6\x0C\x33\xD5\x88\x82\xF4\x9A\xA2\x7C"+\
b"\xDC\x56\x90\x96\x3C\xBC\xCF\x95\x17\x22\x55\x64\x67\x62\x52\x86"+\
b"\xFA\x3B\xFC\xAA\xC7\x1B\xDE\x7F\x01\xB3\x61\x8C\x28\xAE\x64\x7E"+\
b"\x43\xF0\x5A\x50\x60\x50\x85\xD4\xC4\xA6\x92\xC7\x8B\xE5\x04\x80"+\
b"\x74\x0F\xBA\xEB\x7C\x2C\x81\x07\x99\x22\x51\xD1\x9E\xE1\x59\xEE"+\
b"\x77\xC2\x13\x2C\x46\x16\x92\x9A\x69\xD9\x01\x75\x31\xA6\x20\xB9"+\
b"\x13\x46\x55\xF7\x8C\xC6\xB8\x7C\x8F\xAC\x00\x1A\x58\x68\xC7\xAD"+\
b"\x4E\x34\xB9\xEF\x5F\xCD\x87\x12\x0E\x8A\xEA\xD2\x4D\x66\x5E\x40"+\
b"\xBD\x1D\x30\x8A\x83\xB8\x4F\xC2\xAB\x28\x58\x6C\xEA\xDB\xF5\x87"+\
b"\xA0\x62\x9E\xF9\xF4\xE7\xE8\x65"
my_gcm = AES_GCM(master_key)
decrypted = my_gcm.decrypt(init_value, ciphertext, auth_tag)
"""
def __init__(self, master_key):
self.change_key(master_key)
# GF(2^128) defined by 1 + a + a^2 + a^7 + a^128
# Please note the MSB is x0 and LSB is x127
def gf_2_128_mul(self, x, y):
assert x < (1 << 128)
assert y < (1 << 128)
res = 0
for i in range(127, -1, -1):
res ^= x * ((y >> i) & 1) # branchless
x = (x >> 1) ^ ((x & 1) * 0xE1000000000000000000000000000000)
assert res < 1 << 128
return res
def change_key(self, master_key):
if master_key >= (1 << 128):
raise InvalidInputException('Master key should be 128-bit')
self.__master_key = long_to_bytes(master_key, 16)
self.__aes_ecb = AES.new(self.__master_key, AES.MODE_ECB)
self.__auth_key = bytes_to_long(self.__aes_ecb.encrypt(b'\x00' * 16))
# precompute the table for multiplication in finite field
table = [] # for 8-bit
for i in range(16):
row = []
for j in range(256):
row.append(self.gf_2_128_mul(self.__auth_key, j << (8 * i)))
table.append(tuple(row))
self.__pre_table = tuple(table)
self.prev_init_value = None # reset
def __times_auth_key(self, val):
res = 0
for i in range(16):
res ^= self.__pre_table[i][val & 0xFF]
val >>= 8
return res
def __ghash(self, aad, txt):
len_aad = len(aad)
len_txt = len(txt)
# padding
if 0 == len_aad % 16:
data = aad
else:
data = aad + b'\x00' * (16 - len_aad % 16)
if 0 == len_txt % 16:
data += txt
else:
data += txt + b'\x00' * (16 - len_txt % 16)
tag = 0
assert len(data) % 16 == 0
for i in range(len(data) // 16):
tag ^= bytes_to_long(data[i * 16: (i + 1) * 16])
tag = self.__times_auth_key(tag)
# print 'X\t', hex(tag)
tag ^= ((8 * len_aad) << 64) | (8 * len_txt)
tag = self.__times_auth_key(tag)
return tag
def encrypt(self, init_value, plaintext, auth_data=b''):
if init_value >= (1 << 96):
raise InvalidInputException('IV should be 96-bit')
# a naive checking for IV reuse
if init_value == self.prev_init_value:
raise InvalidInputException('IV must not be reused!')
self.prev_init_value = init_value
len_plaintext = len(plaintext)
# len_auth_data = len(auth_data)
if len_plaintext > 0:
counter = Counter.new(
nbits=32,
prefix=long_to_bytes(init_value, 12),
initial_value=2, # notice this
allow_wraparound=False)
aes_ctr = AES.new(self.__master_key, AES.MODE_CTR, counter=counter)
if 0 != len_plaintext % 16:
padded_plaintext = plaintext + \
b'\x00' * (16 - len_plaintext % 16)
else:
padded_plaintext = plaintext
ciphertext = aes_ctr.encrypt(padded_plaintext)[:len_plaintext]
else:
ciphertext = b''
auth_tag = self.__ghash(auth_data, ciphertext)
# print 'GHASH\t', hex(auth_tag)
auth_tag ^= bytes_to_long(self.__aes_ecb.encrypt(
long_to_bytes((init_value << 32) | 1, 16)))
# assert len(ciphertext) == len(plaintext)
assert auth_tag < (1 << 128)
return ciphertext, auth_tag
def decrypt(self, init_value, ciphertext, auth_tag, auth_data=b''):
# if init_value >= (1 << 96):
# raise InvalidInputException('IV should be 96-bit')
# if auth_tag >= (1 << 128):
# raise InvalidInputException('Tag should be 128-bit')
if auth_tag != self.__ghash(auth_data, ciphertext) ^ \
bytes_to_long(self.__aes_ecb.encrypt(
long_to_bytes((init_value << 32) | 1, 16))):
raise InvalidTagException
len_ciphertext = len(ciphertext)
if len_ciphertext > 0:
counter = Counter.new(
nbits=32,
prefix=long_to_bytes(init_value, 12),
initial_value=2,
allow_wraparound=True)
aes_ctr = AES.new(self.__master_key, AES.MODE_CTR, counter=counter)
if 0 != len_ciphertext % 16:
padded_ciphertext = ciphertext + \
b'\x00' * (16 - len_ciphertext % 16)
else:
padded_ciphertext = ciphertext
plaintext = aes_ctr.decrypt(padded_ciphertext)[:len_ciphertext]
else:
plaintext = b''
return plaintext
def aes_gcm(self, ciphertext, nounce, aes_key, hdr, tag_auth, decrypt=True):
cipher = AES.new(aes_key, AES.MODE_GCM, nounce)
if hdr is not None:
cipher.update(hdr)
if decrypt:
plaintext = cipher.decrypt(ciphertext)
try:
cipher.verify(tag_auth)
return plaintext
except ValueError:
return None
return None
def aes_cbc(self, key, iv, data, decrypt=True):
if decrypt:
return AES.new(key, AES.MODE_CBC, IV=iv).decrypt(data)
else:
return AES.new(key, AES.MODE_CBC, IV=iv).encrypt(data)
def aes_ecb(self, key, data, decrypt=True):
if decrypt:
return AES.new(key, AES.MODE_ECB).decrypt(data)
else:
return AES.new(key, AES.MODE_ECB).encrypt(data)
def aes_ctr(self, key, counter, enc_data, decrypt=True):
ctr = Counter.new(128, initial_value=counter)
# Create the AES cipher object and decrypt the ciphertext, basically this here is just aes ctr 256 :)
cipher = AES.new(key, AES.MODE_CTR, counter=ctr)
if decrypt:
data = cipher.decrypt(enc_data)
else:
data = cipher.encrypt(enc_data)
return data
def aes_ccm(self, key, nounce, tag_auth, data, decrypt=True):
cipher = AES.new(key, AES.MODE_CCM, nounce)
if decrypt:
plaintext = cipher.decrypt(data)
try:
cipher.verify(tag_auth)
return plaintext
except ValueError:
return None
else:
ciphertext = cipher.encrypt(data)
return ciphertext
def aes_cmac_verify(self, key, plain, compare):
ctx = CMAC.new(key, ciphermod=AES)
ctx.update(plain)
result = ctx.hexdigest()
if result != compare:
print("AES-CMAC failed !")
else:
print("AES-CMAC ok !")
class rsa: # RFC8017
def __init__(self, hashtype="SHA256"):
if hashtype == "SHA1":
self.hash = hashlib.sha1
self.digestLen = 0x14
elif hashtype == "SHA256":
self.hash = hashlib.sha256
self.digestLen = 0x20
def pss_test(self):
N = "a2ba40ee07e3b2bd2f02ce227f36a195024486e49c19cb41bbbdfbba98b22b0e577c2eeaffa20d883a76e65e394c69d4b3c05a1e8fadda27edb2a42bc000fe888b9b32c22d15add0cd76b3e7936e19955b220dd17d4ea904b1ec102b2e4de7751222aa99151024c7cb41cc5ea21d00eeb41f7c800834d2c6e06bce3bce7ea9a5"
e = "010001"
D = "050e2c3e38d886110288dfc68a9533e7e12e27d2aa56d2cdb3fb6efa990bcff29e1d2987fb711962860e7391b1ce01ebadb9e812d2fbdfaf25df4ae26110a6d7a26f0b810f54875e17dd5c9fb6d641761245b81e79f8c88f0e55a6dcd5f133abd35f8f4ec80adf1bf86277a582894cb6ebcd2162f1c7534f1f4947b129151b71"
MSG = "859eef2fd78aca00308bdc471193bf55bf9d78db8f8a672b484634f3c9c26e6478ae10260fe0dd8c082e53a5293af2173cd50c6d5d354febf78b26021c25c02712e78cd4694c9f469777e451e7f8e9e04cd3739c6bbfedae487fb55644e9ca74ff77a53cb729802f6ed4a5ffa8ba159890fc "
salt = "e3b5d5d002c1bce50c2b65ef88a188d83bce7e61"
N = int(N, 16)
e = int(e, 16)
D = int(D, 16)
MSG = unhexlify(MSG)
salt = unhexlify(salt)
signature = self.pss_sign(D, N, self.hash(MSG), salt, 1024) # pkcs_1_pss_encode_sha256
isvalid = self.pss_verify(e, N, self.hash(MSG), signature, 1024)
if isvalid:
print("Test passed.")
else:
print("Test failed.")
def i2osp(self, x, x_len):
"""Converts the integer x to its big-endian representation of length
x_len.
"""
if x > 256 ** x_len:
raise IntegerTooLarge
h = hex(x)[2:]
if h[-1] == 'L':
h = h[:-1]
if len(h) & 1 == 1:
h = '0%s' % h
x = unhexlify(h)
return b'\x00' * int(x_len - len(x)) + x
def os2ip(self, x):
"""Converts the byte string x representing an integer reprented using the
big-endian convient to an integer.
"""
h = hexlify(x)
return int(h, 16)
# def os2ip(self, X):
# return int.from_bytes(X, byteorder='big')
def mgf1(self, input, length):
counter = 0
output = b''
while len(output) < length:
C = self.i2osp(counter, 4)
output += self.hash(input + C)
counter += 1
return output[:length]
def assert_int(self, var: int, name: str):
if isinstance(var, int):
return
raise TypeError('%s should be an integer, not %s' % (name, var.__class__))
def sign(self, tosign, D, N, emBits=1024):
self.assert_int(tosign, 'message')
self.assert_int(D, 'D')
self.assert_int(N, 'n')
if tosign < 0:
raise ValueError('Only non-negative numbers are supported')
if tosign > N:
tosign1 = divmod(tosign, N)[1]
signature = pow(tosign1, D, N)
raise OverflowError("The message %i is too long for n=%i" % (tosign, N))
signature = pow(tosign, D, N)
hexsign = self.i2osp(signature, emBits // 8)
return hexsign
def pss_sign(self, D, N, msghash, salt, emBits=1024):
if isinstance(D, str):
D = unhexlify(D)
D = self.os2ip(D)
if isinstance(N, str):
N = unhexlify(N)
N = self.os2ip(N)
slen = len(salt)
emLen = self.ceil_div(emBits, 8)
inBlock = b"\x00" * 8 + msghash + salt
hhash = self.hash(inBlock)
PSlen = emLen - self.digestLen - slen - 1 - 1
DB = (PSlen * b"\x00") + b"\x01" + salt
rlen = emLen - len(hhash) - 1
dbMask = self.mgf1(hhash, rlen)
maskedDB = bytearray()
for i in range(0, len(dbMask)):
maskedDB.append(dbMask[i] ^ DB[i])
maskedDB[0] = maskedDB[0] & 0x7F
EM = maskedDB + hhash + b"\xbc"
tosign = self.os2ip(EM)
# EM=hexlify(EM).decode('utf-8')
# tosign = int(EM,16)
return self.sign(tosign, D, N, emBits)
# 6B1EAA2042A5C8DA8B1B4A8320111A70A0CBA65233D1C6E418EF8156E82A8F96BD843F047FF25AB9702A6582C8387298753E628F23448B4580E09CBD2A483C623B888F47C4BD2C5EFF09013C6DFF67DB59BAB3037F0BEE05D5660264D28CC6251631FE75CE106D931A04FA032FEA31259715CE0FAB1AE0E2F8130807AF4019A61B9C060ECE59104F22156FEE8108F17DC80D7C2F8397AFB9780994F7C5A0652F93D1B48010B0B248AB9711235787D797FBA4D10A29BCF09628585D405640A866B15EE9D7526A2703E72A19811EF447F6E5C43F915B3808EBC79EA4BCF78903DBDE32E47E239CFB5F2B5986D0CBBFBE6BACDC29B2ADE006D23D0B90775B1AE4DD
def ceil_div(self, a, b):
(q, r) = divmod(a, b)
if r:
return q + 1
else:
return q
def pss_verify(self, e, N, msghash, signature, emBits=1024, salt=None):
if salt == None:
slen = self.digestLen
else:
slen = len(salt)
sig = self.os2ip(signature)
EM = pow(sig, e, N)
# EM = unhexlify(hex(EM)[2:])
EM = self.i2osp(EM, emBits // 8)
emLen = len(signature)
valBC = EM[-1]
if valBC != 0xbc:
print("[rsa_pss] : 0xbc check failed")
return False
mhash = EM[emLen - self.digestLen - 1:-1]
maskedDB = EM[:emLen - self.digestLen - 1]
lmask = ~(0xFF >> (8 * emLen + 1 - emBits))
if EM[0] & lmask:
print("[rsa_pss] : lmask check failed")
return False
dbMask = self.mgf1(mhash, emLen - self.digestLen - 1)
DB = bytearray()
for i in range(0, len(dbMask)):
DB.append(dbMask[i] ^ maskedDB[i])
TS = bytearray()
TS.append(DB[0] & ~lmask)
TS.extend(DB[1:])
PS = (b"\x00" * (emLen - self.digestLen - slen - 2)) + b"\x01"
if TS[:len(PS)] != PS:
print(TS[:len(PS)])
print(PS)
print("[rsa_pss] : 0x01 check failed")
return False
if salt is not None:
inBlock = b"\x00" * 8 + msghash + salt
mhash = self.hash(inBlock)
if mhash == mhash:
return True
else:
return False
else:
salt = TS[-self.digestLen:]
inBlock = b"\x00" * 8 + msghash + salt
mhash = self.hash(inBlock)
if mhash == mhash:
return True
else:
return False
class hash():
def __init__(self, hashtype="SHA256"):
if hashtype == "SHA1":
self.hash = self.sha1
self.digestLen = 0x14
elif hashtype == "SHA256":
self.hash = self.sha256
self.digestLen = 0x20
elif hashtype == "MD5":
self.hash = self.md5
self.digestLen = 0x10
def sha1(self, msg):
return hashlib.sha1(msg).digest()
def sha256(self, msg):
return hashlib.sha256(msg).digest()
def md5(self, msg):
return hashlib.md5(msg).digest()

1238
edl/Library/firehose.py Executable file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,920 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# (c) B.Kerler 2018-2019
import os
import sys
import logging
import json
from binascii import hexlify, unhexlify
from struct import unpack, pack
from edl.Library.firehose import firehose
from edl.Library.xmlparser import xmlparser
from edl.Library.utils import do_tcp_server
from edl.Library.utils import LogBase, getint
from edl.Config.qualcomm_config import memory_type
from edl.Config.qualcomm_config import infotbl, msmids, secureboottbl, sochw
try:
import xml.etree.cElementTree as ET
from xml.etree import cElementTree as ElementTree
except ImportError:
import xml.etree.ElementTree as ET
from xml.etree import ElementTree
try:
from edl.Library.Modules.init import modules
except ImportError as e:
pass
class firehose_client(metaclass=LogBase):
def __init__(self, arguments, cdc, sahara, loglevel, printer):
self.cdc = cdc
self.sahara = sahara
self.arguments = arguments
self.printer = printer
self.info = self.__logger.info
self.error = self.__logger.error
self.warning = self.__logger.warning
self.__logger.setLevel(loglevel)
if loglevel == logging.DEBUG:
logfilename = "log.txt"
fh = logging.FileHandler(logfilename)
self.__logger.addHandler(fh)
self.cfg = firehose.cfg()
if not arguments["--memory"] is None:
self.cfg.MemoryName = arguments["--memory"].lower()
else:
self.cfg.MemoryName = ""
self.cfg.ZLPAwareHost = 1
self.cfg.SkipStorageInit = arguments["--skipstorageinit"]
self.cfg.SkipWrite = arguments["--skipwrite"]
self.cfg.MaxPayloadSizeToTargetInBytes = getint(arguments["--maxpayload"])
self.cfg.SECTOR_SIZE_IN_BYTES = getint(arguments["--sectorsize"])
self.cfg.bit64 = sahara.bit64
devicemodel = ""
skipresponse = False
if "--skipresponse" in arguments:
if arguments["--skipresponse"]:
skipresponse = True
if "--devicemodel" in arguments:
if arguments["--devicemodel"] is not None:
devicemodel = arguments["--devicemodel"]
self.firehose = firehose(cdc, xmlparser(), self.cfg, self.__logger.level, devicemodel, sahara.serial,
skipresponse,
self.getluns(arguments), arguments)
self.connected = False
self.firehose.connect()
if "hwid" in dir(sahara):
if sahara.hwid is not None:
hwid = (sahara.hwid >> 32) & 0xFFFFFF
socid = ((sahara.hwid >> 32) >> 16)
if hwid in msmids:
self.target_name = msmids[hwid]
self.info(f"Target detected: {self.target_name}")
if self.cfg.MemoryName == "":
if self.target_name in memory_type.preferred_memory:
type = memory_type.preferred_memory[self.target_name]
if type == memory_type.nand:
self.cfg.MemoryName = "nand"
if type == memory_type.spinor:
self.cfg.MemoryName = "spinor"
elif type == memory_type.emmc:
self.cfg.MemoryName = "eMMC"
elif type == memory_type.ufs:
self.cfg.MemoryName = "UFS"
self.warning("Based on the chipset, we assume " +
self.cfg.MemoryName + " as default memory type..., if it fails, try using " +
"--memory\" with \"UFS\",\"NAND\" or \"spinor\" instead !")
elif socid in sochw:
self.target_name = sochw[socid].split(",")[0]
# We assume ufs is fine (hopefully), set it as default
if self.cfg.MemoryName == "":
self.warning(
"No --memory option set, we assume \"eMMC\" as default ..., if it fails, try using \"--memory\" " +
"with \"UFS\",\"NAND\" or \"spinor\" instead !")
self.cfg.MemoryName = "eMMC"
if self.firehose.configure(0):
funcs = "Supported functions:\n-----------------\n"
for function in self.firehose.supported_functions:
funcs += function + ","
funcs = funcs[:-1]
self.info(funcs)
self.target_name = self.firehose.cfg.TargetName
self.connected = True
try:
if self.firehose.modules is None:
self.firehose.modules = modules(fh=self.firehose, serial=self.firehose.serial,
supported_functions=self.firehose.supported_functions,
loglevel=self.__logger.level,
devicemodel=self.firehose.devicemodel, args=self.arguments)
except Exception as err: # pylint: disable=broad-except
self.firehose.modules = None
def check_cmd(self, func):
if not self.firehose.supported_functions:
return True
for sfunc in self.firehose.supported_functions:
if func.lower() == sfunc.lower():
return True
return False
def find_bootable_partition(self, rawprogram):
part = -1
for xml in rawprogram:
with open(xml, "r") as fl:
for evt, elem in ET.iterparse(fl, events=["end"]):
if elem.tag == "program":
label = elem.get("label")
if label in ['xbl', 'xbl_a', 'sbl1']:
if part != -1:
self.error("[FIREHOSE] multiple bootloader found!")
return -1
part = elem.get("physical_partition_number")
return part
def getluns(self, argument):
if argument["--lun"] is not None:
return [int(argument["--lun"])]
luns = []
if self.cfg.MemoryName.lower() == "ufs":
for i in range(0, self.cfg.maxlun):
luns.append(i)
else:
luns = [0]
return luns
def check_param(self, parameters):
error = False
params = ""
for parameter in parameters:
params += parameter + " "
if parameter not in parameters:
error = True
if error:
if len(parameters) == 1:
self.printer("Argument " + params + "required.")
else:
self.printer("Arguments " + params + "required.")
return False
return True
def get_storage_info(self):
if "getstorageinfo" in self.firehose.supported_functions:
storageinfo = self.firehose.cmd_getstorageinfo()
for info in storageinfo:
if "storage_info" in info:
rs = info.replace("INFO: ", "")
field = json.loads(rs)
if "storage_info" in field:
info = field["storage_info"]
return info
return None
def handle_firehose(self, cmd, options):
if cmd == "gpt":
luns = self.getluns(options)
directory = options["<directory>"]
if directory is None:
directory = ""
genxml = False
if "--genxml" in options:
if options["--genxml"]:
genxml = True
for lun in luns:
sfilename = os.path.join(directory, f"gpt_main{str(lun)}.bin")
data, guid_gpt = self.firehose.get_gpt(lun, int(options["--gpt-num-part-entries"]),
int(options["--gpt-part-entry-size"]),
int(options["--gpt-part-entry-start-lba"]))
if guid_gpt is None:
break
with open(sfilename, "wb") as write_handle:
write_handle.write(data)
self.printer(f"Dumped GPT from Lun {str(lun)} to {sfilename}")
sfilename = os.path.join(directory, f"gpt_backup{str(lun)}.bin")
with open(sfilename, "wb") as write_handle:
write_handle.write(data[self.firehose.cfg.SECTOR_SIZE_IN_BYTES * 2:])
self.printer(f"Dumped Backup GPT from Lun {str(lun)} to {sfilename}")
if genxml:
guid_gpt.generate_rawprogram(lun, self.firehose.cfg.SECTOR_SIZE_IN_BYTES, directory)
return True
elif cmd == "printgpt":
luns = self.getluns(options)
for lun in luns:
data, guid_gpt = self.firehose.get_gpt(lun, int(options["--gpt-num-part-entries"]),
int(options["--gpt-part-entry-size"]),
int(options["--gpt-part-entry-start-lba"]))
if guid_gpt is None:
break
self.printer(f"\nParsing Lun {str(lun)}:")
guid_gpt.print()
return True
elif cmd == "r":
if not self.check_param(["<partitionname>", "<filename>"]):
return False
partitionname = options["<partitionname>"]
filename = options["<filename>"]
filenames = filename.split(",")
partitions = partitionname.split(",")
if len(partitions) != len(filenames):
self.error("You need to gives as many filenames as given partitions.")
return False
i = 0
for partition in partitions:
if partition=="gpt":
luns = self.getluns(options)
for lun in luns:
partfilename=filenames[i]+".lun%d" % lun
if self.firehose.cmd_read(lun, 0, 32, partfilename):
self.printer(
f"Dumped sector {str(0)} with sector count {str(32)} " +
f"as {partfilename}.")
continue
partfilename = filenames[i]
i += 1
res = self.firehose.detect_partition(options, partition)
if res[0]:
lun = res[1]
rpartition = res[2]
if self.firehose.cmd_read(lun, rpartition.sector, rpartition.sectors, partfilename):
self.printer(
f"Dumped sector {str(rpartition.sector)} with sector count {str(rpartition.sectors)} " +
f"as {partfilename}.")
else:
fpartitions = res[1]
self.error(f"Error: Couldn't detect partition: {partition}\nAvailable partitions:")
for lun in fpartitions:
for rpartition in fpartitions[lun]:
if self.cfg.MemoryName == "emmc":
self.error("\t" + rpartition)
else:
self.error(lun + ":\t" + rpartition)
return False
return True
elif cmd == "rl":
if not self.check_param(["<directory>"]):
return False
directory = options["<directory>"]
if options["--skip"]:
skip = options["--skip"].split(",")
else:
skip = []
genxml = False
if "--genxml" in options:
if options["--genxml"]:
genxml = True
if not os.path.exists(directory):
os.mkdir(directory)
luns = self.getluns(options)
for lun in luns:
data, guid_gpt = self.firehose.get_gpt(lun, int(options["--gpt-num-part-entries"]),
int(options["--gpt-part-entry-size"]),
int(options["--gpt-part-entry-start-lba"]))
if guid_gpt is None:
break
if len(luns) > 1:
storedir = os.path.join(directory, "lun" + str(lun))
else:
storedir = directory
if not os.path.exists(storedir):
os.mkdir(storedir)
sfilename = os.path.join(storedir, f"gpt_main{str(lun)}.bin")
with open(sfilename, "wb") as write_handle:
write_handle.write(data)
sfilename = os.path.join(storedir, f"gpt_backup{str(lun)}.bin")
with open(sfilename, "wb") as write_handle:
write_handle.write(data[self.firehose.cfg.SECTOR_SIZE_IN_BYTES * 2:])
if genxml:
guid_gpt.generate_rawprogram(lun, self.firehose.cfg.SECTOR_SIZE_IN_BYTES, storedir)
for partition in guid_gpt.partentries:
partitionname = partition.name
if partition.name in skip:
continue
filename = os.path.join(storedir, partitionname + ".bin")
self.info(
f"Dumping partition {str(partition.name)} with sector count {str(partition.sectors)} " +
f"as {filename}.")
if self.firehose.cmd_read(lun, partition.sector, partition.sectors, filename):
self.info(f"Dumped partition {str(partition.name)} with sector count " +
f"{str(partition.sectors)} as {filename}.")
return True
elif cmd == "rf":
if not self.check_param(["<filename>"]):
return False
filename = options["<filename>"]
storageinfo = self.get_storage_info()
if storageinfo is not None and self.cfg.MemoryName.lower() in ["spinor" , "nand"]:
totalsectors = None
if "total_blocks" in storageinfo:
totalsectors = storageinfo["total_blocks"]
if "num_physical" in storageinfo:
num_physical = storageinfo["num_physical"]
luns = [0]
if num_physical > 0:
luns=[]
for i in range(num_physical):
luns.append(i)
if totalsectors is not None:
for lun in luns:
buffer=self.firehose.cmd_read_buffer(physical_partition_number=lun,
start_sector=0, num_partition_sectors=1, display=False)
storageinfo = self.get_storage_info()
if "total_blocks" in storageinfo:
totalsectors = storageinfo["total_blocks"]
if len(luns) > 1:
sfilename = filename + f".lun{str(lun)}"
else:
sfilename = filename
self.printer(f"Dumping sector 0 with sector count {str(totalsectors)} as {filename}.")
if self.firehose.cmd_read(lun, 0, totalsectors, sfilename):
self.printer(
f"Dumped sector 0 with sector count {str(totalsectors)} as {filename}.")
else:
luns = self.getluns(options)
for lun in luns:
data, guid_gpt = self.firehose.get_gpt(lun, int(options["--gpt-num-part-entries"]),
int(options["--gpt-part-entry-size"]),
int(options["--gpt-part-entry-start-lba"]))
if guid_gpt is None:
break
if len(luns) > 1:
sfilename = filename + f".lun{str(lun)}"
else:
sfilename = filename
self.printer(f"Dumping sector 0 with sector count {str(guid_gpt.totalsectors)} as {filename}.")
if self.firehose.cmd_read(lun, 0, guid_gpt.totalsectors, sfilename):
self.printer(f"Dumped sector 0 with sector count {str(guid_gpt.totalsectors)} as {filename}.")
return True
elif cmd == "pbl":
if not self.check_param(["<filename>"]):
return False
if not self.check_cmd("peek"):
self.error("Peek command isn't supported by edl loader")
return False
else:
filename = options["<filename>"]
if self.target_name in infotbl:
target_name = infotbl[self.target_name]
if len(target_name[0]) > 0:
if self.firehose.cmd_peek(target_name[0][0], target_name[0][1], filename, True):
self.printer(f"Dumped pbl at offset {hex(target_name[0][0])} as {filename}.")
return True
else:
self.error("No known pbl offset for this chipset")
else:
self.error("Unknown target chipset")
self.error("Error on dumping pbl")
return False
elif cmd == "qfp":
if not self.check_param(["<filename>"]):
return False
if not self.check_cmd("peek"):
self.error("Peek command isn't supported by edl loader")
return False
else:
filename = options["<filename>"]
if self.target_name not in infotbl:
self.error("Unknown target chipset")
else:
target_name = infotbl[self.target_name]
if len(target_name[1]) > 0:
if self.firehose.cmd_peek(target_name[1][0], target_name[1][1], filename):
self.printer(f"Dumped qfprom at offset {hex(target_name[1][0])} as {filename}.")
return True
else:
self.error("No known qfprom offset for this chipset")
self.error("Error on dumping qfprom")
return False
elif cmd == "secureboot":
if not self.check_cmd("peek"):
self.error("Peek command isn't supported by edl loader")
return False
else:
if self.target_name in secureboottbl:
self.target_name = secureboottbl[self.target_name]
value = unpack("<I", self.firehose.cmd_peek(self.target_name, 4))[0]
is_secure = False
for area in range(0, 4):
sec_boot = (value >> (area * 8)) & 0xFF
pk_hashindex = sec_boot & 3
oem_pkhash = True if ((sec_boot >> 4) & 1) == 1 else False
auth_enabled = True if ((sec_boot >> 5) & 1) == 1 else False
use_serial = True if ((sec_boot >> 6) & 1) == 1 else False
if auth_enabled:
is_secure = True
self.printer(f"Sec_Boot{str(area)} " +
f"PKHash-Index:{str(pk_hashindex)} " +
f"OEM_PKHash: {str(oem_pkhash)} " +
f"Auth_Enabled: {str(auth_enabled)}" +
f"Use_Serial: {str(use_serial)}")
if is_secure:
self.printer("Secure boot enabled.")
else:
self.printer("Secure boot disabled.")
return True
else:
self.error("Unknown target chipset")
return False
elif cmd == "memtbl":
if not self.check_param(["<filename>"]):
return False
if not self.check_cmd("peek"):
self.error("Peek command isn't supported by edl loader")
return False
else:
filename = options["<filename>"]
if self.target_name in infotbl:
self.target_name = infotbl[self.target_name]
if len(self.target_name[2]) > 0:
if self.firehose.cmd_peek(self.target_name[2][0], self.target_name[2][1], filename):
self.printer(f"Dumped memtbl at offset {hex(self.target_name[2][0])} as {filename}.")
return True
else:
self.error("No known memtbl offset for this chipset")
else:
self.error("Unknown target chipset")
self.error("Error on dumping memtbl")
return False
elif cmd == "footer":
if not self.check_param(["<filename>"]):
return False
luns = self.getluns(options)
filename = options["<filename>"]
for lun in luns:
data, guid_gpt = self.firehose.get_gpt(lun, int(options["--gpt-num-part-entries"]),
int(options["--gpt-part-entry-size"]),
int(options["--gpt-part-entry-start-lba"]))
if guid_gpt is None:
break
pnames = ["userdata2", "metadata", "userdata", "reserved1", "reserved2", "reserved3"]
for partition in guid_gpt.partentries:
if partition.name in pnames:
self.printer(f"Detected partition: {partition.name}")
data = self.firehose.cmd_read_buffer(lun,
partition.sector +
(partition.sectors -
(0x4000 // self.firehose.cfg.SECTOR_SIZE_IN_BYTES)),
(0x4000 // self.firehose.cfg.SECTOR_SIZE_IN_BYTES), False)
if data == b"":
continue
val = unpack("<I", data[:4])[0]
if (val & 0xFFFFFFF0) == 0xD0B5B1C0:
with open(filename, "wb") as write_handle:
write_handle.write(data)
self.printer(f"Dumped footer from {partition.name} as {filename}.")
return True
self.error("Error: Couldn't detect footer partition.")
return False
elif cmd == "rs":
if options["--lun"] is not None:
lun = int(options["--lun"])
else:
lun = 0
if not self.check_param(["<filename>", "<sectors>", "<start_sector>"]):
return False
start = int(options["<start_sector>"])
sectors = int(options["<sectors>"])
filename = options["<filename>"]
if self.firehose.cmd_read(lun, start, sectors, filename, True):
self.printer(f"Dumped sector {str(start)} with sector count {str(sectors)} as {filename}.")
return True
elif cmd == "peek":
if not self.check_param(["<offset>", "<length>", "<filename>"]):
return False
if not self.check_cmd("peek"):
self.error("Peek command isn't supported by edl loader")
return False
else:
offset = getint(options["<offset>"])
length = getint(options["<length>"])
filename = options["<filename>"]
self.firehose.cmd_peek(offset, length, filename, True)
self.info(
f"Peek data from offset {hex(offset)} and length {hex(length)} was written to {filename}")
return True
elif cmd == "peekhex":
if not self.check_param(["<offset>", "<length>"]):
return False
if not self.check_cmd("peek"):
self.error("Peek command isn't supported by edl loader")
return False
else:
offset = getint(options["<offset>"])
length = getint(options["<length>"])
resp = self.firehose.cmd_peek(offset, length, "", True)
self.printer("\n")
self.printer(hexlify(resp))
return True
elif cmd == "peekqword":
if not self.check_param(["<offset>"]):
return False
if not self.check_cmd("peek"):
self.error("Peek command isn't supported by edl loader")
return False
else:
offset = getint(options["<offset>"])
resp = self.firehose.cmd_peek(offset, 8, "", True)
self.printer("\n")
self.printer(hex(unpack("<Q", resp[:8])[0]))
return True
elif cmd == "peekdword":
if not self.check_param(["<offset>"]):
return False
if not self.check_cmd("peek"):
self.error("Peek command isn't supported by edl loader")
return False
else:
offset = getint(options["<offset>"])
resp = self.firehose.cmd_peek(offset, 4, "", True)
self.printer("\n")
self.printer(hex(unpack("<I", resp[:4])[0]))
return True
elif cmd == "poke":
if not self.check_param(["<offset>", "<filename>"]):
return False
if not self.check_cmd("poke"):
self.error("Poke command isn't supported by edl loader")
return False
else:
offset = getint(options["<offset>"])
filename = options["<filename>"]
return self.firehose.cmd_poke(offset, "", filename, True)
elif cmd == "pokehex":
if not self.check_param(["<offset>", "<data>"]):
return False
if not self.check_cmd("poke"):
self.error("Poke command isn't supported by edl loader")
return False
else:
offset = getint(options["<offset>"])
data = unhexlify(options["<data>"])
return self.firehose.cmd_poke(offset, data, "", True)
elif cmd == "pokeqword":
if not self.check_param(["<offset>", "<data>"]):
return False
if not self.check_cmd("poke"):
self.error("Poke command isn't supported by edl loader")
return False
else:
offset = getint(options["<offset>"])
data = pack("<Q", getint(options["<data>"]))
return self.firehose.cmd_poke(offset, data, "", True)
elif cmd == "pokedword":
if not self.check_param(["<offset>", "<data>"]):
return False
if not self.check_cmd("poke"):
self.error("Poke command isn't supported by edl loader")
return False
else:
offset = getint(options["<offset>"])
data = pack("<I", getint(options["<data>"]))
return self.firehose.cmd_poke(offset, data, "", True)
elif cmd == "memcpy":
if not self.check_param(["<offset>", "<size>"]):
return False
if not self.check_cmd("poke"):
self.printer("Poke command isn't supported by edl loader")
else:
srcoffset = getint(options["<offset>"])
size = getint(options["<size>"])
dstoffset = srcoffset + size
if self.firehose.cmd_memcpy(dstoffset, srcoffset, size):
self.printer(f"Memcpy from {hex(srcoffset)} to {hex(dstoffset)} succeeded")
return True
else:
return False
elif cmd == "reset":
return self.firehose.cmd_reset()
elif cmd == "nop":
if not self.check_cmd("nop"):
self.error("Nop command isn't supported by edl loader")
return False
else:
return self.firehose.cmd_nop()
elif cmd == "setbootablestoragedrive":
if not self.check_param(["<lun>"]):
return False
if not self.check_cmd("setbootablestoragedrive"):
self.error("setbootablestoragedrive command isn't supported by edl loader")
return False
else:
return self.firehose.cmd_setbootablestoragedrive(int(options["<lun>"]))
elif cmd == "getstorageinfo":
if not self.check_cmd("getstorageinfo"):
self.error("getstorageinfo command isn't supported by edl loader")
return False
else:
return self.firehose.cmd_getstorageinfo_string()
elif cmd == "w":
if not self.check_param(["<partitionname>", "<filename>"]):
return False
partitionname = options["<partitionname>"]
filename = options["<filename>"]
if options["--lun"] is not None:
lun = int(options["--lun"])
else:
lun = 0
startsector = 0
if not os.path.exists(filename):
self.error(f"Error: Couldn't find file: {filename}")
return False
if partitionname.lower() == "gpt":
sectors = os.stat(filename).st_size // self.firehose.cfg.SECTOR_SIZE_IN_BYTES
res = [True, lun, sectors]
else:
res = self.firehose.detect_partition(options, partitionname)
if res[0]:
lun = res[1]
sectors = os.stat(filename).st_size // self.firehose.cfg.SECTOR_SIZE_IN_BYTES
if (os.stat(filename).st_size % self.firehose.cfg.SECTOR_SIZE_IN_BYTES) > 0:
sectors += 1
if partitionname.lower() != "gpt":
partition = res[2]
if sectors > partition.sectors:
self.error(
f"Error: {filename} has {sectors} sectors but partition only has {partition.sectors}.")
return False
startsector = partition.sector
if self.firehose.modules is not None:
self.firehose.modules.writeprepare()
if self.firehose.cmd_program(lun, startsector, filename):
self.printer(f"Wrote {filename} to sector {str(startsector)}.")
return True
else:
self.printer(f"Error writing {filename} to sector {str(startsector)}.")
return False
else:
if len(res) > 0:
fpartitions = res[1]
self.error(f"Error: Couldn't detect partition: {partitionname}\nAvailable partitions:")
for lun in fpartitions:
for partition in fpartitions[lun]:
if self.cfg.MemoryName == "emmc":
self.error("\t" + partition)
else:
self.error(lun + ":\t" + partition)
return False
elif cmd == "wl":
if not self.check_param(["<directory>"]):
return False
directory = options["<directory>"]
if options["--skip"]:
skip = options["--skip"].split(",")
else:
skip = []
luns = self.getluns(options)
if not os.path.exists(directory):
self.error(f"Error: Couldn't find directory: {directory}")
sys.exit()
filenames = []
if self.firehose.modules is not None:
self.firehose.modules.writeprepare()
for dirName, subdirList, fileList in os.walk(directory):
for fname in fileList:
filenames.append(os.path.join(dirName, fname))
for lun in luns:
data, guid_gpt = self.firehose.get_gpt(lun, int(options["--gpt-num-part-entries"]),
int(options["--gpt-part-entry-size"]),
int(options["--gpt-part-entry-start-lba"]))
if guid_gpt is None:
break
if "partentries" in dir(guid_gpt):
for filename in filenames:
for partition in guid_gpt.partentries:
partname = filename[filename.rfind("/") + 1:]
if ".bin" in partname[-4:] or ".img" in partname[-4:] or ".mbn" in partname[-4:]:
partname = partname[:-4]
if partition.name == partname:
if partition.name in skip:
continue
sectors = os.stat(filename).st_size // self.firehose.cfg.SECTOR_SIZE_IN_BYTES
if (os.stat(filename).st_size % self.firehose.cfg.SECTOR_SIZE_IN_BYTES) > 0:
sectors += 1
if sectors > partition.sectors:
self.error(f"Error: {filename} has {sectors} sectors but partition " +
f"only has {partition.sectors}.")
return False
self.printer(f"Writing {filename} to partition {str(partition.name)}.")
self.firehose.cmd_program(lun, partition.sector, filename)
else:
self.printer("Couldn't write partition. Either wrong memorytype given or no gpt partition.")
return False
return True
elif cmd == "ws":
if not self.check_param(["<start_sector>"]):
return False
if options["--lun"] is not None:
lun = int(options["--lun"])
else:
lun = 0
start = int(options["<start_sector>"])
filename = options["<filename>"]
if not os.path.exists(filename):
self.error(f"Error: Couldn't find file: {filename}")
return False
if self.firehose.modules is not None:
self.firehose.modules.writeprepare()
if self.firehose.cmd_program(lun, start, filename):
self.printer(f"Wrote {filename} to sector {str(start)}.")
return True
else:
self.error(f"Error on writing {filename} to sector {str(start)}")
return False
elif cmd == "wf":
if not self.check_param(["<filename>"]):
return False
if options["--lun"] is not None:
lun = int(options["--lun"])
else:
lun = 0
start = 0
filename = options["<filename>"]
if not os.path.exists(filename):
self.error(f"Error: Couldn't find file: {filename}")
return False
if self.firehose.modules is not None:
self.firehose.modules.writeprepare()
if self.firehose.cmd_program(lun, start, filename):
self.printer(f"Wrote {filename} to sector {str(start)}.")
return True
else:
self.error(f"Error on writing {filename} to sector {str(start)}")
return False
elif cmd == "e":
if not self.check_param(["<partitionname>"]):
return False
luns = self.getluns(options)
partitionname = options["<partitionname>"]
for lun in luns:
data, guid_gpt = self.firehose.get_gpt(lun, int(options["--gpt-num-part-entries"]),
int(options["--gpt-part-entry-size"]),
int(options["--gpt-part-entry-start-lba"]))
if guid_gpt is None:
break
if self.firehose.modules is not None:
self.firehose.modules.writeprepare()
if "partentries" in dir(guid_gpt):
for partition in guid_gpt.partentries:
if partition.name == partitionname:
self.firehose.cmd_erase(lun, partition.sector, partition.sectors)
self.printer(
f"Erased {partitionname} starting at sector {str(partition.sector)} " +
f"with sector count {str(partition.sectors)}.")
return True
else:
self.printer("Couldn't erase partition. Either wrong memorytype given or no gpt partition.")
return False
self.error(f"Error: Couldn't detect partition: {partitionname}")
return False
elif cmd == "ep":
if not self.check_param(["<partitionname>", "<sectors>"]):
return False
luns = self.getluns(options)
partitionname = options["<partitionname>"]
sectors = int(options["<sectors>"])
for lun in luns:
data, guid_gpt = self.firehose.get_gpt(lun, int(options["--gpt-num-part-entries"]),
int(options["--gpt-part-entry-size"]),
int(options["--gpt-part-entry-start-lba"]))
if guid_gpt is None:
break
if self.firehose.modules is not None:
self.firehose.modules.writeprepare()
if "partentries" in dir(guid_gpt):
for partition in guid_gpt.partentries:
if partition.name == partitionname:
self.firehose.cmd_erase(lun, partition.sector, sectors)
self.printer(
f"Erased {partitionname} starting at sector {str(partition.sector)} " +
f"with sector count {str(sectors)}.")
return True
else:
self.printer("Couldn't erase partition. Either wrong memorytype given or no gpt partition.")
return False
self.error(f"Error: Couldn't detect partition: {partitionname}")
return False
elif cmd == "es":
if not self.check_param(["<start_sector>", "<sectors>"]):
return False
if options["--lun"] is not None:
lun = int(options["--lun"])
else:
lun = 0
start = int(options["<start_sector>"])
sectors = int(options["<sectors>"])
if self.firehose.modules is not None:
self.firehose.modules.writeprepare()
if self.firehose.cmd_erase(lun, start, sectors):
self.printer(f"Erased sector {str(start)} with sector count {str(sectors)}.")
return True
return False
elif cmd == "xml":
if not self.check_param(["<xmlfile>"]):
return False
return self.firehose.cmd_xml(options["<xmlfile>"])
elif cmd == "rawxml":
if not self.check_param(["<xmlstring>"]):
return False
return self.firehose.cmd_rawxml(options["<xmlstring>"])
elif cmd == "send":
if not self.check_param(["<command>"]):
return False
command = options["<command>"]
resp = self.firehose.cmd_send(command, True)
self.printer("\n")
self.printer(resp)
return True
elif cmd == "server":
return do_tcp_server(self, options, self.handle_firehose)
elif cmd == "modules":
if not self.check_param(["<command>", "<options>"]):
return False
mcommand = options["<command>"]
moptions = options["<options>"]
if self.firehose.modules is None:
self.error("Feature is not supported")
return False
else:
return self.firehose.modules.run(command=mcommand, args=moptions)
elif cmd == "qfil":
self.info("[qfil] raw programming...")
rawprogram = options["<rawprogram>"].split(",")
imagedir = options["<imagedir>"]
patch = options["<patch>"].split(",")
for xml in rawprogram:
filename = os.path.join(imagedir, xml)
if os.path.exists(filename):
self.info("[qfil] programming %s" % xml)
fl = open(filename, "r")
for evt, elem in ET.iterparse(fl, events=["end"]):
if elem.tag == "program":
if elem.get("filename", ""):
filename = os.path.join(imagedir, elem.get("filename"))
if not os.path.isfile(filename):
self.error("%s doesn't exist!" % filename)
continue
partition_number = int(elem.get("physical_partition_number"))
num_disk_sectors = self.firehose.getlunsize(partition_number)
start_sector = elem.get("start_sector")
if "NUM_DISK_SECTORS" in start_sector:
start_sector = start_sector.replace("NUM_DISK_SECTORS", str(num_disk_sectors))
if "-" in start_sector or "*" in start_sector or "/" in start_sector or \
"+" in start_sector:
start_sector = start_sector.replace(".", "")
start_sector = eval(start_sector)
self.info(f"[qfil] programming {filename} to partition({partition_number})" +
f"@sector({start_sector})...")
self.firehose.cmd_program(int(partition_number), int(start_sector), filename)
else:
self.warning(f"File : {filename} not found.")
self.info("[qfil] raw programming ok.")
self.info("[qfil] patching...")
for xml in patch:
filename = os.path.join(imagedir, xml)
self.info("[qfil] patching with %s" % xml)
if os.path.exists(filename):
fl = open(filename, "r")
for evt, elem in ET.iterparse(fl, events=["end"]):
if elem.tag == "patch":
filename = elem.get("filename")
if filename != "DISK":
continue
start_sector = elem.get("start_sector")
size_in_bytes = elem.get("size_in_bytes")
self.info(
f"[qfil] patching {filename} sector({start_sector}), size={size_in_bytes}".format(
filename=filename, start_sector=start_sector, size_in_bytes=size_in_bytes))
content = ElementTree.tostring(elem).decode("utf-8")
CMD = "<?xml version=\"1.0\" ?><data>\n {content} </data>".format(
content=content)
print(CMD)
self.firehose.xmlsend(CMD)
else:
self.warning(f"File : {filename} not found.")
self.info("[qfil] patching ok")
bootable = self.find_bootable_partition(rawprogram)
if bootable != -1:
if self.firehose.cmd_setbootablestoragedrive(bootable):
self.info("[qfil] partition({partition}) is now bootable\n".format(partition=bootable))
else:
self.info(
"[qfil] set partition({partition}) as bootable failed\n".format(partition=bootable))
else:
self.error("Unknown/Missing command, a command is required.")
return False

357
edl/Library/gpt.py Executable file
View file

@ -0,0 +1,357 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# (c) B.Kerler 2018-2021
import argparse
import os
import sys
import logging
from enum import Enum
from struct import unpack, pack
from binascii import hexlify
try:
from edl.Library.utils import LogBase, structhelper
except:
from utils import LogBase, structhelper
class gpt(metaclass=LogBase):
class gpt_header:
def __init__(self, data):
sh = structhelper(data)
self.signature = sh.bytes(8)
self.revision = sh.dword()
self.header_size = sh.dword()
self.crc32 = sh.dword()
self.reserved = sh.dword()
self.current_lba = sh.qword()
self.backup_lba = sh.qword()
self.first_usable_lba = sh.qword()
self.last_usable_lba = sh.qword()
self.disk_guid = sh.bytes(16)
self.part_entry_start_lba = sh.qword()
self.num_part_entries = sh.dword()
self.part_entry_size = sh.dword()
class gpt_partition:
def __init__(self, data):
sh = structhelper(data)
self.type = sh.bytes(16)
self.unique = sh.bytes(16)
self.first_lba = sh.qword()
self.last_lba = sh.qword()
self.flags = sh.qword()
self.name = sh.string(72)
class efi_type(Enum):
EFI_UNUSED = 0x00000000
EFI_MBR = 0x024DEE41
EFI_SYSTEM = 0xC12A7328
EFI_BIOS_BOOT = 0x21686148
EFI_IFFS = 0xD3BFE2DE
EFI_SONY_BOOT = 0xF4019732
EFI_LENOVO_BOOT = 0xBFBFAFE7
EFI_MSR = 0xE3C9E316
EFI_BASIC_DATA = 0xEBD0A0A2
EFI_LDM_META = 0x5808C8AA
EFI_LDM = 0xAF9B60A0
EFI_RECOVERY = 0xDE94BBA4
EFI_GPFS = 0x37AFFC90
EFI_STORAGE_SPACES = 0xE75CAF8F
EFI_HPUX_DATA = 0x75894C1E
EFI_HPUX_SERVICE = 0xE2A1E728
EFI_LINUX_DAYA = 0x0FC63DAF
EFI_LINUX_RAID = 0xA19D880F
EFI_LINUX_ROOT32 = 0x44479540
EFI_LINUX_ROOT64 = 0x4F68BCE3
EFI_LINUX_ROOT_ARM32 = 0x69DAD710
EFI_LINUX_ROOT_ARM64 = 0xB921B045
EFI_LINUX_SWAP = 0x0657FD6D
EFI_LINUX_LVM = 0xE6D6D379
EFI_LINUX_HOME = 0x933AC7E1
EFI_LINUX_SRV = 0x3B8F8425
EFI_LINUX_DM_CRYPT = 0x7FFEC5C9
EFI_LINUX_LUKS = 0xCA7D7CCB
EFI_LINUX_RESERVED = 0x8DA63339
EFI_FREEBSD_BOOT = 0x83BD6B9D
EFI_FREEBSD_DATA = 0x516E7CB4
EFI_FREEBSD_SWAP = 0x516E7CB5
EFI_FREEBSD_UFS = 0x516E7CB6
EFI_FREEBSD_VINUM = 0x516E7CB8
EFI_FREEBSD_ZFS = 0x516E7CBA
EFI_OSX_HFS = 0x48465300
EFI_OSX_UFS = 0x55465300
EFI_OSX_ZFS = 0x6A898CC3
EFI_OSX_RAID = 0x52414944
EFI_OSX_RAID_OFFLINE = 0x52414944
EFI_OSX_RECOVERY = 0x426F6F74
EFI_OSX_LABEL = 0x4C616265
EFI_OSX_TV_RECOVERY = 0x5265636F
EFI_OSX_CORE_STORAGE = 0x53746F72
EFI_SOLARIS_BOOT = 0x6A82CB45
EFI_SOLARIS_ROOT = 0x6A85CF4D
EFI_SOLARIS_SWAP = 0x6A87C46F
EFI_SOLARIS_BACKUP = 0x6A8B642B
EFI_SOLARIS_USR = 0x6A898CC3
EFI_SOLARIS_VAR = 0x6A8EF2E9
EFI_SOLARIS_HOME = 0x6A90BA39
EFI_SOLARIS_ALTERNATE = 0x6A9283A5
EFI_SOLARIS_RESERVED1 = 0x6A945A3B
EFI_SOLARIS_RESERVED2 = 0x6A9630D1
EFI_SOLARIS_RESERVED3 = 0x6A980767
EFI_SOLARIS_RESERVED4 = 0x6A96237F
EFI_SOLARIS_RESERVED5 = 0x6A8D2AC7
EFI_NETBSD_SWAP = 0x49F48D32
EFI_NETBSD_FFS = 0x49F48D5A
EFI_NETBSD_LFS = 0x49F48D82
EFI_NETBSD_RAID = 0x49F48DAA
EFI_NETBSD_CONCAT = 0x2DB519C4
EFI_NETBSD_ENCRYPT = 0x2DB519EC
EFI_CHROMEOS_KERNEL = 0xFE3A2A5D
EFI_CHROMEOS_ROOTFS = 0x3CB8E202
EFI_CHROMEOS_FUTURE = 0x2E0A753D
EFI_HAIKU = 0x42465331
EFI_MIDNIGHTBSD_BOOT = 0x85D5E45E
EFI_MIDNIGHTBSD_DATA = 0x85D5E45A
EFI_MIDNIGHTBSD_SWAP = 0x85D5E45B
EFI_MIDNIGHTBSD_UFS = 0x0394EF8B
EFI_MIDNIGHTBSD_VINUM = 0x85D5E45C
EFI_MIDNIGHTBSD_ZFS = 0x85D5E45D
EFI_CEPH_JOURNAL = 0x45B0969E
EFI_CEPH_ENCRYPT = 0x45B0969E
EFI_CEPH_OSD = 0x4FBD7E29
EFI_CEPH_ENCRYPT_OSD = 0x4FBD7E29
EFI_CEPH_CREATE = 0x89C57F98
EFI_CEPH_ENCRYPT_CREATE = 0x89C57F98
EFI_OPENBSD = 0x824CC7A0
EFI_QNX = 0xCEF5A9AD
EFI_PLAN9 = 0xC91818F9
EFI_VMWARE_VMKCORE = 0x9D275380
EFI_VMWARE_VMFS = 0xAA31E02A
EFI_VMWARE_RESERVED = 0x9198EFFC
def __init__(self, num_part_entries=0, part_entry_size=0, part_entry_start_lba=0, loglevel=logging.INFO, *args,
**kwargs):
self.num_part_entries = num_part_entries
self.__logger = self.__logger
self.part_entry_size = part_entry_size
self.part_entry_start_lba = part_entry_start_lba
self.totalsectors = None
self.header = None
self.sectorsize = None
self.partentries = []
self.error = self.__logger.error
self.__logger.setLevel(loglevel)
if loglevel == logging.DEBUG:
logfilename = "log.txt"
fh = logging.FileHandler(logfilename)
self.__logger.addHandler(fh)
def parseheader(self, gptdata, sectorsize=512):
return self.gpt_header(gptdata[sectorsize:sectorsize + 0x5C])
def parse(self, gptdata, sectorsize=512):
self.header = self.gpt_header(gptdata[sectorsize:sectorsize + 0x5C])
self.sectorsize = sectorsize
if self.header.signature != b"EFI PART":
return False
if self.header.revision != 0x10000:
self.error("Unknown GPT revision.")
return False
if self.part_entry_start_lba != 0:
start = self.part_entry_start_lba
else:
start = self.header.part_entry_start_lba * sectorsize
entrysize = self.header.part_entry_size
self.partentries = []
class partf:
unique = b""
first_lba = 0
last_lba = 0
flags = 0
sector = 0
sectors = 0
type = b""
name = ""
num_part_entries = self.header.num_part_entries
for idx in range(0, num_part_entries):
data = gptdata[start + (idx * entrysize):start + (idx * entrysize) + entrysize]
if int(hexlify(data[16:32]), 16) == 0:
break
partentry = self.gpt_partition(data)
pa = partf()
guid1 = unpack("<I", partentry.unique[0:0x4])[0]
guid2 = unpack("<H", partentry.unique[0x4:0x6])[0]
guid3 = unpack("<H", partentry.unique[0x6:0x8])[0]
guid4 = unpack("<H", partentry.unique[0x8:0xA])[0]
guid5 = hexlify(partentry.unique[0xA:0x10]).decode('utf-8')
pa.unique = "{:08x}-{:04x}-{:04x}-{:04x}-{}".format(guid1, guid2, guid3, guid4, guid5)
pa.sector = partentry.first_lba
pa.sectors = partentry.last_lba - partentry.first_lba + 1
pa.flags = partentry.flags
type = int(unpack("<I", partentry.type[0:0x4])[0])
try:
pa.type = self.efi_type(type).name
except:
pa.type = hex(type)
pa.name = partentry.name.replace(b"\x00\x00", b"").decode('utf-16')
if pa.type == "EFI_UNUSED":
continue
self.partentries.append(pa)
self.totalsectors = self.header.last_usable_lba + 34
return True
def print(self):
print(self.tostring())
def tostring(self):
mstr = "\nGPT Table:\n-------------\n"
for partition in self.partentries:
mstr += ("{:20} Offset 0x{:016x}, Length 0x{:016x}, Flags 0x{:08x}, UUID {}, Type {}\n".format(
partition.name + ":", partition.sector * self.sectorsize, partition.sectors * self.sectorsize,
partition.flags, partition.unique, partition.type))
mstr += ("\nTotal disk size:0x{:016x}, sectors:0x{:016x}\n".format(self.totalsectors * self.sectorsize,
self.totalsectors))
return mstr
def generate_rawprogram(self, lun, sectorsize, directory):
fname = "rawprogram" + str(lun) + ".xml"
with open(os.path.join(directory, fname), "wb") as wf:
mstr = "<?xml version=\"1.0\" ?>\n<data>\n"
partofsingleimage = "false"
readbackverify = "false"
sparse = "false"
for partition in self.partentries:
filename = partition.name + ".bin"
mstr += f"\t<program SECTOR_SIZE_IN_BYTES=\"{sectorsize}\" " + \
f"file_sector_offset=\"0\" " \
f"filename=\"{filename}\" " + \
f"label=\"{partition.name}\" " \
f"num_partition_sectors=\"{partition.sectors}\" " + \
f"partofsingleimage=\"{partofsingleimage}\" " \
f"physical_partition_number=\"{str(lun)}\" " + \
f"readbackverify=\"{readbackverify}\" " \
f"size_in_KB=\"{(partition.sectors * sectorsize / 1024):.1f}\" " \
f"sparse=\"{sparse}\" " + \
f"start_byte_hex=\"{hex(partition.sector * sectorsize)}\" " \
f"start_sector=\"{partition.sector}\"/>\n"
partofsingleimage = "true"
sectors = self.header.first_usable_lba
mstr += f"\t<program SECTOR_SIZE_IN_BYTES=\"{sectorsize}\" " + \
f"file_sector_offset=\"0\" " + \
f"filename=\"gpt_main{str(lun)}.bin\" " + \
f"label=\"PrimaryGPT\" " + \
f"num_partition_sectors=\"{sectors}\" " + \
f"partofsingleimage=\"{partofsingleimage}\" " + \
f"physical_partition_number=\"{str(lun)}\" " + \
f"readbackverify=\"{readbackverify}\" " + \
f"size_in_KB=\"{(sectors * sectorsize / 1024):.1f}\" " + \
f"sparse=\"{sparse}\" " + \
f"start_byte_hex=\"0x0\" " + \
f"start_sector=\"0\"/>\n"
sectors = self.header.first_usable_lba - 1
mstr += f"\t<program SECTOR_SIZE_IN_BYTES=\"{sectorsize}\" " + \
f"file_sector_offset=\"0\" " + \
f"filename=\"gpt_backup{str(lun)}.bin\" " + \
f"label=\"BackupGPT\" " + \
f"num_partition_sectors=\"{sectors}\" " + \
f"partofsingleimage=\"{partofsingleimage}\" " + \
f"physical_partition_number=\"{str(lun)}\" " + \
f"readbackverify=\"{readbackverify}\" " + \
f"size_in_KB=\"{(sectors * sectorsize / 1024):.1f}\" " + \
f"sparse=\"{sparse}\" " + \
f"start_byte_hex=\"({sectorsize}*NUM_DISK_SECTORS)-{sectorsize * sectors}.\" " + \
f"start_sector=\"NUM_DISK_SECTORS-{sectors}.\"/>\n"
mstr += "</data>"
wf.write(bytes(mstr, 'utf-8'))
print(f"Wrote partition xml as {fname}")
def print_gptfile(self, filename):
try:
filesize = os.stat(filename).st_size
with open(filename, "rb") as rf:
size = min(32 * 4096, filesize)
data = rf.read(size)
for sectorsize in [512, 4096]:
result = self.parse(data, sectorsize)
if result:
break
if result:
print(self.tostring())
return result
except Exception as e:
self.error(str(e))
return ""
def test_gpt(self):
res = self.print_gptfile(os.path.join("TestFiles", "gpt_sm8180x.bin"))
assert res, "GPT Partition wasn't decoded properly"
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="GPT utils")
subparsers = parser.add_subparsers(dest="command", help='sub-command help')
parser_print = subparsers.add_parser("print", help="Print the gpt table")
parser_print.add_argument("image", help="The path of the GPT disk image")
parser_test = subparsers.add_parser("test", help="Run self-test")
parser_extract = subparsers.add_parser("extract", help="Extract the partitions")
parser_extract.add_argument("image", help="The path of the GPT disk image")
parser_extract.add_argument("-out", "-o", help="The path to extract the partitions")
parser_extract.add_argument("-partition", "-p", help="Extract specific partitions (separated by comma)")
args = parser.parse_args()
if args.command not in ["print", "extract", "test"]:
parser.error("Command is mandatory")
gp = gpt()
if args.command == "print":
if not os.path.exists(args.image):
print(f"File {args.image} does not exist. Aborting.")
sys.exit(1)
gp.print_gptfile(args.image)
elif args.command == "test":
gp.test_gpt()
elif args.command == "extract":
if not os.path.exists(args.image):
print(f"File {args.image} does not exist. Aborting.")
sys.exit(1)
filesize = os.stat(args.image).st_size
with open(args.image, "rb", buffering=1024 * 1024) as rf:
data = rf.read(min(32 * 4096, filesize))
ssize = None
for sectorsize in [512, 4096]:
result = gp.parse(data, sectorsize)
if result:
ssize = sectorsize
break
if ssize is not None:
for partition in gp.partentries:
if args.partition is not None:
if partition != args.partition:
continue
name = partition.name
start = partition.sector * ssize
length = partition.sectors * ssize
out = args.out
if out is None:
out = "."
if not os.path.exists(out):
os.makedirs(out)
filename = os.path.join(out, name)
rf.seek(start)
bytestoread = length
with open(filename, "wb", buffering=1024 * 1024) as wf:
while bytestoread > 0:
size = min(bytestoread, 0x200000)
rf.read(size)
wf.write(size)
bytestoread -= size
print(f"Extracting {name} to {filename} at {hex(start)}, length {hex(length)}")

240
edl/Library/hdlc.py Executable file
View file

@ -0,0 +1,240 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# (c) B.Kerler 2018-2019
import logging
from binascii import hexlify
from struct import unpack
import time
MAX_PACKET_LEN = 4096
crcTbl = (
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78)
def serial16le(data):
out = bytearray()
out.append(data & 0xFF)
out.append((data >> 8) & 0xFF)
return out
def serial16(data):
out = bytearray()
out.append((data >> 8) & 0xFF)
out.append(data & 0xFF)
return out
def serial32le(data):
out = bytearray()
out += serial16le(data & 0xFFFF)
out += serial16le((data >> 16) & 0xFFFF)
return out
def crc16(iv, data):
for byte in data:
iv = ((iv >> 8) & 0xFFFF) ^ crcTbl[(iv ^ byte) & 0xFF]
return ~iv & 0xFFFF
def serial32(data):
out = bytearray()
out += serial16((data >> 16) & 0xFFFF)
out += serial16(data & 0xFFFF)
return out
def escape(indata):
outdata = bytearray()
for i in range(0, len(indata)):
buf = indata[i]
if buf == 0x7e:
outdata.append(0x7d)
outdata.append(0x5e)
elif buf == 0x7d:
outdata.append(0x7d)
outdata.append(0x5d)
else:
outdata.append(buf)
return outdata
def unescape(indata):
mescape = False
out = bytearray()
for buf in indata:
if mescape:
if buf == 0x5e:
out.append(0x7e)
elif buf == 0x5d:
out.append(0x7d)
else:
logging.error("Fatal error unescaping buffer!")
return None
mescape = False
else:
if buf == 0x7d:
mescape = True
else:
out.append(buf)
if len(out) == 0:
return None
return out
def convert_cmdbuf(indata):
crc16val = crc16(0xFFFF, indata)
indata.extend(bytearray(serial16le(crc16val)))
outdata = escape(indata)
outdata.append(0x7E)
return outdata
class hdlc:
def __init__(self, cdc):
self.cdc = cdc
self.programmer = None
self.timeout = 1500
def receive_reply(self, timeout=None):
replybuf = bytearray()
if timeout is None:
timeout = self.timeout
tmp = self.cdc.read(MAX_PACKET_LEN, timeout)
if tmp == bytearray():
return 0
if tmp == b"":
return 0
retry = 0
while tmp[-1] != 0x7E:
time.sleep(0.01)
tmp += self.cdc.read(MAX_PACKET_LEN, timeout)
retry += 1
if retry > 5:
break
replybuf.extend(tmp)
data = unescape(replybuf)
# print(hexlify(data))
if len(data) > 3:
crc16val = crc16(0xFFFF, data[:-3])
reccrc = int(data[-3]) + (int(data[-2]) << 8)
if crc16val != reccrc:
return -1
else:
time.sleep(0.01)
data = self.cdc.read(MAX_PACKET_LEN, timeout)
if len(data) > 3:
crc16val = crc16(0xFFFF, data[:-3])
reccrc = int(data[-3]) + (int(data[-2]) << 8)
if crc16val != reccrc:
return -1
return data
return data[:-3]
def receive_reply_nocrc(self, timeout=None):
replybuf = bytearray()
if timeout is None:
timeout = self.timeout
tmp = self.cdc.read(MAX_PACKET_LEN, timeout)
if tmp == bytearray():
return 0
if tmp == b"":
return 0
retry = 0
while tmp[-1] != 0x7E:
# time.sleep(0.05)
tmp += self.cdc.read(MAX_PACKET_LEN, timeout)
retry += 1
if retry > 5:
break
replybuf.extend(tmp)
data = unescape(replybuf)
# print(hexlify(data))
if len(data) > 3:
# crc16val = self.crc16(0xFFFF, data[:-3])
# reccrc = int(data[-3]) + (int(data[-2]) << 8)
return data[:-3]
else:
time.sleep(0.5)
data = self.cdc.read(MAX_PACKET_LEN, timeout)
if len(data) > 3:
# crc16val = self.crc16(0xFFFF, data[:-3])
# reccrc = int(data[-3]) + (int(data[-2]) << 8)
return data[:-3]
else:
return data
def send_unframed_buf(self, outdata, prefixflag):
# ttyflush()
if prefixflag:
tmp = bytearray()
tmp.append(0x7E)
tmp.extend(outdata)
outdata = tmp
return self.cdc.write(outdata[:MAX_PACKET_LEN])
# FlushFileBuffers(ser)
def send_cmd_base(self, outdata, prefixflag, nocrc=False):
if isinstance(outdata, str):
outdata = bytes(outdata, 'utf-8')
packet = convert_cmdbuf(bytearray(outdata))
if self.send_unframed_buf(packet, prefixflag):
if nocrc:
return self.receive_reply_nocrc()
else:
return self.receive_reply()
return b""
def send_cmd(self, outdata, nocrc=False):
return self.send_cmd_base(outdata, 1, nocrc)
def send_cmd_np(self, outdata, nocrc=False):
return self.send_cmd_base(outdata, 0, nocrc)
def show_errpacket(self, descr, pktbuf):
if len(pktbuf) == 0:
return
logging.error("Error: %s " % descr)
if pktbuf[1] == 0x0e:
pktbuf[-4] = 0
# puts(pktbuf+2)
ret = self.receive_reply()
errorcode = unpack("<I", ret[2:2 + 4])
logging.error("Error code = %08x\n\n", errorcode)
else:
print(hexlify(pktbuf))

78
edl/Library/memparse.py Executable file
View file

@ -0,0 +1,78 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# (c) B.Kerler 2018-2021
import os
import pt64
import pt
import argparse
def pt64_walk(data, ttbr, tnsz, levels=3):
print("Dumping page tables (levels=%d)" % levels)
print("First level (ptbase = %016x)" % ttbr)
print("---------------------------------------------")
fl = data[ttbr - ttbr:ttbr - ttbr + 0x1000]
if levels <= 1:
return
for (va, fle) in pt64.parse_pt(fl, 0, tnsz, 1):
if "TABLE" in str(fle):
print("Second level (ptbase = %016x)" % fle.output)
print("---------------------------------------------")
sl = data[fle.output - ttbr:fle.output - ttbr + 0x4000]
sl = pt64.parse_pt(sl, va, tnsz, 2)
if levels <= 2:
continue
for (mva, sle) in sl:
if "TABLE" in str(sle):
print("Third level (ptbase = %016x)" % sle.output)
print("---------------------------------------------")
tl = data[sle.output - ttbr:sle.output - ttbr + 0x1000]
pt64.parse_pt(tl, mva, tnsz, 3)
def pt32_walk(data, ttbr, skip):
print("First level (va = %08x)" % ttbr)
print("---------------------------------------------")
fl = data[ttbr - ttbr:ttbr - ttbr + 0x4000]
i = 0
for (va, fl) in pt.parse_pt(fl):
i += 1
if i <= skip:
continue
if type(fl) == pt.pt_desc:
print("")
print("Second level (va = %08x)" % va)
print("---------------------------------------------")
sldata = data[fl.coarse_base - ttbr:fl.coarse_base - ttbr + 0x400]
pt.parse_spt(sldata, va)
def main():
parser = argparse.ArgumentParser(
prog="memparse",
usage="python memparse.py -arch <32,64> -in <filename> -mem <offset>",
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('-in', '--in', dest='infile', help='memory dump', default="")
parser.add_argument('-arch', '--arch', dest='arch', help='architecture=32,64', default="32")
parser.add_argument('-mem', '--mem', dest='mem', help='memoryoffset', default="0x200000")
args = parser.parse_args()
if args.infile == "":
print("You need to add an -in [memorydump filename]")
return
with open(args.infile, "rb") as rf:
data = rf.read()
if args.arch == "32":
pt32_walk(data, int(args.mem, 16), False)
else:
pt64_walk(data, int(args.mem, 16), 0, 3)
main()

789
edl/Library/nand_config.py Normal file
View file

@ -0,0 +1,789 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# (c) B.Kerler 2018-2019
import ctypes
from enum import Enum
from edl.Config.qualcomm_config import secgen, secureboottbl
c_uint8 = ctypes.c_uint8
# nandbase MSM_NAND_BASE
# qfprom SECURITY_CONTROL_BASE_PHYS
config_tbl = {
# bam nandbase bcraddr secureboot pbl qfprom memtbl
3: ["9x25", 1, 0xf9af0000, 0xfc401a40, secureboottbl["MDM9x25"], secgen[2][0], secgen[2][1], secgen[2][2]],
8: ["9x35", 1, 0xf9af0000, 0xfc401a40, secureboottbl["MDM9x35"], secgen[2][0], secgen[2][1], secgen[2][2]],
10: ["9x45", 1, 0x79B0000, 0x183f000, secureboottbl["MDM9x45"], secgen[2][0], secgen[2][1], secgen[2][2]],
16: ["9x55", 0, 0x79B0000, 0x183f000, secureboottbl["MDM9x45"], secgen[5][0], secgen[5][1], secgen[5][2]],
17: ["9x60", 0, 0x79B0000, 0x183f000, secureboottbl["MDM9x60"], secgen[5][0], secgen[5][1], secgen[5][2]],
12: ["9x07", 0, 0x79B0000, 0x183f000, secureboottbl["MDM9607"], secgen[5][0], secgen[5][1], secgen[5][2]]
}
supported_flash = {
# Flash ID Density(MB) Wid Pgsz Blksz oobsz onenand Manuf */
0x2690ac2c: [(512 << 20), 0, 4096, (4096 << 6), 224, 0], # QUECTEL_NAND_FM6BD4G2GXA
0x2690ac98: [(512 << 20), 0, 4096, (4096 << 6), 256, 0], # QUECTEL_NAND_NM14FSK2LAXCL
0x1500aa98: [(256 << 20), 0, 2048, (2048 << 6), 64, 0],
0x5500ba98: [(256 << 20), 1, 2048, (2048 << 6), 64, 0],
0xd580b12c: [(256 << 20), 1, 2048, (2048 << 6), 64, 0],
0x5590bc2c: [(512 << 20), 1, 2048, (2048 << 6), 64, 0],
0x1580aa2c: [(256 << 20), 0, 2048, (2048 << 6), 64, 0],
0x1590aa2c: [(256 << 20), 0, 2048, (2048 << 6), 64, 0],
0x1590ac2c: [(512 << 20), 0, 2048, (2048 << 6), 64, 0],
0x5580baad: [(256 << 20), 1, 2048, (2048 << 6), 64, 0],
0x5510baad: [(256 << 20), 1, 2048, (2048 << 6), 64, 0],
# 0x004000ec: [(256 << 20), 0, 2048, (2048 << 6), 64, 1],
# 0x005c00ec: [(256 << 20), 0, 2048, (2048 << 6), 64, 1],
# 0x005800ec: [(256 << 20), 0, 2048, (2048 << 6), 64, 1],
0x5580ba2c: [(256 << 20), 1, 2048, (2048 << 6), 64, 0],
0x6600b3ec: [(1024 << 20), 1, 4096, (4096 << 6), 128, 0],
0x55d1b32c: [(1024 << 20), 1, 2048, (2048 << 6), 64, 0]
# 0x1500aaec: 0xFF00FFFF, (256 << 20), 0, 2048, (2048 << 6), 64, 0],
# 0x5500baec: 0xFF00FFFF, (256 << 20), 1, 2048, (2048 << 6), 64, 0}, /*Sams */
# 0x6600bcec: 0xFF00FFFF, (512 << 20), 1, 4096, (4096 << 6), 128, 0}, /*Sams */
# 0x2600482c: 0xFF00FFFF, (2048 << 20), 0, 4096, (4096 << 7), 224, 0}, /*8bit bch ecc */
}
class BadFlags(Enum):
BAD_UNDEF = 0
BAD_FILL = 1
BAD_SKIP = 2
BAD_IGNORE = 3
BAD_DISABLE = 4
nand_ids = [
("NAND 16MiB 1,8V 8-bit", 0x33, 16),
("NAND 16MiB 3,3V 8-bit", 0x73, 16),
("NAND 16MiB 1,8V 16-bit", 0x43, 16),
("NAND 16MiB 3,3V 16-bit", 0x53, 16),
("NAND 32MiB 1,8V 8-bit", 0x35, 32),
("NAND 32MiB 3,3V 8-bit", 0x75, 32),
("NAND 32MiB 1,8V 16-bit", 0x45, 32),
("NAND 32MiB 3,3V 16-bit", 0x55, 32),
("NAND 64MiB 1,8V 8-bit", 0x36, 64),
("NAND 64MiB 3,3V 8-bit", 0x76, 64),
("NAND 64MiB 1,8V 16-bit", 0x46, 64),
("NAND 64MiB 3,3V 16-bit", 0x56, 64),
("NAND 128MiB 1,8V 8-bit", 0x78, 128),
("NAND 128MiB 1,8V 8-bit", 0x39, 128),
("NAND 128MiB 3,3V 8-bit", 0x79, 128),
("NAND 128MiB 1,8V 16-bit", 0x72, 128),
("NAND 128MiB 1,8V 16-bit", 0x49, 128),
("NAND 128MiB 3,3V 16-bit", 0x74, 128),
("NAND 128MiB 3,3V 16-bit", 0x59, 128),
("NAND 256MiB 3,3V 8-bit", 0x71, 256),
# 512 Megabit
("NAND 64MiB 1,8V 8-bit", 0xA2, 64),
("NAND 64MiB 1,8V 8-bit", 0xA0, 64),
("NAND 64MiB 3,3V 8-bit", 0xF2, 64),
("NAND 64MiB 3,3V 8-bit", 0xD0, 64),
("NAND 64MiB 1,8V 16-bit", 0xB2, 64),
("NAND 64MiB 1,8V 16-bit", 0xB0, 64),
("NAND 64MiB 3,3V 16-bit", 0xC2, 64),
("NAND 64MiB 3,3V 16-bit", 0xC0, 64),
# 1 Gigabit
("NAND 128MiB 1,8V 8-bit", 0xA1, 128),
("NAND 128MiB 3,3V 8-bit", 0xF1, 128),
("NAND 128MiB 3,3V 8-bit", 0xD1, 128),
("NAND 128MiB 1,8V 16-bit", 0xB1, 128),
("NAND 128MiB 3,3V 16-bit", 0xC1, 128),
("NAND 128MiB 1,8V 16-bit", 0xAD, 128),
# 2 Gigabit
("NAND 256MiB 1.8V 8-bit", 0xAA, 256),
("NAND 256MiB 3.3V 8-bit", 0xDA, 256),
("NAND 256MiB 1.8V 16-bit", 0xBA, 256),
("NAND 256MiB 3.3V 16-bit", 0xCA, 256),
# 4 Gigabit
("NAND 512MiB 1.8V 8-bit", 0xAC, 512),
("NAND 512MiB 3.3V 8-bit", 0xDC, 512),
("NAND 512MiB 1.8V 16-bit", 0xBC, 512),
("NAND 512MiB 3.3V 16-bit", 0xCC, 512),
# 8 Gigabit
("NAND 1GiB 1.8V 8-bit", 0xA3, 1024),
("NAND 1GiB 3.3V 8-bit", 0xD3, 1024),
("NAND 1GiB 1.8V 16-bit", 0xB3, 1024),
("NAND 1GiB 3.3V 16-bit", 0xC3, 1024),
# 16 Gigabit
("NAND 2GiB 1.8V 8-bit", 0xA5, 2048),
("NAND 2GiB 3.3V 8-bit", 0xD5, 2048),
("NAND 2GiB 1.8V 16-bit", 0xB5, 2048),
("NAND 2GiB 3.3V 16-bit", 0xC5, 2048),
# 32 Gigabit
("NAND 4GiB 1.8V 8-bit", 0xA7, 4096),
("NAND 4GiB 3.3V 8-bit", 0xD7, 4096),
("NAND 4GiB 1.8V 16-bit", 0xB7, 4096),
("NAND 4GiB 3.3V 16-bit", 0xC7, 4096),
# 64 Gigabit
("NAND 8GiB 1.8V 8-bit", 0xAE, 8192),
("NAND 8GiB 3.3V 8-bit", 0xDE, 8192),
("NAND 8GiB 1.8V 16-bit", 0xBE, 8192),
("NAND 8GiB 3.3V 16-bit", 0xCE, 8192),
# 128 Gigabit
("NAND 16GiB 1.8V 8-bit", 0x1A, 16384),
("NAND 16GiB 3.3V 8-bit", 0x3A, 16384),
("NAND 16GiB 1.8V 16-bit", 0x2A, 16384),
("NAND 16GiB 3.3V 16-bit", 0x4A, 16384),
# 256 Gigabit
("NAND 32GiB 1.8V 8-bit", 0x1C, 32768),
("NAND 32GiB 3.3V 8-bit", 0x3C, 32768),
("NAND 32GiB 1.8V 16-bit", 0x2C, 32768),
("NAND 32GiB 3.3V 16-bit", 0x4C, 32768),
# 512 Gigabit
("NAND 64GiB 1.8V 8-bit", 0x1E, 65536),
("NAND 64GiB 3.3V 8-bit", 0x3E, 65536),
("NAND 64GiB 1.8V 16-bit", 0x2E, 65536),
("NAND 64GiB 3.3V 16-bit", 0x4E, 65536),
(0, 0, 0),
]
nand_manuf_ids = [
(0x98, "Toshiba"),
(0xec, "Samsung"),
(0x04, "Fujitsu"),
(0x8f, "National"),
(0x07, "Renesas"),
(0x20, "ST Micro"),
(0xad, "Hynix"),
(0x2c, "Micron"),
(0xc8, "Elite Semiconductor"),
(0x01, "Spansion/AMD"),
(0xef, "Winbond"),
(0x0, "")
]
toshiba_tbl = {
# small_slc,device_width,density_mbits
0x36: [True, 8, 512],
0x46: [True, 16, 512],
0x79: [True, 8, 1024],
0xA0: [False, -1, 512],
0xB0: [False, -1, 512],
0xC0: [False, -1, 512],
0xD0: [False, -1, 512],
0xA1: [False, -1, 1024],
0xB1: [False, -1, 1024],
0xC1: [False, -1, 1024],
0xD1: [False, -1, 1024],
0xAA: [False, -1, 2048],
0xBA: [False, -1, 2048],
0xCA: [False, -1, 2048],
0xDA: [False, -1, 2048],
0xAC: [False, -1, 4096],
0xBC: [False, -1, 4096],
0xCC: [False, -1, 4096],
0xDC: [False, -1, 4096],
0xA3: [False, -1, 8192],
0xB3: [False, -1, 8192],
0xC3: [False, -1, 8192],
0xD3: [False, -1, 8192],
0xA5: [False, -1, 16384],
0xB5: [False, -1, 16384],
0xC5: [False, -1, 16384],
0xD5: [False, -1, 16384],
}
samsung_tbl = {
# small_slc,device_width,density_mbits
0x45: [True, 16, 256],
0x55: [True, 16, 256],
0x35: [True, 8, 256],
0x75: [True, 8, 256],
0x46: [True, 16, 512],
0x56: [True, 16, 512],
0x76: [True, 8, 512],
0x36: [True, 8, 512],
0x72: [True, 16, 1024],
0x74: [True, 16, 1024],
0x79: [True, 8, 1024],
0x78: [True, 8, 1024],
0x71: [True, 8, 2048],
0xDC: [True, 8, 4096],
0xA1: [False, -1, 1024],
0xB1: [False, -1, 1024],
0xC1: [False, -1, 1024],
0xD1: [False, -1, 1024],
0xAA: [False, -1, 2048],
0xBA: [False, -1, 2048],
0xCA: [False, -1, 2048],
0xDA: [False, -1, 2048],
0xAC: [False, -1, 4096],
0xBC: [False, -1, 4096],
0xCC: [False, -1, 4096],
0xA3: [False, -1, 8192],
0xB3: [False, -1, 8192],
0xC3: [False, -1, 8192],
0xD3: [False, -1, 8192],
0xA5: [False, -1, 16384],
0xB5: [False, -1, 16384],
0xC5: [False, -1, 16384],
0xD5: [False, -1, 16384]
}
class SettingsOpt:
def __init__(self, parent, chipset):
self.PAGESIZE = 4096
self.parent = parent
self.bad_loader = 0
self.sectors_per_page = 0
self.sectorsize = 512
self.flash_mfr = ""
self.flash_descr = ""
self.IsWideFlash = 0
self.badsector = 0
self.badflag = 0
self.badposition = 0
self.badplace = 0
self.bch_mode = 0
self.ecc_size = 0
self.ecc_bit = 0
self.num_pages_per_blk = 64
self.udflag = 0
self.sahara = 1
self.args_disable_ecc = 0 # 0=enable, 1=disable
self.bad_processing_flag = BadFlags.BAD_SKIP.value
self.cwsize = 0
self.rflag = 0
self.cfg1_enable_bch_ecc = 0
self.bad_loader = 0
self.OOBSIZE = 0
self.MAXBLOCK = 0
self.UD_SIZE_BYTES = 516
self.BAD_BLOCK_BYTE_NUM = 0
self.BAD_BLOCK_IN_SPARE_AREA = 0
self.ECC_MODE = 0
self.bad_loader = 1
self.secureboot = secureboottbl["MDM9607"]
self.pbl = secgen[5][0]
self.qfprom = secgen[5][1]
self.memtbl = secgen[5][2]
self.chipname = "Unknown"
if chipset in config_tbl:
self.chipname, self.bam, self.nandbase, self.bcraddr, self.secureboot, self.pbl, \
self.qfprom, self.memtbl = config_tbl[chipset]
self.bad_loader = 0
else:
loadername = parent.sahara.programmer.lower()
for chipid in config_tbl:
if config_tbl[chipid][0] in loadername:
self.chipname, self.bam, self.nandbase, self.bcraddr, self.secureboot, \
self.pbl, self.qfprom, self.memtbl = config_tbl[chipid]
self.bad_loader = 0
if chipset == 0xFF:
self.bad_loader = 0
class nand_toshiba_ids(ctypes.LittleEndianStructure):
_fields_ = [
("mid", c_uint8, 8),
("did", c_uint8, 8),
("icn", c_uint8, 2),
("bpc", c_uint8, 2),
("rsvd0", c_uint8, 4),
("page_size", c_uint8, 2),
("spare_size", c_uint8, 2),
("block_size", c_uint8, 2),
("org", c_uint8, 1),
("rsvd1", c_uint8, 1),
("rsvd2", c_uint8, 8),
]
class nand_toshiba_id_t(ctypes.Union):
_anonymous_ = ("bit",)
_fields_ = [
("bit", nand_toshiba_ids),
("asDword", ctypes.c_uint32)
]
class nand_samsung_ids(ctypes.LittleEndianStructure):
_fields_ = [
("mid", c_uint8, 8),
("did", c_uint8, 8),
("icn", c_uint8, 2),
("bpc", c_uint8, 2),
("nspp", c_uint8, 2),
("ip", c_uint8, 1),
("cp", c_uint8, 1),
("page_size", c_uint8, 2),
("spare_size", c_uint8, 2),
("sam0", c_uint8, 1),
("block_size", c_uint8, 2),
("org", c_uint8, 1),
("sam1", c_uint8, 1),
("ecc", c_uint8, 2),
("plane", c_uint8, 2),
("plane_size", c_uint8, 3),
("rsvd", c_uint8, 1),
]
class nand_samsung_id_t(ctypes.Union):
_anonymous_ = ("bit",)
_fields_ = [
("bit", nand_toshiba_ids),
("asDword", ctypes.c_uint32)
]
class NandDevice:
# NAND_DEVn_CFG0 bits
DISABLE_STATUS_AFTER_WRITE = 4
CW_PER_PAGE = 6
UD_SIZE_BYTES = 9
ECC_PARITY_SIZE_BYTES_RS = 19
SPARE_SIZE_BYTES = 23
NUM_ADDR_CYCLES = 27
STATUS_BFR_READ = 30
SET_RD_MODE_AFTER_STATUS = 31
# NAND_DEV1_CFG0 bits
DEV0_CFG1_ECC_DISABLE = 0
WIDE_FLASH = 1
NAND_RECOVERY_CYCLES = 2
CS_ACTIVE_BSY = 5
BAD_BLOCK_BYTE_NUM = 6
BAD_BLOCK_IN_SPARE_AREA = 16
WR_RD_BSY_GAP = 17
ENABLE_BCH_ECC = 27
ECC_ENCODER_CGC_EN = 23
ECC_DECODER_CGC_EN = 24
DISABLE_ECC_RESET_AFTER_OPDONE = 25
ENABLE_NEW_ECC = 27
ECC_MODE_DEV1 = 28 # 28:29
# NAND_DEV0_ECC_CFG bits
ECC_CFG_ECC_DISABLE = 0
ECC_SW_RESET = 1
ECC_MODE = 4
ECC_PARITY_SIZE_BYTES_BCH = 8
ECC_NUM_DATA_BYTES = 16
ECC_ENC_CLK_SHUTDOWN = 28
ECC_DEC_CLK_SHUTDOWN = 29
ECC_FORCE_CLK_OPEN = 30
# NAND_DEV_CMD1 bits
READ_ADDR = 0
# NAND_DEV_CMD_VLD bits
READ_START_VLD = 0
READ_STOP_VLD = 1
WRITE_START_VLD = 2
ERASE_START_VLD = 3
SEQ_READ_START_VLD = 4
NAND_CMD_PARAM = 0xec
ECC_BCH_4BIT = 2
def __init__(self, settings):
self.settings = settings
# device commands
self.NAND_CMD_SOFT_RESET = 0x01
self.NAND_CMD_PAGE_READ = 0x32
self.NAND_CMD_PAGE_READ_ECC = 0x33
self.NAND_CMD_PAGE_READ_ALL = 0x34
self.NAND_CMD_SEQ_PAGE_READ = 0x15
self.NAND_CMD_PRG_PAGE = 0x36
self.NAND_CMD_PRG_PAGE_ECC = 0x37
self.NAND_CMD_PRG_PAGE_ALL = 0x39
self.NAND_CMD_BLOCK_ERASE = 0x3A
self.NAND_CMD_FETCH_ID = 0x0B
self.NAND_CMD_STATUS = 0x0C
self.NAND_CMD_RESET = 0x0D
# addr offsets
self.NAND_FLASH_CMD = settings.nandbase + 0
self.NAND_ADDR0 = settings.nandbase + 4
self.NAND_ADDR1 = settings.nandbase + 8
self.NAND_FLASH_CHIP_SELECT = settings.nandbase + 0xc
self.NAND_EXEC_CMD = settings.nandbase + 0x10
self.NAND_FLASH_STATUS = settings.nandbase + 0x14
self.NAND_BUFFER_STATUS = settings.nandbase + 0x18
self.NAND_DEV0_CFG0 = settings.nandbase + 0x20
self.NAND_DEV0_CFG1 = settings.nandbase + 0x24
self.NAND_DEV0_ECC_CFG = settings.nandbase + 0x28
self.NAND_DEV1_ECC_CFG = settings.nandbase + 0x2C
self.NAND_DEV1_CFG0 = settings.nandbase + 0x30
self.NAND_DEV1_CFG1 = settings.nandbase + 0x34
self.NAND_SFLASHC_CMD = settings.nandbase + 0x38
self.NAND_SFLASHC_EXEC = settings.nandbase + 0x3C
self.NAND_READ_ID = settings.nandbase + 0x40
self.NAND_READ_STATUS = settings.nandbase + 0x44
self.NAND_CONFIG_DATA = settings.nandbase + 0x50
self.NAND_CONFIG = settings.nandbase + 0x54
self.NAND_CONFIG_MODE = settings.nandbase + 0x58
self.NAND_CONFIG_STATUS = settings.nandbase + 0x60
self.NAND_DEV_CMD0 = settings.nandbase + 0xA0
self.NAND_DEV_CMD1 = settings.nandbase + 0xA4
self.NAND_DEV_CMD2 = settings.nandbase + 0xA8
self.NAND_DEV_CMD_VLD = settings.nandbase + 0xAC
self.SFLASHC_BURST_CFG = settings.nandbase + 0xE0
self.NAND_EBI2_ECC_BUF_CFG = settings.nandbase + 0xF0
self.NAND_HW_INFO = settings.nandbase + 0xFC
self.NAND_FLASH_BUFFER = settings.nandbase + 0x100
self.PAGE_ACC = 1 << 4
self.LAST_PAGE = 1 << 5
self.CW_PER_PAGE = 6
self.ECC_CFG_ECC_DISABLE = 0
self.flashinfo = None
def gettbl(self, nandid, tbl):
flashinfo = {}
tid = nand_toshiba_id_t()
tid.asDword = nandid
# did,slc_small_device,device_width,density_mbits
if tid.did in tbl:
fdev = tbl[tid.did]
small_slc = fdev[0]
slc_device_width = fdev[1]
density_mbits = fdev[2]
else:
return None
if small_slc:
flashinfo["page_size"] = 512
flashinfo["feature_flags1_ecc"] = 2
flashinfo["block_size_kbytes"] = 16
flashinfo["param_per_block"] = 32
flashinfo["spare_size"] = 16
flashinfo["otp_sequence_cfg"] = "FLASH_NAND_OTP_SEQUENCE_CFG6"
flashinfo["dev_width"] = slc_device_width
else:
if tid.org == 0:
flashinfo["dev_width"] = 8
else:
flashinfo["dev_width"] = 16
if tid.spare_size == 0 or tid.spare_size == 1:
flashinfo["feature_flags1_ecc"] = 1
elif tid.spare_size == 2:
flashinfo["feature_flags1_ecc"] = 8
else:
flashinfo["feature_flags1_ecc"] = 0
flashinfo["page_size"] = 1024 << tid.page_size
flashinfo["page_size_kb"] = flashinfo["page_size"] >> 10
flashinfo["block_size_kb"] = 64 << tid.block_size
flashinfo["pages_per_block"] = flashinfo["block_size_kb"] // flashinfo["page_size_kb"]
flashinfo["spare_size"] = (8 << tid.spare_size) * (flashinfo["page_size"] // 512)
if flashinfo["page_size"] == 2048 or flashinfo["page_size"] == 4096:
flashinfo["otp_sequence_cfg"] = "FLASH_NAND_OTP_SEQUENCE_CFG2"
else:
flashinfo["otp_sequence_cfg"] = "FLASH_NAND_OTP_SEQUENCE_UNKNOWN"
flashinfo["block_count"] = (1024 // 8 * density_mbits) // flashinfo["block_size_kb"]
if flashinfo["page_size"] == 2048 and flashinfo["feature_flags1_ecc"] > 0:
flashinfo["bad_block_info_byte_offset"] = 2048
flashinfo["udata_max"] = 16
flashinfo["max_corrected_udata_bytes"] = 16
flashinfo["bad_block_info_byte_length"] = 1 if flashinfo["dev_width"] == 8 else 2
elif flashinfo["page_size"] == 4096 and flashinfo["feature_flags1_ecc"] > 0:
flashinfo["bad_block_info_byte_offset"] = 4096
flashinfo["udata_max"] = 32
flashinfo["max_corrected_udata_bytes"] = 32
flashinfo["bad_block_info_byte_length"] = 1 if flashinfo["dev_width"] == 8 else 2
self.settings.PAGESIZE = flashinfo["page_size"]
self.settings.BLOCKSIZE = flashinfo["block_size_kb"] * 1024
if flashinfo["dev_width"] == 8:
self.settings.IsWideFlash = 0
else:
self.settings.IsWideFlash = 1
self.settings.MAXBLOCK = flashinfo["block_count"]
self.settings.BAD_BLOCK_IN_SPARE_AREA = flashinfo["bad_block_info_byte_offset"]
return flashinfo
def toshiba_config(self, nandid):
flashinfo = self.gettbl(nandid, toshiba_tbl)
self.flashinfo = flashinfo
if (nandid >> 8) & 0xFF == 0xac:
self.settings.OOBSIZE = 256
self.settings.PAGESIZE = 4096
self.settings.MAXBLOCK = 2048
# 8Bit_HW_ECC
elif (nandid >> 8) & 0xFF == 0xaa:
self.settings.OOBSIZE = 128
self.settings.PAGESIZE = 2048
self.settings.MAXBLOCK = 2048
# 8Bit_HW_ECC
elif (nandid >> 8) & 0xFF == 0xa1:
self.settings.OOBSIZE = 128
self.settings.PAGESIZE = 2048
self.settings.MAXBLOCK = 1024
# 8Bit_HW_ECC
self.settings.CW_PER_PAGE = (self.settings.PAGESIZE >> 9) - 1
self.settings.SPARE_SIZE_BYTES = 0
def samsung_config(self, nandid):
flashinfo = self.gettbl(nandid, samsung_tbl)
self.flashinfo = flashinfo
# self.settings.SPARE_SIZE_BYTES = flashinfo["spare_size"]
self.settings.CW_PER_PAGE = (self.settings.PAGESIZE >> 9) - 1
self.settings.SPARE_SIZE_BYTES = 0
def generic_config(self, nandid, chipsize):
devcfg = (nandid >> 24) & 0xff
self.settings.PAGESIZE = 1024 << (devcfg & 0x3)
self.settings.BLOCKSIZE = 64 << ((devcfg >> 4) & 0x3)
if chipsize != 0:
self.settings.MAXBLOCK = chipsize * 1024 // self.settings.BLOCKSIZE
else:
self.settings.MAXBLOCK = 0x800
self.settings.CW_PER_PAGE = (self.settings.PAGESIZE >> 9) - 1
def nand_setup(self, nandid):
"""
qcommand -p%qdl% -k11 -c "m 79b0020 295409c0" #NAND_DEV0_CFG0
qcommand -p%qdl% -k11 -c "m 79b0024 08065d5d" #NAND_DEV0_CFG1
qcommand -p%qdl% -k11 -c "m 79b0028 42040d10" #NAND_DEV0_ECC_CFG
qcommand -p%qdl% -k11 -c "m 79b00f0 00000203" NAND_EBI2_ECC_BUF_CFG
"""
fid = (nandid >> 8) & 0xff
pid = nandid & 0xff
self.settings.flash_mfr = ""
for info in nand_manuf_ids:
if info[0] == pid:
self.settings.flash_mfr = info[1]
break
chipsize = 0
for info in nand_ids:
if info[1] == fid:
chipsize = info[2]
self.settings.flash_descr = info[0]
break
self.settings.cfg1_enable_bch_ecc = 1
self.settings.IsWideFlash = 0
self.settings.SPARE_SIZE_BYTES = 0
self.settings.OOBSIZE = 0
self.settings.ECC_PARITY_SIZE_BYTES = 0
self.settings.BAD_BLOCK_BYTE_NUM = 0
self.settings.ecc_bit = 4
if pid == 0x98: # Toshiba
self.toshiba_config(nandid)
if nandid == 0x2690AC98:
self.settings.ecc_bit = 8
elif pid == 0xEC: # Samsung
self.samsung_config(nandid)
elif pid == 0x2C: # Micron
self.generic_config(nandid, chipsize)
# MT29AZ5A3CHHWD
if nandid == 0x2690AC2C or nandid == 0x26D0A32C:
self.settings.ecc_bit = 8
elif pid == 0x01:
self.generic_config(nandid, chipsize)
if nandid == 0x1590AC01: # jsfc 4G
self.settings.OOBSIZE = 128
self.settings.PAGESIZE = 2048
self.settings.SPARE_SIZE_BYTES = 4
else:
self.generic_config(nandid, chipsize)
if nandid in supported_flash:
nd = supported_flash[nandid]
# density = nd[]
# width
chipsize = nd[0] // 1024
self.settings.IsWideFlash = nd[1]
self.settings.PAGESIZE = nd[2]
self.settings.BLOCKSIZE = nd[3]
self.settings.OOBSIZE = nd[4]
self.settings.IsOneNand = nd[5]
if chipsize != 0:
self.settings.MAXBLOCK = chipsize * 1024 // self.settings.BLOCKSIZE
else:
self.settings.MAXBLOCK = 0x800
self.settings.sectorsize = 512
self.settings.sectors_per_page = self.settings.PAGESIZE // self.settings.sectorsize
if self.settings.ecc_bit == 4:
self.settings.ECC_MODE = 0 # 0=4 bit ECC error
elif self.settings.ecc_bit == 8:
self.settings.ECC_MODE = 1 # 1=8 bit ECC error
elif self.settings.ecc_bit == 16:
self.settings.ECC_MODE = 2 # 2=16 bit ECC error
if self.settings.ecc_size == 0:
if self.settings.ecc_bit == 4:
self.settings.ecc_size = 1
elif self.settings.ecc_bit == 8 or self.settings.ecc_bit == 16:
self.settings.ecc_size = 2
if self.settings.OOBSIZE == 0:
self.settings.OOBSIZE = (8 << self.settings.ecc_size) * (self.settings.CW_PER_PAGE + 1)
if 256 >= self.settings.OOBSIZE > 128:
self.settings.OOBSIZE = 256
if self.settings.SPARE_SIZE_BYTES == 0:
# HAM1
if self.settings.ECC_MODE == 0:
self.settings.SPARE_SIZE_BYTES = 4
else:
self.settings.SPARE_SIZE_BYTES = 2
if self.settings.cfg1_enable_bch_ecc:
hw_ecc_bytes = 0
self.settings.UD_SIZE_BYTES = self.settings.SPARE_SIZE_BYTES + self.settings.sectorsize # 516 or 517
if self.settings.SPARE_SIZE_BYTES == 2:
self.settings.UD_SIZE_BYTES += 2
if self.settings.IsWideFlash:
self.settings.UD_SIZE_BYTES += 1
else:
hw_ecc_bytes = 10
self.settings.UD_SIZE_BYTES = 512
if self.settings.ECC_PARITY_SIZE_BYTES == 0:
self.settings.ECC_PARITY_SIZE_BYTES = 3 # HAM1
if self.settings.ecc_bit == 4: # BCH4
self.settings.ECC_PARITY_SIZE_BYTES = 7
elif self.settings.ecc_bit == 8: # BCH8
self.settings.ECC_PARITY_SIZE_BYTES = 13
elif self.settings.ecc_bit == 16: # BCH16
self.settings.ECC_PARITY_SIZE_BYTES = 26
linuxcwsize = 528
if self.settings.cfg1_enable_bch_ecc and self.settings.ecc_bit == 8:
linuxcwsize = 532
if nandid == 0x1590AC2C: # fixme
linuxcwsize = 532
if self.settings.BAD_BLOCK_BYTE_NUM == 0:
self.settings.BAD_BLOCK_BYTE_NUM = (
self.settings.PAGESIZE - (linuxcwsize * (self.settings.sectors_per_page - 1)) + 1)
# UD_SIZE_BYTES must be 512, 516 or 517. If ECC-Protection 516 for x16Bit-Nand and 517 for x8-bit Nand
cfg0 = 0 << self.SET_RD_MODE_AFTER_STATUS \
| 0 << self.STATUS_BFR_READ \
| 5 << self.NUM_ADDR_CYCLES \
| self.settings.SPARE_SIZE_BYTES << self.SPARE_SIZE_BYTES \
| hw_ecc_bytes << self.ECC_PARITY_SIZE_BYTES_RS \
| self.settings.UD_SIZE_BYTES << self.UD_SIZE_BYTES \
| self.settings.CW_PER_PAGE << self.CW_PER_PAGE \
| 0 << self.DISABLE_STATUS_AFTER_WRITE
bad_block_byte = self.settings.BAD_BLOCK_BYTE_NUM
wide_bus = self.settings.IsWideFlash
bch_disabled = self.settings.args_disable_ecc # option in gui, implemented
cfg1 = 0 << self.ECC_MODE_DEV1 \
| 1 << self.ENABLE_NEW_ECC \
| 0 << self.DISABLE_ECC_RESET_AFTER_OPDONE \
| 0 << self.ECC_DECODER_CGC_EN \
| 0 << self.ECC_ENCODER_CGC_EN \
| 2 << self.WR_RD_BSY_GAP \
| 0 << self.BAD_BLOCK_IN_SPARE_AREA \
| bad_block_byte << self.BAD_BLOCK_BYTE_NUM \
| 0 << self.CS_ACTIVE_BSY \
| 7 << self.NAND_RECOVERY_CYCLES \
| wide_bus << self.WIDE_FLASH \
| bch_disabled << self.ENABLE_BCH_ECC
"""
cfg0_raw = (self.settings.CW_PER_PAGE-1) << CW_PER_PAGE \
| self.settings.UD_SIZE_BYTES << UD_SIZE_BYTES \
| 5 << NUM_ADDR_CYCLES \
| 0 << SPARE_SIZE_BYTES
cfg1_raw = 7 << NAND_RECOVERY_CYCLES \
| 0 << CS_ACTIVE_BSY \
| 17 << BAD_BLOCK_BYTE_NUM \
| 1 << BAD_BLOCK_IN_SPARE_AREA \
| 2 << WR_RD_BSY_GAP \
| wide_bus << WIDE_FLASH \
| 1 << DEV0_CFG1_ECC_DISABLE
"""
ecc_bch_cfg = 1 << self.ECC_FORCE_CLK_OPEN \
| 0 << self.ECC_DEC_CLK_SHUTDOWN \
| 0 << self.ECC_ENC_CLK_SHUTDOWN \
| self.settings.UD_SIZE_BYTES << self.ECC_NUM_DATA_BYTES \
| self.settings.ECC_PARITY_SIZE_BYTES << self.ECC_PARITY_SIZE_BYTES_BCH \
| self.settings.ECC_MODE << self.ECC_MODE \
| 0 << self.ECC_SW_RESET \
| bch_disabled << self.ECC_CFG_ECC_DISABLE
if self.settings.UD_SIZE_BYTES == 516:
ecc_buf_cfg = 0x203
elif self.settings.UD_SIZE_BYTES == 517:
ecc_buf_cfg = 0x204
else:
ecc_buf_cfg = 0x1FF
return cfg0, cfg1, ecc_buf_cfg, ecc_bch_cfg
class nandregs:
def __init__(self, parent):
self.register_mapping = {
}
self.reverse_mapping = {}
self.create_reverse_mapping()
self.parent = parent
def __getattribute__(self, name):
if name in ("register_mapping", "parent"):
return super(nandregs, self).__getattribute__(name)
if name in self.register_mapping:
return self.parent.mempeek(self.register_mapping[name])
return super(nandregs, self).__getattribute__(name)
def __setattr__(self, name, value):
if name in ("register_mapping", "parent"):
super(nandregs, self).__setattr__(name, value)
if name in self.register_mapping:
self.parent.mempoke(self.register_mapping[name], value)
else:
super(nandregs, self).__setattr__(name, value)
def read(self, register):
if isinstance(register, str):
register = self.register_mapping.get(register.lower(), None)
return self.parent.mempeek(register)
def write(self, register, value):
if isinstance(register, str):
register = self.register_mapping.get(register.lower(), None)
return self.parent.mempoke(register, value)
def save(self):
reg_dict = {}
for reg in self.register_mapping:
reg_v = self.read(reg)
reg_dict[reg] = reg_v
return reg_dict
def restore(self, value=None):
if value is None:
value = {}
for reg in self.register_mapping:
reg_v = value[reg]
self.write(reg, reg_v)
def create_reverse_mapping(self):
self.reverse_mapping = {v: k for k, v in self.register_mapping.items()}

171
edl/Library/pt.py Executable file
View file

@ -0,0 +1,171 @@
import struct
def get_n(x):
return int(x[6:8] + x[4:6] + x[2:4] + x[0:2], 16)
def parse_pt(data):
va = 0
entries = []
while va < len(data):
entry = struct.unpack("<L", data[va:va + 4])[0]
f = get_fld(entry)
if f is None:
va += 4
continue
entries.append((int(va / 4) << 20, f))
print("%08x %s" % ((int(va / 4) << 20), str(f)))
va += 4
return entries
def parse_spt(data, base):
va = 0
while va < 0x400:
entry = struct.unpack("<L", data[va:va + 4])[0]
f = get_sld(entry)
if f != 'UNSUPPORTED' and f.apx == 0 and f.ap == 3 and f.nx == 0:
print("%08x %s - WX !!" % (base + (int(va / 4) << 12), f))
else:
print("%08x %s" % (base + (int(va / 4) << 12), f))
va += 4
def get_fld(mfld):
s = mfld & 3
if s == 0:
return fault_desc(mfld)
if s == 1:
return pt_desc(mfld)
if s == 2:
return section_desc(mfld)
if s == 3:
return reserved_desc(mfld)
return None
def get_sld(msld):
s = msld & 3
if s == 1:
return sld_lp(msld)
if s > 1:
return sld_xsp(msld)
return "UNSUPPORTED"
class descriptor(object):
def __init__(self, mfld):
pass
def get_name(self):
pass
def __repr__(self):
s = "%8s " % self.get_name()
for attr, value in self.__dict__.items():
try:
s += "%s=%s, " % (attr, hex(value))
except:
s += "%s=%s, " % (attr, value)
return s
class fld(descriptor):
pass
class fault_desc(fld):
def get_name(self):
return "FAULT"
class reserved_desc(fld):
def get_name(self):
return "RESERVED"
class pt_desc(fld):
def __init__(self, desc):
self.coarse_base = (desc >> 10) << 10
self.p = (desc >> 9) & 1
self.domain = (desc >> 5) & 15
self.sbz1 = (desc >> 4) & 1
self.ns = (desc >> 3) & 1
self.sbz2 = (desc >> 2) & 1
def get_name(self):
return "PT"
class section_desc(fld):
def __init__(self, desc):
self.section_base = (desc >> 20) << 20
self.ns = (desc >> 19) & 1
self.zero = ns = (desc >> 18) & 1
self.ng = (desc >> 17) & 1
self.s = (desc >> 16) & 1
self.apx = (desc >> 15) & 1
self.tex = (desc >> 12) & 7
self.ap = (desc >> 10) & 3
self.p = (desc >> 9) & 1
self.domain = (desc >> 5) & 15
self.nx = (desc >> 4) & 1
self.c = (desc >> 3) & 1
self.b = (desc >> 2) & 1
def get_name(self):
return "SECTION"
class sld(descriptor):
pass
class sld_lp(sld):
def __init__(self, desc):
self.page_base = (desc >> 16) << 16
self.nx = (desc >> 15) & 1
self.tex = (desc >> 12) & 7
self.ng = (desc >> 11) & 1
self.s = (desc >> 10) & 1
self.apx = (desc >> 9) & 1
self.sbz = (desc >> 6) & 7
self.ap = (desc >> 4) & 3
self.c = (desc >> 3) & 1
self.b = (desc >> 2) & 1
def get_name(self):
return "LARGEPAGE"
class sld_xsp(sld):
def __init__(self, desc):
self.desc = desc
self.page_base = (desc >> 12) << 12
self.ng = (desc >> 11) & 1
self.s = (desc >> 10) & 1
self.apx = (desc >> 9) & 1
self.tex = (desc >> 6) & 7
self.ap = (desc >> 4) & 3
self.c = (desc >> 3) & 1
self.b = (desc >> 2) & 1
self.nx = desc & 1
def get_name(self):
return "XSMALLPAGE"

153
edl/Library/pt64.py Executable file
View file

@ -0,0 +1,153 @@
import struct
"""
only supports 4KB granule w/ 25<=TnSZ<=33
https://armv8-ref.codingbelief.com/en/chapter_d4/d42_7_the_algorithm_for_finding_the_translation_table_entries.html
"""
def get_level_index(va, level):
if level == 1:
return (va >> 30) & 0x3F
if level == 2:
return (va >> 21) & 0x1FF
if level == 3:
return (va >> 12) & 0x1FF
raise NotImplementedError()
def get_level_bits(level, tnsz):
if level == 1:
return 37 - tnsz + 26 + 1 - 30
if level == 2:
return 9
if level == 3:
return 9
raise NotImplementedError()
def get_level_size(tnsz, level):
return 2 ** get_level_bits(level, tnsz) * 8
def get_va_for_level(va, index, level):
if level == 1:
return va + (index << 30)
if level == 2:
return va + (index << 21)
if level == 3:
return va + (index << 12)
return va
def parse_pt(data, base, tnsz, level=1):
i = 0
entries = []
while i < min(len(data), get_level_size(tnsz, level)):
mentry = struct.unpack("<Q", data[i:i + 8])[0]
f = get_fld(mentry, level)
if f is None:
i += 8
continue
va = get_va_for_level(base, int(i / 8), level)
if f != 'UNSUPPORTED' and f.apx == 0 and f.ap == 3 and f.xn == 0:
print("%016x %s - WX !!" % (va, f))
else:
print("%016x %s" % (va, f))
entries.append((va, f))
i += 8
return entries
def get_fld(mfld, level):
s = mfld & 3
if s == 0:
return None
if s == 1:
return block_entry4k(mfld, level)
if s == 2:
return None
if s == 3:
return table_entry4k(mfld, level)
return None
class descriptor(object):
def get_name(self):
pass
def __repr__(self):
s = "%8s " % self.get_name()
for attr, value in self.__dict__.items():
try:
s += "%s=%s, " % (attr, hex(value))
except:
s += "%s=%s, " % (attr, value)
return s
class fld(descriptor):
pass
class entry(fld):
def __init__(self, desc, level):
self.level = level
self.nshigh = desc >> 63
self.apx = (desc >> 61) & 3
self.xn = (desc >> 60) & 1
self.pxn = (desc >> 59) & 1
self.attrindex = (desc >> 2) & 7
self.ns = (desc >> 5) & 1
self.ap = (desc >> 6) & 3
self.sh = (desc >> 8) & 3
self.af = (desc >> 10) & 1
self.nG = (desc >> 11) & 1
class entry4k(entry):
def __init__(self, desc, level):
entry.__init__(self, desc, level)
self.output = ((desc & 0xFFFFFFFFFFFF) >> 12) << 12
class fault_entry(fld):
def get_name(self):
return "FAULT"
class block_entry4k(entry4k):
def __init__(self, desc, level):
entry4k.__init__(self, desc, level)
# shift = 39-9*level
# self.output = ((desc & 0xFFFFFFFFFFFFL) >> shift) << shift
def get_name(self):
return "BLOCK4"
class table_entry4k(entry4k):
def __init__(self, desc, level):
entry4k.__init__(self, desc, level)
def get_name(self):
return "TABLE4"

834
edl/Library/sahara.py Executable file
View file

@ -0,0 +1,834 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# (c) B.Kerler 2018-2021
import binascii
import time
import os
import sys
import logging
import inspect
from struct import unpack, pack
current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parent_dir = os.path.dirname(current_dir)
sys.path.insert(0, parent_dir)
from edl.Library.utils import read_object, print_progress, rmrf, LogBase
from edl.Config.qualcomm_config import sochw, msmids, root_cert_hash
def convertmsmid(msmid):
msmiddb = []
if int(msmid, 16) & 0xFF == 0xe1 or msmid == '00000000':
return [msmid]
socid = int(msmid, 16) >> 16
if socid in sochw:
names = sochw[socid].split(",")
for name in names:
for ids in msmids:
if msmids[ids] == name:
rmsmid = hex(ids)[2:].lower()
while len(rmsmid) < 8:
rmsmid = '0' + rmsmid
msmiddb.append(rmsmid)
return msmiddb
class sahara(metaclass=LogBase):
SAHARA_VERSION = 2
SAHARA_MIN_VERSION = 1
class cmd:
SAHARA_HELLO_REQ = 0x1
SAHARA_HELLO_RSP = 0x2
SAHARA_READ_DATA = 0x3
SAHARA_END_TRANSFER = 0x4
SAHARA_DONE_REQ = 0x5
SAHARA_DONE_RSP = 0x6
SAHARA_RESET_REQ = 0x7
SAHARA_RESET_RSP = 0x8
SAHARA_MEMORY_DEBUG = 0x9
SAHARA_MEMORY_READ = 0xA
SAHARA_CMD_READY = 0xB
SAHARA_SWITCH_MODE = 0xC
SAHARA_EXECUTE_REQ = 0xD
SAHARA_EXECUTE_RSP = 0xE
SAHARA_EXECUTE_DATA = 0xF
SAHARA_64BIT_MEMORY_DEBUG = 0x10
SAHARA_64BIT_MEMORY_READ = 0x11
SAHARA_64BIT_MEMORY_READ_DATA = 0x12
SAHARA_RESET_STATE_MACHINE_ID = 0x13
class exec_cmd:
SAHARA_EXEC_CMD_NOP = 0x00
SAHARA_EXEC_CMD_SERIAL_NUM_READ = 0x01
SAHARA_EXEC_CMD_MSM_HW_ID_READ = 0x02
SAHARA_EXEC_CMD_OEM_PK_HASH_READ = 0x03
SAHARA_EXEC_CMD_SWITCH_TO_DMSS_DLOAD = 0x04
SAHARA_EXEC_CMD_SWITCH_TO_STREAM_DLOAD = 0x05
SAHARA_EXEC_CMD_READ_DEBUG_DATA = 0x06
SAHARA_EXEC_CMD_GET_SOFTWARE_VERSION_SBL = 0x07
class sahara_mode:
SAHARA_MODE_IMAGE_TX_PENDING = 0x0
SAHARA_MODE_IMAGE_TX_COMPLETE = 0x1
SAHARA_MODE_MEMORY_DEBUG = 0x2
SAHARA_MODE_COMMAND = 0x3
class status:
SAHARA_STATUS_SUCCESS = 0x00 # Invalid command received in current state
SAHARA_NAK_INVALID_CMD = 0x01 # Protocol mismatch between host and target
SAHARA_NAK_PROTOCOL_MISMATCH = 0x02 # Invalid target protocol version
SAHARA_NAK_INVALID_TARGET_PROTOCOL = 0x03 # Invalid host protocol version
SAHARA_NAK_INVALID_HOST_PROTOCOL = 0x04 # Invalid packet size received
SAHARA_NAK_INVALID_PACKET_SIZE = 0x05 # Unexpected image ID received
SAHARA_NAK_UNEXPECTED_IMAGE_ID = 0x06 # Invalid image header size received
SAHARA_NAK_INVALID_HEADER_SIZE = 0x07 # Invalid image data size received
SAHARA_NAK_INVALID_DATA_SIZE = 0x08 # Invalid image type received
SAHARA_NAK_INVALID_IMAGE_TYPE = 0x09 # Invalid tranmission length
SAHARA_NAK_INVALID_TX_LENGTH = 0x0A # Invalid reception length
SAHARA_NAK_INVALID_RX_LENGTH = 0x0B # General transmission or reception error
SAHARA_NAK_GENERAL_TX_RX_ERROR = 0x0C # Error while transmitting READ_DATA packet
SAHARA_NAK_READ_DATA_ERROR = 0x0D # Cannot receive specified number of program headers
SAHARA_NAK_UNSUPPORTED_NUM_PHDRS = 0x0E # Invalid data length received for program headers
SAHARA_NAK_INVALID_PDHR_SIZE = 0x0F # Multiple shared segments found in ELF image
SAHARA_NAK_MULTIPLE_SHARED_SEG = 0x10 # Uninitialized program header location
SAHARA_NAK_UNINIT_PHDR_LOC = 0x11 # Invalid destination address
SAHARA_NAK_INVALID_DEST_ADDR = 0x12 # Invalid data size received in image header
SAHARA_NAK_INVALID_IMG_HDR_DATA_SIZE = 0x13 # Invalid ELF header received
SAHARA_NAK_INVALID_ELF_HDR = 0x14 # Unknown host error received in HELLO_RESP
SAHARA_NAK_UNKNOWN_HOST_ERROR = 0x15 # Timeout while receiving data
SAHARA_NAK_TIMEOUT_RX = 0x16 # Timeout while transmitting data
SAHARA_NAK_TIMEOUT_TX = 0x17 # Invalid mode received from host
SAHARA_NAK_INVALID_HOST_MODE = 0x18 # Invalid memory read access
SAHARA_NAK_INVALID_MEMORY_READ = 0x19 # Host cannot handle read data size requested
SAHARA_NAK_INVALID_DATA_SIZE_REQUEST = 0x1A # Memory debug not supported
SAHARA_NAK_MEMORY_DEBUG_NOT_SUPPORTED = 0x1B # Invalid mode switch
SAHARA_NAK_INVALID_MODE_SWITCH = 0x1C # Failed to execute command
SAHARA_NAK_CMD_EXEC_FAILURE = 0x1D # Invalid parameter passed to command execution
SAHARA_NAK_EXEC_CMD_INVALID_PARAM = 0x1E # Unsupported client command received
SAHARA_NAK_EXEC_CMD_UNSUPPORTED = 0x1F # Invalid client command received for data response
SAHARA_NAK_EXEC_DATA_INVALID_CLIENT_CMD = 0x20 # Failed to authenticate hash table
SAHARA_NAK_HASH_TABLE_AUTH_FAILURE = 0x21 # Failed to verify hash for a given segment of ELF image
SAHARA_NAK_HASH_VERIFICATION_FAILURE = 0x22 # Failed to find hash table in ELF image
SAHARA_NAK_HASH_TABLE_NOT_FOUND = 0x23 # Target failed to initialize
SAHARA_NAK_TARGET_INIT_FAILURE = 0x24 # Failed to authenticate generic image
SAHARA_NAK_IMAGE_AUTH_FAILURE = 0x25 # Invalid ELF hash table size. Too bit or small.
SAHARA_NAK_INVALID_IMG_HASH_TABLE_SIZE = 0x26
SAHARA_NAK_MAX_CODE = 0x7FFFFFFF # To ensure 32-bits wide */
ErrorDesc = {
0x00: "Invalid command received in current state",
0x01: "Protocol mismatch between host and target",
0x02: "Invalid target protocol version",
0x03: "Invalid host protocol version",
0x04: "Invalid packet size received",
0x05: "Unexpected image ID received",
0x06: "Invalid image header size received",
0x07: "Invalid image data size received",
0x08: "Invalid image type received",
0x09: "Invalid tranmission length",
0x0A: "Invalid reception length",
0x0B: "General transmission or reception error",
0x0C: "Error while transmitting READ_DATA packet",
0x0D: "Cannot receive specified number of program headers",
0x0E: "Invalid data length received for program headers",
0x0F: "Multiple shared segments found in ELF image",
0x10: "Uninitialized program header location",
0x11: "Invalid destination address",
0x12: "Invalid data size received in image header",
0x13: "Invalid ELF header received",
0x14: "Unknown host error received in HELLO_RESP",
0x15: "Timeout while receiving data",
0x16: "Timeout while transmitting data",
0x17: "Invalid mode received from host",
0x18: "Invalid memory read access",
0x19: "Host cannot handle read data size requested",
0x1A: "Memory debug not supported",
0x1B: "Invalid mode switch",
0x1C: "Failed to execute command",
0x1D: "Invalid parameter passed to command execution",
0x1E: "Unsupported client command received",
0x1F: "Invalid client command received for data response",
0x20: "Failed to authenticate hash table",
0x21: "Failed to verify hash for a given segment of ELF image",
0x22: "Failed to find hash table in ELF image",
0x23: "Target failed to initialize",
0x24: "Failed to authenticate generic image",
0x25: "Invalid ELF hash table size. Too bit or small.",
0x26: "Invalid IMG Hash Table Size"
}
def init_loader_db(self):
loaderdb = {}
for (dirpath, dirnames, filenames) in os.walk(os.path.join(parent_dir,"Loaders")):
for filename in filenames:
fn = os.path.join(dirpath, filename)
found = False
for ext in [".bin", ".mbn", ".elf"]:
if ext in filename[-4:]:
found = True
break
if not found:
continue
try:
hwid = filename.split("_")[0].lower()
msmid = hwid[:8]
devid = hwid[8:]
pkhash = filename.split("_")[1].lower()
for msmid in convertmsmid(msmid):
mhwid = msmid + devid
mhwid = mhwid.lower()
if mhwid not in loaderdb:
loaderdb[mhwid] = {}
if pkhash not in loaderdb[mhwid]:
loaderdb[mhwid][pkhash] = fn
else:
loaderdb[mhwid][pkhash].append(fn)
except Exception as e: # pylint: disable=broad-except
self.debug(str(e))
continue
self.loaderdb = loaderdb
return loaderdb
def get_error_desc(self, status):
if status in self.ErrorDesc:
return "Error: " + self.ErrorDesc[status]
else:
return "Unknown error"
pkt_hello_req = [
('cmd', 'I'),
('len', 'I'),
('version', 'I'),
('version_min', 'I'),
('max_cmd_len', 'I'),
('mode', 'I'),
('res1', 'I'),
('res2', 'I'),
('res3', 'I'),
('res4', 'I'),
('res5', 'I'),
('res6', 'I')]
pkt_cmd_hdr = [
('cmd', 'I'),
('len', 'I')
]
pkt_read_data = [
('id', 'I'),
('data_offset', 'I'),
('data_len', 'I')
]
pkt_read_data_64 = [
('id', 'Q'),
('data_offset', 'Q'),
('data_len', 'Q')
]
pkt_memory_debug = [
('memory_table_addr', 'I'),
('memory_table_length', 'I')
]
pkt_memory_debug_64 = [
('memory_table_addr', 'Q'),
('memory_table_length', 'Q')
]
'''
execute_cmd=[
('cmd', 'I'),
('len', 'I'),
('client_cmd','I')
]
'''
pkt_execute_rsp_cmd = [
('cmd', 'I'),
('len', 'I'),
('client_cmd', 'I'),
('data_len', 'I')
]
pkt_image_end = [
('id', 'I'),
('status', 'I')
]
pkt_done = [
('cmd', 'I'),
('len', 'I'),
('status', 'I')
]
pbl_info = [
('serial', 'I'),
('msm_id', 'I'),
('pk_hash', '32s'),
('pbl_sw', 'I')
]
parttbl = [
('save_pref', 'I'),
('mem_base', 'I'),
('length', 'I'),
('desc', '20s'),
('filename', '20s')
]
parttbl_64bit = [
('save_pref', 'Q'),
('mem_base', 'Q'),
('length', 'Q'),
('desc', '20s'),
('filename', '20s')
]
def __init__(self, cdc, loglevel):
self.cdc = cdc
self.__logger = self.__logger
self.info = self.__logger.info
self.debug = self.__logger.debug
self.error = self.__logger.error
self.warning = self.__logger.warning
self.id = None
self.loaderdb = None
self.version = 2.1
self.programmer = None
self.mode = ""
self.serial = None
self.serials = None
self.sblversion = None
self.hwid = None
self.pkhash = None
self.hwidstr = None
self.msm_id = None
self.oem_id = None
self.model_id = None
self.oem_str = None
self.msm_str = None
self.bit64 = False
self.pktsize = None
self.init_loader_db()
self.__logger.setLevel(loglevel)
if loglevel == logging.DEBUG:
logfilename = "log.txt"
fh = logging.FileHandler(logfilename)
self.__logger.addHandler(fh)
def get_rsp(self):
data = []
try:
v = self.cdc.read()
if v == b'':
return [None, None]
if b"<?xml" in v:
return ["firehose", None]
pkt = read_object(v[0:0x2 * 0x4], self.pkt_cmd_hdr)
if "cmd" in pkt:
cmd = pkt["cmd"]
if cmd == self.cmd.SAHARA_HELLO_REQ:
data = read_object(v[0x0:0xC * 0x4], self.pkt_hello_req)
elif cmd == self.cmd.SAHARA_DONE_RSP:
data = read_object(v[0x0:0x3 * 4], self.pkt_done)
elif cmd == self.cmd.SAHARA_END_TRANSFER:
data = read_object(v[0x8:0x8 + 0x2 * 0x4], self.pkt_image_end)
elif cmd == self.cmd.SAHARA_64BIT_MEMORY_READ_DATA:
self.bit64 = True
data = read_object(v[0x8:0x8 + 0x3 * 0x8], self.pkt_read_data_64)
elif cmd == self.cmd.SAHARA_READ_DATA:
self.bit64 = False
data = read_object(v[0x8:0x8 + 0x3 * 0x4], self.pkt_read_data)
elif cmd == self.cmd.SAHARA_64BIT_MEMORY_DEBUG:
self.bit64 = True
data = read_object(v[0x8:0x8 + 0x2 * 0x8], self.pkt_memory_debug_64)
elif cmd == self.cmd.SAHARA_MEMORY_DEBUG:
self.bit64 = False
data = read_object(v[0x8:0x8 + 0x2 * 0x4], self.pkt_memory_debug)
elif cmd == self.cmd.SAHARA_EXECUTE_RSP:
data = read_object(v[0:0x4 * 0x4], self.pkt_execute_rsp_cmd)
elif cmd == self.cmd.SAHARA_CMD_READY or cmd == self.cmd.SAHARA_RESET_RSP:
data = []
else:
return [None,None]
return [pkt, data]
except Exception as e: # pylint: disable=broad-except
self.error(str(e))
return [None,None]
def cmd_hello(self, mode, version_min=1, max_cmd_len=0): # CMD 0x1, RSP 0x2
cmd = self.cmd.SAHARA_HELLO_RSP
length = 0x30
version = self.SAHARA_VERSION
responsedata = pack("<IIIIIIIIIIII", cmd, length, version, version_min, max_cmd_len, mode, 0, 0, 0, 0, 0, 0)
try:
self.cdc.write(responsedata)
return True
except Exception as e: # pylint: disable=broad-except
self.error(str(e))
return False
def connect(self):
try:
v = self.cdc.read()
if len(v) > 1:
if v[0] == 0x01:
cmd = read_object(v[0:0x2 * 0x4], self.pkt_cmd_hdr)
if cmd['cmd'] == self.cmd.SAHARA_HELLO_REQ:
data = read_object(v[0x0:0xC * 0x4], self.pkt_hello_req)
self.pktsize = data['max_cmd_len']
self.version = float(str(data['version']) + "." + str(data['version_min']))
return ["sahara", data]
elif v[0] == self.cmd.SAHARA_END_TRANSFER:
return ["sahara", None]
elif b"<?xml" in v:
return ["firehose", None]
elif v[0] == 0x7E:
return ["nandprg", None]
else:
data = b"<?xml version=\"1.0\" ?><data><nop /></data>"
self.cdc.write(data)
res = self.cdc.read()
if res == b"":
try:
data = b"\x7E\x06\x4E\x95\x7E" # Streaming nop
self.cdc.write(data)
res = self.cdc.read()
if b"\x7E\x0D\x16\x00\x00\x00\x00" in res or b"Invalid Command" in res:
return ["nandprg", None]
else:
return ["", None]
except Exception as e: # pylint: disable=broad-except
self.error(str(e))
return ["", None]
if b"<?xml" in res:
return ["firehose", None]
elif len(res) > 0 and res[0] == self.cmd.SAHARA_END_TRANSFER:
print("Device is in Sahara error state, please reboot the device.")
return ["sahara", None]
else:
data = b"\x7E\x11\x00\x12\x00\xA0\xE3\x00\x00\xC1\xE5\x01\x40\xA0\xE3\x1E\xFF\x2F\xE1\x4B\xD9\x7E"
self.cdc.write(data)
res = self.cdc.read()
if len(res) > 0 and res[1] == 0x12:
return ["nandprg", None]
else:
self.cmd_modeswitch(self.sahara_mode.SAHARA_MODE_COMMAND)
return ["sahara", None]
except Exception as e: # pylint: disable=broad-except
self.error(str(e))
self.cmd_modeswitch(self.sahara_mode.SAHARA_MODE_MEMORY_DEBUG)
cmd, pkt = self.get_rsp()
if None in [cmd , pkt]:
return ["", None]
return ["sahara", pkt]
def enter_command_mode(self):
if not self.cmd_hello(self.sahara_mode.SAHARA_MODE_COMMAND):
return False
cmd, pkt = self.get_rsp()
if cmd["cmd"] == self.cmd.SAHARA_CMD_READY:
return True
elif "status" in pkt:
self.error(self.get_error_desc(pkt["status"]))
return False
return False
def cmdexec_nop(self):
res = self.cmd_exec(self.exec_cmd.SAHARA_EXEC_CMD_NOP)
return res
def cmdexec_get_serial_num(self):
res = self.cmd_exec(self.exec_cmd.SAHARA_EXEC_CMD_SERIAL_NUM_READ)
return unpack("<I", res)[0]
def cmdexec_get_msm_hwid(self):
res = self.cmd_exec(self.exec_cmd.SAHARA_EXEC_CMD_MSM_HW_ID_READ)
try:
return unpack("<Q", res[0:0x8])[0]
except Exception as e: # pylint: disable=broad-except
self.debug(str(e))
return None
def cmdexec_get_pkhash(self):
try:
res = self.cmd_exec(self.exec_cmd.SAHARA_EXEC_CMD_OEM_PK_HASH_READ)[0:0x20]
return binascii.hexlify(res).decode('utf-8')
except Exception as e: # pylint: disable=broad-except
self.error(str(e))
return None
def cmdexec_get_sbl_version(self):
res = self.cmd_exec(self.exec_cmd.SAHARA_EXEC_CMD_GET_SOFTWARE_VERSION_SBL)
return unpack("<I", res)[0]
def cmdexec_switch_to_dmss_dload(self):
res = self.cmd_exec(self.exec_cmd.SAHARA_EXEC_CMD_SWITCH_TO_DMSS_DLOAD)
return res
def cmdexec_switch_to_stream_dload(self):
res = self.cmd_exec(self.exec_cmd.SAHARA_EXEC_CMD_SWITCH_TO_STREAM_DLOAD)
return res
def cmdexec_read_debug_data(self):
res = self.cmd_exec(self.exec_cmd.SAHARA_EXEC_CMD_READ_DEBUG_DATA)
return res
def cmd_info(self):
if self.enter_command_mode():
self.serial = self.cmdexec_get_serial_num()
self.serials = "{:08x}".format(self.serial)
self.hwid = self.cmdexec_get_msm_hwid()
self.pkhash = self.cmdexec_get_pkhash()
# if self.version>=2.4:
# self.sblversion = "{:08x}".format(self.cmdexec_get_sbl_version())
if self.hwid is not None:
self.hwidstr = "{:016x}".format(self.hwid)
self.msm_id = int(self.hwidstr[2:8], 16)
self.oem_id = int(self.hwidstr[-8:-4], 16)
self.model_id = int(self.hwidstr[-4:], 16)
self.oem_str = "{:04x}".format(self.oem_id)
self.model_id = "{:04x}".format(self.model_id)
self.msm_str = "{:08x}".format(self.msm_id)
if self.msm_id in msmids:
cpustr = f"CPU detected: \"{msmids[self.msm_id]}\"\n"
else:
cpustr = "Unknown CPU, please send log as issue to https://github.com/bkerler/edl\n"
"""
if self.version >= 2.4:
self.info(f"\n------------------------\n" +
f"HWID: 0x{self.hwidstr} (MSM_ID:0x{self.msm_str}," +
f"OEM_ID:0x{self.oem_str}," +
f"MODEL_ID:0x{self.model_id})\n" +
f"PK_HASH: 0x{self.pkhash}\n" +
f"Serial: 0x{self.serials}\n" +
f"SBL Version: 0x{self.sblversion}\n")
else:
"""
self.info(f"\n------------------------\n" +
f"HWID: 0x{self.hwidstr} (MSM_ID:0x{self.msm_str}," +
f"OEM_ID:0x{self.oem_str}," +
f"MODEL_ID:0x{self.model_id})\n" +
cpustr +
f"PK_HASH: 0x{self.pkhash}\n" +
f"Serial: 0x{self.serials}\n")
if self.programmer == "":
if self.hwidstr in self.loaderdb:
mt = self.loaderdb[self.hwidstr]
unfused = False
for rootcert in root_cert_hash:
if self.pkhash[0:16] in root_cert_hash[rootcert]:
unfused = True
if unfused:
self.info("Possibly unfused device detected, so any loader should be fine...")
if self.pkhash[0:16] in mt:
self.programmer = mt[self.pkhash[0:16]]
self.info(f"Trying loader: {self.programmer}")
else:
for loader in mt:
self.programmer = mt[loader]
self.info(f"Possible loader available: {self.programmer}")
for loader in mt:
self.programmer = mt[loader]
self.info(f"Trying loader: {self.programmer}")
break
elif self.pkhash[0:16] in mt:
self.programmer = self.loaderdb[self.hwidstr][self.pkhash[0:16]]
self.info(f"Detected loader: {self.programmer}")
else:
for loader in self.loaderdb[self.hwidstr]:
self.programmer = self.loaderdb[self.hwidstr][loader]
self.info(f"Trying loader: {self.programmer}")
break
# print("Couldn't find a loader for given hwid and pkhash :(")
# exit(0)
elif self.hwidstr is not None and self.pkhash is not None:
msmid = self.hwidstr[:8]
for hwidstr in self.loaderdb:
if msmid == hwidstr[:8]:
for pkhash in self.loaderdb[hwidstr]:
if self.pkhash[0:16] == pkhash:
self.programmer = self.loaderdb[hwidstr][pkhash]
self.info(f"Trying loader: {self.programmer}")
self.cmd_modeswitch(self.sahara_mode.SAHARA_MODE_COMMAND)
return True
self.error(
f"Couldn't find a loader for given hwid and pkhash ({self.hwidstr}_{self.pkhash[0:16]}" +
"_[FHPRG/ENPRG].bin) :(")
return False
else:
self.error(f"Couldn't find a suitable loader :(")
return False
self.cmd_modeswitch(self.sahara_mode.SAHARA_MODE_COMMAND)
return True
return False
def streaminginfo(self):
if self.enter_command_mode():
self.serial = self.cmdexec_get_serial_num()
self.info(f"Device serial : {hex(self.serial)}")
self.cmd_modeswitch(self.sahara_mode.SAHARA_MODE_COMMAND)
return True
return False
def cmd_done(self):
if self.cdc.write(pack("<II", self.cmd.SAHARA_DONE_REQ, 0x8)):
cmd, pkt = self.get_rsp()
time.sleep(0.3)
if cmd["cmd"] == self.cmd.SAHARA_DONE_RSP:
return True
elif cmd["cmd"] == self.cmd.SAHARA_END_TRANSFER:
if pkt["status"] == self.status.SAHARA_NAK_INVALID_CMD:
self.error("Invalid Transfer command received.")
return False
return True
return False
def cmd_reset(self):
self.cdc.write(pack("<II", self.cmd.SAHARA_RESET_REQ, 0x8))
try:
cmd, pkt = self.get_rsp()
except Exception as e: # pylint: disable=broad-except
self.debug(str(e))
return False
if cmd["cmd"] == self.cmd.SAHARA_RESET_RSP:
return True
elif "status" in pkt:
self.error(self.get_error_desc(pkt["status"]))
return False
return False
def read_memory(self, addr, bytestoread, display=False, wf=None):
data = b""
old = 0
pos = 0
total = bytestoread
if display:
print_progress(0, 100, prefix='Progress:', suffix='Complete', bar_length=50)
while bytestoread > 0:
if bytestoread > 0x080000:
length = 0x080000
else:
length = bytestoread
bytesread = 0
try:
self.cdc.read(1, 1)
except Exception as e: # pylint: disable=broad-except
self.debug(str(e))
pass
if self.bit64:
if not self.cdc.write(pack("<IIQQ", self.cmd.SAHARA_64BIT_MEMORY_READ, 0x8 + 8 + 8, addr + pos,
length)):
return None
else:
if not self.cdc.write(
pack("<IIII", self.cmd.SAHARA_MEMORY_READ, 0x8 + 4 + 4, addr + pos, length)):
return None
while length > 0:
try:
tmp = self.cdc.read(length)
except Exception as e: # pylint: disable=broad-except
self.debug(str(e))
return None
length -= len(tmp)
pos += len(tmp)
bytesread += len(tmp)
if wf is not None:
wf.write(tmp)
else:
data += tmp
if display:
prog = round(float(pos) / float(total) * float(100), 1)
if prog > old:
if display:
print_progress(prog, 100, prefix='Progress:', suffix='Complete', bar_length=50)
old = prog
bytestoread -= bytesread
if display:
print_progress(100, 100, prefix='Progress:', suffix='Complete', bar_length=50)
'''
try:
self.cdc.read(0)
except:
return data
'''
return data
def dump_partitions(self, partition):
for part in partition:
filename = part["filename"]
desc = part["desc"]
mem_base = part["mem_base"]
length = part["length"]
print(f"Dumping {filename}({desc}) at {hex(mem_base)}, length {hex(length)}")
fname = os.path.join("memory", filename)
with open(fname, "wb") as wf:
if self.read_memory(mem_base, length, True, wf):
print("Done dumping memory")
else:
self.error("Error dumping memory")
self.cmd_reset()
return True
def debug_mode(self):
if not self.cmd_hello(self.sahara_mode.SAHARA_MODE_MEMORY_DEBUG):
return False
if os.path.exists("memory"):
rmrf("memory")
os.mkdir("memory")
cmd, pkt = self.get_rsp()
if cmd["cmd"] == self.cmd.SAHARA_MEMORY_DEBUG or cmd["cmd"] == self.cmd.SAHARA_64BIT_MEMORY_DEBUG:
memory_table_addr = pkt["memory_table_addr"]
memory_table_length = pkt["memory_table_length"]
if self.bit64:
pktsize = 8 + 8 + 8 + 20 + 20
if memory_table_length % pktsize == 0:
if memory_table_length != 0:
print(
f"Reading 64-Bit partition from {hex(memory_table_addr)} with length of " +
"{hex(memory_table_length)}")
ptbldata = self.read_memory(memory_table_addr, memory_table_length)
num_entries = len(ptbldata) // pktsize
partitions = []
for id_entry in range(0, num_entries):
pd = read_object(ptbldata[id_entry * pktsize:(id_entry * pktsize) + pktsize],
self.parttbl_64bit)
desc = pd["desc"].replace(b"\x00", b"").decode('utf-8')
filename = pd["filename"].replace(b"\x00", b"").decode('utf-8')
mem_base = pd["mem_base"]
save_pref = pd["save_pref"]
length = pd["length"]
partitions.append(dict(desc=desc, filename=filename, mem_base=mem_base, length=length,
save_pref=save_pref))
print(
f"{filename}({desc}): Offset {hex(mem_base)}, Length {hex(length)}, " +
"SavePref {hex(save_pref)}")
self.dump_partitions(partitions)
return True
return True
else:
pktsize = (4 + 4 + 4 + 20 + 20)
if memory_table_length % pktsize == 0:
if memory_table_length != 0:
print(f"Reading 32-Bit partition from {hex(memory_table_addr)} " +
f"with length of {hex(memory_table_length)}")
ptbldata = self.read_memory(memory_table_addr, memory_table_length)
num_entries = len(ptbldata) // pktsize
partitions = []
for id_entry in range(0, num_entries):
pd = read_object(ptbldata[id_entry * pktsize:(id_entry * pktsize) + pktsize], self.parttbl)
desc = pd["desc"].replace(b"\x00", b"").decode('utf-8')
filename = pd["filename"].replace(b"\x00", b"").decode('utf-8')
mem_base = pd["mem_base"]
save_pref = pd["save_pref"]
length = pd["length"]
partitions.append(dict(desc=desc, filename=filename, mem_base=mem_base, length=length,
save_pref=save_pref))
print(f"{filename}({desc}): Offset {hex(mem_base)}, " +
f"Length {hex(length)}, SavePref {hex(save_pref)}")
self.dump_partitions(partitions)
return True
elif "status" in pkt:
self.error(self.get_error_desc(pkt["status"]))
return False
return False
def upload_loader(self):
if self.programmer == "":
return ""
try:
self.info(f"Uploading loader {self.programmer} ...")
with open(self.programmer, "rb") as rf:
programmer = rf.read()
except Exception as e: # pylint: disable=broad-except
self.error(str(e))
sys.exit()
if not self.cmd_hello(self.sahara_mode.SAHARA_MODE_IMAGE_TX_PENDING):
return ""
try:
datalen = len(programmer)
done = False
while datalen > 0 or done:
cmd, pkt = self.get_rsp()
if cmd == -1 or pkt == -1:
if self.cmd_done():
return self.mode # Do NOT remove
else:
self.error("Timeout while uploading loader. Wrong loader ?")
return ""
if cmd["cmd"] == self.cmd.SAHARA_64BIT_MEMORY_READ_DATA:
self.bit64 = True
elif cmd["cmd"] == self.cmd.SAHARA_READ_DATA:
self.bit64 = False
elif cmd["cmd"] == self.cmd.SAHARA_END_TRANSFER:
if pkt["status"] == self.status.SAHARA_STATUS_SUCCESS:
self.cmd_done()
return self.mode
else:
return ""
elif "status" in pkt:
self.error(self.get_error_desc(pkt["status"]))
return ""
else:
self.error("Unexpected error on uploading")
return ""
self.id = pkt["id"]
if self.id == 0x7:
self.mode = "nandprg"
elif self.id == 0xB:
self.mode = "enandprg"
elif self.id >= 0xC:
self.mode = "firehose"
data_offset = pkt["data_offset"]
data_len = pkt["data_len"]
if data_offset + data_len > len(programmer):
while len(programmer) < data_offset + data_len:
programmer += b"\xFF"
data_to_send = programmer[data_offset:data_offset + data_len]
self.cdc.write(data_to_send)
datalen -= data_len
self.info("Loader uploaded.")
cmd, pkt = self.get_rsp()
if cmd["cmd"] == self.cmd.SAHARA_END_TRANSFER:
if pkt["status"] == self.status.SAHARA_STATUS_SUCCESS:
self.cmd_done()
return self.mode
return ""
except Exception as e: # pylint: disable=broad-except
self.error("Unexpected error on uploading, maybe signature of loader wasn't accepted ?\n" + str(e))
return ""
def cmd_modeswitch(self, mode):
data = pack("<III", self.cmd.SAHARA_SWITCH_MODE, 0xC, mode)
self.cdc.write(data)
def cmd_exec(self, mcmd): # CMD 0xD, RSP 0xE, CMD2 0xF
# Send request
data = pack("<III", self.cmd.SAHARA_EXECUTE_REQ, 0xC, mcmd)
self.cdc.write(data)
# Get info about request
cmd, pkt = self.get_rsp()
if cmd["cmd"] == self.cmd.SAHARA_EXECUTE_RSP:
# Ack
data = pack("<III", self.cmd.SAHARA_EXECUTE_DATA, 0xC, mcmd)
self.cdc.write(data)
payload = self.cdc.read(pkt["data_len"])
return payload
elif "status" in pkt:
self.error(self.get_error_desc(pkt["status"]))
return None
return [cmd, pkt]

236
edl/Library/sparse.py Executable file
View file

@ -0,0 +1,236 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# (c) B.Kerler 2018-2021
import logging
import sys
import os
import sys
import inspect
from queue import Queue
from struct import unpack
current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parent_dir = os.path.dirname(current_dir)
sys.path.insert(0, parent_dir)
from edl.Library.utils import LogBase, print_progress
class QCSparse(metaclass=LogBase):
def __init__(self, filename, loglevel):
self.rf = open(filename, 'rb')
self.data = Queue()
self.__logger = self.__logger
self.offset = 0
self.tmpdata = bytearray()
self.__logger.setLevel(loglevel)
self.major_version = None
self.minor_version = None
self.file_hdr_sz = None
self.chunk_hdr_sz = None
self.blk_sz = None
self.total_blks = None
self.total_chunks = None
self.image_checksum = None
self.info = self.__logger.info
self.debug = self.__logger.debug
self.error = self.__logger.error
self.warning = self.__logger.warning
if loglevel == logging.DEBUG:
logfilename = "log.txt"
fh = logging.FileHandler(logfilename)
self.__logger.addHandler(fh)
def readheader(self):
header = unpack("<I4H4I", self.rf.read(0x1C))
magic = header[0]
self.major_version = header[1]
self.minor_version = header[2]
self.file_hdr_sz = header[3]
self.chunk_hdr_sz = header[4]
self.blk_sz = header[5]
self.total_blks = header[6]
self.total_chunks = header[7]
self.image_checksum = header[8]
if magic != 0xED26FF3A:
return False
if self.file_hdr_sz != 28:
self.error("The file header size was expected to be 28, but is %u." % self.file_hdr_sz)
return False
if self.chunk_hdr_sz != 12:
self.error("The chunk header size was expected to be 12, but is %u." % self.chunk_hdr_sz)
return False
self.info("Sparse Format detected. Using unpacked image.")
return True
def get_chunk_size(self):
if self.total_blks < self.offset:
self.error(
"The header said we should have %u output blocks, but we saw %u" % (self.total_blks, self.offset))
return -1
header = unpack("<2H2I", self.rf.read(self.chunk_hdr_sz))
chunk_type = header[0]
chunk_sz = header[2]
total_sz = header[3]
data_sz = total_sz - 12
if chunk_type == 0xCAC1:
if data_sz != (chunk_sz * self.blk_sz):
self.error(
"Raw chunk input size (%u) does not match output size (%u)" % (data_sz, chunk_sz * self.blk_sz))
return -1
else:
self.rf.seek(self.rf.tell() + chunk_sz * self.blk_sz)
return chunk_sz * self.blk_sz
elif chunk_type == 0xCAC2:
if data_sz != 4:
self.error("Fill chunk should have 4 bytes of fill, but this has %u" % data_sz)
return -1
else:
return chunk_sz * self.blk_sz // 4
elif chunk_type == 0xCAC3:
return chunk_sz * self.blk_sz
elif chunk_type == 0xCAC4:
if data_sz != 4:
self.error("CRC32 chunk should have 4 bytes of CRC, but this has %u" % data_sz)
return -1
else:
self.rf.seek(self.rf.tell() + 4)
return 0
else:
self.debug("Unknown chunk type 0x%04X" % chunk_type)
return -1
def unsparse(self):
if self.total_blks < self.offset:
self.error(
"The header said we should have %u output blocks, but we saw %u" % (self.total_blks, self.offset))
return -1
header = unpack("<2H2I", self.rf.read(self.chunk_hdr_sz))
chunk_type = header[0]
chunk_sz = header[2]
total_sz = header[3]
data_sz = total_sz - 12
if chunk_type == 0xCAC1:
if data_sz != (chunk_sz * self.blk_sz):
self.error(
"Raw chunk input size (%u) does not match output size (%u)" % (data_sz, chunk_sz * self.blk_sz))
return -1
else:
self.debug("Raw data")
data = self.rf.read(chunk_sz * self.blk_sz)
self.offset += chunk_sz
return data
elif chunk_type == 0xCAC2:
if data_sz != 4:
self.error("Fill chunk should have 4 bytes of fill, but this has %u" % data_sz)
return -1
else:
fill_bin = self.rf.read(4)
fill = unpack("<I", fill_bin)
self.debug(format("Fill with 0x%08X" % fill))
data = fill_bin * (chunk_sz * self.blk_sz // 4)
self.offset += chunk_sz
return data
elif chunk_type == 0xCAC3:
data = b'\x00' * chunk_sz * self.blk_sz
self.offset += chunk_sz
return data
elif chunk_type == 0xCAC4:
if data_sz != 4:
self.error("CRC32 chunk should have 4 bytes of CRC, but this has %u" % data_sz)
return -1
else:
crc_bin = self.rf.read(4)
crc = unpack("<I", crc_bin)
self.debug(format("Unverified CRC32 0x%08X" % crc))
return b""
else:
self.debug("Unknown chunk type 0x%04X" % chunk_type)
return -1
def getsize(self):
self.rf.seek(0x1C)
length = 0
chunk = 0
while chunk < self.total_chunks:
tlen = self.get_chunk_size()
if tlen == -1:
break
length += tlen
chunk += 1
self.rf.seek(0x1C)
return length
def read(self, length=None):
if length is None:
return self.unsparse()
if length <= len(self.tmpdata):
tdata = self.tmpdata[:length]
self.tmpdata = self.tmpdata[length:]
return tdata
while len(self.tmpdata) < length:
self.tmpdata.extend(self.unsparse())
if length <= len(self.tmpdata):
tdata = self.tmpdata[:length]
self.tmpdata = self.tmpdata[length:]
return tdata
if __name__ == "__main__":
if len(sys.argv) < 3:
print("./sparse.py <sparse_partition.img> <outfile>")
sys.exit()
sp = QCSparse(sys.argv[1], logging.INFO)
if sp.readheader():
print("Extracting sectors to " + sys.argv[2])
with open(sys.argv[2], "wb") as wf:
"""
old=0
while sp.offset<sp.total_blks:
prog=int(sp.offset / sp.total_blks * 100)
if prog>old:
print_progress(prog, 100, prefix='Progress:', suffix='Complete', bar_length=50)
old=prog
data=sp.read()
if data==b"" or data==-1:
break
wf.write(data)
if len(sp.tmpdata)>0:
wf.write(sp.tmpdata)
sp.tmpdata=bytearray()
print_progress(100, 100, prefix='Progress:', suffix='Complete', bar_length=50)
"""
fsize = sp.getsize()
SECTOR_SIZE_IN_BYTES = 4096
num_partition_sectors = 5469709
MaxPayloadSizeToTargetInBytes = 0x200000
bytesToWrite = SECTOR_SIZE_IN_BYTES * num_partition_sectors
total = SECTOR_SIZE_IN_BYTES * num_partition_sectors
old = 0
pos = 0
while fsize > 0:
wlen = MaxPayloadSizeToTargetInBytes // SECTOR_SIZE_IN_BYTES * SECTOR_SIZE_IN_BYTES
if fsize < wlen:
wlen = fsize
wdata = sp.read(wlen)
bytesToWrite -= wlen
fsize -= wlen
pos += wlen
pv = wlen % SECTOR_SIZE_IN_BYTES
if pv != 0:
filllen = (wlen // SECTOR_SIZE_IN_BYTES * SECTOR_SIZE_IN_BYTES) + \
SECTOR_SIZE_IN_BYTES
wdata += b"\x00" * (filllen - wlen)
wlen = len(wdata)
wf.write(wdata)
prog = round(float(pos) / float(total) * float(100), 1)
if prog > old:
print_progress(prog, 100, prefix='Progress:', suffix='Complete (Sector %d)'
% (pos // SECTOR_SIZE_IN_BYTES),
bar_length=50)
print("Done.")

1069
edl/Library/streaming.py Executable file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,436 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# (c) B.Kerler 2018-2021
import sys
import os
import logging
from binascii import hexlify, unhexlify
from struct import unpack, pack
from edl.Library.streaming import Streaming
from edl.Library.utils import do_tcp_server, LogBase, getint
class streaming_client(metaclass=LogBase):
def __init__(self, arguments, cdc, sahara, loglevel, printer):
self.cdc = cdc
self.__logger = self.__logger
self.sahara = sahara
self.arguments = arguments
self.streaming = Streaming(cdc, sahara, loglevel)
self.printer = printer
self.__logger.setLevel(loglevel)
self.error = self.__logger.error
self.info = self.__logger.info
if loglevel == logging.DEBUG:
logfilename = "log.txt"
fh = logging.FileHandler(logfilename)
self.__logger.addHandler(fh)
def disconnect(self):
self.cdc.close()
sys.exit(0)
def check_param(self, parameters):
error = False
params = ""
for parameter in parameters:
params += parameter + " "
if parameter not in parameters:
error = True
if error:
if len(parameters) == 1:
self.printer("Argument " + params + "required.")
else:
self.printer("Arguments " + params + "required.")
return False
return True
def print_partitions(self, partitions):
self.printer("Name Offset\t\tLength\t\tAttr\t\t\tFlash")
self.printer("-------------------------------------------------------------")
for name in partitions:
partition = partitions[name]
for i in range(0x10 - len(name)):
name += " "
offset = partition[
"offset"] * self.streaming.settings.num_pages_per_blk * self.streaming.settings.PAGESIZE
length = partition[
"length"] * self.streaming.settings.num_pages_per_blk * self.streaming.settings.PAGESIZE
attr1 = partition["attr1"]
attr2 = partition["attr2"]
attr3 = partition["attr3"]
which_flash = partition["which_flash"]
self.printer(
f"{name}\t%08X\t%08X\t{hex(attr1)}/{hex(attr2)}/{hex(attr3)}\t{which_flash}" % (offset, length))
def handle_streaming(self, cmd, options):
mode = 0
"""
offset = getint(options["<offset>"])
length = getint(options["<length>"])
filename = options["<filename>"]
self.streaming.streaming_mode=self.streaming.Qualcomm
self.streaming.memread=self.streaming.qc_memread
self.streaming.memtofile(offset, length, filename)
"""
if "<mode>" in options:
mode = options["<mode>"]
if self.streaming.connect(mode):
xflag = 0
self.streaming.hdlc.receive_reply(5)
if self.streaming.streaming_mode == self.streaming.Patched:
self.streaming.nand_init(xflag)
if cmd == "gpt":
directory = options["<directory>"]
if directory is None:
directory = ""
data = self.streaming.read_partition_table()
sfilename = os.path.join(directory, f"partition.bin")
if data != b"":
with open(sfilename, "wb") as write_handle:
write_handle.write(data)
self.printer(f"Dumped Partition Table to {sfilename}")
else:
self.error(f"Error on dumping partition table to {sfilename}")
elif cmd == "printgpt":
partitions = self.streaming.get_partitions()
self.print_partitions(partitions)
self.streaming.nand_post()
elif cmd == "r":
partitionname = options["<partitionname>"]
filename = options["<filename>"]
filenames = filename.split(",")
partitions = partitionname.split(",")
if len(partitions) != len(filenames):
self.error("You need to gives as many filenames as given partitions.")
return
i = 0
rpartitions = self.streaming.get_partitions()
for partition in partitions:
if partition.lower() in rpartitions:
spartition = rpartitions[partition]
offset = spartition["offset"]
length = spartition["length"]
# attr1 = spartition["attr1"]
# attr2 = spartition["attr2"]
# attr3 = spartition["attr3"]
partfilename = filenames[i]
self.printer(f"Dumping Partition {partition}...")
self.streaming.read_raw(offset, length, self.streaming.settings.UD_SIZE_BYTES, partfilename)
self.printer(f"Dumped sector {str(offset)} with sector count {str(length)} as {partfilename}.")
else:
self.error(f"Error: Couldn't detect partition: {partition}\nAvailable partitions:")
self.print_partitions(rpartitions)
elif cmd == "rs":
sector = getint(options["<start_sector>"]) # Page
sectors = getint(options["<sectors>"])
filename = options["<filename>"]
self.printer(f"Dumping at Sector {hex(sector)} with Sectorcount {hex(sectors)}...")
if self.streaming.read_sectors(sector, sectors, filename, True):
self.printer(f"Dumped sector {str(sector)} with sector count {str(sectors)} as {filename}.")
elif cmd == "rf":
sector = 0
sectors = self.streaming.settings.MAXBLOCK * self.streaming.settings.num_pages_per_blk * \
self.streaming.settings.sectors_per_page
filename = options["<filename>"]
self.printer(f"Dumping Flash from sector 0 to sector {hex(sectors)}...")
if self.streaming.read_sectors(sector, sectors, filename, True):
self.printer(f"Dumped sector {str(sector)} with sector count {str(sectors)} as {filename}.")
elif cmd == "rl":
directory = options["<directory>"]
if options["--skip"]:
skip = options["--skip"].split(",")
else:
skip = []
if not os.path.exists(directory):
os.mkdir(directory)
storedir = directory
if not os.path.exists(storedir):
os.mkdir(storedir)
sfilename = os.path.join(storedir, f"partition.bin")
partdata = self.streaming.read_partition_table()
if partdata != -1:
with open(sfilename, "wb") as write_handle:
write_handle.write(partdata)
else:
self.error(f"Couldn't detect partition header.")
return
partitions = self.streaming.get_partitions()
for partition in partitions:
if partition in skip:
continue
filename = os.path.join(storedir, partition + ".bin")
spartition = partitions[partition]
offset = spartition["offset"]
length = spartition["length"]
# attr1 = spartition["attr1"]
# attr2 = spartition["attr2"]
# attr3 = spartition["attr3"]
partfilename = filename
self.info(f"Dumping partition {str(partition)} with block count {str(length)} as " +
f"{filename}.")
self.streaming.read_raw(offset, length, self.streaming.settings.UD_SIZE_BYTES, partfilename)
elif cmd == "peek":
offset = getint(options["<offset>"])
length = getint(options["<length>"])
filename = options["<filename>"]
if self.streaming.memtofile(offset, length, filename):
self.info(
f"Peek data from offset {hex(offset)} and length {hex(length)} was written to {filename}")
elif cmd == "peekhex":
offset = getint(options["<offset>"])
length = getint(options["<length>"])
resp = self.streaming.memread(offset, length)
self.printer("\n")
self.printer(hexlify(resp))
elif cmd == "peekqword":
offset = getint(options["<offset>"])
resp = self.streaming.memread(offset, 8)
self.printer("\n")
self.printer(hex(unpack("<Q", resp[:8])[0]))
elif cmd == "peekdword":
offset = getint(options["<offset>"])
resp = self.streaming.mempeek(offset)
self.printer("\n")
self.printer(hex(resp))
elif cmd == "poke":
offset = getint(options["<offset>"])
filename = unhexlify(options["<filename>"])
try:
with open(filename, "rb") as rf:
data = rf.read()
if self.streaming.memwrite(offset, data):
self.info("Poke succeeded.")
else:
self.error("Poke failed.")
except Exception as e:
self.error(str(e))
elif cmd == "pokehex":
offset = getint(options["<offset>"])
data = unhexlify(options["<data>"])
if self.streaming.memwrite(offset, data):
self.info("Poke succeeded.")
else:
self.error("Poke failed.")
elif cmd == "pokeqword":
offset = getint(options["<offset>"])
data = pack("<Q", getint(options["<data>"]))
if self.streaming.memwrite(offset, data):
self.info("Poke succeeded.")
else:
self.error("Poke failed.")
elif cmd == "pokedword":
offset = getint(options["<offset>"])
data = pack("<I", getint(options["<data>"]))
if self.streaming.mempoke(offset, data):
self.info("Poke succeeded.")
else:
self.error("Poke failed.")
elif cmd == "reset":
if self.streaming.reset():
self.info("Reset succeeded.")
elif cmd == "memtbl":
filename = options["<filename>"]
memtbl = self.streaming.settings.memtbl
data = self.streaming.memread(memtbl[0], memtbl[1])
if data != b"":
with open(filename, "wb") as wf:
wf.write(data)
self.printer(f"Dumped memtbl at offset {hex(memtbl[0])} as {filename}.")
else:
self.error("Error on dumping memtbl")
elif cmd == "secureboot":
value = self.streaming.mempeek(self.streaming.settings.secureboot)
if value != -1:
is_secure = False
for area in range(0, 4):
sec_boot = (value >> (area * 8)) & 0xFF
pk_hashindex = sec_boot & 3
oem_pkhash = True if ((sec_boot >> 4) & 1) == 1 else False
auth_enabled = True if ((sec_boot >> 5) & 1) == 1 else False
use_serial = True if ((sec_boot >> 6) & 1) == 1 else False
if auth_enabled:
is_secure = True
self.printer(f"Sec_Boot{str(area)} PKHash-Index:{str(pk_hashindex)} " +
f"OEM_PKHash: {str(oem_pkhash)} " +
f"Auth_Enabled: {str(auth_enabled)} " +
f"Use_Serial: {str(use_serial)}")
if is_secure:
self.printer("Secure boot enabled.")
else:
self.printer("Secure boot disabled.")
else:
self.error("Unknown target chipset")
elif cmd == "pbl":
filename = options["<filename>"]
pbl = self.streaming.settings.pbl
self.printer("Dumping pbl....")
data = self.streaming.memread(pbl[0], pbl[1])
if data != b"":
with open(filename, "wb") as wf:
wf.write(data)
self.printer(f"Dumped pbl at offset {hex(pbl[0])} as {filename}.")
else:
self.error("Error on dumping pbl")
elif cmd == "qfp":
filename = options["<filename>"]
qfp = self.streaming.settings.qfprom
self.printer("Dumping qfprom....")
data = self.streaming.memread(qfp[0], qfp[1])
if data != b"":
with open(filename, "wb") as wf:
wf.write(data)
self.printer(f"Dumped qfprom at offset {hex(qfp[0])} as {filename}.")
else:
self.error("Error on dumping qfprom")
elif cmd == "memcpy":
if not self.check_param(["<offset>", "<size>"]):
return False
srcoffset = getint(options["<offset>"])
size = getint(options["<size>"])
dstoffset = srcoffset + size
if self.streaming.cmd_memcpy(dstoffset, srcoffset, size):
self.printer(f"Memcpy from {hex(srcoffset)} to {hex(dstoffset)} succeeded")
return True
else:
return False
###############################
elif cmd == "nop":
# resp=self.streaming.send(b"\x7E\x09")
self.error("Nop command isn't supported by streaming loader")
return True
elif cmd == "setbootablestoragedrive":
self.error("setbootablestoragedrive command isn't supported by streaming loader")
return True
elif cmd == "getstorageinfo":
self.error("getstorageinfo command isn't supported by streaming loader")
return True
elif cmd == "w":
if not self.check_param(["<partitionname>", "<filename>"]):
return False
partitionname = options["<partitionname>"]
filename = options["<filename>"]
partitionfilename = ""
if "--partitionfilename" in options:
partitionfilename = options["--partitionfilename"]
if not os.path.exists(partitionfilename):
self.error(f"Error: Couldn't find partition file: {partitionfilename}")
return False
if not os.path.exists(filename):
self.error(f"Error: Couldn't find file: {filename}")
return False
if partitionfilename == "":
rpartitions = self.streaming.get_partitions()
else:
rpartitions = self.streaming.get_partitions(partitionfilename)
if self.streaming.enter_flash_mode():
if partitionname in rpartitions:
spartition = rpartitions[partitionname]
offset = spartition["offset"]
length = spartition["length"]
# attr1 = spartition["attr1"]
# attr2 = spartition["attr2"]
# attr3 = spartition["attr3"]
sectors = int(os.stat(
filename).st_size / self.streaming.settings.num_pages_per_blk /
self.streaming.settings.PAGESIZE)
if sectors > length:
self.error(
f"Error: {filename} has {sectors} sectors but partition only has {length}.")
return False
if self.streaming.modules is not None:
self.streaming.modules.writeprepare()
if self.streaming.write_flash(partitionname, filename):
self.printer(f"Wrote {filename} to sector {str(offset)}.")
return True
else:
self.printer(f"Error writing {filename} to sector {str(offset)}.")
return False
else:
self.error(f"Error: Couldn't detect partition: {partitionname}\nAvailable partitions:")
self.print_partitions(rpartitions)
return False
elif cmd == "wl":
if not self.check_param(["<directory>"]):
return False
directory = options["<directory>"]
if options["--skip"]:
skip = options["--skip"].split(",")
else:
skip = []
if not os.path.exists(directory):
self.error(f"Error: Couldn't find directory: {directory}")
return False
filenames = []
if self.streaming.enter_flash_mode():
if self.streaming.modules is not None:
self.streaming.modules.writeprepare()
rpartitions = self.streaming.get_partitions()
for dirName, subdirList, fileList in os.walk(directory):
for fname in fileList:
filenames.append(os.path.join(dirName, fname))
for filename in filenames:
for partition in rpartitions:
partname = filename[filename.rfind("/") + 1:]
if ".bin" in partname[-4:]:
partname = partname[:-4]
if partition == partname:
if partition in skip:
continue
spartition = rpartitions[partition]
offset = spartition["offset"]
length = spartition["length"]
# attr1 = spartition["attr1"]
# attr2 = spartition["attr2"]
# attr3 = spartition["attr3"]
sectors = int(os.stat(filename).st_size /
self.streaming.settings.num_pages_per_blk /
self.streaming.settings.PAGESIZE)
if sectors > length:
self.error(
f"Error: {filename} has {sectors} sectors but partition only has {length}.")
return False
self.printer(f"Writing {filename} to partition {str(partition)}.")
self.streaming.write_flash(partition, filename)
else:
self.printer("Couldn't write partition. Either wrong memorytype given or no gpt partition.")
return False
return True
elif cmd == "ws":
self.error("ws command isn't supported by streaming loader") # todo
return False
elif cmd == "wf":
self.error("wf command isn't supported by streaming loader") # todo
return False
elif cmd == "e":
self.error("e command isn't supported by streaming loader") # todo
return False
elif cmd == "es":
self.error("es command isn't supported by streaming loader") # todo
return False
elif cmd == "xml":
self.error("xml command isn't supported by streaming loader")
return False
elif cmd == "rawxml":
self.error("rawxml command isn't supported by streaming loader")
return False
elif cmd == "send":
self.error("send command isn't supported by streaming loader")
return False
###############################
elif cmd == "server":
return do_tcp_server(self, options, self.handle_streaming)
elif cmd == "modules":
if not self.check_param(["<command>", "<options>"]):
return False
command = options["<command>"]
options = options["<options>"]
if self.streaming.modules is None:
self.error("Feature is not supported")
return False
else:
return self.streaming.modules.run(mainargs=options, command=command)
else:
self.error("Unknown/Missing command, a command is required.")
return False

658
edl/Library/usblib.py Executable file
View file

@ -0,0 +1,658 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# (c) B.Kerler 2018-2021
import io
import logging
import usb.core # pyusb
import usb.util
import time
import inspect
import array
import usb.backend.libusb0
import usb.backend.libusb1
from enum import Enum
from binascii import hexlify
from ctypes import c_void_p, c_int
from edl.Library.utils import *
USB_DIR_OUT = 0 # to device
USB_DIR_IN = 0x80 # to host
# USB types, the second of three bRequestType fields
USB_TYPE_MASK = (0x03 << 5)
USB_TYPE_STANDARD = (0x00 << 5)
USB_TYPE_CLASS = (0x01 << 5)
USB_TYPE_VENDOR = (0x02 << 5)
USB_TYPE_RESERVED = (0x03 << 5)
# USB recipients, the third of three bRequestType fields
USB_RECIP_MASK = 0x1f
USB_RECIP_DEVICE = 0x00
USB_RECIP_INTERFACE = 0x01
USB_RECIP_ENDPOINT = 0x02
USB_RECIP_OTHER = 0x03
# From Wireless USB 1.0
USB_RECIP_PORT = 0x04
USB_RECIP_RPIPE = 0x05
tag = 0
CDC_CMDS = {
"SEND_ENCAPSULATED_COMMAND": 0x00,
"GET_ENCAPSULATED_RESPONSE": 0x01,
"SET_COMM_FEATURE": 0x02,
"GET_COMM_FEATURE": 0x03,
"CLEAR_COMM_FEATURE": 0x04,
"SET_LINE_CODING": 0x20,
"GET_LINE_CODING": 0x21,
"SET_CONTROL_LINE_STATE": 0x22,
"SEND_BREAK": 0x23, # wValue is break time
}
class UsbClass(metaclass=LogBase):
def load_windows_dll(self):
if os.name == 'nt':
windows_dir = None
try:
# add pygame folder to Windows DLL search paths
windows_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "Windows")
try:
os.add_dll_directory(windows_dir)
except Exception:
pass
os.environ['PATH'] = windows_dir + ';' + os.environ['PATH']
except Exception:
pass
del windows_dir
def __init__(self, loglevel=logging.INFO, portconfig=None, devclass=-1):
self.load_windows_dll()
self.connected = False
self.timeout = None
self.vid = None
self.pid = None
self.device = None
self.EP_IN = None
self.EP_OUT = None
self.interface = None
self.stopbits = None
self.databits = None
self.baudrate = None
self.parity = None
self.configuration = None
self.backend = None
self.loglevel = loglevel
self.portconfig = portconfig
self.devclass = devclass
self.__logger = self.__logger
self.info = self.__logger.info
self.error = self.__logger.error
self.warning = self.__logger.warning
self.debug = self.__logger.debug
self.__logger.setLevel(loglevel)
self.buffer = array.array('B', [0]) * 1048576
if loglevel == logging.DEBUG:
logfilename = "log.txt"
fh = logging.FileHandler(logfilename)
self.__logger.addHandler(fh)
if sys.platform.startswith('freebsd') or sys.platform.startswith('linux'):
self.backend = usb.backend.libusb1.get_backend(find_library=lambda x: "libusb-1.0.so")
elif sys.platform.startswith('win32'):
self.backend = usb.backend.libusb1.get_backend(find_library=lambda x: "libusb-1.0.dll")
if self.backend is not None:
self.backend.lib.libusb_set_option.argtypes = [c_void_p, c_int]
self.backend.lib.libusb_set_option(self.backend.ctx, 1)
def verify_data(self, data, pre="RX:"):
self.debug("", stack_info=True)
if isinstance(data, bytes) or isinstance(data, bytearray):
if data[:5] == b"<?xml":
try:
rdata = b""
for line in data.split(b"\n"):
try:
self.debug(pre + line.decode('utf-8'))
rdata += line + b"\n"
except Exception as e: # pylint: disable=broad-except
v = hexlify(line)
self.debug(str(e))
self.debug(pre + v.decode('utf-8'))
return rdata
except Exception as e: # pylint: disable=broad-except
self.debug(str(e))
pass
if logging.DEBUG >= self.__logger.level:
self.debug(pre + hexlify(data).decode('utf-8'))
else:
if logging.DEBUG >= self.__logger.level:
self.debug(pre + data)
return data
def getinterfacecount(self):
if self.vid is not None:
self.device = usb.core.find(idVendor=self.vid, idProduct=self.pid)
if self.device is None:
self.debug("Couldn't detect the device. Is it connected ?")
return False
try:
self.device.set_configuration()
except Exception as e:
self.debug(str(e))
pass
self.configuration = self.device.get_active_configuration()
self.debug(2, self.configuration)
return self.configuration.bNumInterfaces
else:
self.error("No device detected. Is it connected ?")
return 0
def setlinecoding(self, baudrate=None, parity=0, databits=8, stopbits=1):
sbits = {1: 0, 1.5: 1, 2: 2}
dbits = {5, 6, 7, 8, 16}
pmodes = {0, 1, 2, 3, 4}
brates = {300, 600, 1200, 2400, 4800, 9600, 14400,
19200, 28800, 38400, 57600, 115200, 230400}
if stopbits is not None:
if stopbits not in sbits.keys():
valid = ", ".join(str(k) for k in sorted(sbits.keys()))
raise ValueError("Valid stopbits are " + valid)
self.stopbits = stopbits
else:
self.stopbits = 0
if databits is not None:
if databits not in dbits:
valid = ", ".join(str(d) for d in sorted(dbits))
raise ValueError("Valid databits are " + valid)
self.databits = databits
else:
self.databits = 0
if parity is not None:
if parity not in pmodes:
valid = ", ".join(str(pm) for pm in sorted(pmodes))
raise ValueError("Valid parity modes are " + valid)
self.parity = parity
else:
self.parity = 0
if baudrate is not None:
if baudrate not in brates:
brs = sorted(brates)
dif = [abs(br - baudrate) for br in brs]
best = brs[dif.index(min(dif))]
raise ValueError(
"Invalid baudrates, nearest valid is {}".format(best))
self.baudrate = baudrate
linecode = [
self.baudrate & 0xff,
(self.baudrate >> 8) & 0xff,
(self.baudrate >> 16) & 0xff,
(self.baudrate >> 24) & 0xff,
sbits[self.stopbits],
self.parity,
self.databits]
txdir = 0 # 0:OUT, 1:IN
req_type = 1 # 0:std, 1:class, 2:vendor
recipient = 1 # 0:device, 1:interface, 2:endpoint, 3:other
req_type = (txdir << 7) + (req_type << 5) + recipient
data = bytearray(linecode)
wlen = self.device.ctrl_transfer(
req_type, CDC_CMDS["SET_LINE_CODING"],
data_or_wlength=data, windex=1)
self.debug("Linecoding set, {}b sent".format(wlen))
def setbreak(self):
txdir = 0 # 0:OUT, 1:IN
req_type = 1 # 0:std, 1:class, 2:vendor
recipient = 1 # 0:device, 1:interface, 2:endpoint, 3:other
req_type = (txdir << 7) + (req_type << 5) + recipient
wlen = self.device.ctrl_transfer(
bmrequesttype=req_type, brequest=CDC_CMDS["SEND_BREAK"],
wvalue=0, data_or_wlength=0, windex=1)
self.debug("Break set, {}b sent".format(wlen))
def setcontrollinestate(self, rts=None, dtr=None, isftdi=False):
ctrlstate = (2 if rts else 0) + (1 if dtr else 0)
if isftdi:
ctrlstate += (1 << 8) if dtr is not None else 0
ctrlstate += (2 << 8) if rts is not None else 0
txdir = 0 # 0:OUT, 1:IN
req_type = 2 if isftdi else 1 # 0:std, 1:class, 2:vendor
# 0:device, 1:interface, 2:endpoint, 3:other
recipient = 0 if isftdi else 1
req_type = (txdir << 7) + (req_type << 5) + recipient
wlen = self.device.ctrl_transfer(
bmrequesttype=req_type,
brequest=1 if isftdi else CDC_CMDS["SET_CONTROL_LINE_STATE"],
wvalue=ctrlstate,
windex=1,
data_or_wlength=0)
self.debug("Linecoding set, {}b sent".format(wlen))
def connect(self, EP_IN=-1, EP_OUT=-1):
if self.connected:
self.close()
self.connected = False
for usbid in self.portconfig:
vid = usbid[0]
pid = usbid[1]
interface = usbid[2]
self.device = usb.core.find(idVendor=vid, idProduct=pid, backend=self.backend)
if self.device is not None:
self.vid = vid
self.pid = pid
self.interface = interface
break
if self.device is None:
self.debug("Couldn't detect the device. Is it connected ?")
return False
try:
if self.device is not None:
self.configuration = self.device.get_active_configuration()
except usb.core.USBError as e:
if e.strerror == "Configuration not set":
self.device.set_configuration()
self.configuration = self.device.get_active_configuration()
if e.errno == 13:
self.backend = usb.backend.libusb0.get_backend()
self.device = usb.core.find(idVendor=vid, idProduct=pid, backend=self.backend)
if self.interface == -1:
for interfacenum in range(0, self.configuration.bNumInterfaces):
itf = usb.util.find_descriptor(self.configuration, bInterfaceNumber=interfacenum)
try:
if self.device.is_kernel_driver_active(interfacenum):
self.debug("Detaching kernel driver")
self.device.detach_kernel_driver(interfacenum)
except Exception as err:
self.debug("No kernel driver supported: " + str(err))
usb.util.claim_interface(self.device, interfacenum)
if self.devclass != -1:
if itf.bInterfaceClass == self.devclass: # MassStorage
self.interface = interfacenum
break
else:
self.interface = interfacenum
break
self.debug(self.configuration)
if self.interface > self.configuration.bNumInterfaces:
print("Invalid interface, max number is %d" % self.configuration.bNumInterfaces)
return False
if self.interface != -1:
itf = usb.util.find_descriptor(self.configuration, bInterfaceNumber=self.interface)
if EP_OUT == -1:
# match the first OUT endpoint
self.EP_OUT = usb.util.find_descriptor(itf,
custom_match=lambda em: usb.util.endpoint_direction(
em.bEndpointAddress) == usb.util.ENDPOINT_OUT)
else:
self.EP_OUT = EP_OUT
if EP_IN == -1:
# match the first OUT endpoint
self.EP_IN = usb.util.find_descriptor(itf,
custom_match=lambda em: usb.util.endpoint_direction(
em.bEndpointAddress) == usb.util.ENDPOINT_IN)
else:
self.EP_IN = EP_IN
self.connected = True
return True
else:
print("Couldn't find MassStorage interface. Aborting.")
self.connected = False
return False
def close(self, reset=False):
if self.connected:
usb.util.dispose_resources(self.device)
try:
if not self.device.is_kernel_driver_active(self.interface):
# self.device.attach_kernel_driver(self.interface) #Do NOT uncomment
self.device.attach_kernel_driver(0)
if reset:
self.device.reset()
except Exception as e: # pylint: disable=broad-except
self.debug(str(e))
pass
del self.device
def write(self, command):
pktsize=self.EP_OUT.wMaxPacketSize
if isinstance(command, str):
command = bytes(command, 'utf-8')
if command == b'':
try:
self.EP_OUT.write(b'')
except usb.core.USBError as e:
error = str(e.strerror)
if "timeout" in error:
time.sleep(0.01)
try:
self.EP_OUT.write(b'')
except Exception as e: # pylint: disable=broad-except
self.debug(str(e))
return False
return True
else:
i = 0
try:
buffer=array.array('B',command)
self.EP_OUT.write(buffer)
except Exception as e: # pylint: disable=broad-except
# print("Error while writing")
if "timed out" in str(e):
self.debug(str(e))
time.sleep(0.01)
i += 1
if i == 3:
return False
pass
else:
self.error(str(e))
return False
if self.loglevel == logging.DEBUG:
self.verify_data(bytearray(command), "TX:")
return True
def read(self, length=0x80, timeout=None):
if self.loglevel==logging.DEBUG:
self.debug(inspect.currentframe().f_back.f_code.co_name + ":" + hex(length))
tmp = bytearray()
extend = tmp.extend
if timeout is None:
timeout = self.timeout
buffer = self.buffer
ep_read = self.EP_IN.read
while len(tmp) == 0:
try:
length=ep_read(buffer, timeout)
extend(buffer[:length])
if len(tmp)>0:
return tmp
except usb.core.USBError as e:
error = str(e.strerror)
if "timed out" in error:
# if platform.system()=='Windows':
# time.sleep(0.05)
# print("Waiting...")
self.debug("Timed out")
self.debug(tmp)
return tmp
elif "Overflow" in error:
self.error("USB Overflow")
sys.exit(0)
elif e.errno is not None:
print(repr(e), type(e), e.errno)
sys.exit(0)
else:
break
if self.loglevel == logging.DEBUG:
self.verify_data(tmp, "RX:")
return tmp
def ctrl_transfer(self, bmrequesttype, brequest, wvalue, windex, data_or_wlength):
ret = self.device.ctrl_transfer(bmrequesttype=bmrequesttype, brequest=brequest, wvalue=wvalue, windex=windex,
data_or_wlength=data_or_wlength)
return ret[0] | (ret[1] << 8)
class ScsiCmds(Enum):
SC_TEST_UNIT_READY = 0x00,
SC_REQUEST_SENSE = 0x03,
SC_FORMAT_UNIT = 0x04,
SC_READ_6 = 0x08,
SC_WRITE_6 = 0x0a,
SC_INQUIRY = 0x12,
SC_MODE_SELECT_6 = 0x15,
SC_RESERVE = 0x16,
SC_RELEASE = 0x17,
SC_MODE_SENSE_6 = 0x1a,
SC_START_STOP_UNIT = 0x1b,
SC_SEND_DIAGNOSTIC = 0x1d,
SC_PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1e,
SC_READ_FORMAT_CAPACITIES = 0x23,
SC_READ_CAPACITY = 0x25,
SC_WRITE_10 = 0x2a,
SC_VERIFY = 0x2f,
SC_READ_10 = 0x28,
SC_SYNCHRONIZE_CACHE = 0x35,
SC_READ_TOC = 0x43,
SC_READ_HEADER = 0x44,
SC_MODE_SELECT_10 = 0x55,
SC_MODE_SENSE_10 = 0x5a,
SC_READ_12 = 0xa8,
SC_WRITE_12 = 0xaa,
SC_PASCAL_MODE = 0xff
command_block_wrapper = [
('dCBWSignature', '4s'),
('dCBWTag', 'I'),
('dCBWDataTransferLength', 'I'),
('bmCBWFlags', 'B'),
('bCBWLUN', 'B'),
('bCBWCBLength', 'B'),
('CBWCB', '16s'),
]
command_block_wrapper_len = 31
command_status_wrapper = [
('dCSWSignature', '4s'),
('dCSWTag', 'I'),
('dCSWDataResidue', 'I'),
('bCSWStatus', 'B')
]
command_status_wrapper_len = 13
class Scsi:
"""
FIHTDC, PCtool
"""
SC_READ_NV = 0xf0
SC_SWITCH_STATUS = 0xf1
SC_SWITCH_PORT = 0xf2
SC_MODEM_STATUS = 0xf4
SC_SHOW_PORT = 0xf5
SC_MODEM_DISCONNECT = 0xf6
SC_MODEM_CONNECT = 0xf7
SC_DIAG_RUT = 0xf8
SC_READ_BATTERY = 0xf9
SC_READ_IMAGE = 0xfa
SC_ENABLE_ALL_PORT = 0xfd
SC_MASS_STORGE = 0xfe
SC_ENTER_DOWNLOADMODE = 0xff
SC_ENTER_FTMMODE = 0xe0
SC_SWITCH_ROOT = 0xe1
"""
//Div2-5-3-Peripheral-LL-ADB_ROOT-00+/* } FIHTDC, PCtool */
//StevenCPHuang 2011/08/12 porting base on 1050 --
//StevenCPHuang_20110820,add Moto's mode switch cmd to support PID switch function ++
"""
SC_MODE_SWITCH = 0xD6
# /StevenCPHuang_20110820,add Moto's mode switch cmd to support PID switch function --
def __init__(self, loglevel=logging.INFO, vid=None, pid=None, interface=-1):
self.vid = vid
self.pid = pid
self.interface = interface
self.Debug = False
self.usb = None
self.loglevel = loglevel
def connect(self):
self.usb = UsbClass(loglevel=self.loglevel, portconfig=[self.vid, self.pid, self.interface], devclass=8)
if self.usb.connect():
return True
return False
# htcadb = "55534243123456780002000080000616687463800100000000000000000000";
# Len 0x6, Command 0x16, "HTC" 01 = Enable, 02 = Disable
def send_mass_storage_command(self, lun, cdb, direction, data_length):
global tag
cmd = cdb[0]
if 0 <= cmd < 0x20:
cdb_len = 6
elif 0x20 <= cmd < 0x60:
cdb_len = 10
elif 0x60 <= cmd < 0x80:
cdb_len = 0
elif 0x80 <= cmd < 0xA0:
cdb_len = 16
elif 0xA0 <= cmd < 0xC0:
cdb_len = 12
else:
cdb_len = 6
if len(cdb) != cdb_len:
print("Error, cdb length doesn't fit allowed cbw packet length")
return 0
if (cdb_len == 0) or (cdb_len > command_block_wrapper_len):
print("Error, invalid data packet length, should be max of 31 bytes.")
return 0
else:
data = write_object(command_block_wrapper, b"USBC", tag, data_length, direction, lun, cdb_len, cdb)[
'raw_data']
print(hexlify(data))
if len(data) != 31:
print("Error, invalid data packet length, should be 31 bytes, but length is %d" % len(data))
return 0
tag += 1
self.usb.write(data, 31)
return tag
def send_htc_adbenable(self):
# do_reserve from f_mass_storage.c
print("Sending HTC adb enable command")
common_cmnd = b"\x16htc\x80\x01" # reserve_cmd + 'htc' + len + flag
'''
Flag values:
1: Enable adb daemon from mass_storage
2: Disable adb daemon from mass_storage
3: cancel unmount BAP cdrom
4: cancel unmount HSM rom
'''
lun = 0
datasize = common_cmnd[4]
timeout = 5000
ret_tag = self.send_mass_storage_command(lun, common_cmnd, USB_DIR_IN, datasize)
ret_tag += self.send_mass_storage_command(lun, common_cmnd, USB_DIR_IN, datasize)
if datasize > 0:
data = self.usb.read(datasize, timeout)
print("DATA: " + hexlify(data).decode('utf-8'))
print("Sent HTC adb enable command")
def send_htc_ums_adbenable(self): # HTC10
# ums_ctrlrequest from f_mass_storage.c
print("Sending HTC ums adb enable command")
brequesttype = USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE
brequest = 0xa0
wvalue = 1
'''
wValue:
0: Disable adb daemon
1: Enable adb daemon
'''
windex = 0
w_length = 1
ret = self.usb.ctrl_transfer(brequesttype, brequest, wvalue, windex, w_length)
print("Sent HTC ums adb enable command: %x" % ret)
def send_zte_adbenable(self): # zte blade
common_cmnd = b"\x86zte\x80\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" # reserve_cmd + 'zte' + len + flag
common_cmnd2 = b"\x86zte\x80\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" # reserve_cmd + 'zte' + len + flag
'''
Flag values:
0: disable adbd ---for 736T
1: enable adbd ---for 736T
2: disable adbd ---for All except 736T
3: enable adbd ---for All except 736T
'''
lun = 0
datasize = common_cmnd[4]
timeout = 5000
ret_tag = self.send_mass_storage_command(lun, common_cmnd, USB_DIR_IN, datasize)
ret_tag += self.send_mass_storage_command(lun, common_cmnd, USB_DIR_IN, datasize)
ret_tag = self.send_mass_storage_command(lun, common_cmnd2, USB_DIR_IN, datasize)
ret_tag += self.send_mass_storage_command(lun, common_cmnd2, USB_DIR_IN, datasize)
if datasize > 0:
data = self.usb.read(datasize, timeout)
print("DATA: " + hexlify(data).decode('utf-8'))
print("Send HTC adb enable command")
def send_fih_adbenable(self): # motorola xt560, nokia 3.1, #f_mass_storage.c
if self.usb.connect():
print("Sending FIH adb enable command")
datasize = 0x24
# reserve_cmd + 'FI' + flag + len + none
common_cmnd = bytes([self.SC_SWITCH_PORT]) + b"FI1" + struct.pack("<H", datasize)
'''
Flag values:
common_cmnd[3]->1: Enable adb daemon from mass_storage
common_cmnd[3]->0: Disable adb daemon from mass_storage
'''
lun = 0
# datasize=common_cmnd[4]
timeout = 5000
ret_tag = None
ret_tag += self.send_mass_storage_command(lun, common_cmnd, USB_DIR_IN, 0x600)
# ret_tag+=self.send_mass_storage_command(lun, common_cmnd, USB_DIR_IN, 0x600)
if datasize > 0:
data = self.usb.read(datasize, timeout)
print("DATA: " + hexlify(data).decode('utf-8'))
print("Sent FIH adb enable command")
self.usb.close()
def send_alcatel_adbenable(self): # Alcatel MW41
if self.usb.connect():
print("Sending alcatel adb enable command")
datasize = 0x24
common_cmnd = b"\x16\xf9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
lun = 0
timeout = 5000
ret_tag = None
ret_tag += self.send_mass_storage_command(lun, common_cmnd, USB_DIR_IN, 0x600)
if datasize > 0:
data = self.usb.read(datasize, timeout)
print("DATA: " + hexlify(data).decode('utf-8'))
print("Sent alcatel adb enable command")
self.usb.close()
def send_fih_root(self):
# motorola xt560, nokia 3.1, huawei u8850, huawei Ideos X6,
# lenovo s2109, triumph M410, viewpad 7, #f_mass_storage.c
if self.usb.connect():
print("Sending FIH root command")
datasize = 0x24
# reserve_cmd + 'FIH' + len + flag + none
common_cmnd = bytes([self.SC_SWITCH_ROOT]) + b"FIH" + struct.pack("<H", datasize)
lun = 0
# datasize = common_cmnd[4]
timeout = 5000
ret_tag = self.send_mass_storage_command(lun, common_cmnd, USB_DIR_IN, 0x600)
ret_tag += self.send_mass_storage_command(lun, common_cmnd, USB_DIR_IN, 0x600)
if datasize > 0:
data = self.usb.read(datasize, timeout)
print("DATA: " + hexlify(data).decode('utf-8'))
print("Sent FIH root command")
self.usb.close()
def close(self):
self.usb.close()
return True

52
edl/Library/usbscsi.py Executable file
View file

@ -0,0 +1,52 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# (c) B.Kerler 2018-2021
import argparse
from edl.Library.usblib import *
def main():
info = 'MassStorageBackdoor (c) B.Kerler 2019.'
parser = argparse.ArgumentParser(description=info)
print("\n" + info + "\n\n")
parser.add_argument('-vid', metavar="<vid>", help='[Option] Specify vid, default=0x2e04)', default="0x2e04")
parser.add_argument('-pid', metavar="<pid>", help='[Option] Specify pid, default=0xc025)', default="0xc025")
parser.add_argument('-interface', metavar="<pid>", help='[Option] Specify interface number)', default="")
parser.add_argument('-nokia', help='[Option] Enable Nokia adb backdoor', action='store_true')
parser.add_argument('-alcatel', help='[Option] Enable alcatel adb backdoor', action='store_true')
parser.add_argument('-zte', help='[Option] Enable zte adb backdoor', action='store_true')
parser.add_argument('-htc', help='[Option] Enable htc adb backdoor', action='store_true')
parser.add_argument('-htcums', help='[Option] Enable htc ums adb backdoor', action='store_true')
args = parser.parse_args()
vid = None
pid = None
if args.vid != "":
vid = int(args.vid, 16)
if args.pid != "":
pid = int(args.pid, 16)
if args.interface != "":
interface = int(args.interface, 16)
else:
interface = -1
usbscsi = Scsi(vid, pid, interface)
if usbscsi.connect():
if args.nokia:
usbscsi.send_fih_adbenable()
usbscsi.send_fih_root()
elif args.zte:
usbscsi.send_zte_adbenable()
elif args.htc:
usbscsi.send_htc_adbenable()
elif args.htcums:
usbscsi.send_htc_ums_adbenable()
elif args.alcatel:
usbscsi.send_alcatel_adbenable()
else:
print("A command is required. Use -h to see options.")
exit(0)
usbscsi.close()
if __name__ == '__main__':
main()

587
edl/Library/utils.py Executable file
View file

@ -0,0 +1,587 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# (c) B.Kerler 2018-2021
import sys
import logging
import logging.config
import codecs
import struct
import os
import shutil
import stat
import colorama
import copy
from struct import unpack, pack
try:
from capstone import *
except ImportError:
print("Capstone library is missing (optional).")
try:
from keystone import *
except ImportError:
print("Keystone library is missing (optional).")
class structhelper:
pos = 0
def __init__(self, data, pos=0):
self.pos = 0
self.data = data
def qword(self):
dat = unpack("<Q", self.data[self.pos:self.pos + 8])[0]
self.pos += 8
return dat
def dword(self):
dat = unpack("<I", self.data[self.pos:self.pos + 4])[0]
self.pos += 4
return dat
def dwords(self, dwords=1):
dat = unpack("<" + str(dwords) + "I", self.data[self.pos:self.pos + 4 * dwords])
self.pos += 4 * dwords
return dat
def short(self):
dat = unpack("<H", self.data[self.pos:self.pos + 2])[0]
self.pos += 2
return dat
def shorts(self, shorts):
dat = unpack("<" + str(shorts) + "H", self.data[self.pos:self.pos + 2*shorts])
self.pos += 2 * shorts
return dat
def bytes(self, rlen=1):
dat = self.data[self.pos:self.pos + rlen]
self.pos += rlen
if rlen == 1: return dat[0]
return dat
def string(self, rlen=1):
dat = self.data[self.pos:self.pos + rlen]
self.pos += rlen
return dat
def getpos(self):
return self.pos
def seek(self, pos):
self.pos = pos
def do_tcp_server(client, arguments, handler):
def tcpprint(arg):
if isinstance(arg, bytes) or isinstance(arg, bytearray):
return connection.sendall(arg)
else:
return connection.sendall(bytes(str(arg), 'utf-8'))
client.printer = tcpprint
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
port = int(arguments["--tcpport"])
server_address = ('localhost', port)
print('starting up on %s port %s' % server_address)
sock.bind(server_address)
sock.listen(1)
response = None
while True:
print('waiting for a connection')
connection, client_address = sock.accept()
try:
print('connection from', client_address)
while True:
data = connection.recv(4096).decode('utf-8')
if data == '':
break
print('received %s' % data)
if data:
print('handling request')
lines = data.split("\n")
for line in lines:
if ":" in line:
cmd = line.split(":")[0]
marguments = line.split(":")[1]
try:
opts = parse_args(cmd, marguments, arguments)
except:
response = "Wrong arguments\n<NAK>\n"
opts = None
if opts is not None:
if handler(cmd, opts):
response = "<ACK>\n"
else:
response = "<NAK>\n"
connection.sendall(bytes(response, 'utf-8'))
finally:
connection.close()
def parse_args(cmd, args, mainargs):
options = {}
opts = None
if "," in args:
opts = args.split(",")
else:
opts = [args]
for arg in mainargs:
if "--" in arg:
options[arg] = mainargs[arg]
if cmd == "gpt":
options["<directory>"] = opts[0]
elif cmd == "r":
options["<partitionname>"] = opts[0]
options["<filename>"] = opts[1]
elif cmd == "rl":
options["<directory>"] = opts[0]
elif cmd == "rf":
options["<filename>"] = opts[0]
elif cmd == "rs":
options["<start_sector>"] = opts[0]
options["<sectors>"] = opts[1]
options["<filename>"] = opts[2]
elif cmd == "w":
options["<partitionname>"] = opts[0]
options["<filename>"] = opts[0]
elif cmd == "wl":
options["<directory>"] = opts[0]
elif cmd == "wf":
options["<filename>"] = opts[0]
elif cmd == "ws":
options["<start_sector>"] = opts[0]
options["<filename>"] = opts[1]
elif cmd == "e":
options["<partitionname>"] = opts[0]
elif cmd == "es":
options["<start_sector>"] = opts[0]
options["<sectors>"] = opts[1]
elif cmd == "footer":
options["<filename>"] = opts[0]
elif cmd == "peek":
options["<offset>"] = opts[0]
options["<length>"] = opts[1]
options["<filename>"] = opts[2]
elif cmd == "peekhex":
options["<offset>"] = opts[0]
options["<length>"] = opts[1]
elif cmd == "peekdword":
options["<offset>"] = opts[0]
elif cmd == "peekqword":
options["<offset>"] = opts[0]
elif cmd == "memtbl":
options["<filename>"] = opts[0]
elif cmd == "poke":
options["<offset>"] = opts[0]
options["<filename>"] = opts[1]
elif cmd == "pokehex":
options["<offset>"] = opts[0]
options["<data>"] = opts[1]
elif cmd == "pokedword":
options["<offset>"] = opts[0]
options["<data>"] = opts[1]
elif cmd == "pokeqword":
options["<offset>"] = opts[0]
options["<data>"] = opts[1]
elif cmd == "memcpy":
options["<offset>"] = opts[0]
options["<size>"] = opts[1]
elif cmd == "pbl":
options["<filename>"] = opts[0]
elif cmd == "qfp":
options["<filename>"] = opts[0]
elif cmd == "setbootablestoragedrive":
options["<lun>"] = opts[0]
elif cmd == "send":
options["<command>"] = opts[0]
elif cmd == "xml":
options["<xmlfile>"] = opts[0]
elif cmd == "rawxml":
options["<xmlstring>"] = opts[0]
return options
def getint(valuestr):
try:
return int(valuestr)
except:
try:
return int(valuestr, 16)
except:
return 0
class ColorFormatter(logging.Formatter):
LOG_COLORS = {
logging.ERROR: colorama.Fore.RED,
logging.DEBUG: colorama.Fore.LIGHTMAGENTA_EX,
logging.WARNING: colorama.Fore.YELLOW,
}
def format(self, record, *args, **kwargs):
# if the corresponding logger has children, they may receive modified
# record, so we want to keep it intact
new_record = copy.copy(record)
if new_record.levelno in self.LOG_COLORS:
pad = ""
if new_record.name != "root":
print(new_record.name)
pad = "[LIB]: "
# we want levelname to be in different color, so let's modify it
new_record.msg = "{pad}{color_begin}{msg}{color_end}".format(
pad=pad,
msg=new_record.msg,
color_begin=self.LOG_COLORS[new_record.levelno],
color_end=colorama.Style.RESET_ALL,
)
# now we can let standart formatting take care of the rest
return super(ColorFormatter, self).format(new_record, *args, **kwargs)
class LogBase(type):
debuglevel = logging.root.level
def __init__(cls, *args):
super().__init__(*args)
logger_attribute_name = '_' + cls.__name__ + '__logger'
logger_debuglevel_name = '_' + cls.__name__ + '__debuglevel'
logger_name = '.'.join([c.__name__ for c in cls.mro()[-2::-1]])
LOG_CONFIG = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"root": {
"()": ColorFormatter,
"format": "%(name)s - %(message)s",
}
},
"handlers": {
"root": {
# "level": cls.__logger.level,
"formatter": "root",
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout",
}
},
"loggers": {
"": {
"handlers": ["root"],
# "level": cls.debuglevel,
"propagate": False
}
},
}
logging.config.dictConfig(LOG_CONFIG)
logger = logging.getLogger(logger_name)
setattr(cls, logger_attribute_name, logger)
setattr(cls, logger_debuglevel_name, cls.debuglevel)
def del_rw(action, name, exc):
os.chmod(name, stat.S_IWRITE)
os.remove(name)
def rmrf(path):
if os.path.exists(path):
if os.path.isfile(path):
del_rw("", path, "")
else:
shutil.rmtree(path, onerror=del_rw)
class elf:
class memorysegment:
phy_addr = 0
virt_start_addr = 0
virt_end_addr = 0
file_start_addr = 0
file_end_addr = 0
def __init__(self, indata, filename):
self.data = indata
self.filename = filename
self.header, self.pentry = self.parse()
self.memorylayout = []
for entry in self.pentry:
ms = self.memorysegment()
ms.phy_addr = entry.phy_addr
ms.virt_start_addr = entry.virt_addr
ms.virt_end_addr = entry.virt_addr + entry.seg_mem_len
ms.file_start_addr = entry.from_file
ms.file_end_addr = entry.from_file + entry.seg_file_len
self.memorylayout.append(ms)
def getfileoffset(self, offset):
for memsegment in self.memorylayout:
if memsegment.virt_end_addr >= offset >= memsegment.virt_start_addr:
return offset - memsegment.virt_start_addr + memsegment.file_start_addr
return None
def getvirtaddr(self, fileoffset):
for memsegment in self.memorylayout:
if memsegment.file_end_addr >= fileoffset >= memsegment.file_start_addr:
return memsegment.virt_start_addr + fileoffset - memsegment.file_start_addr
return None
def getbaseaddr(self, offset):
for memsegment in self.memorylayout:
if memsegment.virt_end_addr >= offset >= memsegment.virt_start_addr:
return memsegment.virt_start_addr
return None
class programentry:
p_type = 0
from_file = 0
virt_addr = 0
phy_addr = 0
seg_file_len = 0
seg_mem_len = 0
p_flags = 0
p_align = 0
def parse_programentry(self, dat):
pe = self.programentry()
if self.elfclass == 1:
(pe.p_type, pe.from_file, pe.virt_addr, pe.phy_addr, pe.seg_file_len, pe.seg_mem_len, pe.p_flags,
pe.p_align) = struct.unpack("<IIIIIIII", dat)
elif self.elfclass == 2:
(pe.p_type, pe.p_flags, pe.from_file, pe.virt_addr, pe.phy_addr, pe.seg_file_len, pe.seg_mem_len,
pe.p_align) = struct.unpack("<IIQQQQQQ", dat)
return pe
def parse(self):
self.elfclass = self.data[4]
if self.elfclass == 1: # 32Bit
start = 0x28
elif self.elfclass == 2: # 64Bit
start = 0x34
else:
print("Error on parsing " + self.filename)
return ['', '']
elfheadersize, programheaderentrysize, programheaderentrycount = struct.unpack("<HHH",
self.data[start:start + 3 * 2])
programheadersize = programheaderentrysize * programheaderentrycount
header = self.data[0:elfheadersize + programheadersize]
pentry = []
for i in range(0, programheaderentrycount):
start = elfheadersize + (i * programheaderentrysize)
end = start + programheaderentrysize
pentry.append(self.parse_programentry(self.data[start:end]))
return [header, pentry]
class patchtools:
cstyle = False
bDebug = False
def __init__(self, bdebug=False):
self.bDebug = bdebug
def has_bad_uart_chars(self, data):
badchars = [b'\x00', b'\n', b'\r', b'\x08', b'\x7f', b'\x20', b'\x09']
for idx, c in enumerate(data):
c = bytes([c])
if c in badchars:
return True
return False
def generate_offset(self, offset):
div = 0
found = False
while not found and div < 0x606:
data = struct.pack("<I", offset + div)
data2 = struct.pack("<H", div)
badchars = self.has_bad_uart_chars(data)
if not badchars:
badchars = self.has_bad_uart_chars(data2)
if not (badchars):
return div
div += 4
# if div is not found within positive offset, try negative offset
div = 0
while not found and div < 0x606:
data = struct.pack("<I", offset - div)
data2 = struct.pack("<H", div)
badchars = self.has_bad_uart_chars(data)
if not badchars:
badchars = self.has_bad_uart_chars(data2)
if not badchars:
return -div
div += 4
return 0
# Usage: offset, "X24"
def generate_offset_asm(self, offset, reg):
div = self.generate_offset(offset)
abase = ((offset + div) & 0xFFFF0000) >> 16
a = ((offset + div) & 0xFFFF)
strasm = ""
if div > 0:
strasm += "# " + hex(offset) + "\n"
strasm += "mov " + reg + ", #" + hex(a) + ";\n"
strasm += "movk " + reg + ", #" + hex(abase) + ", LSL#16;\n"
strasm += "sub " + reg + ", " + reg + ", #" + hex(div) + ";\n"
else:
strasm += "# " + hex(offset) + "\n"
strasm += "mov " + reg + ", #" + hex(a) + ";\n"
strasm += "movk " + reg + ", #" + hex(abase) + ", LSL#16;\n"
strasm += "add " + reg + ", " + reg + ", #" + hex(-div) + ";\n"
return strasm
def uart_valid_sc(self, sc):
badchars = [b'\x00', b'\n', b'\r', b'\x08', b'\x7f', b'\x20', b'\x09']
for idx, c in enumerate(sc):
c = bytes([c])
if c in badchars:
print("bad char 0x%s in SC at offset %d, opcode # %d!\n" % (codecs.encode(c, 'hex'), idx, idx / 4))
print(codecs.encode(sc, 'hex'))
return False
return True
def disasm(self, code, size):
cs = Cs(CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN)
instr = []
for i in cs.disasm(code, size):
# print("0x%x:\t%s\t%s" % (i.address, i.mnemonic, i.op_str))
instr.append("%s\t%s" % (i.mnemonic, i.op_str))
return instr
def assembler(self, code):
ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN)
if self.bDebug:
try:
encoding, count = ks.asm(code)
except KsError as e:
print(e)
print(e.stat_count)
print(code[e.stat_count:e.stat_count + 10])
exit(0)
if self.bDebug:
# walk every line to find the (first) error
for idx, line in enumerate(code.splitlines()):
print("%02d: %s" % (idx, line))
if len(line) and line[0] != '.':
try:
encoding, count = ks.asm(line)
except Exception as e:
print("bummer: " + str(e))
else:
exit(0)
else:
encoding, count = ks.asm(code)
sc = ""
count = 0
out = ""
for i in encoding:
if self.cstyle:
out += ("\\x%02x" % i)
else:
out += ("%02x" % i)
sc += "%02x" % i
count += 1
# if bDebug and count % 4 == 0:
# out += ("\n")
return out
def find_binary(self, data, strf, pos=0):
t = strf.split(b".")
pre = 0
offsets = []
while pre != -1:
pre = data[pos:].find(t[0], pre)
if pre == -1:
if len(offsets) > 0:
for offset in offsets:
error = 0
rt = offset + len(t[0])
for i in range(1, len(t)):
if t[i] == b'':
rt += 1
continue
rt += 1
prep = data[rt:].find(t[i])
if (prep != 0):
error = 1
break
rt += len(t[i])
if error == 0:
return offset
else:
return None
else:
offsets.append(pre)
pre += 1
return None
def read_object(data: object, definition: object) -> object:
"""
Unpacks a structure using the given data and definition.
"""
obj = {}
object_size = 0
pos = 0
for (name, stype) in definition:
object_size += struct.calcsize(stype)
obj[name] = struct.unpack(stype, data[pos:pos + struct.calcsize(stype)])[0]
pos += struct.calcsize(stype)
obj['object_size'] = object_size
obj['raw_data'] = data
return obj
def write_object(definition, *args):
"""
Unpacks a structure using the given data and definition.
"""
obj = {}
object_size = 0
data = b""
i = 0
for (name, stype) in definition:
object_size += struct.calcsize(stype)
arg = args[i]
try:
data += struct.pack(stype, arg)
except Exception as e:
print("Error:" + str(e))
break
i += 1
obj['object_size'] = len(data)
obj['raw_data'] = data
return obj
def print_progress(iteration, total, prefix='', suffix='', decimals=1, bar_length=100):
"""
Call in a loop to create terminal progress bar
@params:
iteration - Required : current iteration (Int)
total - Required : total iterations (Int)
prefix - Optional : prefix string (Str)
suffix - Optional : suffix string (Str)
decimals - Optional : positive number of decimals in percent complete (Int)
bar_length - Optional : character length of bar (Int)
"""
str_format = "{0:." + str(decimals) + "f}"
percents = str_format.format(100 * (iteration / float(total)))
filled_length = int(round(bar_length * iteration / float(total)))
bar = '' * filled_length + '-' * (bar_length - filled_length)
sys.stdout.write('\r%s |%s| %s%s %s' % (prefix, bar, percents, '%', suffix))
if iteration == total:
sys.stdout.write('\n')
sys.stdout.flush()

36
edl/Library/xmlparser.py Executable file
View file

@ -0,0 +1,36 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# (c) B.Kerler 2018-2021
import xml.etree.ElementTree as ET
class xmlparser:
def getresponse(self, input):
lines = input.split(b"<?xml")
content = {}
for line in lines:
if line == b'':
continue
line = b"<?xml" + line
parser = ET.XMLParser(encoding="utf-8")
tree = ET.fromstring(line, parser=parser)
e = ET.ElementTree(tree).getroot()
for atype in e.findall('response'):
for field in atype.attrib:
content[field] = atype.attrib[field]
return content
def getlog(self, input):
lines = input.split(b"<?xml")
data = []
for line in lines:
if line == b'':
continue
line = b"<?xml" + line
parser = ET.XMLParser(encoding="utf-8")
tree = ET.fromstring(line, parser=parser)
e = ET.ElementTree(tree).getroot()
for atype in e.findall('log'):
if 'value' in atype.attrib:
data.append(atype.attrib['value'])
return data

BIN
edl/Windows/libusb-1.0.dll Executable file

Binary file not shown.

0
edl/__init__.py Executable file
View file