Close

Unpacker for firmware .pck

A project log for Project 'Landlord'

Open source firmware for Worx Landroid robotic mower.

daniel-wiegertDaniel Wiegert 07/19/2016 at 19:096 Comments

Warning etc etc, use with caution... Might contain bugs :)

#!/usr/bin/python3.5
import sys
if len(sys.argv) <2:
  print( "usage:" )
  print("  " + sys.argv[0] + " [.PCK file]", "[Folder-prefix]" )
  print( "  " )
  exit()
import os
from binascii import crc32
from struct import unpack
#get unpack file
src_file_path = sys.argv[1]
#read file length
src_file_size = os.path.getsize(src_file_path)
#read file
b = bytearray(open(src_file_path, 'rb').read())
# crude bytearray to String converter
def toStr(b):
  return "".join(["%c"%i for i in b]).strip("\x00")
# convert 4 bytes to int
def word2int(w):
  return unpack("<I", w)[0]
# crude filetable parser
def getHeader(b, datastart):
  beginstart=b.find(63) #"?"
  filename = toStr(b[0:beginstart]).replace("\\","/")
  beginstart+=1
  beginlength=b.find(63,beginstart) #"?"
  start = int(b[beginstart:beginlength],16) + datastart + 4
  beginlength+=1
  length = int(b[beginlength:beginlength+10],16)
  return {
    "filename": filename,
    "start" : start,
    "length" : length,
    }
#crude way to create nonexisting directories
def checkCreateDir(filename):
  if not os.path.exists(os.path.dirname(filename)):
    try:
        os.makedirs(os.path.dirname(filename))
    except OSError as exc: # Guard against race condition
        if exc.errno != errno.EEXIST:
            raise
def save(filename, data):
  checkCreateDir(prefix + "/" + filename) #create missing directories
  print ("writing %s\t (%d bytes)"%(filename,len(data)))
  open(prefix + "/" + filename, 'wb').write(data)
## Start decoding
print("Unknown byte 0:\t"+ str(b[0]))
print("Unknown byte 1:\t"+ str(b[1]))
print("Unknown byte 2:\t"+ str(b[2]))
print("Unknown byte 3:\t"+ str(b[3]))
prefix = sys.argv[2] if len(sys.argv)>=3 else "."
headerstart_token = bytearray((int(62), int(0x0d), int(0x0a)))
headerstart = b.find(headerstart_token,0)+3
pos=headerstart
datastart_token = bytearray((int(0x0d), int(0x0a), int(0x0d), int(0x0a)))
datastart = b.find(datastart_token,0)
print("header end: \t"+ str(datastart))
nHeaders = b.count(int(13),4,datastart)
print("# files \t: "+str(nHeaders))
# crude way to parse the directory table
files = []
print ("found %i files\n"%nHeaders)
token = bytearray((int(0x0d), int(0x0a)))
for i in range(nHeaders):
  pos2=b.find(token,pos)
  h = getHeader(b[pos:pos2], datastart)
  files.append(h)
  print(h)
  pos = int(pos2)+2
for h in files:
  bFile = b[h["start"]:h["start"]+h["length"]]
#write stripped file
  save(h["filename"],bFile)
print("Done!")

Discussions

land_lord wrote 06/16/2022 at 14:03 point

The unpacker is also here https://github.com/Damme/LandLord/blob/master/unpack_pck.py

But I unpacked fw v2.66 with the code from this page, it works great!

$ python --version
Python 3.7.10

$ db504_v2.66_extracted % python ../../unpacker.py ../db504_v2.66_30-10-2017.pck
Unknown byte 0:    197
Unknown byte 1:    24
Unknown byte 2:    200
Unknown byte 3:    67
header end:     2418
# files     : 50
found 50 files

