/src/clamav/libclamav/ishield.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (C) 2013-2024 Cisco Systems, Inc. and/or its affiliates. All rights reserved. |
3 | | * Copyright (C) 2009-2013 Sourcefire, Inc. |
4 | | * |
5 | | * Authors: aCaB <acab@clamav.net> |
6 | | * |
7 | | * This program is free software; you can redistribute it and/or modify |
8 | | * it under the terms of the GNU General Public License version 2 as |
9 | | * published by the Free Software Foundation. |
10 | | * |
11 | | * This program is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | * GNU General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU General Public License |
17 | | * along with this program; if not, write to the Free Software |
18 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
19 | | * MA 02110-1301, USA. |
20 | | */ |
21 | | |
22 | | /* common routines to deal with installshield archives and installers */ |
23 | | |
24 | | #if HAVE_CONFIG_H |
25 | | #include "clamav-config.h" |
26 | | #endif |
27 | | |
28 | | #include <sys/types.h> |
29 | | #include <sys/stat.h> |
30 | | #include <fcntl.h> |
31 | | #ifdef HAVE_UNISTD_H |
32 | | #include <unistd.h> |
33 | | #endif |
34 | | #if HAVE_STRING_H |
35 | | #include <string.h> |
36 | | #endif |
37 | | #include <limits.h> |
38 | | #if HAVE_STRINGS_H |
39 | | #include <strings.h> |
40 | | #endif |
41 | | #if defined(HAVE_MMAP) && defined(HAVE_SYS_MMAN_H) |
42 | | #include <sys/mman.h> |
43 | | #endif |
44 | | #include <zlib.h> |
45 | | |
46 | | #include "clamav.h" |
47 | | #include "scanners.h" |
48 | | #include "others.h" |
49 | | #include "fmap.h" |
50 | | #include "ishield.h" |
51 | | |
52 | | #ifndef LONG_MAX |
53 | | #define LONG_MAX ((-1UL) >> 1) |
54 | | #endif |
55 | | |
56 | | #ifndef HAVE_ATTRIB_PACKED |
57 | | #define __attribute__(x) |
58 | | #endif |
59 | | #ifdef HAVE_PRAGMA_PACK |
60 | | #pragma pack(1) |
61 | | #endif |
62 | | #ifdef HAVE_PRAGMA_PACK_HPPA |
63 | | #pragma pack 1 |
64 | | #endif |
65 | | |
66 | | /* PACKED things go here */ |
67 | | |
68 | | struct IS_HDR { |
69 | | uint32_t magic; |
70 | | uint32_t unk1; /* version ??? */ |
71 | | uint32_t unk2; /* ??? */ |
72 | | uint32_t data_off; |
73 | | uint32_t data_sz; /* ??? */ |
74 | | } __attribute__((packed)); |
75 | | |
76 | | struct IS_FB { |
77 | | char fname[0x104]; /* MAX_PATH */ |
78 | | uint32_t unk1; /* 6 */ |
79 | | uint32_t unk2; |
80 | | uint64_t csize; |
81 | | uint32_t unk3; |
82 | | uint32_t unk4; /* 1 */ |
83 | | uint32_t unk5; |
84 | | uint32_t unk6; |
85 | | uint32_t unk7; |
86 | | uint32_t unk8; |
87 | | uint32_t unk9; |
88 | | uint32_t unk10; |
89 | | uint32_t unk11; |
90 | | } __attribute__((packed)); |
91 | | |
92 | | struct IS_COMPONENT { |
93 | | uint32_t str_name_off; |
94 | | uint32_t unk_str1_off; |
95 | | uint32_t unk_str2_off; |
96 | | uint16_t unk_flags; |
97 | | uint32_t unk_str3_off; |
98 | | uint32_t unk_str4_off; |
99 | | uint16_t ordinal_id; |
100 | | uint32_t str_shortname_off; |
101 | | uint32_t unk_str6_off; |
102 | | uint32_t unk_str7_off; |
103 | | uint32_t unk_str8_off; |
104 | | char guid1[16]; |
105 | | char guid2[16]; |
106 | | uint32_t unk_str9_off; |
107 | | char unk1[3]; |
108 | | uint16_t unk_flags2; |
109 | | uint32_t unk3[5]; |
110 | | uint32_t unk_str10_off; |
111 | | uint32_t unk4[4]; |
112 | | uint16_t unk5; |
113 | | uint16_t sub_comp_cnt; |
114 | | uint32_t sub_comp_offs_array; |
115 | | uint32_t next_comp_off; |
116 | | uint32_t unk_str11_off; |
117 | | uint32_t unk_str12_off; |
118 | | uint32_t unk_str13_off; |
119 | | uint32_t unk_str14_off; |
120 | | uint32_t str_next1_off; |
121 | | uint32_t str_next2_off; |
122 | | } __attribute__((packed)); |
123 | | |
124 | | struct IS_INSTTYPEHDR { |
125 | | uint32_t unk1; |
126 | | uint32_t cnt; |
127 | | uint32_t off; |
128 | | } __attribute__((packed)); |
129 | | |
130 | | struct IS_INSTTYPEITEM { |
131 | | uint32_t str_name1_off; |
132 | | uint32_t str_name2_off; |
133 | | uint32_t str_name3_off; |
134 | | uint32_t cnt; |
135 | | uint32_t off; |
136 | | } __attribute__((packed)); |
137 | | |
138 | | struct IS_OBJECTS { |
139 | | /* 200 */ uint32_t strings_off; |
140 | | /* 204 */ uint32_t zero1; |
141 | | /* 208 */ uint32_t comps_off; |
142 | | /* 20c */ uint32_t dirs_off; |
143 | | /* 210 */ uint32_t zero2; |
144 | | /* 214 */ uint32_t unk1, unk2; /* 0x4a636 304694 uguali - NOT AN OFFSET! */ |
145 | | /* 21c */ uint32_t dirs_cnt; |
146 | | /* 220 */ uint32_t zero3; |
147 | | /* 224 */ uint32_t dirs_sz; /* dirs_cnt * 4 */ |
148 | | /* 228 */ uint32_t files_cnt; |
149 | | /* 22c */ uint32_t dir_sz2; /* same as dirs_sz ?? */ |
150 | | /* 230 */ uint16_t unk5; /* 1 - comp count ?? */ |
151 | | /* 232 */ uint32_t insttype_off; |
152 | | /* 234 */ uint16_t zero4; |
153 | | /* 238 */ uint32_t zero5; |
154 | | /* 23c */ uint32_t unk7; /* 0xd0 - 208 */ |
155 | | /* 240 */ uint16_t unk8; |
156 | | /* 242 */ uint32_t unk9; |
157 | | /* 246 */ uint32_t unk10; |
158 | | } __attribute__((packed)); |
159 | | |
160 | | struct IS_FILEITEM { |
161 | | uint16_t flags; /* 0 = EXTERNAL | 4 = INTERNAL | 8 = NAME_fuckup_rare | c = name_fuckup_common */ |
162 | | uint64_t size; |
163 | | uint64_t csize; |
164 | | uint64_t stream_off; |
165 | | uint8_t md5[16]; |
166 | | uint64_t versioninfo_id; |
167 | | uint32_t zero1; |
168 | | uint32_t zero2; |
169 | | uint32_t str_name_off; |
170 | | uint16_t dir_id; |
171 | | uint32_t unk13; /* 0, 20, 21 ??? */ |
172 | | uint32_t unk14; /* timestamp ??? */ |
173 | | uint32_t unk15; /* begins with 1 then 2 but not the cab# ??? */ |
174 | | uint32_t prev_dup_id; /* msvcrt #38(0, 97, 2) #97(38, 1181, 3) ... , 0, 1) */ |
175 | | uint32_t next_dup_id; |
176 | | uint8_t flag_has_dup; /* HAS_NEXT = 2 | HAS_BOTH = 3 | HAS_PREV = 1 */ |
177 | | uint16_t datafile_id; |
178 | | } __attribute__((packed)); |
179 | | |
180 | | #ifdef HAVE_PRAGMA_PACK |
181 | | #pragma pack() |
182 | | #endif |
183 | | #ifdef HAVE_PRAGMA_PACK_HPPA |
184 | | #pragma pack |
185 | | #endif |
186 | | |
187 | | static cl_error_t is_dump_and_scan(cli_ctx *ctx, off_t off, size_t fsize); |
188 | | static const uint8_t skey[] = {0xec, 0xca, 0x79, 0xf8}; /* ~0x13, ~0x35, ~0x86, ~0x07 */ |
189 | | |
190 | | /* Extracts the content of MSI based IS */ |
191 | | cl_error_t cli_scanishield_msi(cli_ctx *ctx, off_t off) |
192 | 0 | { |
193 | 0 | cl_error_t ret; |
194 | 0 | const uint8_t *buf; |
195 | 0 | unsigned int fcount, scanned = 0; |
196 | 0 | fmap_t *map = ctx->fmap; |
197 | |
|
198 | 0 | cli_dbgmsg("in ishield-msi\n"); |
199 | 0 | if (!(buf = fmap_need_off_once(map, off, 0x20))) { |
200 | 0 | cli_dbgmsg("ishield-msi: short read for header\n"); |
201 | 0 | return CL_SUCCESS; |
202 | 0 | } |
203 | | |
204 | 0 | off += 0x20; |
205 | 0 | 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)) { |
206 | 0 | return CL_SUCCESS; |
207 | 0 | } |
208 | | |
209 | 0 | if (!(fcount = cli_readint32(buf))) { |
210 | 0 | cli_dbgmsg("ishield-msi: no files?\n"); |
211 | 0 | return CL_SUCCESS; |
212 | 0 | } |
213 | | |
214 | 0 | while (fcount--) { |
215 | 0 | struct IS_FB fb; |
216 | 0 | uint8_t obuf[BUFSIZ], *key = (uint8_t *)&fb.fname; |
217 | 0 | char *filename = NULL; |
218 | 0 | char *tempfile; |
219 | 0 | unsigned int i, lameidx = 0, keylen; |
220 | 0 | int ofd; |
221 | 0 | uint64_t csize; |
222 | 0 | z_stream z; |
223 | |
|
224 | 0 | if (fmap_readn(map, &fb, off, sizeof(fb)) != sizeof(fb)) { |
225 | 0 | cli_dbgmsg("ishield-msi: short read for fileblock\n"); |
226 | 0 | return CL_SUCCESS; |
227 | 0 | } |
228 | | |
229 | 0 | off += sizeof(fb); |
230 | 0 | fb.fname[sizeof(fb.fname) - 1] = '\0'; |
231 | |
|
232 | 0 | csize = le64_to_host(fb.csize); |
233 | 0 | if (!CLI_ISCONTAINED_0_TO(map->len, off, csize)) { |
234 | 0 | cli_dbgmsg("ishield-msi: next stream is out of file, giving up\n"); |
235 | 0 | return CL_SUCCESS; |
236 | 0 | } |
237 | | |
238 | 0 | if (ctx->engine->maxfilesize && csize > ctx->engine->maxfilesize) { |
239 | 0 | cli_dbgmsg("ishield-msi: skipping stream due to size limits (%lu vs %lu)\n", (unsigned long int)csize, (unsigned long int)ctx->engine->maxfilesize); |
240 | 0 | off += csize; |
241 | 0 | continue; |
242 | 0 | } |
243 | | |
244 | 0 | keylen = strlen((const char *)key); |
245 | 0 | if (!keylen) { |
246 | 0 | return CL_SUCCESS; |
247 | 0 | } |
248 | | |
249 | 0 | filename = cli_safer_strdup((const char *)key); |
250 | | |
251 | | /* FIXMEISHIELD: cleanup the spam below */ |
252 | 0 | 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); |
253 | 0 | if (!(tempfile = cli_gentemp(ctx->sub_tmpdir))) { |
254 | 0 | if (NULL != filename) { |
255 | 0 | free(filename); |
256 | 0 | } |
257 | 0 | return CL_EMEM; |
258 | 0 | } |
259 | | |
260 | 0 | if ((ofd = open(tempfile, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR)) < 0) { |
261 | 0 | cli_dbgmsg("ishield-msi: failed to create file %s\n", tempfile); |
262 | 0 | free(tempfile); |
263 | 0 | if (NULL != filename) { |
264 | 0 | free(filename); |
265 | 0 | } |
266 | 0 | return CL_ECREAT; |
267 | 0 | } |
268 | | |
269 | 0 | for (i = 0; i < keylen; i++) { |
270 | 0 | key[i] ^= skey[i & 3]; |
271 | 0 | } |
272 | |
|
273 | 0 | memset(&z, 0, sizeof(z)); |
274 | 0 | inflateInit(&z); |
275 | |
|
276 | 0 | ret = CL_SUCCESS; |
277 | |
|
278 | 0 | while (csize) { |
279 | 0 | uint8_t buf2[BUFSIZ]; |
280 | 0 | z.avail_in = MIN(csize, sizeof(buf2)); |
281 | 0 | if (fmap_readn(map, buf2, off, z.avail_in) != z.avail_in) { |
282 | 0 | cli_dbgmsg("ishield-msi: premature EOS or read fail\n"); |
283 | 0 | break; |
284 | 0 | } |
285 | 0 | off += z.avail_in; |
286 | 0 | for (i = 0; i < z.avail_in; i++, lameidx++) { |
287 | 0 | uint8_t c = buf2[i]; |
288 | 0 | c = (c >> 4) | (c << 4); |
289 | 0 | c ^= key[(lameidx & 0x3ff) % keylen]; |
290 | 0 | buf2[i] = c; |
291 | 0 | } |
292 | 0 | csize -= z.avail_in; |
293 | 0 | z.next_in = buf2; |
294 | 0 | do { |
295 | 0 | int inf; |
296 | 0 | z.avail_out = sizeof(obuf); |
297 | 0 | z.next_out = obuf; |
298 | 0 | inf = inflate(&z, 0); |
299 | 0 | if (inf != Z_OK && inf != Z_STREAM_END && inf != Z_BUF_ERROR) { |
300 | 0 | cli_dbgmsg("ishield-msi: bad stream\n"); |
301 | 0 | csize = 0; |
302 | 0 | off += csize; |
303 | 0 | break; |
304 | 0 | } |
305 | 0 | if (cli_writen(ofd, obuf, sizeof(obuf) - z.avail_out) == (size_t)-1) { |
306 | 0 | ret = CL_EWRITE; |
307 | 0 | csize = 0; |
308 | 0 | break; |
309 | 0 | } |
310 | 0 | if (ctx->engine->maxfilesize && z.total_out > ctx->engine->maxfilesize) { |
311 | 0 | cli_dbgmsg("ishield-msi: trimming output file due to size limits (%lu vs %lu)\n", z.total_out, (unsigned long int)ctx->engine->maxfilesize); |
312 | 0 | off += csize; |
313 | 0 | csize = 0; |
314 | 0 | break; |
315 | 0 | } |
316 | 0 | } while (!z.avail_out); |
317 | 0 | } |
318 | |
|
319 | 0 | inflateEnd(&z); |
320 | |
|
321 | 0 | if (ret == CL_SUCCESS) { |
322 | 0 | cli_dbgmsg("ishield-msi: extracted to %s\n", tempfile); |
323 | |
|
324 | 0 | if (lseek(ofd, 0, SEEK_SET) == -1) { |
325 | 0 | cli_dbgmsg("ishield-msi: call to lseek() failed\n"); |
326 | 0 | ret = CL_ESEEK; |
327 | 0 | } |
328 | 0 | ret = cli_magic_scan_desc(ofd, tempfile, ctx, filename, LAYER_ATTRIBUTES_NONE); |
329 | 0 | } |
330 | 0 | close(ofd); |
331 | |
|
332 | 0 | if (!ctx->engine->keeptmp) { |
333 | 0 | if (cli_unlink(tempfile)) { |
334 | 0 | ret = CL_EUNLINK; |
335 | 0 | } |
336 | 0 | } |
337 | 0 | free(tempfile); |
338 | |
|
339 | 0 | if (NULL != filename) { |
340 | 0 | free(filename); |
341 | 0 | } |
342 | |
|
343 | 0 | if (ret != CL_SUCCESS) { |
344 | 0 | return ret; |
345 | 0 | } |
346 | | |
347 | 0 | scanned++; |
348 | 0 | if (ctx->engine->maxfiles && scanned >= ctx->engine->maxfiles) { |
349 | 0 | cli_dbgmsg("ishield-msi: File limit reached (max: %u)\n", ctx->engine->maxfiles); |
350 | 0 | return CL_EMAXFILES; |
351 | 0 | } |
352 | 0 | } |
353 | 0 | return CL_SUCCESS; |
354 | 0 | } |
355 | | |
356 | | struct IS_CABSTUFF { |
357 | | struct CABARRAY { |
358 | | unsigned int cabno; |
359 | | off_t off; |
360 | | size_t sz; |
361 | | } *cabs; |
362 | | off_t hdr; |
363 | | size_t hdrsz; |
364 | | unsigned int cabcnt; |
365 | | }; |
366 | | |
367 | | static void md5str(uint8_t *sum); |
368 | | static cl_error_t is_parse_hdr(cli_ctx *ctx, struct IS_CABSTUFF *c); |
369 | | static cl_error_t is_extract_cab(cli_ctx *ctx, uint64_t off, uint64_t size, uint64_t csize); |
370 | | |
371 | | /* Extract the content of older (non-MSI) IS */ |
372 | | cl_error_t cli_scanishield(cli_ctx *ctx, off_t off, size_t sz) |
373 | 12.6k | { |
374 | 12.6k | cl_error_t ret = CL_SUCCESS; |
375 | 12.6k | const char *fname, *path, *version, *strsz, *data; |
376 | 12.6k | char *eostr; |
377 | 12.6k | long fsize; |
378 | 12.6k | off_t coff = off; |
379 | 12.6k | struct IS_CABSTUFF c = {NULL, -1, 0, 0}; |
380 | 12.6k | fmap_t *map = ctx->fmap; |
381 | 12.6k | unsigned fc = 0; |
382 | | |
383 | 12.8k | while (ret == CL_SUCCESS) { |
384 | 12.7k | fname = fmap_need_offstr(map, coff, 2048); |
385 | 12.7k | if (!fname) break; |
386 | 9.02k | coff += strlen(fname) + 1; |
387 | | |
388 | 9.02k | path = fmap_need_offstr(map, coff, 2048); |
389 | 9.02k | if (!path) break; |
390 | 8.93k | coff += strlen(path) + 1; |
391 | | |
392 | 8.93k | version = fmap_need_offstr(map, coff, 2048); |
393 | 8.93k | if (!version) break; |
394 | 8.92k | coff += strlen(version) + 1; |
395 | | |
396 | 8.92k | strsz = fmap_need_offstr(map, coff, 2048); |
397 | 8.92k | if (!strsz) break; |
398 | 8.86k | coff += strlen(strsz) + 1; |
399 | | |
400 | 8.86k | data = &strsz[strlen(strsz) + 1]; |
401 | | |
402 | 8.86k | fsize = strtol(strsz, &eostr, 10); |
403 | 8.86k | if (fsize < 0 || fsize == LONG_MAX || |
404 | 8.86k | !*strsz || !eostr || eostr == strsz || *eostr || |
405 | 8.86k | (unsigned long)fsize >= sz || |
406 | 8.86k | (size_t)(data - fname) >= sz - fsize) break; |
407 | | |
408 | 189 | cli_dbgmsg("ishield: @%lx found file %s (%s) - version %s - size %lu\n", (unsigned long int)coff, fname, path, version, (unsigned long int)fsize); |
409 | 189 | if (CL_SUCCESS != cli_matchmeta(ctx, fname, fsize, fsize, 0, fc++, 0)) { |
410 | 0 | ret = CL_VIRUS; |
411 | 0 | break; |
412 | 0 | } |
413 | | |
414 | 189 | sz -= (data - fname) + fsize; |
415 | | |
416 | 189 | if (!strncasecmp(fname, "data", 4)) { |
417 | 0 | long cabno; |
418 | 0 | if (!strcasecmp(fname + 4, "1.hdr")) { |
419 | 0 | if (c.hdr == -1) { |
420 | 0 | cli_dbgmsg("ishield: added data1.hdr to array\n"); |
421 | 0 | c.hdr = coff; |
422 | 0 | c.hdrsz = fsize; |
423 | 0 | coff += fsize; |
424 | 0 | continue; |
425 | 0 | } |
426 | 0 | cli_warnmsg("ishield: got multiple header files\n"); |
427 | 0 | } |
428 | 0 | cabno = strtol(fname + 4, &eostr, 10); |
429 | 0 | if (cabno > 0 && cabno < 65536 && fname[4] && eostr && eostr != &fname[4] && !strcasecmp(eostr, ".cab")) { |
430 | 0 | unsigned int i; |
431 | 0 | for (i = 0; i < c.cabcnt && i != c.cabs[i].cabno; i++) { |
432 | 0 | } |
433 | 0 | if (i == c.cabcnt) { |
434 | 0 | c.cabcnt++; |
435 | 0 | if (!(c.cabs = cli_max_realloc_or_free(c.cabs, sizeof(struct CABARRAY) * c.cabcnt))) { |
436 | 0 | ret = CL_EMEM; |
437 | 0 | break; |
438 | 0 | } |
439 | 0 | cli_dbgmsg("ishield: added data%lu.cab to array\n", cabno); |
440 | 0 | c.cabs[i].cabno = cabno; |
441 | 0 | c.cabs[i].off = coff; |
442 | 0 | c.cabs[i].sz = fsize; |
443 | 0 | coff += fsize; |
444 | 0 | continue; |
445 | 0 | } |
446 | 0 | cli_warnmsg("ishield: got multiple data%lu.cab files\n", cabno); |
447 | 0 | } |
448 | 0 | } |
449 | | |
450 | 189 | fmap_unneed_ptr(map, fname, data - fname); |
451 | 189 | ret = is_dump_and_scan(ctx, coff, fsize); |
452 | 189 | coff += fsize; |
453 | 189 | } |
454 | | |
455 | 12.6k | if ((ret == CL_SUCCESS) && |
456 | 12.6k | (c.cabcnt || c.hdr != -1)) { |
457 | |
|
458 | 0 | if (CL_SUCCESS == (ret = is_parse_hdr(ctx, &c))) { |
459 | 0 | unsigned int i; |
460 | 0 | if (c.hdr != -1) { |
461 | 0 | cli_dbgmsg("ishield: scanning data1.hdr\n"); |
462 | 0 | ret = is_dump_and_scan(ctx, c.hdr, c.hdrsz); |
463 | 0 | } |
464 | 0 | for (i = 0; i < c.cabcnt && ret == CL_SUCCESS; i++) { |
465 | 0 | cli_dbgmsg("ishield: scanning data%u.cab\n", c.cabs[i].cabno); |
466 | 0 | ret = is_dump_and_scan(ctx, c.cabs[i].off, c.cabs[i].sz); |
467 | 0 | } |
468 | |
|
469 | 0 | } else if (ret == CL_BREAK) { |
470 | 0 | ret = CL_SUCCESS; |
471 | 0 | } |
472 | 0 | } |
473 | | |
474 | 12.6k | if (c.cabs) { |
475 | 0 | free(c.cabs); |
476 | 0 | } |
477 | | |
478 | 12.6k | return ret; |
479 | 12.6k | } |
480 | | |
481 | | /* Utility func to scan a fd @ a given offset and size */ |
482 | | static cl_error_t is_dump_and_scan(cli_ctx *ctx, off_t off, size_t fsize) |
483 | 189 | { |
484 | 189 | char *fname; |
485 | 189 | const char *buf; |
486 | 189 | cl_error_t ofd, ret = CL_SUCCESS; |
487 | 189 | fmap_t *map = ctx->fmap; |
488 | | |
489 | 189 | if (!fsize) { |
490 | 9 | cli_dbgmsg("ishield: skipping empty file\n"); |
491 | 9 | return CL_SUCCESS; |
492 | 9 | } |
493 | | |
494 | 180 | if (!(fname = cli_gentemp(ctx->sub_tmpdir))) { |
495 | 0 | return CL_EMEM; |
496 | 0 | } |
497 | | |
498 | 180 | if ((ofd = open(fname, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR)) < 0) { |
499 | 0 | cli_errmsg("ishield: failed to create file %s\n", fname); |
500 | 0 | free(fname); |
501 | 0 | return CL_ECREAT; |
502 | 0 | } |
503 | | |
504 | 360 | while (fsize) { |
505 | 180 | size_t rd = MIN(fsize, map->pgsz); |
506 | 180 | if (!(buf = fmap_need_off_once(map, off, rd))) { |
507 | 0 | cli_dbgmsg("ishield: read error\n"); |
508 | 0 | ret = CL_EREAD; |
509 | 0 | break; |
510 | 0 | } |
511 | 180 | if (cli_writen(ofd, buf, rd) != rd) { |
512 | 0 | ret = CL_EWRITE; |
513 | 0 | break; |
514 | 0 | } |
515 | 180 | fsize -= rd; |
516 | 180 | off += rd; |
517 | 180 | } |
518 | | |
519 | 180 | if (!fsize) { |
520 | 180 | cli_dbgmsg("ishield: extracted to %s\n", fname); |
521 | 180 | if (lseek(ofd, 0, SEEK_SET) == -1) { |
522 | 0 | cli_dbgmsg("ishield: call to lseek() failed\n"); |
523 | 0 | ret = CL_ESEEK; |
524 | 0 | } |
525 | 180 | ret = cli_magic_scan_desc(ofd, fname, ctx, NULL, LAYER_ATTRIBUTES_NONE); |
526 | 180 | } |
527 | | |
528 | 180 | close(ofd); |
529 | | |
530 | 180 | if (!ctx->engine->keeptmp) { |
531 | 180 | if (cli_unlink(fname)) { |
532 | 0 | ret = CL_EUNLINK; |
533 | 0 | } |
534 | 180 | } |
535 | | |
536 | 180 | free(fname); |
537 | 180 | return ret; |
538 | 180 | } |
539 | | |
540 | | /* Process data1.hdr and extracts all the available files from dataX.cab */ |
541 | | static cl_error_t is_parse_hdr(cli_ctx *ctx, struct IS_CABSTUFF *c) |
542 | 0 | { |
543 | 0 | uint32_t h1_data_off, objs_files_cnt, objs_dirs_off; |
544 | 0 | unsigned int off, i, scanned = 0; |
545 | 0 | int ret = CL_BREAK; |
546 | 0 | char hash[33], *hdr; |
547 | 0 | fmap_t *map = ctx->fmap; |
548 | |
|
549 | 0 | const struct IS_HDR *h1; |
550 | 0 | struct IS_OBJECTS *objs; |
551 | | /* struct IS_INSTTYPEHDR *typehdr; -- UNUSED */ |
552 | |
|
553 | 0 | if (!c->hdr || !c->hdrsz || !c->cabcnt) { |
554 | 0 | cli_dbgmsg("is_parse_hdr: inconsistent hdr, maybe a false match\n"); |
555 | 0 | return CL_SUCCESS; |
556 | 0 | } |
557 | | |
558 | 0 | if (!(h1 = fmap_need_off(map, c->hdr, c->hdrsz))) { |
559 | 0 | cli_dbgmsg("is_parse_hdr: not enough room for H1\n"); |
560 | 0 | return CL_SUCCESS; |
561 | 0 | } |
562 | | |
563 | 0 | hdr = (char *)h1; |
564 | 0 | h1_data_off = le32_to_host(h1->data_off); |
565 | 0 | objs = (struct IS_OBJECTS *)fmap_need_ptr(map, hdr + h1_data_off, sizeof(*objs)); |
566 | 0 | if (!objs) { |
567 | 0 | cli_dbgmsg("is_parse_hdr: not enough room for OBJECTS\n"); |
568 | 0 | return CL_SUCCESS; |
569 | 0 | } |
570 | | |
571 | 0 | cli_dbgmsg("is_parse_hdr: magic %x, unk1 %x, unk2 %x, data_off %x, data_sz %x\n", |
572 | 0 | h1->magic, h1->unk1, h1->unk2, h1_data_off, h1->data_sz); |
573 | 0 | if (le32_to_host(h1->magic) != 0x28635349) { |
574 | 0 | cli_dbgmsg("is_parse_hdr: bad magic. wrong version?\n"); |
575 | 0 | return CL_SUCCESS; |
576 | 0 | } |
577 | | |
578 | 0 | fmap_unneed_ptr(map, h1, sizeof(*h1)); |
579 | | |
580 | | /* cli_errmsg("COMPONENTS\n"); */ |
581 | | /* off = le32_to_host(objs->comps_off) + h1_data_off; */ |
582 | | /* for(i=1; ; i++) { */ |
583 | | /* struct IS_COMPONENT *cmp = (struct IS_COMPONENT *)(hdr + off); */ |
584 | | /* if(!CLI_ISCONTAINED(hdr, c->hdrsz, ((char *)cmp), sizeof(*cmp))) { */ |
585 | | /* cli_dbgmsg("is_extract: not enough room for COMPONENT\n"); */ |
586 | | /* free(hdr); */ |
587 | | /* return CL_SUCCESS; */ |
588 | | /* } */ |
589 | | /* cli_errmsg("%06u\t%s\n", i, &hdr[le32_to_host(cmp->str_name_off) + h1_data_off]); */ |
590 | | /* spam_strarray(hdr, h1_data_off + cmp->sub_comp_offs_array, h1_data_off, cmp->sub_comp_cnt); */ |
591 | | /* if(!cmp->next_comp_off) break; */ |
592 | | /* off = le32_to_host(cmp->next_comp_off) + h1_data_off; */ |
593 | | /* } */ |
594 | | |
595 | | /* cli_errmsg("DIRECTORIES (%u)", le32_to_host(objs->dirs_cnt)); */ |
596 | 0 | objs_dirs_off = le32_to_host(objs->dirs_off); |
597 | | /* spam_strarray(hdr, h1_data_off + objs_dirs_off, h1_data_off + objs_dirs_off, objs->dirs_cnt); */ |
598 | | |
599 | | /* typehdr = (struct INSTTYPEHDR *)&hdr[h1_data_off + le32_to_host(objs->insttype_off)]; */ |
600 | | /* printf("INSTTYPES (unk1: %d)\n-----------\n", typehdr->unk1); */ |
601 | | /* off = typehdr->off + h1_data_off; */ |
602 | | /* for(i=1; i<=typehdr->cnt; i++) { */ |
603 | | /* uint32_t x = *(uint32_t *)(&hdr[off]); */ |
604 | | /* struct INSTTYPEITEM *item = (struct INSTTYPEITEM *)&hdr[x + h1_data_off]; */ |
605 | | /* 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]); */ |
606 | | /* printf("components:\n"); */ |
607 | | /* spam_strarray(hdr, h1_data_off + item->off, h1_data_off, item->cnt); */ |
608 | | /* off+=4; */ |
609 | | /* } */ |
610 | | |
611 | | /* dir = &hdr[*(uint32_t *)(&hdr[h1_data_off + objs_dirs_off + 4 * file->dir_id]) + h1_data_off + objs_dirs_off] */ |
612 | |
|
613 | 0 | objs_files_cnt = le32_to_host(objs->files_cnt); |
614 | 0 | off = h1_data_off + objs_dirs_off + le32_to_host(objs->dir_sz2); |
615 | 0 | fmap_unneed_ptr(map, objs, sizeof(*objs)); |
616 | 0 | for (i = 0; i < objs_files_cnt; i++) { |
617 | 0 | struct IS_FILEITEM *file = (struct IS_FILEITEM *)fmap_need_off(map, c->hdr + off, sizeof(*file)); |
618 | |
|
619 | 0 | if (file) { |
620 | 0 | const char *emptyname = "", *dir_name = emptyname, *file_name = emptyname; |
621 | 0 | 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 */ |
622 | 0 | uint32_t file_rel = objs_dirs_off + h1_data_off + le32_to_host(file->str_name_off); /* rel off of fname */ |
623 | 0 | uint64_t file_stream_off, file_size, file_csize; |
624 | 0 | uint16_t cabno; |
625 | |
|
626 | 0 | memcpy(hash, file->md5, 16); |
627 | 0 | md5str((uint8_t *)hash); |
628 | 0 | if (fmap_need_ptr_once(map, &hdr[dir_rel], 4)) { |
629 | 0 | dir_rel = cli_readint32(&hdr[dir_rel]) + h1_data_off + objs_dirs_off; |
630 | 0 | if (fmap_need_str(map, &hdr[dir_rel], c->hdrsz - dir_rel)) |
631 | 0 | dir_name = &hdr[dir_rel]; |
632 | 0 | } |
633 | 0 | if (fmap_need_str(map, &hdr[file_rel], c->hdrsz - file_rel)) |
634 | 0 | file_name = &hdr[file_rel]; |
635 | |
|
636 | 0 | file_stream_off = le64_to_host(file->stream_off); |
637 | 0 | file_size = le64_to_host(file->size); |
638 | 0 | file_csize = le64_to_host(file->csize); |
639 | 0 | cabno = le16_to_host(file->datafile_id); |
640 | |
|
641 | 0 | switch (le16_to_host(file->flags)) { |
642 | 0 | case 0: |
643 | | /* FIXMEISHIELD: for FS scan ? */ |
644 | 0 | cli_dbgmsg("is_parse_hdr: skipped external file:%s\\%s (size: %llu csize: %llu md5:%s)\n", |
645 | 0 | dir_name, |
646 | 0 | file_name, |
647 | 0 | (long long)file_size, (long long)file_csize, hash); |
648 | 0 | break; |
649 | 0 | case 4: |
650 | 0 | 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", |
651 | 0 | dir_name, |
652 | 0 | file_name, |
653 | 0 | (long long)file_size, (long long)file_csize, hash, (long long)file_stream_off, |
654 | 0 | cabno, file->unk13, file->unk14, file->unk15); |
655 | 0 | if (file->flag_has_dup & 1) |
656 | 0 | cli_dbgmsg("is_parse_hdr: not scanned (dup)\n"); |
657 | 0 | else { |
658 | 0 | if (file_size) { |
659 | 0 | unsigned int j; |
660 | 0 | cl_error_t cabret = CL_SUCCESS; |
661 | |
|
662 | 0 | if (ctx->engine->maxfilesize && file_csize > ctx->engine->maxfilesize) { |
663 | 0 | 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); |
664 | 0 | break; |
665 | 0 | } |
666 | | |
667 | 0 | for (j = 0; j < c->cabcnt && c->cabs[j].cabno != cabno; j++) { |
668 | 0 | } |
669 | 0 | if (j != c->cabcnt) { |
670 | 0 | if (CLI_ISCONTAINED(c->cabs[j].off, c->cabs[j].sz, file_stream_off + c->cabs[j].off, file_csize)) { |
671 | 0 | scanned++; |
672 | 0 | if (ctx->engine->maxfiles && scanned >= ctx->engine->maxfiles) { |
673 | 0 | cli_dbgmsg("is_parse_hdr: File limit reached (max: %u)\n", ctx->engine->maxfiles); |
674 | 0 | if (file_name != emptyname) |
675 | 0 | fmap_unneed_ptr(map, (void *)file_name, strlen(file_name) + 1); |
676 | 0 | if (dir_name != emptyname) |
677 | 0 | fmap_unneed_ptr(map, (void *)dir_name, strlen(dir_name) + 1); |
678 | 0 | return CL_EMAXFILES; |
679 | 0 | } |
680 | 0 | cabret = is_extract_cab(ctx, file_stream_off + c->cabs[j].off, file_size, file_csize); |
681 | 0 | } else { |
682 | 0 | ret = CL_SUCCESS; |
683 | 0 | cli_dbgmsg("is_parse_hdr: stream out of file\n"); |
684 | 0 | } |
685 | 0 | } else { |
686 | 0 | ret = CL_SUCCESS; |
687 | 0 | cli_dbgmsg("is_parse_hdr: data%u.cab not available\n", cabno); |
688 | 0 | } |
689 | 0 | if (cabret == CL_BREAK) { |
690 | 0 | ret = CL_SUCCESS; |
691 | 0 | cabret = CL_SUCCESS; |
692 | 0 | } |
693 | 0 | if (cabret != CL_SUCCESS) { |
694 | 0 | if (file_name != emptyname) |
695 | 0 | fmap_unneed_ptr(map, (void *)file_name, strlen(file_name) + 1); |
696 | 0 | if (dir_name != emptyname) |
697 | 0 | fmap_unneed_ptr(map, (void *)dir_name, strlen(dir_name) + 1); |
698 | 0 | return cabret; |
699 | 0 | } |
700 | 0 | } else { |
701 | 0 | cli_dbgmsg("is_parse_hdr: skipped empty file\n"); |
702 | 0 | } |
703 | 0 | } |
704 | 0 | break; |
705 | 0 | default: |
706 | 0 | cli_dbgmsg("is_parse_hdr: skipped unknown file entry %u\n", i); |
707 | 0 | } |
708 | 0 | if (file_name != emptyname) |
709 | 0 | fmap_unneed_ptr(map, (void *)file_name, strlen(file_name) + 1); |
710 | 0 | if (dir_name != emptyname) |
711 | 0 | fmap_unneed_ptr(map, (void *)dir_name, strlen(dir_name) + 1); |
712 | 0 | fmap_unneed_ptr(map, file, sizeof(*file)); |
713 | 0 | } else { |
714 | 0 | ret = CL_SUCCESS; |
715 | 0 | cli_dbgmsg("is_parse_hdr: FILEITEM out of bounds\n"); |
716 | 0 | } |
717 | 0 | off += sizeof(*file); |
718 | 0 | } |
719 | 0 | return ret; |
720 | 0 | } |
721 | | |
722 | | static void md5str(uint8_t *sum) |
723 | 0 | { |
724 | 0 | int i; |
725 | 0 | for (i = 15; i >= 0; i--) { |
726 | 0 | uint8_t lo = (sum[i] & 0xf), hi = (sum[i] >> 4); |
727 | 0 | lo += '0' + (lo > 9) * '\''; |
728 | 0 | hi += '0' + (hi > 9) * '\''; |
729 | 0 | sum[i * 2 + 1] = lo; |
730 | 0 | sum[i * 2] = hi; |
731 | 0 | } |
732 | 0 | sum[32] = '\0'; |
733 | 0 | } |
734 | | |
735 | 0 | #define IS_CABBUFSZ 65536 |
736 | | |
737 | | static cl_error_t is_extract_cab(cli_ctx *ctx, uint64_t off, uint64_t size, uint64_t csize) |
738 | 0 | { |
739 | 0 | cl_error_t ret = CL_SUCCESS; |
740 | 0 | const uint8_t *inbuf; |
741 | 0 | uint8_t *outbuf; |
742 | 0 | char *tempfile; |
743 | 0 | int ofd; |
744 | 0 | z_stream z; |
745 | 0 | uint64_t outsz = 0; |
746 | 0 | int success = 0; |
747 | 0 | fmap_t *map = ctx->fmap; |
748 | |
|
749 | 0 | if (!(outbuf = malloc(IS_CABBUFSZ))) { |
750 | 0 | cli_errmsg("is_extract_cab: Unable to allocate memory for outbuf\n"); |
751 | 0 | return CL_EMEM; |
752 | 0 | } |
753 | | |
754 | 0 | if (!(tempfile = cli_gentemp(ctx->sub_tmpdir))) { |
755 | 0 | free(outbuf); |
756 | 0 | return CL_EMEM; |
757 | 0 | } |
758 | 0 | if ((ofd = open(tempfile, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR)) < 0) { |
759 | 0 | cli_errmsg("is_extract_cab: failed to create file %s\n", tempfile); |
760 | 0 | free(tempfile); |
761 | 0 | free(outbuf); |
762 | 0 | return CL_ECREAT; |
763 | 0 | } |
764 | | |
765 | 0 | while (csize) { |
766 | 0 | uint16_t chunksz; |
767 | 0 | success = 0; |
768 | 0 | if (csize < 2) { |
769 | 0 | cli_dbgmsg("is_extract_cab: no room for chunk size\n"); |
770 | 0 | break; |
771 | 0 | } |
772 | 0 | csize -= 2; |
773 | 0 | if (!(inbuf = fmap_need_off_once(map, off, 2))) { |
774 | 0 | cli_dbgmsg("is_extract_cab: short read for chunk size\n"); |
775 | 0 | break; |
776 | 0 | } |
777 | 0 | off += 2; |
778 | 0 | chunksz = inbuf[0] | (inbuf[1] << 8); |
779 | 0 | if (!chunksz) { |
780 | 0 | cli_dbgmsg("is_extract_cab: zero sized chunk\n"); |
781 | 0 | continue; |
782 | 0 | } |
783 | 0 | if (csize < chunksz) { |
784 | 0 | cli_dbgmsg("is_extract_cab: chunk is bigger than csize\n"); |
785 | 0 | break; |
786 | 0 | } |
787 | 0 | csize -= chunksz; |
788 | 0 | if (!(inbuf = fmap_need_off_once(map, off, chunksz))) { |
789 | 0 | cli_dbgmsg("is_extract_cab: short read for chunk\n"); |
790 | 0 | break; |
791 | 0 | } |
792 | 0 | off += chunksz; |
793 | 0 | memset(&z, 0, sizeof(z)); |
794 | 0 | inflateInit2(&z, -MAX_WBITS); |
795 | 0 | z.next_in = (uint8_t *)inbuf; |
796 | 0 | z.avail_in = chunksz; |
797 | 0 | while (1) { |
798 | 0 | int zret; |
799 | 0 | z.next_out = outbuf; |
800 | 0 | z.avail_out = IS_CABBUFSZ; |
801 | 0 | zret = inflate(&z, 0); |
802 | 0 | if (zret == Z_OK || zret == Z_STREAM_END || zret == Z_BUF_ERROR) { |
803 | 0 | unsigned int umpd = IS_CABBUFSZ - z.avail_out; |
804 | 0 | if (cli_writen(ofd, outbuf, umpd) != umpd) |
805 | 0 | break; |
806 | 0 | outsz += umpd; |
807 | 0 | if (zret == Z_STREAM_END || z.avail_out == IS_CABBUFSZ /* FIXMEISHIELD: is the latter ok? */) { |
808 | 0 | success = 1; |
809 | 0 | break; |
810 | 0 | } |
811 | 0 | if (ctx->engine->maxfilesize && z.total_out > ctx->engine->maxfilesize) { |
812 | 0 | 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); |
813 | 0 | success = 1; |
814 | 0 | outsz = size; |
815 | 0 | break; |
816 | 0 | } |
817 | 0 | continue; |
818 | 0 | } |
819 | 0 | cli_dbgmsg("is_extract_cab: file decompression failed with %d\n", zret); |
820 | 0 | break; |
821 | 0 | } |
822 | 0 | inflateEnd(&z); |
823 | 0 | if (!success) break; |
824 | 0 | } |
825 | 0 | free(outbuf); |
826 | 0 | if (success) { |
827 | 0 | if (outsz != size) |
828 | 0 | cli_dbgmsg("is_extract_cab: extracted %llu bytes to %s, expected %llu, scanning anyway.\n", (long long)outsz, tempfile, (long long)size); |
829 | 0 | else |
830 | 0 | cli_dbgmsg("is_extract_cab: extracted to %s\n", tempfile); |
831 | 0 | if (lseek(ofd, 0, SEEK_SET) == -1) |
832 | 0 | cli_dbgmsg("is_extract_cab: call to lseek() failed\n"); |
833 | 0 | ret = cli_magic_scan_desc(ofd, tempfile, ctx, NULL, LAYER_ATTRIBUTES_NONE); |
834 | 0 | } |
835 | |
|
836 | 0 | close(ofd); |
837 | 0 | if (!ctx->engine->keeptmp) |
838 | 0 | if (cli_unlink(tempfile)) ret = CL_EUNLINK; |
839 | 0 | free(tempfile); |
840 | 0 | return success ? ret : CL_BREAK; |
841 | 0 | } |