-
-
Save svanheule/273fd573c43e3cb0300c4ea31e6bfed0 to your computer and use it in GitHub Desktop.
#!/usr/bin/python3 | |
import argparse | |
import binascii | |
import hashlib | |
import struct | |
# Image header starts with four uint32_be, followed by an MD5 digest | |
# * version (?) | |
# * magic number (0xaa55d98f) | |
# * header length (0x1000) | |
# * total file size, including header | |
# * MD5 digest | |
# | |
# The MD5 digest in the header must be calculated as follows: | |
# 1. Set [0x10:0x20] to zero (storage area for the MD5 digest) | |
# 2. Set [0x130:0x1b0] to zero (storage area for the RSA signature) | |
# | |
# At an offset of 0x130, an RSA signature is present (in reversed byte order). | |
# The public key is: | |
# BgIAAACkAABSU0ExAAQAAAEAAQD9lxDCQ5DFNSYJBriTmTmZlE | |
# MYVgGcZTO+AIwmdVjhaeJI6wWtN7DqCaHQlOqJ2xvKNrLB+wA1 | |
# NxUh7VDViymotq/+9QDf7qEtJHmesjirvPN6Hfrf+FO4/hmjbV | |
# XgytHORxGta5KW4QHVIwyMSVPOvMC4A5lFIh+D1kJW5GXWtA== | |
# The image signature is a SHA-1 digest calculated the following way: | |
# 1. Set 0x80 bytes starting at 0x130 to zero; this is the storage area for the signature | |
# 2. Drop the first 0x14 bytes of the image | |
# 3. Calculate the MD5 digest of the remaining data | |
# 4. Calculate the SHA-1 digest of the MD5 digest | |
struct_header = struct.Struct('>4I16s') | |
IMAGE_MAGIC = 0xaa55d98f | |
struct_img_part = struct.Struct('>32s5I') | |
parser = argparse.ArgumentParser('parse TP-Link mvebu image') | |
parser.add_argument('file', help='path to file') | |
parser.add_argument('-x', '--extract', action='store_true', help='extract data parts') | |
parser.add_argument('-v', '--verbose', action='store_true', help='print more info') | |
parser.add_argument('-s', '--signature', action='store_true', help='extract rsa signature') | |
parser.add_argument('-d', '--digest', action='store_true', help='calculate md5 digest') | |
args = parser.parse_args() | |
def read_at(f, offset, length): | |
f.seek(offset) | |
return f.read(length) | |
def print_part_table_header(): | |
print('NAME | NAND? | OFFSET | PART SIZE | SEEK | LENGTH ') | |
print('---------------------------------+-------+----------+-----------+----------+----------') | |
def print_part_table_line(part): | |
print('{:32s} | {:1d} | {:8x} | {:8x} | {:8x} | {:8x}'.format( | |
part[0].decode('ascii').strip('\0'), | |
part[5], | |
part[1], | |
part[2], | |
part[3], | |
part[4] | |
)) | |
with open(args.file, 'rb') as image: | |
header = struct_header.unpack(read_at(image, 0, struct_header.size)) | |
signature = read_at(image, 0x130, 0x80) | |
version, magic, part_table_offset, file_size, checksum = header | |
rsa_signature = read_at(image, 0x130, 0x80) | |
image_data = bytearray(read_at(image, 0, file_size)) | |
image_data[0x130:0x130+0x80] = bytearray(0x80) | |
signature_digest = hashlib.sha1(hashlib.md5(image_data[0x14:]).digest()).digest() | |
image_data[0x10:0x10+0x10] = bytearray(0x10) | |
image_digest = hashlib.md5(image_data).digest() | |
if args.verbose: | |
print(f'Header start: {version:08x} {magic:08x}') | |
print(f'File size: {file_size:x}') | |
print(f'Payload table offset: {part_table_offset:x}') | |
print('Checksum:', binascii.hexlify(checksum).decode('ascii')) | |
if checksum == image_digest: | |
print('Checksum correct') | |
else: | |
print('Checksum incorrect! Calculated {}'.format( | |
binascii.hexlify(image_digest).decode('ascii') | |
)) | |
if args.signature: | |
print(binascii.hexlify(rsa_signature[::-1]).decode('ascii')) | |
if args.digest: | |
print(binascii.hexlify(signature_digest).decode('ascii')) | |
parts = dict() | |
end_of_table = False | |
img_parts_start = part_table_offset | |
offset_entry = img_parts_start | |
while not end_of_table: | |
img_part = read_at(image, offset_entry, struct_img_part.size) | |
offset_entry += struct_img_part.size | |
img_part = struct_img_part.unpack(img_part) | |
name = img_part[0].decode('ascii').strip('\0') | |
part_offset, part_size = img_part[1:3] | |
payload_offset, payload_size = img_part[3:5] | |
store_in_nand = bool(img_part[5]) | |
if len(name) > 0: | |
parts[name] = img_part | |
end_of_table = (len(name) == 0) or (offset_entry == img_parts_start+32*struct_img_part.size) | |
if args.verbose: | |
print('Found {} parts'.format(len(parts))) | |
if len(parts): | |
print_part_table_header() | |
for name in parts: | |
print_part_table_line(parts[name]) | |
if args.extract: | |
for name in parts: | |
offset, size = parts[name][3:5] | |
payload_data = read_at(image, offset, size) | |
with open(f'{name}.bin', 'wb') as part: | |
part.write(payload_data) |
#!/bin/bash | |
DIGEST_CALC=$(./parse.py -d $1) | |
DIGEST_SIGN=$(./parse.py -s $1 | xxd -revert -plain | openssl rsautl -verify -pubin -inkey rsa.pem | xxd -s 15 -plain) | |
echo "Image contains $DIGEST_CALC" | |
echo "Calculated $DIGEST_SIGN" |
I was able to extract the public key
BgIAAACkAABSU0ExAAQAAAEAAQCtdVIi5h5+e4v16PPyGj8o10hKva+bycG1F5TW7abW1RDK6PEanVepTEs0hVZgTL09z7taV3JyD7m2Mtfj6JTK0+U9VsPg61mvOmoHR0ibiy6mehK0KTqPO2gAUjmpAZhX32BKBFG8LPEJVNN0e/eeN1UDHpFwNzqYHdEF7tu4wA==
by opening /lib/libcmm.so from the fw upgrade file and ctrl + f for ==
trying parse.py TD-W9960V1_1.3.0_0.8.0_up_boot210831_2021-09-0_1631785075811.bin
shows this error
Traceback (most recent call last):
File "C:\Users\ddddd\Folder45\parse.py", line 105, in <module>
img_part = struct_img_part.unpack(img_part)
struct.error: unpack requires a buffer of 52 bytes
the firmware upgrade file right out of the configuration file
<Url val=http://download.tplinkcloud.com/firmware/TD-W9960V1_1.3.0_0.8.0_up_boot210831_2021-09-0_1631785075811.bin />
what's next ?
Thanks a lot for your reply : )
Probably you need to check if the image you're trying to process actually uses the same format as the OC200 does. This script mainly exists to illustrate how the image checksum works for OC200 firmware files. If you want to use this for something else, you're on your own to figure out if it's applicable or not.
Probably you need to check if the image you're trying to process actually uses the same format as the OC200 does
@svanheule How Would i do that ?
Probably you need to check if the image you're trying to process actually uses the same format as the OC200 does
@svanheule How Would i do that ?
all i need is a starting point
it can't be that no one on the whole internet explored modifying tp link images :_/
OpenWrt supports at least three TP-Link specific image formats, aside from a whole range of other image formats. I suggest you start exploring the firmware-utils repository, and use a hex editor or a tool like binwalk to explore the binary image.
https://git.openwrt.org/?p=project/firmware-utils.git;a=tree;f=src;hb=HEAD
If you need more support, please post your question elsewhere. I don't have time to look into some random device for you, and posting request for help here isn't going to draw much attention from anyone besides me.
It's been a while since I've looked at this device, and I don't even have one anymore. I also don't know about any other devices that use the same image format.
If you have a firmware image using the same format as the OC200,
verify.sh
should be able to tell if the checksum is correct.parse.py
can also extract the different data partitions embedded inside the firmware image, using the-x
argument;./parsy.py -h
should provide you with a help output. Other than that, there's little more you can do with these scripts.