{'filename': 'db504 v2.66 30-10-2017.bin', 'start': 2422, 'length': 363076}
{'filename': 'web_files/debugPage.html', 'start': 365498, 'length': 23187}
{'filename': 'web_files/dummy.html', 'start': 388685, 'length': 2}
{'filename': 'web_files/index.htm', 'start': 388687, 'length': 13929}
{'filename': 'web_files/index.html', 'start': 402616, 'length': 13929}
{'filename': 'web_files/log.html', 'start': 416545, 'length': 4116}
{'filename': 'web_files/manifest.json', 'start': 420661, 'length': 667}
{'filename': 'web_files/css/style.css', 'start': 421328, 'length': 10653}
{'filename': 'web_files/css/styleLog.css', 'start': 431981, 'length': 13279}
{'filename': 'web_files/img/android-icon-1x.png', 'start': 445260, 'length': 1663}
{'filename': 'web_files/img/android-icon-2x.png', 'start': 446923, 'length': 2349}
{'filename': 'web_files/img/android-icon-3x.png', 'start': 449272, 'length': 3098}
{'filename': 'web_files/img/android-icon-4x.png', 'start': 452370, 'length': 4108}
{'filename': 'web_files/img/back-highlight.png', 'start': 456478, 'length': 1344}
{'filename': 'web_files/img/back.png', 'start': 457822, 'length': 1345}
{'filename': 'web_files/img/close.png', 'start': 459167, 'length': 1038}
{'filename': 'web_files/img/ios-icon-58@2x.png', 'start': 460205, 'length': 1661}
{'filename': 'web_files/img/ios-icon-60@2x.png', 'start': 461866, 'length': 2864}
{'filename': 'web_files/img/ios-icon-60@3x.png', 'start': 464730, 'length': 3897}
{'filename': 'web_files/img/ios-icon-76.png', 'start': 468627, 'length': 2097}
{'filename': 'web_files/img/ios-icon-76@2x.png', 'start': 470724, 'length': 17439}
{'filename': 'web_files/img/lock.png', 'start': 488163, 'length': 1321}
{'filename': 'web_files/img/logo.png', 'start': 489484, 'length': 2530}
{'filename': 'web_files/img/menu-highlight.png', 'start': 492014, 'length': 1090}
{'filename': 'web_files/img/menu.png', 'start': 493104, 'length': 1084}
{'filename': 'web_files/img/minus-highlight.png', 'start': 494188, 'length': 1035}
{'filename': 'web_files/img/minus.png', 'start': 495223, 'length': 1041}
{'filename': 'web_files/img/plus-highlight.png', 'start': 496264, 'length': 1150}
{'filename': 'web_files/img/plus.png', 'start': 497414, 'length': 1142}
{'filename': 'web_files/js/app.js.gz', 'start': 498556, 'length': 5517}
{'filename': 'web_files/js/lib.js.gz', 'start': 504073, 'length': 87122}
{'filename': 'web_files/locale/da.js', 'start': 591195, 'length': 3869}
{'filename': 'web_files/locale/de.js', 'start': 595064, 'length': 4019}
{'filename': 'web_files/locale/en.js', 'start': 599083, 'length': 3759}
{'filename': 'web_files/locale/es.js', 'start': 602842, 'length': 4118}
{'filename': 'web_files/locale/fi.js', 'start': 606960, 'length': 4022}
{'filename': 'web_files/locale/fr.js', 'start': 610982, 'length': 4231}
{'filename': 'web_files/locale/it.js', 'start': 615213, 'length': 3870}
{'filename': 'web_files/locale/nb.js', 'start': 619083, 'length': 3777}
{'filename': 'web_files/locale/nl.js', 'start': 622860, 'length': 3854}
{'filename': 'web_files/locale/pt.js', 'start': 626714, 'length': 4101}
{'filename': 'web_files/locale/sv.js', 'start': 630815, 'length': 3870}
{'filename': 'wifi/1iuinst1.bin', 'start': 634685, 'length': 9612}
{'filename': 'wifi/2iuinst2.bin', 'start': 644297, 'length': 4}
{'filename': 'wifi/3iudata.bin', 'start': 644301, 'length': 400}
{'filename': 'wifi/4ffinst1.bin', 'start': 644701, 'length': 65388}
{'filename': 'wifi/5ffinst2.bin', 'start': 710089, 'length': 30128}
{'filename': 'wifi/6ffdata.bin', 'start': 740217, 'length': 58464}
{'filename': 'wifi/wifiFwVersion.txt', 'start': 798681, 'length': 6}
{'filename': 'wire/Boundary 0.7.bin', 'start': 798687, 'length': 229376}
writing db504 v2.66 30-10-2017.bin     (363076 bytes)
writing web_files/debugPage.html     (23187 bytes)
writing web_files/dummy.html     (2 bytes)
writing web_files/index.htm     (13929 bytes)
writing web_files/index.html     (13929 bytes)
writing web_files/log.html     (4116 bytes)
writing web_files/manifest.json     (667 bytes)
writing web_files/css/style.css     (10653 bytes)
writing web_files/css/styleLog.css     (13279 bytes)
writing web_files/img/android-icon-1x.png     (1663 bytes)
writing web_files/img/android-icon-2x.png     (2349 bytes)
writing web_files/img/android-icon-3x.png     (3098 bytes)
writing web_files/img/android-icon-4x.png     (4108 bytes)
writing web_files/img/back-highlight.png     (1344 bytes)
writing web_files/img/back.png     (1345 bytes)
writing web_files/img/close.png     (1038 bytes)
writing web_files/img/ios-icon-58@2x.png     (1661 bytes)
writing web_files/img/ios-icon-60@2x.png     (2864 bytes)
writing web_files/img/ios-icon-60@3x.png     (3897 bytes)
writing web_files/img/ios-icon-76.png     (2097 bytes)
writing web_files/img/ios-icon-76@2x.png     (17439 bytes)
writing web_files/img/lock.png     (1321 bytes)
writing web_files/img/logo.png     (2530 bytes)
writing web_files/img/menu-highlight.png     (1090 bytes)
writing web_files/img/menu.png     (1084 bytes)
writing web_files/img/minus-highlight.png     (1035 bytes)
writing web_files/img/minus.png     (1041 bytes)
writing web_files/img/plus-highlight.png     (1150 bytes)
writing web_files/img/plus.png     (1142 bytes)
writing web_files/js/app.js.gz     (5517 bytes)
writing web_files/js/lib.js.gz     (87122 bytes)
writing web_files/locale/da.js     (3869 bytes)
writing web_files/locale/de.js     (4019 bytes)
writing web_files/locale/en.js     (3759 bytes)
writing web_files/locale/es.js     (4118 bytes)
writing web_files/locale/fi.js     (4022 bytes)
writing web_files/locale/fr.js     (4231 bytes)
writing web_files/locale/it.js     (3870 bytes)
writing web_files/locale/nb.js     (3777 bytes)
writing web_files/locale/nl.js     (3854 bytes)
writing web_files/locale/pt.js     (4101 bytes)
writing web_files/locale/sv.js     (3870 bytes)
writing wifi/1iuinst1.bin     (9612 bytes)
writing wifi/2iuinst2.bin     (4 bytes)
writing wifi/3iudata.bin     (400 bytes)
writing wifi/4ffinst1.bin     (65388 bytes)
writing wifi/5ffinst2.bin     (30128 bytes)
writing wifi/6ffdata.bin     (58464 bytes)
writing wifi/wifiFwVersion.txt     (6 bytes)
writing wire/Boundary 0.7.bin     (229376 bytes)
Done!


  Are you sure? yes | no

Andy Brown wrote 03/01/2019 at 11:47 point

Thanks for this, have extracted db510-v3.26.pck and looked like it went fine:

Unknown byte 0: 242
Unknown byte 1: 202
Unknown byte 2: 19
Unknown byte 3: 105
header end:     234
# files         : 4
found 4 files

{'start': 238, 'filename': 'db510 v3.26 22-12-2017.bin', 'length': 195844}
{'start': 196082, 'filename': 'wifi_firmware/wififw v0.12.bin', 'length': 523520}
{'start': 719602, 'filename': 'wire_firmware/Boundary 2.0.bin', 'length': 32768}
{'start': 752370, 'filename': 'wire_firmware/Boundary BL 1.1.bin', 'length': 32768}
writing db510 v3.26 22-12-2017.bin       (195844 bytes)
writing wifi_firmware/wififw v0.12.bin   (523520 bytes)
writing wire_firmware/Boundary 2.0.bin   (32768 bytes)
writing wire_firmware/Boundary BL 1.1.bin        (32768 bytes)

So looks good. Is there any way of reversing the .bin or as it's a compiled ARM binary I'm guessing that's a no?

Anywhere have the original source for the stock firmware (Is it under GPL has anyone done an information request from Worx by any chance)?

  Are you sure? yes | no

bouni wrote 05/09/2017 at 10:35 point

Hi Daniel,

I've just used your unpacker with the latest Firmware available for my 2017 Worx WR101SI.
It unpacks without problems, but if i scan the binary for strings, there isn't a single one that makes sense as well as no known headers.
Do you know if the kind of obfuscate their binarys?

  Are you sure? yes | no

michi wrote 08/10/2016 at 15:58 point

Hello,

I have tested the script . Here came the following error :

Unknown byte 0:    17
Unknown byte 1:    38
Unknown byte 2:    172
Unknown byte 3:    156
header end:     2418
Traceback (most recent call last):
  File "unpack_pck.py", line 61, in
    nHeaders = b.count(int(13),4,datastart)
TypeError: Type int doesn't support the buffer API

Greetings

  Are you sure? yes | no

Daniel Wiegert wrote 08/10/2016 at 19:16 point

Hmm, I am no python guru, what version do you run? It works in my Python 3.5.2

  Are you sure? yes | no

Daniel Wiegert wrote 08/11/2016 at 14:07 point

I just tried python 2 and got same error. This is written for python 3.

  Are you sure? yes | no