libclamav/ishield.c
a4b30692
 /*
c442ca9c
  *  Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
  *  Copyright (C) 2009-2013 Sourcefire, Inc.
a4b30692
  *
  *  Authors: aCaB <acab@clamav.net>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2 as
  *  published by the Free Software Foundation.
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
  */
 
 /* common routines to deal with installshield archives and installers */
 
7ebede9b
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #ifdef	HAVE_UNISTD_H
 #include <unistd.h>
 #endif
9c3c24ca
 #if HAVE_STRING_H
7ebede9b
 #include <string.h>
9c3c24ca
 #endif
 #include <limits.h>
86d59b24
 #if HAVE_STRINGS_H
a8271e9d
 #include <strings.h>
86d59b24
 #endif
11195c0b
 #if defined(HAVE_MMAP) && defined(HAVE_SYS_MMAN_H)
 #include <sys/mman.h>
 #endif
7ebede9b
 #include <zlib.h>
 
60d8d2c3
 #include "clamav.h"
7ebede9b
 #include "scanners.h"
 #include "others.h"
303f7b34
 #include "fmap.h"
7ebede9b
 #include "ishield.h"
 
9c3c24ca
 #ifndef LONG_MAX
 #define LONG_MAX ((-1UL)>>1)
 #endif
 
a4b30692
 #ifndef HAVE_ATTRIB_PACKED
 #define __attribute__(x)
 #endif
 #ifdef HAVE_PRAGMA_PACK
 #pragma pack(1)
 #endif
 #ifdef HAVE_PRAGMA_PACK_HPPA
 #pragma pack 1
 #endif
 
 /* PACKED things go here */
 
9a4da6af
 struct IS_HDR {
     uint32_t magic; 
     uint32_t unk1; /* version ??? */
     uint32_t unk2; /* ??? */
     uint32_t data_off;
     uint32_t data_sz; /* ??? */
 } __attribute__((packed));
 
a4b30692
 struct IS_FB {
     char fname[0x104]; /* MAX_PATH */
     uint32_t unk1; /* 6 */
     uint32_t unk2;
     uint64_t csize;
     uint32_t unk3;
     uint32_t unk4; /* 1 */
     uint32_t unk5;
     uint32_t unk6;
     uint32_t unk7;
     uint32_t unk8;
     uint32_t unk9;
     uint32_t unk10;
     uint32_t unk11;
 } __attribute__((packed));
 
1d7cebd8
 struct IS_COMPONENT {
     uint32_t str_name_off;
     uint32_t unk_str1_off;
     uint32_t unk_str2_off;
     uint16_t unk_flags;
     uint32_t unk_str3_off;
     uint32_t unk_str4_off;
     uint16_t ordinal_id;
     uint32_t str_shortname_off;
     uint32_t unk_str6_off;
     uint32_t unk_str7_off;
     uint32_t unk_str8_off;
     char guid1[16];
     char guid2[16];
     uint32_t unk_str9_off;
     char unk1[3];
     uint16_t unk_flags2;
     uint32_t unk3[5];
     uint32_t unk_str10_off;
     uint32_t unk4[4];
     uint16_t unk5;
     uint16_t sub_comp_cnt;
     uint32_t sub_comp_offs_array;
     uint32_t next_comp_off;
     uint32_t unk_str11_off;
     uint32_t unk_str12_off;
     uint32_t unk_str13_off;
     uint32_t unk_str14_off;
1b98609f
     uint32_t str_next1_off;
     uint32_t str_next2_off;
1d7cebd8
 } __attribute__((packed));
 
 struct IS_INSTTYPEHDR {
     uint32_t unk1;
     uint32_t cnt;
     uint32_t off;
 } __attribute__((packed));
 
 struct IS_INSTTYPEITEM {
     uint32_t str_name1_off;
     uint32_t str_name2_off;
     uint32_t str_name3_off;
     uint32_t cnt;
     uint32_t off;
 } __attribute__((packed));
 
 
 struct IS_OBJECTS {
     /* 200 */ uint32_t strings_off;
     /* 204 */ uint32_t zero1;
     /* 208 */ uint32_t comps_off;
     /* 20c */ uint32_t dirs_off;
     /* 210 */ uint32_t zero2;
     /* 214 */ uint32_t unk1, unk2; /* 0x4a636 304694 uguali - NOT AN OFFSET! */
     /* 21c */ uint32_t dirs_cnt;
     /* 220 */ uint32_t zero3;
     /* 224 */ uint32_t dirs_sz; /* dirs_cnt * 4 */
     /* 228 */ uint32_t files_cnt;
     /* 22c */ uint32_t dir_sz2; /* same as dirs_sz ?? */
     /* 230 */ uint16_t unk5; /* 1 - comp count ?? */
     /* 232 */ uint32_t insttype_off;
     /* 234 */ uint16_t zero4;
     /* 238 */ uint32_t zero5;
     /* 23c */ uint32_t unk7; /* 0xd0 - 208 */
     /* 240 */ uint16_t unk8;
     /* 242 */ uint32_t unk9;
     /* 246 */ uint32_t unk10;   
 } __attribute__((packed));
 
 
 struct IS_FILEITEM {
     uint16_t flags; /* 0 = EXTERNAL | 4 = INTERNAL | 8 = NAME_fuckup_rare | c = name_fuckup_common */
     uint64_t size;
     uint64_t csize;
     uint64_t stream_off;
     uint8_t md5[16];
     uint64_t versioninfo_id;
     uint32_t zero1;
     uint32_t zero2;
     uint32_t str_name_off;
     uint16_t dir_id;
     uint32_t unk13; /* 0, 20, 21 ??? */
     uint32_t unk14; /* timestamp ??? */
     uint32_t unk15; /* begins with 1 then 2 but not the cab# ??? */
     uint32_t prev_dup_id; /* msvcrt #38(0, 97, 2) #97(38, 1181, 3) ... , 0, 1) */
     uint32_t next_dup_id;
     uint8_t flag_has_dup; /* HAS_NEXT = 2 | HAS_BOTH = 3 | HAS_PREV = 1 */
     uint16_t datafile_id;
 } __attribute__((packed));
 
 
