├── .github └── FUNDING.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── add_fp_5110.sh ├── driver_51x0.py ├── driver_51x0_spi.py ├── driver_51x7.py ├── driver_52xd.py ├── driver_53x5.py ├── driver_53xd.py ├── driver_5503.py ├── driver_55x4.py ├── dump_5335.py ├── dump_5385.py ├── dump_5395.py ├── dumper_53x5.py ├── flash_5395.py ├── flasher_53x5.py ├── goodix.py ├── log ├── ENGINE.log ├── README.md ├── WBDI.log └── goodix_enable_logs.reg ├── preprocessor.py ├── print_config_53x5.py ├── protocol.py ├── requirements.txt ├── run_5110.py ├── run_5117.py ├── run_5120_spi.py ├── run_521d.py ├── run_532d.py ├── run_5385.py ├── run_538d.py ├── run_5395.py ├── run_5503.py ├── run_55a4.py ├── run_55b4.py ├── tool.py ├── wireshark ├── README.md ├── goodix_message.lua └── wrapless_goodix_message.lua └── wrapless.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: mpi3d 2 | custom: "btc.com/btc/address/1MPi3D1HxS71k83XnqK3yB8CNmyHQeerZZ" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # Images 132 | *.png 133 | *.pgm 134 | 135 | # Binaries firmwares 136 | *.bin 137 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "firmware"] 2 | path = firmware 3 | url = https://github.com/goodix-fp-linux-dev/goodix-firmware.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Goodix Fingerprint Linux Development 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Goodix FP Dump 2 | 3 | All our work to make Goodix fingerprint sensors work on Linux. 4 | You can communicate with us at the Discord channel [Goodix Fingerprint Linux Development](https://discord.com/invite/6xZ6k34Vqg). 5 | 6 | The libfprint driver development can be found at https://github.com/goodix-fp-linux-dev/libfprint. 7 | 8 | ## How to use it 9 | 10 | We do not recommend using this for now. This is very unstable. 11 | Also, this make people to create many duplicates issues to tell us that it doesn't work. Of course, we already know that. 12 | Because of this, programs execution might be disabled in the future. 13 | So please think carefully before running this or creating an issue. 14 | 15 | ``` sh 16 | python --version # Must be Python 3.10 or newer 17 | git clone --recurse-submodules https://github.com/goodix-fp-linux-dev/goodix-fp-dump.git 18 | cd goodix-fp-dump 19 | python -m venv .venv 20 | source .venv/bin/activate 21 | pip install -r requirements.txt 22 | sudo lsusb -vd "27c6:" | grep "idProduct" # Returns the device ID 23 | sudo python3 run_5110.py # Change "5110" to your device ID 24 | ``` 25 | -------------------------------------------------------------------------------- /add_fp_5110.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python run_5110.py 3 | mogrify -crop 64x80+0+0 -format jpg fingerprint.pgm 4 | mkdir fpr 5 | mv fingerprint.jpg fpr/$(cat id).jpg 6 | echo $(($(cat id) + 1)) > id 7 | -------------------------------------------------------------------------------- /driver_51x0.py: -------------------------------------------------------------------------------- 1 | import random 2 | import re 3 | import socket 4 | import struct 5 | import subprocess 6 | 7 | import crcmod 8 | 9 | import goodix 10 | import protocol 11 | import tool 12 | 13 | TARGET_FIRMWARE = "GF_ST411SEC_APP_12117" 14 | IAP_FIRMWARE = "MILAN_ST411SEC_IAP_12101" 15 | VALID_FIRMWARE = "GF_ST411SEC_APP_121[0-9]{2}" 16 | 17 | PSK = bytes.fromhex( 18 | "0000000000000000000000000000000000000000000000000000000000000000") 19 | 20 | PSK_WHITE_BOX = bytes.fromhex( 21 | "ec35ae3abb45ed3f12c4751f1e5c2cc05b3c5452e9104d9f2a3118644f37a04b" 22 | "6fd66b1d97cf80f1345f76c84f03ff30bb51bf308f2a9875c41e6592cd2a2f9e" 23 | "60809b17b5316037b69bb2fa5d4c8ac31edb3394046ec06bbdacc57da6a756c5") 24 | 25 | PMK_HASH = bytes.fromhex( 26 | "ba1a86037c1d3c71c3af344955bd69a9a9861d9e911fa24985b677e8dbd72d43") 27 | 28 | DEVICE_CONFIG = bytes.fromhex( 29 | "701160712c9d2cc91ce518fd00fd00fd03ba000180ca000400840015b3860000" 30 | "c4880000ba8a0000b28c0000aa8e0000c19000bbbb9200b1b1940000a8960000" 31 | "b6980000009a000000d2000000d4000000d6000000d800000050000105d00000" 32 | "00700000007200785674003412200010402a0102042200012024003200800001" 33 | "005c008000560004205800030232000c02660003007c000058820080152a0182" 34 | "032200012024001400800001005c000001560004205800030232000c02660003" 35 | "007c0000588200801f2a0108005c008000540010016200040364001900660003" 36 | "007c0001582a0108005c0000015200080054000001660003007c00015800892e") 37 | 38 | SENSOR_WIDTH = 80 39 | SENSOR_HEIGHT = 88 40 | 41 | 42 | def init_device(product: int): 43 | device = goodix.Device(product, protocol.USBProtocol) 44 | 45 | device.nop() 46 | device.enable_chip(True) 47 | device.nop() 48 | 49 | return device 50 | 51 | 52 | def check_psk(device: goodix.Device): 53 | success, flags, psk = device.preset_psk_read(0xbb020003) 54 | if not success: 55 | raise ValueError("Failed to read PSK") 56 | 57 | if flags != 0xbb020003: 58 | raise ValueError("Invalid flags") 59 | 60 | print(f"PSK: {psk.hex()}") 61 | return psk == PMK_HASH 62 | 63 | def write_psk(device: goodix.Device): 64 | if not device.preset_psk_write(0xbb010003, PSK_WHITE_BOX): 65 | return False 66 | 67 | if not check_psk(device): 68 | return False 69 | 70 | return True 71 | 72 | 73 | def erase_firmware(device: goodix.Device): 74 | device.mcu_erase_app(0, False) 75 | device.disconnect() 76 | 77 | 78 | def update_firmware(device: goodix.Device): 79 | firmware_file = open(f"firmware/51x0/{TARGET_FIRMWARE}.bin", "rb") 80 | firmware = firmware_file.read() 81 | firmware_file.close() 82 | 83 | try: 84 | length = len(firmware) 85 | for i in range(0, length, 1008): 86 | if not device.write_firmware(i, firmware[i:i + 1008]): 87 | raise ValueError("Failed to write firmware") 88 | 89 | if not device.check_firmware( 90 | 0, length, 91 | crcmod.predefined.mkCrcFun("crc-32-mpeg")(firmware)): 92 | raise ValueError("Failed to check firmware") 93 | 94 | except Exception as error: 95 | print( 96 | tool.warning( 97 | f"The program went into serious problems while trying to " 98 | f"update the firmware: {error}")) 99 | 100 | erase_firmware(device) 101 | 102 | raise error 103 | 104 | device.reset(False, True, 20) 105 | device.disconnect() 106 | 107 | 108 | def run_driver(device: goodix.Device): 109 | tls_server = subprocess.Popen([ 110 | "openssl", "s_server", "-nocert", "-psk", 111 | PSK.hex(), "-port", "4433", "-quiet" 112 | ], 113 | stdout=subprocess.PIPE, 114 | stderr=subprocess.STDOUT) 115 | 116 | try: 117 | success, number = device.reset(True, False, 20) 118 | if not success: 119 | raise ValueError("Reset failed") 120 | if number != 2048: 121 | raise ValueError("Invalid reset number") 122 | 123 | if device.read_sensor_register(0x0000, 4) != b"\xa2\x04\x25\x00": 124 | raise ValueError("Invalid chip ID") 125 | 126 | otp = device.read_otp() 127 | if len(otp) < 64: 128 | raise ValueError("Invalid OTP") 129 | 130 | # OTP 0 = 5332383733342e0032778aa2d495ca05 131 | # 5107050a7d0bfd274103110cf17f800c 132 | # 38813034a57f5ef406c4bd4201bdb7b9 133 | # b7b7b7b9b7b73230a55a5ea1850cfd71 134 | # OTP 1 = 5332423937332e000a777aa3452cec02 135 | # 510705027d4bd5274103d10cf18f700c 136 | # 38c13033a58f5ff407f48e71018eb6b7 137 | # b6b6b6b7b6b63450a55a5fa0c814d548 138 | 139 | # OTP[00] = CP_DATA[00] = 0x53 140 | # OTP[01] = CP_DATA[01] = 0x32 141 | # OTP[02] = CP_DATA[02] = 0x38 142 | # OTP[03] = CP_DATA[03] = 0x37 143 | # OTP[04] = CP_DATA[04] = 0x33 144 | # OTP[05] = CP_DATA[05] = 0x34 145 | # OTP[06] = CP_DATA[06] = 0x2e 146 | # OTP[07] = CP_DATA[07] = 0x00 147 | # OTP[08] = CP_DATA[08] = 0x32 148 | # OTP[09] = CP_DATA[09] = 0x77 149 | # OTP[10] = CP_DATA[10] = 0x8a 150 | # OTP[11] = FT_DATA[00] = 0xa2 151 | # OTP[12] = FT_DATA[01] = 0xd4 152 | # OTP[13] = FT_DATA[02] = 0x95 153 | # OTP[14] = FT_DATA[03] = 0xca 154 | # OTP[15] = FT_DATA[04] = 0x05 155 | # OTP[16] = FT_DATA[05] = 0x51 156 | # OTP[17] = FT_DATA[06] = 0x07 157 | # OTP[18] = FT_DATA[07] = 0x05 158 | # OTP[19] = FT_DATA[08] = 0x0a 159 | # OTP[20] = MT_DATA[00] = 0x7d 160 | # OTP[21] = MT_DATA[01] = 0x0b 161 | # OTP[22] = MT_DATA[02] = ~CRC_8_CHECKSUM(MT_DAC_DATA) & 0xff = 0xfd 162 | # OTP[23] = MT_DATA[03] = 0x27 163 | # OTP[24] = MT_DATA[04] = 0x41 164 | # OTP[25] = MT_DATA[05] = 0x03 165 | # OTP[26] = MT_DATA[06] = 0x11 166 | # OTP[27] = MT_DATA[07] = FDT_OFFSET = 0x0c 167 | # OTP[28] = FT_DATA[09] = 0xf1 168 | # OTP[29] = MT_DATA[08] = 0x7f 169 | # OTP[30] = MT_DATA[09] = 0x80 170 | # OTP[31] = MT_DATA[10] = 0x0c 171 | # OTP[32] = MT_DATA[11] = 0x38 172 | # OTP[33] = MT_DATA[12] = 0x81 173 | # OTP[34] = MT_DATA[13] = 0x30 174 | # OTP[35] = MT_DATA[14] = 0x34 175 | # OTP[36] = CP_DATA[11] = 0xa5 176 | # OTP[37] = CP_DATA[12] = 0x7f 177 | # OTP[38] = CP_DATA[13] = 0x5e 178 | # OTP[39] = CP_DATA[14] = 0xf4 179 | # OTP[40] = MT_DATA[15] = 0x06 180 | # OTP[41] = MT_DATA[16] = 0xc4 181 | # OTP[42] = MT_DATA[17] = TCODE << 4 | DELTA & 0Xf = 0xbd 182 | # OTP[43] = MT_DATA[18] = THRESHHOLD = 0x42 183 | # OTP[44] = MT_DATA[19] = 0x01 184 | # OTP[45] = MT_DATA[20] = 0xbd 185 | # OTP[46] = MT_DATA[21] = MT_DAC_DATA[0] = 0xb7 186 | # OTP[47] = MT_DATA[22] = MT_DAC_DATA[1] = 0xb9 187 | # OTP[48] = MT_DATA[23] = MT_DAC_DATA[2] = 0xb7 188 | # OTP[49] = MT_DATA[24] = MT_DAC_DATA[3] = 0xb7 189 | # OTP[50] = FT_DATA[10] = FT_DAC_DATA[0] = 0xb7 190 | # OTP[51] = FT_DATA[11] = FT_DAC_DATA[1] = 0xb9 191 | # OTP[52] = FT_DATA[12] = FT_DAC_DATA[2] = 0xb7 192 | # OTP[53] = FT_DATA[13] = FT_DAC_DATA[3] = 0xb7 193 | # OTP[54] = MT_DATA[25] = 0x32 194 | # OTP[55] = MT_DATA[26] = 0x30 195 | # OTP[56] = FT_DATA[14] = 0xa5 196 | # OTP[57] = FT_DATA[15] = 0x5a 197 | # OTP[58] = FT_DATA[16] = 0x5e 198 | # OTP[59] = FT_DATA[17] = 0xa1 199 | # OTP[60] = ~CRC_8_CHECKSUM(CP_DATA) & 0xff = 0x85 200 | # OTP[61] = ~CRC_8_CHECKSUM(FT_DATA) & 0xff = 0x0c 201 | # OTP[62] = FT_DATA[18] = ~CRC_8_CHECKSUM(FT_DAC_DATA) & 0xff = 0xfd 202 | # OTP[63] = ~CRC_8_CHECKSUM(MT_DATA) & 0xff = 0x71 203 | 204 | if ~crcmod.predefined.mkCrcFun("crc-8")(otp[0:11] + 205 | otp[36:40]) & 0xff != otp[60]: 206 | raise ValueError("Invalid OTP CP data checksum") 207 | 208 | if ~crcmod.predefined.mkCrcFun("crc-8")(otp[20:28] + otp[29:36] + 209 | otp[40:50] + 210 | otp[54:56]) & 0xff != otp[63]: 211 | raise ValueError("Invalid OTP MT data checksum") 212 | 213 | if ~crcmod.predefined.mkCrcFun("crc-8")(otp[11:20] + otp[28:29] + 214 | otp[50:54] + otp[56:60] + 215 | otp[62:63]) & 0xff != otp[61]: 216 | raise ValueError("Invalid OTP FT data checksum") 217 | 218 | if ~crcmod.predefined.mkCrcFun("crc-8")(otp[50:54]) & 0xff != otp[62]: 219 | raise ValueError("Invalid OTP DAC FT data checksum") 220 | 221 | if ~crcmod.predefined.mkCrcFun("crc-8")(otp[46:50]) & 0xff != otp[22]: 222 | raise ValueError("Invalid OTP DAC MT data checksum") 223 | 224 | if otp[50:54] != otp[46:50]: 225 | raise ValueError("Invalid OTP DAC data") 226 | 227 | if otp[42] == 0x00 or otp[42] != ~otp[43] & 0xff: 228 | if otp[43] == 0x00 or otp[43] != ~otp[43] & 0xff: 229 | if otp[42] == 0x00 or otp[43] != otp[42]: 230 | raise ValueError("Invalid OTP Tcode and threshold data") 231 | 232 | tcode = ((otp[42] >> 4) + 1) * 16 + 64 233 | delta = int(((otp[42] & 0xf) + 2) * 25600 / tcode / 3) >> 4 & 0xff 234 | 235 | if otp[27] != 0x00: 236 | if otp[27] & 3 == otp[27] >> 4 & 3: 237 | fdt_offset = otp[27] & 3 238 | elif otp[27] & 3 == otp[27] >> 2 & 3: 239 | fdt_offset = otp[27] & 3 240 | elif otp[27] >> 4 & 3 == otp[27] >> 2 & 3: 241 | fdt_offset = otp[27] >> 4 & 3 242 | else: 243 | fdt_offset = 0 244 | else: 245 | fdt_offset = 0 246 | 247 | success, number = device.reset(True, False, 20) 248 | if not success: 249 | raise ValueError("Reset failed") 250 | if number != 2048: 251 | raise ValueError("Invalid reset number") 252 | 253 | device.mcu_switch_to_idle_mode(20) 254 | 255 | device.write_sensor_register(0x0220, 256 | struct.pack("> 4) + 1) * 16 + 64 233 | delta = int(((otp[42] & 0xf) + 2) * 25600 / tcode / 3) >> 4 & 0xff 234 | 235 | if otp[27] != 0x00: 236 | if otp[27] & 3 == otp[27] >> 4 & 3: 237 | fdt_offset = otp[27] & 3 238 | elif otp[27] & 3 == otp[27] >> 2 & 3: 239 | fdt_offset = otp[27] & 3 240 | elif otp[27] >> 4 & 3 == otp[27] >> 2 & 3: 241 | fdt_offset = otp[27] >> 4 & 3 242 | else: 243 | fdt_offset = 0 244 | else: 245 | fdt_offset = 0 246 | 247 | success, number = device.reset(True, False, 20) 248 | if not success: 249 | raise ValueError("Reset failed") 250 | if number != 2048: 251 | raise ValueError("Invalid reset number") 252 | 253 | device.mcu_switch_to_idle_mode(20) 254 | 255 | device.write_sensor_register(0x0220, 256 | struct.pack("H", len(PSK)) + PSK) * 2 89 | pmk = hashlib.sha256(raw_pmk).digest() 90 | pmk_hmac = hmac.new(pmk, mod, hashlib.sha256).digest() 91 | firmware_hmac = hmac.new(pmk_hmac, firmware, hashlib.sha256).digest() 92 | 93 | try: 94 | length = len(firmware) 95 | for i in range(0, length, 256): 96 | if not device.write_firmware(i, firmware[i:i + 256], 2): 97 | raise ValueError("Failed to write firmware") 98 | 99 | if not device.check_firmware(None, None, None, firmware_hmac): 100 | raise ValueError("Failed to check firmware") 101 | 102 | except Exception as error: 103 | print( 104 | tool.warning( 105 | f"The program went into serious problems while trying to " 106 | f"update the firmware: {error}")) 107 | 108 | erase_firmware(device) 109 | 110 | raise error 111 | 112 | device.reset(False, True, 50) 113 | device.disconnect() 114 | 115 | 116 | def run_driver(device: goodix.Device): 117 | tls_server = subprocess.Popen([ 118 | "openssl", "s_server", "-nocert", "-psk", 119 | PSK.hex(), "-port", "4433", "-quiet" 120 | ], 121 | stdout=subprocess.PIPE, 122 | stderr=subprocess.STDOUT) 123 | 124 | try: 125 | if not device.reset(True, False, 20)[0]: 126 | raise ValueError("Reset failed") 127 | 128 | device.read_sensor_register(0x0000, 129 | 4) # Read chip ID (0x00a5 or 0x00a6) 130 | 131 | otp = device.read_otp() 132 | 133 | if len(otp) < 64: 134 | raise ValueError("Invalid OTP") 135 | 136 | # OTP 1: 4e4c4d31372e0000b9828da2a2d73e09 137 | # 08196896800000ee6014a774a060b614 138 | # ea2704009b0056f007212723a1a7a300 139 | # 00000000000000000000000083760000 140 | # OTP 2: 4e4b35594c2e00002983759520190009 141 | # 08274c96800000f0103cae6ea010593c 142 | # ea2f04009c0053f00729312ba8b0aa00 143 | # 000000000000000000000000f3830000 144 | 145 | tls_client = socket.socket() 146 | tls_client.connect(("localhost", 4433)) 147 | 148 | try: 149 | tool.connect_device(device, tls_client) 150 | 151 | if not device.upload_config_mcu(DEVICE_CONFIG): 152 | raise ValueError("Failed to upload config") 153 | 154 | device.set_drv_state() 155 | 156 | device.mcu_get_pov_image() 157 | 158 | device.mcu_switch_to_fdt_mode( 159 | b"\x0d\x01\x27\x01\x21\x01\x27\x01" 160 | b"\x23\x01\x00\x00\x00\x00\x00\x00" 161 | b"\x00\x00\x00\x00\x00\x00\x00\x00" 162 | b"\x00\x00\x00", False) 163 | device.mcu_switch_to_fdt_mode( 164 | b"\x0d\x01\x27\x01\x21\x01\x27\x01" 165 | b"\x23\x01\x00\x00\x00\x00\x00\x00" 166 | b"\x00\x00\x00\x00\x00\x00\x00\x00" 167 | b"\x00\x00\x01", True) 168 | 169 | device.write_sensor_register(0x022c, b"\x0a\x03") 170 | 171 | tls_client.sendall( 172 | device.mcu_get_image( 173 | b"\x01\x03\x27\x01\x21\x01\x27\x01\x23\x01", 174 | goodix.FLAGS_TRANSPORT_LAYER_SECURITY_DATA)[9:]) 175 | 176 | tool.write_pgm( 177 | tool.decode_image(tls_server.stdout.read(7684)[:-4]), 178 | SENSOR_WIDTH, SENSOR_HEIGHT, "clear-0.pgm") 179 | 180 | device.write_sensor_register(0x022c, b"\x0a\x02") 181 | 182 | device.write_sensor_register(0x022c, b"\x0a\x03") 183 | 184 | tls_client.sendall( 185 | device.mcu_get_image( 186 | b"\x81\x03\x27\x01\x21\x01\x27\x01\x23\x01", 187 | goodix.FLAGS_TRANSPORT_LAYER_SECURITY_DATA)[9:]) 188 | 189 | tool.write_pgm( 190 | tool.decode_image(tls_server.stdout.read(7684)[:-4]), 191 | SENSOR_WIDTH, SENSOR_HEIGHT, "clear-1.pgm") 192 | 193 | device.write_sensor_register(0x022c, b"\x0a\x02") 194 | 195 | device.write_sensor_register(0x022c, b"\x0a\x03") 196 | 197 | tls_client.sendall( 198 | device.mcu_get_image( 199 | b"\x81\x03\x18\x01\x12\x01\x18\x01\x14\x01", 200 | goodix.FLAGS_TRANSPORT_LAYER_SECURITY_DATA)[9:]) 201 | 202 | tool.write_pgm( 203 | tool.decode_image(tls_server.stdout.read(7684)[:-4]), 204 | SENSOR_WIDTH, SENSOR_HEIGHT, "clear-2.pgm") 205 | 206 | device.write_sensor_register(0x022c, b"\x0a\x02") 207 | 208 | device.mcu_switch_to_fdt_mode( 209 | b"\x8d\x01\x27\x01\x21\x01\x27\x01" 210 | b"\x23\x01\x00\x00\x00\x00\x00\x00" 211 | b"\x00\x00\x00\x00\x00\x00\x00\x00" 212 | b"\x00\x00\x00", False) 213 | device.mcu_switch_to_fdt_mode( 214 | b"\x8d\x01\x27\x01\x21\x01\x27\x01" 215 | b"\x23\x01\x00\x00\x00\x00\x00\x00" 216 | b"\x00\x00\x00\x00\x00\x00\x00\x00" 217 | b"\x00\x00\x01", True) 218 | 219 | device.write_sensor_register(0x022c, b"\x0a\x03") 220 | 221 | tls_client.sendall( 222 | device.mcu_get_image( 223 | b"\x81\x03\x27\x01\x21\x01\x27\x01\x23\x01", 224 | goodix.FLAGS_TRANSPORT_LAYER_SECURITY_DATA)[9:]) 225 | 226 | tool.write_pgm( 227 | tool.decode_image(tls_server.stdout.read(7684)[:-4]), 228 | SENSOR_WIDTH, SENSOR_HEIGHT, "clear-3.pgm") 229 | 230 | device.write_sensor_register(0x022c, b"\x0a\x02") 231 | 232 | device.mcu_switch_to_fdt_mode( 233 | b"\x0d\x01\x27\x01\x21\x01\x27\x01" 234 | b"\x23\x01\x00\x00\x00\x00\x00\x00" 235 | b"\x00\x00\x00\x00\x00\x00\x00\x00" 236 | b"\x00\x00\x00", False) 237 | device.mcu_switch_to_fdt_mode( 238 | b"\x0d\x01\x27\x01\x21\x01\x27\x01" 239 | b"\x23\x01\x00\x00\x00\x00\x00\x00" 240 | b"\x00\x00\x00\x00\x00\x00\x00\x00" 241 | b"\x00\x00\x01", True) 242 | 243 | device.set_pov_config(DEVICE_POV_CONFIG) 244 | 245 | device.mcu_switch_to_sleep_mode() 246 | 247 | device.query_mcu_state(b"\x01\x01\x01", False) 248 | 249 | device.mcu_switch_to_fdt_down( 250 | b"\x9c\x01\x27\x01\x21\x01\x27\x01" 251 | b"\x23\x01\x8d\x8d\x86\x86\x97\x97" 252 | b"\x8f\x8f\x9b\x9b\x92\x92\x96\x96" 253 | b"\x8c\x8c\x00\x00\x05\x03\xa7\x00" 254 | b"\xa1\x00\xa7\x00\xa3\x00\x00", False) 255 | 256 | device.mcu_switch_to_fdt_down( 257 | b"\x9c\x01\x27\x01\x21\x01\x27\x01" 258 | b"\x23\x01\x8d\x8d\x86\x86\x97\x97" 259 | b"\x8f\x8f\x9b\x9b\x92\x92\x96\x96" 260 | b"\x8c\x8c\x01\x00\x05\x03\xa7\x00" 261 | b"\xa1\x00\xa7\x00\xa3\x00\x00", False) 262 | 263 | device.mcu_switch_to_sleep_mode() 264 | 265 | device.query_mcu_state(b"\x00\x00\x00", False) 266 | 267 | device.query_mcu_state(b"\x01\x01\x01", False) 268 | 269 | device.mcu_switch_to_fdt_down( 270 | b"\x9c\x01\x27\x01\x21\x01\x27\x01" 271 | b"\x23\x01\x8d\x8d\x86\x86\x97\x97" 272 | b"\x8f\x8f\x9b\x9b\x92\x92\x96\x96" 273 | b"\x8c\x8c\x00\x00\x05\x03\xa7\x00" 274 | b"\xa1\x00\xa7\x00\xa3\x00\x00", False) 275 | 276 | print("Waiting for finger...") 277 | 278 | device.mcu_switch_to_fdt_down( 279 | b"\x9c\x01\x27\x01\x21\x01\x27\x01" 280 | b"\x23\x01\x8d\x8d\x86\x86\x97\x97" 281 | b"\x8f\x8f\x9b\x9b\x92\x92\x96\x96" 282 | b"\x8c\x8c\x01\x00\x05\x03\xa7\x00" 283 | b"\xa1\x00\xa7\x00\xa3\x00\x00", True) 284 | 285 | device.mcu_switch_to_fdt_mode( 286 | b"\x0d\x01\x27\x01\x21\x01\x27\x01" 287 | b"\x23\x01\x8d\x8d\x86\x86\x97\x97" 288 | b"\x8f\x8f\x9b\x9b\x92\x92\x96\x96" 289 | b"\x8c\x8c\x00", False) 290 | 291 | device.mcu_switch_to_fdt_mode( 292 | b"\x0d\x01\x27\x01\x21\x01\x27\x01" 293 | b"\x23\x01\x8d\x8d\x86\x86\x97\x97" 294 | b"\x8f\x8f\x9b\x9b\x92\x92\x96\x96" 295 | b"\x8c\x8c\x01", True) 296 | 297 | device.write_sensor_register(0x022c, b"\x05\x03") 298 | 299 | tls_client.sendall( 300 | device.mcu_get_image( 301 | b"\x45\x03\xa7\x00\xa1\x00\xa7\x00\xa3\x00", 302 | goodix.FLAGS_TRANSPORT_LAYER_SECURITY_DATA)[9:]) 303 | 304 | tool.write_pgm( 305 | tool.decode_image(tls_server.stdout.read(7684)[:-4]), 306 | SENSOR_WIDTH, SENSOR_HEIGHT, "fingerprint.pgm") 307 | 308 | finally: 309 | tls_client.close() 310 | finally: 311 | tls_server.terminate() 312 | 313 | 314 | def main(product: int): 315 | print( 316 | tool.warning( 317 | "This program might break your device.\n" 318 | "Consider that it may flash the device firmware.\n" 319 | "Continue at your own risk.\n" 320 | "But don't hold us responsible if your device is broken!\n" 321 | "Don't run this program as part of a regular process.")) 322 | 323 | code = random.randint(0, 9999) 324 | 325 | if input(f"Type {code} to continue and confirm that you are not a bot: " 326 | ) != str(code): 327 | print("Abort") 328 | return 329 | 330 | previous_firmware = None 331 | 332 | device = init_device(product) 333 | 334 | while True: 335 | firmware = device.firmware_version() 336 | print(f"Firmware: {firmware}") 337 | 338 | valid_psk = check_psk(device) 339 | print(f"Valid PSK: {valid_psk}") 340 | 341 | if firmware == previous_firmware: 342 | raise ValueError("Unchanged firmware") 343 | 344 | previous_firmware = firmware 345 | 346 | if re.fullmatch(TARGET_FIRMWARE, firmware): 347 | if not valid_psk: 348 | erase_firmware(device) 349 | continue 350 | 351 | run_driver(device) 352 | return 353 | 354 | if re.fullmatch(VALID_FIRMWARE, firmware): 355 | erase_firmware(device) 356 | continue 357 | 358 | if re.fullmatch(IAP_FIRMWARE, firmware): 359 | if not valid_psk: 360 | if not write_psk(device): 361 | raise ValueError("Failed to write PSK") 362 | 363 | update_firmware(device) 364 | 365 | device = init_device(product) 366 | 367 | continue 368 | 369 | raise ValueError( 370 | "Invalid firmware\n" + 371 | tool.warning("Please consider that removing this security " 372 | "is a very bad idea!")) 373 | -------------------------------------------------------------------------------- /driver_53x5.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | import logging 3 | import os 4 | import re 5 | import time 6 | 7 | import Crypto.Hash.SHA256 8 | 9 | import protocol 10 | import tool 11 | import wrapless 12 | 13 | VALID_FIRMWARE = r"GF5288_HTSEC_APP_100(11|20)" 14 | 15 | PSK = bytes.fromhex( 16 | "0000000000000000000000000000000000000000000000000000000000000000") 17 | 18 | PSK_WHITE_BOX = bytes.fromhex( 19 | "ec35ae3abb45ed3f12c4751f1e5c2cc05b3c5452e9104d9f2a3118644f37a04b" 20 | "6fd66b1d97cf80f1345f76c84f03ff30bb51bf308f2a9875c41e6592cd2a2f9e" 21 | "60809b17b5316037b69bb2fa5d4c8ac31edb3394046ec06bbdacc57da6a756c5") 22 | 23 | SENSOR_WIDTH = 108 24 | SENSOR_HEIGHT = 88 25 | 26 | 27 | def is_valid_psk(device: wrapless.Device): 28 | psk_hash = device.read_psk_hash() 29 | return psk_hash == Crypto.Hash.SHA256.SHA256Hash(PSK).digest() 30 | 31 | 32 | def write_psk(device: wrapless.Device): 33 | print(f"Writing white-box all-zero PSK") 34 | device.write_psk_white_box(PSK_WHITE_BOX) 35 | 36 | if not is_valid_psk(device): 37 | raise Exception("Could not set all-zero PSK") 38 | 39 | 40 | def device_enable(device: wrapless.Device): 41 | device.reset(0, False) 42 | time.sleep(0.01) 43 | 44 | reg_data = device.read_data(0, 4, 0.2) 45 | chip_id = wrapless.decode_u32(reg_data) 46 | if chip_id >> 8 == 0x220C: 47 | sensor_type = 9 48 | else: 49 | raise Exception(f"Unsupported chip ID: {chip_id}") 50 | 51 | print(f"Chip ID: {chip_id:#x}") 52 | print(f"Sensor type: {sensor_type}") 53 | 54 | 55 | OTP_HASH_TABLE = bytes.fromhex( 56 | "00 07 0e 09 1c 1b 12 15 38 3f 36 31 24 23 2a 2d" 57 | "70 77 7e 79 6c 6b 62 65 48 4f 46 41 54 53 5a 5d" 58 | "e0 e7 ee e9 fc fb f2 f5 d8 df d6 d1 c4 c3 ca cd" 59 | "90 97 9e 99 8c 8b 82 85 a8 af a6 a1 b4 b3 ba bd" 60 | "c7 c0 c9 ce db dc d5 d2 ff f8 f1 f6 e3 e4 ed ea" 61 | "b7 b0 b9 be ab ac a5 a2 8f 88 81 86 93 94 9d 9a" 62 | "27 20 29 2e 3b 3c 35 32 1f 18 11 16 03 04 0d 0a" 63 | "57 50 59 5e 4b 4c 45 42 6f 68 61 66 73 74 7d 7a" 64 | "89 8e 87 80 95 92 9b 9c b1 b6 bf b8 ad aa a3 a4" 65 | "f9 fe f7 f0 e5 e2 eb ec c1 c6 cf c8 dd da d3 d4" 66 | "69 6e 67 60 75 72 7b 7c 51 56 5f 58 4d 4a 43 44" 67 | "19 1e 17 10 05 02 0b 0c 21 26 2f 28 3d 3a 33 34" 68 | "4e 49 40 47 52 55 5c 5b 76 71 78 7f 6a 6d 64 63" 69 | "3e 39 30 37 22 25 2c 2b 06 01 08 0f 1a 1d 14 13" 70 | "ae a9 a0 a7 b2 b5 bc bb 96 91 98 9f 8a 8d 84 83" 71 | "de d9 d0 d7 c2 c5 cc cb e6 e1 e8 ef fa fd f4 f3") 72 | 73 | 74 | def compute_otp_hash(data): 75 | checksum = 0 76 | for byte in data: 77 | checksum = OTP_HASH_TABLE[checksum ^ byte] 78 | return ~checksum & 0xFF 79 | 80 | 81 | def verify_otp_hash(otp): 82 | data = otp[:25] + otp[26:] 83 | received_hash = otp[25] 84 | computed_hash = compute_otp_hash(data) 85 | 86 | if received_hash == computed_hash: 87 | print("Valid OTP") 88 | else: 89 | raise Exception( 90 | f"OTP hash incorrect: {received_hash} != {computed_hash}") 91 | 92 | 93 | FDT_BASE_LEN = 24 94 | 95 | 96 | @dataclasses.dataclass 97 | class CalibrationParams: 98 | tcode: int 99 | 100 | delta_fdt: int 101 | delta_down: int 102 | delta_up: int 103 | delta_img: int 104 | delta_nav: int 105 | 106 | dac_h: int 107 | dac_l: int 108 | 109 | dac_delta: int 110 | 111 | fdt_base_down: bytes 112 | fdt_base_up: bytes 113 | fdt_base_manual: bytes 114 | 115 | calib_image: list[int] | None 116 | 117 | def update_fdt_bases(self, fdt_base: bytes): 118 | assert len(fdt_base) == FDT_BASE_LEN 119 | self.fdt_base_down = fdt_base[:] 120 | self.fdt_base_up = fdt_base[:] 121 | self.fdt_base_manual = fdt_base[:] 122 | 123 | 124 | def check_sensor(device: wrapless.Device): 125 | otp = device.read_otp(0.2) 126 | print(f"OTP: {otp.hex(' ')}") 127 | 128 | verify_otp_hash(otp) 129 | 130 | diff = otp[17] >> 1 & 0x1F 131 | print(f"[0x11]:{otp[0x11]:#x}, diff[5:1]={diff:#x}") 132 | 133 | tcode = otp[23] + 1 if otp[23] != 0 else 0 134 | 135 | if diff == 0: 136 | delta_fdt = 0 # uninit? 137 | delta_down = 0xD 138 | delta_up = 0xB 139 | delta_img = 0xC8 140 | delta_nav = 0x28 141 | else: 142 | tmp = diff + 5 143 | tmp2 = (tmp * 0x32) >> 4 144 | 145 | delta_fdt = tmp2 // 5 146 | delta_down = tmp2 // 3 147 | delta_up = delta_down - 2 148 | delta_img = 0xC8 149 | delta_nav = tmp * 4 150 | 151 | if otp[17] == 0 or otp[22] == 0 or otp[31] == 0: 152 | dac_h = 0x97 153 | dac_l = 0xD0 154 | else: 155 | # dac_h = otp[17][0] | otp[22] 156 | dac_h = (otp[17] << 8 ^ otp[22]) & 0x1FF 157 | # dac_l = otp[17][6] | otp[31] 158 | dac_l = (otp[17] & 0x40) << 2 ^ otp[31] 159 | 160 | print(f"tcode:{hex(tcode)} delta down:{hex(delta_down)} " 161 | f"delta up:{hex(delta_up)} delta img:{hex(delta_img)} " 162 | f"delta nav:{hex(delta_nav)} dac_h:{hex(dac_h)} dac_l:{hex(dac_l)}") 163 | 164 | dac_delta = 0xC83 // tcode 165 | print(f"sensor broken dac_delta={dac_delta}") 166 | 167 | fdt_base = b"\x00" * FDT_BASE_LEN 168 | return CalibrationParams( 169 | tcode, 170 | delta_fdt, 171 | delta_down, 172 | delta_up, 173 | delta_img, 174 | delta_nav, 175 | dac_h, 176 | dac_l, 177 | dac_delta, 178 | fdt_base[:], 179 | fdt_base[:], 180 | fdt_base[:], 181 | None, 182 | ) 183 | 184 | 185 | DEFAULT_CONFIG = bytes.fromhex( 186 | "40 11 6c 7d 28 a5 28 cd 1c e9 10 f9 00 f9 00 f9" 187 | "00 04 02 00 00 08 00 11 11 ba 00 01 80 ca 00 07" 188 | "00 84 00 be b2 86 00 c5 b9 88 00 b5 ad 8a 00 9d" 189 | "95 8c 00 00 be 8e 00 00 c5 90 00 00 b5 92 00 00" 190 | "9d 94 00 00 af 96 00 00 bf 98 00 00 b6 9a 00 00" 191 | "a7 30 00 6c 1c 50 00 01 05 d0 00 00 00 70 00 00" 192 | "00 72 00 78 56 74 00 34 12 26 00 00 12 20 00 10" 193 | "40 12 00 03 04 02 02 16 21 2c 02 0a 03 2a 01 02" 194 | "00 22 00 01 20 24 00 32 00 80 00 05 04 5c 00 00" 195 | "01 56 00 28 20 58 00 01 00 32 00 24 02 82 00 80" 196 | "0c 20 02 88 0d 2a 01 92 07 22 00 01 20 24 00 14" 197 | "00 80 00 05 04 5c 00 00 01 56 00 08 20 58 00 03" 198 | "00 32 00 08 04 82 00 80 0c 20 02 88 0d 2a 01 18" 199 | "04 5c 00 80 00 54 00 00 01 62 00 09 03 64 00 18" 200 | "00 82 00 80 0c 20 02 88 0d 2a 01 18 04 5c 00 80" 201 | "00 52 00 08 00 54 00 00 01 00 00 00 00 00 61 4f") 202 | 203 | 204 | def fix_config_checksum(config): 205 | checksum = 0xA5A5 206 | for short_idx in range(0, len(config) - 2, 2): 207 | short = int.from_bytes(config[short_idx:short_idx + 2], 208 | byteorder="little") 209 | checksum += short 210 | checksum &= 0xFFFF 211 | checksum = 0x10000 - checksum 212 | checksum_bytes = checksum.to_bytes(length=2, byteorder="little") 213 | 214 | config[-2] = checksum_bytes[0] 215 | config[-1] = checksum_bytes[1] 216 | 217 | 218 | TCODE_TAG = 0x5C 219 | DAC_L_TAG = 0x220 220 | DELTA_DOWN_TAG = 0x82 221 | 222 | 223 | def replace_value_in_section(config, section_num, tag, value): 224 | value_bytes = int.to_bytes(value, length=2, byteorder="little") 225 | 226 | section_table = config[1:0x11] 227 | section_base = section_table[section_num * 2] 228 | section_size = section_table[section_num * 2 + 1] 229 | 230 | for entry_base in range(section_base, section_base + section_size, 4): 231 | entry_tag = int.from_bytes(config[entry_base:entry_base + 2], 232 | byteorder="little") 233 | if entry_tag == tag: 234 | config[entry_base + 2] = value_bytes[0] 235 | config[entry_base + 3] = value_bytes[1] 236 | 237 | 238 | def upload_config(device: wrapless.Device, calib_params: CalibrationParams): 239 | chip_config = bytearray(DEFAULT_CONFIG) 240 | replace_value_in_section(chip_config, 2, TCODE_TAG, calib_params.tcode) 241 | replace_value_in_section(chip_config, 3, TCODE_TAG, calib_params.tcode) 242 | replace_value_in_section(chip_config, 4, TCODE_TAG, calib_params.tcode) 243 | replace_value_in_section(chip_config, 2, DAC_L_TAG, 244 | calib_params.dac_l << 4 | 8) 245 | replace_value_in_section(chip_config, 3, DAC_L_TAG, 246 | calib_params.dac_l << 4 | 8) 247 | replace_value_in_section(chip_config, 2, DELTA_DOWN_TAG, 248 | calib_params.delta_down << 8 | 0x80) 249 | fix_config_checksum(chip_config) 250 | 251 | device.upload_config(chip_config, 0.5) 252 | 253 | 254 | def get_fdt_base_with_tx(device: wrapless.Device, tx_enable: bool, 255 | calib_params: CalibrationParams): 256 | op_code = 0xD 257 | if not tx_enable: 258 | op_code |= 0x80 259 | 260 | payload = op_code.to_bytes(length=1, byteorder="little") 261 | payload += calib_params.fdt_base_manual 262 | fdt_base = device.execute_fdt_operation( 263 | wrapless.FingerDetectionOperation.MANUAL, payload, 0.5) 264 | assert fdt_base is not None 265 | return fdt_base 266 | 267 | 268 | def get_adjusted_dac(sensor_image: list[int], calib_image: list[int], 269 | dac: int): 270 | raise NotImplementedError 271 | 272 | 273 | HV_VALUE = 6 274 | 275 | 276 | def get_image( 277 | device: wrapless.Device, 278 | tx_enable: bool, 279 | hv_enable: bool, 280 | use_dac: str, 281 | adjust_dac: bool, 282 | is_finger: bool, 283 | calib_params: CalibrationParams, 284 | ): 285 | if tx_enable: 286 | op_code = 0x1 287 | else: 288 | op_code = 0x81 289 | 290 | if is_finger: 291 | op_code |= 0x40 292 | 293 | if hv_enable: 294 | hv_value = HV_VALUE 295 | else: 296 | hv_value = 0x10 297 | 298 | if use_dac == "h": 299 | dac = calib_params.dac_h 300 | elif use_dac == "l": 301 | dac = calib_params.dac_l 302 | else: 303 | raise Exception("Invalid DAC type") 304 | 305 | request = op_code.to_bytes(length=1, byteorder="little") 306 | request += hv_value.to_bytes(length=1, byteorder="little") 307 | request += dac.to_bytes(length=2, byteorder="little") 308 | image = tool.decode_image(device.get_image(request, 0.5)) 309 | 310 | if adjust_dac: 311 | assert calib_params.calib_image is not None 312 | adjusted_dac = get_adjusted_dac(image, calib_params.calib_image, dac) 313 | if use_dac == "h": 314 | calib_params.dac_h = adjusted_dac 315 | elif use_dac == "l": 316 | calib_params.dac_l = adjusted_dac 317 | else: 318 | raise Exception("Invalid DAC type") 319 | 320 | return image 321 | 322 | 323 | def is_fdt_base_valid(fdt_data_1: bytes, fdt_data_2: bytes, max_delta: int): 324 | assert len(fdt_data_1) == len(fdt_data_2) 325 | logging.debug(f"Checking FDT data, max delta: {max_delta}") 326 | for idx in range(0, len(fdt_data_1), 2): 327 | fdt_val_1 = int.from_bytes(fdt_data_1[idx:idx + 2], byteorder="little") 328 | fdt_val_2 = int.from_bytes(fdt_data_2[idx:idx + 2], byteorder="little") 329 | 330 | delta = abs((fdt_val_2 >> 1) - (fdt_val_1 >> 1)) 331 | if delta > max_delta: 332 | return False 333 | return True 334 | 335 | 336 | def validate_base_img(base_image_1: list[int], base_image_2: list[int], 337 | image_threshold: int): 338 | assert len(base_image_1) == SENSOR_WIDTH * SENSOR_HEIGHT 339 | assert len(base_image_2) == SENSOR_WIDTH * SENSOR_HEIGHT 340 | 341 | diff_sum = 0 342 | for row_idx in range(2, SENSOR_HEIGHT - 2): 343 | for col_idx in range(2, SENSOR_WIDTH - 2): 344 | offset = row_idx * SENSOR_WIDTH + col_idx 345 | image_val_1 = base_image_1[offset] 346 | image_val_2 = base_image_2[offset] 347 | diff_sum += abs(image_val_2 - image_val_1) 348 | 349 | avg = diff_sum / ((SENSOR_HEIGHT - 4) * (SENSOR_WIDTH - 4)) 350 | logging.debug( 351 | f"Checking image data, avg: {avg:.2f}, threshold: {image_threshold}") 352 | if avg > image_threshold: 353 | raise Exception("Invalid base image") 354 | 355 | 356 | def generate_fdt_base(fdt_data: bytes): 357 | fdt_base = b"" 358 | for idx in range(0, len(fdt_data), 2): 359 | fdt_val = int.from_bytes(fdt_data[idx:idx + 2], byteorder="little") 360 | fdt_base_val = (fdt_val & 0xFFFE) * 0x80 | fdt_val >> 1 361 | fdt_base += fdt_base_val.to_bytes(length=2, byteorder="little") 362 | return fdt_base 363 | 364 | 365 | def update_all_base(device: wrapless.Device, calib_params: CalibrationParams): 366 | upload_config(device, calib_params) 367 | 368 | fdt_data_tx_enabled = get_fdt_base_with_tx(device, True, calib_params) 369 | 370 | image_tx_enabled = get_image(device, True, True, "l", False, False, 371 | calib_params) 372 | 373 | fdt_data_tx_disabled = get_fdt_base_with_tx(device, False, calib_params) 374 | 375 | fdt_base_valid = is_fdt_base_valid(fdt_data_tx_enabled, 376 | fdt_data_tx_disabled, 377 | calib_params.delta_fdt) 378 | if not fdt_base_valid: 379 | raise Exception("Invalid FDT") 380 | 381 | image_tx_disabled = get_image(device, False, True, "l", False, False, 382 | calib_params) 383 | 384 | validate_base_img(image_tx_enabled, image_tx_disabled, 385 | calib_params.delta_img) 386 | 387 | fdt_data_tx_enabled_2 = get_fdt_base_with_tx(device, True, calib_params) 388 | 389 | fdt_base_valid = is_fdt_base_valid(fdt_data_tx_enabled_2, 390 | fdt_data_tx_disabled, 391 | calib_params.delta_fdt) 392 | if not fdt_base_valid: 393 | raise Exception("Invalid FDT") 394 | 395 | calib_params.update_fdt_bases(generate_fdt_base(fdt_data_tx_enabled)) 396 | calib_params.calib_image = image_tx_enabled 397 | 398 | print(f"FDT manual base: {calib_params.fdt_base_manual.hex(' ', 2)}") 399 | print("Decoding and saving calibration image") 400 | tool.write_pgm(calib_params.calib_image, SENSOR_HEIGHT, SENSOR_WIDTH, 401 | "clear.pgm") 402 | 403 | 404 | def device_init(device: wrapless.Device): 405 | device.ping() 406 | 407 | firmware_version = device.read_firmware_version() 408 | print(f"Firmware version: {firmware_version}") 409 | if re.fullmatch(VALID_FIRMWARE, firmware_version) is None: 410 | raise Exception("Chip does not have a valid firmware") 411 | 412 | device_enable(device) 413 | 414 | print("Checking sensor") 415 | calib_params = check_sensor(device) 416 | print("Sensor check successful") 417 | 418 | print("Checking PSK hash") 419 | if not is_valid_psk(device): 420 | print("Updating PSK") 421 | write_psk(device) 422 | print("All-zero PSK set up") 423 | 424 | print("Establishing GTLS connection") 425 | device.establish_gtls_connection(PSK) 426 | print("Connection successfully established") 427 | 428 | print("Updating all base") 429 | update_all_base(device, calib_params) 430 | print("Update completed") 431 | 432 | print("Set to sleep mode") 433 | device.set_sleep_mode(0.2) 434 | 435 | return calib_params 436 | 437 | 438 | def generate_fdt_up_base(fdt_data, touch_flag, 439 | calib_params: CalibrationParams): 440 | fdt_vals = [] 441 | for idx in range(0, len(fdt_data), 2): 442 | fdt_val = int.from_bytes(fdt_data[idx:idx + 2], byteorder="little") 443 | fdt_vals.append(fdt_val) 444 | 445 | fdt_base_up_vals = [] 446 | for fdt_val in fdt_vals: 447 | val = (fdt_val >> 1) + calib_params.delta_down 448 | fdt_base_up_vals.append(val * 0x100 | val) 449 | 450 | for idx in range(0xC): 451 | if ((touch_flag >> idx) & 1) == 0: 452 | fdt_base_up_vals[idx] = (calib_params.delta_up * 0x100 453 | | calib_params.delta_up) 454 | 455 | fdt_base_up = b"" 456 | for fdt_val in fdt_base_up_vals: 457 | fdt_base_up += fdt_val.to_bytes(2, "little") 458 | 459 | return fdt_base_up 460 | 461 | 462 | def wait_for_finger_down(device: wrapless.Device, 463 | calib_params: CalibrationParams): 464 | fdt_data, touch_flag = device.wait_for_fdt_event( 465 | wrapless.FingerDetectionOperation.DOWN) 466 | calib_params.fdt_base_up = generate_fdt_up_base(fdt_data, touch_flag, 467 | calib_params) 468 | return fdt_data 469 | 470 | 471 | def wait_for_finger_up(device: wrapless.Device, 472 | calib_params: CalibrationParams): 473 | fdt_data, _ = device.wait_for_fdt_event( 474 | wrapless.FingerDetectionOperation.UP) 475 | calib_params.fdt_base_down = generate_fdt_base(fdt_data) 476 | return fdt_data 477 | 478 | 479 | def main(product: int): 480 | if "DEBUG" in os.environ: 481 | logging.basicConfig(level=logging.DEBUG) 482 | 483 | device = wrapless.Device(product, protocol.USBProtocol) 484 | calib_params = device_init(device) 485 | 486 | print("Powering on sensor") 487 | device.ec_control("on", 0.2) 488 | 489 | print("Setting up finger down detection") 490 | device.execute_fdt_operation(wrapless.FingerDetectionOperation.DOWN, 491 | calib_params.fdt_base_down, 0.5) 492 | 493 | print("Waiting for finger down") 494 | event_fdt_data = wait_for_finger_down(device, calib_params) 495 | 496 | manual_fdt_data = get_fdt_base_with_tx(device, False, calib_params) 497 | fdt_base_valid = is_fdt_base_valid(event_fdt_data, manual_fdt_data, 498 | calib_params.delta_fdt) 499 | if fdt_base_valid: 500 | raise Exception("Temperature event") 501 | 502 | print("Reading finger image") 503 | # TODO: DAC dynamic adjustment should be True 504 | finger_image = get_image(device, True, True, "h", False, True, 505 | calib_params) 506 | tool.write_pgm(finger_image, SENSOR_HEIGHT, SENSOR_WIDTH, "raw_finger.pgm") 507 | 508 | print("Setting up finger up detection") 509 | device.execute_fdt_operation(wrapless.FingerDetectionOperation.UP, 510 | calib_params.fdt_base_up, 0.5) 511 | 512 | print("Waiting for finger up") 513 | event_fdt_data = wait_for_finger_up(device, calib_params) 514 | 515 | print("Set to sleep mode") 516 | device.set_sleep_mode(0.2) 517 | 518 | print("Powering off sensor") 519 | time.sleep(0.5) 520 | device.ec_control("off", 0.2) 521 | 522 | print("Done") 523 | -------------------------------------------------------------------------------- /driver_53xd.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import hmac 3 | import random 4 | import re 5 | import socket 6 | import struct 7 | import subprocess 8 | 9 | import goodix 10 | import protocol 11 | import tool 12 | 13 | TARGET_FIRMWARE = "GF5298_GM168SEC_APP_13016" 14 | IAP_FIRMWARE = "MILAN_GM168SEC_IAP_10007" 15 | VALID_FIRMWARE = "GF5298_GM168SEC_APP_130[0-9]{2}" 16 | 17 | PSK = bytes.fromhex( 18 | "0000000000000000000000000000000000000000000000000000000000000000") 19 | 20 | PSK_WHITE_BOX = bytes.fromhex( 21 | "ec35ae3abb45ed3f12c4751f1e5c2cc05b3c5452e9104d9f2a3118644f37a04b" 22 | "6fd66b1d97cf80f1345f76c84f03ff30bb51bf308f2a9875c41e6592cd2a2f9e" 23 | "60809b17b5316037b69bb2fa5d4c8ac31edb3394046ec06bbdacc57da6a756c5") 24 | 25 | PMK_HASH = bytes.fromhex( 26 | "66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925") 27 | 28 | DEVICE_CONFIG = bytes.fromhex( 29 | "701160712c9d2cc91ce518fd00fd00fd03ba000180ca0008008400bec38600b1" 30 | "b68800baba8a00b3b38c00bcbc8e00b1b19000bbbb9200b1b194000000960000" 31 | "00980000009a000000d2000000d4000000d6000000d800000050000105d00000" 32 | "00700000007200785674003412200010402a0102042200012024003200800001" 33 | "005c000101560024205800010232000402660000027c00005882007f082a0182" 34 | "072200012024001400800001405c00e700560006145800040232000c02660000" 35 | "027c000058820080082a0108005c000101540000016200080464001000660000" 36 | "027c0000582a0108005c00dc005200080054000001660000027c00005820c51d") 37 | 38 | SENSOR_WIDTH = 80 39 | SENSOR_HEIGHT = 64 40 | 41 | 42 | def init_device(product: int): 43 | device = goodix.Device(product, protocol.USBProtocol) 44 | 45 | device.nop() 46 | 47 | return device 48 | 49 | 50 | def check_psk(device: goodix.Device): 51 | reply = device.preset_psk_read(0xbb020001, len(PMK_HASH), 0) 52 | if not reply[0]: 53 | raise ValueError("Failed to read PSK") 54 | 55 | if reply[1] != 0xbb020001: 56 | raise ValueError("Invalid flags") 57 | 58 | return reply[2] == PMK_HASH 59 | 60 | 61 | def write_psk(device: goodix.Device): 62 | if not device.preset_psk_write(0xbb010003, PSK_WHITE_BOX, 114, 0, 63 | bytes.fromhex("56a5bb956b7c8d9e0000")): 64 | return False 65 | 66 | if not check_psk(device): 67 | return False 68 | 69 | return True 70 | 71 | 72 | def erase_firmware(device: goodix.Device): 73 | device.mcu_erase_app(50, True) 74 | 75 | 76 | def update_firmware(device: goodix.Device): 77 | firmware_file = open(f"firmware/53xd/{TARGET_FIRMWARE}.bin", "rb") 78 | firmware = firmware_file.read() 79 | firmware_file.close() 80 | 81 | mod = b"" 82 | for i in range(1, 65): 83 | mod += struct.pack("H", len(PSK)) + PSK) * 2 85 | pmk = hashlib.sha256(raw_pmk).digest() 86 | pmk_hmac = hmac.new(pmk, mod, hashlib.sha256).digest() 87 | firmware_hmac = hmac.new(pmk_hmac, firmware, hashlib.sha256).digest() 88 | 89 | try: 90 | length = len(firmware) 91 | for i in range(0, length, 256): 92 | if not device.write_firmware(i, firmware[i:i + 256], 2): 93 | raise ValueError("Failed to write firmware") 94 | 95 | if not device.check_firmware(None, None, None, firmware_hmac): 96 | raise ValueError("Failed to check firmware") 97 | 98 | except Exception as error: 99 | print( 100 | tool.warning( 101 | f"The program went into serious problems while trying to " 102 | f"update the firmware: {error}")) 103 | 104 | erase_firmware(device) 105 | 106 | raise error 107 | 108 | device.reset(False, True, 50) 109 | device.disconnect() 110 | 111 | 112 | def run_driver(device: goodix.Device): 113 | tls_server = subprocess.Popen([ 114 | "openssl", "s_server", "-nocert", "-psk", 115 | PSK.hex(), "-port", "4433", "-quiet" 116 | ], 117 | stdout=subprocess.PIPE, 118 | stderr=subprocess.STDOUT) 119 | 120 | try: 121 | if not device.reset(True, False, 20)[0]: 122 | raise ValueError("Reset failed") 123 | 124 | device.read_sensor_register(0x0000, 4) # Read chip ID (0x00a6) 125 | 126 | device.read_otp() 127 | # OTP: 4e4e53304b2e0000517681a4aa89e409085c5c96800000f0a06ca56ea0a0746c 128 | # e7280400980052f0072228249fa5a10000000000000000000000000009ff0000 129 | 130 | tls_client = socket.socket() 131 | tls_client.connect(("localhost", 4433)) 132 | 133 | try: 134 | tool.connect_device(device, tls_client) 135 | 136 | if not device.upload_config_mcu(DEVICE_CONFIG): 137 | raise ValueError("Failed to upload config") 138 | 139 | device.mcu_switch_to_fdt_mode( 140 | b"\x0d\x01\x28\x01\x22\x01\x28\x01" 141 | b"\x24\x01\x00\x00\x00\x00\x00\x00" 142 | b"\x00\x00\x00\x00\x00\x00\x00\x00" 143 | b"\x00\x00\x00", False) 144 | device.mcu_switch_to_fdt_mode( 145 | b"\x0d\x01\x28\x01\x22\x01\x28\x01" 146 | b"\x24\x01\x00\x00\x00\x00\x00\x00" 147 | b"\x00\x00\x00\x00\x00\x00\x00\x00" 148 | b"\x00\x00\x01", True) 149 | 150 | device.write_sensor_register(0x022c, b"\x0a\x03") 151 | 152 | tls_client.sendall( 153 | device.mcu_get_image( 154 | b"\x01\x03\x28\x01\x22\x01\x28\x01\x24\x01", 155 | goodix.FLAGS_TRANSPORT_LAYER_SECURITY_DATA)[9:]) 156 | 157 | tool.write_pgm( 158 | tool.decode_image(tls_server.stdout.read(7684)[:-4]), 159 | SENSOR_WIDTH, SENSOR_HEIGHT, "clear-0.pgm") 160 | 161 | device.write_sensor_register(0x022c, b"\x0a\x02") 162 | 163 | device.write_sensor_register(0x022c, b"\x0a\x03") 164 | 165 | tls_client.sendall( 166 | device.mcu_get_image( 167 | b"\x81\x03\x28\x01\x22\x01\x28\x01\x24\x01", 168 | goodix.FLAGS_TRANSPORT_LAYER_SECURITY_DATA)[9:]) 169 | 170 | tool.write_pgm( 171 | tool.decode_image(tls_server.stdout.read(7684)[:-4]), 172 | SENSOR_WIDTH, SENSOR_HEIGHT, "clear-1.pgm") 173 | 174 | device.write_sensor_register(0x022c, b"\x0a\x02") 175 | 176 | device.write_sensor_register(0x022c, b"\x0a\x03") 177 | 178 | tls_client.sendall( 179 | device.mcu_get_image( 180 | b"\x81\x03\x19\x01\x13\x01\x19\x01\x15\x01", 181 | goodix.FLAGS_TRANSPORT_LAYER_SECURITY_DATA)[9:]) 182 | 183 | tool.write_pgm( 184 | tool.decode_image(tls_server.stdout.read(7684)[:-4]), 185 | SENSOR_WIDTH, SENSOR_HEIGHT, "clear-2.pgm") 186 | 187 | device.write_sensor_register(0x022c, b"\x0a\x02") 188 | 189 | device.mcu_switch_to_fdt_mode( 190 | b"\x8d\x01\x28\x01\x22\x01\x28\x01" 191 | b"\x24\x01\x00\x00\x00\x00\x00\x00" 192 | b"\x00\x00\x00\x00\x00\x00\x00\x00" 193 | b"\x00\x00\x00", False) 194 | device.mcu_switch_to_fdt_mode( 195 | b"\x8d\x01\x28\x01\x22\x01\x28\x01" 196 | b"\x24\x01\x00\x00\x00\x00\x00\x00" 197 | b"\x00\x00\x00\x00\x00\x00\x00\x00" 198 | b"\x00\x00\x01", True) 199 | 200 | device.write_sensor_register(0x022c, b"\x0a\x03") 201 | 202 | tls_client.sendall( 203 | device.mcu_get_image( 204 | b"\x81\x03\x28\x01\x22\x01\x28\x01\x24\x01", 205 | goodix.FLAGS_TRANSPORT_LAYER_SECURITY_DATA)[9:]) 206 | 207 | tool.write_pgm( 208 | tool.decode_image(tls_server.stdout.read(7684)[:-4]), 209 | SENSOR_WIDTH, SENSOR_HEIGHT, "clear-3.pgm") 210 | 211 | device.write_sensor_register(0x022c, b"\x0a\x02") 212 | 213 | device.mcu_switch_to_fdt_mode( 214 | b"\x0d\x01\x28\x01\x22\x01\x28\x01" 215 | b"\x24\x01\x00\x00\x00\x00\x00\x00" 216 | b"\x00\x00\x00\x00\x00\x00\x00\x00" 217 | b"\x00\x00\x00", False) 218 | device.mcu_switch_to_fdt_mode( 219 | b"\x0d\x01\x28\x01\x22\x01\x28\x01" 220 | b"\x24\x01\x00\x00\x00\x00\x00\x00" 221 | b"\x00\x00\x00\x00\x00\x00\x00\x00" 222 | b"\x00\x00\x01", True) 223 | 224 | device.mcu_switch_to_sleep_mode() 225 | 226 | device.query_mcu_state(b"\x01\x01\x01", False) 227 | 228 | device.mcu_switch_to_fdt_down( 229 | b"\x8c\x01\x28\x01\x22\x01\x28\x01" 230 | b"\x24\x01\x91\x91\x8b\x8b\x96\x96" 231 | b"\x91\x91\x98\x98\x90\x90\x92\x92" 232 | b"\x88\x88\x00", False) 233 | 234 | print("Waiting for finger...") 235 | 236 | device.mcu_switch_to_fdt_down( 237 | b"\x8c\x01\x28\x01\x22\x01\x28\x01" 238 | b"\x24\x01\x91\x91\x8b\x8b\x96\x96" 239 | b"\x91\x91\x98\x98\x90\x90\x92\x92" 240 | b"\x88\x88\x01", True) 241 | 242 | device.mcu_switch_to_fdt_mode( 243 | b"\x0d\x01\x28\x01\x22\x01\x28\x01" 244 | b"\x24\x01\x91\x91\x8b\x8b\x96\x96" 245 | b"\x91\x91\x98\x98\x90\x90\x92\x92" 246 | b"\x88\x88\x00", False) 247 | 248 | device.mcu_switch_to_fdt_mode( 249 | b"\x0d\x01\x28\x01\x22\x01\x28\x01" 250 | b"\x24\x01\x91\x91\x8b\x8b\x96\x96" 251 | b"\x91\x91\x98\x98\x90\x90\x92\x92" 252 | b"\x88\x88\x01", True) 253 | 254 | device.write_sensor_register(0x022c, b"\x05\x03") 255 | 256 | tls_client.sendall( 257 | device.mcu_get_image( 258 | b"\x41\x03\xa5\x00\x9f\x00\xa5\x00\xa1\x00", 259 | goodix.FLAGS_TRANSPORT_LAYER_SECURITY_DATA)[9:]) 260 | 261 | tool.write_pgm( 262 | tool.decode_image(tls_server.stdout.read(7684)[:-4]), 263 | SENSOR_WIDTH, SENSOR_HEIGHT, "fingerprint.pgm") 264 | 265 | finally: 266 | tls_client.close() 267 | finally: 268 | tls_server.terminate() 269 | 270 | 271 | def main(product: int): 272 | print( 273 | tool.warning( 274 | "This program might break your device.\n" 275 | "Consider that it may flash the device firmware.\n" 276 | "Continue at your own risk.\n" 277 | "But don't hold us responsible if your device is broken!\n" 278 | "Don't run this program as part of a regular process.")) 279 | 280 | code = random.randint(0, 9999) 281 | 282 | if input(f"Type {code} to continue and confirm that you are not a bot: " 283 | ) != str(code): 284 | print("Abort") 285 | return 286 | 287 | previous_firmware = None 288 | 289 | device = init_device(product) 290 | 291 | while True: 292 | firmware = device.firmware_version() 293 | print(f"Firmware: {firmware}") 294 | 295 | valid_psk = check_psk(device) 296 | print(f"Valid PSK: {valid_psk}") 297 | 298 | if firmware == previous_firmware: 299 | raise ValueError("Unchanged firmware") 300 | 301 | previous_firmware = firmware 302 | 303 | if re.fullmatch(TARGET_FIRMWARE, firmware): 304 | if not valid_psk: 305 | erase_firmware(device) 306 | continue 307 | 308 | run_driver(device) 309 | return 310 | 311 | if re.fullmatch(VALID_FIRMWARE, firmware): 312 | erase_firmware(device) 313 | continue 314 | 315 | if re.fullmatch(IAP_FIRMWARE, firmware): 316 | if not valid_psk: 317 | if not write_psk(device): 318 | raise ValueError("Failed to write PSK") 319 | 320 | update_firmware(device) 321 | 322 | device = init_device(product) 323 | 324 | continue 325 | 326 | raise ValueError( 327 | "Invalid firmware\n" + 328 | tool.warning("Please consider that removing this security " 329 | "is a very bad idea!")) 330 | -------------------------------------------------------------------------------- /driver_5503.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import hmac 3 | import random 4 | import re 5 | import socket 6 | import struct 7 | import subprocess 8 | from time import sleep 9 | 10 | import crcmod 11 | 12 | import goodix 13 | import protocol 14 | import tool 15 | 16 | WORKING_FIRMWARE = "GF32[0,5]8_RTSEC_APP_10062" 17 | TARGET_FIRMWARE = "GF3208_RTSEC_APP_10062" 18 | IAP_FIRMWARE = "MILAN_RTSEC_IAP_10027" 19 | VALID_FIRMWARE = "GF32[0-9]{2}_RTSEC_APP_100[0-9]{2}" 20 | 21 | PSK = bytes.fromhex( 22 | "0000000000000000000000000000000000000000000000000000000000000000") 23 | 24 | PSK_WHITE_BOX = bytes.fromhex( 25 | "ec35ae3abb45ed3f12c4751f1e5c2cc05b3c5452e9104d9f2a3118644f37a04b" 26 | "6fd66b1d97cf80f1345f76c84f03ff30bb51bf308f2a9875c41e6592cd2a2f9e" 27 | "60809b17b5316037b69bb2fa5d4c8ac31edb3394046ec06bbdacc57da6a756c5") 28 | 29 | PMK_HASH = bytes.fromhex( 30 | "81b8ff490612022a121a9449ee3aad2792f32b9f3141182cd01019945ee50361") 31 | 32 | DEVICE_CONFIG = bytes.fromhex( 33 | "581160712c9d2cc91ce518fd00fd00fd03ba000180ca0004008400c0b38600bb" 34 | "c48800baba8a00b2b28c00aaaa8e00c1c19000bbbb9200b1b1940000a8960000" 35 | "b6980000009a000000d2000000d4000000d6000000d800000050000105d00000" 36 | "00700000007200785674003412200010402a0102042200012024003200800001" 37 | "005c008000560024205800030032000c02660000027c000058820080152a0182" 38 | "032200012024001400800001005c000001560004205800030032000c02660000" 39 | "027c000058820080162a0108005c000001540000016200080464001000660000" 40 | "027c0000582a0108005c0000015200080054000001660000027c00005800a474" 41 | ) 42 | 43 | SENSOR_WIDTH = 80 44 | SENSOR_HEIGHT = 64 45 | 46 | def init_device(product: int): 47 | device = goodix.Device(product, protocol.USBProtocol) 48 | 49 | device.nop() 50 | 51 | return device 52 | 53 | 54 | def check_psk(device: goodix.Device): 55 | reply = device.preset_psk_read(0xbb020007) 56 | if not reply[0]: 57 | raise ValueError("Failed to read PSK") 58 | 59 | if reply[1] != 0xbb020007: 60 | raise ValueError("Invalid flags") 61 | 62 | return reply[2] == PMK_HASH 63 | 64 | 65 | def write_psk(device: goodix.Device): 66 | if not device.preset_psk_write(0xbb010003, PSK_WHITE_BOX): 67 | return False 68 | 69 | if not check_psk(device): 70 | return False 71 | 72 | return True 73 | 74 | 75 | def erase_firmware(device: goodix.Device): 76 | device.mcu_erase_app(50, False) 77 | device.disconnect() 78 | 79 | 80 | def update_firmware(device: goodix.Device): 81 | firmware_file = open(f"firmware/5503/{TARGET_FIRMWARE}.bin", "rb") 82 | firmware = firmware_file.read() 83 | firmware_file.close() 84 | 85 | mod = b"" 86 | for i in range(1, 65): 87 | mod += struct.pack("H", len(PSK)) + PSK) * 2 89 | pmk = hashlib.sha256(raw_pmk).digest() 90 | pmk_hmac = hmac.new(pmk, mod, hashlib.sha256).digest() 91 | firmware_hmac = hmac.new(pmk_hmac, firmware, hashlib.sha256).digest() 92 | 93 | try: 94 | length = len(firmware) 95 | for i in range(0, length, 256): 96 | if not device.write_firmware(i, firmware[i:i + 256]): 97 | raise ValueError("Failed to write firmware") 98 | 99 | if not device.check_firmware( 100 | 0, length, 101 | crcmod.predefined.mkCrcFun("crc-32-mpeg")(firmware), 102 | firmware_hmac): 103 | raise ValueError("Failed to check firmware") 104 | 105 | except Exception as error: 106 | print( 107 | tool.warning( 108 | f"The program went into serious problems while trying to " 109 | f"update the firmware: {error}")) 110 | 111 | erase_firmware(device) 112 | 113 | raise error 114 | 115 | device.reset(False, True, 100) 116 | device.disconnect() 117 | 118 | def run_driver(device: goodix.Device): 119 | tls_server = subprocess.Popen([ 120 | "openssl", "s_server", "-nocert", "-psk", 121 | PSK.hex(), "-port", "4433", "-quiet" 122 | ], 123 | stdout=subprocess.PIPE, 124 | stderr=subprocess.STDOUT) 125 | 126 | try: 127 | if not device.reset(True, False, 20)[0]: 128 | raise ValueError("Reset failed") 129 | 130 | device.read_sensor_register(0000, 4) # Read chip ID (0x00a1) 131 | 132 | device.nop() 133 | 134 | device.read_otp() 135 | 136 | device.pov_image_check() 137 | 138 | tls_client = socket.socket() 139 | tls_client.connect(("localhost", 4433)) 140 | 141 | try: 142 | tool.connect_device(device, tls_client) 143 | 144 | if not device.upload_config_mcu(DEVICE_CONFIG): 145 | raise ValueError("Failed to upload config") 146 | 147 | device.set_drv_state() 148 | device.set_drv_state() # The windows driver does this twice 149 | 150 | device.mcu_get_pov_image() 151 | 152 | device.mcu_switch_to_fdt_mode( 153 | bytes.fromhex("0d018b0084008c0088008096809180928085808c8086"), 154 | True 155 | ) 156 | 157 | tls_client.sendall( 158 | device.mcu_get_image( 159 | bytes.fromhex("01008b0084008c008800"), 160 | goodix.FLAGS_TRANSPORT_LAYER_SECURITY_DATA)[9:] 161 | ) 162 | 163 | tls_server.stdout.flush() 164 | 165 | clear_0_image = tool.decode_image(tls_server.stdout.read(7684)[:-4]) 166 | tool.write_pgm( 167 | clear_0_image, 168 | SENSOR_WIDTH, SENSOR_HEIGHT, "clear-0.pgm" 169 | ) 170 | 171 | tls_server.stdout.flush() 172 | 173 | device.mcu_switch_to_fdt_mode( 174 | bytes.fromhex("0d018b0084008c0088008096809180928085808c8086"), 175 | True 176 | ) 177 | 178 | device.mcu_switch_to_idle_mode(20) 179 | 180 | device.read_sensor_register(0x0082, 2) 181 | 182 | tls_client.sendall( 183 | device.mcu_get_image( 184 | bytes.fromhex("01008b0084008c008800"), 185 | goodix.FLAGS_TRANSPORT_LAYER_SECURITY_DATA)[9:] 186 | ) 187 | 188 | tls_server.stdout.flush() 189 | 190 | clear_1_image = tool.decode_image(tls_server.stdout.read(7684)[:-4]) 191 | tool.write_pgm( 192 | clear_1_image, 193 | SENSOR_WIDTH, SENSOR_HEIGHT, "clear-1.pgm" 194 | ) 195 | 196 | tls_server.stdout.flush() 197 | 198 | # Reset scanner 199 | device.mcu_switch_to_fdt_mode( 200 | bytes.fromhex("0d018b0084008c0088008096809180928085808c8086"), 201 | True 202 | ) 203 | 204 | # Set PC state?? cmd:000f80b980ae80b980af80b580aa00000000000000000000000016068b0084008c0088000a020a03 205 | # Maybe this is required. Idk. Windows driver sends it. 206 | 207 | device.nop() 208 | 209 | device.query_mcu_state( 210 | bytes.fromhex("000132"), # 010032 211 | True 212 | ) 213 | 214 | device.pov_image_check() 215 | 216 | device.mcu_switch_to_fdt_down( 217 | bytes.fromhex("0c018b0084008c00880080b980ae80b980af80b580aa"), 218 | False 219 | ) 220 | 221 | print("Please place your finger on the sensor") 222 | 223 | device.mcu_switch_to_fdt_down( 224 | bytes.fromhex("0c018b0084008c00880080b980ae80b980af80b580aa"), 225 | True 226 | ) 227 | 228 | tls_server.stdout.flush() 229 | 230 | tls_client.sendall( 231 | device.mcu_get_image( 232 | bytes.fromhex("01008b0084008c008800"), 233 | goodix.FLAGS_TRANSPORT_LAYER_SECURITY_DATA)[9:] 234 | ) 235 | 236 | tls_server.stdout.flush() 237 | sleep(0.1) 238 | 239 | fingerprint_image = tool.decode_image(tls_server.stdout.read(7684)[:-4]) 240 | tool.write_pgm( 241 | fingerprint_image, 242 | SENSOR_WIDTH, SENSOR_HEIGHT, "fingerprint-0.pgm" 243 | ) 244 | 245 | tls_server.stdout.flush() 246 | 247 | # Finger scanned, reset sensor 248 | device.mcu_switch_to_fdt_mode( 249 | bytes.fromhex("0d018b0084008c00880080b980ae80b980af80b580aa"), 250 | True 251 | ) 252 | 253 | print("Please remove your finger from the sensor") 254 | 255 | device.mcu_switch_to_fdt_up( 256 | bytes.fromhex("0e018b0084008c008800809580898099808a808d808d") 257 | ) 258 | 259 | device.mcu_switch_to_fdt_up( 260 | bytes.fromhex("0e018b0084008c008800809b808e80a28090809f80a8") 261 | ) 262 | 263 | device.mcu_switch_to_fdt_down( 264 | bytes.fromhex("0c018b0084008c00880080ba80af80ba80b080b680ab"), 265 | False 266 | ) 267 | 268 | device.query_mcu_state( 269 | bytes.fromhex("010032"), 270 | True 271 | ) 272 | 273 | finally: 274 | tls_client.close() 275 | finally: 276 | tls_server.terminate() 277 | 278 | 279 | def main(product: int): 280 | # print( 281 | # tool.warning( 282 | # "This program might break your device.\n" 283 | # "Consider that it may flash the device firmware.\n" 284 | # "Continue at your own risk.\n" 285 | # "But don't hold us responsible if your device is broken!\n" 286 | # "Don't run this program as part of a regular process.")) 287 | 288 | # code = random.randint(0, 9999) 289 | 290 | # if input(f"Type {code} to continue and confirm that you are not a bot: " 291 | # ) != str(code): 292 | # print("Abort") 293 | # return 294 | 295 | previous_firmware = None 296 | 297 | device = init_device(product) 298 | 299 | while True: 300 | firmware = device.firmware_version() 301 | print(f"Firmware: {firmware}") 302 | 303 | valid_psk = check_psk(device) 304 | print(f"Valid PSK: {valid_psk}") 305 | 306 | if firmware == IAP_FIRMWARE: 307 | iap = IAP_FIRMWARE 308 | else: 309 | iap = device.get_iap_version(25) 310 | print(f"IAP: {iap}") 311 | 312 | if iap != IAP_FIRMWARE: 313 | raise ValueError( 314 | "Invalid IAP\n" + 315 | tool.warning("Please consider that removing this security " 316 | "is a very bad idea!")) 317 | 318 | if firmware == previous_firmware: 319 | raise ValueError("Unchanged firmware") 320 | 321 | previous_firmware = firmware 322 | 323 | if re.fullmatch(WORKING_FIRMWARE, firmware): 324 | if not valid_psk: 325 | if not write_psk(device): 326 | raise ValueError("Failed to write PSK") 327 | 328 | run_driver(device) 329 | return 330 | 331 | if re.fullmatch(VALID_FIRMWARE, firmware): 332 | erase_firmware(device) 333 | 334 | device = init_device(product) 335 | 336 | continue 337 | 338 | if re.fullmatch(IAP_FIRMWARE, firmware): 339 | if not valid_psk: 340 | if not write_psk(device): 341 | raise ValueError("Failed to write PSK") 342 | 343 | update_firmware(device) 344 | 345 | device = init_device(product) 346 | 347 | continue 348 | 349 | raise ValueError( 350 | "Invalid firmware\n" + 351 | tool.warning("Please consider that removing this security " 352 | "is a very bad idea!")) 353 | -------------------------------------------------------------------------------- /driver_55x4.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import hmac 3 | import random 4 | import re 5 | import socket 6 | import struct 7 | import subprocess 8 | 9 | import crcmod 10 | 11 | import goodix 12 | import protocol 13 | import tool 14 | 15 | TARGET_FIRMWARE = "GF3268_RTSEC_APP_10041" 16 | IAP_FIRMWARE = "MILAN_RTSEC_IAP_10027" 17 | VALID_FIRMWARE = "GF32[0-9]{2}_RTSEC_APP_100[0-9]{2}" 18 | 19 | PSK = bytes.fromhex( 20 | "0000000000000000000000000000000000000000000000000000000000000000") 21 | 22 | PSK_WHITE_BOX = bytes.fromhex( 23 | "ec35ae3abb45ed3f12c4751f1e5c2cc05b3c5452e9104d9f2a3118644f37a04b" 24 | "6fd66b1d97cf80f1345f76c84f03ff30bb51bf308f2a9875c41e6592cd2a2f9e" 25 | "60809b17b5316037b69bb2fa5d4c8ac31edb3394046ec06bbdacc57da6a756c5") 26 | 27 | PMK_HASH = bytes.fromhex( 28 | "81b8ff490612022a121a9449ee3aad2792f32b9f3141182cd01019945ee50361") 29 | 30 | DEVICE_CONFIG = bytes.fromhex( 31 | "6011607124952cc114d510e500e514f9030402000008001111ba000180ca0007" 32 | "008400c0b38600bbc48800baba8a00b2b28c00aaaa8e00c1c19000bbbb9200b1" 33 | "b1940000a8960000b6980000bf9a0000ba50000105d000000070000000720078" 34 | "56740034122600001220001040120003042a0102002200012024003200800001" 35 | "005c008000560008205800010032002c028200800cba000180ca0007002a0182" 36 | "03200010402200012024001400800005005c0000015600082058000300820080" 37 | "142a0108005c0080006200090364001800220000202a0108005c000001520008" 38 | "0054000001000000000000000000000000000000000000000000000000009a69") 39 | 40 | SENSOR_WIDTH = 88 41 | SENSOR_HEIGHT = 108 42 | 43 | 44 | def init_device(product: int): 45 | device = goodix.Device(product, protocol.USBProtocol) 46 | 47 | device.nop() 48 | 49 | return device 50 | 51 | 52 | def check_psk(device: goodix.Device): 53 | reply = device.preset_psk_read(0xbb020007) 54 | if not reply[0]: 55 | raise ValueError("Failed to read PSK") 56 | 57 | if reply[1] != 0xbb020007: 58 | raise ValueError("Invalid flags") 59 | 60 | return reply[2] == PMK_HASH 61 | 62 | 63 | def write_psk(device: goodix.Device): 64 | if not device.preset_psk_write(0xbb010003, PSK_WHITE_BOX): 65 | return False 66 | 67 | if not check_psk(device): 68 | return False 69 | 70 | return True 71 | 72 | 73 | def erase_firmware(device: goodix.Device): 74 | device.mcu_erase_app(50, False) 75 | device.disconnect() 76 | 77 | 78 | def update_firmware(device: goodix.Device): 79 | firmware_file = open(f"firmware/55x4/{TARGET_FIRMWARE}.bin", "rb") 80 | firmware = firmware_file.read() 81 | firmware_file.close() 82 | 83 | mod = b"" 84 | for i in range(1, 65): 85 | mod += struct.pack("H", len(PSK)) + PSK) * 2 87 | pmk = hashlib.sha256(raw_pmk).digest() 88 | pmk_hmac = hmac.new(pmk, mod, hashlib.sha256).digest() 89 | firmware_hmac = hmac.new(pmk_hmac, firmware, hashlib.sha256).digest() 90 | 91 | try: 92 | length = len(firmware) 93 | for i in range(0, length, 256): 94 | if not device.write_firmware(i, firmware[i:i + 256]): 95 | raise ValueError("Failed to write firmware") 96 | 97 | if not device.check_firmware( 98 | 0, length, 99 | crcmod.predefined.mkCrcFun("crc-32-mpeg")(firmware), 100 | firmware_hmac): 101 | raise ValueError("Failed to check firmware") 102 | 103 | except Exception as error: 104 | print( 105 | tool.warning( 106 | f"The program went into serious problems while trying to " 107 | f"update the firmware: {error}")) 108 | 109 | erase_firmware(device) 110 | 111 | raise error 112 | 113 | device.reset(False, True, 100) 114 | device.disconnect() 115 | 116 | 117 | def run_driver(device: goodix.Device): 118 | tls_server = subprocess.Popen([ 119 | "openssl", "s_server", "-nocert", "-psk", 120 | PSK.hex(), "-port", "4433", "-quiet" 121 | ], 122 | stdout=subprocess.PIPE, 123 | stderr=subprocess.STDOUT) 124 | 125 | try: 126 | if not device.reset(True, False, 20)[0]: 127 | raise ValueError("Reset failed") 128 | 129 | device.read_sensor_register(0x0000, 4) # Read chip ID (0x00a1) 130 | 131 | device.read_otp() 132 | # OTP: 0867860a12cc02faa65d2b4b0204e20cc20c9664087bf80706000000c02d431d 133 | 134 | tls_client = socket.socket() 135 | tls_client.connect(("localhost", 4433)) 136 | 137 | try: 138 | tool.connect_device(device, tls_client) 139 | 140 | if not device.upload_config_mcu(DEVICE_CONFIG): 141 | raise ValueError("Failed to upload config") 142 | 143 | device.mcu_switch_to_fdt_mode( 144 | b"\x0d\x01\x80\x12\x80\x12\x80\x98" 145 | b"\x80\x82\x80\x12\x80\xa0\x80\x99" 146 | b"\x80\x7f\x80\x12\x80\x9f\x80\x93" 147 | b"\x80\x7e", True) 148 | 149 | tls_client.sendall( 150 | device.mcu_get_image( 151 | b"\x01\x00", 152 | goodix.FLAGS_TRANSPORT_LAYER_SECURITY_DATA)[9:]) 153 | 154 | tool.write_pgm( 155 | tool.decode_image(tls_server.stdout.read(14260)[:-4]), 156 | SENSOR_WIDTH, SENSOR_HEIGHT, "clear-0.pgm") 157 | 158 | device.mcu_switch_to_fdt_mode( 159 | b"\x0d\x01\x80\x12\x80\x12\x80\x98" 160 | b"\x80\x82\x80\x12\x80\xa0\x80\x99" 161 | b"\x80\x7f\x80\x12\x80\x9f\x80\x93" 162 | b"\x80\x7e", True) 163 | 164 | device.mcu_switch_to_idle_mode(20) 165 | 166 | device.read_sensor_register(0x0082, 2) 167 | 168 | tls_client.sendall( 169 | device.mcu_get_image( 170 | b"\x01\x00", 171 | goodix.FLAGS_TRANSPORT_LAYER_SECURITY_DATA)[9:]) 172 | 173 | tool.write_pgm( 174 | tool.decode_image(tls_server.stdout.read(14260)[:-4]), 175 | SENSOR_WIDTH, SENSOR_HEIGHT, "clear-1.pgm") 176 | 177 | device.mcu_switch_to_fdt_mode( 178 | b"\x0d\x01\x80\x12\x80\x12\x80\x98" 179 | b"\x80\x82\x80\x12\x80\xa0\x80\x99" 180 | b"\x80\x7f\x80\x12\x80\x9f\x80\x93" 181 | b"\x80\x7e", True) 182 | 183 | if not device.switch_to_sleep_mode(0x6c): 184 | raise ValueError("Failed to switch to sleep mode") 185 | 186 | print("Waiting for finger...") 187 | 188 | device.mcu_switch_to_fdt_down( 189 | b"\x0c\x01\x80\xb0\x80\xc4\x80\xba" 190 | b"\x80\xa6\x80\xb7\x80\xc7\x80\xc0" 191 | b"\x80\xaa\x80\xb4\x80\xc4\x80\xba" 192 | b"\x80\xa6", True) 193 | 194 | tls_client.sendall( 195 | device.mcu_get_image( 196 | b"\x01\x00", 197 | goodix.FLAGS_TRANSPORT_LAYER_SECURITY_DATA)[9:]) 198 | 199 | tool.write_pgm( 200 | tool.decode_image(tls_server.stdout.read(14260)[:-4]), 201 | SENSOR_WIDTH, SENSOR_HEIGHT, "fingerprint.pgm") 202 | 203 | finally: 204 | tls_client.close() 205 | finally: 206 | tls_server.terminate() 207 | 208 | 209 | def main(product: int): 210 | print( 211 | tool.warning( 212 | "This program might break your device.\n" 213 | "Consider that it may flash the device firmware.\n" 214 | "Continue at your own risk.\n" 215 | "But don't hold us responsible if your device is broken!\n" 216 | "Don't run this program as part of a regular process.")) 217 | 218 | code = random.randint(0, 9999) 219 | 220 | if input(f"Type {code} to continue and confirm that you are not a bot: " 221 | ) != str(code): 222 | print("Abort") 223 | return 224 | 225 | previous_firmware = None 226 | 227 | device = init_device(product) 228 | 229 | while True: 230 | firmware = device.firmware_version() 231 | print(f"Firmware: {firmware}") 232 | 233 | valid_psk = check_psk(device) 234 | print(f"Valid PSK: {valid_psk}") 235 | 236 | if firmware == IAP_FIRMWARE: 237 | iap = IAP_FIRMWARE 238 | else: 239 | iap = device.get_iap_version(25) 240 | print(f"IAP: {iap}") 241 | 242 | if iap != IAP_FIRMWARE: 243 | raise ValueError( 244 | "Invalid IAP\n" + 245 | tool.warning("Please consider that removing this security " 246 | "is a very bad idea!")) 247 | 248 | if firmware == previous_firmware: 249 | raise ValueError("Unchanged firmware") 250 | 251 | previous_firmware = firmware 252 | 253 | if re.fullmatch(TARGET_FIRMWARE, firmware): 254 | if not valid_psk: 255 | if not write_psk(device): 256 | raise ValueError("Failed to write PSK") 257 | 258 | run_driver(device) 259 | return 260 | 261 | if re.fullmatch(VALID_FIRMWARE, firmware): 262 | erase_firmware(device) 263 | 264 | device = init_device(product) 265 | 266 | continue 267 | 268 | if re.fullmatch(IAP_FIRMWARE, firmware): 269 | if not valid_psk: 270 | if not write_psk(device): 271 | raise ValueError("Failed to write PSK") 272 | 273 | update_firmware(device) 274 | 275 | device = init_device(product) 276 | 277 | continue 278 | 279 | raise ValueError( 280 | "Invalid firmware\n" + 281 | tool.warning("Please consider that removing this security " 282 | "is a very bad idea!")) 283 | -------------------------------------------------------------------------------- /dump_5335.py: -------------------------------------------------------------------------------- 1 | import dumper_53x5 2 | 3 | dumper_53x5.main(0x5335) 4 | -------------------------------------------------------------------------------- /dump_5385.py: -------------------------------------------------------------------------------- 1 | import dumper_53x5 2 | 3 | dumper_53x5.main(0x5385) 4 | -------------------------------------------------------------------------------- /dump_5395.py: -------------------------------------------------------------------------------- 1 | import dumper_53x5 2 | 3 | dumper_53x5.main(0x5395) 4 | -------------------------------------------------------------------------------- /dumper_53x5.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | import protocol 5 | import wrapless 6 | 7 | import crccheck 8 | 9 | IAP_START = 0x0 10 | IAP_END = 0x3000 11 | IAP_FILE = "iap_firmware.bin" 12 | APP_FILE = "app_firmware.bin" 13 | 14 | PAGE_SIZE = 0x200 15 | NUM_PAGES = 0xFF 16 | 17 | 18 | def dump_iap_firmware(device): 19 | print(f"Dumping IAP firmware from {hex(IAP_START)} to {hex(IAP_END)}") 20 | dump = b"" 21 | for page_start in range(IAP_START, IAP_END, 0x400): 22 | dump += device.read_firmware(page_start, 0x400) 23 | 24 | print(f"Writing dumped IAP firmware to {IAP_FILE}") 25 | with open(IAP_FILE, "wb") as dump_file: 26 | dump_file.write(dump) 27 | 28 | 29 | def extract_firmware_info(last_flash_page): 30 | firmware_length = int.from_bytes( 31 | last_flash_page[PAGE_SIZE - 8 : PAGE_SIZE - 4], byteorder="little" 32 | ) 33 | firmware_crc32 = int.from_bytes( 34 | last_flash_page[PAGE_SIZE - 4 : PAGE_SIZE], byteorder="little" 35 | ) 36 | 37 | firmware_length_not = int.from_bytes( 38 | last_flash_page[PAGE_SIZE - 0x10 : PAGE_SIZE - 0xC], byteorder="little" 39 | ) 40 | firmware_crc32_not = int.from_bytes( 41 | last_flash_page[PAGE_SIZE - 0xC : PAGE_SIZE - 0x8], byteorder="little" 42 | ) 43 | 44 | assert firmware_length == firmware_length_not ^ 0xFFFFFFFF 45 | assert firmware_crc32 == firmware_crc32_not ^ 0xFFFFFFFF 46 | 47 | return (firmware_length, firmware_crc32) 48 | 49 | 50 | def dump_app_firmware(device, firmware_length, firmware_crc32): 51 | print( 52 | f"Dumping APP firmware from {hex(IAP_END)} to {hex(IAP_END + firmware_length)}" 53 | ) 54 | dump = b"" 55 | for page_start in range(IAP_END, IAP_END + firmware_length, 0x400): 56 | dump += device.read_firmware(page_start, 0x400) 57 | 58 | dump = dump[:firmware_length] 59 | 60 | dump_crc32 = crccheck.crc.Crc32Mpeg2.calc(dump) 61 | if dump_crc32 != firmware_crc32: 62 | raise Exception("Invalid CRC32!") 63 | 64 | print(f"Writing dumped APP firmware to {APP_FILE}") 65 | with open(APP_FILE, "wb") as dump_file: 66 | dump_file.write(dump) 67 | 68 | 69 | def parse_mask(mask: bytes): 70 | n_bits = len(mask) * 8 71 | mask_val = int.from_bytes(mask, byteorder="little") 72 | for _ in range(n_bits): 73 | yield mask_val & 1 == 1 74 | mask_val >>= 1 75 | 76 | 77 | def print_masked_buffer(content: bytes, mask: bytes): 78 | for c, m in zip(content, parse_mask(mask)): 79 | if m: 80 | print(f"{c:02x}", end=" ") 81 | else: 82 | print("--", end=" ") 83 | print() 84 | 85 | 86 | def parse_0x20_flash_buffer(data): 87 | tag_1 = int.from_bytes(data[:0x4], byteorder="little") 88 | tag_2 = int.from_bytes(data[0x4:0x8], byteorder="little") 89 | 90 | assert tag_1 & 0xFFFF == (tag_1 >> 0x10) ^ 0xFFFF 91 | assert tag_2 & 0xFFFF == (tag_2 >> 0x10) ^ 0xFFFF 92 | 93 | saved_crc = int.from_bytes(data[0x30:0x34], byteorder="little") 94 | saved_content = data[0xC:0x2C] 95 | saved_mask = data[0x2C:0x30] 96 | 97 | crc = crccheck.crc.Crc32Mpeg2.calc(data[0x4:0x30]) 98 | if crc != saved_crc: 99 | raise Exception("Invalid CRC32!") 100 | 101 | return (saved_content, saved_mask) 102 | 103 | 104 | def dump_otp(device): 105 | otp_base = PAGE_SIZE * (NUM_PAGES - 3) 106 | print(f"Dumping OTP from flash ({hex(otp_base)})") 107 | pages = device.read_firmware(otp_base, PAGE_SIZE * 2) 108 | 109 | page_1 = pages[:PAGE_SIZE] 110 | content, mask = parse_0x20_flash_buffer(page_1) 111 | 112 | print("OTP: ", end="") 113 | print_masked_buffer(content, mask) 114 | 115 | 116 | def dump_usb_pid(device): 117 | usb_pid_base = PAGE_SIZE * (NUM_PAGES - 5) 118 | print(f"Dumping USB PID from flash ({hex(usb_pid_base)})") 119 | pages = device.read_firmware(usb_pid_base, PAGE_SIZE * 2) 120 | 121 | page_1 = pages[:PAGE_SIZE] 122 | content, mask = parse_0x20_flash_buffer(page_1) 123 | 124 | print("USB PID: ", end="") 125 | print_masked_buffer(content, mask) 126 | 127 | 128 | def dump_option_byte(device): 129 | print(f"Dumping option byte") 130 | option_byte = device.read_option_byte() 131 | print(option_byte) 132 | 133 | 134 | def main(product: int): 135 | if "DEBUG" in os.environ: 136 | logging.basicConfig(level=logging.DEBUG) 137 | 138 | device = wrapless.Device(product, protocol.USBProtocol) 139 | device.ping() 140 | 141 | firmware_version = device.read_firmware_version() 142 | print(f"Firmware version: {firmware_version}") 143 | 144 | dump_otp(device) 145 | 146 | dump_usb_pid(device) 147 | 148 | dump_iap_firmware(device) 149 | 150 | last_two_pages = device.read_firmware(PAGE_SIZE * (NUM_PAGES - 1), PAGE_SIZE * 2) 151 | 152 | last_flash_page = last_two_pages[:PAGE_SIZE] 153 | firmware_length, firmware_crc32 = extract_firmware_info(last_flash_page) 154 | print(f"APP firmware length: {hex(firmware_length)}") 155 | print(f"APP firmware CRC32: {hex(firmware_crc32)}") 156 | 157 | dump_app_firmware(device, firmware_length, firmware_crc32) 158 | 159 | option_byte_page = last_two_pages[PAGE_SIZE:] 160 | print(f"Option Byte page: {option_byte_page[:0x24]}") 161 | 162 | dump_option_byte(device) 163 | -------------------------------------------------------------------------------- /flash_5395.py: -------------------------------------------------------------------------------- 1 | import flasher_53x5 2 | 3 | # A 5395 sensor in IAP mode reports 5740 as USB PID 4 | flasher_53x5.main(0x5740, "GF5288_HTSEC_APP_10020.bin") 5 | -------------------------------------------------------------------------------- /flasher_53x5.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | import protocol 5 | import wrapless 6 | 7 | 8 | def main(product: int, target_firmware_name: str): 9 | if "DEBUG" in os.environ: 10 | logging.basicConfig(level=logging.DEBUG) 11 | 12 | device = wrapless.Device(product, protocol.USBProtocol) 13 | device.ping() 14 | 15 | firmware_version = device.read_firmware_version() 16 | print(f"Firmware version: {firmware_version}") 17 | 18 | _, chip_vendor, kind, _ = firmware_version.split("_") 19 | if kind != "IAP": 20 | raise Exception("Chip already has firmware") 21 | 22 | target_chip_vendor = target_firmware_name.split("_")[1] 23 | if chip_vendor[:2] != target_chip_vendor[:2]: 24 | raise Exception("Chip vendor does not match") 25 | 26 | with open(f"firmware/53x5/{target_firmware_name}.bin", "rb") as firmware_file: 27 | firmware = firmware_file.read() 28 | 29 | device.update_firmware(firmware) 30 | -------------------------------------------------------------------------------- /log/ENGINE.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodix-fp-linux-dev/goodix-fp-dump/cc43bb3b3154a0bccc0412ae024013c7e1923139/log/ENGINE.log -------------------------------------------------------------------------------- /log/README.md: -------------------------------------------------------------------------------- 1 | To enable the driver logs, use the [goodix_enable_logs.reg](goodix_enable_logs.reg) file on Windows. 2 | 3 | After a reboot, logs should be accessible at `C:\ProgramData\Goodix`: 4 | 5 | + [ENGINE.log](ENGINE.log) 6 | + [WBDI.log](WBDI.log) 7 | -------------------------------------------------------------------------------- /log/goodix_enable_logs.reg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodix-fp-linux-dev/goodix-fp-dump/cc43bb3b3154a0bccc0412ae024013c7e1923139/log/goodix_enable_logs.reg -------------------------------------------------------------------------------- /preprocessor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import tool 4 | import argparse 5 | 6 | def crop(image, width, height, x, y, new_width, new_height): 7 | res = [] 8 | for y1 in range(y, y + new_height): 9 | idx = y1 * width + x 10 | res = res + image[idx:idx + new_width] 11 | 12 | return res 13 | 14 | def threshold_filter(image, low, high): 15 | result = [] 16 | coverage = 0 17 | for pixel in image: 18 | if pixel < low: 19 | result.append(0) 20 | elif pixel > high: 21 | result.append(4095) 22 | else: 23 | coverage = coverage + 1 24 | result.append(pixel) 25 | 26 | return result, coverage 27 | 28 | def get_histogram(image): 29 | res = [0] * 256 30 | for pixel in image: 31 | idx = int(pixel / 16) 32 | res[idx] = res[idx] + 1 33 | 34 | return res 35 | 36 | def get_cumul_histogram(hist): 37 | res = [0] * 256 38 | res[0] = hist[0] 39 | for idx in range(1, len(hist)): 40 | res[idx] = hist[idx] + res[idx - 1] 41 | 42 | return res 43 | 44 | def get_level(cumul_hist, value): 45 | for idx in range(len(cumul_hist)): 46 | if cumul_hist[idx] > value: 47 | return idx 48 | 49 | return len(cumul_hist) 50 | 51 | def mean_filter(image, width, height, mask_radius, mask = None): 52 | res = [] 53 | for y in range(0, height): 54 | for x in range(0, width): 55 | cnt = 0 56 | val = 0 57 | for y2 in range(y - mask_radius, y + mask_radius + 1): 58 | if y2 < 0 or y2 >= height: 59 | continue 60 | for x2 in range(x - mask_radius, x + mask_radius + 1): 61 | if x2 < 0 or x2 >= width: 62 | continue 63 | if mask: 64 | weight = mask[(x2 - x - mask_radius) + (mask_radius * 2 + 1) * (y2 - y - mask_radius)] 65 | else: 66 | weight = 1 67 | cnt = cnt + weight 68 | val = val + image[y2 * width + x2] * weight 69 | if cnt == 0: 70 | cnt = 1 71 | val = val / cnt 72 | res.append(val) 73 | 74 | return res 75 | 76 | def subtract_image(bg_img, img, offset, bg_weight, img_weight): 77 | subtracted = [] 78 | for bg_pixel, img_pixel in zip(bg_img, img): 79 | val = offset + img_weight * img_pixel / 100 - bg_weight * bg_pixel / 100 80 | if val < 0: 81 | val = 0 82 | if val > 4095: 83 | val = 4095 84 | subtracted.append(val) 85 | return subtracted 86 | 87 | def hist_equalization(image, black_lvl, white_lvl): 88 | # map [black_lvl .. white_lvl] to [0 .. 4095] 89 | # pixel < black_lvl: pixel = 0 90 | # pixel > white_lvl: pixel = 4095 91 | # 4096 * (pixel - black_lvl) / (white_lvl - black_lvl) 92 | res = [] 93 | for pixel in image: 94 | if pixel < black_lvl: 95 | pixel = 0 96 | elif pixel > white_lvl: 97 | pixel = 4095 98 | else: 99 | pixel = int(4095 * (pixel - black_lvl) / (white_lvl - black_lvl)) 100 | res.append(pixel) 101 | 102 | return res 103 | 104 | def main(args): 105 | (bg_width, bg_height, bg_depth, bg_img) = tool.read_pgm(args.background) 106 | (width, height, depth, raw_img) = tool.read_pgm(args.image) 107 | 108 | assert bg_width == width and bg_height == height 109 | assert bg_depth == depth == 4095 110 | 111 | # Crop image to drop black border 112 | raw_img = crop(raw_img, width, height, 1, 1, width - 2, height - 2) 113 | bg_img = crop(bg_img, width, height, 1, 1, width - 2, height - 2) 114 | width = width - 2 115 | height = height - 2 116 | 117 | hist = get_histogram(raw_img) 118 | print("Raw image histogram: %s" % hist) 119 | 120 | subtracted = subtract_image(bg_img, raw_img, 1000, 100, 100) 121 | subtracted, coverage = threshold_filter(subtracted, 0, 1700) 122 | total = width * height 123 | print(f"Coverage: {coverage} out of {total}, %d%%" % (100 * coverage / total)) 124 | 125 | hist = get_histogram(subtracted) 126 | print("Subtracted image histogram: %s" % hist) 127 | total = width * height - hist[0] - hist[1] - hist[2] - hist[255] - hist[254] - hist[253] 128 | # We don't want to account black and white pixels 129 | hist[0] = 0 130 | hist[255] = 0 131 | cumul_hist = get_cumul_histogram(hist) 132 | 133 | # Black level starts at 0.1% of total pixels 134 | black_lvl = get_level(cumul_hist, 1 * total / 1000) * 16 135 | print(f"Black level: {black_lvl}") 136 | 137 | # White level starts at 99% of total pixels 138 | white_lvl = get_level(cumul_hist, 99 * total / 100) * 16 139 | print(f"Estimated white level: {white_lvl}") 140 | 141 | res = hist_equalization(subtracted, black_lvl, white_lvl) 142 | 143 | hist = get_histogram(res) 144 | print("Histogram equalized image histogram: %s" % hist) 145 | 146 | mean_1 = mean_filter(res, width, height, 1, [1, 2, 1, 2, 4, 2, 1, 2, 1]) 147 | mean_sub_1 = subtract_image(mean_1, res, 0, 100, 200) 148 | 149 | tool.write_pgm(res, height, width, "stage-2-hist-eq.pgm") 150 | tool.write_pgm(subtracted, height, width, "stage-1-subtracted.pgm") 151 | tool.write_pgm(mean_1, height, width, "stage-3-mean.pgm") 152 | tool.write_pgm(mean_sub_1, height, width, "result.pgm") 153 | 154 | if __name__ == "__main__": 155 | parser = argparse.ArgumentParser() 156 | parser.add_argument("background", type=str) 157 | parser.add_argument("image", type=str) 158 | args = parser.parse_args() 159 | 160 | main(args) 161 | -------------------------------------------------------------------------------- /print_config_53x5.py: -------------------------------------------------------------------------------- 1 | from driver_53x5 import DEFAULT_CONFIG 2 | 3 | 4 | def get_sections(config): 5 | sections = [] 6 | 7 | section_table = config[1:0x11] 8 | for section_table_offt in range(0, 0x10, 2): 9 | section_base = section_table[section_table_offt] 10 | section_size = section_table[section_table_offt + 1] 11 | 12 | section = config[section_base : section_base + section_size] 13 | sections.append(section) 14 | 15 | return sections 16 | 17 | 18 | def parse_section(section): 19 | entries = [] 20 | for entry_offt in range(0, len(section), 4): 21 | addr = int.from_bytes(section[entry_offt : entry_offt + 2], byteorder="little") 22 | value = int.from_bytes( 23 | section[entry_offt + 2 : entry_offt + 4], byteorder="little" 24 | ) 25 | entries.append((addr, value)) 26 | return entries 27 | 28 | 29 | def main(): 30 | sections = get_sections(DEFAULT_CONFIG) 31 | for section_idx, section in enumerate(sections): 32 | print() 33 | print(f"Section {section_idx}:") 34 | 35 | entries = parse_section(section) 36 | for entry in entries: 37 | addr, value = entry 38 | print(f" {addr:x}:\t{value:x}") 39 | 40 | 41 | if __name__ == "__main__": 42 | main() 43 | -------------------------------------------------------------------------------- /protocol.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import struct 3 | import time 4 | 5 | import periphery 6 | import spidev 7 | import usb 8 | 9 | 10 | class Protocol(abc.ABC): 11 | 12 | @abc.abstractmethod 13 | def __init__(self, vendor: int, product: int, timeout: float | None = 5): 14 | ... 15 | 16 | @abc.abstractmethod 17 | def write(self, data: bytes, timeout: float | None = 5): 18 | ... 19 | 20 | @abc.abstractmethod 21 | def read(self, size: int = 0x4000, timeout: float | None = 5) -> bytes: 22 | ... 23 | 24 | @abc.abstractmethod 25 | def disconnect(self, timeout: float | None = 5): 26 | ... 27 | 28 | 29 | class USBProtocol(Protocol): 30 | 31 | def __init__(self, vendor, product, timeout=5): 32 | super().__init__(vendor, product, timeout) 33 | 34 | if timeout is not None: 35 | timeout += time.time() 36 | 37 | while True: 38 | device = usb.core.find(idVendor=vendor, idProduct=product) 39 | 40 | if device is not None: 41 | try: 42 | usb.control.get_status(device) 43 | break 44 | 45 | except usb.core.USBError as error: 46 | if (error.backend_error_code != -1 47 | and error.backend_error_code != -4): 48 | raise error 49 | 50 | if timeout is not None and time.time() > timeout: 51 | if device is None: 52 | raise TimeoutError("Device not found", -5, 19) 53 | 54 | raise TimeoutError("Invalid device state", -12, 131) 55 | 56 | time.sleep(0.01) 57 | 58 | self.device: usb.core.Device = device 59 | 60 | print(f"Found Goodix device: \"{self.device.product}\" " 61 | f"from \"{self.device.manufacturer}\" " 62 | f"on bus {self.device.bus} " 63 | f"address {self.device.address}.") 64 | 65 | interface_data = usb.util.find_descriptor( 66 | self.device.get_active_configuration(), 67 | custom_match=lambda interface: interface.bInterfaceClass == usb. 68 | legacy.CLASS_DATA or interface.bInterfaceClass == usb.legacy. 69 | CLASS_VENDOR_SPEC) 70 | 71 | if interface_data is None: 72 | raise ConnectionError("Interface data not found", -5, 6) 73 | 74 | print(f"Found interface data: {interface_data.bInterfaceNumber}") 75 | 76 | endpoint_in = usb.util.find_descriptor( 77 | interface_data, 78 | custom_match=lambda endpoint: usb.util.endpoint_direction( 79 | endpoint.bEndpointAddress) == usb.legacy.ENDPOINT_IN and usb. 80 | util.endpoint_type(endpoint.bmAttributes 81 | ) == usb.legacy.ENDPOINT_TYPE_BULK) 82 | 83 | if endpoint_in is None: 84 | raise ConnectionError("Endpoint in not found", -5, 6) 85 | 86 | self.endpoint_in: int = endpoint_in.bEndpointAddress 87 | print(f"Found endpoint in: {hex(self.endpoint_in)}") 88 | 89 | endpoint_out = usb.util.find_descriptor( 90 | interface_data, 91 | custom_match=lambda endpoint: usb.util.endpoint_direction( 92 | endpoint.bEndpointAddress) == usb.legacy.ENDPOINT_OUT and usb. 93 | util.endpoint_type(endpoint.bmAttributes 94 | ) == usb.legacy.ENDPOINT_TYPE_BULK) 95 | 96 | if endpoint_out is None: 97 | raise ConnectionError("Endpoint out not found", -5, 6) 98 | 99 | self.endpoint_out: int = endpoint_out.bEndpointAddress 100 | print(f"Found endpoint out: {hex(self.endpoint_out)}") 101 | 102 | if self.device.is_kernel_driver_active( 103 | interface_data.bInterfaceNumber): 104 | self.device.detach_kernel_driver(interface_data.bInterfaceNumber) 105 | 106 | self.device.set_configuration() 107 | 108 | def write(self, data, timeout=5): 109 | timeout = 0 if timeout is None else round(timeout * 1000) 110 | 111 | length = len(data) 112 | if length % 0x40: 113 | data += b"\x00" * (0x40 - length % 0x40) 114 | 115 | for i in range(0, length, 0x40): 116 | self.device.write(self.endpoint_out, data[i:i + 0x40], timeout) 117 | 118 | def read(self, size=0x10000, timeout=5): 119 | timeout = 0 if timeout is None else round(timeout * 1000) 120 | 121 | data: bytes = self.device.read(self.endpoint_in, size, 122 | timeout).tobytes() 123 | return data 124 | 125 | def disconnect(self, timeout=5): 126 | 127 | if timeout is not None: 128 | timeout += time.time() 129 | 130 | while True: 131 | try: 132 | usb.control.get_status(self.device) 133 | 134 | except usb.core.USBError as error: 135 | if (error.backend_error_code == -1 136 | or error.backend_error_code == -4): 137 | break 138 | 139 | raise error 140 | 141 | if timeout is not None and time.time() > timeout: 142 | raise TimeoutError("Device is still connected", -7, 110) 143 | 144 | time.sleep(0.01) 145 | 146 | 147 | class SPIProtocol(Protocol): 148 | READ_SIZE = 256 # higher values could lock up the device until full power reset 149 | 150 | def __init__(self, vendor, product, timeout=5): 151 | super().__init__(vendor, product, timeout) 152 | 153 | self.device: spidev.SpiDev = spidev.SpiDev(0, 0) 154 | self.interrupt: periphery.CdevGPIO = periphery.CdevGPIO( 155 | "/dev/gpiochip0", 279, 'in', edge='falling') 156 | self.buffer = bytearray() 157 | self.seq = int() 158 | 159 | def _xfer(self, data: bytes, read_size=READ_SIZE): 160 | return bytearray( 161 | self.device.xfer2(data + 162 | b'\0' * read_size)[len(data):]).rstrip(b'\0') 163 | 164 | def write(self, data, timeout=5): 165 | #timeout = 0 if timeout is None else round(timeout * 1000) 166 | 167 | self.seq += 1 168 | data = b"\xcc\xf2" + struct.pack(' timeout: 179 | raise TimeoutError() 180 | time.sleep(0.12) 181 | #self.interrupt.poll() 182 | self.buffer += self._xfer(b"\xbb\xf1\x00\x00", size - l) 183 | l = len(self.buffer) 184 | 185 | return bytes(self.buffer[:size]) 186 | 187 | def disconnect(self, timeout=5): 188 | pass 189 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyusb 2 | crcmod 3 | python-periphery 4 | spidev 5 | pycryptodome 6 | crccheck 7 | -------------------------------------------------------------------------------- /run_5110.py: -------------------------------------------------------------------------------- 1 | import driver_51x0 2 | 3 | driver_51x0.main(0x5110) 4 | -------------------------------------------------------------------------------- /run_5117.py: -------------------------------------------------------------------------------- 1 | import driver_51x7 2 | 3 | driver_51x7.main(0x5117) 4 | -------------------------------------------------------------------------------- /run_5120_spi.py: -------------------------------------------------------------------------------- 1 | import driver_51x0_spi 2 | 3 | driver_51x0_spi.main(0x5120) 4 | -------------------------------------------------------------------------------- /run_521d.py: -------------------------------------------------------------------------------- 1 | import driver_52xd 2 | 3 | driver_52xd.main(0x521d) 4 | -------------------------------------------------------------------------------- /run_532d.py: -------------------------------------------------------------------------------- 1 | import driver_53xd 2 | 3 | driver_53xd.main(0x532d) 4 | -------------------------------------------------------------------------------- /run_5385.py: -------------------------------------------------------------------------------- 1 | import driver_53x5 2 | 3 | driver_53x5.main(0x5385) 4 | -------------------------------------------------------------------------------- /run_538d.py: -------------------------------------------------------------------------------- 1 | import driver_53xd 2 | 3 | driver_53xd.main(0x538d) 4 | -------------------------------------------------------------------------------- /run_5395.py: -------------------------------------------------------------------------------- 1 | import driver_53x5 2 | 3 | driver_53x5.main(0x5395) 4 | -------------------------------------------------------------------------------- /run_5503.py: -------------------------------------------------------------------------------- 1 | import driver_5503 2 | 3 | driver_5503.main(0x5503) 4 | -------------------------------------------------------------------------------- /run_55a4.py: -------------------------------------------------------------------------------- 1 | import driver_55x4 2 | 3 | driver_55x4.main(0x55a4) 4 | -------------------------------------------------------------------------------- /run_55b4.py: -------------------------------------------------------------------------------- 1 | import driver_55x4 2 | 3 | driver_55x4.main(0x55b4) 4 | -------------------------------------------------------------------------------- /tool.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import time 3 | 4 | import goodix 5 | 6 | 7 | def warning(text: str): 8 | decorator = "#" * len(max(text.split("\n"), key=len)) 9 | return f"\033[31;5m{decorator}\n{text}\n{decorator}\033[0m" 10 | 11 | 12 | def connect_device(device: goodix.Device, tls_client: socket.socket): 13 | tls_client.sendall(device.request_tls_connection()) 14 | 15 | device.protocol.write( 16 | goodix.encode_message_pack(tls_client.recv(1024), 17 | goodix.FLAGS_TRANSPORT_LAYER_SECURITY)) 18 | 19 | tls_client.sendall( 20 | goodix.check_message_pack(device.protocol.read(), 21 | goodix.FLAGS_TRANSPORT_LAYER_SECURITY)) 22 | tls_client.sendall( 23 | goodix.check_message_pack(device.protocol.read(), 24 | goodix.FLAGS_TRANSPORT_LAYER_SECURITY)) 25 | tls_client.sendall( 26 | goodix.check_message_pack(device.protocol.read(), 27 | goodix.FLAGS_TRANSPORT_LAYER_SECURITY)) 28 | 29 | device.protocol.write( 30 | goodix.encode_message_pack(tls_client.recv(1024), 31 | goodix.FLAGS_TRANSPORT_LAYER_SECURITY)) 32 | 33 | time.sleep(0.01) # Important otherwise an USBTimeout error occur 34 | 35 | 36 | def decode_image(data: bytes): 37 | image: list[int] = [] 38 | for i in range(0, len(data), 6): 39 | chunk = data[i:i + 6] 40 | 41 | image.append(((chunk[0] & 0xf) << 8) + chunk[1]) 42 | image.append((chunk[3] << 4) + (chunk[0] >> 4)) 43 | image.append(((chunk[5] & 0xf) << 8) + chunk[2]) 44 | image.append((chunk[4] << 4) + (chunk[5] >> 4)) 45 | 46 | return image 47 | 48 | 49 | def write_pgm(image: list[int], width: int, height: int, path: str): 50 | img_str = "" 51 | print(f"image: {width} x {height}, length: {len(image)}") 52 | for i in range(len(image)): 53 | if (i % height) == 0: 54 | img_str += "\n" 55 | img_str += "%4d" % image[i] + " " 56 | 57 | file = open(path, "w") 58 | file.write(f"P2\n{height} {width}\n4095\n") 59 | file.write("\n" + img_str) 60 | 61 | def read_pgm(path: str): 62 | with open(path, "r") as file: 63 | data = file.readlines() 64 | 65 | # It isn't compliant with PGM spec, but can read files produced 66 | # by write_pgm() 67 | header = data[0].split() 68 | if header[0] != "P2": 69 | return None 70 | dimensions = data[1].split() 71 | (width, height) = (int(dimensions[0]), int(dimensions[1])) 72 | depth = int(data[2].split()[0]) 73 | image = [] 74 | for line in data[3:]: 75 | for num in line.split(): 76 | image.append(int(num)) 77 | 78 | return (width, height, depth, image) 79 | -------------------------------------------------------------------------------- /wireshark/README.md: -------------------------------------------------------------------------------- 1 | To decode the traffic of the Goodix device in Wireshark, copy the [goodix_message.lua](goodix_message.lua) file in `~/.local/lib/wireshark/plugins`. 2 | 3 | If your device use the wrapless protocol, use [wrapless_goodix_message.lua](wrapless_goodix_message.lua) instead. 4 | -------------------------------------------------------------------------------- /wireshark/goodix_message.lua: -------------------------------------------------------------------------------- 1 | protocol = Proto("goodix", "Goodix Fingerprint Sensor Message Protocol") 2 | 3 | cmd0_field = ProtoField.uint8("goodix.cmd0", "Command 0", base.HEX, nil, 0xf0) 4 | cmd1_field = ProtoField.uint8("goodix.cmd1", "Command 1", base.HEX, nil, 0x0e) 5 | length_field = ProtoField.uint16("goodix.length", "Length", base.DEC) 6 | checksum_field = ProtoField.uint8("goodix.checksum", "Checksum", base.HEX) 7 | 8 | ack_config = ProtoField.bool("goodix.ack.has_no_config", "MCU has no config", 2, nil, 0x02) 9 | ack_true = ProtoField.bool("goodix.ack.true", "Always True", 2, nil, 0x01) 10 | ack_cmd = ProtoField.uint8("goodix.ack.cmd", "ACK Command", base.HEX) 11 | success = ProtoField.bool("goodix.success", "Success") 12 | failed = ProtoField.bool("goodix.failed", "Failed") 13 | number = ProtoField.uint8("goodix.number", "Number", base.HEX) 14 | 15 | version = ProtoField.string("goodix.version", "Version") 16 | enable_chip = ProtoField.bool("goodix.enable_chip", "Enable chip") 17 | sleep_time = ProtoField.uint8("goodix.sleep_time", "Sleep time") 18 | read_length = ProtoField.uint8("goodix.read_length", "Length") 19 | 20 | mcu_state_image = ProtoField.bool("goodix.mcu_state.is_image_valid", "Is Image Valid", 8, nil, 0x01) -- Meaning unknown 21 | mcu_state_tls = ProtoField.bool("goodix.mcu_state.is_tls_connected", "Is Tls Connected", 8, nil, 0x02) 22 | mcu_state_spi = ProtoField.bool("goodix.mcu_state.is_spi_send", "Is Spi Send", 8, nil, 0x04) -- Meaning unknown 23 | mcu_state_locked = ProtoField.bool("goodix.mcu_state.is_locked", "Is Locked", 8, nil, 0x08) -- Meaning unknown 24 | 25 | reset_sensor = ProtoField.bool("goodix.reset.sensor", "Reset Sensor", 8, nil, 0x01) 26 | reset_mcu = ProtoField.bool("goodix.reset.mcu", "Soft Reset MCU", 8, nil, 0x02) 27 | reset_sensor_copy = ProtoField.bool("goodix.reset.sensor_copy", "Reset Sensor Copy", 8, nil, 0x04) 28 | reset_number = ProtoField.uint16("goodix.reset.number", "Sensor Reset Number") 29 | 30 | register_multiple = ProtoField.bool("goodix.register.multiple", "Multiple Addresses") 31 | register_address = ProtoField.uint16("goodix.register.address", "Base Address", base.HEX) 32 | 33 | psk_flags = ProtoField.uint32("goodix.psk.flags", "PSK Flags", base.HEX) 34 | psk_length = ProtoField.uint32("goodix.psk.length", "PSK Lenght") 35 | 36 | firmware_offset = ProtoField.uint32("goodix.firmware.offset", "Firmware Offset") 37 | firmware_length = ProtoField.uint32("goodix.firmware.length", "Firmware Lenght") 38 | firmware_checksum = ProtoField.uint32("goodix.firmware.checksum", "Firmware Checksum") 39 | 40 | powerdown_scan_frequency = ProtoField.uint16("goodix.powerdown_scan_frequency", "Powerdown Scan Frequecy") 41 | 42 | config_sensor_chip = ProtoField.uint8("goodix.config_sensor_chip", "Sensor Chip", base.RANGE_STRING, 43 | {{0x00, 0x00, "GF3208"}, {0x01, 0x01, "GF3288"}, {0x02, 0x02, "GF3266"}}, 0xf0) 44 | 45 | mode = ProtoField.uint8("goodix.mode", "Mode", base.RANGE_STRING, 46 | {{0x01, 0x01, "Image, NAV or Sleep"}, {0x0c, 0x0c, "FDT Down"}, {0xd, 0xd, "FDT Manual"}, {0x0e, 0x0e, "FDT Up"}, 47 | {0x10, 0xf0, "FF"}}) 48 | base_type = ProtoField.uint8("goodix.base_type", "Base Type") 49 | 50 | protocol.fields = {pack_flags, cmd0_field, cmd1_field, length_field, checksum_field, ack_cmd, ack_true, ack_config, 51 | success, failed, number, version, enable_chip, sleep_time, mcu_state_image, mcu_state_tls, 52 | mcu_state_spi, mcu_state_locked, reset_sensor, reset_mcu, reset_sensor_copy, reset_number, 53 | register_multiple, register_address, read_length, powerdown_scan_frequency, config_sensor_chip, mode, 54 | base_type, psk_flags, psk_length, firmware_offset, firmware_length, firmware_checksum} 55 | 56 | function extract_cmd0_cmd1(cmd) 57 | return bit.rshift(cmd, 4), bit.rshift(cmd % 16, 1) 58 | end 59 | 60 | function get_cmd_name(cmd) 61 | cmd0, cmd1 = extract_cmd0_cmd1(cmd) 62 | 63 | if commands[cmd0][cmd1] ~= nil then 64 | return commands[cmd0][cmd1].name 65 | else 66 | return string.format("%s.%x", commands[cmd0].category_name, cmd1) 67 | end 68 | end 69 | 70 | commands = { 71 | [0x0] = { 72 | category_name = "NOP", 73 | [0x0] = { 74 | name = "nop", 75 | dissect_command = function(tree, buf) 76 | end 77 | } 78 | }, 79 | [0x2] = { 80 | category_name = "IMA", 81 | 82 | [0] = { 83 | name = "MCU Get Image", 84 | dissect_command = function(tree, buf) 85 | tree:add_le(mode, buf(0, 1)) 86 | tree:add_le(base_type, buf(1, 1)) 87 | end, 88 | dissect_reply = function(tree, buf) 89 | end 90 | } 91 | }, 92 | [0x3] = { 93 | category_name = "FDT", 94 | 95 | [1] = { 96 | name = "MCU Switch To Fdt Down", 97 | dissect_command = function(tree, buf) 98 | tree:add_le(mode, buf(0, 1)) 99 | tree:add_le(base_type, buf(1, 1)) 100 | end, 101 | dissect_reply = function(tree, buf) 102 | end 103 | }, 104 | [2] = { 105 | name = "MCU Switch To Fdt Up", 106 | dissect_command = function(tree, buf) 107 | tree:add_le(mode, buf(0, 1)) 108 | tree:add_le(base_type, buf(1, 1)) 109 | end, 110 | dissect_reply = function(tree, buf) 111 | end 112 | }, 113 | [3] = { 114 | name = "MCU Switch To Fdt Mode", 115 | dissect_command = function(tree, buf) 116 | tree:add_le(mode, buf(0, 1)) 117 | tree:add_le(base_type, buf(1, 1)) 118 | end, 119 | dissect_reply = function(tree, buf) 120 | end 121 | } 122 | }, 123 | [0x4] = { 124 | category_name = "FF", 125 | 126 | [0] = { 127 | name = "FF", 128 | dissect_command = function(tree, buf) 129 | tree:add_le(mode, buf(0, 1)) 130 | tree:add_le(base_type, buf(1, 1)) 131 | end, 132 | dissect_reply = function(tree, buf) 133 | end 134 | } 135 | }, 136 | [0x5] = { 137 | category_name = "NAV", 138 | 139 | [0] = { 140 | name = "NAV", 141 | dissect_command = function(tree, buf) 142 | tree:add_le(mode, buf(0, 1)) 143 | tree:add_le(base_type, buf(1, 1)) 144 | end, 145 | dissect_reply = function(tree, buf) 146 | end 147 | } 148 | }, 149 | [0x6] = { 150 | category_name = "SLE", 151 | [0] = { 152 | name = "MCU Switch To Sleep Mode", 153 | dissect_command = function(tree, buf) 154 | tree:add_le(mode, buf(0, 1)) 155 | tree:add_le(base_type, buf(1, 1)) 156 | end, 157 | dissect_reply = function(tree, buf) 158 | end 159 | } 160 | }, 161 | [0x7] = { 162 | category_name = "IDL", 163 | 164 | [0] = { 165 | name = "MCU Switch To Idle Mode", 166 | dissect_command = function(tree, buf) 167 | tree:add_le(sleep_time, buf(0, 1)) 168 | tree:add_le(base_type, buf(1, 1)) 169 | end, 170 | dissect_reply = function(tree, buf) 171 | end 172 | } 173 | }, 174 | [0x8] = { 175 | category_name = "REG", 176 | [0] = { 177 | name = "Write Sensor Register", 178 | dissect_command = function(tree, buf) 179 | tree:add_le(register_multiple, buf(0, 1)) 180 | tree:add_le(register_address, buf(1, 2)) 181 | end 182 | }, 183 | [1] = { 184 | name = "Read Sensor Register", 185 | dissect_command = function(tree, buf) 186 | tree:add_le(register_multiple, buf(0, 1)) 187 | tree:add_le(register_address, buf(1, 2)) 188 | tree:add_le(read_length, buf(3, 1)):append_text(" bytes") 189 | end, 190 | dissect_reply = function(tree, buf) 191 | end 192 | } 193 | }, 194 | [0x9] = { 195 | category_name = "CHIP", 196 | 197 | [0] = { 198 | name = "Upload Config MCU Download Chip Config", 199 | dissect_command = function(tree, buf) 200 | tree:add_le(config_sensor_chip, buf(0, 1)) 201 | end, 202 | dissect_reply = function(tree, buf) 203 | tree:add_le(success, buf(0, 1)) 204 | end 205 | }, 206 | [1] = { 207 | name = "Switch To Sleep Mode", 208 | dissect_command = function(tree, buf) 209 | tree:add_le(number, buf(0, 1)) 210 | end, 211 | dissect_reply = function(tree, buf) 212 | tree:add_le(success, buf(0, 1)) 213 | end 214 | }, 215 | [2] = { 216 | name = "Set Powerdown Scan Frequency", 217 | dissect_command = function(tree, buf) 218 | tree:add_le(powerdown_scan_frequency, buf(0, 2)) 219 | end, 220 | dissect_reply = function(tree, buf) 221 | tree:add_le(success, buf(0, 1)) 222 | end 223 | }, 224 | [3] = { 225 | name = "Enable Chip", 226 | dissect_command = function(tree, buf) 227 | tree:add_le(enable_chip, buf(0, 1)) 228 | end 229 | } 230 | }, 231 | [0xa] = { 232 | category_name = "OTHER", 233 | 234 | [1] = { 235 | name = "Reset", 236 | dissect_command = function(tree, buf) 237 | tree:add_le(reset_sensor, buf(0, 1)) 238 | tree:add_le(reset_mcu, buf(0, 1)) 239 | tree:add_le(reset_sensor_copy, buf(0, 1)) 240 | tree:add_le(sleep_time, buf(1, 1)) 241 | end, 242 | dissect_reply = function(tree, buf) 243 | tree:add_le(success, buf(0, 1)) 244 | tree:add_le(reset_number, buf(1, 2)) 245 | end 246 | }, 247 | [2] = { 248 | name = "MCU Erase App", 249 | dissect_command = function(tree, buf) 250 | tree:add_le(sleep_time, buf(1, 1)) 251 | end, 252 | dissect_reply = function(tree, buf) 253 | tree:add_le(success, buf(0, 1)) 254 | end 255 | }, 256 | [3] = { 257 | name = "Read OTP", 258 | dissect_command = function(tree, buf) 259 | end, 260 | dissect_reply = function(tree, buf) 261 | end 262 | }, 263 | [4] = { 264 | name = "Firmware Version", 265 | dissect_command = function(tree, buf) 266 | end, 267 | dissect_reply = function(tree, buf) 268 | tree:add_le(version, buf()) 269 | end 270 | }, 271 | [6] = { 272 | name = "Set PC State", 273 | dissect_command = function(tree, buf) 274 | tree:add_le(base_type, buf(0, 1)) 275 | end, 276 | dissect_reply = function(tree, buf) 277 | end 278 | }, 279 | [7] = { 280 | name = "Query MCU State", 281 | dissect_command = function(tree, buf) 282 | tree:add_le(number, buf(0, 1)) 283 | end, 284 | dissect_reply = function(tree, buf) 285 | tree:add_le(mcu_state_image, buf(1, 1)) 286 | tree:add_le(mcu_state_tls, buf(1, 1)) 287 | tree:add_le(mcu_state_spi, buf(1, 1)) 288 | tree:add_le(mcu_state_locked, buf(1, 1)) 289 | end 290 | } 291 | }, 292 | [0xb] = { 293 | category_name = "MSG", 294 | 295 | [0] = { 296 | name = "Ack", 297 | dissect_reply = function(tree, buf) 298 | tree:add_le(ack_true, buf(1, 1)) 299 | tree:add_le(ack_config, buf(1, 1)) 300 | tree:add_le(ack_cmd, buf(0, 1)):append_text(" (" .. get_cmd_name(buf(0, 1):le_uint()) .. ")") 301 | end 302 | } 303 | }, 304 | [0xc] = { 305 | category_name = "NOTI", 306 | 307 | [2] = { 308 | name = "Set DRV State", 309 | dissect_command = function(tree, buf) 310 | end, 311 | dissect_reply = function(tree, buf) 312 | end 313 | }, 314 | [3] = { 315 | name = "MCU Set Led State", 316 | dissect_command = function(tree, buf) 317 | end, 318 | dissect_reply = function(tree, buf) 319 | end 320 | } 321 | 322 | }, 323 | [0xd] = { 324 | category_name = "TLSCONN", 325 | 326 | [0] = { 327 | name = "Request TLS Connection", 328 | dissect_command = function(tree, buf) 329 | end 330 | }, 331 | [1] = { 332 | name = "MCU Get POV Image", 333 | dissect_command = function(tree, buf) 334 | -- Seemingly gives the same response over TLS as sending Ima.0 does, 335 | -- but without reading a new image from the sensor. Not seen used, 336 | -- untested. 337 | end, 338 | dissect_reply = function(tree, buf) 339 | end 340 | }, 341 | [2] = { 342 | name = "TLS Successfully Established", 343 | dissect_command = function(tree, buf) 344 | end 345 | }, 346 | 347 | [3] = { 348 | name = "POV Image Check", 349 | dissect_command = function(tree, buf) 350 | end, 351 | dissect_reply = function(tree, buf) 352 | end 353 | } 354 | }, 355 | [0xe] = { 356 | category_name = "PROD", 357 | [0] = { 358 | name = "Preset Psk Write", 359 | dissect_command = function(tree, buf) 360 | tree:add_le(psk_flags, buf(0, 4)) 361 | tree:add_le(psk_length, buf(4, 4)) 362 | end, 363 | dissect_reply = function(tree, buf) 364 | tree:add_le(failed, buf(0, 1)) 365 | end 366 | }, 367 | [2] = { 368 | name = "Preset Psk Read", 369 | dissect_command = function(tree, buf) 370 | tree:add_le(psk_flags, buf(0, 4)) 371 | tree:add_le(psk_length, buf(4, 4)) 372 | end, 373 | dissect_reply = function(tree, buf) 374 | tree:add_le(failed, buf(0, 1)) 375 | tree:add_le(psk_flags, buf(1, 4)) 376 | tree:add_le(psk_length, buf(5, 4)) 377 | end 378 | } 379 | }, 380 | [0xf] = { 381 | category_name = "UPFW", 382 | [0] = { 383 | name = "Write Firmware", 384 | dissect_command = function(tree, buf) 385 | tree:add_le(firmware_offset, buf(0, 4)) 386 | tree:add_le(firmware_length, buf(4, 4)) 387 | end, 388 | dissect_reply = function(tree, buf) 389 | tree:add_le(success, buf(0, 1)) 390 | end 391 | }, 392 | [1] = { 393 | name = "Read Firmware", 394 | dissect_command = function(tree, buf) 395 | tree:add_le(firmware_offset, buf(0, 4)) 396 | tree:add_le(firmware_length, buf(4, 4)) 397 | end, 398 | dissect_reply = function(tree, buf) 399 | end 400 | }, 401 | [2] = { 402 | name = "Check Firmware", 403 | dissect_command = function(tree, buf) 404 | tree:add_le(firmware_offset, buf(0, 4)) 405 | tree:add_le(firmware_length, buf(4, 4)) 406 | tree:add_le(firmware_checksum, buf(8, 4)) 407 | end, 408 | dissect_reply = function(tree, buf) 409 | tree:add_le(success, buf(0, 1)) 410 | end 411 | }, 412 | [3] = { 413 | name = "Get IAP Version", 414 | dissect_command = function(tree, buf) 415 | tree:add_le(read_length, buf(0, 1)) 416 | end, 417 | dissect_reply = function(tree, buf) 418 | tree:add_le(version, buf()) 419 | end 420 | } 421 | } 422 | } 423 | 424 | function protocol.dissector(buffer, pinfo, tree) 425 | length = buffer:len() 426 | if length == 0 then 427 | return 428 | end 429 | 430 | pinfo.cols.protocol = "Goodix" 431 | 432 | local subtree = tree:add(protocol, buffer(), "Goodix Message Protocol") 433 | 434 | body_buffer = buffer(3, buffer:len() - 4):tvb() 435 | 436 | subtree:add_le(cmd0_field, buffer(0, 1)) 437 | subtree:add_le(cmd1_field, buffer(0, 1)) 438 | local length_bytes = buffer(1, 2) 439 | subtree:add_le(length_field, length_bytes):append_text(" bytes (including checksum)") 440 | subtree:add_le(checksum_field, buffer(3 + length_bytes:le_uint() - 1, 1)) 441 | 442 | from_host = pinfo.src == Address.ip("1.1.1.1") or tostring(pinfo.src) == "host" 443 | 444 | local cmd_subtree = subtree:add(protocol, body_buffer()) 445 | 446 | cmd_val = buffer(0, 1):le_uint() 447 | cmd0_val, cmd1_val = extract_cmd0_cmd1(cmd_val) 448 | 449 | if from_host then 450 | summary = "Command: " .. get_cmd_name(cmd_val) 451 | 452 | if commands[cmd0_val][cmd1_val] ~= nil then 453 | commands[cmd0_val][cmd1_val].dissect_command(cmd_subtree, body_buffer) 454 | end 455 | else 456 | summary = "Reply: " .. get_cmd_name(cmd_val) 457 | 458 | if commands[cmd0_val][cmd1_val] ~= nil then 459 | commands[cmd0_val][cmd1_val].dissect_reply(cmd_subtree, body_buffer) 460 | end 461 | end 462 | 463 | cmd_subtree.text = summary 464 | pinfo.cols.info = summary 465 | end 466 | 467 | DissectorTable.get("tls.port"):add(1, protocol) 468 | DissectorTable.get("tls.port"):add(1, protocol) 469 | 470 | DissectorTable.get("usb.protocol"):add_for_decode_as(protocol) 471 | DissectorTable.get("usb.product"):add_for_decode_as(protocol) 472 | DissectorTable.get("usb.device"):add_for_decode_as(protocol) 473 | 474 | goodix_pack = Proto("goodix.pack", "Goodix Fingerprint USB Package") 475 | goodix_pack_flags = ProtoField.uint8("goodix.pack.flags", "Flags", base.HEX) 476 | goodix_pack_length = ProtoField.uint16("goodix.pack.length", "Length", base.DEC) 477 | goodix_pack_ckecksum = ProtoField.uint8("goodix.pack.checksum", "Checksum", base.HEX) 478 | 479 | function goodix_pack.init() 480 | state_map = 0 481 | missing_bytes = 0 482 | cache = {} 483 | end 484 | 485 | goodix_pack.fields = {goodix_pack_flags, goodix_pack_length, goodix_pack_ckecksum} 486 | 487 | function goodix_pack.dissector(buffer, pinfo, tree) 488 | length = buffer:len() 489 | if length == 0 then 490 | return 491 | end 492 | 493 | pinfo.cols.protocol = "Goodix Pack" 494 | 495 | local subtree = tree:add(goodix_pack, buffer(), "Goodix Message Pack") 496 | 497 | local ccache = cache[pinfo.number] 498 | 499 | if ccache == nil then 500 | if missing_bytes > 0 then 501 | pinfo.cols.info = string.format("Goodix Pack Reassembly, missing %d bytes", missing_bytes) 502 | state_map = state_map .. buffer:bytes() 503 | if buffer:len() < missing_bytes then 504 | missing_bytes = missing_bytes - buffer:len() 505 | cache[pinfo.number] = { 506 | complete = 0, 507 | missing = missing_bytes 508 | } 509 | return 510 | else 511 | new_buffer = ByteArray.tvb(state_map)(0):tvb("Reassembled TVB") 512 | cache[pinfo.number] = { 513 | complete = 1, 514 | content = state_map 515 | } 516 | state_map = 0 517 | missing_bytes = 0 518 | end 519 | else 520 | new_buffer = buffer 521 | cache[pinfo.number] = { 522 | complete = 1 523 | } 524 | end 525 | else 526 | if ccache.complete and ccache.content then 527 | new_buffer = ByteArray.tvb(ccache.content)(0):tvb("Reassembled TVB") 528 | else 529 | new_buffer = buffer 530 | end 531 | end 532 | 533 | buffer = new_buffer 534 | 535 | flags_byte = buffer(0, 1) 536 | subtree:add_le(goodix_pack_flags, flags_byte) 537 | length_bytes = buffer(1, 2) 538 | subtree:add_le(goodix_pack_length, length_bytes):append_text(" bytes") 539 | ckecksum_byte = buffer(3, 1) 540 | subtree:add_le(goodix_pack_ckecksum, ckecksum_byte) 541 | 542 | local flags_int = flags_byte:le_uint() 543 | local length_int = length_bytes:le_uint() 544 | 545 | pinfo.cols.info = string.format("Goodix Pack 0x%x %d", flags_int, buffer:len()) 546 | 547 | if flags_int == 0xa0 or flags_int == 0xb0 or flags_int == 0xb2 then 548 | if length_int + 4 > buffer:len() then 549 | state_map = buffer:bytes() 550 | missing_bytes = length_int - (buffer:len() - 4) 551 | 552 | pinfo.cols.info = string.format("Goodix Pack Fragment Start 0x%x %d", flags_int, buffer:len()) 553 | return 554 | end 555 | elseif ccache.complete == 0 then 556 | pinfo.cols.info = string.format("Goodix Pack Fragment Continue %d, %d", buffer:len(), ccache.missing) 557 | return 558 | end 559 | 560 | if flags_int == 0xa0 then 561 | body_buffer = buffer(4, length_int):tvb() 562 | second_dissector = Dissector.get("goodix") 563 | second_dissector:call(body_buffer, pinfo, subtree) 564 | elseif flags_int == 0xb0 then 565 | Dissector.get("tls"):call(buffer(4, length_int):tvb(), pinfo, tree) 566 | elseif flags_int == 0xb2 then 567 | Dissector.get("tls"):call(buffer(4 + 9, length_int - 9):tvb(), pinfo, tree) 568 | else 569 | body_buffer = buffer(4, buffer:len() - 4):tvb() 570 | cmd_subtree = subtree:add(goodix_pack, body_buffer()) 571 | pinfo.cols.info = string.format("Goodix Pack Unknown 0x%02x", flags_int) 572 | 573 | end 574 | 575 | end 576 | 577 | usb_table = DissectorTable.get("usb.bulk") 578 | 579 | usb_table:add(0x000a, goodix_pack) 580 | 581 | usb_table:add(0x00ff, goodix_pack) 582 | 583 | usb_table:add(0xffff, goodix_pack) 584 | -------------------------------------------------------------------------------- /wireshark/wrapless_goodix_message.lua: -------------------------------------------------------------------------------- 1 | protocol = Proto("goodix", "Goodix Fingerprint Sensor Message Protocol") 2 | 3 | cmd0_field = ProtoField.uint8("goodix.cmd0", "Command 0", base.HEX, nil, 0xf0) 4 | cmd1_field = ProtoField.uint8("goodix.cmd1", "Command 1", base.HEX, nil, 0x0e) 5 | contd_field = ProtoField.bool("goodix.contd", "Continued", 8, nil, 0x1) 6 | length_field = ProtoField.uint16("goodix.length", "Length", base.DEC) 7 | checksum_field = ProtoField.uint8("goodix.checksum", "Checksum", base.HEX) 8 | 9 | ack_config = ProtoField.bool("goodix.ack.has_no_config", "MCU has no config", 2, nil, 0x02) 10 | ack_true = ProtoField.bool("goodix.ack.true", "Always True", 2, nil, 0x01) 11 | ack_cmd = ProtoField.uint8("goodix.ack.cmd", "ACK Command", base.HEX) 12 | success = ProtoField.bool("goodix.success", "Success") 13 | failed = ProtoField.bool("goodix.failed", "Failed") 14 | power_isolate = ProtoField.uint8("goodix.ec_control.power_isolate", "Power isolate", base.HEX) 15 | remote_wakeup = ProtoField.uint8("goodix.ec_control.remote_wakeup", "Remote wakeup", base.HEX) 16 | 17 | version = ProtoField.string("goodix.version", "Version") 18 | enable_chip = ProtoField.bool("goodix.enable_chip", "Enable chip") 19 | sleep_time = ProtoField.uint8("goodix.sleep_time", "Sleep time") 20 | read_length = ProtoField.uint8("goodix.read_length", "Length") 21 | 22 | reset_sensor = ProtoField.bool("goodix.reset.sensor", "Reset Sensor", 8, nil, 0x01) 23 | reset_mcu = ProtoField.bool("goodix.reset.mcu", "Soft Reset MCU", 8, nil, 0x02) 24 | reset_reply_irq = ProtoField.bool("goodix.reset.reply_irq", "Reply with IRQ", 8, nil, 0x04) 25 | reset_irq_status = ProtoField.uint16("goodix.reset.irq_status", "IRQ Status", base.HEX) 26 | 27 | register_multiple = ProtoField.bool("goodix.register.multiple", "Multiple Addresses") 28 | register_address = ProtoField.uint16("goodix.register.address", "Base Address", base.HEX) 29 | 30 | psk_msg_type = ProtoField.uint32("goodix.psk_msg.type", "PSK message type", base.RANGE_STRING, 31 | {{0xb001, 0xb001, "SGX Sealed PSK"}, {0xb002, 0xb002, "Encrypted-Signed PSK"}, {0xb003, 0xb003, "PSK SHA256 Hash"}}) 32 | psk_msg_length = ProtoField.uint32("goodix.psk_msg.length", "PSK message length", base.UNIT_STRING, {" bytes"}) 33 | psk_msg_content = ProtoField.bytes("goodix.psk_msg.content", "PSK message content", base.SPACE) 34 | 35 | gtls_type = ProtoField.uint32("goodix.gtls.type", "TLS Handshake message type", base.RANGE_STRING, 36 | {{0xff01, 0xff01, "Client hello (client_random)"}, 37 | {0xff02, 0xff02, "Server identity (server_random | server_identity)"}, 38 | {0xff03, 0xff03, "Client done (client_identity | 0xeeeeeeee)"}, 39 | {0xff04, 0xff04, "Server done"}}) 40 | gtls_length = ProtoField.uint32("goodix.gtls.length", "TLS Handshake message length", base.UNIT_STRING, {" bytes"}) 41 | gtls_content = ProtoField.bytes("goodix.gtls.content", "TLS Handshake content", base.SPACE) 42 | 43 | image_type = ProtoField.uint32("goodix.image.type", "Image message type", base.HEX) 44 | image_length = ProtoField.uint32("goodix.image.length", "Image message length", base.UNIT_STRING, {" bytes"}) 45 | image_content = ProtoField.bytes("goodix.image.content", "Image message content", base.SPACE) 46 | 47 | fdt_irq_status = ProtoField.uint16("goodix.fdt.irq_status", "Milan FDT IRQ status") 48 | fdt_touchflag = ProtoField.uint16("goodix.fdt.touchflag", "FDT touchflag") 49 | fdt_content = ProtoField.bytes("goodix.fdt.content", "FDT message content", base.SPACE) 50 | 51 | firmware_offset = ProtoField.uint32("goodix.firmware.offset", "Firmware Offset") 52 | firmware_length = ProtoField.uint32("goodix.firmware.length", "Firmware Lenght") 53 | firmware_checksum = ProtoField.uint32("goodix.firmware.checksum", "Firmware Checksum") 54 | 55 | powerdown_scan_frequency = ProtoField.uint16("goodix.powerdown_scan_frequency", "Powerdown Scan Frequecy") 56 | 57 | config_sensor_chip = ProtoField.uint8("goodix.config_sensor_chip", "Sensor Chip", base.RANGE_STRING, 58 | {{0x00, 0x00, "GF3208"}, {0x01, 0x01, "GF3288"}, {0x02, 0x02, "GF3266"}}, 0xf0) 59 | 60 | mode = ProtoField.uint8("goodix.mode", "Mode", base.RANGE_STRING, 61 | {{0x01, 0x01, "Image, NAV or Sleep"}, {0x0c, 0x0c, "FDT Down"}, {0xd, 0xd, "FDT Manual"}, {0x0e, 0x0e, "FDT Up"}, 62 | {0x10, 0xf0, "FF"}}) 63 | base_type = ProtoField.uint8("goodix.base_type", "Base Type") 64 | 65 | protocol.fields = {pack_flags, cmd0_field, cmd1_field, contd_field, length_field, checksum_field, ack_cmd, ack_true, ack_config, 66 | success, failed, power_isolate, remote_wakeup, version, enable_chip, sleep_time, 67 | reset_sensor, reset_mcu, reset_reply_irq, reset_irq_status, 68 | register_multiple, register_address, read_length, powerdown_scan_frequency, config_sensor_chip, mode, 69 | base_type, psk_msg_type, psk_msg_length, psk_msg_content, gtls_type, gtls_length, gtls_content, 70 | image_type, image_length, image_content, firmware_offset, firmware_length, firmware_checksum, 71 | fdt_irq_status, fdt_touchflag, fdt_content} 72 | 73 | function extract_cmd0_cmd1(cmd) 74 | return bit.rshift(cmd, 4), bit.rshift(cmd % 16, 1) 75 | end 76 | 77 | function get_cmd_name(cmd) 78 | cmd0, cmd1 = extract_cmd0_cmd1(cmd) 79 | 80 | if commands[cmd0][cmd1] ~= nil then 81 | return commands[cmd0][cmd1].name 82 | else 83 | return string.format("%s.%x", commands[cmd0].category_name, cmd1) 84 | end 85 | end 86 | 87 | commands = { 88 | [0x0] = { -- Correct 89 | category_name = "PING", 90 | 91 | [0x0] = { 92 | name = "Ping", 93 | dissect_command = function(tree, buf) 94 | end 95 | } 96 | }, 97 | [0x2] = { 98 | category_name = "IMA", 99 | 100 | [0] = { 101 | name = "MCU Get Image", 102 | dissect_command = function(tree, buf) 103 | -- bits[0]: maybe restart on failure 104 | -- bits[6]: is finger (unused) 105 | -- bits[7]: SPI set TX flag 106 | -- bytes[1]: HV enable (1 bit) | HV value (4 bits) 107 | -- bytes[2:4]: DAC 108 | end, 109 | dissect_reply = function(tree, buf) 110 | tree:add_le(image_type, buf(0, 4)) 111 | tree:add_le(image_length, buf(4, 4)) 112 | tree:add(image_content, buf(8)) 113 | end 114 | } 115 | }, 116 | [0x3] = { -- Correct 117 | category_name = "FDT", 118 | 119 | [1] = { 120 | name = "MCU Switch To Fdt Down", 121 | dissect_command = function(tree, buf) 122 | -- bits[0]: reply immediately 123 | -- bits[1]: 0: FDT down, 1: FDT up 124 | -- bits[2]: SPI read touch mask 125 | -- bits[3]: SPI read FDT base 126 | -- bits[4]: SPI send 0xc1 0xa1 127 | -- bits[7]: SPI set TX flag 128 | -- bits[8]: Update FDT base 129 | -- bytes[2:0x20] FDT base 130 | end, 131 | dissect_reply = function(tree, buf) 132 | tree:add_le(fdt_irq_status, buf(0, 2)) 133 | tree:add_le(fdt_touchflag, buf(2, 2)) 134 | tree:add(fdt_content, buf(4)) 135 | end 136 | }, 137 | [2] = { 138 | name = "MCU Switch To Fdt Up", 139 | dissect_command = function(tree, buf) 140 | -- Same as 1 141 | end, 142 | dissect_reply = function(tree, buf) 143 | tree:add_le(fdt_irq_status, buf(0, 2)) 144 | tree:add_le(fdt_touchflag, buf(2, 2)) 145 | tree:add(fdt_content, buf(4)) 146 | end 147 | }, 148 | [3] = { 149 | name = "MCU Switch To Fdt Mode", 150 | dissect_command = function(tree, buf) 151 | -- Same as 1 152 | end, 153 | dissect_reply = function(tree, buf) 154 | tree:add_le(fdt_irq_status, buf(0, 2)) 155 | tree:add_le(fdt_touchflag, buf(2, 2)) 156 | tree:add(fdt_content, buf(4)) 157 | end 158 | } 159 | }, 160 | [0x4] = { 161 | category_name = "FF", 162 | 163 | [0] = { 164 | name = "FF", 165 | dissect_command = function(tree, buf) 166 | -- bits[2]: SPI read touch mask 167 | -- bits[3]: SPI read FDT base 168 | -- bits[4]: SPI send 0xc4 169 | -- bits[7]: SPI set TX flag 170 | end, 171 | dissect_reply = function(tree, buf) 172 | -- byte[0:2]: IRQ 173 | -- byte[2:4]: touch mask (2 bytes) (optional) 174 | -- byte[4:28]: FDT base (0x18 bytes) (optional) 175 | end 176 | } 177 | }, 178 | [0x5] = { 179 | category_name = "NAV", 180 | 181 | [0] = { 182 | name = "NAV", 183 | dissect_command = function(tree, buf) 184 | -- bits[0]: maybe restart on failure 185 | -- bits[7]: SPI set TX flag 186 | end, 187 | dissect_reply = function(tree, buf) 188 | tree:add_le(image_type, buf(0, 4)) 189 | tree:add_le(image_length, buf(4, 4)) 190 | tree:add(image_content, buf(8)) 191 | end 192 | } 193 | }, 194 | [0x6] = { 195 | category_name = "SLE", 196 | 197 | [0] = { 198 | name = "MCU Switch To Sleep Mode", 199 | dissect_command = function(tree, buf) 200 | -- bits[0]: Put sensor in sleep mode 201 | -- bits[1]: Put MCU in deep sleep 202 | -- bits[2]: Call suspend callback 203 | end, 204 | } 205 | }, 206 | [0x7] = { 207 | category_name = "IDL", 208 | 209 | [0] = { 210 | name = "MCU Switch To Idle Mode", 211 | dissect_command = function(tree, buf) 212 | tree:add_le(sleep_time, buf(0, 1)) 213 | end, 214 | } 215 | }, 216 | [0x8] = { -- Correct 217 | category_name = "REG", 218 | 219 | [0] = { 220 | name = "Write Sensor Register", 221 | dissect_command = function(tree, buf) 222 | tree:add_le(register_multiple, buf(0, 1)) 223 | -- repeated if multiple 224 | tree:add_le(register_address, buf(1, 2)) 225 | -- bytes[3:5]: value to write 226 | -- end repeated 227 | end 228 | }, 229 | [1] = { 230 | name = "Read Sensor Register", 231 | dissect_command = function(tree, buf) 232 | tree:add_le(register_multiple, buf(0, 1)) 233 | -- repeated if multiple 234 | tree:add_le(register_address, buf(1, 2)) 235 | -- end repeated 236 | -- if not multiple 237 | tree:add_le(read_length, buf(3, 2)):append_text(" bytes") 238 | end, 239 | dissect_reply = function(tree, buf) 240 | -- bytes[:]: data 241 | end 242 | } 243 | }, 244 | [0x9] = { 245 | category_name = "CHIP", 246 | 247 | [0] = { -- Correct 248 | name = "Set chip config", 249 | dissect_command = function(tree, buf) 250 | -- bytes[0:0x100]: sensor config 251 | end, 252 | dissect_reply = function(tree, buf) 253 | tree:add_le(success, buf(0, 1)) 254 | end 255 | }, 256 | [1] = { 257 | name = "Select suspend callback", 258 | dissect_command = function(tree, buf) 259 | -- bits[2]: select suspend callback (override config) 260 | -- bits[3]: select suspend-related bit (override config) 261 | -- bits[4:8]: 0 or 2, otherwise nop 262 | end, 263 | dissect_reply = function(tree, buf) 264 | tree:add_le(success, buf(0, 1)) 265 | end 266 | }, 267 | [2] = { 268 | name = "Power-related GPIOA pin 1", 269 | dissect_command = function(tree, buf) 270 | end, 271 | dissect_reply = function(tree, buf) 272 | -- byte[0]: GPIOA pin 1 (maybe clock?) read result 273 | end 274 | }, 275 | [3] = { -- Correct 276 | name = "Get USB PID buffer", 277 | dissect_command = function(tree, buf) 278 | end, 279 | dissect_reply = function(tree, buf) 280 | -- byte[0:0x20]: 0x20 buffer 281 | end 282 | }, 283 | [4] = { -- Correct 284 | name = "Flash USB PID buffer", 285 | dissect_command = function(tree, buf) 286 | -- byte[0:0x20]: 0x20 buffer 287 | -- byte[0x20:0x24]: 0x20 buffer mask 288 | end, 289 | dissect_reply = function(tree, buf) 290 | tree:add_le(success, buf(0, 1)) 291 | end 292 | }, 293 | [6] = { 294 | name = "Override handling message", 295 | dissect_command = function(tree, buf) 296 | -- byte[0]: Should override 297 | end 298 | } 299 | }, 300 | [0xa] = { -- Correct 301 | category_name = "OTHER", 302 | 303 | [0] = { 304 | name = "Set SPI prescaler", 305 | dissect_command = function(tree, buf) 306 | -- byte[0]: SPI clock prescaler 307 | -- byte[1]: 0 -> get, 1 -> set 308 | end, 309 | dissect_reply = function(tree, buf) 310 | tree:add_le(success, buf(0, 1)) 311 | -- byte[1]: SPI clock prescaler 312 | end 313 | }, 314 | [1] = { 315 | name = "Reset", 316 | dissect_command = function(tree, buf) 317 | tree:add_le(reset_sensor, buf(0, 1)) 318 | tree:add_le(reset_mcu, buf(0, 1)) 319 | tree:add_le(reset_reply_irq, buf(0, 1)) 320 | tree:add_le(sleep_time, buf(1, 1)) 321 | end, 322 | dissect_reply = function(tree, buf) 323 | -- byte[0]: event happened 324 | -- byte[1:3]: IRQ (only if event happened) 325 | end 326 | }, 327 | [2] = { 328 | name = "Delete APP firmware info", 329 | dissect_command = function(tree, buf) 330 | tree:add_le(sleep_time, buf(1, 1)) 331 | end, 332 | dissect_reply = function(tree, buf) 333 | tree:add_le(success, buf(0, 1)) 334 | end 335 | }, 336 | [3] = { 337 | name = "Read OTP", 338 | dissect_command = function(tree, buf) 339 | end, 340 | dissect_reply = function(tree, buf) 341 | -- byte[0:0x20]: OTP 342 | end 343 | }, 344 | [4] = { 345 | name = "Firmware Version", 346 | dissect_command = function(tree, buf) 347 | end, 348 | dissect_reply = function(tree, buf) 349 | tree:add_le(version, buf()) 350 | end 351 | }, 352 | [5] = { 353 | name = "SPI send", 354 | dissect_command = function(tree, buf) 355 | -- byte[0]: value to send 356 | -- byte[1]: sleep time 357 | end, 358 | dissect_reply = function(tree, buf) 359 | end 360 | }, 361 | [6] = { 362 | name = "Flash OTP", 363 | dissect_command = function(tree, buf) 364 | -- byte[0:0x20]: OTP 365 | -- byte[0x20:0x24]: OTP mask 366 | end, 367 | dissect_reply = function(tree, buf) 368 | tree:add_le(success, buf(0, 1)) 369 | end 370 | }, 371 | [7] = { 372 | name = "EC control", 373 | dissect_command = function(tree, buf) 374 | tree:add_le(power_isolate, buf(0, 1)) 375 | tree:add_le(remote_wakeup, buf(1, 1)) 376 | end, 377 | dissect_reply = function(tree, buf) 378 | tree:add_le(success, buf(0, 1)) 379 | end 380 | } 381 | }, 382 | [0xb] = { -- Correct 383 | category_name = "MSG", 384 | 385 | [0] = { 386 | name = "Ack", 387 | dissect_reply = function(tree, buf) 388 | tree:add_le(ack_true, buf(1, 1)) 389 | tree:add_le(ack_config, buf(1, 1)) 390 | tree:add_le(ack_cmd, buf(0, 1)):append_text(" (" .. get_cmd_name(buf(0, 1):le_uint()) .. ")") 391 | end 392 | } 393 | }, 394 | [0xc] = { 395 | category_name = "NOTI", 396 | 397 | [0] = { 398 | name = "ESD Happened", -- Electro Static Discharge? 399 | dissect_reply = function(tree, buf) 400 | -- byte[0:2]: IRQ 401 | end 402 | }, 403 | [1] = { 404 | name = "Wake up", 405 | dissect_reply = function(tree, buf) 406 | -- byte[0:2]: IRQ 407 | end 408 | }, 409 | [2] = { 410 | name = "Press Power Button", 411 | dissect_reply = function(tree, buf) 412 | tree:add_le(success, buf(0, 1)) 413 | end 414 | }, 415 | }, 416 | [0xd] = { -- Correct 417 | category_name = "TLSHANDSHAKE", 418 | 419 | [1] = { 420 | name = "TLS handshake", 421 | dissect_command = function(tree, buf) 422 | tree:add_le(gtls_type, buf(0, 4)) 423 | tree:add_le(gtls_length, buf(4, 4)) 424 | tree:add(gtls_content, buf(8)) 425 | end, 426 | dissect_reply = function(tree, buf) 427 | tree:add_le(gtls_type, buf(0, 4)) 428 | tree:add_le(gtls_length, buf(4, 4)) 429 | tree:add(gtls_content, buf(8)) 430 | end 431 | }, 432 | }, 433 | [0xe] = { -- Correct 434 | category_name = "PROD", 435 | 436 | [1] = { 437 | name = "PSK setup write", 438 | dissect_command = function(tree, buf) 439 | tree:add_le(psk_msg_type, buf(0, 4)) 440 | tree:add_le(psk_msg_length, buf(4, 4)) 441 | tree:add(psk_msg_content, buf(8)) 442 | end, 443 | dissect_reply = function(tree, buf) 444 | tree:add_le(failed, buf(0, 1)) 445 | end 446 | }, 447 | [2] = { 448 | name = "PSK setup read", 449 | dissect_command = function(tree, buf) 450 | tree:add_le(psk_msg_type, buf(0, 4)) 451 | end, 452 | dissect_reply = function(tree, buf) 453 | tree:add_le(failed, buf(0, 1)) 454 | tree:add_le(psk_msg_type, buf(1, 4)) 455 | tree:add_le(psk_msg_length, buf(5, 4)) 456 | tree:add(psk_msg_content, buf(9)) 457 | end 458 | } 459 | }, 460 | [0xf] = { -- Correct 461 | category_name = "FLASH", 462 | 463 | [0] = { 464 | name = "Write Firmware", 465 | dissect_command = function(tree, buf) 466 | -- byte[0:2]: write base 467 | -- byte[2:4]: write size (< 0x8000), bit 0xF: base * 0x400 468 | -- byte[4:]: data to write 469 | end, 470 | dissect_reply = function(tree, buf) 471 | tree:add_le(success, buf(0, 1)) 472 | end 473 | }, 474 | [1] = { 475 | name = "Read Firmware", 476 | dissect_command = function(tree, buf) 477 | -- byte[0:2]: read base 478 | -- byte[2:4]: read size (< 0x8000), bit 0xF: base * 0x400 479 | end, 480 | dissect_reply = function(tree, buf) 481 | -- byte[:]: read reply 482 | end 483 | }, 484 | [2] = { 485 | name = "Verify and Protect/Enable Firmware", 486 | dissect_command = function(tree, buf) 487 | -- byte[0:2]: check base 488 | -- byte[2:4]: size_1 489 | -- byte[4:8]: crc32 490 | -- byte[8]: enable write protection 491 | -- byte[9:10]: size_2 492 | end, 493 | dissect_reply = function(tree, buf) 494 | tree:add_le(success, buf(0, 1)) 495 | end 496 | }, 497 | [3] = { 498 | name = "Erase Option Byte", 499 | dissect_command = function(tree, buf) 500 | end, 501 | dissect_reply = function(tree, buf) 502 | tree:add_le(success, buf(0, 1)) 503 | end 504 | }, 505 | [4] = { 506 | name = "Get/Program Option Byte", 507 | dissect_command = function(tree, buf) 508 | -- byte[0]: 0 - program option byte, 1 - get option byte 509 | -- byte[1:0x19]: option byte (optional) 510 | end, 511 | dissect_reply = function(tree, buf) 512 | -- byte[0]: 1 - success (in IAP firmware) (optional) 513 | -- byte[1]: 0 - success (in APP firmware - BUG) (optional) 514 | -- byte[0:0x18] option byte (optional) 515 | end 516 | } 517 | } 518 | } 519 | 520 | function protocol.init() 521 | state_map = ByteArray.new() 522 | total_bytes = 0 523 | missing_bytes = 0 524 | cache = {} 525 | end 526 | 527 | function protocol.dissector(buffer, pinfo, tree) 528 | length = buffer:len() 529 | if length == 0 then 530 | return 531 | end 532 | 533 | pinfo.cols.protocol = "Goodix" 534 | 535 | local subtree = tree:add(protocol, buffer(), "Goodix Message Protocol") 536 | 537 | from_host = pinfo.src == Address.ip("1.1.1.1") or tostring(pinfo.src) == "host" 538 | cmd_val = buffer(0, 1):le_uint() 539 | packet_is_cont = bit.band(cmd_val, 1) > 0 540 | 541 | subtree:add_le(cmd0_field, buffer(0, 1)) 542 | subtree:add_le(cmd1_field, buffer(0, 1)) 543 | subtree:add_le(contd_field, buffer(0, 1)) 544 | 545 | if from_host then 546 | summary = "Command: " .. get_cmd_name(cmd_val) 547 | else 548 | summary = "Reply: " .. get_cmd_name(cmd_val) 549 | end 550 | 551 | if packet_is_cont then 552 | if cache[pinfo.number] ~= nil then 553 | missing_bytes = cache[pinfo.number].missing_bytes 554 | total_bytes = cache[pinfo.number].total_bytes 555 | else 556 | cache[pinfo.number] = {} 557 | cache[pinfo.number].missing_bytes = missing_bytes 558 | cache[pinfo.number].total_bytes = total_bytes 559 | end 560 | 561 | local msg_length = missing_bytes 562 | local packet_length = buffer():len() - 1 563 | 564 | if msg_length > packet_length then 565 | local packet_subtree = subtree:add(protocol, buffer(1, packet_length)) 566 | packet_subtree.text = "Data " .. packet_length .. " of " .. total_bytes - 1 .. " bytes" 567 | pinfo.cols.info = summary .. " cont." 568 | missing_bytes = missing_bytes - packet_length 569 | state_map:append(buffer(1):bytes()) 570 | return 571 | else 572 | local packet_subtree = subtree:add(protocol, buffer(1, msg_length - 1)) 573 | packet_subtree.text = "Data " .. msg_length - 1 .. " of " .. total_bytes - 1 .. " bytes" 574 | pinfo.cols.info = summary .. " end" 575 | state_map:append(buffer(1, msg_length):bytes()) 576 | 577 | if cache[pinfo.number].buf == nil then 578 | cache[pinfo.number].buf = state_map 579 | end 580 | 581 | msg_buffer = ByteArray.tvb(cache[pinfo.number].buf, "Message") 582 | missing_bytes = 0 583 | missing_bytes = 0 584 | state_map = ByteArray.new() 585 | total_bytes = 0 586 | end 587 | 588 | else 589 | local msg_length = buffer(1, 2):le_uint() 590 | local packet_length = buffer():len() - 3 591 | 592 | if msg_length > packet_length then 593 | local packet_subtree = subtree:add(protocol, buffer(3, packet_length)) 594 | packet_subtree.text = "Data " .. packet_length .. " of " .. msg_length - 1 .. " bytes" 595 | pinfo.cols.info = summary .. " start" 596 | missing_bytes = msg_length - packet_length 597 | total_bytes = msg_length 598 | state_map = buffer():bytes() 599 | return 600 | else 601 | 602 | local packet_subtree = subtree:add(protocol, buffer(3, msg_length - 1)) 603 | packet_subtree.text = "Data " .. msg_length - 1 .. " of " .. msg_length - 1 .. " bytes" 604 | pinfo.cols.info = summary 605 | msg_buffer = buffer 606 | end 607 | end 608 | local msg_length = msg_buffer(1, 2):le_uint() 609 | subtree:add_le(length_field, msg_buffer(1, 2)):append_text(" bytes (including checksum)") 610 | subtree:add_le(checksum_field, msg_buffer(3 + msg_length - 1, 1)) 611 | 612 | body_buffer = msg_buffer(3, msg_length - 1):tvb() 613 | 614 | local cmd_subtree = subtree:add(protocol, body_buffer()) 615 | 616 | cmd0_val, cmd1_val = extract_cmd0_cmd1(cmd_val) 617 | 618 | if from_host then 619 | if commands[cmd0_val][cmd1_val] ~= nil then 620 | commands[cmd0_val][cmd1_val].dissect_command(cmd_subtree, body_buffer) 621 | end 622 | else 623 | if commands[cmd0_val][cmd1_val] ~= nil then 624 | commands[cmd0_val][cmd1_val].dissect_reply(cmd_subtree, body_buffer) 625 | end 626 | end 627 | cmd_subtree.text = summary 628 | end 629 | 630 | usb_table = DissectorTable.get("usb.bulk") 631 | usb_table:add(0x000a, protocol) 632 | usb_table:add(0x00ff, protocol) 633 | usb_table:add(0xffff, protocol) 634 | -------------------------------------------------------------------------------- /wrapless.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | import enum 3 | import logging 4 | import struct 5 | 6 | import crccheck 7 | import Crypto.Hash.HMAC 8 | import Crypto.Hash.SHA256 9 | import Crypto.Cipher.AES 10 | import usb 11 | 12 | import protocol 13 | 14 | USB_CHUNK_SIZE = 0x40 15 | FIRMWARE_CHUNK_SIZE = 0x400 16 | 17 | 18 | @dataclasses.dataclass 19 | class Message: 20 | _category: int 21 | _command: int 22 | _payload: bytes 23 | 24 | @property 25 | def category(self): 26 | return self._category 27 | 28 | @category.setter 29 | def category(self, category): 30 | assert category <= 0xF 31 | self._category = category 32 | 33 | @property 34 | def command(self): 35 | return self._command 36 | 37 | @command.setter 38 | def command(self, command): 39 | assert command <= 0x7 40 | self._command = command 41 | 42 | @property 43 | def payload(self): 44 | return self._payload 45 | 46 | @payload.setter 47 | def payload(self, payload): 48 | assert len(payload) <= 0xFFFF 49 | self._payload = payload 50 | 51 | 52 | class FingerDetectionOperation(enum.Enum): 53 | DOWN = 1 54 | UP = 2 55 | MANUAL = 3 56 | 57 | 58 | @dataclasses.dataclass 59 | class OptionByte: 60 | WriteProtect: bytes 61 | MainSecurity: bool 62 | OptionProtect: bool 63 | 64 | def to_bytes(self): 65 | option_byte = ( 66 | self.WriteProtect 67 | + self.MainSecurity.to_bytes(0x4, byteorder="little") 68 | + self.OptionProtect.to_bytes(0x4, byteorder="little") 69 | ) 70 | assert len(option_byte) == 0x18 71 | return option_byte 72 | 73 | 74 | class Device: 75 | 76 | def __init__(self, product: int, proto, timeout: float | None = 5): 77 | logging.debug(f"__init__({product}, {proto}, {timeout})") 78 | 79 | self.protocol: protocol.Protocol = proto(0x27C6, product, timeout) 80 | self.gtls_context: GTLSContext | None = None 81 | 82 | # FIXME Empty device reply buffer 83 | # (Current patch while waiting for a fix) 84 | self._empty_buffer() 85 | 86 | def _empty_buffer(self): 87 | logging.debug("_empty_buffer()") 88 | 89 | try: 90 | while True: 91 | self.protocol.read(timeout=0.1) 92 | 93 | except usb.core.USBTimeoutError as error: 94 | if error.backend_error_code == -7: 95 | return 96 | 97 | raise error 98 | 99 | def _recv_next_chunk(self, timeout: float | None): 100 | for _ in range(10): 101 | chunk = self.protocol.read(USB_CHUNK_SIZE, timeout=timeout) 102 | if chunk: 103 | return chunk 104 | raise Exception("Too many empty reads") 105 | 106 | def _recv_message_from_device( 107 | self, 108 | timeout: float | None, 109 | ): 110 | data = self._recv_next_chunk(timeout) 111 | logging.debug(f"Received chunk from device: {data.hex(' ')}") 112 | 113 | command_byte = data[0] 114 | message_size = struct.unpack("> 4 127 | command = (command_byte & 0xF) >> 1 128 | data = data[:message_size + 3] 129 | 130 | msg_checksum = data[-1] 131 | data = data[:-1] 132 | if msg_checksum != 0x88: 133 | checksum = 0xAA - sum(data) & 0xFF 134 | if msg_checksum != checksum: 135 | raise Exception( 136 | f"Wrong checksum, " 137 | f"expected: {hex(checksum)}, received: {hex(msg_checksum)}" 138 | ) 139 | 140 | payload = data[3:] 141 | 142 | message = Message(category, command, payload) 143 | 144 | logging.info(f"Received message from device: {message}") 145 | 146 | return message 147 | 148 | def _check_ack(self, command_byte: int, timeout: float): 149 | message = self._recv_message_from_device(timeout) 150 | 151 | if message.category != 0xB: 152 | raise Exception("Not an ACK message") 153 | 154 | if message.command != 0: 155 | raise Exception("ACK should not have commands") 156 | 157 | if command_byte != message.payload[0]: 158 | raise Exception("ACK wrong command") 159 | 160 | logging.info(f"Received ACK for {hex(command_byte)}") 161 | 162 | def _send_message_to_device( 163 | self, 164 | message: Message, 165 | use_checksum: bool, 166 | ack_timeout: float, 167 | ): 168 | command_byte = message.category << 4 | message.command << 1 169 | 170 | data = struct.pack(" bytes: 478 | assert size < 0x7FFF 479 | 480 | if addr > 0xFFFF: 481 | if addr % 0x400 != 0: 482 | raise ValueError 483 | addr = addr // 0x400 484 | size |= 1 << 0xF 485 | 486 | msg = struct.pack("> 0x10) & 0xFFFF) 579 | 580 | self._send_message_to_device( 581 | Message(0xF, 2, msg), 582 | True, 583 | 500, 584 | ) 585 | 586 | reply = self._recv_message_from_device(1000) 587 | if reply.category != 0xF or reply.command != 2: 588 | raise Exception("Not a firmware checksum reply") 589 | 590 | if reply.payload[0] == 0: 591 | raise Exception("Firmware checksum not correct") 592 | 593 | self.reset(1, False) 594 | 595 | 596 | class GTLSContext: 597 | def __init__(self, psk: bytes, device: Device): 598 | self.state = 0 599 | self.client_random: bytes | None = None 600 | self.server_random: bytes | None = None 601 | self.client_identity: bytes | None = None 602 | self.server_identity: bytes | None = None 603 | self.symmetric_key: bytes | None = None 604 | self.symmetric_iv: bytes | None = None 605 | self.hmac_key: bytes | None = None 606 | self.hmac_client_counter_init: int | None = None 607 | self.hmac_server_counter_init: int | None = None 608 | self.hmac_client_counter: int | None = None 609 | self.hmac_server_counter: int | None = None 610 | self.psk = psk 611 | self.device = device 612 | 613 | def _client_hello_step(self): 614 | if self.state >= 2: 615 | raise Exception(f"Cannot send client hello, state: {self.state}") 616 | 617 | self.client_random = Crypto.Random.get_random_bytes(0x20) 618 | logging.debug(f"client_random: {self.client_random.hex(' ')}") 619 | 620 | self.device._send_mcu(0xFF01, self.client_random) 621 | self.state = 2 622 | 623 | def _server_identity_step(self): 624 | if self.state != 2: 625 | raise Exception( 626 | f"Cannot receive server identity, state: {self.state}") 627 | 628 | data = self.device._recv_mcu(0xFF02) 629 | if len(data) != 0x40: 630 | raise Exception("Wrong payload size") 631 | 632 | self.server_random = data[:0x20] 633 | logging.debug(f"server_random: {self.server_random.hex(' ')}") 634 | self.server_identity = data[0x20:] 635 | logging.debug(f"server_identity: {self.server_identity.hex(' ')}") 636 | 637 | session_key = _derive_session_key( 638 | self.psk, self.client_random + self.server_random, 0x44) 639 | 640 | self.symmetric_key = session_key[:0x10] 641 | logging.debug(f"symmetric_key: {self.symmetric_key.hex(' ')}") 642 | session_key = session_key[0x10:] 643 | 644 | self.symmetric_iv = session_key[:0x10] 645 | logging.debug(f"symmetric_iv: {self.symmetric_iv.hex(' ')}") 646 | session_key = session_key[0x10:] 647 | 648 | self.hmac_key = session_key[:0x20] 649 | logging.debug(f"hmac_key: {self.hmac_key.hex(' ')}") 650 | session_key = session_key[0x20:] 651 | 652 | self.hmac_client_counter_init = struct.unpack("> 1 ^ key) & 0xFFFFFFFF 788 | uVar2 = ((((((( 789 | (key >> 0xF & 0x2000 | key & 0x1000000) >> 1 | key & 0x20000) >> 2 790 | | key & 0x1000) >> 3 | (key >> 7 ^ key) & 0x80000) >> 1 | 791 | (key >> 0xF ^ key) & 0x4000) >> 2 | key & 0x2000) >> 2 792 | | uVar3 & 0x40 | key & 0x20) >> 1 | 793 | (key >> 9 ^ key << 8) & 0x800 | (key >> 0x14 ^ key * 2) & 4 | 794 | (key * 8 ^ key >> 0x10) & 0x4000 | 795 | (key >> 2 ^ key >> 0x10) & 0x80 | 796 | (key << 6 ^ key >> 7) & 0x100 | (key & 0x100) << 7) 797 | uVar2 = uVar2 & 0xFFFFFFFF 798 | uVar1 = key & 0xFFFF 799 | key = ((key ^ 800 | (uVar3 >> 0x14 ^ key) >> 10) << 0x1F | key >> 1) & 0xFFFFFFFF 801 | 802 | input_element = struct.unpack("> 8) & 0xFFFF) + (uVar2 & 0xFF | uVar1 & 1) * 0x100 806 | decrypted_data += struct.pack("