-
-
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" |
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.
@svanheule How Would i do that ?