a4b30692
 #ifdef HAVE_PRAGMA_PACK
 #pragma pack()
 #endif
 #ifdef HAVE_PRAGMA_PACK_HPPA
 #pragma pack
 #endif
 
7ebede9b
 
a4b30692
 
9a4da6af
 static int is_dump_and_scan(cli_ctx *ctx, off_t off, size_t fsize);
a8271e9d
 static const uint8_t skey[] = { 0xec, 0xca, 0x79, 0xf8 }; /* ~0x13, ~0x35, ~0x86, ~0x07 */
a4b30692
 
1d7cebd8
 /* Extracts the content of MSI based IS */
9a4da6af
 int cli_scanishield_msi(cli_ctx *ctx, off_t off) {
f304dc68
     const uint8_t *buf;
12a812db
     unsigned int fcount, scanned = 0;
7ebede9b
     int ret;
49cc1e3c
     fmap_t *map = *ctx->fmap;
7ebede9b
 
     cli_dbgmsg("in ishield-msi\n");
9a4da6af
     if(!(buf = fmap_need_off_once(map, off, 0x20))) {
7ebede9b
 	cli_dbgmsg("ishield-msi: short read for header\n");
 	return CL_CLEAN;
     }
9a4da6af
     off += 0x20;
12a812db
     if(cli_readint32(buf + 8) | cli_readint32(buf + 0xc) | cli_readint32(buf + 0x10) | cli_readint32(buf + 0x14) | cli_readint32(buf + 0x18) | cli_readint32(buf + 0x1c))
 	return CL_CLEAN;
7ebede9b
     if(!(fcount = cli_readint32(buf))) {
 	cli_dbgmsg("ishield-msi: no files?\n");
 	return CL_CLEAN;
     }
     while(fcount--) {
a4b30692
 	struct IS_FB fb;
7ebede9b
 	uint8_t obuf[BUFSIZ], *key = (uint8_t *)&fb.fname;
 	char *tempfile;
 	unsigned int i, lameidx=0, keylen;
 	int ofd;
 	uint64_t csize;
 	z_stream z;
 
9a4da6af
 	if(fmap_readn(map, &fb, off, sizeof(fb)) != sizeof(fb)) {
7ebede9b
 	    cli_dbgmsg("ishield-msi: short read for fileblock\n");
 	    return CL_CLEAN;
 	}
9a4da6af
 	off += sizeof(fb);
7ebede9b
 	fb.fname[sizeof(fb.fname)-1] = '\0';
 	csize = le64_to_host(fb.csize);
9a4da6af
 	if(!CLI_ISCONTAINED(0, map->len, off, csize)) {
 	    cli_dbgmsg("ishield-msi: next stream is out of file, giving up\n");
 	    return CL_CLEAN;
 	}
12a812db
 	if(ctx->engine->maxfilesize && csize > ctx->engine->maxfilesize) {
e78b5186
 	    cli_dbgmsg("ishield-msi: skipping stream due to size limits (%lu vs %lu)\n", (unsigned long int) csize, (unsigned long int) ctx->engine->maxfilesize);
9a4da6af
 	    off += csize;
12a812db
 	    continue;
 	}
 
7ebede9b
 	keylen = strlen((const char *)key);
 	if(!keylen) return CL_CLEAN;
12a812db
 	/* FIXMEISHIELD: cleanup the spam below */
4cda88cc
 	cli_dbgmsg("ishield-msi: File %s (csize: %llx, unk1:%x unk2:%x unk3:%x unk4:%x unk5:%x unk6:%x unk7:%x unk8:%x unk9:%x unk10:%x unk11:%x)\n", key, (long long)csize, fb.unk1, fb.unk2, fb.unk3, fb.unk4, fb.unk5, fb.unk6, fb.unk7, fb.unk8, fb.unk9, fb.unk10, fb.unk11);
7ebede9b
 	if(!(tempfile = cli_gentemp(ctx->engine->tmpdir))) return CL_EMEM;
 	if((ofd = open(tempfile, O_RDWR|O_CREAT|O_TRUNC|O_BINARY, S_IRUSR|S_IWUSR)) < 0) {
 	    cli_dbgmsg("ishield-msi: failed to create file %s\n", tempfile);
 	    free(tempfile);
 	    return CL_ECREAT;
 	}
 
 	for(i=0; i<keylen; i++)
 	    key[i] ^= skey[i & 3];
 	memset(&z, 0, sizeof(z));
 	inflateInit(&z);
4cda88cc
 	ret = CL_SUCCESS;
7ebede9b
 	while(csize) {
9a4da6af
 	    uint8_t buf2[BUFSIZ];
 	    z.avail_in = MIN(csize, sizeof(buf2));
cd94be7a
 	    if((uInt)fmap_readn(map, buf2, off, z.avail_in) != z.avail_in) {
7ebede9b
 		cli_dbgmsg("ishield-msi: premature EOS or read fail\n");
9a4da6af
 		break;
7ebede9b
 	    }
9a4da6af
 	    off += z.avail_in;
7ebede9b
 	    for(i=0; i<z.avail_in; i++, lameidx++) {
9a4da6af
 		uint8_t c = buf2[i];
7ebede9b
 		c = (c>>4) | (c<<4);
 		c ^= key[(lameidx & 0x3ff) % keylen];
9a4da6af
 		buf2[i] = c;
7ebede9b
 	    }
 	    csize -= z.avail_in;
9a4da6af
 	    z.next_in = buf2;
7ebede9b
 	    do {
f0b36be4
 		int inf;
7ebede9b
 		z.avail_out = sizeof(obuf);
 		z.next_out = obuf;
f0b36be4
 		inf = inflate(&z, 0);
 		if(inf != Z_OK && inf != Z_STREAM_END && inf != Z_BUF_ERROR) {
7ebede9b
 		    cli_dbgmsg("ishield-msi: bad stream\n");
 		    csize = 0;
9a4da6af
 		    off += csize;
7ebede9b
 		    break;
 		}
4cda88cc
 		if (cli_writen(ofd, obuf, sizeof(obuf) - z.avail_out) < 0) {
 		    ret = CL_EWRITE;
 		    csize = 0;
 		    break;
 		}
12a812db
 		if(ctx->engine->maxfilesize && z.total_out > ctx->engine->maxfilesize) {
e78b5186
 		    cli_dbgmsg("ishield-msi: trimming output file due to size limits (%lu vs %lu)\n", z.total_out, (unsigned long int) ctx->engine->maxfilesize);
9a4da6af
 		    off += csize;
12a812db
 		    csize = 0;
 		    break;
 		}
7ebede9b
 	    } while (!z.avail_out);
 	}
 
 	inflateEnd(&z);
 
4cda88cc
 	if (ret == CL_SUCCESS) {
 	    cli_dbgmsg("ishield-msi: extracted to %s\n", tempfile);
7ebede9b
 
96914546
 	    if (lseek(ofd, 0, SEEK_SET) == -1) {
             cli_dbgmsg("ishield-msi: call to lseek() failed\n");
             ret = CL_ESEEK;
         }
d39cb658
 	    ret = cli_magic_scandesc(ofd, tempfile, ctx);
4cda88cc
 	}
7ebede9b
 	close(ofd);
 
 	if(!ctx->engine->keeptmp)
 	    if(cli_unlink(tempfile)) ret = CL_EUNLINK;
 	free(tempfile);
 
 	if(ret != CL_CLEAN)
 	    return ret;
12a812db
 
 	scanned++;
 	if (ctx->engine->maxfiles && scanned>=ctx->engine->maxfiles) {
 	    cli_dbgmsg("ishield-msi: File limit reached (max: %u)\n", ctx->engine->maxfiles);
 	    return CL_EMAXFILES;
 	}
7ebede9b
     }
     return CL_CLEAN;
 }
