| ... | ... |
@@ -174,7 +174,8 @@ static const char *ftypes_int[] = {
|
| 174 | 174 |
"1:0:cafebabe0000003?:Java class file:CL_TYPE_ANY:CL_TYPE_JAVA:73", |
| 175 | 175 |
"1:EOF-512:6b6f6c79:DMG container file:CL_TYPE_ANY:CL_TYPE_DMG:75", |
| 176 | 176 |
"0:0:78617221:XAR container file:CL_TYPE_ANY:CL_TYPE_XAR:75", |
| 177 |
- "4:1024:482B:HFS+ partition:CL_TYPE_PART_ANY:CL_TYPE_PART_HFSPLUS:75", |
|
| 177 |
+ "4:1024:482B0004:HFS+ partition:CL_TYPE_PART_ANY:CL_TYPE_PART_HFSPLUS:75", |
|
| 178 |
+ "4:1024:48580005:HFSX partition:CL_TYPE_PART_ANY:CL_TYPE_PART_HFSPLUS:75", |
|
| 178 | 179 |
NULL |
| 179 | 180 |
}; |
| 180 | 181 |
|
| ... | ... |
@@ -27,16 +27,450 @@ |
| 27 | 27 |
#include "hfsplus.h" |
| 28 | 28 |
#include "scanners.h" |
| 29 | 29 |
|
| 30 |
+static void headerrecord_to_host(hfsHeaderRecord *); |
|
| 31 |
+static void headerrecord_print(const char *, hfsHeaderRecord *); |
|
| 32 |
+static void nodedescriptor_to_host(hfsNodeDescriptor *); |
|
| 33 |
+static void nodedescriptor_print(const char *, hfsNodeDescriptor *); |
|
| 34 |
+static void forkdata_to_host(hfsPlusForkData *); |
|
| 35 |
+static void forkdata_print(const char *, hfsPlusForkData *); |
|
| 36 |
+ |
|
| 37 |
+static int hfsplus_volumeheader(cli_ctx *, hfsPlusVolumeHeader **); |
|
| 38 |
+static int hfsplus_readheader(cli_ctx *, hfsPlusVolumeHeader *, hfsNodeDescriptor *, |
|
| 39 |
+ hfsHeaderRecord *, int, const char *); |
|
| 40 |
+static int hfsplus_scanfile(cli_ctx *, hfsPlusVolumeHeader *, hfsHeaderRecord *, |
|
| 41 |
+ hfsPlusForkData *, const char *); |
|
| 42 |
+ |
|
| 43 |
+/* Header Record : fix endianness for useful fields */ |
|
| 44 |
+static void headerrecord_to_host(hfsHeaderRecord *hdr) |
|
| 45 |
+{
|
|
| 46 |
+ hdr->treeDepth = be16_to_host(hdr->treeDepth); |
|
| 47 |
+ hdr->rootNode = be32_to_host(hdr->rootNode); |
|
| 48 |
+ hdr->leafRecords = be32_to_host(hdr->leafRecords); |
|
| 49 |
+ hdr->firstLeafNode = be32_to_host(hdr->firstLeafNode); |
|
| 50 |
+ hdr->lastLeafNode = be32_to_host(hdr->lastLeafNode); |
|
| 51 |
+ hdr->nodeSize = be16_to_host(hdr->nodeSize); |
|
| 52 |
+ hdr->maxKeyLength = be16_to_host(hdr->maxKeyLength); |
|
| 53 |
+ hdr->totalNodes = be32_to_host(hdr->totalNodes); |
|
| 54 |
+ hdr->freeNodes = be32_to_host(hdr->freeNodes); |
|
| 55 |
+ hdr->attributes = be32_to_host(hdr->attributes); /* not too useful */ |
|
| 56 |
+} |
|
| 57 |
+ |
|
| 58 |
+static void headerrecord_print(const char *pfx, hfsHeaderRecord *hdr) |
|
| 59 |
+{
|
|
| 60 |
+ cli_dbgmsg("%s Header: depth %hu root %u leafRecords %u firstLeaf %u lastLeaf %u nodeSize %hu\n",
|
|
| 61 |
+ pfx, hdr->treeDepth, hdr->rootNode, hdr->leafRecords, hdr->firstLeafNode, |
|
| 62 |
+ hdr->lastLeafNode, hdr->nodeSize); |
|
| 63 |
+ cli_dbgmsg("%s Header: maxKeyLength %hu totalNodes %u freeNodes %u btreeType %hu attributes %x\n",
|
|
| 64 |
+ pfx, hdr->maxKeyLength, hdr->totalNodes, hdr->freeNodes, |
|
| 65 |
+ hdr->btreeType, hdr->attributes); |
|
| 66 |
+} |
|
| 67 |
+ |
|
| 68 |
+/* Node Descriptor : fix endianness for useful fields */ |
|
| 69 |
+static void nodedescriptor_to_host(hfsNodeDescriptor *node) |
|
| 70 |
+{
|
|
| 71 |
+ node->fLink = be32_to_host(node->fLink); |
|
| 72 |
+ node->bLink = be32_to_host(node->bLink); |
|
| 73 |
+ node->numRecords = be16_to_host(node->numRecords); |
|
| 74 |
+} |
|
| 75 |
+ |
|
| 76 |
+static void nodedescriptor_print(const char *pfx, hfsNodeDescriptor *node) |
|
| 77 |
+{
|
|
| 78 |
+ cli_dbgmsg("%s Desc: fLink %u bLink %u kind %d height %u numRecords %u\n",
|
|
| 79 |
+ pfx, node->fLink, node->bLink, node->kind, node->height, node->numRecords); |
|
| 80 |
+} |
|
| 81 |
+ |
|
| 82 |
+/* ForkData : fix endianness */ |
|
| 83 |
+static void forkdata_to_host(hfsPlusForkData *fork) |
|
| 84 |
+{
|
|
| 85 |
+ int i; |
|
| 86 |
+ |
|
| 87 |
+ fork->logicalSize = be64_to_host(fork->logicalSize); |
|
| 88 |
+ fork->clumpSize = be32_to_host(fork->clumpSize); /* does this matter for read-only? */ |
|
| 89 |
+ fork->totalBlocks = be32_to_host(fork->totalBlocks); |
|
| 90 |
+ for (i=0; i < 8; i++) {
|
|
| 91 |
+ fork->extents[i].startBlock = be32_to_host(fork->extents[i].startBlock); |
|
| 92 |
+ fork->extents[i].blockCount = be32_to_host(fork->extents[i].blockCount); |
|
| 93 |
+ } |
|
| 94 |
+} |
|
| 95 |
+ |
|
| 96 |
+static void forkdata_print(const char *pfx, hfsPlusForkData *fork) |
|
| 97 |
+{
|
|
| 98 |
+ int i; |
|
| 99 |
+ cli_dbgmsg("%s logicalSize %lu clumpSize %lu totalBlocks %lu\n", pfx,
|
|
| 100 |
+ fork->logicalSize, fork->clumpSize, fork->totalBlocks); |
|
| 101 |
+ for (i=0; i < 8; i++) {
|
|
| 102 |
+ if (fork->extents[i].startBlock == 0) |
|
| 103 |
+ break; |
|
| 104 |
+ cli_dbgmsg("%s extent[%d] startBlock %lu blockCount %lu\n", pfx, i,
|
|
| 105 |
+ fork->extents[i].startBlock, fork->extents[i].blockCount); |
|
| 106 |
+ } |
|
| 107 |
+} |
|
| 108 |
+ |
|
| 109 |
+/* Read and convert the HFS+ volume header */ |
|
| 110 |
+static int hfsplus_volumeheader(cli_ctx *ctx, hfsPlusVolumeHeader **header) |
|
| 111 |
+{
|
|
| 112 |
+ hfsPlusVolumeHeader *volHeader; |
|
| 113 |
+ const uint8_t *mPtr; |
|
| 114 |
+ |
|
| 115 |
+ if (!header) {
|
|
| 116 |
+ return CL_ENULLARG; |
|
| 117 |
+ } |
|
| 118 |
+ |
|
| 119 |
+ /* Start with volume header, 512 bytes at offset 1024 */ |
|
| 120 |
+ if ((*ctx->fmap)->len < 1536) {
|
|
| 121 |
+ cli_dbgmsg("cli_scanhfsplus: too short for HFS+\n");
|
|
| 122 |
+ return CL_EFORMAT; |
|
| 123 |
+ } |
|
| 124 |
+ mPtr = fmap_need_off_once(*ctx->fmap, 1024, 512); |
|
| 125 |
+ if (!mPtr) {
|
|
| 126 |
+ cli_errmsg("cli_scanhfsplus: cannot read header from map\n");
|
|
| 127 |
+ return CL_EMAP; |
|
| 128 |
+ } |
|
| 129 |
+ |
|
| 130 |
+ volHeader = cli_malloc(sizeof(hfsPlusVolumeHeader)); |
|
| 131 |
+ if (!volHeader) {
|
|
| 132 |
+ cli_errmsg("cli_scanhfsplus: header malloc failed\n");
|
|
| 133 |
+ return CL_EMEM; |
|
| 134 |
+ } |
|
| 135 |
+ *header = volHeader; |
|
| 136 |
+ memcpy(volHeader, mPtr, 512); |
|
| 137 |
+ |
|
| 138 |
+ volHeader->signature = be16_to_host(volHeader->signature); |
|
| 139 |
+ volHeader->version = be16_to_host(volHeader->version); |
|
| 140 |
+ if ((volHeader->signature == 0x482B) && (volHeader->version == 4)) {
|
|
| 141 |
+ cli_dbgmsg("cli_scanhfsplus: HFS+ signature matched\n");
|
|
| 142 |
+ return CL_EFORMAT; |
|
| 143 |
+ } |
|
| 144 |
+ if ((volHeader->signature == 0x4858) && (volHeader->version == 5)) {
|
|
| 145 |
+ cli_dbgmsg("cli_scanhfsplus: HFSX v5 signature matched\n");
|
|
| 146 |
+ return CL_EFORMAT; |
|
| 147 |
+ } |
|
| 148 |
+ else {
|
|
| 149 |
+ cli_dbgmsg("cli_scanhfsplus: no matching signature\n");
|
|
| 150 |
+ return CL_EFORMAT; |
|
| 151 |
+ } |
|
| 152 |
+ /* skip fields that will definitely be ignored */ |
|
| 153 |
+ volHeader->attributes = be32_to_host(volHeader->attributes); |
|
| 154 |
+ volHeader->fileCount = be32_to_host(volHeader->fileCount); |
|
| 155 |
+ volHeader->folderCount = be32_to_host(volHeader->folderCount); |
|
| 156 |
+ volHeader->blockSize = be32_to_host(volHeader->blockSize); |
|
| 157 |
+ volHeader->totalBlocks = be32_to_host(volHeader->totalBlocks); |
|
| 158 |
+ |
|
| 159 |
+ cli_dbgmsg("HFS+ Header:\n");
|
|
| 160 |
+ cli_dbgmsg("Signature: %x\n", volHeader->signature);
|
|
| 161 |
+ cli_dbgmsg("Attributes: %x\n", volHeader->attributes);
|
|
| 162 |
+ cli_dbgmsg("File Count: %lu\n", volHeader->fileCount);
|
|
| 163 |
+ cli_dbgmsg("Folder Count: %lu\n", volHeader->folderCount);
|
|
| 164 |
+ cli_dbgmsg("Block Size: %lu\n", volHeader->blockSize);
|
|
| 165 |
+ cli_dbgmsg("Total Blocks: %lu\n", volHeader->totalBlocks);
|
|
| 166 |
+ |
|
| 167 |
+ /* Block Size must be power of 2 between 512 and 1 MB */ |
|
| 168 |
+ if ((volHeader->blockSize < 512) || (volHeader->blockSize > (1 << 20))) {
|
|
| 169 |
+ cli_dbgmsg("cli_scanhfsplus: Invalid blocksize\n");
|
|
| 170 |
+ return CL_EFORMAT; |
|
| 171 |
+ } |
|
| 172 |
+ if (volHeader->blockSize & (volHeader->blockSize - 1)) {
|
|
| 173 |
+ cli_dbgmsg("cli_scanhfsplus: Invalid blocksize\n");
|
|
| 174 |
+ return CL_EFORMAT; |
|
| 175 |
+ } |
|
| 176 |
+ |
|
| 177 |
+ forkdata_to_host(&(volHeader->allocationFile)); |
|
| 178 |
+ forkdata_to_host(&(volHeader->extentsFile)); |
|
| 179 |
+ forkdata_to_host(&(volHeader->catalogFile)); |
|
| 180 |
+ forkdata_to_host(&(volHeader->attributesFile)); |
|
| 181 |
+ forkdata_to_host(&(volHeader->startupFile)); |
|
| 182 |
+ |
|
| 183 |
+ if (cli_debug_flag) {
|
|
| 184 |
+ forkdata_print("allocationFile", &(volHeader->allocationFile));
|
|
| 185 |
+ forkdata_print("extentsFile", &(volHeader->extentsFile));
|
|
| 186 |
+ forkdata_print("catalogFile", &(volHeader->catalogFile));
|
|
| 187 |
+ forkdata_print("attributesFile", &(volHeader->attributesFile));
|
|
| 188 |
+ forkdata_print("startupFile", &(volHeader->startupFile));
|
|
| 189 |
+ } |
|
| 190 |
+ |
|
| 191 |
+ return CL_CLEAN; |
|
| 192 |
+} |
|
| 193 |
+ |
|
| 194 |
+/* Read and convert the header node */ |
|
| 195 |
+static int hfsplus_readheader(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsNodeDescriptor *nodeDesc, |
|
| 196 |
+ hfsHeaderRecord *headerRec, int headerType, const char *name) |
|
| 197 |
+{
|
|
| 198 |
+ const uint8_t *mPtr = NULL; |
|
| 199 |
+ off_t offset; |
|
| 200 |
+ uint32_t minSize; |
|
| 201 |
+ |
|
| 202 |
+ /* Node Size must be power of 2 between 512 and 32768 */ |
|
| 203 |
+ /* Node Size for Catalog or Attributes must be at least 4096 */ |
|
| 204 |
+ switch (headerType) {
|
|
| 205 |
+ case HFS_FILETREE_ALLOCATION: |
|
| 206 |
+ offset = volHeader->allocationFile.extents[0].startBlock * volHeader->blockSize; |
|
| 207 |
+ minSize = 512; |
|
| 208 |
+ break; |
|
| 209 |
+ case HFS_FILETREE_EXTENTS: |
|
| 210 |
+ offset = volHeader->extentsFile.extents[0].startBlock * volHeader->blockSize; |
|
| 211 |
+ minSize = 512; |
|
| 212 |
+ break; |
|
| 213 |
+ case HFS_FILETREE_CATALOG: |
|
| 214 |
+ offset = volHeader->catalogFile.extents[0].startBlock * volHeader->blockSize; |
|
| 215 |
+ minSize = 4096; |
|
| 216 |
+ break; |
|
| 217 |
+ case HFS_FILETREE_ATTRIBUTES: |
|
| 218 |
+ offset = volHeader->attributesFile.extents[0].startBlock * volHeader->blockSize; |
|
| 219 |
+ minSize = 4096; |
|
| 220 |
+ break; |
|
| 221 |
+ case HFS_FILETREE_STARTUP: |
|
| 222 |
+ offset = volHeader->startupFile.extents[0].startBlock * volHeader->blockSize; |
|
| 223 |
+ minSize = 512; |
|
| 224 |
+ break; |
|
| 225 |
+ default: |
|
| 226 |
+ cli_errmsg("hfsplus_readheader: %s: invalid headerType %d\n", name, headerType);
|
|
| 227 |
+ return CL_EARG; |
|
| 228 |
+ } |
|
| 229 |
+ mPtr = fmap_need_off_once(*ctx->fmap, offset, volHeader->blockSize); |
|
| 230 |
+ if (!mPtr) {
|
|
| 231 |
+ cli_dbgmsg("hfsplus_header: %s: headerNode is out-of-range\n", name);
|
|
| 232 |
+ return CL_EFORMAT; |
|
| 233 |
+ } |
|
| 234 |
+ |
|
| 235 |
+ /* Node descriptor first */ |
|
| 236 |
+ memcpy(nodeDesc, mPtr, sizeof(hfsNodeDescriptor)); |
|
| 237 |
+ nodedescriptor_to_host(nodeDesc); |
|
| 238 |
+ nodedescriptor_print(name, nodeDesc); |
|
| 239 |
+ if (nodeDesc->kind != HFS_NODEKIND_HEADER) {
|
|
| 240 |
+ cli_dbgmsg("hfsplus_header: %s: headerNode not header kind\n", name);
|
|
| 241 |
+ return CL_EFORMAT; |
|
| 242 |
+ } |
|
| 243 |
+ if ((nodeDesc->bLink != 0) || (nodeDesc->height != 0) || (nodeDesc->numRecords != 3)) {
|
|
| 244 |
+ cli_dbgmsg("hfsplus_header: %s: Invalid headerNode\n", name);
|
|
| 245 |
+ return CL_EFORMAT; |
|
| 246 |
+ } |
|
| 247 |
+ |
|
| 248 |
+ /* Then header record */ |
|
| 249 |
+ memcpy(headerRec, mPtr + sizeof(hfsNodeDescriptor), sizeof(hfsHeaderRecord)); |
|
| 250 |
+ headerrecord_to_host(headerRec); |
|
| 251 |
+ headerrecord_print(name, headerRec); |
|
| 252 |
+ |
|
| 253 |
+ if ((headerRec->nodeSize < minSize) || (volHeader->blockSize > 32768)) {
|
|
| 254 |
+ cli_dbgmsg("hfsplus_header: %s: Invalid nodesize\n", name);
|
|
| 255 |
+ return CL_EFORMAT; |
|
| 256 |
+ } |
|
| 257 |
+ if (headerRec->nodeSize & (headerRec->nodeSize - 1)) {
|
|
| 258 |
+ cli_dbgmsg("hfsplus_header: %s: Invalid nodesize\n", name);
|
|
| 259 |
+ return CL_EFORMAT; |
|
| 260 |
+ } |
|
| 261 |
+ /* KeyLength must be between 6 and 516 for catalog */ |
|
| 262 |
+ if (headerType == HFS_FILETREE_CATALOG) {
|
|
| 263 |
+ if ((headerRec->maxKeyLength < 6) || (headerRec->maxKeyLength > 516)) {
|
|
| 264 |
+ cli_dbgmsg("hfsplus_header: %s: Invalid cat maxKeyLength\n", name);
|
|
| 265 |
+ return CL_EFORMAT; |
|
| 266 |
+ } |
|
| 267 |
+ if (headerRec->maxKeyLength > (headerRec->nodeSize / 2)) {
|
|
| 268 |
+ cli_dbgmsg("hfsplus_header: %s: Invalid cat maxKeyLength based on nodeSize\n", name);
|
|
| 269 |
+ return CL_EFORMAT; |
|
| 270 |
+ } |
|
| 271 |
+ } |
|
| 272 |
+ else if (headerType == HFS_FILETREE_EXTENTS) {
|
|
| 273 |
+ if (headerRec->maxKeyLength != 10) {
|
|
| 274 |
+ cli_dbgmsg("hfsplus_header: %s: Invalid ext maxKeyLength\n", name);
|
|
| 275 |
+ return CL_EFORMAT; |
|
| 276 |
+ } |
|
| 277 |
+ } |
|
| 278 |
+ |
|
| 279 |
+ /* hdr->treeDepth = rootnode->height */ |
|
| 280 |
+ return CL_CLEAN; |
|
| 281 |
+} |
|
| 282 |
+ |
|
| 283 |
+/* Read and dump a file for scanning */ |
|
| 284 |
+static int hfsplus_scanfile(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsHeaderRecord *extHeader, |
|
| 285 |
+ hfsPlusForkData *fork, const char *dirname) |
|
| 286 |
+{
|
|
| 287 |
+ hfsPlusExtentDescriptor *currExt; |
|
| 288 |
+ const uint8_t *mPtr = NULL; |
|
| 289 |
+ char *tmpname = NULL; |
|
| 290 |
+ int ofd, ret = CL_CLEAN; |
|
| 291 |
+ uint64_t targetSize; |
|
| 292 |
+ uint32_t outputBlocks = 0; |
|
| 293 |
+ uint8_t ext; |
|
| 294 |
+ |
|
| 295 |
+ /* bad record checks */ |
|
| 296 |
+ if (!fork || (fork->logicalSize == 0) || (fork->totalBlocks == 0)) {
|
|
| 297 |
+ cli_dbgmsg("hfsplus_dumpfile: Empty file.\n");
|
|
| 298 |
+ return CL_CLEAN; |
|
| 299 |
+ } |
|
| 300 |
+ |
|
| 301 |
+ /* check limits */ |
|
| 302 |
+ targetSize = fork->logicalSize; |
|
| 303 |
+ if (targetSize > ULONG_MAX) {
|
|
| 304 |
+ cli_dbgmsg("hfsplus_dumpfile: File too large for limit check.\n");
|
|
| 305 |
+ return CL_EFORMAT; |
|
| 306 |
+ } |
|
| 307 |
+ ret = cli_checklimits("hfsplus_scanfile", ctx, (unsigned long)targetSize, 0, 0);
|
|
| 308 |
+ if (ret != CL_CLEAN) {
|
|
| 309 |
+ return ret; |
|
| 310 |
+ } |
|
| 311 |
+ |
|
| 312 |
+ /* open file */ |
|
| 313 |
+ ret = cli_gentempfd(dirname, &tmpname, &ofd); |
|
| 314 |
+ if (ret != CL_CLEAN) {
|
|
| 315 |
+ cli_dbgmsg("hfsplus_dumpfile: Cannot generate temporary file.\n");
|
|
| 316 |
+ return ret; |
|
| 317 |
+ } |
|
| 318 |
+ cli_dbgmsg("hfsplus_dumpfile: Extracting to %s\n", tmpname);
|
|
| 319 |
+ |
|
| 320 |
+ ext = 0; |
|
| 321 |
+ /* Dump file, extent by extent */ |
|
| 322 |
+ do {
|
|
| 323 |
+ uint32_t currBlock, endBlock, outputSize; |
|
| 324 |
+ if (targetSize == 0) {
|
|
| 325 |
+ cli_dbgmsg("hfsplus_dumpfile: output complete\n");
|
|
| 326 |
+ break; |
|
| 327 |
+ } |
|
| 328 |
+ if (outputBlocks >= fork->totalBlocks) {
|
|
| 329 |
+ cli_dbgmsg("hfsplus_dumpfile: output all blocks, remaining size " STDu64 "\n", targetSize);
|
|
| 330 |
+ break; |
|
| 331 |
+ } |
|
| 332 |
+ /* Prepare extent */ |
|
| 333 |
+ if (ext < 8) {
|
|
| 334 |
+ currExt = &(fork->extents[ext]); |
|
| 335 |
+ cli_dbgmsg("hfsplus_dumpfile: extent %u\n", ext);
|
|
| 336 |
+ } |
|
| 337 |
+ else {
|
|
| 338 |
+ cli_dbgmsg("hfsplus_dumpfile: need next extent from ExtentOverflow\n");
|
|
| 339 |
+ /* Not implemented yet */ |
|
| 340 |
+ ret = CL_EFORMAT; |
|
| 341 |
+ break; |
|
| 342 |
+ } |
|
| 343 |
+ /* have extent, so validate and get block range */ |
|
| 344 |
+ if ((currExt->startBlock == 0) || (currExt->blockCount == 0)) {
|
|
| 345 |
+ cli_dbgmsg("hfsplus_dumpfile: next extent empty, done\n");
|
|
| 346 |
+ break; |
|
| 347 |
+ } |
|
| 348 |
+ currBlock = currExt->startBlock; |
|
| 349 |
+ endBlock = currExt->startBlock + currExt->blockCount - 1; |
|
| 350 |
+ if ((currBlock > volHeader->totalBlocks) || (endBlock > volHeader->totalBlocks) |
|
| 351 |
+ || (currExt->blockCount > volHeader->totalBlocks)) {
|
|
| 352 |
+ cli_dbgmsg("hfsplus_dumpfile: bad extent!\n");
|
|
| 353 |
+ ret = CL_EFORMAT; |
|
| 354 |
+ break; |
|
| 355 |
+ } |
|
| 356 |
+ /* Write the blocks, walking the map */ |
|
| 357 |
+ while (currBlock <= endBlock) {
|
|
| 358 |
+ size_t to_write = MIN(targetSize, volHeader->blockSize); |
|
| 359 |
+ ssize_t written; |
|
| 360 |
+ off_t offset = currBlock * volHeader->blockSize; |
|
| 361 |
+ /* move map to next block */ |
|
| 362 |
+ mPtr = fmap_need_off_once(*ctx->fmap, offset, volHeader->blockSize); |
|
| 363 |
+ if (!mPtr) {
|
|
| 364 |
+ cli_errmsg("hfsplus_dumpfile: map error\n");
|
|
| 365 |
+ ret = CL_EMAP; |
|
| 366 |
+ break; |
|
| 367 |
+ } |
|
| 368 |
+ written = cli_writen(ofd, mPtr, to_write); |
|
| 369 |
+ if (written != to_write) {
|
|
| 370 |
+ cli_errmsg("hfsplus_dumpfile: write error\n");
|
|
| 371 |
+ ret = CL_EWRITE; |
|
| 372 |
+ break; |
|
| 373 |
+ } |
|
| 374 |
+ targetSize -= to_write; |
|
| 375 |
+ outputSize += to_write; |
|
| 376 |
+ currBlock++; |
|
| 377 |
+ if (targetSize == 0) {
|
|
| 378 |
+ cli_dbgmsg("hfsplus_dumpfile: output complete\n");
|
|
| 379 |
+ break; |
|
| 380 |
+ } |
|
| 381 |
+ if (outputBlocks >= fork->totalBlocks) {
|
|
| 382 |
+ cli_dbgmsg("hfsplus_dumpfile: output all blocks, remaining size " STDu64 "\n", targetSize);
|
|
| 383 |
+ break; |
|
| 384 |
+ } |
|
| 385 |
+ } |
|
| 386 |
+ /* Finished the extent, move to next */ |
|
| 387 |
+ ext++; |
|
| 388 |
+ } while (ret == CL_CLEAN); |
|
| 389 |
+ |
|
| 390 |
+ /* if successful so far, scan the output */ |
|
| 391 |
+ if (ret == CL_CLEAN) {
|
|
| 392 |
+ ret = cli_magic_scandesc(ofd, ctx); |
|
| 393 |
+ } |
|
| 394 |
+ |
|
| 395 |
+ if (ofd >= 0) {
|
|
| 396 |
+ close(ofd); |
|
| 397 |
+ } |
|
| 398 |
+ if (!ctx->engine->keeptmp) {
|
|
| 399 |
+ if (cli_unlink(tmpname)) {
|
|
| 400 |
+ ret = CL_EUNLINK; |
|
| 401 |
+ } |
|
| 402 |
+ } |
|
| 403 |
+ free(tmpname); |
|
| 404 |
+ |
|
| 405 |
+ return ret; |
|
| 406 |
+} |
|
| 407 |
+ |
|
| 408 |
+ |
|
| 30 | 409 |
int cli_scanhfsplus(cli_ctx *ctx) |
| 31 | 410 |
{
|
| 411 |
+ char *targetdir = NULL; |
|
| 32 | 412 |
int ret = CL_CLEAN; |
| 413 |
+ hfsPlusVolumeHeader *volHeader = NULL; |
|
| 414 |
+ hfsNodeDescriptor catFileDesc; |
|
| 415 |
+ hfsHeaderRecord catFileHeader; |
|
| 416 |
+ hfsNodeDescriptor extentFileDesc; |
|
| 417 |
+ hfsHeaderRecord extentFileHeader; |
|
| 33 | 418 |
|
| 34 | 419 |
if (!ctx || !ctx->fmap) {
|
| 35 | 420 |
cli_errmsg("cli_scanhfsplus: Invalid context\n");
|
| 36 | 421 |
return CL_ENULLARG; |
| 37 | 422 |
} |
| 38 | 423 |
|
| 39 |
- cli_dbgmsg("cli_scanhfsplus: starting scan\n");
|
|
| 424 |
+ cli_dbgmsg("cli_scanhfsplus: scanning partition content\n");
|
|
| 425 |
+ /* first, read volume header contents */ |
|
| 426 |
+ ret = hfsplus_volumeheader(ctx, &volHeader); |
|
| 427 |
+ if (ret != CL_CLEAN) {
|
|
| 428 |
+ goto freeHeader; |
|
| 429 |
+ } |
|
| 430 |
+ |
|
| 431 |
+ |
|
| 432 |
+/* |
|
| 433 |
+cli_dbgmsg("sizeof(hfsUniStr255) is %lu\n", sizeof(hfsUniStr255));
|
|
| 434 |
+cli_dbgmsg("sizeof(hfsPlusBSDInfo) is %lu\n", sizeof(hfsPlusBSDInfo));
|
|
| 435 |
+cli_dbgmsg("sizeof(hfsPlusExtentDescriptor) is %lu\n", sizeof(hfsPlusExtentDescriptor));
|
|
| 436 |
+cli_dbgmsg("sizeof(hfsPlusExtentRecord) is %lu\n", sizeof(hfsPlusExtentRecord));
|
|
| 437 |
+cli_dbgmsg("sizeof(hfsPlusForkData) is %lu\n", sizeof(hfsPlusForkData));
|
|
| 438 |
+cli_dbgmsg("sizeof(hfsPlusVolumeHeader) is %lu\n", sizeof(hfsPlusVolumeHeader));
|
|
| 439 |
+cli_dbgmsg("sizeof(hfsNodeDescriptor) is %lu\n", sizeof(hfsNodeDescriptor));
|
|
| 440 |
+ */ |
|
| 441 |
+ |
|
| 442 |
+ /* Get root node (header node) of catalog file */ |
|
| 443 |
+ ret = hfsplus_readheader(ctx, volHeader, &extentFileDesc, &extentFileHeader, HFS_FILETREE_EXTENTS, "extentFile"); |
|
| 444 |
+ if (ret != CL_CLEAN) {
|
|
| 445 |
+ goto freeHeader; |
|
| 446 |
+ } |
|
| 447 |
+ ret = hfsplus_readheader(ctx, volHeader, &catFileDesc, &catFileHeader, HFS_FILETREE_CATALOG, "catalogFile"); |
|
| 448 |
+ if (ret != CL_CLEAN) {
|
|
| 449 |
+ goto freeHeader; |
|
| 450 |
+ } |
|
| 451 |
+ |
|
| 452 |
+ /* Create temp folder for contents */ |
|
| 453 |
+ if (!(targetdir = cli_gentemp(ctx->engine->tmpdir))) {
|
|
| 454 |
+ ret = CL_ETMPDIR; |
|
| 455 |
+ goto freeHeader; |
|
| 456 |
+ } |
|
| 457 |
+ if (mkdir(targetdir, 0700)) {
|
|
| 458 |
+ cli_errmsg("cli_scandmg: Cannot create temporary directory %s\n", targetdir);
|
|
| 459 |
+ ret = CL_ETMPDIR; |
|
| 460 |
+ goto freeDirname; |
|
| 461 |
+ } |
|
| 462 |
+ cli_dbgmsg("cli_scandmg: Extracting into %s\n", targetdir);
|
|
| 463 |
+ |
|
| 464 |
+ ret = hfsplus_scanfile(ctx, volHeader, &extentFileHeader, &(volHeader->catalogFile), targetdir); |
|
| 465 |
+ |
|
| 466 |
+ /* Clean up extracted content, if needed */ |
|
| 467 |
+ if (!ctx->engine->keeptmp) {
|
|
| 468 |
+ cli_rmdirs(targetdir); |
|
| 469 |
+ } |
|
| 40 | 470 |
|
| 471 |
+freeDirname: |
|
| 472 |
+ free(targetdir); |
|
| 473 |
+freeHeader: |
|
| 474 |
+ free(volHeader); |
|
| 41 | 475 |
return ret; |
| 42 | 476 |
} |
| ... | ... |
@@ -27,6 +27,266 @@ |
| 27 | 27 |
|
| 28 | 28 |
#include "cltypes.h" |
| 29 | 29 |
|
| 30 |
+/* Structures based on Apple Technote 1150 */ |
|
| 31 |
+ |
|
| 32 |
+/* volume attributes that may affect reading */ |
|
| 33 |
+enum hfsVolAttributes {
|
|
| 34 |
+ /* hfsVolumeHardwareLockBit = 7, */ |
|
| 35 |
+ hfsVolumeUnmountedBit = 8, |
|
| 36 |
+ hfsVolumeSparedBlocksBit = 9, |
|
| 37 |
+ /* hfsVolumeNoCacheRequiredBit = 10, */ |
|
| 38 |
+ hfsBootVolumeInconsistentBit = 11, |
|
| 39 |
+ hfsCatalogNodeIDsReusedBit = 12, |
|
| 40 |
+ hfsVolumeJournaledBit = 13 |
|
| 41 |
+ /* hfsVolumeSoftwareLockBit = 15 */ |
|
| 42 |
+}; |
|
| 43 |
+ |
|
| 44 |
+/* reserved CatalogNodeID values */ |
|
| 45 |
+enum {
|
|
| 46 |
+ hfsRootParentID = 1, |
|
| 47 |
+ hfsRootFolderID = 2, |
|
| 48 |
+ hfsExtentsFileID = 3, |
|
| 49 |
+ hfsCatalogFileID = 4, |
|
| 50 |
+ hfsBadBlockFileID = 5, |
|
| 51 |
+ hfsAllocationFileID = 6, |
|
| 52 |
+ hfsStartupFileID = 7, |
|
| 53 |
+ hfsAttributesFileID = 8, |
|
| 54 |
+ hfsRepairCatalogFileID = 14, |
|
| 55 |
+ hfsBogusExtentFileID = 15, |
|
| 56 |
+ hfsFirstUserCatalogNodeID = 16 |
|
| 57 |
+}; |
|
| 58 |
+ |
|
| 59 |
+#ifndef HAVE_ATTRIB_PACKED |
|
| 60 |
+#define __attribute__(x) |
|
| 61 |
+#endif |
|
| 62 |
+ |
|
| 63 |
+#ifdef HAVE_PRAGMA_PACK |
|
| 64 |
+#pragma pack(2) |
|
| 65 |
+#endif |
|
| 66 |
+ |
|
| 67 |
+#ifdef HAVE_PRAGMA_PACK_HPPA |
|
| 68 |
+#pragma pack 2 |
|
| 69 |
+#endif |
|
| 70 |
+ |
|
| 71 |
+/* Basic HFS+ structures */ |
|
| 72 |
+struct hfsUniStr255 {
|
|
| 73 |
+ uint16_t length; |
|
| 74 |
+ uint16_t unicode[255]; |
|
| 75 |
+} __attribute__((__packed__)); |
|
| 76 |
+typedef struct hfsUniStr255 hfsUniStr255; |
|
| 77 |
+ |
|
| 78 |
+struct hfsPlusExtentDescriptor {
|
|
| 79 |
+ uint32_t startBlock; |
|
| 80 |
+ uint32_t blockCount; |
|
| 81 |
+} __attribute__((__packed__)); |
|
| 82 |
+typedef struct hfsPlusExtentDescriptor hfsPlusExtentDescriptor; |
|
| 83 |
+typedef hfsPlusExtentDescriptor hfsPlusExtentRecord[8]; |
|
| 84 |
+ |
|
| 85 |
+struct hfsPlusForkData {
|
|
| 86 |
+ uint64_t logicalSize; |
|
| 87 |
+ uint32_t clumpSize; |
|
| 88 |
+ uint32_t totalBlocks; |
|
| 89 |
+ hfsPlusExtentRecord extents; |
|
| 90 |
+} __attribute__((__packed__)); |
|
| 91 |
+typedef struct hfsPlusForkData hfsPlusForkData; |
|
| 92 |
+ |
|
| 93 |
+/* HFS+ Volume Header (512 bytes) */ |
|
| 94 |
+struct hfsPlusVolumeHeader {
|
|
| 95 |
+ uint16_t signature; /* H+ for HFS+, HX for HFSX */ |
|
| 96 |
+ uint16_t version; |
|
| 97 |
+ uint32_t attributes; |
|
| 98 |
+ uint32_t lastMountedVersion; |
|
| 99 |
+ uint32_t journalInfoBlock; |
|
| 100 |
+ |
|
| 101 |
+ uint32_t createDate; |
|
| 102 |
+ uint32_t modifyDate; |
|
| 103 |
+ uint32_t backupDate; |
|
| 104 |
+ uint32_t checkedDate; |
|
| 105 |
+ |
|
| 106 |
+ uint32_t fileCount; |
|
| 107 |
+ uint32_t folderCount; |
|
| 108 |
+ |
|
| 109 |
+ uint32_t blockSize; |
|
| 110 |
+ uint32_t totalBlocks; |
|
| 111 |
+ uint32_t freeBlocks; |
|
| 112 |
+ |
|
| 113 |
+ uint32_t nextAllocation; |
|
| 114 |
+ uint32_t rsrcClumpSize; |
|
| 115 |
+ uint32_t dataClumpSize; |
|
| 116 |
+ uint32_t nextCatalogID; /* Next unused catalog ID */ |
|
| 117 |
+ |
|
| 118 |
+ uint32_t writeCount; |
|
| 119 |
+ uint64_t encodingsBitmap; |
|
| 120 |
+ |
|
| 121 |
+ uint32_t finderInfo[8]; /* for Finder */ |
|
| 122 |
+ |
|
| 123 |
+ hfsPlusForkData allocationFile; |
|
| 124 |
+ hfsPlusForkData extentsFile; |
|
| 125 |
+ hfsPlusForkData catalogFile; |
|
| 126 |
+ hfsPlusForkData attributesFile; |
|
| 127 |
+ hfsPlusForkData startupFile; |
|
| 128 |
+} __attribute__((__packed__)); |
|
| 129 |
+typedef struct hfsPlusVolumeHeader hfsPlusVolumeHeader; |
|
| 130 |
+ |
|
| 131 |
+#define HFS_FILETREE_ALLOCATION 1 |
|
| 132 |
+#define HFS_FILETREE_EXTENTS 2 |
|
| 133 |
+#define HFS_FILETREE_CATALOG 3 |
|
| 134 |
+#define HFS_FILETREE_ATTRIBUTES 4 |
|
| 135 |
+#define HFS_FILETREE_STARTUP 5 |
|
| 136 |
+ |
|
| 137 |
+/******************************/ |
|
| 138 |
+/* File properties */ |
|
| 139 |
+/******************************/ |
|
| 140 |
+ |
|
| 141 |
+/* BSD object info (16 bytes) */ |
|
| 142 |
+/* important parts for scanning are fileMode and the special part */ |
|
| 143 |
+struct hfsPlusBSDInfo {
|
|
| 144 |
+ uint32_t ownerID; |
|
| 145 |
+ uint32_t groupID; |
|
| 146 |
+ uint8_t adminFlags; |
|
| 147 |
+ uint8_t ownerFlags; |
|
| 148 |
+ uint16_t fileMode; |
|
| 149 |
+ union {
|
|
| 150 |
+ uint32_t iNodeNum; |
|
| 151 |
+ uint32_t linkCount; |
|
| 152 |
+ uint32_t rawDevice; |
|
| 153 |
+ } special; |
|
| 154 |
+} __attribute__((__packed__)); |
|
| 155 |
+typedef struct hfsPlusBSDInfo hfsPlusBSDInfo; |
|
| 156 |
+ |
|
| 157 |
+#define HFS_MODE_TYPEMASK 0170000 |
|
| 158 |
+#define HFS_MODE_DIRECTORY 0040000 |
|
| 159 |
+#define HFS_MODE_FILE 0100000 |
|
| 160 |
+#define HFS_MODE_SOFTLINK 0120000 |
|
| 161 |
+ |
|
| 162 |
+/******************************/ |
|
| 163 |
+/* Node and tree structures */ |
|
| 164 |
+/******************************/ |
|
| 165 |
+ |
|
| 166 |
+/* B-tree node descriptor (14 bytes) */ |
|
| 167 |
+struct hfsNodeDescriptor {
|
|
| 168 |
+ uint32_t fLink; |
|
| 169 |
+ uint32_t bLink; |
|
| 170 |
+ int8_t kind; |
|
| 171 |
+ uint8_t height; |
|
| 172 |
+ uint16_t numRecords; |
|
| 173 |
+ uint16_t reserved; |
|
| 174 |
+} __attribute__((__packed__)); |
|
| 175 |
+typedef struct hfsNodeDescriptor hfsNodeDescriptor; |
|
| 176 |
+ |
|
| 177 |
+/* Node kinds are int8_t */ |
|
| 178 |
+#define HFS_NODEKIND_LEAF -1 |
|
| 179 |
+#define HFS_NODEKIND_INDEX 0 |
|
| 180 |
+#define HFS_NODEKIND_HEADER 1 |
|
| 181 |
+#define HFS_NODEKIND_MAP 2 |
|
| 182 |
+ |
|
| 183 |
+/* B-tree header record (106 bytes) */ |
|
| 184 |
+struct hfsHeaderRecord {
|
|
| 185 |
+ uint16_t treeDepth; |
|
| 186 |
+ uint32_t rootNode; |
|
| 187 |
+ uint32_t leafRecords; |
|
| 188 |
+ uint32_t firstLeafNode; |
|
| 189 |
+ uint32_t lastLeafNode; |
|
| 190 |
+ uint16_t nodeSize; |
|
| 191 |
+ uint16_t maxKeyLength; |
|
| 192 |
+ uint32_t totalNodes; |
|
| 193 |
+ uint32_t freeNodes; |
|
| 194 |
+ uint16_t reserved1; |
|
| 195 |
+ uint32_t clumpSize; |
|
| 196 |
+ uint8_t btreeType; |
|
| 197 |
+ uint8_t keyCompareType; |
|
| 198 |
+ uint32_t attributes; |
|
| 199 |
+ uint32_t reserved3[16]; |
|
| 200 |
+} __attribute__((__packed__)); |
|
| 201 |
+typedef struct hfsHeaderRecord hfsHeaderRecord; |
|
| 202 |
+ |
|
| 203 |
+#define HFS_HEADERATTR_MASK 0x00000006 |
|
| 204 |
+#define HFS_HEADERATTR_BIGKEYS 0x00000002 |
|
| 205 |
+#define HFS_HEADERATTR_VARKEYS 0x00000004 |
|
| 206 |
+ |
|
| 207 |
+struct hfsPlusCatalogKey {
|
|
| 208 |
+ uint16_t keyLength; |
|
| 209 |
+ uint32_t parentID; /* CNID */ |
|
| 210 |
+ hfsUniStr255 nodeName; |
|
| 211 |
+} __attribute__((__packed__)); |
|
| 212 |
+typedef struct hfsPlusCatalogKey hfsPlusCatalogKey; |
|
| 213 |
+ |
|
| 214 |
+struct hfsPlusCatalogFolder {
|
|
| 215 |
+ int16_t recordType; |
|
| 216 |
+ uint16_t flags; |
|
| 217 |
+ uint32_t valence; |
|
| 218 |
+ uint32_t folderID; /* CNID */ |
|
| 219 |
+ uint32_t dates[5]; |
|
| 220 |
+ hfsPlusBSDInfo permissions; |
|
| 221 |
+ uint16_t userInfo[8]; /* FolderInfo */ |
|
| 222 |
+ uint16_t finderInfo[8]; /* ExtendedFolderInfo */ |
|
| 223 |
+ uint32_t textEncoding; |
|
| 224 |
+ uint32_t reserved; |
|
| 225 |
+} __attribute__((__packed__)); |
|
| 226 |
+typedef struct hfsPlusCatalogFolder hfsPlusCatalogFolder; |
|
| 227 |
+ |
|
| 228 |
+struct hfsPlusCatalogFile {
|
|
| 229 |
+ int16_t recordType; |
|
| 230 |
+ uint16_t flags; |
|
| 231 |
+ uint32_t reserved1; |
|
| 232 |
+ uint32_t fileID; /* CNID */ |
|
| 233 |
+ uint32_t dates[5]; |
|
| 234 |
+ hfsPlusBSDInfo permissions; |
|
| 235 |
+ uint16_t userInfo[8]; /* FileInfo */ |
|
| 236 |
+ uint16_t finderInfo[8]; /* ExtendedFileInfo */ |
|
| 237 |
+ uint32_t textEncoding; |
|
| 238 |
+ uint32_t reserved2; |
|
| 239 |
+ hfsPlusForkData dataFork; |
|
| 240 |
+ hfsPlusForkData resourceFork; |
|
| 241 |
+}; |
|
| 242 |
+typedef struct hfsPlusCatalogFile hfsPlusCatalogFile; |
|
| 243 |
+ |
|
| 244 |
+struct hfsPlusCatalogThread {
|
|
| 245 |
+ int16_t recordType; |
|
| 246 |
+ int16_t reserved; |
|
| 247 |
+ uint32_t parentID; /* CNID */ |
|
| 248 |
+ hfsUniStr255 nodeName; |
|
| 249 |
+} __attribute__((__packed__)); |
|
| 250 |
+typedef struct hfsPlusCatalogThread hfsPlusCatalogThread; |
|
| 251 |
+ |
|
| 252 |
+#define HFSPLUS_RECTYPE_FOLDER 0x0001 |
|
| 253 |
+#define HFSPLUS_RECTYPE_FILE 0x0002 |
|
| 254 |
+#define HFSPLUS_RECTYPE_FOLDERTHREAD 0x0003 |
|
| 255 |
+#define HFSPLUS_RECTYPE_FILETHREAD 0x0004 |
|
| 256 |
+/* HFS types are similar |
|
| 257 |
+#define HFS_RECTYPE_FOLDER 0x0100 |
|
| 258 |
+#define HFS_RECTYPE_FILE 0x0200 |
|
| 259 |
+#define HFS_RECTYPE_FOLDERTHREAD 0x0300 |
|
| 260 |
+#define HFS_RECTYPE_FILETHREAD 0x0400 |
|
| 261 |
+ */ |
|
| 262 |
+ |
|
| 263 |
+#define HFS_HARD_LINK_FILE_TYPE 0x686C6E6B /* hlnk */ |
|
| 264 |
+ |
|
| 265 |
+/* Extents structures */ |
|
| 266 |
+struct hfsPlusExtentKey {
|
|
| 267 |
+ uint16_t keyLength; |
|
| 268 |
+ uint8_t forkType; |
|
| 269 |
+ uint8_t pad; |
|
| 270 |
+ uint32_t fileID; /* CNID */ |
|
| 271 |
+ uint32_t startBlock; |
|
| 272 |
+} __attribute__((__packed__)); |
|
| 273 |
+typedef struct hfsPlusExtentKey hfsPlusExtentKey; |
|
| 274 |
+ |
|
| 275 |
+#define HFSPLUS_FORKTYPE_DATA 0x00 |
|
| 276 |
+#define HFSPLUS_FORKTYPE_RSRC 0xFF |
|
| 277 |
+ |
|
| 278 |
+#ifdef HAVE_PRAGMA_PACK |
|
| 279 |
+#pragma pack() |
|
| 280 |
+#endif |
|
| 281 |
+ |
|
| 282 |
+#ifdef HAVE_PRAGMA_PACK_HPPA |
|
| 283 |
+#pragma pack |
|
| 284 |
+#endif |
|
| 285 |
+ |
|
| 286 |
+#define HFS_VOL_INCONSISTENT(hdr) \ |
|
| 287 |
+ ((hdr->attributes & (1 << hfsBootVolumeInconsistentBit)) \ |
|
| 288 |
+ || !(hdr->attributes & (1 << hfsVolumeUnmountedBit))) |
|
| 289 |
+ |
|
| 30 | 290 |
int cli_scanhfsplus(cli_ctx *ctx); |
| 31 | 291 |
|
| 32 | 292 |
#endif |