-
-
Save dogtopus/b2aa1e90bdc66aeeb36c0b88f7ce8a88 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3 | |
# CSR OTAU binary parser | |
# https://developer.qualcomm.com/qfile/34081/csr102x_otau_overview.pdf | |
# For use with test and demonstration only. This is obviously not official and | |
# is not affiliated with Qualcomm. | |
import io | |
import os | |
import sys | |
MAGIC_HEADER = b'APPUHDR2' | |
MAGIC_PART = b'PARTDATA' | |
MAGIC_FOOTER = b'APPUPFTR' | |
def read_otau(otau_path, out_path=None): | |
with open(otau_path, 'rb') as otau: | |
magic = otau.read(8) | |
if magic != MAGIC_HEADER: | |
raise RuntimeError('Not an OTAU file.') | |
print_otau_header(otau) | |
seq = 0 | |
while True: | |
magic = otau.read(8) | |
if len(magic) == 0: | |
print('EOF') | |
break | |
if magic == MAGIC_PART: | |
print() | |
ptype, pnum, psize = parse_partition_header(otau) | |
if out_path is not None: | |
out_file_path = os.path.join(out_path, f'{os.path.basename(otau_path)}.s{seq}.n{pnum}.t{ptype}') | |
print(f'Dumping partition {pnum} to', out_file_path) | |
with open(out_file_path, 'wb') as part: | |
part.write(otau.read(psize)) | |
else: | |
otau.seek(psize, io.SEEK_CUR) | |
seq += 1 | |
elif magic == MAGIC_FOOTER: | |
footer_size = int.from_bytes(otau.read(4), 'big') | |
if out_path is not None: | |
print(f'Dumping footer') | |
with open(f'{os.path.basename(otau_path)}.footer', 'wb') as footer: | |
footer.write(otau.read(footer_size)) | |
else: | |
otau.seek(footer_size, io.SEEK_CUR) | |
else: | |
raise RuntimeError(f'Unexpected magic {repr(magic)}') | |
def print_otau_header(otau): | |
hsize = int.from_bytes(otau.read(4), 'big') | |
begin_pos = otau.tell() | |
model = otau.read(8) | |
ver = int.from_bytes(otau.read(4), 'big') | |
n_compatatible_upgrades = int.from_bytes(otau.read(2), 'big') | |
compatatible_upgrades = tuple(int.from_bytes(otau.read(4), 'big') for _ in range(n_compatatible_upgrades)) | |
ps_config_ver = int.from_bytes(otau.read(2), 'big') | |
n_prev_ps_config_vers = int.from_bytes(otau.read(2), 'big') | |
prev_ps_config_vers = tuple(int.from_bytes(otau.read(2), 'big') for _ in range(n_prev_ps_config_vers)) | |
end_pos = otau.tell() | |
assert end_pos - begin_pos == hsize, f'Header size mismatch (got {end_pos - begin_pos}, expecting {hsize})' | |
print('Model:', model) | |
print('Upgrade version:', f'{ver >> 16}.{ver & 0xffff}') | |
print('Compatible upgrades:') | |
for cu in compatatible_upgrades: | |
print(f' - {cu >> 16}.{cu & 0xffff}') | |
print('PS (Persistent Storage?) config ver:', ps_config_ver) | |
print('Prev PS config vers:') | |
for ppsv in prev_ps_config_vers: | |
print(f' - {ppsv}') | |
def parse_partition_header(otau): | |
# exclude ptype and pnum fields | |
psize = int.from_bytes(otau.read(4), 'big') - 4 | |
ptype = int.from_bytes(otau.read(2), 'big') | |
pnum = int.from_bytes(otau.read(2), 'big') | |
print('Partition #:', pnum) | |
print('Partition type:', ptype) | |
print('Payload size:', psize) | |
return ptype, pnum, psize | |
if __name__ == '__main__': | |
if len(sys.argv) < 2: | |
print(f'Usage: {sys.argv[0]} <otau> [outdir]') | |
sys.exit(1) | |
read_otau(sys.argv[1], (sys.argv[2] if len(sys.argv) >= 3 else None)) |
@Plutoberth You can just take it. It can be considered as public domain or under Unlicense.
Also thanks for sharing your project. I'm really interested in it. Fun fact: this script was created when I was looking at firmwares for my WH-1000XM2 but ended up not going further than extracting the files due to lack of time and interest.
Awesome, thanks! Pretty cool goal :)
Uhm.. So, like what else can you do with the resulting files?
I tried to throw the CSR-dfu2
one (HDX-2916_V0411.bin.s0.n0.t1) at ghidra, but it couldn't find any function with any of the likely languages.
@mirh No idea. IIRC Qualcomm never mentioned anything public about what architecture the application processor uses for the entire CSR line. The DSP though is usually Kalimba and there seems to be public documentation of it.
AFAICT in this case (and well, all the other "big cans" until the WH-1000XM3) they used a CSR8675.
But I tri... Oh, what the fu they tell you it's RISC straight out of the product brief!
And guess what? Let's just say that I could easily find the entire csr8670 ADK (which should be pretty much just an older version of the same chip) and they used this compiler. No shit ghidra was sometimes struggling to even find strings with something this weird.
https://reverseengineering.stackexchange.com/questions/22435/csr-dfu-file-format
And CSR-dfu2 is itself another file system with multiple files inside.
vm.app
sony_FlashInfo.kap
So.. the second one is probably (I took a guess) a "kalimba application package", and I presume it should mostly just be concerned with audio. I couldn't find an unpacker for kalpac2, nor a disassembler for kalasm2 (even compilers seem to be a miracle) so that's definitively a dead end.
As for the first one that looks 100% like a normal executable file (if even in its own format).
But alas I'm too much of a noob to work with IDA or radare.
https://github.com/ramikg/csr-dfu-parser#disassembly
Ah yes, the cursed 16-bit "byte" or hextets. They are pretty common if you look at the right corner of the Internet but never gets support by major reverse engineering toolkit.
Hi, thanks for your work. What's the license for this snippet? I'm working on Plutoberth/SonyHeadphonesClient#35 .