56e5821b
 
1d7cebd8
 
 struct IS_CABSTUFF {
a8271e9d
     struct CABARRAY {
 	unsigned int cabno;
 	off_t off;
 	size_t sz;
     } *cabs;
     off_t hdr;
     size_t hdrsz;
     unsigned int cabcnt;
 };
 
1d7cebd8
 static void md5str(uint8_t *sum);
9a4da6af
 static int is_parse_hdr(cli_ctx *ctx, struct IS_CABSTUFF *c);
430a6261
 static int is_extract_cab(cli_ctx *ctx, uint64_t off, uint64_t size, uint64_t csize);
56e5821b
 
1d7cebd8
 /* Extract the content of older (non-MSI) IS */
9a4da6af
 int cli_scanishield(cli_ctx *ctx, off_t off, size_t sz) {
f304dc68
     const char *fname, *path, *version, *strsz, *data;
     char *eostr;
9a4da6af
     int ret = CL_CLEAN;
a8271e9d
     long fsize;
56e5821b
     off_t coff = off;
1d7cebd8
     struct IS_CABSTUFF c = { NULL, -1, 0, 0 };
49cc1e3c
     fmap_t *map = *ctx->fmap;
248c2f4d
     unsigned fc = 0;
7a307529
     int virus_found = 0;
56e5821b
 
a8271e9d
     while(ret == CL_CLEAN) {
9a4da6af
 	fname = fmap_need_offstr(map, coff, 2048);
 	if(!fname) break;
 	coff += strlen(fname) + 1;
56e5821b
 
9a4da6af
 	path = fmap_need_offstr(map, coff, 2048);
 	if(!path) break;
 	coff += strlen(path) + 1;
56e5821b
 
9a4da6af
 	version = fmap_need_offstr(map, coff, 2048);
 	if(!version) break;
 	coff += strlen(version) + 1;
56e5821b
 
9a4da6af
 	strsz = fmap_need_offstr(map, coff, 2048);
 	if(!strsz) break;
 	coff += strlen(strsz) + 1;
56e5821b
 
9a4da6af
 	data = &strsz[strlen(strsz) + 1];
56e5821b
 
 	fsize = strtol(strsz, &eostr, 10);
1b98609f
 	if(fsize < 0 || fsize == LONG_MAX ||
 	   !*strsz || !eostr || eostr == strsz || *eostr ||
a8271e9d
 	   (unsigned long)fsize >= sz ||
cd94be7a
 	   (size_t)(data - fname) >= sz - fsize
1b98609f
 	) break;
56e5821b
 
e78b5186
 	cli_dbgmsg("ishield: @%lx found file %s (%s) - version %s - size %lu\n", (unsigned long int) coff, fname, path, version, (unsigned long int) fsize);
248c2f4d
 	if(cli_matchmeta(ctx, fname, fsize, fsize, 0, fc++, 0, NULL) == CL_VIRUS) {
d7979d4f
             if (!SCAN_ALLMATCHES) {
7a307529
                 ret = CL_VIRUS;
                 break;
             }
             ret = CL_CLEAN;
             virus_found = 1;
248c2f4d
 	}
9a4da6af
 	sz -= (data - fname) + fsize;
 
a8271e9d
 	if(!strncasecmp(fname, "data", 4)) {
 	    long cabno;
 	    if(!strcasecmp(fname + 4, "1.hdr")) {
 		if(c.hdr == -1) {
c19515d5
 		    cli_dbgmsg("ishield: added data1.hdr to array\n");
a8271e9d
 		    c.hdr = coff;
 		    c.hdrsz = fsize;
 		    coff += fsize;
 		    continue;
 		}
 		cli_warnmsg("ishield: got multiple header files\n");
 	    }
 	    cabno = strtol(fname + 4, &eostr, 10);
 	    if(cabno > 0 && cabno < 65536 && fname[4] && eostr && eostr != &fname[4] && !strcasecmp(eostr, ".cab")) {
 		unsigned int i;
 		for(i=0; i<c.cabcnt && i!=c.cabs[i].cabno; i++) { }
 		if(i==c.cabcnt) {
 		    c.cabcnt++;
 		    if(!(c.cabs = cli_realloc2(c.cabs, sizeof(struct CABARRAY) * c.cabcnt))) {
 			ret = CL_EMEM;
 			break;
 		    }
c19515d5
 		    cli_dbgmsg("ishield: added data%lu.cab to array\n", cabno);
a8271e9d
 		    c.cabs[i].cabno = cabno;
 		    c.cabs[i].off = coff;
 		    c.cabs[i].sz = fsize;
 		    coff += fsize;
 		    continue;
 		}
 		cli_warnmsg("ishield: got multiple data%lu.cab files\n", cabno);
 	    }
 	}
 
9a4da6af
 	fmap_unneed_ptr(map, fname, data-fname);
 	ret = is_dump_and_scan(ctx, coff, fsize);
a8271e9d
 	coff += fsize;
     }
56e5821b
 
a8271e9d
     if(ret == CL_CLEAN && (c.cabcnt || c.hdr != -1)) {
9a4da6af
       if((ret = is_parse_hdr(ctx, &c)) == CL_CLEAN) {
a8271e9d
 	    unsigned int i;
c19515d5
 	    if(c.hdr != -1) {
1b98609f
 		cli_dbgmsg("ishield: scanning data1.hdr\n");
9a4da6af
 		ret = is_dump_and_scan(ctx, c.hdr, c.hdrsz);
c19515d5
 	    }
a8271e9d
 	    for(i=0; i<c.cabcnt && ret == CL_CLEAN; i++) {
1b98609f
 		cli_dbgmsg("ishield: scanning data%u.cab\n", c.cabs[i].cabno);
9a4da6af
 		ret = is_dump_and_scan(ctx, c.cabs[i].off, c.cabs[i].sz);
a8271e9d
 	    }
1b98609f
       } else if( ret == CL_BREAK ) ret = CL_CLEAN;
56e5821b
     }
a8271e9d
     if(c.cabs) free(c.cabs);
7a307529
 
     if (virus_found != 0)
         return CL_VIRUS;
1b98609f
     return ret;
56e5821b
 }
