Created
August 18, 2021 18:36
-
-
Save masthoon/018527d6e137e4980b63a33c847278e1 to your computer and use it in GitHub Desktop.
Extract and decompress parts of RFU firmware
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
import struct | |
import lzma | |
PJL_ESC = b'\x1b%-12345X' | |
def parse_pjl_command(fw): | |
assert(fw[0] == 0x20) | |
cmd, info = fw[1:256].split(b' ', 1) | |
name, value = info.split(b'=', 1) | |
value, _ = value.split(b'\n', 1) | |
if cmd == b'UPGRADE' and name == b'SIZE': | |
size = int(value) | |
print("Size of extracted fw (PJL): 0x{:x}".format(size)) | |
return size | |
elif cmd == b"COMMENT" and name == b'MODEL': | |
print("Extract fw of model: {}".format(value)) | |
return 0 | |
# not handled | |
print("Not handled {} - {} = {}".format(cmd, name, value)) | |
return 0 | |
def extract_pjl(fw): | |
off = 0 | |
fw_size = 0 | |
# ESC | |
while fw[off] != 0x1b: | |
off += 1 | |
# Check ESC | |
if not fw[off:].startswith(PJL_ESC): | |
print("Invalid ESC command") | |
return '' | |
off += len(PJL_ESC) | |
while 1: | |
# Check PJL | |
if fw[off:].startswith(PJL_ESC): | |
off += len(PJL_ESC) | |
while fw[off] in [0xD, 0xA]: | |
off += 1 | |
break | |
if not fw[off:].startswith(b'@PJL'): | |
print("Invalid PJL command") | |
return '' | |
off += 4 | |
# Parse it | |
size = parse_pjl_command(fw[off:]) | |
if size > 0: | |
assert fw_size == 0, "Already defined fw_size" | |
fw_size = size | |
# Check padding | |
while fw[off] not in [0xD, 0xA]: | |
off += 1 | |
while fw[off] in [0xD, 0xA]: | |
off += 1 | |
if fw_size == 0: | |
# Retry (firmware may have two headers) | |
return extract_pjl(fw[off:]) | |
fw_max_len = len(fw) | |
assert(fw_max_len - off >= fw_size) | |
end_fw = off + fw_size | |
ext_fw = fw[off:end_fw] | |
print("Signature Bytes left 0x{:x} [{}...]".format(fw_max_len - end_fw, fw[end_fw:end_fw+40])) | |
return ext_fw | |
def u32(s): | |
"""u32(s) -> int | |
Unpack 32 bits integer from a little endian str representation | |
""" | |
return struct.unpack('<I', s)[0] | |
def u32b(s): | |
"""u32(s) -> int | |
Unpack 32 bits integer from a little endian str representation | |
""" | |
return struct.unpack('>I', s)[0] | |
FW_START = 0x40000 | |
def extract_and_save_lz(fw): | |
off = 0 | |
id_ = 0 | |
# CHECK magic if starts at 0 or FW_START | |
if fw[0xc:0xe] != b'\xbe\xac': | |
fw = fw[FW_START:] | |
fw = fw[0xc:] | |
while off < len(fw): | |
magic = u32(fw[off:off+4]) | |
size = u32b(fw[off+4:off+8]) | |
unk_c = u32(fw[off+0xc:off+0x10]) | |
unk_10 = u32(fw[off+0x10:off+0x14]) | |
unk_14 = u32(fw[off+0x14:off+0x18]) | |
assert(magic & 0xffff == 0xacbe) | |
print("Magic {:x} Size {:x} UNK (c){:x} (10){:x} (14){:x}".format(magic, size, unk_c, unk_10, unk_14)) | |
if magic & 0x04000000: | |
# Compressed | |
print("UNCOMPRESS") | |
data = lzma.decompress(fw[off + 32:off + 32 + size]) | |
else: | |
data = fw[off + 8:off + 8 + size] | |
filename = 'extracted{}_{:x}.re'.format(id_, unk_10) | |
print("Dumped {} size 0x{:x}".format(filename, len(data))) | |
f = open(filename, 'wb') | |
f.write(data) | |
f.close() | |
off += size + 0x20 | |
id_ += 1 | |
def main(): | |
filename = sys.argv[1] | |
fw = open(filename, 'rb').read() | |
fw = extract_pjl(fw) | |
if fw: | |
print("Succesfully extracted PJL command to extracted.pjl.re") | |
f = open('extracted.pjl.re', 'wb') | |
f.write(fw) | |
f.close() | |
extract_and_save_lz(fw) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment