├── README.md ├── LICENSE ├── unify_trustlet ├── .gitignore ├── ubisqsh.py ├── qcmodemat.py └── ubidump /README.md: -------------------------------------------------------------------------------- 1 | # qc_modem_tools 2 | 3 | Some tools for reversing QDSP hexagon 4 | 5 | See blog at https://bkerler.github.io/2019/11/15/bring-light-to-the-darkness/ 6 | 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Bjoern Kerler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /unify_trustlet: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | import sys, os, struct 3 | 4 | ELF_HEADER_SIZE = 0x34 5 | E_PHNUM_OFFSET = 0x2C 6 | PHDR_SIZE = 0x20 7 | P_FILESZ_OFFSET = 0x10 8 | P_OFFSET_OFFSET = 0x4 9 | 10 | def main(): 11 | 12 | #Reading the arguments 13 | if len(sys.argv) != 4: 14 | print "USAGE: " 15 | return 16 | trustlet_dir = sys.argv[1] 17 | trustlet_name = sys.argv[2] 18 | output_file_path = sys.argv[3] 19 | 20 | #Reading the ELF header from the ".mdt" file 21 | mdt = open(os.path.join(trustlet_dir, "%s.mdt" % trustlet_name), "rb") 22 | elf_header = mdt.read(ELF_HEADER_SIZE) 23 | phnum = struct.unpack("I", rf.read(4))[0] 11 | blksize = unpack(">I", rf.read(4))[0] 12 | data = unpack(">I", rf.read(4))[0] 13 | rf.seek(curpos+0x3C) 14 | crc = unpack(">I", rf.read(4))[0] 15 | rf.seek(curpos) 16 | return [hdrsize,blksize,data,crc] 17 | 18 | def parse_ubihdr2(rf): 19 | curpos=rf.tell() 20 | magic = rf.read(4) 21 | if magic == b"UBI!": 22 | flag = unpack("I", rf.read(4))[0] 25 | rf.seek(curpos + 0x3C) 26 | crc = unpack(">I", rf.read(4))[0] 27 | rf.seek(curpos) 28 | return [flag,blk,crc] 29 | 30 | def main(): 31 | if len(sys.argv)<2: 32 | print("Usage: ubisqsh.py ") 33 | sys.exit() 34 | filename=sys.argv[1] 35 | with open(filename,'rb') as rf: 36 | with open(filename+".out","wb") as wf: 37 | pos=0 38 | while pos 6 | """ 7 | from __future__ import division, print_function 8 | 9 | try: 10 | from crcmod.crcmod import * 11 | import crcmod.predefined 12 | except ImportError: 13 | # Make this backward compatible 14 | from crcmod import * 15 | import predefined 16 | import argparse 17 | import struct 18 | from binascii import b2a_hex 19 | import lzo 20 | import zlib 21 | import os 22 | import errno 23 | import datetime 24 | import sys 25 | from collections import defaultdict 26 | 27 | import pkg_resources 28 | 29 | if sys.version_info[0] == 2: 30 | stdin = sys.stdin 31 | stdout = sys.stdout 32 | else: 33 | stdin = sys.stdin.buffer 34 | stdout = sys.stdout.buffer 35 | 36 | 37 | dependencies = [ 38 | 'python-lzo>=1.11', 39 | 'crcmod>=1.7' 40 | ] 41 | 42 | pkg_resources.require(dependencies) 43 | 44 | 45 | if sys.version_info[0] == 3: 46 | def cmp(a,b): 47 | return (a>b) - (a self.pos: 66 | self.seekforward(newpos - self.pos) 67 | self.pos = newpos 68 | elif whence==1: 69 | if newpos < 0: 70 | print("WARNING: can't seek stdout backwards") 71 | return -1 72 | if newpos > 0: 73 | self.seekforward(newpos) 74 | self.pos += newpos 75 | else: 76 | print("WARNING: can't seek stdout from EOF") 77 | return -1 78 | 79 | def seekforward(self, size): 80 | """ 81 | Seek forward by writing NUL bytes. 82 | """ 83 | sys.stdout.flush() 84 | chunk = b"\x00" * 0x10000 85 | while size > 0: 86 | if len(chunk) > size: 87 | chunk = chunk[:size] 88 | stdout.write(chunk) 89 | size -= len(chunk) 90 | 91 | def write(self, data): 92 | sys.stdout.flush() 93 | stdout.write(data) 94 | self.pos += len(data) 95 | 96 | def truncate(self, size): 97 | """ 98 | Ignore this. 99 | """ 100 | pass 101 | 102 | 103 | ########### block level objects ############ 104 | 105 | class UbiEcHeader: 106 | """ 107 | The Erase count header 108 | """ 109 | hdrsize = 16*4 110 | def __init__(self): 111 | self.magic = b'UBI#' 112 | def parse(self, data): 113 | self.magic, self.version, self.erasecount, self.vid_hdr_ofs, self.data_ofs, \ 114 | self.image_seq, hdr_crc = struct.unpack(">4sB3xQLLL32xL", data) 115 | if self.magic != b'UBI#': 116 | raise Exception("magic num mismatch") 117 | if hdr_crc != crc32(data[:-4]): 118 | raise Exception("crc mismatch") 119 | def encode(self): 120 | data = struct.pack(">4sB3xQLLL32x", self.magic, self.version, self.erasecount, self.vid_hdr_ofs, self.data_ofs, \ 121 | self.image_seq) 122 | return data + struct.pack(">L", crc32(data)) 123 | def __repr__(self): 124 | return "EC: magic=%s, v%d, ec=%d, vidhdr=%x, data=%x, imgseq=%x" % ( 125 | self.magic, self.version, self.erasecount, self.vid_hdr_ofs, 126 | self.data_ofs, self.image_seq) 127 | 128 | 129 | VTBL_VOLID=0x7fffefff 130 | class UbiVidHead: 131 | """ 132 | The volume id header 133 | """ 134 | hdrsize = 16*4 135 | def __init__(self): 136 | self.vol_id = VTBL_VOLID 137 | self.magic = b'UBI!' 138 | 139 | def parse(self, data): 140 | self.magic, self.version, self.vol_type, self.copy_flag, self.compat, self.vol_id, \ 141 | self.lnum, self.data_size, self.used_ebs, self.data_pad, self.data_crc, \ 142 | self.sqnum, hdr_crc = struct.unpack(">4s4BLL4x4L4xQ12xL", data) 143 | if self.magic != b'UBI!': 144 | raise Exception("magic num mismatch") 145 | if hdr_crc != crc32(data[:-4]): 146 | raise Exception("crc mismatch") 147 | 148 | def encode(self): 149 | data = struct.pack(">4s4BLL4x4L4xQ12x", self.magic, self.version, self.vol_type, self.copy_flag, self.compat, self.vol_id, \ 150 | self.lnum, self.data_size, self.used_ebs, self.data_pad, self.data_crc, \ 151 | self.sqnum) 152 | return data + struct.pack(">L", crc32(data)) 153 | 154 | def __repr__(self): 155 | if hasattr(self, 'magic'): 156 | return "VID: magic=%s, v%d, vt=%d, cp=%d, compat=%d, volid=%x, lnum=[%d], " \ 157 | "dsize=%d, usedebs=%d, datapad=%d, datacrc=%x, sqnum=%d" % ( 158 | self.magic, self.version, self.vol_type, self.copy_flag, self.compat, 159 | self.vol_id, self.lnum, self.data_size, self.used_ebs, self.data_pad, 160 | self.data_crc, self.sqnum) 161 | else: 162 | return "VID" 163 | 164 | 165 | class UbiVtblRecord: 166 | """ 167 | A volume table record. 168 | """ 169 | hdrsize = 4*4+128+24+4 170 | def __init__(self): 171 | self.reserved_pebs = 0 172 | def parse(self, data): 173 | self.reserved_pebs, self.alignment, self.data_pad, self.vol_type, self.upd_marker, \ 174 | name_len, self.name, self.flags, crc = struct.unpack(">3LBBH128sB23xL", data) 175 | if crc != crc32(data[:-4]): 176 | raise Exception("crc mismatch") 177 | self.name = self.name[:name_len] 178 | def encode(self): 179 | data = struct.pack(">3LBBH128sB23x", self.reserved_pebs, self.alignment, self.data_pad, self.vol_type, self.upd_marker, \ 180 | name_len, self.name, self.flags) 181 | return data + struct.pack(">L", crc32(data)) 182 | def empty(self): 183 | if hasattr(self, 'name'): 184 | return self.reserved_pebs==0 and self.alignment==0 and self.data_pad==0 \ 185 | and self.vol_type==0 and self.upd_marker==0 and self.name==b'' and self.flags==0 186 | else: 187 | return True 188 | 189 | def __repr__(self): 190 | return "VREC: rsvpebs=%d, align=%d, datapad=%d, voltype=%d, updmark=%d, flags=%x, name=%s" % ( 191 | self.reserved_pebs, self.alignment, self.data_pad, self.vol_type, 192 | self.upd_marker, self.flags, self.name) 193 | 194 | 195 | class UbiVolume: 196 | """ 197 | provides read access to a specific volume in an UBI image. 198 | """ 199 | def __init__(self, blks, volid, dataofs): 200 | """ 201 | takes an UbiBlocks object, a volumeid, and a baseoffset. 202 | """ 203 | self.blks = blks 204 | self.volid = volid 205 | self.dataofs = dataofs 206 | 207 | def read(self, lnum, offs, size): 208 | return self.blks.readvolume(self.volid, lnum, self.dataofs+offs, size) 209 | 210 | def write(self, lnum, offs, data): 211 | return self.blks.writevolume(self.volid, lnum, self.dataofs+offs, data) 212 | 213 | def hexdump(self, lnum, offs, size): 214 | print("[%03d:0x%05x] %s" % (lnum, offs, b2a_hex(self.read(lnum, offs, size)))) 215 | 216 | 217 | class RawVolume: 218 | """ 219 | provides read access to a raw data volume 220 | """ 221 | def __init__(self, fh): 222 | self.fh = fh 223 | self.leb_size = self.find_block_size() 224 | 225 | def read(self, lnum, offs, size): 226 | self.fh.seek(lnum*self.leb_size+offs) 227 | return self.fh.read(size) 228 | 229 | def write(self, lnum, offs, data): 230 | self.fh.seek(lnum*self.leb_size+offs) 231 | return self.fh.write(data) 232 | 233 | def find_block_size(self): 234 | self.fh.seek(0) 235 | data = self.fh.read(0x200) 236 | values = struct.unpack("<12L", data[:4*12]) 237 | if values[0] == 0x06101831 and values[5] == 6: 238 | # is superblock 239 | return values[9] 240 | 241 | def hexdump(self, lnum, offs, size): 242 | print("[%03d:0x%05x] %s" % (lnum, offs, b2a_hex(self.read(lnum, offs, size)))) 243 | 244 | 245 | class UbiBlocks: 246 | """ 247 | Block level access to an UBI image. 248 | """ 249 | def __init__(self, fh): 250 | self.fh = fh 251 | self.lebsize = self.find_blocksize() 252 | 253 | fh.seek(0, 2) 254 | self.filesize = fh.tell() 255 | self.maxlebs = self.filesize // self.lebsize 256 | 257 | self.scanblocks() 258 | 259 | if not VTBL_VOLID in self.vmap: 260 | print("no volume directory, %d physical volumes" % len(self.vmap)) 261 | return 262 | self.scanvtbls(self.vmap[VTBL_VOLID][0]) 263 | 264 | print("%d named volumes found, %d physical volumes, blocksize=0x%x" % (self.nr_named, len(self.vmap), self.lebsize)) 265 | 266 | def find_blocksize(self): 267 | self.fh.seek(0) 268 | magic = self.fh.read(4) 269 | if magic != b'UBI#': 270 | raise Exception("not an UBI image") 271 | for log_blocksize in range(10,20): 272 | self.fh.seek(1< physical lnum 281 | """ 282 | self.vmap = defaultdict(lambda : defaultdict(int)) 283 | for lnum in range(self.maxlebs): 284 | 285 | try: 286 | ec = UbiEcHeader() 287 | hdr = self.readblock(lnum, 0, ec.hdrsize) 288 | ec.parse(hdr) 289 | 290 | vid = UbiVidHead() 291 | viddata = self.readblock(lnum, ec.vid_hdr_ofs, vid.hdrsize) 292 | vid.parse(viddata) 293 | 294 | self.vmap[vid.vol_id][vid.lnum] = lnum 295 | except: 296 | pass 297 | 298 | def readblock(self, lnum, offs, size): 299 | self.fh.seek(lnum * self.lebsize + offs) 300 | return self.fh.read(size) 301 | 302 | def writeblock(self, lnum, offs, data): 303 | self.fh.seek(lnum * self.lebsize + offs) 304 | return self.fh.write(data) 305 | 306 | def hexdump(self, lnum, offs, size): 307 | print("[%03d:0x%05x] %s" % (lnum, offs, b2a_hex(self.readblock(lnum, offs, size)))) 308 | 309 | def scanvtbls(self, lnum): 310 | """ 311 | reads the volume table 312 | """ 313 | ec = UbiEcHeader() 314 | hdr = self.readblock(lnum, 0, ec.hdrsize) 315 | ec.parse(hdr) 316 | 317 | self.ec = ec 318 | 319 | try: 320 | vid = UbiVidHead() 321 | viddata = self.readblock(lnum, ec.vid_hdr_ofs, vid.hdrsize) 322 | vid.parse(viddata) 323 | 324 | self.vid = vid 325 | 326 | self.vtbl = [] 327 | self.nr_named = 0 328 | 329 | if vid.vol_id == VTBL_VOLID: 330 | for i in range(128): 331 | vrec = UbiVtblRecord() 332 | vrecdata = self.readblock(lnum, self.ec.data_ofs + i * vrec.hdrsize, vrec.hdrsize) 333 | vrec.parse(vrecdata) 334 | 335 | self.vtbl.append(vrec) 336 | 337 | if not vrec.empty(): 338 | self.nr_named += 1 339 | except: 340 | print(ec) 341 | print("viddata:%s" % b2a_hex(viddata)) 342 | import traceback 343 | traceback.print_exc() 344 | 345 | self.vid = UbiVidHead() 346 | self.vtbl = [ UbiVtblRecord() ] 347 | 348 | def dumpvtbl(self): 349 | print("%s %s" % (self.ec, self.vid)) 350 | for v in self.vtbl: 351 | if not v.empty(): 352 | print(" %s" % v) 353 | 354 | for volid, lmap in self.vmap.items(): 355 | print("volume %x : %d lebs" % (volid, len(lmap))) 356 | 357 | def nr_named(self): 358 | return self.nr_named 359 | 360 | def getvrec(self, volid): 361 | return self.vtbl[volid] 362 | 363 | def getvolume(self, volid): 364 | return UbiVolume(self, volid, self.ec.data_ofs) 365 | 366 | def readvolume(self, volid, lnum, offs, size): 367 | physlnum = self.vmap[volid].get(lnum, None) 368 | if physlnum is None: 369 | raise Exception("volume does not contain lnum") 370 | return self.readblock(physlnum, offs, size) 371 | 372 | def writevolume(self, volid, lnum, offs, data): 373 | physlnum = self.vmap[volid].get(lnum, None) 374 | if physlnum is None: 375 | raise Exception("volume does not contain lnum") 376 | return self.writeblock(physlnum, offs, data) 377 | 378 | 379 | 380 | ################ filesytem level objects ################## 381 | 382 | UBIFS_INO_KEY = 0 383 | UBIFS_DATA_KEY = 1 384 | UBIFS_DENT_KEY = 2 385 | UBIFS_XENT_KEY = 3 386 | 387 | """ 388 | key format: (inum, (type<<29) | value) 389 | 390 | key types: UBIFS_*_KEY: INO, DATA, DENT, XENT 391 | 392 | inode: + 0 393 | dirent: + hash 394 | xent: + hash 395 | data: + 0 397 | 398 | """ 399 | def unpackkey(key): 400 | if len(key)==16 and key[8:]!=b'\x00'*8: 401 | print("key has more than 8 bytes: %s" % b2a_hex(key)) 402 | inum, value = struct.unpack(">29, value&0x1FFFFFFF) 404 | 405 | 406 | def packkey(key): 407 | inum, ityp, value = key 408 | return struct.pack(">4 429 | a &= 0xFFFFFFFF 430 | a *= 11 431 | a &= 0xFFFFFFFF 432 | a &= 0x1FFFFFFF 433 | if a <= 2: a += 3 434 | return a 435 | 436 | 437 | def decompress(data, buflen, compr_type): 438 | if compr_type==0: 439 | return data 440 | elif compr_type==1: 441 | return lzo.decompress(data, False, buflen) 442 | elif compr_type==2: 443 | return zlib.decompress(data, -zlib.MAX_WBITS) 444 | else: 445 | raise Exception("unknown compression type") 446 | 447 | 448 | def compress(data, compr_type): 449 | if compr_type==0: 450 | return data 451 | elif compr_type==1: 452 | return lzo.compress(data, False) 453 | elif compr_type==2: 454 | return zlib.compress(data, -zlib.MAX_WBITS) 455 | else: 456 | raise Exception("unknown compression type") 457 | 458 | 459 | # the blocksize is a fixed value, independent of the underlying device. 460 | UBIFS_BLOCKSIZE = 4096 461 | 462 | ########### objects for the various node types ########### 463 | class UbiFsInode: 464 | """ 465 | Leafnode in the B-tree, contains information for a specific file or directory. 466 | 467 | It's b-tree key is formatted like this: 468 | * 32 bit inode number 469 | * the 3 bit node type: 0 for inode 470 | * a 29 bit zero value. 471 | """ 472 | nodetype = 0 473 | hdrsize = 16 + 5*8 + 11*4 + 2*4 + 28 474 | def __init__(self): 475 | pass 476 | def parse(self, data): 477 | self.key, self.creat_sqnum, self.size, self.atime_sec, self.ctime_sec, self.mtime_sec, \ 478 | self.atime_nsec, self.ctime_nsec, self.mtime_nsec, self.nlink, self.uid, self.gid, \ 479 | self.mode, self.flags, self.data_len, self.xattr_cnt, self.xattr_size, \ 480 | self.xattr_names, self.compr_type = struct.unpack("<16s5Q11L4xLH26x", data[:self.hdrsize]) 481 | 482 | # data contains the symlink string for symbolic links 483 | self.data = data[self.hdrsize:] 484 | if len(self.data) != self.data_len: 485 | raise Exception("inode data size mismatch") 486 | 487 | def encode(self): 488 | return struct.pack("<16s5Q11L4xLH26x", \ 489 | self.key, self.creat_sqnum, self.size, self.atime_sec, self.ctime_sec, self.mtime_sec, \ 490 | self.atime_nsec, self.ctime_nsec, self.mtime_nsec, self.nlink, self.uid, self.gid, \ 491 | self.mode, self.flags, self.data_len, self.xattr_cnt, self.xattr_size, \ 492 | self.xattr_names, self.compr_type) 493 | 494 | def inodedata_repr(self): 495 | types = ["0", "FIFO", "CHAR", "3", "DIRENT", "5", "BLOCK", "7", "FILE", "9", "LINK", "11", "SOCK", "13", "14", "15"] 496 | typ = (self.mode>>12)&0xF 497 | if typ in (2, 6): # CHAR or BLOCK 498 | return types[typ] + ":" + b2a_hex(self.data).decode('ascii') 499 | return types[typ] + ":%s" % self.data 500 | 501 | def __repr__(self): 502 | return "INODE: key=%s, sq=%04x, size=%5d, n=%3d, uid:gid=%d:%d, mode=%06o, fl=%x, dl=%3d, " \ 503 | "xattr=%d:%d, xanames=%d, comp=%d -- %s" % (formatkey(self.key), self.creat_sqnum, 504 | self.size, self.nlink, self.uid, self.gid, self.mode, self.flags, self.data_len, 505 | self.xattr_cnt, self.xattr_size, self.xattr_names, self.compr_type, self.inodedata_repr()) 506 | # todo: self.atime_sec, self.ctime_sec, self.mtime_sec, self.atime_nsec, self.ctime_nsec, self.mtime_nsec, 507 | def atime(self): 508 | return self.atime_sec + self.atime_nsec / 1000000000.0 509 | def mtime(self): 510 | return self.mtime_sec + self.mtime_nsec / 1000000000.0 511 | def ctime(self): 512 | return self.ctime_sec + self.ctime_nsec / 1000000000.0 513 | 514 | 515 | class UbiFsData: 516 | """ 517 | Leafnode in the B-tree, contains a datablock 518 | 519 | It's b-tree key is formatted like this: 520 | * 32 bit inode number 521 | * the 3 bit node type: 1 for data 522 | * a 29 bit file blocknumber 523 | """ 524 | nodetype = 1 525 | hdrsize = 16 + 4 + 4 526 | def __init__(self): 527 | pass 528 | def parse(self, data): 529 | self.key, self.size, self.compr_type = struct.unpack("<16sLH2x", data[:self.hdrsize]) 530 | self.data = decompress(data[self.hdrsize:], self.size, self.compr_type) 531 | if len(self.data) != self.size: 532 | raise Exception("data size mismatch") 533 | 534 | def encode(self): 535 | return struct.pack("<16sLH2x", self.key, len(self.data), self.compr_type) + compress(self.data, self.compr_type) 536 | 537 | def __repr__(self): 538 | return "DATA: key=%s, size=%d, comp=%d" % (formatkey(self.key), self.size, self.compr_type) 539 | 540 | 541 | class UbiFsDirEntry: 542 | """ 543 | Leafnode in the B-tree, contains a directory entry. 544 | 545 | It's b-tree key is formatted like this: 546 | * 32 bit inode number ( of the directory containing this dirent ) 547 | * the 3 bit node type: 2 for dirent 548 | * a 29 bit name hash 549 | """ 550 | TYPE_REGULAR = 0 551 | TYPE_DIRECTORY = 1 552 | TYPE_SYMLINK = 2 553 | TYPE_BLOCKDEV = 3 554 | TYPE_CHARDEV = 4 555 | TYPE_FIFO = 5 556 | TYPE_SOCKET = 6 557 | 558 | ALL_TYPES = 127 559 | 560 | nodetype = 2 561 | hdrsize = 16 + 8+4+4 562 | 563 | def __init__(self): 564 | pass 565 | def parse(self, data): 566 | self.key, self.inum, self.type, nlen = struct.unpack("<16sQxBH4x", data[:self.hdrsize]) 567 | self.name = data[self.hdrsize:-1] 568 | if len(self.name) != nlen: 569 | raise Exception("name length mismatch") 570 | def encode(self): 571 | return struct.pack("<16sQxBH4x", self.key, self.inum, self.type, nlen) 572 | def __repr__(self): 573 | typenames = [ 'reg', 'dir', 'lnk', 'blk', 'chr', 'fifo', 'sock' ] 574 | # type: UBIFS_ITYPE_REG, UBIFS_ITYPE_DIR, etc 575 | return "DIRENT: key=%s, inum=%05d, type=%d:%s -- %s" % (formatkey(self.key), self.inum, self.type, typenames[self.type], self.name) 576 | 577 | 578 | class UbiFsExtendedAttribute: 579 | """ 580 | Leafnode in the B-tree, contains extended attributes. 581 | 582 | It's b-tree key is formatted like this: 583 | * 32 bit inode number ( of the directory containing this dirent ) 584 | * the 3 bit node type: 3 for xent 585 | * a 29 bit hash of the attribute name. 586 | """ 587 | nodetype = 3 588 | hdrsize = 0 589 | def __init__(self): 590 | pass 591 | def parse(self, data): 592 | # TODO 593 | pass 594 | def __repr__(self): 595 | return "EA" 596 | 597 | 598 | class UbiFsTruncation: 599 | """ 600 | Used only in the journal 601 | """ 602 | nodetype = 4 603 | hdrsize = 4+12+2*8 604 | def __init__(self): 605 | pass 606 | def parse(self, data): 607 | self.inum, self.old_size, self.new_size = struct.unpack("%d" % (self.inum, self.old_size, self.new_size) 612 | 613 | 614 | class UbiFsPadding: 615 | """ 616 | """ 617 | nodetype = 5 618 | hdrsize = 4 619 | def __init__(self): 620 | pass 621 | def parse(self, data): 622 | self.pad_len, = struct.unpack_from("= len(data): 742 | raise Exception("parse error") 743 | branch = self.Branch() 744 | branch.parse(data[o:o+branch.hdrsize]) ; o += branch.hdrsize 745 | branch.key = data[o:o+8] ; o += 8 746 | self.branches.append(branch) 747 | def encode(self): 748 | data = struct.pack("= key, returns relation to the key 759 | 760 | these are all possibilities with 1 branches 761 | 762 | key < b0 -> 'lt', 0 763 | key == b0 -> 'eq', 0 764 | b0 < key -> 'gt', 0 765 | 766 | these are all possibilities with 2 branches 767 | key < b0 < b1 -> 'lt', 0 768 | key == b0 < b1 -> 'eq', 0 769 | b0 < key < b1 -> 'gt', 0 770 | b0 < key == b1 -> 'eq', 1 771 | b0 < b1 < key -> 'gt', 1 772 | 773 | add two more options for every next branch. 774 | 775 | """ 776 | for i, b in enumerate(self.branches): 777 | c = comparekeys(key, b.key) 778 | if c<0: 779 | if i==0: 780 | # before first item 781 | return ('lt', i) 782 | else: 783 | # between prev and this item 784 | return ('gt', i-1) 785 | elif c==0: 786 | # found item 787 | return ('eq', i) 788 | # else c>0 -> continue searching 789 | 790 | # after last item 791 | return ('gt', i) 792 | 793 | 794 | 795 | class UbiFsCommitStart: 796 | nodetype = 10 797 | hdrsize = 8 798 | def __init__(self): 799 | pass 800 | def parse(self, data): 801 | self.cmt_no, = struct.unpack(" want = %08x" % ( b2a_hex(hdrdata), b2a_hex(nodedata), crc32(hdrdata[8:] + nodedata), ch.crc)) 926 | raise Exception("invalid node crc") 927 | node.parse(nodedata) 928 | 929 | return node 930 | 931 | def writenode(self, node): 932 | """ 933 | Write a node from a lnum + offset. 934 | 935 | TODO 936 | """ 937 | 938 | nodedata = node.encode() 939 | 940 | node.hdr.len = len(nodedata) + node.hdr.hdrsize 941 | hdrdata = node.hdr.encode() 942 | 943 | node.hdr.crc = crc32(hdrdata[8:] + nodedata) 944 | hdrdata = node.hdr.encode() 945 | 946 | self.vol.write(node.hdr.lnum, node.hdr.offs, hdrdata+nodedata) 947 | 948 | def dumpnode(self, lnum, offs): 949 | node = self.readnode(lnum, offs) 950 | print("[%03d:0x%05x-0x%05x] %s" % (lnum, offs, offs+node.hdr.len, node)) 951 | 952 | def printrecursive(self, idx): 953 | """ 954 | Recursively dump all b-tree nodes. 955 | """ 956 | print("[%03d:0x%05x-0x%05x] %s" % (idx.hdr.lnum, idx.hdr.offs, idx.hdr.offs+idx.hdr.len, idx)) 957 | if not hasattr(idx, 'branches'): 958 | #print(idx) 959 | return 960 | for i, b in enumerate(idx.branches): 961 | print("%s %d %s -> " % (" " * (6-idx.level), i, b), end=" ") 962 | try: 963 | n = self.readnode(b.lnum, b.offs) 964 | self.printrecursive(n) 965 | except Exception as e: 966 | print("ERROR %s" % e) 967 | 968 | def printmbitems(self): 969 | print("--log [%03d] .. [%03d]" % (self.mst.log_lnum, self.mst.log_lnum+self.sb.log_lebs-1)) 970 | try: 971 | self.dumpnode(self.mst.log_lnum, 0) 972 | self.vol.hexdump(self.mst.log_lnum, 0, 0x100) 973 | except Exception as e: 974 | print(e) 975 | print("--root") 976 | try: 977 | self.dumpnode(self.mst.root_lnum, self.mst.root_offs) 978 | self.vol.hexdump(self.mst.root_lnum, self.mst.root_offs, self.mst.root_len) 979 | except Exception as e: 980 | print(e) 981 | print("--gc [%03d]" % (self.mst.gc_lnum)) 982 | try: 983 | self.vol.hexdump(self.mst.gc_lnum, 0, 0x100) 984 | except Exception as e: 985 | print(e) 986 | print("--ihead") 987 | try: 988 | self.vol.hexdump(self.mst.ihead_lnum, self.mst.ihead_offs, self.mst.index_size) 989 | except Exception as e: 990 | print(e) 991 | print("--lpt [%03d] .. [%03d]" % (self.mst.lpt_lnum, self.mst.lpt_lnum+self.sb.lpt_lebs-1)) 992 | try: 993 | self.vol.hexdump(self.mst.lpt_lnum, self.mst.lpt_offs, 0x100) 994 | except Exception as e: 995 | print(e) 996 | print("--nhead") 997 | try: 998 | self.vol.hexdump(self.mst.nhead_lnum, self.mst.nhead_offs, 0x100) 999 | except Exception as e: 1000 | print(e) 1001 | print("--ltab") 1002 | try: 1003 | self.vol.hexdump(self.mst.ltab_lnum, self.mst.ltab_offs, 0x100) 1004 | except Exception as e: 1005 | print(e) 1006 | print("--lsave") 1007 | try: 1008 | self.vol.hexdump(self.mst.lsave_lnum, self.mst.lsave_offs, 0x100) 1009 | self.dumpnode(self.mst.lsave_lnum, self.mst.lsave_offs) 1010 | except Exception as e: 1011 | print(e) 1012 | print("--lscan") 1013 | try: 1014 | self.vol.hexdump(self.mst.lscan_lnum, 0, 0x100) 1015 | self.dumpnode(self.mst.lscan_lnum, 0) 1016 | except Exception as e: 1017 | print(e) 1018 | 1019 | class Cursor: 1020 | """ 1021 | The Cursor represents a position in the b-tree. 1022 | """ 1023 | def __init__(self, fs, stack): 1024 | self.fs = fs 1025 | self.stack = stack 1026 | 1027 | def next(self): 1028 | """ move cursor to next entry """ 1029 | if not self.stack: 1030 | # starting at 'eof' 1031 | page = self.fs.root 1032 | ix = 0 1033 | else: 1034 | page, ix = self.stack.pop() 1035 | while self.stack and ix==len(page.branches)-1: 1036 | page, ix = self.stack.pop() 1037 | if ix==len(page.branches)-1: 1038 | return 1039 | ix += 1 1040 | self.stack.append( (page, ix) ) 1041 | while page.level: 1042 | page = self.fs.readnode(page.branches[ix].lnum, page.branches[ix].offs) 1043 | ix = 0 1044 | self.stack.append( (page, ix) ) 1045 | 1046 | def prev(self): 1047 | """ move cursor to next entry """ 1048 | if not self.stack: 1049 | # starting at 'eof' 1050 | page = self.fs.root 1051 | ix = len(page.branches)-1 1052 | else: 1053 | page, ix = self.stack.pop() 1054 | while self.stack and ix==0: 1055 | page, ix = self.stack.pop() 1056 | if ix==0: 1057 | return 1058 | ix -= 1 1059 | self.stack.append( (page, ix) ) 1060 | while page.level: 1061 | page = self.fs.readnode(page.branches[ix].lnum, page.branches[ix].offs) 1062 | ix = len(page.branches)-1 1063 | self.stack.append( (page, ix) ) 1064 | def eof(self): 1065 | return len(self.stack)==0 1066 | def __repr__(self): 1067 | return "[%s]" % (",".join(str(_[1]) for _ in self.stack)) 1068 | 1069 | def getkey(self): 1070 | """ 1071 | Returns the key tuple for the current item 1072 | """ 1073 | if self.stack: 1074 | page, ix = self.stack[-1] 1075 | return unpackkey(page.branches[ix].key) 1076 | 1077 | def getnode(self): 1078 | """ 1079 | Returns the node object for the current item 1080 | """ 1081 | if self.stack: 1082 | page, ix = self.stack[-1] 1083 | return self.fs.readnode(page.branches[ix].lnum, page.branches[ix].offs) 1084 | 1085 | 1086 | def find(self, rel, key, root=None): 1087 | """ 1088 | returns a cursor for the relation + key. 1089 | 1090 | ('lt', searchkey) searches for the highest ordered node with a key less than `searchkey` 1091 | ('ge', searchkey) searches for the lowest ordered node with a key greater or equal to `searchkey` 1092 | etc... 1093 | 1094 | """ 1095 | stack = [] 1096 | page = self.root if root is None else root 1097 | 1098 | while len(stack)<32: 1099 | act, ix = page.find(packkey(key)) 1100 | stack.append( (page, ix) ) 1101 | if page.level==0: 1102 | break 1103 | page = self.readnode(page.branches[ix].lnum, page.branches[ix].offs) 1104 | 1105 | if len(stack)==32: 1106 | raise Exception("tree too deep") 1107 | 1108 | cursor = self.Cursor(self, stack) 1109 | 1110 | """ 1111 | act rel: | lt le eq ge gt 1112 | (lt, 0) key < 0 | None None None pass pass 1113 | (eq, ix) key == ix | -- pass pass pass ++ 1114 | (gt, ix) ix < key < ix+1 | pass pass None ++ ++ 1115 | """ 1116 | 1117 | if (act+rel) in ('gtlt', 'gtle', 'eqle', 'eqeq', 'eqge', 'ltge', 'ltgt'): 1118 | return cursor 1119 | if (act+rel) in ('ltlt', 'ltle', 'lteq', 'gteq'): 1120 | return None 1121 | if (act+rel) == 'eqlt': 1122 | cursor.prev() 1123 | return cursor 1124 | if (act+rel) in ('eqgt', 'gtge', 'gtgt'): 1125 | cursor.next() 1126 | return cursor 1127 | 1128 | raise Exception("unexpected case") 1129 | 1130 | def setkey(self, key, node): 1131 | pass 1132 | #todo - adding a 1133 | 1134 | 1135 | def recursefiles(self, inum, path, filter = 1< inode.size: 1181 | print("WARNING: found more (%d bytes) for inode %05d, than specified in the inode(%d bytes) -- %s" % (savedlen, inum, inode.size, ubiname)) 1182 | elif savedlen < inode.size: 1183 | # padding file with zeros 1184 | fh.seek(inode.size) 1185 | fh.truncate(inode.size) 1186 | 1187 | def findfile(self, path, inum = 1): 1188 | """ 1189 | find the inode of the given `path`, starting in the directory specified by `inum` 1190 | 1191 | `path` must be a list of path elements. ( so not a '/' separated path string ) 1192 | """ 1193 | itype = UbiFsDirEntry.TYPE_DIRECTORY 1194 | for part in path: 1195 | if itype!=UbiFsDirEntry.TYPE_DIRECTORY: 1196 | # not a directory 1197 | return None 1198 | c = self.find('eq', (inum, UBIFS_DENT_KEY, namehash(part))) 1199 | if not c or c.eof(): 1200 | # not found 1201 | return None 1202 | dirent = c.getnode() 1203 | inum, itype = dirent.inum, dirent.type 1204 | return inum 1205 | 1206 | 1207 | def modestring(mode): 1208 | """ 1209 | return a "-rw-r--r--" style mode string 1210 | """ 1211 | # 4 bits type 1212 | # 3 bits suid/sgid/sticky 1213 | # 3 bits owner perm 1214 | # 3 bits group perm 1215 | # 3 bits other perm 1216 | typechar = "?pc?d?b?-?l?s???" 1217 | 1218 | def rwx(bits, extra, xchar): 1219 | rflag = "-r"[(bits>>2)&1] 1220 | wflag = "-w"[(bits>>1)&1] 1221 | xflag = ("-x" + xchar.upper() + xchar.lower())[(bits&1)+2*extra] 1222 | 1223 | return rflag + wflag + xflag 1224 | 1225 | return typechar[mode>>12] + rwx((mode>>6)&7, (mode>>11)&1, 's') + rwx((mode>>3)&7, (mode>>10)&1, 's') + rwx(mode&7, (mode>>9)&1, 't') 1226 | 1227 | 1228 | def timestring(t): 1229 | return datetime.datetime.utcfromtimestamp(t).strftime("%Y-%m-%d %H:%M:%S") 1230 | 1231 | 1232 | def processvolume(vol, volumename, args): 1233 | """ 1234 | Perform actions specified by `args` on `vol`. 1235 | 1236 | `vol` can be either a RawVolume ( an image file containing only the filesystem, 1237 | no flash block management layer. 1238 | 1239 | Or a UbiVolume, with the block management layer. 1240 | """ 1241 | fs = UbiFs(vol, args.masteroffset) 1242 | if args.verbose: 1243 | fs.dumpfs() 1244 | 1245 | root = fs.root 1246 | if args.root: 1247 | lnum, offset = args.root.split(':', 1) 1248 | lnum = int(lnum, 16) 1249 | offset = int(offset, 16) 1250 | root = fs.readnode(lnum, offset) 1251 | 1252 | if args.hexdump and isinstance(vol, RawVolume): 1253 | vol.hexdump(*args.hexdump) 1254 | if args.nodedump: 1255 | fs.dumpnode(*args.nodedump) 1256 | 1257 | if args.dumptree: 1258 | fs.printrecursive(root) 1259 | if args.verbose: 1260 | fs.printmbitems() 1261 | if args.savedir: 1262 | savedir = args.savedir.encode(args.encoding) 1263 | 1264 | count = 0 1265 | for inum, path in fs.recursefiles(1, [], root=root): 1266 | try: 1267 | os.makedirs(os.path.join(*[savedir, volumename] + path[:-1])) 1268 | except OSError as e: 1269 | # be happy if someone already created the path 1270 | if e.errno != errno.EEXIST: 1271 | raise 1272 | 1273 | fullpath = os.path.join(*[savedir, volumename] + path) 1274 | with open(fullpath, "wb") as fh: 1275 | fs.exportfile(inum, fh, os.path.join(*path)) 1276 | 1277 | if args.preserve: 1278 | c = fs.find('eq', (inum, UBIFS_INO_KEY, 0)) 1279 | inode = c.getnode() 1280 | 1281 | # note: we have to do this after closing the file, since the close after exportfile 1282 | # will update the last-modified time. 1283 | print("time = %s, %s -- %s" % (inode.atime(), inode.mtime(), fullpath)) 1284 | os.utime(fullpath, (inode.atime(), inode.mtime())) 1285 | os.chmod(fullpath, inode.mode) 1286 | 1287 | count += 1 1288 | print("saved %d files" % count) 1289 | 1290 | if args.listfiles: 1291 | for inum, path in fs.recursefiles(1, [], UbiFsDirEntry.ALL_TYPES, root=root): 1292 | c = fs.find('eq', (inum, UBIFS_INO_KEY, 0)) 1293 | inode = c.getnode() 1294 | 1295 | if (inode.mode>>12) in (2, 6): # char or block dev. 1296 | ma, mi = struct.unpack("BB", inode.data[:2]) 1297 | sizestr = "%d,%4d" % (ma, mi) 1298 | else: 1299 | sizestr = str(inode.size) 1300 | 1301 | if (inode.mode>>12) == 10: 1302 | linkdata = inode.data 1303 | if args.encoding: 1304 | linkdata = linkdata.decode(args.encoding, 'ignore') 1305 | linkstr = " -> %s" % linkdata 1306 | else: 1307 | linkstr = "" 1308 | 1309 | filename = b"/".join(path) 1310 | if args.encoding: 1311 | filename = filename.decode(args.encoding, 'ignore') 1312 | print("%s %2d %-5d %-5d %10s %s %s%s" % (modestring(inode.mode), inode.nlink, inode.uid, inode.gid, sizestr, timestring(inode.mtime_sec), filename, linkstr)) 1313 | 1314 | for srcfile in args.cat: 1315 | if len(args.cat)>1: 1316 | print("==>", srcfile, "<==") 1317 | inum = fs.findfile(srcfile.lstrip('/').split('/')) 1318 | if inum: 1319 | fs.exportfile(inum, SeekableStdout(), srcfile) 1320 | if len(args.cat)>1: 1321 | print() 1322 | else: 1323 | print("Not found") 1324 | 1325 | 1326 | def processblocks(fh, args): 1327 | """ 1328 | Perform operations on a UbiBlocks type image: starting with bytes 'UBI#' 1329 | """ 1330 | blks = UbiBlocks(fh) 1331 | if args.verbose: 1332 | print("===== block =====") 1333 | blks.dumpvtbl() 1334 | if args.hexdump: 1335 | if args.volume is None: 1336 | blks.hexdump(*args.hexdump) 1337 | else: 1338 | vol = blks.getvolume(args.volume) 1339 | vol.hexdump(*args.hexdump) 1340 | 1341 | for volid in range(128): 1342 | vrec = blks.getvrec(volid) 1343 | if vrec.empty(): 1344 | continue 1345 | vol = blks.getvolume(volid) 1346 | 1347 | try: 1348 | print("== volume %s ==" % vrec.name) 1349 | 1350 | processvolume(vol, vrec.name, args) 1351 | except Exception as e: 1352 | print("E: %s" % e) 1353 | if args.debug: 1354 | raise 1355 | 1356 | ################################################## 1357 | # raw hexdumper 1358 | def findpattern(data, pattn, blocksize): 1359 | o = 0 1360 | while o4sLQLLL32sL", data) 1379 | print("%08x: %s %08x %010x %08x %08x %08x %s %08x" % (o, m, v, ec, vidofs, datofs, iseq, zero, crc)) 1380 | 1381 | 1382 | def raw_vid_dump(o, data): 1383 | i = 0 1384 | while i < len(data): 1385 | print("%08x: %s" % (o+i, data[i:i+0xAC])) 1386 | i += 0xAC 1387 | 1388 | 1389 | def raw_vhdr_dump(o, data): 1390 | data = data.rstrip(b'\xff') 1391 | data2 = b'' 1392 | o2 = 0 1393 | if len(data)!=64: 1394 | data2 = data[64:].lstrip(b'\xff') 1395 | o2 = o + data.find(data2) 1396 | data = data[:64] 1397 | 1398 | m, v, vt, cf, compat, volid, lnum, zero1, dsize, usedebs, pad, dcrc, zero2, sqnum, zero3, hcrc = struct.unpack(">4s4BLL4s4L4sQ12sL", data) 1399 | 1400 | print("%08x: %s %d %d %d %d %08x %08x %s %08x %08x %08x %08x %s %010x %s %08x" % (o, m, v, vt, cf, compat, volid, lnum, zero1, dsize, usedebs, pad, dcrc, zero2, sqnum, zero3, hcrc)) 1401 | if len(data2)==0xAC*0x80: 1402 | raw_vid_dump(o2, data2) 1403 | 1404 | 1405 | def raw_node_dump(o, data): 1406 | ch = UbiFsCommonHeader() 1407 | ch.parse(data[:24]) 1408 | 1409 | node = ch.getnode() 1410 | try: 1411 | node.parse(data[24:]) 1412 | except Exception as e: 1413 | pass 1414 | 1415 | try: 1416 | print("%08x: %s - %s" % (o, repr(ch), repr(node))) 1417 | except Exception as e: 1418 | print("%08x: %s" % (o, b2a_hex(data))) 1419 | 1420 | 1421 | def rawhexdump(fh, args): 1422 | data = fh.read() 1423 | 1424 | ofs = [] 1425 | for pattn, bs in ((b'UBI#', 64), (b'UBI!', 64), (b'\x31\x18\x10\x06', 8)): 1426 | for o in findpattern(data, pattn, bs): 1427 | ofs.append( (o, pattn) ) 1428 | 1429 | ofs = sorted(ofs, key=lambda o: o[0]) 1430 | print("found %d magic numbers" % len(ofs)) 1431 | 1432 | ofs.append( (len(data), None) ) 1433 | 1434 | for (o0, p), (o1, _) in zip(ofs, ofs[1:]): 1435 | if p==b'UBI#': 1436 | raw_ec_dump(o0, data[o0:o1]) 1437 | elif p==b'UBI!': 1438 | raw_vhdr_dump(o0, data[o0:o1]) 1439 | elif p==b'\x31\x18\x10\x06': 1440 | raw_node_dump(o0, data[o0:o1]) 1441 | else: 1442 | print("%08x: %s" % (o0, b2a_hex(data[o0:o1]))) 1443 | 1444 | 1445 | ################################################## 1446 | def processfile(fn, args): 1447 | with open(fn, "rb") as fh: 1448 | if args.rawdump: 1449 | rawhexdump(fh, args) 1450 | else: 1451 | magic = fh.read(4) 1452 | if magic == b'UBI#': 1453 | processblocks(fh, args) 1454 | elif magic == b'\x31\x18\x10\x06': 1455 | processvolume(RawVolume(fh), b"raw", args) 1456 | else: 1457 | print("Unknown file type") 1458 | 1459 | 1460 | def main(): 1461 | parser = argparse.ArgumentParser(description='UBIFS dumper.') 1462 | parser.add_argument('--savedir', '-s', type=str, help="save files in all volumes to the specified directory", metavar='DIRECTORY') 1463 | parser.add_argument('--preserve', '-p', action='store_true', help="preserve permissions and timestamps") 1464 | parser.add_argument('--cat', '-c', type=str, action="append", help="extract a single file to stdout", metavar='FILE', default=[]) 1465 | parser.add_argument('--listfiles', '-l', action='store_true', help="list directory contents") 1466 | parser.add_argument('--dumptree', '-d', action='store_true', help="dump the filesystem b-tree contents") 1467 | parser.add_argument('--verbose', '-v', action='count', help="print extra info") 1468 | parser.add_argument('--debug', action='store_true', help="abort on exceptions") 1469 | parser.add_argument('--encoding', '-e', type=str, help="filename encoding, default=utf-8", default='utf-8') 1470 | parser.add_argument('--masteroffset', '-m', type=str, help="Which master node to use.") 1471 | parser.add_argument('--root', '-R', type=str, help="Which Root node to use (hexlnum:hexoffset).") 1472 | parser.add_argument('--rawdump', action='store_true', help="Raw hexdump of entire volume.") 1473 | parser.add_argument('--volume', type=str, help="which volume to hexdump") 1474 | parser.add_argument('--hexdump', type=str, help="hexdump part of a volume/leb[/ofs[/size]]", metavar="LEB:OFF:N") 1475 | parser.add_argument('--nodedump', type=str, help="dump specific node at volume/leb[/ofs]", metavar="LEB:OFF") 1476 | parser.add_argument('FILES', type=str, nargs='+', help="list of ubi images to use") 1477 | args = parser.parse_args() 1478 | 1479 | if args.masteroffset: 1480 | args.masteroffset = [int(_,0) for _ in args.masteroffset.split(':')] 1481 | if args.volume: 1482 | args.volume = int(args.volume, 0) 1483 | if args.hexdump: 1484 | args.hexdump = [int(_, 0) for _ in args.hexdump.split(":")] 1485 | if len(args.hexdump) == 1: 1486 | args.hexdump.append(0) 1487 | if len(args.hexdump) == 2: 1488 | args.hexdump.append(0x100) 1489 | 1490 | if args.nodedump: 1491 | args.nodedump = [int(_, 0) for _ in args.nodedump.split(":")] 1492 | if len(args.nodedump) == 1: 1493 | args.nodedump.append(0) 1494 | 1495 | for fn in args.FILES: 1496 | print("==>", fn, "<==") 1497 | try: 1498 | processfile(fn, args) 1499 | except Exception as e: 1500 | print("ERROR", e) 1501 | if args.debug: 1502 | raise 1503 | 1504 | 1505 | if __name__ == '__main__': 1506 | main() 1507 | 1508 | --------------------------------------------------------------------------------