a8271e9d
 
 
1d7cebd8
 /* Utility func to scan a fd @ a given offset and size */
9a4da6af
 static int is_dump_and_scan(cli_ctx *ctx, off_t off, size_t fsize) {
f304dc68
     char *fname;
     const char *buf;
a8271e9d
     int ofd, ret = CL_CLEAN;
49cc1e3c
     fmap_t *map = *ctx->fmap;
a8271e9d
 
     if(!fsize) {
1b98609f
 	cli_dbgmsg("ishield: skipping empty file\n");
a8271e9d
 	return CL_CLEAN;
     }
     if(!(fname = cli_gentemp(ctx->engine->tmpdir)))
 	return CL_EMEM;
 
     if((ofd = open(fname, O_RDWR|O_CREAT|O_TRUNC|O_BINARY, S_IRUSR|S_IWUSR)) < 0) {
 	cli_errmsg("ishield: failed to create file %s\n", fname);
 	free(fname);
 	return CL_ECREAT;
     }
     while(fsize) {
9a4da6af
 	size_t rd = MIN(fsize, map->pgsz);
 	if(!(buf = fmap_need_off_once(map, off, rd))) {
1b98609f
 	    cli_dbgmsg("ishield: read error\n");
a8271e9d
 	    ret = CL_EREAD;
 	    break;
 	}
9a4da6af
 	if(cli_writen(ofd, buf, rd) <= 0) {
a8271e9d
 	    ret = CL_EWRITE;
 	    break;
 	}
9a4da6af
 	fsize -= rd;
 	off += rd;
a8271e9d
     }
     if(!fsize) {
1b98609f
 	cli_dbgmsg("ishield: extracted to %s\n", fname);
96914546
 	if (lseek(ofd, 0, SEEK_SET) == -1) {
         cli_dbgmsg("ishield: call to lseek() failed\n");
         ret = CL_ESEEK;
     }
d39cb658
 	ret = cli_magic_scandesc(ofd, fname, ctx);
a8271e9d
     }
     close(ofd);
     if(!ctx->engine->keeptmp)
 	if(cli_unlink(fname)) ret = CL_EUNLINK;
     free(fname);
     return ret;
 }
