Created
October 21, 2020 21:20
-
-
Save svanheule/273fd573c43e3cb0300c4ea31e6bfed0 to your computer and use it in GitHub Desktop.
TP-Link OC200 image validation
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
#!/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) |
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
#!/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" |
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.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
all i need is a starting point
it can't be that no one on the whole internet explored modifying tp link images :_/