/* ForkData : fix endianness */ static void forkdata_to_host(hfsPlusForkData *fork) { int i; fork->logicalSize = be64_to_host(fork->logicalSize); fork->clumpSize = be32_to_host(fork->clumpSize); /* does this matter for read-only? */ fork->totalBlocks = be32_to_host(fork->totalBlocks); for (i=0; i < 8; i++) { fork->extents[i].startBlock = be32_to_host(fork->extents[i].startBlock); fork->extents[i].blockCount = be32_to_host(fork->extents[i].blockCount); } }
int cli_scanxar(cli_ctx *ctx) { int rc = CL_SUCCESS; unsigned int cksum_fails = 0; unsigned int extract_errors = 0; #if HAVE_LIBXML2 int fd = -1; struct xar_header hdr; fmap_t *map = *ctx->fmap; long length, offset, size, at; int encoding; z_stream strm; char *toc, *tmpname; xmlTextReaderPtr reader = NULL; int a_hash, e_hash; unsigned char *a_cksum = NULL, *e_cksum = NULL; memset(&strm, 0x00, sizeof(z_stream)); /* retrieve xar header */ if (fmap_readn(*ctx->fmap, &hdr, 0, sizeof(hdr)) != sizeof(hdr)) { cli_dbgmsg("cli_scanxar: Invalid header, too short.\n"); return CL_EFORMAT; } hdr.magic = be32_to_host(hdr.magic); if (hdr.magic == XAR_HEADER_MAGIC) { cli_dbgmsg("cli_scanxar: Matched magic\n"); } else { cli_dbgmsg("cli_scanxar: Invalid magic\n"); return CL_EFORMAT; } hdr.size = be16_to_host(hdr.size); hdr.version = be16_to_host(hdr.version); hdr.toc_length_compressed = be64_to_host(hdr.toc_length_compressed); hdr.toc_length_decompressed = be64_to_host(hdr.toc_length_decompressed); hdr.chksum_alg = be32_to_host(hdr.chksum_alg); /* cli_dbgmsg("hdr.magic %x\n", hdr.magic); */ /* cli_dbgmsg("hdr.size %i\n", hdr.size); */ /* cli_dbgmsg("hdr.version %i\n", hdr.version); */ /* cli_dbgmsg("hdr.toc_length_compressed %lu\n", hdr.toc_length_compressed); */ /* cli_dbgmsg("hdr.toc_length_decompressed %lu\n", hdr.toc_length_decompressed); */ /* cli_dbgmsg("hdr.chksum_alg %i\n", hdr.chksum_alg); */ /* Uncompress TOC */ strm.next_in = (unsigned char *)fmap_need_off_once(*ctx->fmap, hdr.size, hdr.toc_length_compressed); if (strm.next_in == NULL) { cli_dbgmsg("cli_scanxar: fmap_need_off_once fails on TOC.\n"); return CL_EREAD; } strm.avail_in = hdr.toc_length_compressed; toc = cli_malloc(hdr.toc_length_decompressed+1); if (toc == NULL) { cli_dbgmsg("cli_scanxar: cli_malloc fails on TOC decompress buffer.\n"); return CL_EMEM; } toc[hdr.toc_length_decompressed] = '\0'; strm.avail_out = hdr.toc_length_decompressed; strm.next_out = (unsigned char *)toc; rc = inflateInit(&strm); if (rc != Z_OK) { cli_dbgmsg("cli_scanxar:inflateInit error %i \n", rc); rc = CL_EFORMAT; goto exit_toc; } rc = inflate(&strm, Z_SYNC_FLUSH); if (rc != Z_OK && rc != Z_STREAM_END) { cli_dbgmsg("cli_scanxar:inflate error %i \n", rc); rc = CL_EFORMAT; goto exit_toc; } rc = inflateEnd(&strm); if (rc != Z_OK) { cli_dbgmsg("cli_scanxar:inflateEnd error %i \n", rc); rc = CL_EFORMAT; goto exit_toc; } /* cli_dbgmsg("cli_scanxar: TOC xml:\n%s\n", toc); */ /* printf("cli_scanxar: TOC xml:\n%s\n", toc); */ /* cli_dbgmsg("cli_scanxar: TOC end:\n"); */ /* printf("cli_scanxar: TOC end:\n"); */ /* scan the xml */ cli_dbgmsg("cli_scanxar: scanning xar TOC xml in memory.\n"); rc = cli_mem_scandesc(toc, hdr.toc_length_decompressed, ctx); if (rc != CL_SUCCESS) { if (rc != CL_VIRUS || !SCAN_ALL) goto exit_toc; } /* make a file to leave if --leave-temps in effect */ if(ctx->engine->keeptmp) { if ((rc = cli_gentempfd(ctx->engine->tmpdir, &tmpname, &fd)) != CL_SUCCESS) { cli_dbgmsg("cli_scanxar: Can't create temporary file for TOC.\n"); goto exit_toc; } if (cli_writen(fd, toc, hdr.toc_length_decompressed) < 0) { cli_dbgmsg("cli_scanxar: cli_writen error writing TOC.\n"); rc = CL_EWRITE; xar_cleanup_temp_file(ctx, fd, tmpname); goto exit_toc; } rc = xar_cleanup_temp_file(ctx, fd, tmpname); if (rc != CL_SUCCESS) goto exit_toc; } reader = xmlReaderForMemory(toc, hdr.toc_length_decompressed, "noname.xml", NULL, 0); if (reader == NULL) { cli_dbgmsg("cli_scanxar: xmlReaderForMemory error for TOC\n"); goto exit_toc; } rc = xar_scan_subdocuments(reader, ctx); if (rc != CL_SUCCESS) { cli_dbgmsg("xar_scan_subdocuments returns %i.\n", rc); goto exit_reader; } /* Walk the TOC XML and extract files */ fd = -1; tmpname = NULL; while (CL_SUCCESS == (rc = xar_get_toc_data_values(reader, &length, &offset, &size, &encoding, &a_cksum, &a_hash, &e_cksum, &e_hash))) { int do_extract_cksum = 1; unsigned char * blockp; void *a_sc, *e_sc; void *a_mc, *e_mc; void *a_hash_ctx, *e_hash_ctx; char result[SHA1_HASH_SIZE]; char * expected; /* clean up temp file from previous loop iteration */ if (fd > -1 && tmpname) { rc = xar_cleanup_temp_file(ctx, fd, tmpname); if (rc != CL_SUCCESS) goto exit_reader; } at = offset + hdr.toc_length_compressed + hdr.size; if ((rc = cli_gentempfd(ctx->engine->tmpdir, &tmpname, &fd)) != CL_SUCCESS) { cli_dbgmsg("cli_scanxar: Can't generate temporary file.\n"); goto exit_reader; } cli_dbgmsg("cli_scanxar: decompress into temp file:\n%s, size %li,\n" "from xar heap offset %li length %li\n", tmpname, size, offset, length); a_hash_ctx = xar_hash_init(a_hash, &a_sc, &a_mc); e_hash_ctx = xar_hash_init(e_hash, &e_sc, &e_mc); switch (encoding) { case CL_TYPE_GZ: /* inflate gzip directly because file segments do not contain magic */ memset(&strm, 0, sizeof(strm)); if ((rc = inflateInit(&strm)) != Z_OK) { cli_dbgmsg("cli_scanxar: InflateInit failed: %d\n", rc); rc = CL_EFORMAT; extract_errors++; break; } while ((size_t)at < map->len && (unsigned long)at < offset+hdr.toc_length_compressed+hdr.size+length) { unsigned long avail_in; void * next_in; unsigned int bytes = MIN(map->len - at, map->pgsz); bytes = MIN(length, bytes); if(!(strm.next_in = next_in = (void*)fmap_need_off_once(map, at, bytes))) { cli_dbgmsg("cli_scanxar: Can't read %u bytes @ %lu.\n", bytes, (long unsigned)at); inflateEnd(&strm); rc = CL_EREAD; goto exit_tmpfile; } at += bytes; strm.avail_in = avail_in = bytes; do { int inf, outsize = 0; unsigned char buff[FILEBUFF]; strm.avail_out = sizeof(buff); strm.next_out = buff; inf = inflate(&strm, Z_SYNC_FLUSH); if (inf != Z_OK && inf != Z_STREAM_END && inf != Z_BUF_ERROR) { cli_dbgmsg("cli_scanxar: inflate error %i %s.\n", inf, strm.msg?strm.msg:""); rc = CL_EFORMAT; extract_errors++; break; } bytes = sizeof(buff) - strm.avail_out; xar_hash_update(e_hash_ctx, buff, bytes, e_hash); if (cli_writen(fd, buff, bytes) < 0) { cli_dbgmsg("cli_scanxar: cli_writen error file %s.\n", tmpname); inflateEnd(&strm); rc = CL_EWRITE; goto exit_tmpfile; } outsize += sizeof(buff) - strm.avail_out; if (cli_checklimits("cli_scanxar", ctx, outsize, 0, 0) != CL_CLEAN) { break; } if (inf == Z_STREAM_END) { break; } } while (strm.avail_out == 0); if (rc != CL_SUCCESS) break; avail_in -= strm.avail_in; xar_hash_update(a_hash_ctx, next_in, avail_in, a_hash); } inflateEnd(&strm); break; case CL_TYPE_7Z: #define CLI_LZMA_OBUF_SIZE 1024*1024 #define CLI_LZMA_HDR_SIZE LZMA_PROPS_SIZE+8 #define CLI_LZMA_IBUF_SIZE CLI_LZMA_OBUF_SIZE>>2 /* estimated compression ratio 25% */ { struct CLI_LZMA lz; unsigned long in_remaining = length; unsigned long out_size = 0; unsigned char * buff = __lzma_wrap_alloc(NULL, CLI_LZMA_OBUF_SIZE); int lret; memset(&lz, 0, sizeof(lz)); if (buff == NULL) { cli_dbgmsg("cli_scanxar: memory request for lzma decompression buffer fails.\n"); rc = CL_EMEM; goto exit_tmpfile; } blockp = (void*)fmap_need_off_once(map, at, CLI_LZMA_HDR_SIZE); if (blockp == NULL) { char errbuff[128]; cli_strerror(errno, errbuff, sizeof(errbuff)); cli_dbgmsg("cli_scanxar: Can't read %li bytes @ %li, errno:%s.\n", length, at, errbuff); rc = CL_EREAD; __lzma_wrap_free(NULL, buff); goto exit_tmpfile; } lz.next_in = blockp; lz.avail_in = CLI_LZMA_HDR_SIZE; xar_hash_update(a_hash_ctx, blockp, CLI_LZMA_HDR_SIZE, a_hash); lret = cli_LzmaInit(&lz, 0); if (lret != LZMA_RESULT_OK) { cli_dbgmsg("cli_scanxar: cli_LzmaInit() fails: %i.\n", lret); rc = CL_EFORMAT; __lzma_wrap_free(NULL, buff); extract_errors++; break; } at += CLI_LZMA_HDR_SIZE; in_remaining -= CLI_LZMA_HDR_SIZE; while ((size_t)at < map->len && (unsigned long)at < offset+hdr.toc_length_compressed+hdr.size+length) { SizeT avail_in; SizeT avail_out; void * next_in; unsigned long in_consumed; lz.next_out = buff; lz.avail_out = CLI_LZMA_OBUF_SIZE; lz.avail_in = avail_in = MIN(CLI_LZMA_IBUF_SIZE, in_remaining); lz.next_in = next_in = (void*)fmap_need_off_once(map, at, lz.avail_in); if (lz.next_in == NULL) { char errbuff[128]; cli_strerror(errno, errbuff, sizeof(errbuff)); cli_dbgmsg("cli_scanxar: Can't read %li bytes @ %li, errno: %s.\n", length, at, errbuff); rc = CL_EREAD; __lzma_wrap_free(NULL, buff); cli_LzmaShutdown(&lz); goto exit_tmpfile; } lret = cli_LzmaDecode(&lz); if (lret != LZMA_RESULT_OK && lret != LZMA_STREAM_END) { cli_dbgmsg("cli_scanxar: cli_LzmaDecode() fails: %i.\n", lret); rc = CL_EFORMAT; extract_errors++; break; } in_consumed = avail_in - lz.avail_in; in_remaining -= in_consumed; at += in_consumed; avail_out = CLI_LZMA_OBUF_SIZE - lz.avail_out; if (avail_out == 0) cli_dbgmsg("cli_scanxar: cli_LzmaDecode() produces no output for " "avail_in %lu, avail_out %lu.\n", avail_in, avail_out); xar_hash_update(a_hash_ctx, next_in, in_consumed, a_hash); xar_hash_update(e_hash_ctx, buff, avail_out, e_hash); /* Write a decompressed block. */ /* cli_dbgmsg("Writing %li bytes to LZMA decompress temp file, " */ /* "consumed %li of %li available compressed bytes.\n", */ /* avail_out, in_consumed, avail_in); */ if (cli_writen(fd, buff, avail_out) < 0) { cli_dbgmsg("cli_scanxar: cli_writen error writing lzma temp file for %li bytes.\n", avail_out); __lzma_wrap_free(NULL, buff); cli_LzmaShutdown(&lz); rc = CL_EWRITE; goto exit_tmpfile; } /* Check file size limitation. */ out_size += avail_out; if (cli_checklimits("cli_scanxar", ctx, out_size, 0, 0) != CL_CLEAN) { break; } if (lret == LZMA_STREAM_END) break; } cli_LzmaShutdown(&lz); __lzma_wrap_free(NULL, buff); } break; case CL_TYPE_ANY: default: case CL_TYPE_BZ: case CL_TYPE_XZ: /* for uncompressed, bzip2, xz, and unknown, just pull the file, cli_magic_scandesc does the rest */ do_extract_cksum = 0; { unsigned long write_len; if (ctx->engine->maxfilesize) write_len = MIN((size_t)(ctx->engine->maxfilesize), (size_t)length); else write_len = length; if (!(blockp = (void*)fmap_need_off_once(map, at, length))) { char errbuff[128]; cli_strerror(errno, errbuff, sizeof(errbuff)); cli_dbgmsg("cli_scanxar: Can't read %li bytes @ %li, errno:%s.\n", length, at, errbuff); rc = CL_EREAD; goto exit_tmpfile; } xar_hash_update(a_hash_ctx, blockp, length, a_hash); if (cli_writen(fd, blockp, write_len) < 0) { cli_dbgmsg("cli_scanxar: cli_writen error %li bytes @ %li.\n", length, at); rc = CL_EWRITE; goto exit_tmpfile; } /*break;*/ } } if (rc == CL_SUCCESS) { xar_hash_final(a_hash_ctx, result, a_hash); if (a_cksum != NULL) { expected = cli_hex2str((char *)a_cksum); if (xar_hash_check(a_hash, result, expected) != 0) { cli_dbgmsg("cli_scanxar: archived-checksum missing or mismatch.\n"); cksum_fails++; } else { cli_dbgmsg("cli_scanxar: archived-checksum matched.\n"); } free(expected); } if (e_cksum != NULL) { if (do_extract_cksum) { xar_hash_final(e_hash_ctx, result, e_hash); expected = cli_hex2str((char *)e_cksum); if (xar_hash_check(e_hash, result, expected) != 0) { cli_dbgmsg("cli_scanxar: extracted-checksum missing or mismatch.\n"); cksum_fails++; } else { cli_dbgmsg("cli_scanxar: extracted-checksum matched.\n"); } free(expected); } } rc = cli_magic_scandesc(fd, ctx); if (rc != CL_SUCCESS) { if (rc == CL_VIRUS) { cli_dbgmsg("cli_scanxar: Infected with %s\n", cli_get_last_virus(ctx)); if (!SCAN_ALL) goto exit_tmpfile; } else if (rc != CL_BREAK) { cli_dbgmsg("cli_scanxar: cli_magic_scandesc error %i\n", rc); goto exit_tmpfile; } } } if (a_cksum != NULL) { xmlFree(a_cksum); a_cksum = NULL; } if (e_cksum != NULL) { xmlFree(e_cksum); e_cksum = NULL; } } exit_tmpfile: xar_cleanup_temp_file(ctx, fd, tmpname); exit_reader: if (a_cksum != NULL) xmlFree(a_cksum); if (e_cksum != NULL) xmlFree(e_cksum); xmlTextReaderClose(reader); xmlFreeTextReader(reader); exit_toc: free(toc); if (rc == CL_BREAK) rc = CL_SUCCESS; #else cli_dbgmsg("cli_scanxar: can't scan xar files, need libxml2.\n"); #endif if (cksum_fails + extract_errors != 0) { cli_warnmsg("cli_scanxar: %u checksum errors and %u extraction errors, use --debug for more info.\n", cksum_fails, extract_errors); } return rc; }
static int gpt_validate_header(cli_ctx *ctx, struct gpt_header hdr, size_t sectorsize) { uint32_t crc32_calc, crc32_ref; uint64_t tableLastLBA, lastLBA; size_t maplen, ptable_start, ptable_len; unsigned char *ptable; maplen = (*ctx->fmap)->real_len; /* checking header crc32 checksum */ crc32_ref = le32_to_host(hdr.headerCRC32); hdr.headerCRC32 = 0; /* checksum is calculated with field = 0 */ crc32_calc = crc32(0, (unsigned char*)&hdr, sizeof(hdr)); if (crc32_calc != crc32_ref) { cli_dbgmsg("cli_scangpt: GPT header checksum mismatch\n"); gpt_parsemsg("%x != %x\n", crc32_calc, crc32_ref); return CL_EFORMAT; } /* convert endian to host to check partition table */ hdr.signature = be64_to_host(hdr.signature); hdr.revision = be32_to_host(hdr.revision); hdr.headerSize = le32_to_host(hdr.headerSize); hdr.headerCRC32 = crc32_ref; hdr.reserved = le32_to_host(hdr.reserved); hdr.currentLBA = le64_to_host(hdr.currentLBA); hdr.backupLBA = le64_to_host(hdr.backupLBA); hdr.firstUsableLBA = le64_to_host(hdr.firstUsableLBA); hdr.lastUsableLBA = le64_to_host(hdr.lastUsableLBA); hdr.tableStartLBA = le64_to_host(hdr.tableStartLBA); hdr.tableNumEntries = le32_to_host(hdr.tableNumEntries); hdr.tableEntrySize = le32_to_host(hdr.tableEntrySize); hdr.tableCRC32 = le32_to_host(hdr.tableCRC32);; ptable_start = hdr.tableStartLBA * sectorsize; ptable_len = hdr.tableNumEntries * hdr.tableEntrySize; tableLastLBA = (hdr.tableStartLBA + (ptable_len / sectorsize)) - 1; lastLBA = (maplen / sectorsize) - 1; /** HEADER CHECKS **/ gpt_printSectors(ctx, sectorsize); /* check signature */ if (hdr.signature != GPT_SIGNATURE) { cli_dbgmsg("cli_scangpt: Invalid GPT header signature %llx\n", (long long unsigned)hdr.signature); return CL_EFORMAT; } /* check header size */ if (hdr.headerSize != sizeof(hdr)) { cli_dbgmsg("cli_scangpt: GPT header size does not match stated size\n"); return CL_EFORMAT; } /* check reserved value == 0 */ if (hdr.reserved != GPT_HDR_RESERVED) { cli_dbgmsg("cli_scangpt: GPT header reserved is not expected value\n"); return CL_EFORMAT; } /* check that sectors are in a valid configuration */ if (!((hdr.currentLBA == GPT_PRIMARY_HDR_LBA && hdr.backupLBA == lastLBA) || (hdr.currentLBA == lastLBA && hdr.backupLBA == GPT_PRIMARY_HDR_LBA))) { cli_dbgmsg("cli_scangpt: GPT secondary header is not last LBA\n"); return CL_EFORMAT; } if (hdr.firstUsableLBA > hdr.lastUsableLBA) { cli_dbgmsg("cli_scangpt: GPT first usable sectors is after last usable sector\n"); return CL_EFORMAT; } if (hdr.firstUsableLBA <= GPT_PRIMARY_HDR_LBA || hdr.lastUsableLBA >= lastLBA) { cli_dbgmsg("cli_scangpt: GPT usable sectors intersects header sector\n"); return CL_EFORMAT; } if ((hdr.tableStartLBA <= hdr.firstUsableLBA && tableLastLBA >= hdr.firstUsableLBA) || (hdr.tableStartLBA >= hdr.firstUsableLBA && hdr.tableStartLBA <= hdr.lastUsableLBA)) { cli_dbgmsg("cli_scangpt: GPT usable sectors intersects partition table\n"); return CL_EFORMAT; } if (hdr.tableStartLBA <= GPT_PRIMARY_HDR_LBA || tableLastLBA >= lastLBA) { cli_dbgmsg("cli_scangpt: GPT partition table intersects header sector\n"); return CL_EFORMAT; } /* check that valid table entry size */ if (hdr.tableEntrySize != sizeof(struct gpt_partition_entry)) { cli_dbgmsg("cli_scangpt: cannot parse gpt with partition entry sized %u\n", hdr.tableEntrySize); return CL_EFORMAT; } /* check valid table */ if ((ptable_start + ptable_len) > maplen) { cli_dbgmsg("cli_scangpt: GPT partition table extends over fmap limit\n"); return CL_EFORMAT; } /** END HEADER CHECKS **/ /* checking partition table crc32 checksum */ ptable = (unsigned char*)fmap_need_off_once((*ctx->fmap), ptable_start, ptable_len); crc32_calc = crc32(0, ptable, ptable_len); if (crc32_calc != hdr.tableCRC32) { cli_dbgmsg("cli_scangpt: GPT partition table checksum mismatch\n"); gpt_parsemsg("%x != %x\n", crc32_calc, hdr.tableCRC32); return CL_EFORMAT; } return CL_SUCCESS; }
static int gpt_scan_partitions(cli_ctx *ctx, struct gpt_header hdr, size_t sectorsize) { struct gpt_partition_entry gpe; int ret = CL_CLEAN, detection = CL_CLEAN; size_t maplen, part_size = 0; off_t pos = 0, part_off = 0; unsigned i = 0, j = 0; uint32_t max_prtns = 0; /* convert endian to host */ hdr.signature = be64_to_host(hdr.signature); hdr.revision = be32_to_host(hdr.revision); hdr.headerSize = le32_to_host(hdr.headerSize); hdr.headerCRC32 = le32_to_host(hdr.headerCRC32); hdr.reserved = le32_to_host(hdr.reserved); hdr.currentLBA = le64_to_host(hdr.currentLBA); hdr.backupLBA = le64_to_host(hdr.backupLBA); hdr.firstUsableLBA = le64_to_host(hdr.firstUsableLBA); hdr.lastUsableLBA = le64_to_host(hdr.lastUsableLBA); hdr.tableStartLBA = le64_to_host(hdr.tableStartLBA); hdr.tableNumEntries = le32_to_host(hdr.tableNumEntries); hdr.tableEntrySize = le32_to_host(hdr.tableEntrySize); hdr.tableCRC32 = le32_to_host(hdr.tableCRC32); /* print header info for the debug */ cli_dbgmsg("GPT Header:\n"); cli_dbgmsg("Signature: 0x%llx\n", (long long unsigned)hdr.signature); cli_dbgmsg("Revision: %x\n", hdr.revision); gpt_printGUID(hdr.DiskGUID, "DISK GUID"); cli_dbgmsg("Partition Entry Count: %u\n", hdr.tableNumEntries); cli_dbgmsg("Partition Entry Size: %u\n", hdr.tableEntrySize); maplen = (*ctx->fmap)->real_len; /* check engine maxpartitions limit */ if (hdr.tableNumEntries < ctx->engine->maxpartitions) { max_prtns = hdr.tableNumEntries; } else { max_prtns = ctx->engine->maxpartitions; } /* use the partition tables to pass partitions to cli_map_scan */ pos = hdr.tableStartLBA * sectorsize; for (i = 0; i < max_prtns; ++i) { /* read in partition entry */ if (fmap_readn(*ctx->fmap, &gpe, pos, sizeof(gpe)) != sizeof(gpe)) { cli_dbgmsg("cli_scangpt: Invalid GPT partition entry\n"); return CL_EFORMAT; } /* convert the endian to host */ gpe.firstLBA = le64_to_host(gpe.firstLBA); gpe.lastLBA = le64_to_host(gpe.lastLBA); gpe.attributes = le64_to_host(gpe.attributes); for (j = 0; j < 36; ++j) { gpe.name[i] = le16_to_host(gpe.name[i]); } /* check that partition is not empty and within a valid location */ if (gpe.firstLBA == 0) { /* empty partition, invalid */ } else if ((gpe.firstLBA > gpe.lastLBA) || (gpe.firstLBA < hdr.firstUsableLBA) || (gpe.lastLBA > hdr.lastUsableLBA)) { cli_dbgmsg("cli_scangpt: GPT partition exists outside specified bounds\n"); gpt_parsemsg("%llu < %llu, %llu > %llu\n", gpe.firstLBA, hdr.firstUsableLBA, gpe.lastLBA, hdr.lastUsableLBA); /* partition exists outside bounds specified by header or invalid */ } else if (((gpe.lastLBA+1) * sectorsize) > maplen) { /* partition exists outside bounds of the file map */ } else { /* print partition entry data for debug */ cli_dbgmsg("GPT Partition Entry %u:\n", i); gpt_printName(gpe.name, "Name"); gpt_printGUID(gpe.typeGUID, "Type GUID"); gpt_printGUID(gpe.uniqueGUID, "Unique GUID"); cli_dbgmsg("Attributes: %llx\n", (long long unsigned)gpe.attributes); cli_dbgmsg("Blocks: [%llu(%llu) -> %llu(%llu)]\n", (long long unsigned)gpe.firstLBA, (long long unsigned)(gpe.firstLBA * sectorsize), (long long unsigned)gpe.lastLBA, (long long unsigned)((gpe.lastLBA+1) * sectorsize)); /* send the partition to cli_map_scan */ part_off = gpe.firstLBA * sectorsize; part_size = (gpe.lastLBA - gpe.firstLBA + 1) * sectorsize; ret = cli_map_scan(*ctx->fmap, part_off, part_size, ctx, CL_TYPE_PART_ANY); if (ret != CL_CLEAN) { if (SCAN_ALLMATCHES && (ret == CL_VIRUS)) detection = CL_VIRUS; else return ret; } } /* increment the offsets to next partition entry */ pos += hdr.tableEntrySize; } if (i >= ctx->engine->maxpartitions) { cli_dbgmsg("cli_scangpt: max partitions reached\n"); } return detection; }
/* Given mish data, reconstruct the partition details */ static int dmg_handle_mish(cli_ctx *ctx, unsigned int mishblocknum, char *dir, uint64_t xmlOffset, struct dmg_mish_with_stripes *mish_set) { struct dmg_block_data *blocklist = mish_set->stripes; uint64_t totalSectors = 0; uint32_t i; unsigned long projected_size; int ret = CL_CLEAN, ofd; uint8_t sorted = 1, writeable_data = 0; char outfile[NAME_MAX + 1]; /* First loop, fix endian-ness and check if already sorted */ for (i = 0; i < mish_set->mish->blockDataCount; i++) { blocklist[i].type = be32_to_host(blocklist[i].type); // blocklist[i].reserved = be32_to_host(blocklist[i].reserved); blocklist[i].startSector = be64_to_host(blocklist[i].startSector); blocklist[i].sectorCount = be64_to_host(blocklist[i].sectorCount); blocklist[i].dataOffset = be64_to_host(blocklist[i].dataOffset); blocklist[i].dataLength = be64_to_host(blocklist[i].dataLength); cli_dbgmsg("mish %u stripe " STDu32 " type " STDx32 " start " STDu64 " count " STDu64 " source " STDu64 " length " STDu64 "\n", mishblocknum, i, blocklist[i].type, blocklist[i].startSector, blocklist[i].sectorCount, blocklist[i].dataOffset, blocklist[i].dataLength); if ((blocklist[i].dataOffset > xmlOffset) || (blocklist[i].dataOffset + blocklist[i].dataLength > xmlOffset)) { cli_dbgmsg("dmg_handle_mish: invalid stripe offset and/or length\n"); return CL_EFORMAT; } if ((i > 0) && sorted && (blocklist[i].startSector < blocklist[i-1].startSector)) { cli_dbgmsg("dmg_handle_mish: stripes not in order, will have to sort\n"); sorted = 0; } if (dmg_track_sectors(&totalSectors, &writeable_data, i, blocklist[i].type, blocklist[i].sectorCount)) { /* reason was logged from dmg_track_sector_count */ return CL_EFORMAT; } } if (!sorted) { cli_qsort(blocklist, mish_set->mish->blockDataCount, sizeof(struct dmg_block_data), cmp_mish_stripes); } cli_dbgmsg("dmg_handle_mish: stripes in order!\n"); /* Size checks */ if ((writeable_data == 0) || (totalSectors == 0)) { cli_dbgmsg("dmg_handle_mish: no data to output\n"); return CL_CLEAN; } else if (totalSectors > (ULONG_MAX / DMG_SECTOR_SIZE)) { /* cli_checklimits only takes unsigned long for now */ cli_warnmsg("dmg_handle_mish: mish block %u too big to handle (for now)", mishblocknum); return CL_CLEAN; } projected_size = (unsigned long)(totalSectors * DMG_SECTOR_SIZE); ret = cli_checklimits("cli_scandmg", ctx, projected_size, 0, 0); if (ret != CL_CLEAN) { /* limits exceeded */ cli_dbgmsg("dmg_handle_mish: skipping block %u, limits exceeded\n", mishblocknum); return ret; } /* Prepare for file */ snprintf(outfile, sizeof(outfile)-1, "%s"PATHSEP"dmg%02u", dir, mishblocknum); outfile[sizeof(outfile)-1] = '\0'; ofd = open(outfile, O_RDWR|O_CREAT|O_EXCL|O_TRUNC|O_BINARY, 0600); if (ofd < 0) { char err[128]; cli_errmsg("cli_scandmg: Can't create temporary file %s: %s\n", outfile, cli_strerror(errno, err, sizeof(err))); return CL_ETMPFILE; } cli_dbgmsg("dmg_handle_mish: extracting block %u to %s\n", mishblocknum, outfile); /* Push data, stripe by stripe */ for(i=0; i < mish_set->mish->blockDataCount && ret == CL_CLEAN; i++) { switch (blocklist[i].type) { case DMG_STRIPE_EMPTY: case DMG_STRIPE_ZEROES: ret = dmg_stripe_zeroes(ctx, ofd, i, mish_set); break; case DMG_STRIPE_STORED: ret = dmg_stripe_store(ctx, ofd, i, mish_set); break; case DMG_STRIPE_ADC: ret = dmg_stripe_adc(ctx, ofd, i, mish_set); break; case DMG_STRIPE_DEFLATE: ret = dmg_stripe_inflate(ctx, ofd, i, mish_set); break; case DMG_STRIPE_BZ: ret = dmg_stripe_bzip(ctx, ofd, i, mish_set); break; case DMG_STRIPE_SKIP: case DMG_STRIPE_END: default: cli_dbgmsg("dmg_handle_mish: stripe " STDu32 ", skipped\n", i); break; } } /* If okay so far, scan rebuilt partition */ if (ret == CL_CLEAN) { ret = cli_partition_scandesc(ofd, ctx); } close(ofd); if (!ctx->engine->keeptmp) if (cli_unlink(outfile)) return CL_EUNLINK; return ret; }
int cli_scandmg(cli_ctx *ctx) { struct dmg_koly_block hdr; int ret, namelen, ofd; size_t maplen, nread; off_t pos = 0; char *dirname, *tmpfile; const char *outdata; unsigned int file = 0; struct dmg_mish_with_stripes *mish_list = NULL, *mish_list_tail = NULL; enum dmgReadState state = DMG_FIND_BASE_PLIST; int stateDepth[DMG_MAX_STATE]; #if HAVE_LIBXML2 xmlTextReaderPtr reader; #endif if (!ctx || !ctx->fmap) { cli_errmsg("cli_scandmg: Invalid context\n"); return CL_ENULLARG; } maplen = (*ctx->fmap)->real_len; pos = maplen - 512; if (pos <= 0) { cli_dbgmsg("cli_scandmg: Sizing problem for DMG archive.\n"); return CL_CLEAN; } /* Grab koly block */ if (fmap_readn(*ctx->fmap, &hdr, pos, sizeof(hdr)) != sizeof(hdr)) { cli_dbgmsg("cli_scandmg: Invalid DMG trailer block\n"); return CL_EFORMAT; } /* Check magic */ hdr.magic = be32_to_host(hdr.magic); if (hdr.magic == 0x6b6f6c79) { cli_dbgmsg("cli_scandmg: Found koly block @ %ld\n", (long) pos); } else { cli_dbgmsg("cli_scandmg: No koly magic, %8x\n", hdr.magic); return CL_EFORMAT; } hdr.dataForkOffset = be64_to_host(hdr.dataForkOffset); hdr.dataForkLength = be64_to_host(hdr.dataForkLength); cli_dbgmsg("cli_scandmg: data offset %lu len %d\n", (unsigned long)hdr.dataForkOffset, (int)hdr.dataForkLength); hdr.xmlOffset = be64_to_host(hdr.xmlOffset); hdr.xmlLength = be64_to_host(hdr.xmlLength); if (hdr.xmlLength > (uint64_t)INT_MAX) { cli_dbgmsg("cli_scandmg: The embedded XML is way larger than necessary, and probably corrupt or tampered with.\n"); return CL_EFORMAT; } if ((hdr.xmlOffset > (uint64_t)maplen) || (hdr.xmlLength > (uint64_t)maplen) || (hdr.xmlOffset + hdr.xmlLength) > (uint64_t)maplen) { cli_dbgmsg("cli_scandmg: XML out of range for this file\n"); return CL_EFORMAT; } cli_dbgmsg("cli_scandmg: XML offset %lu len %d\n", (unsigned long)hdr.xmlOffset, (int)hdr.xmlLength); if (hdr.xmlLength == 0) { cli_dbgmsg("cli_scandmg: Embedded XML length is zero.\n"); return CL_EFORMAT; } /* Create temp folder for contents */ if (!(dirname = cli_gentemp(ctx->engine->tmpdir))) { return CL_ETMPDIR; } if (mkdir(dirname, 0700)) { cli_errmsg("cli_scandmg: Cannot create temporary directory %s\n", dirname); free(dirname); return CL_ETMPDIR; } cli_dbgmsg("cli_scandmg: Extracting into %s\n", dirname); /* Dump XML to tempfile, if needed */ if (ctx->engine->keeptmp) { int xret; xret = dmg_extract_xml(ctx, dirname, &hdr); if (xret != CL_SUCCESS) { /* Printed err detail inside dmg_extract_xml */ free(dirname); return xret; } } /* scan XML with cli_map_scandesc */ ret = cli_map_scandesc(*ctx->fmap, (off_t)hdr.xmlOffset, (size_t)hdr.xmlLength, ctx); if (ret != CL_CLEAN) { cli_dbgmsg("cli_scandmg: retcode from scanning TOC xml: %s\n", cl_strerror(ret)); if (!ctx->engine->keeptmp) cli_rmdirs(dirname); free(dirname); return ret; } /* page data from map */ outdata = fmap_need_off_once_len(*ctx->fmap, hdr.xmlOffset, hdr.xmlLength, &nread); if (!outdata || (nread != hdr.xmlLength)) { cli_errmsg("cli_scandmg: Failed getting XML from map, len %d\n", (int)hdr.xmlLength); if (!ctx->engine->keeptmp) cli_rmdirs(dirname); free(dirname); return CL_EMAP; } /* time to walk the tree */ /* plist -> dict -> (key:resource_fork) dict -> (key:blkx) array -> dict */ /* each of those bottom level dict should have 4 parts */ /* [ Attributes, Data, ID, Name ], where Data is Base64 mish block */ /* This is the block where we require libxml2 */ #if HAVE_LIBXML2 /* XML_PARSE_NOENT | XML_PARSE_NONET | XML_PARSE_COMPACT */ #define DMG_XML_PARSE_OPTS (1 << 1 | 1 << 11 | 1 << 16) reader = xmlReaderForMemory(outdata, (int)hdr.xmlLength, "toc.xml", NULL, DMG_XML_PARSE_OPTS); if (!reader) { cli_dbgmsg("cli_scandmg: Failed parsing XML!\n"); if (!ctx->engine->keeptmp) cli_rmdirs(dirname); free(dirname); return CL_EFORMAT; } stateDepth[DMG_FIND_BASE_PLIST] = -1; // May need to check for (xmlTextReaderIsEmptyElement(reader) == 0) /* Break loop if have return code or reader can't read any more */ while ((ret == CL_CLEAN) && (xmlTextReaderRead(reader) == 1)) { xmlReaderTypes nodeType; nodeType = xmlTextReaderNodeType(reader); if (nodeType == XML_READER_TYPE_ELEMENT) { // New element, do name check xmlChar *nodeName; int depth; depth = xmlTextReaderDepth(reader); if (depth < 0) { break; } if ((depth > 50) && SCAN_ALGO) { // Possible heuristic, should limit runaway cli_dbgmsg("cli_scandmg: Excessive nesting in DMG TOC.\n"); break; } nodeName = xmlTextReaderLocalName(reader); if (!nodeName) continue; dmg_parsemsg("read: name %s depth %d\n", nodeName, depth); if ((state == DMG_FIND_DATA_MISH) && (depth == stateDepth[state-1])) { xmlChar * textValue; struct dmg_mish_with_stripes *mish_set; /* Reset state early, for continue cases */ stateDepth[DMG_FIND_KEY_DATA] = -1; state--; if (xmlStrcmp(nodeName, "data") != 0) { cli_dbgmsg("cli_scandmg: Not blkx data element\n"); xmlFree(nodeName); continue; } dmg_parsemsg("read: Found blkx data element\n"); /* Pull out data content from text */ if (xmlTextReaderIsEmptyElement(reader)) { cli_dbgmsg("cli_scandmg: blkx data element is empty\n"); xmlFree(nodeName); continue; } if (xmlTextReaderRead(reader) != 1) { xmlFree(nodeName); break; } if (xmlTextReaderNodeType(reader) != XML_READER_TYPE_TEXT) { cli_dbgmsg("cli_scandmg: Next node not text\n"); xmlFree(nodeName); continue; } textValue = xmlTextReaderValue(reader); if (textValue == NULL) { xmlFree(nodeName); continue; } /* Have encoded mish block */ mish_set = cli_malloc(sizeof(struct dmg_mish_with_stripes)); if (mish_set == NULL) { ret = CL_EMEM; xmlFree(textValue); xmlFree(nodeName); break; } ret = dmg_decode_mish(ctx, &file, textValue, mish_set); xmlFree(textValue); if (ret == CL_EFORMAT) { /* Didn't decode, or not a mish block */ ret = CL_CLEAN; xmlFree(nodeName); continue; } else if (ret != CL_CLEAN) { xmlFree(nodeName); continue; } /* Add mish block to list */ if (mish_list_tail != NULL) { mish_list_tail->next = mish_set; mish_list_tail = mish_set; } else { mish_list = mish_set; mish_list_tail = mish_set; } mish_list_tail->next = NULL; } if ((state == DMG_FIND_KEY_DATA) && (depth > stateDepth[state-1]) && (xmlStrcmp(nodeName, "key") == 0)) { xmlChar * textValue; dmg_parsemsg("read: Found key - checking for Data\n"); if (xmlTextReaderRead(reader) != 1) { xmlFree(nodeName); break; } if (xmlTextReaderNodeType(reader) != XML_READER_TYPE_TEXT) { cli_dbgmsg("cli_scandmg: Key node no text\n"); xmlFree(nodeName); continue; } textValue = xmlTextReaderValue(reader); if (textValue == NULL) { cli_dbgmsg("cli_scandmg: no value from xmlTextReaderValue\n"); xmlFree(nodeName); continue; } if (xmlStrcmp(textValue, "Data") == 0) { dmg_parsemsg("read: Matched data\n"); stateDepth[DMG_FIND_KEY_DATA] = depth; state++; } else { dmg_parsemsg("read: text value is %s\n", textValue); } xmlFree(textValue); } if ((state == DMG_FIND_BLKX_CONTAINER) && (depth == stateDepth[state-1])) { if (xmlStrcmp(nodeName, "array") == 0) { dmg_parsemsg("read: Found array blkx\n"); stateDepth[DMG_FIND_BLKX_CONTAINER] = depth; state++; } else if (xmlStrcmp(nodeName, "dict") == 0) { dmg_parsemsg("read: Found dict blkx\n"); stateDepth[DMG_FIND_BLKX_CONTAINER] = depth; state++; } else { cli_dbgmsg("cli_scandmg: Bad blkx, not container\n"); stateDepth[DMG_FIND_KEY_BLKX] = -1; state--; } } if ((state == DMG_FIND_KEY_BLKX) && (depth == stateDepth[state-1] + 1) && (xmlStrcmp(nodeName, "key") == 0)) { xmlChar * textValue; dmg_parsemsg("read: Found key - checking for blkx\n"); if (xmlTextReaderRead(reader) != 1) { xmlFree(nodeName); break; } if (xmlTextReaderNodeType(reader) != XML_READER_TYPE_TEXT) { cli_dbgmsg("cli_scandmg: Key node no text\n"); xmlFree(nodeName); continue; } textValue = xmlTextReaderValue(reader); if (textValue == NULL) { cli_dbgmsg("cli_scandmg: no value from xmlTextReaderValue\n"); xmlFree(nodeName); continue; } if (xmlStrcmp(textValue, "blkx") == 0) { cli_dbgmsg("cli_scandmg: Matched blkx\n"); stateDepth[DMG_FIND_KEY_BLKX] = depth; state++; } else { cli_dbgmsg("cli_scandmg: wanted blkx, text value is %s\n", textValue); } xmlFree(textValue); } if ((state == DMG_FIND_DICT_RESOURCE_FORK) && (depth == stateDepth[state-1])) { if (xmlStrcmp(nodeName, "dict") == 0) { dmg_parsemsg("read: Found resource-fork dict\n"); stateDepth[DMG_FIND_DICT_RESOURCE_FORK] = depth; state++; } else { dmg_parsemsg("read: Not resource-fork dict\n"); stateDepth[DMG_FIND_KEY_RESOURCE_FORK] = -1; state--; } } if ((state == DMG_FIND_KEY_RESOURCE_FORK) && (depth == stateDepth[state-1] + 1) && (xmlStrcmp(nodeName, "key") == 0)) { dmg_parsemsg("read: Found resource-fork key\n"); stateDepth[DMG_FIND_KEY_RESOURCE_FORK] = depth; state++; } if ((state == DMG_FIND_BASE_DICT) && (depth == stateDepth[state-1] + 1) && (xmlStrcmp(nodeName, "dict") == 0)) { dmg_parsemsg("read: Found dict start\n"); stateDepth[DMG_FIND_BASE_DICT] = depth; state++; } if ((state == DMG_FIND_BASE_PLIST) && (xmlStrcmp(nodeName, "plist") == 0)) { dmg_parsemsg("read: Found plist start\n"); stateDepth[DMG_FIND_BASE_PLIST] = depth; state++; } xmlFree(nodeName); } else if ((nodeType == XML_READER_TYPE_END_ELEMENT) && (state > DMG_FIND_BASE_PLIST)) { int significantEnd = 0; int depth = xmlTextReaderDepth(reader); if (depth < 0) { break; } else if (depth < stateDepth[state-1]) { significantEnd = 1; } else if ((depth == stateDepth[state-1]) && (state-1 == DMG_FIND_BLKX_CONTAINER)) { /* Special case, ending blkx container */ significantEnd = 1; } if (significantEnd) { dmg_parsemsg("read: significant end tag, state %d\n", state); stateDepth[state-1] = -1; state--; if ((state-1 == DMG_FIND_KEY_RESOURCE_FORK) || (state-1 == DMG_FIND_KEY_BLKX)) { /* Keys end their own tag (validly) and the next state depends on the following tag */ // cli_dbgmsg("read: significant end tag ending prior key state\n"); stateDepth[state-1] = -1; state--; } } else { dmg_parsemsg("read: not significant end tag, state %d depth %d prior depth %d\n", state, depth, stateDepth[state-1]); } } } xmlFreeTextReader(reader); xmlCleanupParser(); #else cli_dbgmsg("cli_scandmg: libxml2 support is compiled out. It is required for full DMG support.\n"); #endif /* Loop over mish array */ file = 0; while ((ret == CL_CLEAN) && (mish_list != NULL)) { /* Handle & scan mish block */ ret = dmg_handle_mish(ctx, file++, dirname, hdr.xmlOffset, mish_list); free(mish_list->mish); mish_list_tail = mish_list; mish_list = mish_list->next; free(mish_list_tail); } /* Cleanup */ /* If error occurred, need to free mish items and mish blocks */ while (mish_list != NULL) { free(mish_list->mish); mish_list_tail = mish_list; mish_list = mish_list->next; free(mish_list_tail); } if (!ctx->engine->keeptmp) cli_rmdirs(dirname); free(dirname); return ret; }
/* Transform the base64-encoded string into the binary structure * After this, the base64 string (from xml) can be released * If mish_set->mish is set by this function, it must be freed by the caller */ static int dmg_decode_mish(cli_ctx *ctx, unsigned int *mishblocknum, xmlChar *mish_base64, struct dmg_mish_with_stripes *mish_set) { int ret = CL_CLEAN; size_t base64_len, buff_size, decoded_len; uint8_t *decoded; const uint8_t mish_magic[4] = { 0x6d, 0x69, 0x73, 0x68 }; (*mishblocknum)++; base64_len = strlen(mish_base64); dmg_parsemsg("dmg_decode_mish: len of encoded block %u is %lu\n", *mishblocknum, base64_len); /* speed vs memory, could walk the encoded data and skip whitespace in calculation */ buff_size = 3 * base64_len / 4 + 4; dmg_parsemsg("dmg_decode_mish: buffer for mish block %u is %lu\n", *mishblocknum, (unsigned long)buff_size); decoded = cli_malloc(buff_size); if (!decoded) return CL_EMEM; if (sf_base64decode((uint8_t *)mish_base64, base64_len, decoded, buff_size - 1, &decoded_len)) { cli_dbgmsg("dmg_decode_mish: failed base64 decoding on mish block %u\n", *mishblocknum); free(decoded); return CL_EFORMAT; } dmg_parsemsg("dmg_decode_mish: len of decoded mish block %u is %lu\n", *mishblocknum, (unsigned long)decoded_len); if (decoded_len < sizeof(struct dmg_mish_block)) { cli_dbgmsg("dmg_decode_mish: block %u too short for valid mish block\n", *mishblocknum); free(decoded); return CL_EFORMAT; } /* mish check: magic is mish, have to check after conversion from base64 * mish base64 is bWlzaA [but last character can change last two bytes] * won't see that in practice much (affects value of version field) */ if (memcmp(decoded, mish_magic, 4)) { cli_dbgmsg("dmg_decode_mish: block %u does not have mish magic\n", *mishblocknum); free(decoded); return CL_EFORMAT; } mish_set->mish = (struct dmg_mish_block *)decoded; mish_set->mish->startSector = be64_to_host(mish_set->mish->startSector); mish_set->mish->sectorCount = be64_to_host(mish_set->mish->sectorCount); mish_set->mish->dataOffset = be64_to_host(mish_set->mish->dataOffset); // mish_set->mish->bufferCount = be32_to_host(mish_set->mish->bufferCount); mish_set->mish->blockDataCount = be32_to_host(mish_set->mish->blockDataCount); cli_dbgmsg("dmg_decode_mish: startSector = " STDu64 " sectorCount = " STDu64 " dataOffset = " STDu64 " stripeCount = " STDu32 "\n", mish_set->mish->startSector, mish_set->mish->sectorCount, mish_set->mish->dataOffset, mish_set->mish->blockDataCount); /* decoded length should be mish block + blockDataCount * 40 */ if (decoded_len < (sizeof(struct dmg_mish_block) + mish_set->mish->blockDataCount * sizeof(struct dmg_block_data))) { cli_dbgmsg("dmg_decode_mish: mish block %u too small\n", *mishblocknum); free(decoded); mish_set->mish = NULL; return CL_EFORMAT; } else if (decoded_len > (sizeof(struct dmg_mish_block) + mish_set->mish->blockDataCount * sizeof(struct dmg_block_data))) { cli_dbgmsg("dmg_decode_mish: mish block %u bigger than needed, continuing\n", *mishblocknum); } mish_set->stripes = (struct dmg_block_data *)(decoded + sizeof(struct dmg_mish_block)); return CL_CLEAN; }