I'm trying to unpack (extract) and analyse the firmware of an IP Camera (Xiaomi mjsxj02cm).

I have this tf_recovery.img that's supposedly a U-Boot image, but I can't unpack it either using dumpimage or other techniques because mkimage -l doesn't provide me with enough information.

dumpimage does nothing:

$ dumpimage -o out tf_recovery.img
$ ls

mkimage -ldoesn't show any useful information:

$ mkimage -l tf_recovery.img
GP Header: Size 27051956 LoadAddr 5799cfc3

file gives some information, nothing I can use:

$ file tf_recovery.img 
tf_recovery.img: u-boot legacy uImage, MVX2##I3g60b5603KL_LX318####[BR:\3757zXZ, Linux/ARM, OS Kernel Image (lzma), 1724412 bytes, Wed Jun  6 08:02:07 2018, Load Address: 0x20008000, Entry Point: 0x20008000, Header CRC: 0x5799CFC3, Data CRC: 0x2FF27A1D

What I've already tried without success

  1. Multiple versions of u-boot, including the latest (v2019.04-rc1) built from source.
  2. Tried every image type explicitly, by using -T parameter of dumpimage
  3. Simply extracting the image like if it was a compressed archive
  4. Searched for, to the best of my abilities, any alternative methods online

I'd really appreciate if anyone can provide additional ideias on how to unpack this. Thank you!


What ultimately enabled me to understand the firmware structure was Binwalk. Then, it was a matter of using dd to split the parts and putting them back together.

For example, here's the output from Binwalk:

$ binwalk tf_recovery.img 

64            0x40            xz compressed data
2162688       0x210000        Squashfs filesystem, little endian, version 4.0, compression:xz, size: 6502290 bytes, 2019 inodes, blocksize: 131072 bytes, created: 2018-06-06 07:02:05
9830400       0x960000        JFFS2 filesystem, little endian

This means:

  1. There is something from address 0 to 64 (something that binwalk doesn't show, this ended up being u-boot's header).
  2. There's a XZ archive from 64 to 2162688.
  3. There's also a SquashFS filesystem from 2162688 to 9830400.
  4. And finally a JFFS2 filesystem from 9830400 to the end of the file.

To unpack everything:

dd if=tf_recovery.img of=header.bin bs=1 count=64 skip=0
dd if=tf_recovery.img of=xzdata.xz bs=1 count=2162624 skip=64
dd if=tf_recovery.img of=squashfs.bin bs=1 count=7667712 skip=2162688
dd if=tf_recovery.img of=jffs2.bin bs=1 skip=9830400

Putting it back together is a matter of doing the reverse:

dd if=header.bin of=somefile.img bs=1 count=64
dd if=xzdata.xz of=somefile.img bs=1 count=2162624 seek=64
dd if=squashfs.bin of=somefile.img bs=1 count=7667712 seek=2162688
dd if=jffs2.bin of=somefile.img bs=1 count=6488144 seek=9830400

Important note: All these parts were padded to size with x'FF' this is something important to take into account when putting everything back together: How to pad a file with “FF” using dd?

  • it is best to put it all back together using mkimage so the checksums get properly calculated. – uDude Sep 18 '19 at 16:52

Try dumping the byte stream starting at the signature FD 37 7A 58 5A 00, then extract it using 7z or xz.

Note that it is only a Linux kernel image, it probably does not contain the full filesystem image (at most a minimal ramdisk).

  • 1
    The footer for the XZ file is YZ, typically padded with NULLs to a four byte boundary. I say typically as that is not required and not done when using mkimage. – uDude Sep 18 '19 at 16:39

curiosity led me to google for the firmware found it at mi forums it appears Uboot Image header and data that follows is documented fairly well
i just cooked a python script to rip the compressed blob out of the firmware and testing the blob with 7zip yields no errors


import struct
import binascii
import datetime
fileext = { 0:'none',1:'gzip',2:'bzip2',3:'lzma',4:'lzo'}
fin = open("tf_recovery.img" , "rb")
uimghdr = fin.read(64)
magic,        = struct.unpack("!i"   , uimghdr[ 0:4 ] )
headercrc32,  = struct.unpack("!i"   , uimghdr[ 4:8 ] )
timestamp,    = struct.unpack("!i"   , uimghdr[ 8:12] )
datasize,     = struct.unpack("!i"   , uimghdr[12:16] )
LoadAddress,  = struct.unpack("!i"   , uimghdr[16:20] )
EntryPtAddr,  = struct.unpack("!i"   , uimghdr[20:24] )
Datacrc32,    = struct.unpack("!i"   , uimghdr[24:28] )
OperatingSys, = struct.unpack("!b"   , uimghdr[28:29] )
Architecture, = struct.unpack("!b"   , uimghdr[29:30] )
ImageType,    = struct.unpack("!b"   , uimghdr[30:31] )
CompressType, = struct.unpack("!b"   , uimghdr[31:32] )
ImageName,    = struct.unpack("!32s" , uimghdr[32:64] )
uimgdata = fin.read(datasize)
copy = list(uimghdr)
copy[4:8] = '\x00\x00\x00\x00' 
crcdata = ''.join(copy)
realhdrcrc32 = binascii.crc32(crcdata)
realdatacrc32 = binascii.crc32(uimgdata)
assert ( realhdrcrc32  == headercrc32 )
assert ( realdatacrc32 == Datacrc32 )
print ("UBoot Header Magic %s" ) % hex(magic)
print ("UBoot Header crc32 %s" ) % hex( realhdrcrc32)
print ("UBoot Header Tstmp %s" ) % datetime.datetime.fromtimestamp(timestamp)
print ("UBoot Header DSize %s" ) % hex(datasize)
print ("Uboot Compression  %s" ) % fileext[CompressType]
fout = open('out.xz' , 'wb')

and results are

UBoot Header Magic 0x27051956
UBoot Header crc32 0x5799cfc3
UBoot Header Tstmp 2018-06-06 12:32:07
UBoot Header DSize 0x1a4ffc
Uboot Compression  lzma

:\>e:\7Z\7z.exe t out.xz

7-Zip [32] 15.14 : Copyright (c) 1999-2015 Igor Pavlov : 2015-12-31

Scanning the drive for archives:
1 file, 1724412 bytes (1684 KiB)

Testing archive: out.xz
Path = out.xz
Type = xz
Physical Size = 1724412
Method = LZMA2:23 CRC64
Streams = 1
Blocks = 1

Everything is Ok

Size:       3590624
Compressed: 1724412


Your Answer

