Files
tuya-cloudcutter/profile-building/process_storage.py
Cossid b28723ae61 Add RTL8720CF support (#857)
* Initial RTL8720CF support

* Fix RTL8720CF_OTA file validation.

* be a bit more robust on chip string matching

* Really rough refactor of haxomatic for RTL8720CF, not complete.

* Update RTL8720CF 2.3.0 haxomatic hex match strings

* Remove length validation from authkey/uuid so it can work with both Tuya and CloudCutter generated keys.

* Fix bk7231 string detection
Add second RTL8720CF 2.3.0 profile

* Refactor haxomatic to be more modular and maintainable.

* haxomatic - minor cleanup

* profile-building - Pull PSK when pulling schema.

* Haxomatic - Search all binaries for patch patterns.
Update known RTL8720CF match pattern identifiers.

* Change network to custom 10.204.0.1/24 network (204 = 0xCC)
Send multiple DNS servers, which may help devices that hang after DHCP
Spend less time sending wifi connect requests so AP can start listening sooner.

* Update exploit for new offsets.

* Haxomatic - Add 1.0.x SDK

* Update haxomatic for newer found patterns.

* Minor tweaks

* Updates to profile-building

* Add storage parsing to extract_rtl8720cf

* Switch to bk7231tools to extract rtl8720cf storage to remove an unneeded dependency.

* remove debug code

* Add special case for sdk identification for single build missing standard string.

* Find swv before device_class, as we may want to search directly after it.

* Update comments, seek entire bin for storage.

* Add missing new address in profile.
Add ability to process inactive OTA app.

* Update documentation.

* fix typo.

* Fix a type in beken extract.

* Add haxomatic pattern for oddball BK7231N 2.3.1 SDK.

* Haxomatic - Add RTL8720CF 2.3.1 SDK pattern.

* Fix copy/paste typo

* profile-building - proceess_app - add more device class match strings.

* one more

* profile-building - better log SDK data

* Add a special thanks section.

* fix typo

* Clean up documentation.

* documentation - use numbered lists.

* process_app - add another device class identifier.
2025-11-25 17:18:58 -06:00

84 lines
3.9 KiB
Python

import json
import os.path
import sys
def write_file(key, value: str):
try:
with open(os.path.join(base_folder, base_name + "_" + key + ".txt"), "x") as file:
file.write(value)
except:
return
def dump(file, process_inactive_app: bool = False):
with open(file, "r") as storage_file:
storage = json.load(storage_file)
global base_name, base_folder
base_name = os.path.basename(file)[:-13]
base_folder = os.path.dirname(file)
factory_pin = None
firmware_key = None
product_key = None
if 'gw_bi' in storage and storage['gw_bi']:
print(f"[+] uuid: {storage['gw_bi']['uuid']}")
write_file("uuid", storage['gw_bi']['uuid'])
print(f"[+] auth_key: {storage['gw_bi']['auth_key']}")
write_file("auth_key", storage['gw_bi']['auth_key'])
print(f"[+] ap_ssid: {storage['gw_bi']['ap_ssid']}")
write_file("ap_ssid", storage['gw_bi']['ap_ssid'])
if 'fac_pin' in storage['gw_bi'] and storage['gw_bi']['fac_pin'] is not None:
factory_pin = storage['gw_bi']['fac_pin']
print(f"[+] factory pin: {factory_pin}")
write_file("factory_pin", factory_pin)
# Not all firmwares have version information in storage
if 'gw_di' in storage:
if 'swv' in storage['gw_di'] and not process_inactive_app:
print(f"[+] storage swv: {storage['gw_di']['swv']}")
write_file("swv", storage['gw_di']['swv'])
if 'dev_swv' in storage['gw_di'] and not process_inactive_app:
print(f"[+] storage dev_swv: {storage['gw_di']['dev_swv']}")
write_file("mcuswv", storage['gw_di']['dev_swv'])
if 'bv' in storage['gw_di'] and not process_inactive_app:
print(f"[+] storage bv: {storage['gw_di']['bv']}")
write_file("bv", storage['gw_di']['bv'])
if 'firmk' in storage['gw_di'] and storage['gw_di']['firmk'] is not None:
firmware_key = storage['gw_di']['firmk']
print(f"[+] firmware key: {firmware_key}")
write_file("firmware_key", firmware_key)
if 'pk' in storage['gw_di'] and storage['gw_di']['pk'] is not None:
product_key = storage['gw_di']['pk']
print(f"[+] product key: {product_key}")
write_file("product_key", product_key)
if 's_id' in storage['gw_di'] and storage['gw_di']['s_id'] is not None:
schema_id = storage['gw_di']['s_id']
if schema_id in storage:
if write_file("schema_id", schema_id):
print(f"[+] schema: {storage[schema_id]}")
if write_file("schema", json.dumps(storage[schema_id])):
print(f"[+] schema {schema_id}:")
if 'baud_cfg' in storage and 'baud' in storage['baud_cfg']:
print(f"[+] TuyaMCU baud: {storage['baud_cfg']['baud']}")
write_file("tuyamcu_baud", f"{storage['baud_cfg']['baud']}")
elif 'uart_adapt_params' in storage and 'uart_baud' in storage['uart_adapt_params']:
print(f"[+] TuyaMCU baud: {storage['uart_adapt_params']['uart_baud']}")
write_file("tuyamcu_baud", f"{storage['uart_adapt_params']['uart_baud']}")
if not firmware_key and not product_key and not factory_pin:
print("[!] No gw_di, No version or key stored, manual lookup required")
write_file("manually_process", "No version or key stored, manual lookup required")
def run(storage_file: str, process_inactive_app: bool = False):
if not storage_file:
print('Usage: python parse_storage.py <storage.json file>')
sys.exit(1)
if os.path.exists(storage_file):
dump(storage_file, process_inactive_app)
else:
print('[!] Storage file not found')
return
if __name__ == '__main__':
run(sys.argv[1], sys.argv[2])