mirror of https://github.com/x64dbg/btparser
279 lines
7.6 KiB
Plaintext
279 lines
7.6 KiB
Plaintext
|
//--------------------------------------
|
||
|
//--- 010 Editor v5.0beta1 Binary Template
|
||
|
//
|
||
|
// File: ROMFS.bt
|
||
|
// Author: Jordan Milne <jordan.milne-sw@saynotolinux.com>
|
||
|
// Revision: v0.2
|
||
|
// Purpose: Reverse engineering romfs images used in embedded devices
|
||
|
//--------------------------------------
|
||
|
|
||
|
//test suite in hex format at https://github.com/chexum/genromfs/tree/master/selftest
|
||
|
//format documentation at https://www.kernel.org/doc/Documentation/filesystems/romfs.txt
|
||
|
|
||
|
RequiresVersion(4, 0);
|
||
|
|
||
|
//RomFS uses big endian throughout
|
||
|
BigEndian();
|
||
|
|
||
|
//Color correct checksums green, bad checksums red
|
||
|
//Print warnings about correctness issues
|
||
|
#define STRICT_CHECKS 1
|
||
|
|
||
|
//Max number of bytes to read for the superblock checksum
|
||
|
//Defaults to 512 as per the spec.
|
||
|
#define MAX_SB_CHECKSUM_BYTES 512
|
||
|
|
||
|
//Magic bytes that identify a romfs image
|
||
|
#define MAGIC_BYTES_LEN 8
|
||
|
local char MAGIC_BYTES[] = "-rom1fs-";
|
||
|
|
||
|
//round up to the nearest multiple of 16
|
||
|
int pad16(int val)
|
||
|
{
|
||
|
return ((val & 0xf) ? (val + 16) & ~0xf : val);
|
||
|
}
|
||
|
|
||
|
//returns 0 for correct magic bytes, anything else is failure
|
||
|
int RomFSCheckMagicBytes()
|
||
|
{
|
||
|
local char found_bytes[MAGIC_BYTES_LEN];
|
||
|
ReadBytes(found_bytes, 0, MAGIC_BYTES_LEN);
|
||
|
return (Memcmp(MAGIC_BYTES, found_bytes, MAGIC_BYTES_LEN));
|
||
|
}
|
||
|
|
||
|
//returns 0 if checksum field for the superblock is correct and generate is 0
|
||
|
//returns the generated checksum if generate is 1
|
||
|
int32 RomFSSBChecksum(int generate)
|
||
|
{
|
||
|
local int64 skip_pos = -1;
|
||
|
local int32 sum;
|
||
|
|
||
|
//skip the checksum field when calculating the checksum
|
||
|
if(generate)
|
||
|
skip_pos = 3;
|
||
|
|
||
|
sum = RomFSChecksum(0, MAX_SB_CHECKSUM_BYTES, skip_pos);
|
||
|
|
||
|
//negate sum so checksum will work out to 0 when put it's put in the checksum field
|
||
|
//and generate is 0
|
||
|
if(generate)
|
||
|
sum = -sum;
|
||
|
|
||
|
return sum;
|
||
|
}
|
||
|
|
||
|
//alternative to ChecksumAlgStr CHECKSUM_INT_BE that can skip a byte
|
||
|
int32 RomFSChecksum(int64 pos, int64 num_bytes, int64 skip_pos)
|
||
|
{
|
||
|
local int64 i;
|
||
|
local int32 sum = 0;
|
||
|
local int64 file_size = FileSize();
|
||
|
if(num_bytes > file_size)
|
||
|
num_bytes = file_size;
|
||
|
|
||
|
//convert the number of bytes to number of ints
|
||
|
local int64 len = num_bytes >> 2;
|
||
|
|
||
|
for(i = 0; i < len; ++i)
|
||
|
{
|
||
|
if(i != skip_pos)
|
||
|
sum += ReadInt(pos + (i * 4));
|
||
|
}
|
||
|
|
||
|
return sum;
|
||
|
}
|
||
|
|
||
|
//get the length of the RomFS string at pos
|
||
|
int RomFSReadStringLength(int64 pos)
|
||
|
{
|
||
|
return pad16(ReadStringLength(pos));
|
||
|
}
|
||
|
|
||
|
//ROMFS strings are null-terminated and padded to 16 byte boundaries.
|
||
|
typedef struct {
|
||
|
string val;
|
||
|
|
||
|
//length (including terminating null)
|
||
|
local int len = Strlen(val) + 1;
|
||
|
local int pad_len = 16 - (len % 16);
|
||
|
|
||
|
//take up the rest of the space to reach the 16 byte boundary
|
||
|
if(pad_len)
|
||
|
char padding[pad_len];
|
||
|
} RomFSString <read=ReadRomFSString, write=WriteRomFSString>;
|
||
|
|
||
|
string ReadRomFSString(RomFSString& v)
|
||
|
{
|
||
|
string s;
|
||
|
SPrintf( s, "%s", v.val );
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
//Beware strings shorter than than old_len % 16 and longer than old_len + pad,
|
||
|
//they'll invalidate all of the positions (next_header, etc) in the entire image. Oh dear!
|
||
|
void WriteRomFSString(RomFSString& orig_str, string s)
|
||
|
{
|
||
|
local int start = startof(orig_str);
|
||
|
|
||
|
//Delete the old string + padding
|
||
|
DeleteBytes(start, RomFSReadStringLength(start));
|
||
|
//Make way for the new string
|
||
|
InsertBytes(start, pad16(Strlen(s) + 1));
|
||
|
|
||
|
//write in the new string
|
||
|
WriteString(start, s);
|
||
|
}
|
||
|
|
||
|
typedef struct {
|
||
|
#ifdef STRICT_CHECKS
|
||
|
if(RomFSCheckMagicBytes())
|
||
|
{
|
||
|
SetBackColor(cLtRed);
|
||
|
Printf("Incorrect magic bytes at %xh, expected \"%s\"\n", FTell(), MAGIC_BYTES);
|
||
|
}
|
||
|
else
|
||
|
SetBackColor(cLtGreen);
|
||
|
#endif
|
||
|
|
||
|
char magic_bytes[MAGIC_BYTES_LEN] <comment="Magic bytes">;
|
||
|
SetBackColor(cNone);
|
||
|
|
||
|
uint32 fs_size <comment="Filesystem size (in bytes)">;
|
||
|
#ifdef STRICT_CHECKS
|
||
|
//verify the checksum of the superblock (image)
|
||
|
if(RomFSSBChecksum(0))
|
||
|
{
|
||
|
SetBackColor(cLtRed);
|
||
|
Printf("Checksum Mismatch on superblock at %xh\n", FTell());
|
||
|
}
|
||
|
else
|
||
|
SetBackColor(cLtGreen);
|
||
|
#endif
|
||
|
int32 checksum <comment="Sum of the first 512 (or all, if the file is smaller) bytes of the file">;
|
||
|
SetBackColor(cNone);
|
||
|
|
||
|
RomFSString vol_name;
|
||
|
} RomFSHeader;
|
||
|
|
||
|
typedef enum rom_fs_entry_type {
|
||
|
HARD_LINK = 0,
|
||
|
DIRECTORY = 1,
|
||
|
FILE = 2,
|
||
|
SYMLINK = 3,
|
||
|
BLOCK_DEV = 4,
|
||
|
CHAR_DEV = 5,
|
||
|
SOCKET = 6,
|
||
|
FIFO = 7
|
||
|
} RomFSEntryType;
|
||
|
|
||
|
typedef int32 RomFSNextHeader <read=ReadNextHeader, write=WriteNextHeader>;
|
||
|
|
||
|
typedef struct {
|
||
|
RomFSNextHeader next_header : 28 <name="next_header", comment="Position of the next entry in the directory">;
|
||
|
int executable : 1 <name="executable", comment="Whether or not the entry is executable">;
|
||
|
RomFSEntryType type : 3 <name="type", comment="Type of entry">;
|
||
|
} RomFSObjectHdr <optimize=false, read=ReadRomFSObjectHdr>;
|
||
|
|
||
|
string ReadRomFSObjectHdr(RomFSObjectHdr& hdr)
|
||
|
{
|
||
|
string s;
|
||
|
SPrintf(s, "Exec: %d, Next: %s", hdr.executable, ReadNextHeader(hdr.next_header));
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
string ReadNextHeader(int32 next_header)
|
||
|
{
|
||
|
string s;
|
||
|
SPrintf(s, "%d", next_header << 4);
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
void WriteNextHeader(RomFSNextHeader& v, string s)
|
||
|
{
|
||
|
SScanf(s, "%d", v);
|
||
|
v >>= 4;
|
||
|
}
|
||
|
|
||
|
//some people generate invalid dir entries for . and ..
|
||
|
//and mess up parsing. ignore them.
|
||
|
int ValidDirName(string name)
|
||
|
{
|
||
|
return (name != "." && name != "..");
|
||
|
}
|
||
|
|
||
|
typedef struct {
|
||
|
SetBackColor(cLtPurple);
|
||
|
RomFSObjectHdr header;
|
||
|
SetBackColor(0xB9E2FF);
|
||
|
|
||
|
local RomFSEntryType type = header.type;
|
||
|
if(type == DIRECTORY)
|
||
|
int32 special_info <comment="position of the first file entry">;
|
||
|
else if(type == HARD_LINK)
|
||
|
int32 special_info <comment="link destination">;
|
||
|
else if(type == BLOCK_DEV)
|
||
|
int32 special_info <comment="16/16 major/minor number">;
|
||
|
else
|
||
|
int32 special_info <comment="Must be zero">;
|
||
|
int32 data_size <comment="Size of data in bytes (should be 0 except for files and symlinks">;
|
||
|
|
||
|
local int64 struct_start = startof(this);
|
||
|
#ifdef STRICT_CHECKS
|
||
|
//get the length of the struct (with the filename)
|
||
|
local int64 struct_len = 16 + RomFSReadStringLength(struct_start + 16);
|
||
|
|
||
|
if(RomFSChecksum(struct_start, struct_len, -1))
|
||
|
{
|
||
|
Printf("Checksum Mismatch on entry at %xh\n", FTell());
|
||
|
SetBackColor(cRed);
|
||
|
}
|
||
|
else
|
||
|
SetBackColor(cGreen);
|
||
|
#endif
|
||
|
int32 checksum <comment="Checksum for the entire entry excluding data field">;
|
||
|
SetBackColor(0xE2B9FF);
|
||
|
RomFSString name;
|
||
|
if(data_size)
|
||
|
{
|
||
|
SetBackColor(0xFFE2B9);
|
||
|
ubyte data[data_size] <comment="File contents or symlink destination">;
|
||
|
}
|
||
|
} RomFSObject <read=ReadRomFSObject, optimize=false>;
|
||
|
|
||
|
//Try to create entries for the subdirectory's children (if it's a directory and it has children)
|
||
|
void RomFSTryRecurse(RomFSObject& obj)
|
||
|
{
|
||
|
//don't recurse down self-referential or invalid directories
|
||
|
if(obj.header.type == DIRECTORY && ValidDirName(obj.name.val) && obj.special_info != startof(obj))
|
||
|
Recurse(obj.special_info);
|
||
|
}
|
||
|
|
||
|
string ReadRomFSObject(RomFSObject& v)
|
||
|
{
|
||
|
string s;
|
||
|
string type = EnumToString(v.header.type);
|
||
|
SPrintf(s, "%s: %s", type, v.name.val);
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
RomFSHeader header<optimize=false, comment="Filesystem header">;
|
||
|
|
||
|
|
||
|
void Recurse(int64 pos)
|
||
|
{
|
||
|
//while there are still files in the directory
|
||
|
while(pos)
|
||
|
{
|
||
|
FSeek(pos);
|
||
|
RomFSObject entry<comment="Filesystem entry">;
|
||
|
|
||
|
//Get the position of the next entry in the current directory
|
||
|
pos = entry.header.next_header << 4;
|
||
|
|
||
|
//Parse all of the children of this entry if it's valid directory
|
||
|
RomFSTryRecurse(entry);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Recurse(FTell());
|