1d7cebd8
 
1b98609f
 /* Process data1.hdr and extracts all the available files from dataX.cab */
9a4da6af
 static int is_parse_hdr(cli_ctx *ctx, struct IS_CABSTUFF *c) { 
1d7cebd8
     uint32_t h1_data_off, objs_files_cnt, objs_dirs_off;
1b98609f
     unsigned int off, i, scanned = 0;
     int ret = CL_BREAK;
ba65fdc8
     char hash[33], *hdr;
49cc1e3c
     fmap_t *map = *ctx->fmap;
1d7cebd8
 
f304dc68
     const struct IS_HDR *h1;
1d7cebd8
     struct IS_OBJECTS *objs;
     /* struct IS_INSTTYPEHDR *typehdr; -- UNUSED */
 
1b98609f
     if(!c->hdr || !c->hdrsz || !c->cabcnt) {
 	cli_dbgmsg("is_parse_hdr: inconsistent hdr, maybe a false match\n");
 	return CL_CLEAN;
     }
1d7cebd8
 
9a4da6af
     if(!(h1 = fmap_need_off(map, c->hdr, c->hdrsz))) {
1d7cebd8
 	cli_dbgmsg("is_parse_hdr: not enough room for H1\n");
 	return CL_CLEAN;
     }
9a4da6af
     hdr = (char *)h1;
1d7cebd8
     h1_data_off = le32_to_host(h1->data_off);
303f7b34
     objs = (struct IS_OBJECTS *)fmap_need_ptr(map, hdr + h1_data_off, sizeof(*objs));
     if(!objs) {
9a4da6af
         cli_dbgmsg("is_parse_hdr: not enough room for OBJECTS\n");
         return CL_CLEAN;
1d7cebd8
     }
 
1b98609f
     cli_dbgmsg("is_parse_hdr: magic %x, unk1 %x, unk2 %x, data_off %x, data_sz %x\n",
9a4da6af
                h1->magic, h1->unk1, h1->unk2, h1_data_off, h1->data_sz);
1d7cebd8
     if(le32_to_host(h1->magic) != 0x28635349) {
9a4da6af
         cli_dbgmsg("is_parse_hdr: bad magic. wrong version?\n");
         return CL_CLEAN;
1d7cebd8
     }
 
1ae65c29
     fmap_unneed_ptr(map, h1, sizeof(*h1));
 
1d7cebd8
 /*     cli_errmsg("COMPONENTS\n"); */
 /*     off = le32_to_host(objs->comps_off) + h1_data_off; */
 /*     for(i=1;  ; i++) { */
 /* 	struct IS_COMPONENT *cmp = (struct IS_COMPONENT *)(hdr + off); */
 /* 	if(!CLI_ISCONTAINED(hdr, c->hdrsz, ((char *)cmp), sizeof(*cmp))) { */
 /* 	    cli_dbgmsg("is_extract: not enough room for COMPONENT\n"); */
 /* 	    free(hdr); */
 /* 	    return CL_CLEAN; */
 /* 	} */
 /* 	cli_errmsg("%06u\t%s\n", i, &hdr[le32_to_host(cmp->str_name_off) + h1_data_off]); */
 /* 	spam_strarray(hdr, h1_data_off + cmp->sub_comp_offs_array, h1_data_off, cmp->sub_comp_cnt); */
 /* 	if(!cmp->next_comp_off) break; */
 /* 	off = le32_to_host(cmp->next_comp_off) + h1_data_off; */
 /*     } */
 
 /*     cli_errmsg("DIRECTORIES (%u)", le32_to_host(objs->dirs_cnt)); */
     objs_dirs_off = le32_to_host(objs->dirs_off);
 /*     spam_strarray(hdr, h1_data_off + objs_dirs_off, h1_data_off + objs_dirs_off, objs->dirs_cnt); */
 
 /*     typehdr = (struct INSTTYPEHDR *)&hdr[h1_data_off + le32_to_host(objs->insttype_off)]; */
 /*     printf("INSTTYPES (unk1: %d)\n-----------\n", typehdr->unk1); */
 /*     off = typehdr->off + h1_data_off; */
 /*     for(i=1; i<=typehdr->cnt; i++) { */
 /* 	uint32_t x = *(uint32_t *)(&hdr[off]); */
 /* 	struct INSTTYPEITEM *item = (struct INSTTYPEITEM *)&hdr[x + h1_data_off]; */
 /* 	printf("%06u\t%s\t aka %s\taka %s\n", i, &hdr[item->str_name1_off + h1_data_off], &hdr[item->str_name2_off + h1_data_off], &hdr[item->str_name3_off + h1_data_off]); */
 /* 	printf("components:\n"); */
 /* 	spam_strarray(hdr, h1_data_off + item->off, h1_data_off, item->cnt); */
 /* 	off+=4; */
 /*     } */
 
 
 /* dir = &hdr[*(uint32_t *)(&hdr[h1_data_off + objs_dirs_off + 4 * file->dir_id]) + h1_data_off + objs_dirs_off] */
 
     objs_files_cnt = le32_to_host(objs->files_cnt);
     off = h1_data_off + objs_dirs_off + le32_to_host(objs->dir_sz2);
1ae65c29
     fmap_unneed_ptr(map, objs, sizeof(*objs));
1d7cebd8
     for(i=0; i<objs_files_cnt ;i++) {
9a4da6af
 	struct IS_FILEITEM *file = (struct IS_FILEITEM *)fmap_need_off(map, c->hdr + off, sizeof(*file));
1d7cebd8
 
303f7b34
 	if(file) {
1ae65c29
 	    const char *emptyname = "", *dir_name = emptyname, *file_name = emptyname;
1d7cebd8
 	    uint32_t dir_rel = h1_data_off + objs_dirs_off + 4 * le32_to_host(file->dir_id); /* rel off of dir entry from array of rel ptrs */
 	    uint32_t file_rel = objs_dirs_off + h1_data_off + le32_to_host(file->str_name_off); /* rel off of fname */
4f841728
 	    uint64_t file_stream_off, file_size, file_csize;
 	    uint16_t cabno;
1d7cebd8
 
 	    memcpy(hash, file->md5, 16);
 	    md5str((uint8_t *)hash);
f52b5724
 	    if(fmap_need_ptr_once(map, &hdr[dir_rel], 4)) {
1d7cebd8
 		dir_rel = cli_readint32(&hdr[dir_rel]) + h1_data_off + objs_dirs_off;
303f7b34
 		if(fmap_need_str(map, &hdr[dir_rel], c->hdrsz - dir_rel))
1d7cebd8
 		    dir_name = &hdr[dir_rel];
 	    }
303f7b34
 	    if(fmap_need_str(map, &hdr[file_rel], c->hdrsz - file_rel))
1d7cebd8
 		file_name = &hdr[file_rel];
 		
4f841728
 	    file_stream_off = le64_to_host(file->stream_off);
 	    file_size = le64_to_host(file->size);
 	    file_csize = le64_to_host(file->csize);
 	    cabno = le16_to_host(file->datafile_id);
 
1b98609f
 	    switch(le16_to_host(file->flags)) {
1d7cebd8
 	    case 0:
 		/* FIXMEISHIELD: for FS scan ? */
1b98609f
 		cli_dbgmsg("is_parse_hdr: skipped external file:%s\\%s (size: %llu csize: %llu md5:%s)\n",
1d7cebd8
 			   dir_name,
 			   file_name,
4cda88cc
 			   (long long)file_size, (long long)file_csize, hash);
1d7cebd8
 		break;
 	    case 4:
1b98609f
 		cli_dbgmsg("is_parse_hdr: file %s\\%s (size: %llu csize: %llu md5:%s offset:%llx (data%u.cab) 13:%x 14:%x 15:%x)\n",
1d7cebd8
 			   dir_name,
 			   file_name,
4cda88cc
 			   (long long)file_size, (long long)file_csize, hash, (long long)file_stream_off,
4f841728
 			   cabno, file->unk13,  file->unk14,  file->unk15);
1d7cebd8
 		if(file->flag_has_dup & 1)
1b98609f
 		    cli_dbgmsg("is_parse_hdr: not scanned (dup)\n");
1d7cebd8
 		else {
1b98609f
 		    if(file_size) {
c19515d5
 			unsigned int j;
1b98609f
 			int cabret = CL_CLEAN;
 
 			if(ctx->engine->maxfilesize && file_csize > ctx->engine->maxfilesize) {
e78b5186
 			    cli_dbgmsg("is_parse_hdr: skipping file due to size limits (%lu vs %lu)\n", (unsigned long int) file_csize, (unsigned long int) ctx->engine->maxfilesize);
1b98609f
 			    break;
 			}
 
c19515d5
 			for(j=0; j<c->cabcnt && c->cabs[j].cabno != cabno; j++) {}
 			if(j != c->cabcnt) {
1b98609f
  			    if(CLI_ISCONTAINED(c->cabs[j].off, c->cabs[j].sz, file_stream_off + c->cabs[j].off, file_csize)) {
 				scanned++;
 				if (ctx->engine->maxfiles && scanned >= ctx->engine->maxfiles) {
 				    cli_dbgmsg("is_parse_hdr: File limit reached (max: %u)\n", ctx->engine->maxfiles);
9a4da6af
 				    if(file_name != emptyname)
 					fmap_unneed_ptr(map, (void *)file_name, strlen(file_name)+1);
 				    if(dir_name != emptyname)
 					fmap_unneed_ptr(map, (void *)dir_name, strlen(dir_name)+1);
1b98609f
 				    return CL_EMAXFILES;
 				}
430a6261
 				cabret = is_extract_cab(ctx, file_stream_off + c->cabs[j].off, file_size, file_csize);
1b98609f
 			    } else {
 				ret = CL_CLEAN;
c19515d5
  				cli_dbgmsg("is_parse_hdr: stream out of file\n");
1b98609f
 			    }
c19515d5
 			} else {
1b98609f
 			    ret = CL_CLEAN;
c19515d5
 			    cli_dbgmsg("is_parse_hdr: data%u.cab not available\n", cabno);
4f841728
 			}
1b98609f
 			if(cabret == CL_BREAK) {
 			    ret = CL_CLEAN;
 			    cabret = CL_CLEAN;
 			}
 			if(cabret != CL_CLEAN) {
9a4da6af
 			    if(file_name != emptyname)
 				fmap_unneed_ptr(map, (void *)file_name, strlen(file_name)+1);
 			    if(dir_name != emptyname)
 				fmap_unneed_ptr(map, (void *)dir_name, strlen(dir_name)+1);
1b98609f
 			    return cabret;
f0b36be4
 			}
c19515d5
 		    } else {
 			cli_dbgmsg("is_parse_hdr: skipped empty file\n");
1d7cebd8
 		    }
 		}
 		break;
 	    default:
 		cli_dbgmsg("is_parse_hdr: skipped unknown file entry %u\n", i);
 	    }
1ae65c29
 	    if(file_name != emptyname)
9a4da6af
 		fmap_unneed_ptr(map, (void *)file_name, strlen(file_name)+1);
1ae65c29
 	    if(dir_name != emptyname)
9a4da6af
 		fmap_unneed_ptr(map, (void *)dir_name, strlen(dir_name)+1);
f52b5724
 	    fmap_unneed_ptr(map, file, sizeof(*file));
1b98609f
 	} else {
 	    ret = CL_CLEAN;
 	    cli_dbgmsg("is_parse_hdr: FILEITEM out of bounds\n");
 	}
 	off += sizeof(*file);
1d7cebd8
     }
1b98609f
     return ret;
1d7cebd8
 }
 
 
 static void md5str(uint8_t *sum) {
     int i;
     for(i=15; i>=0; i--) {
 	uint8_t lo = (sum[i] & 0xf), hi = (sum[i] >> 4);
 	lo += '0' + (lo > 9) * '\'';
 	hi += '0' + (hi > 9) * '\'';
 	sum[i*2+1] = lo;
 	sum[i*2] = hi;
     }
     sum[32] = '\0';
 }
f0b36be4
 
 
 #define IS_CABBUFSZ 65536
 
430a6261
 static int is_extract_cab(cli_ctx *ctx, uint64_t off, uint64_t size, uint64_t csize) {
f304dc68
     const uint8_t *inbuf;
     uint8_t *outbuf;
f0b36be4
     char *tempfile;
     int ofd, ret = CL_CLEAN;
     z_stream z;
     uint64_t outsz = 0;
     int success = 0;
49cc1e3c
     fmap_t *map = *ctx->fmap;
f0b36be4
 
241e7eb1
     if(!(outbuf = cli_malloc(IS_CABBUFSZ))) {
         cli_errmsg("is_extract_cab: Unable to allocate memory for outbuf\n");
         return CL_EMEM;
     }
430a6261
 
9c3c24ca
     if(!(tempfile = cli_gentemp(ctx->engine->tmpdir))) {
 	free(outbuf);
13529b8e
 	return CL_EMEM;
9c3c24ca
     }
f0b36be4
     if((ofd = open(tempfile, O_RDWR|O_CREAT|O_TRUNC|O_BINARY, S_IRUSR|S_IWUSR)) < 0) {
 	cli_errmsg("is_extract_cab: failed to create file %s\n", tempfile);
 	free(tempfile);
9c3c24ca
 	free(outbuf);
f0b36be4
 	return CL_ECREAT;
     }
 
     while(csize) {
 	uint16_t chunksz;
 	success = 0;
 	if(csize<2) {
1b98609f
 	    cli_dbgmsg("is_extract_cab: no room for chunk size\n");
f0b36be4
 	    break;
 	}
 	csize -= 2;
430a6261
 	if(!(inbuf = fmap_need_off_once(map, off, 2))) {
f0b36be4
 	    cli_dbgmsg("is_extract_cab: short read for chunk size\n");
 	    break;
 	}
430a6261
 	off += 2;
 	chunksz = inbuf[0] | (inbuf[1] << 8);
f0b36be4
 	if(!chunksz) {
 	    cli_dbgmsg("is_extract_cab: zero sized chunk\n");
 	    continue;
 	}
 	if(csize < chunksz) {
 	    cli_dbgmsg("is_extract_cab: chunk is bigger than csize\n");
 	    break;
 	}
 	csize -= chunksz;
430a6261
 	if(!(inbuf = fmap_need_off_once(map, off, chunksz))) {
f0b36be4
 	    cli_dbgmsg("is_extract_cab: short read for chunk\n");
 	    break;
 	}
430a6261
 	off += chunksz;
f0b36be4
 	memset(&z, 0, sizeof(z));
 	inflateInit2(&z, -MAX_WBITS);
 	z.next_in = (uint8_t *)inbuf;
 	z.avail_in = chunksz;
 	while(1) {
 	    int zret;
 	    z.next_out = outbuf;
 	    z.avail_out = IS_CABBUFSZ;
 	    zret = inflate(&z, 0);
 	    if(zret == Z_OK || zret == Z_STREAM_END || zret == Z_BUF_ERROR) {
 		unsigned int umpd = IS_CABBUFSZ - z.avail_out;
 		if(cli_writen(ofd, outbuf, umpd) < (ssize_t)umpd)
 		    break;
 		outsz += umpd;
 		if(zret == Z_STREAM_END || z.avail_out == IS_CABBUFSZ /* FIXMEISHIELD: is the latter ok? */) {
 		    success = 1;
 		    break;
 		}
1b98609f
 		if(ctx->engine->maxfilesize && z.total_out > ctx->engine->maxfilesize) {
e78b5186
 		    cli_dbgmsg("ishield_extract_cab: trimming output file due to size limits (%lu vs %lu)\n", z.total_out, (unsigned long int) ctx->engine->maxfilesize);
1b98609f
 		    success = 1;
 		    outsz = size;
 		    break;
 		}
f0b36be4
 		continue;
 	    }
 	    cli_dbgmsg("is_extract_cab: file decompression failed with %d\n", zret);
 	    break;
 	}
 	inflateEnd(&z);
 	if(!success) break;
     }
9c3c24ca
     free(outbuf);
f0b36be4
     if(success) {
 	if (outsz != size)
4cda88cc
 	    cli_dbgmsg("is_extract_cab: extracted %llu bytes to %s, expected %llu, scanning anyway.\n", (long long)outsz, tempfile, (long long)size);
f0b36be4
 	else
 	    cli_dbgmsg("is_extract_cab: extracted to %s\n", tempfile);
96914546
 	if (lseek(ofd, 0, SEEK_SET) == -1)
         cli_dbgmsg("is_extract_cab: call to lseek() failed\n");
d39cb658
 	ret = cli_magic_scandesc(ofd, tempfile, ctx);
f0b36be4
     }
 
     close(ofd);
     if(!ctx->engine->keeptmp)
 	if(cli_unlink(tempfile)) ret = CL_EUNLINK;
     free(tempfile);
1b98609f
     return success ? ret : CL_BREAK;
f0b36be4
 }