/*
 *  Copyright (C) 2013-2024 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
 *  Copyright (C) 2007-2013 Sourcefire, Inc.
 *
 *  Authors: Alberto Wu, Tomasz Kojm, Andrew Williams
 *
 *  Acknowledgements: The header structures were based upon a PE format
 *                    analysis by B. Luevelsmeyer.
 *
 *  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.
 */
/*
  Portions of Code (i.e. pe_ordinal) Copyright (c) 2014. The YARA Authors. All Rights Reserved.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif

/*
#define _XOPEN_SOURCE 500
*/

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#if HAVE_STRING_H
#include <string.h>
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <time.h>
#include <stdarg.h>

#include "clamav.h"
#include "others.h"
#include "pe.h"
#include "petite.h"
#include "fsg.h"
#include "spin.h"
#include "upx.h"
#include "yc.h"
#include "aspack.h"
#include "wwunpack.h"
#include "unsp.h"
#include "scanners.h"
#include "str.h"
#include "entconv.h"
#include "execs.h"
#include "mew.h"
#include "upack.h"
#include "matcher.h"
#include "matcher-hash.h"
#include "disasm.h"
#include "special.h"
#include "ishield.h"
#include "asn1.h"

#include "json_api.h"

#include "clamav_rust.h"

#define DCONF ctx->dconf->pe

#define PE_IMAGE_DOS_SIGNATURE 0x5a4d     /* MZ */
#define PE_IMAGE_DOS_SIGNATURE_OLD 0x4d5a /* ZM */
#define PE_IMAGE_NT_SIGNATURE 0x00004550
#define PE32_SIGNATURE 0x010b
#define PE32P_SIGNATURE 0x020b
#define OPT_HDR_SIZE_DIFF (sizeof(struct pe_image_optional_hdr64) - sizeof(struct pe_image_optional_hdr32))

#define UPX_NRV2B "\x11\xdb\x11\xc9\x01\xdb\x75\x07\x8b\x1e\x83\xee\xfc\x11\xdb\x11\xc9\x11\xc9\x75\x20\x41\x01\xdb"
#define UPX_NRV2D "\x83\xf0\xff\x74\x78\xd1\xf8\x89\xc5\xeb\x0b\x01\xdb\x75\x07\x8b\x1e\x83\xee\xfc\x11\xdb\x11\xc9"
#define UPX_NRV2E "\xeb\x52\x31\xc9\x83\xe8\x03\x72\x11\xc1\xe0\x08\x8a\x06\x46\x83\xf0\xff\x74\x75\xd1\xf8\x89\xc5"
#define UPX_LZMA1_FIRST "\x56\x83\xc3\x04\x53\x50\xc7\x03"
#define UPX_LZMA1_SECOND "\x90\x90\x90\x55\x57\x56\x53\x83"
#define UPX_LZMA0 "\x56\x83\xc3\x04\x53\x50\xc7\x03\x03\x00\x00\x00\x90\x90\x90\x55\x57\x56\x53\x83"
#define UPX_LZMA2 "\x56\x83\xc3\x04\x53\x50\xc7\x03\x03\x00\x02\x00\x90\x90\x90\x90\x90\x55\x57\x56"

#define PE_MAXNAMESIZE 256
#define PE_MAXIMPORTS 1024

#define EC64(x) ((uint64_t)cli_readint64(&(x))) /* Convert little endian to host */
#define EC32(x) ((uint32_t)cli_readint32(&(x)))
#define EC16(x) ((uint16_t)cli_readint16(&(x)))
/* lower and upper boundary alignment (size vs offset) */
#define PEALIGN(o, a) (((a)) ? (((o) / (a)) * (a)) : (o))
#define PESALIGN(o, a) (((a)) ? (((o) / (a) + ((o) % (a) != 0)) * (a)) : (o))

// TODO Replace all of these with static inline functions
#define CLI_UNPSIZELIMITS(NAME, CHK)                           \
    if (cli_checklimits(NAME, ctx, (CHK), 0, 0) != CL_CLEAN) { \
        cli_exe_info_destroy(peinfo);                          \
        return CL_CLEAN;                                       \
    }

#define CLI_UNPTEMP(NAME, FREEME)                                                                 \
    if (!(tempfile = cli_gentemp(ctx->sub_tmpdir))) {                                             \
        cli_exe_info_destroy(peinfo);                                                             \
        cli_multifree FREEME;                                                                     \
        return CL_EMEM;                                                                           \
    }                                                                                             \
    if ((ndesc = open(tempfile, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR)) < 0) { \
        cli_dbgmsg(NAME ": Can't create file %s\n", tempfile);                                    \
        free(tempfile);                                                                           \
        cli_exe_info_destroy(peinfo);                                                             \
        cli_multifree FREEME;                                                                     \
        return CL_ECREAT;                                                                         \
    }

#define CLI_TMPUNLK()               \
    if (!ctx->engine->keeptmp) {    \
        if (cli_unlink(tempfile)) { \
            free(tempfile);         \
            return CL_EUNLINK;      \
        }                           \
    }

#ifdef HAVE__INTERNAL__SHA_COLLECT
#define SHA_OFF                \
    do {                       \
        ctx->sha_collect = -1; \
    } while (0)
#define SHA_RESET                       \
    do {                                \
        ctx->sha_collect = sha_collect; \
    } while (0)
#else
#define SHA_OFF \
    do {        \
    } while (0)
#define SHA_RESET \
    do {          \
    } while (0)
#endif

#define FSGCASE(NAME, FREESEC)                            \
    case 0: /* Unpacked and NOT rebuilt */                \
        cli_dbgmsg(NAME ": Successfully decompressed\n"); \
        close(ndesc);                                     \
        if (cli_unlink(tempfile)) {                       \
            cli_exe_info_destroy(peinfo);                 \
            free(tempfile);                               \
            FREESEC;                                      \
            return CL_EUNLINK;                            \
        }                                                 \
        free(tempfile);                                   \
        FREESEC;                                          \
        found       = 0;                                  \
        upx_success = 1;                                  \
        break; /* FSG ONLY! - scan raw data after upx block */

#define SPINCASE()                                         \
    case 2:                                                \
        free(spinned);                                     \
        close(ndesc);                                      \
        if (cli_unlink(tempfile)) {                        \
            cli_exe_info_destroy(peinfo);                  \
            free(tempfile);                                \
            return CL_EUNLINK;                             \
        }                                                  \
        cli_dbgmsg("cli_scanpe: PESpin: Size exceeded\n"); \
        free(tempfile);                                    \
        break;

#define CLI_UNPRESULTS_(NAME, FSGSTUFF, EXPR, GOOD, FREEME)                                                     \
    switch (EXPR) {                                                                                             \
        case GOOD: /* Unpacked and rebuilt */                                                                   \
            cli_dbgmsg(NAME ": Unpacked and rebuilt executable saved in %s\n", tempfile);                       \
            cli_multifree FREEME;                                                                               \
            cli_exe_info_destroy(peinfo);                                                                       \
            lseek(ndesc, 0, SEEK_SET);                                                                          \
            cli_dbgmsg("***** Scanning rebuilt PE file *****\n");                                               \
            SHA_OFF;                                                                                            \
            if (CL_SUCCESS != (ret = cli_magic_scan_desc(ndesc, tempfile, ctx, NULL, LAYER_ATTRIBUTES_NONE))) { \
                close(ndesc);                                                                                   \
                SHA_RESET;                                                                                      \
                CLI_TMPUNLK();                                                                                  \
                free(tempfile);                                                                                 \
                return ret;                                                                                     \
            }                                                                                                   \
            SHA_RESET;                                                                                          \
            close(ndesc);                                                                                       \
            CLI_TMPUNLK();                                                                                      \
            free(tempfile);                                                                                     \
            return CL_CLEAN;                                                                                    \
                                                                                                                \
            FSGSTUFF;                                                                                           \
                                                                                                                \
        default:                                                                                                \
            cli_dbgmsg(NAME ": Unpacking failed\n");                                                            \
            close(ndesc);                                                                                       \
            if (cli_unlink(tempfile)) {                                                                         \
                cli_exe_info_destroy(peinfo);                                                                   \
                free(tempfile);                                                                                 \
                cli_multifree FREEME;                                                                           \
                return CL_EUNLINK;                                                                              \
            }                                                                                                   \
            cli_multifree FREEME;                                                                               \
            free(tempfile);                                                                                     \
    }

// The GOOD parameter indicates what a successful unpacking should return.
#define CLI_UNPRESULTS(NAME, EXPR, GOOD, FREEME) CLI_UNPRESULTS_(NAME, (void)0, EXPR, GOOD, FREEME)

// TODO The second argument to FSGCASE below should match what gets freed as
// indicated by FREEME, otherwise a memory leak can occur (as currently used,
// it looks like dest can get leaked by these macros).
#define CLI_UNPRESULTSFSG1(NAME, EXPR, GOOD, FREEME) CLI_UNPRESULTS_(NAME, FSGCASE(NAME, free(sections)), EXPR, GOOD, FREEME)
#define CLI_UNPRESULTSFSG2(NAME, EXPR, GOOD, FREEME) CLI_UNPRESULTS_(NAME, FSGCASE(NAME, (void)0), EXPR, GOOD, FREEME)

#define DETECT_BROKEN_PE (SCAN_HEURISTIC_BROKEN && !ctx->corrupted_input)

extern const unsigned int hashlen[];

struct offset_list {
    uint32_t offset;
    struct offset_list *next;
};

struct pe_image_import_descriptor {
    union {
        uint32_t Characteristics;
        uint32_t OriginalFirstThunk;
    } u;
    uint32_t TimeDateStamp;
    uint32_t ForwarderChain;
    uint32_t Name;
    uint32_t FirstThunk;
};

#define PE_IMAGEDIR_ORDINAL_FLAG32 0x80000000
#define PE_IMAGEDIR_ORDINAL_FLAG64 0x8000000000000000L

struct pe_image_thunk32 {
    union {
        uint32_t ForwarderString;
        uint32_t Function;
        uint32_t Ordinal;
        uint32_t AddressOfData;
    } u;
};

struct pe_image_thunk64 {
    union {
        uint64_t ForwarderString;
        uint64_t Function;
        uint64_t Ordinal;
        uint64_t AddressOfData;
    } u;
};

struct pe_image_import_by_name {
    uint16_t Hint;
    uint8_t Name[1];
};

static void cli_multifree(void *f, ...)
{
    void *ff;
    va_list ap;
    free(f);
    va_start(ap, f);
    while ((ff = va_arg(ap, void *))) free(ff);
    va_end(ap);
}

struct vinfo_list {
    uint32_t rvas[16];
    unsigned int count;
};

static int versioninfo_cb(void *opaque, uint32_t type, uint32_t name, uint32_t lang, uint32_t rva)
{
    struct vinfo_list *vlist = (struct vinfo_list *)opaque;

    cli_dbgmsg("versioninfo_cb: type: %x, name: %x, lang: %x, rva: %x\n", type, name, lang, rva);
    vlist->rvas[vlist->count] = rva;
    if (++vlist->count == sizeof(vlist->rvas) / sizeof(vlist->rvas[0]))
        return 1;
    return 0;
}

/* Given an RVA (relative to the ImageBase), return the file offset of the
 * corresponding data */
uint32_t cli_rawaddr(uint32_t rva, const struct cli_exe_section *shp, uint16_t nos, unsigned int *err, size_t fsize, uint32_t hdr_size)
{
    int i, found = 0;
    uint32_t ret;

    if (rva < hdr_size) { /* Out of section EP - mapped to imagebase+rva */
        if (rva >= fsize) {
            *err = 1;
            return 0;
        }

        *err = 0;
        return rva;
    }

    for (i = nos - 1; i >= 0; i--) {
        if (shp[i].rsz && shp[i].rva <= rva && shp[i].rsz > (rva - shp[i].rva)) {
            found = 1;
            break;
        }
    }

    if (!found) {
        *err = 1;
        return 0;
    }

    ret  = (rva - shp[i].rva) + shp[i].raw;
    *err = 0;
    return ret;
}

/*
   void findres(uint32_t by_type, uint32_t by_name, fmap_t *map, struct cli_exe_info *peinfo, int (*cb)(void *, uint32_t, uint32_t, uint32_t, uint32_t), void *opaque)
   callback based res lookup

   by_type: lookup type
   by_name: lookup name or (unsigned)-1 to look for any name
   res_rva: base resource rva (i.e. dirs[2].VirtualAddress)
   map, peinfo: same as in scanpe
   cb: the callback function executed on each successful match
   opaque: an opaque pointer passed to the callback

   the callback proto is
   int pe_res_cballback (void *opaque, uint32_t type, uint32_t name, uint32_t lang, uint32_t rva);
   the callback shall return 0 to continue the lookup or 1 to abort
*/
void findres(uint32_t by_type, uint32_t by_name, fmap_t *map, struct cli_exe_info *peinfo, int (*cb)(void *, uint32_t, uint32_t, uint32_t, uint32_t), void *opaque)
{
    unsigned int err = 0;
    uint32_t type, type_offs, name, name_offs, lang, lang_offs;
    const uint8_t *resdir, *type_entry, *name_entry, *lang_entry;
    uint16_t type_cnt, name_cnt, lang_cnt;
    uint32_t res_rva;

    if (NULL == peinfo || peinfo->ndatadirs < 3) {
        return;
    }

    if (0 != peinfo->offset) {
        cli_dbgmsg("findres: Assumption Violated: Looking for version info when peinfo->offset != 0\n");
    }

    res_rva = peinfo->dirs[2].VirtualAddress;

    if (!(resdir = fmap_need_off_once(map, cli_rawaddr(res_rva, peinfo->sections, peinfo->nsections, &err, map->len, peinfo->hdr_size), 16)) || err)
        return;

    type_cnt   = (uint16_t)cli_readint16(resdir + 12);
    type_entry = resdir + 16;
    if (!(by_type >> 31)) {
        type_entry += type_cnt * 8;
        type_cnt = (uint16_t)cli_readint16(resdir + 14);
    }

    while (type_cnt--) {
        if (!fmap_need_ptr_once(map, type_entry, 8))
            return;
        type      = cli_readint32(type_entry);
        type_offs = cli_readint32(type_entry + 4);
        if (type == by_type && (type_offs >> 31)) {
            type_offs &= 0x7fffffff;
            if (!(resdir = fmap_need_off_once(map, cli_rawaddr(res_rva + type_offs, peinfo->sections, peinfo->nsections, &err, map->len, peinfo->hdr_size), 16)) || err)
                return;

            name_cnt   = (uint16_t)cli_readint16(resdir + 12);
            name_entry = resdir + 16;
            if (by_name == 0xffffffff)
                name_cnt += (uint16_t)cli_readint16(resdir + 14);
            else if (!(by_name >> 31)) {
                name_entry += name_cnt * 8;
                name_cnt = (uint16_t)cli_readint16(resdir + 14);
            }
            while (name_cnt--) {
                if (!fmap_need_ptr_once(map, name_entry, 8))
                    return;
                name      = cli_readint32(name_entry);
                name_offs = cli_readint32(name_entry + 4);
                if ((by_name == 0xffffffff || name == by_name) && (name_offs >> 31)) {
                    name_offs &= 0x7fffffff;
                    if (!(resdir = fmap_need_off_once(map, cli_rawaddr(res_rva + name_offs, peinfo->sections, peinfo->nsections, &err, map->len, peinfo->hdr_size), 16)) || err)
                        return;

                    lang_cnt   = (uint16_t)cli_readint16(resdir + 12) + (uint16_t)cli_readint16(resdir + 14);
                    lang_entry = resdir + 16;
                    while (lang_cnt--) {
                        if (!fmap_need_ptr_once(map, lang_entry, 8))
                            return;
                        lang      = cli_readint32(lang_entry);
                        lang_offs = cli_readint32(lang_entry + 4);
                        if (!(lang_offs >> 31)) {
                            if (cb(opaque, type, name, lang, res_rva + lang_offs))
                                return;
                        }
                        lang_entry += 8;
                    }
                }
                name_entry += 8;
            }
            return; /* FIXME: unless we want to find ALL types */
        }
        type_entry += 8;
    }
}

static void cli_parseres_special(uint32_t base, uint32_t rva, fmap_t *map, struct cli_exe_info *peinfo, size_t fsize, unsigned int level, uint32_t type, unsigned int *maxres, struct swizz_stats *stats)
{
    unsigned int err = 0, i;
    const uint8_t *resdir;
    const uint8_t *entry, *oentry;
    uint16_t named, unnamed;
    uint32_t rawaddr = cli_rawaddr(rva, peinfo->sections, peinfo->nsections, &err, fsize, peinfo->hdr_size);
    uint32_t entries;

    if (level > 2 || !*maxres) return;
    *maxres -= 1;
    if (err || !(resdir = fmap_need_off_once(map, rawaddr, 16)))
        return;
    named   = (uint16_t)cli_readint16(resdir + 12);
    unnamed = (uint16_t)cli_readint16(resdir + 14);

    entries = /*named+*/ unnamed;
    if (!entries)
        return;
    rawaddr += named * 8; /* skip named */
    /* this is just used in a heuristic detection, so don't give error on failure */
    if (!(entry = fmap_need_off(map, rawaddr + 16, entries * 8))) {
        cli_dbgmsg("cli_parseres_special: failed to read resource directory at:%lu\n", (unsigned long)rawaddr + 16);
        return;
    }
    oentry = entry;
    /*for (i=0; i<named; i++) {
        uint32_t id, offs;
        id = cli_readint32(entry);
        offs = cli_readint32(entry+4);
        if(offs>>31)
            cli_parseres( base, base + (offs&0x7fffffff), srcfd, peinfo, fsize, level+1, type, maxres, stats);
        entry+=8;
    }*/
    for (i = 0; i < unnamed; i++, entry += 8) {
        uint32_t id, offs;
        if (stats->errors >= SWIZZ_MAXERRORS) {
            cli_dbgmsg("cli_parseres_special: resources broken, ignoring\n");
            return;
        }
        id = cli_readint32(entry) & 0x7fffffff;
        if (level == 0) {
            type = 0;
            switch (id) {
                case 4:  /* menu */
                case 5:  /* dialog */
                case 6:  /* string */
                case 11: /* msgtable */
                    type = id;
                    break;
                case 16:
                    type = id;
                    /* 14: version */
                    stats->has_version = 1;
                    break;
                case 24: /* manifest */
                    stats->has_manifest = 1;
                    break;
                    /* otherwise keep it 0, we don't want it */
            }
        }
        if (!type) {
            /* if we are not interested in this type, skip */
            continue;
        }
        offs = cli_readint32(entry + 4);
        if (offs >> 31)
            cli_parseres_special(base, base + (offs & 0x7fffffff), map, peinfo, fsize, level + 1, type, maxres, stats);
        else {
            offs    = cli_readint32(entry + 4);
            rawaddr = cli_rawaddr(base + offs, peinfo->sections, peinfo->nsections, &err, fsize, peinfo->hdr_size);
            if (!err && (resdir = fmap_need_off_once(map, rawaddr, 16))) {
                uint32_t isz = cli_readint32(resdir + 4);
                const uint8_t *str;
                rawaddr = cli_rawaddr(cli_readint32(resdir), peinfo->sections, peinfo->nsections, &err, fsize, peinfo->hdr_size);
                if (err || !isz || isz >= fsize || rawaddr + isz >= fsize) {
                    cli_dbgmsg("cli_parseres_special: invalid resource table entry: %lu + %lu\n",
                               (unsigned long)rawaddr,
                               (unsigned long)isz);
                    stats->errors++;
                    continue;
                }
                if ((id & 0xff) != 0x09) /* english res only */
                    continue;
                if ((str = fmap_need_off_once(map, rawaddr, isz)))
                    cli_detect_swizz_str(str, isz, stats, type);
            }
        }
    }
    fmap_unneed_ptr(map, oentry, entries * 8);
}

static unsigned int cli_hashsect(fmap_t *map, struct cli_exe_section *s, unsigned char **digest, int *foundhash, int *foundwild)
{
    const void *hashme;

    if (s->rsz > CLI_MAX_ALLOCATION) {
        cli_dbgmsg("cli_hashsect: skipping hash calculation for too big section\n");
        return 0;
    }

    if (!s->rsz) return 0;
    if (!(hashme = fmap_need_off_once(map, s->raw, s->rsz))) {
        cli_dbgmsg("cli_hashsect: unable to read section data\n");
        return 0;
    }

    if (foundhash[CLI_HASH_MD5] || foundwild[CLI_HASH_MD5])
        cl_hash_data("md5", hashme, s->rsz, digest[CLI_HASH_MD5], NULL);
    if (foundhash[CLI_HASH_SHA1] || foundwild[CLI_HASH_SHA1])
        cl_sha1(hashme, s->rsz, digest[CLI_HASH_SHA1], NULL);
    if (foundhash[CLI_HASH_SHA256] || foundwild[CLI_HASH_SHA256])
        cl_sha256(hashme, s->rsz, digest[CLI_HASH_SHA256], NULL);

    return 1;
}

/* check hash section sigs */
static cl_error_t scan_pe_mdb(cli_ctx *ctx, struct cli_exe_section *exe_section)
{
    struct cli_matcher *mdb_sect = ctx->engine->hm_mdb;
    unsigned char *hashset[CLI_HASH_AVAIL_TYPES];
    const char *virname = NULL;
    int foundsize[CLI_HASH_AVAIL_TYPES];
    int foundwild[CLI_HASH_AVAIL_TYPES];
    cli_hash_type_t type;
    cl_error_t ret     = CL_CLEAN;
    unsigned char *md5 = NULL;

    /* pick hashtypes to generate */
    for (type = CLI_HASH_MD5; type < CLI_HASH_AVAIL_TYPES; type++) {
        foundsize[type] = cli_hm_have_size(mdb_sect, type, exe_section->rsz);
        foundwild[type] = cli_hm_have_wild(mdb_sect, type);
        if (foundsize[type] || foundwild[type]) {
            hashset[type] = malloc(hashlen[type]);
            if (!hashset[type]) {
                cli_errmsg("scan_pe_mdb: malloc failed!\n");
                for (; type > 0;)
                    free(hashset[--type]);
                return CL_EMEM;
            }
        } else {
            hashset[type] = NULL;
        }
    }

    /* Generate hashes */
    cli_hashsect(ctx->fmap, exe_section, hashset, foundsize, foundwild);

    /* Print hash */
    if (cli_debug_flag) {
        md5 = hashset[CLI_HASH_MD5];
        if (md5) {
            cli_dbgmsg("MDB hashset: %u:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
                       exe_section->rsz, md5[0], md5[1], md5[2], md5[3], md5[4], md5[5], md5[6], md5[7],
                       md5[8], md5[9], md5[10], md5[11], md5[12], md5[13], md5[14], md5[15]);
        } else if (cli_always_gen_section_hash) {
            const void *hashme = fmap_need_off_once(ctx->fmap, exe_section->raw, exe_section->rsz);
            if (!(hashme)) {
                cli_errmsg("scan_pe_mdb: unable to read section data\n");
                ret = CL_EREAD;
                goto end;
            }

            md5 = malloc(16);
            if (!(md5)) {
                cli_errmsg("scan_pe_mdb: malloc failed!\n");
                ret = CL_EMEM;
                goto end;
            }

            cl_hash_data("md5", hashme, exe_section->rsz, md5, NULL);

            cli_dbgmsg("MDB: %u:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
                       exe_section->rsz, md5[0], md5[1], md5[2], md5[3], md5[4], md5[5], md5[6], md5[7],
                       md5[8], md5[9], md5[10], md5[11], md5[12], md5[13], md5[14], md5[15]);

            free(md5);

        } else {
            cli_dbgmsg("MDB: %u:notgenerated\n", exe_section->rsz);
        }
    }

    /* Do scans */
    for (type = CLI_HASH_MD5; type < CLI_HASH_AVAIL_TYPES; type++) {
        if (foundsize[type] && cli_hm_scan(hashset[type], exe_section->rsz, &virname, mdb_sect, type) == CL_VIRUS) {
            ret = cli_append_virus(ctx, virname);
            if (ret != CL_SUCCESS) {
                break;
            }
        }
        if (foundwild[type] && cli_hm_scan_wild(hashset[type], &virname, mdb_sect, type) == CL_VIRUS) {
            ret = cli_append_virus(ctx, virname);
            if (ret != CL_SUCCESS) {
                break;
            }
        }
    }

end:
    for (type = CLI_HASH_AVAIL_TYPES; type > 0;)
        free(hashset[--type]);
    return ret;
}

/* imptbl scanning */
static char *pe_ordinal(const char *dll, uint16_t ord)
{
    char name[64];
    name[0] = '\0';

    if (strncasecmp(dll, "WS2_32.dll", 10) == 0 ||
        strncasecmp(dll, "wsock32.dll", 11) == 0) {
        switch (ord) {
            case 1:
                sprintf(name, "accept");
                break;
            case 2:
                sprintf(name, "bind");
                break;
            case 3:
                sprintf(name, "closesocket");
                break;
            case 4:
                sprintf(name, "connect");
                break;
            case 5:
                sprintf(name, "getpeername");
                break;
            case 6:
                sprintf(name, "getsockname");
                break;
            case 7:
                sprintf(name, "getsockopt");
                break;
            case 8:
                sprintf(name, "htonl");
                break;
            case 9:
                sprintf(name, "htons");
                break;
            case 10:
                sprintf(name, "ioctlsocket");
                break;
            case 11:
                sprintf(name, "inet_addr");
                break;
            case 12:
                sprintf(name, "inet_ntoa");
                break;
            case 13:
                sprintf(name, "listen");
                break;
            case 14:
                sprintf(name, "ntohl");
                break;
            case 15:
                sprintf(name, "ntohs");
                break;
            case 16:
                sprintf(name, "recv");
                break;
            case 17:
                sprintf(name, "recvfrom");
                break;
            case 18:
                sprintf(name, "select");
                break;
            case 19:
                sprintf(name, "send");
                break;
            case 20:
                sprintf(name, "sendto");
                break;
            case 21:
                sprintf(name, "setsockopt");
                break;
            case 22:
                sprintf(name, "shutdown");
                break;
            case 23:
                sprintf(name, "socket");
                break;
            case 24:
                sprintf(name, "GetAddrInfoW");
                break;
            case 25:
                sprintf(name, "GetNameInfoW");
                break;
            case 26:
                sprintf(name, "WSApSetPostRoutine");
                break;
            case 27:
                sprintf(name, "FreeAddrInfoW");
                break;
            case 28:
                sprintf(name, "WPUCompleteOverlappedRequest");
                break;
            case 29:
                sprintf(name, "WSAAccept");
                break;
            case 30:
                sprintf(name, "WSAAddressToStringA");
                break;
            case 31:
                sprintf(name, "WSAAddressToStringW");
                break;
            case 32:
                sprintf(name, "WSACloseEvent");
                break;
            case 33:
                sprintf(name, "WSAConnect");
                break;
            case 34:
                sprintf(name, "WSACreateEvent");
                break;
            case 35:
                sprintf(name, "WSADuplicateSocketA");
                break;
            case 36:
                sprintf(name, "WSADuplicateSocketW");
                break;
            case 37:
                sprintf(name, "WSAEnumNameSpaceProvidersA");
                break;
            case 38:
                sprintf(name, "WSAEnumNameSpaceProvidersW");
                break;
            case 39:
                sprintf(name, "WSAEnumNetworkEvents");
                break;
            case 40:
                sprintf(name, "WSAEnumProtocolsA");
                break;
            case 41:
                sprintf(name, "WSAEnumProtocolsW");
                break;
            case 42:
                sprintf(name, "WSAEventSelect");
                break;
            case 43:
                sprintf(name, "WSAGetOverlappedResult");
                break;
            case 44:
                sprintf(name, "WSAGetQOSByName");
                break;
            case 45:
                sprintf(name, "WSAGetServiceClassInfoA");
                break;
            case 46:
                sprintf(name, "WSAGetServiceClassInfoW");
                break;
            case 47:
                sprintf(name, "WSAGetServiceClassNameByClassIdA");
                break;
            case 48:
                sprintf(name, "WSAGetServiceClassNameByClassIdW");
                break;
            case 49:
                sprintf(name, "WSAHtonl");
                break;
            case 50:
                sprintf(name, "WSAHtons");
                break;
            case 51:
                sprintf(name, "gethostbyaddr");
                break;
            case 52:
                sprintf(name, "gethostbyname");
                break;
            case 53:
                sprintf(name, "getprotobyname");
                break;
            case 54:
                sprintf(name, "getprotobynumber");
                break;
            case 55:
                sprintf(name, "getservbyname");
                break;
            case 56:
                sprintf(name, "getservbyport");
                break;
            case 57:
                sprintf(name, "gethostname");
                break;
            case 58:
                sprintf(name, "WSAInstallServiceClassA");
                break;
            case 59:
                sprintf(name, "WSAInstallServiceClassW");
                break;
            case 60:
                sprintf(name, "WSAIoctl");
                break;
            case 61:
                sprintf(name, "WSAJoinLeaf");
                break;
            case 62:
                sprintf(name, "WSALookupServiceBeginA");
                break;
            case 63:
                sprintf(name, "WSALookupServiceBeginW");
                break;
            case 64:
                sprintf(name, "WSALookupServiceEnd");
                break;
            case 65:
                sprintf(name, "WSALookupServiceNextA");
                break;
            case 66:
                sprintf(name, "WSALookupServiceNextW");
                break;
            case 67:
                sprintf(name, "WSANSPIoctl");
                break;
            case 68:
                sprintf(name, "WSANtohl");
                break;
            case 69:
                sprintf(name, "WSANtohs");
                break;
            case 70:
                sprintf(name, "WSAProviderConfigChange");
                break;
            case 71:
                sprintf(name, "WSARecv");
                break;
            case 72:
                sprintf(name, "WSARecvDisconnect");
                break;
            case 73:
                sprintf(name, "WSARecvFrom");
                break;
            case 74:
                sprintf(name, "WSARemoveServiceClass");
                break;
            case 75:
                sprintf(name, "WSAResetEvent");
                break;
            case 76:
                sprintf(name, "WSASend");
                break;
            case 77:
                sprintf(name, "WSASendDisconnect");
                break;
            case 78:
                sprintf(name, "WSASendTo");
                break;
            case 79:
                sprintf(name, "WSASetEvent");
                break;
            case 80:
                sprintf(name, "WSASetServiceA");
                break;
            case 81:
                sprintf(name, "WSASetServiceW");
                break;
            case 82:
                sprintf(name, "WSASocketA");
                break;
            case 83:
                sprintf(name, "WSASocketW");
                break;
            case 84:
                sprintf(name, "WSAStringToAddressA");
                break;
            case 85:
                sprintf(name, "WSAStringToAddressW");
                break;
            case 86:
                sprintf(name, "WSAWaitForMultipleEvents");
                break;
            case 87:
                sprintf(name, "WSCDeinstallProvider");
                break;
            case 88:
                sprintf(name, "WSCEnableNSProvider");
                break;
            case 89:
                sprintf(name, "WSCEnumProtocols");
                break;
            case 90:
                sprintf(name, "WSCGetProviderPath");
                break;
            case 91:
                sprintf(name, "WSCInstallNameSpace");
                break;
            case 92:
                sprintf(name, "WSCInstallProvider");
                break;
            case 93:
                sprintf(name, "WSCUnInstallNameSpace");
                break;
            case 94:
                sprintf(name, "WSCUpdateProvider");
                break;
            case 95:
                sprintf(name, "WSCWriteNameSpaceOrder");
                break;
            case 96:
                sprintf(name, "WSCWriteProviderOrder");
                break;
            case 97:
                sprintf(name, "freeaddrinfo");
                break;
            case 98:
                sprintf(name, "getaddrinfo");
                break;
            case 99:
                sprintf(name, "getnameinfo");
                break;
            case 101:
                sprintf(name, "WSAAsyncSelect");
                break;
            case 102:
                sprintf(name, "WSAAsyncGetHostByAddr");
                break;
            case 103:
                sprintf(name, "WSAAsyncGetHostByName");
                break;
            case 104:
                sprintf(name, "WSAAsyncGetProtoByNumber");
                break;
            case 105:
                sprintf(name, "WSAAsyncGetProtoByName");
                break;
            case 106:
                sprintf(name, "WSAAsyncGetServByPort");
                break;
            case 107:
                sprintf(name, "WSAAsyncGetServByName");
                break;
            case 108:
                sprintf(name, "WSACancelAsyncRequest");
                break;
            case 109:
                sprintf(name, "WSASetBlockingHook");
                break;
            case 110:
                sprintf(name, "WSAUnhookBlockingHook");
                break;
            case 111:
                sprintf(name, "WSAGetLastError");
                break;
            case 112:
                sprintf(name, "WSASetLastError");
                break;
            case 113:
                sprintf(name, "WSACancelBlockingCall");
                break;
            case 114:
                sprintf(name, "WSAIsBlocking");
                break;
            case 115:
                sprintf(name, "WSAStartup");
                break;
            case 116:
                sprintf(name, "WSACleanup");
                break;
            case 151:
                sprintf(name, "__WSAFDIsSet");
                break;
            case 500:
                sprintf(name, "WEP");
                break;
            default:
                break;
        }
    } else if (strncasecmp(dll, "oleaut32.dll", 12) == 0) {
        switch (ord) {
            case 2:
                sprintf(name, "SysAllocString");
                break;
            case 3:
                sprintf(name, "SysReAllocString");
                break;
            case 4:
                sprintf(name, "SysAllocStringLen");
                break;
            case 5:
                sprintf(name, "SysReAllocStringLen");
                break;
            case 6:
                sprintf(name, "SysFreeString");
                break;
            case 7:
                sprintf(name, "SysStringLen");
                break;
            case 8:
                sprintf(name, "VariantInit");
                break;
            case 9:
                sprintf(name, "VariantClear");
                break;
            case 10:
                sprintf(name, "VariantCopy");
                break;
            case 11:
                sprintf(name, "VariantCopyInd");
                break;
            case 12:
                sprintf(name, "VariantChangeType");
                break;
            case 13:
                sprintf(name, "VariantTimeToDosDateTime");
                break;
            case 14:
                sprintf(name, "DosDateTimeToVariantTime");
                break;
            case 15:
                sprintf(name, "SafeArrayCreate");
                break;
            case 16:
                sprintf(name, "SafeArrayDestroy");
                break;
            case 17:
                sprintf(name, "SafeArrayGetDim");
                break;
            case 18:
                sprintf(name, "SafeArrayGetElemsize");
                break;
            case 19:
                sprintf(name, "SafeArrayGetUBound");
                break;
            case 20:
                sprintf(name, "SafeArrayGetLBound");
                break;
            case 21:
                sprintf(name, "SafeArrayLock");
                break;
            case 22:
                sprintf(name, "SafeArrayUnlock");
                break;
            case 23:
                sprintf(name, "SafeArrayAccessData");
                break;
            case 24:
                sprintf(name, "SafeArrayUnaccessData");
                break;
            case 25:
                sprintf(name, "SafeArrayGetElement");
                break;
            case 26:
                sprintf(name, "SafeArrayPutElement");
                break;
            case 27:
                sprintf(name, "SafeArrayCopy");
                break;
            case 28:
                sprintf(name, "DispGetParam");
                break;
            case 29:
                sprintf(name, "DispGetIDsOfNames");
                break;
            case 30:
                sprintf(name, "DispInvoke");
                break;
            case 31:
                sprintf(name, "CreateDispTypeInfo");
                break;
            case 32:
                sprintf(name, "CreateStdDispatch");
                break;
            case 33:
                sprintf(name, "RegisterActiveObject");
                break;
            case 34:
                sprintf(name, "RevokeActiveObject");
                break;
            case 35:
                sprintf(name, "GetActiveObject");
                break;
            case 36:
                sprintf(name, "SafeArrayAllocDescriptor");
                break;
            case 37:
                sprintf(name, "SafeArrayAllocData");
                break;
            case 38:
                sprintf(name, "SafeArrayDestroyDescriptor");
                break;
            case 39:
                sprintf(name, "SafeArrayDestroyData");
                break;
            case 40:
                sprintf(name, "SafeArrayRedim");
                break;
            case 41:
                sprintf(name, "SafeArrayAllocDescriptorEx");
                break;
            case 42:
                sprintf(name, "SafeArrayCreateEx");
                break;
            case 43:
                sprintf(name, "SafeArrayCreateVectorEx");
                break;
            case 44:
                sprintf(name, "SafeArraySetRecordInfo");
                break;
            case 45:
                sprintf(name, "SafeArrayGetRecordInfo");
                break;
            case 46:
                sprintf(name, "VarParseNumFromStr");
                break;
            case 47:
                sprintf(name, "VarNumFromParseNum");
                break;
            case 48:
                sprintf(name, "VarI2FromUI1");
                break;
            case 49:
                sprintf(name, "VarI2FromI4");
                break;
            case 50:
                sprintf(name, "VarI2FromR4");
                break;
            case 51:
                sprintf(name, "VarI2FromR8");
                break;
            case 52:
                sprintf(name, "VarI2FromCy");
                break;
            case 53:
                sprintf(name, "VarI2FromDate");
                break;
            case 54:
                sprintf(name, "VarI2FromStr");
                break;
            case 55:
                sprintf(name, "VarI2FromDisp");
                break;
            case 56:
                sprintf(name, "VarI2FromBool");
                break;
            case 57:
                sprintf(name, "SafeArraySetIID");
                break;
            case 58:
                sprintf(name, "VarI4FromUI1");
                break;
            case 59:
                sprintf(name, "VarI4FromI2");
                break;
            case 60:
                sprintf(name, "VarI4FromR4");
                break;
            case 61:
                sprintf(name, "VarI4FromR8");
                break;
            case 62:
                sprintf(name, "VarI4FromCy");
                break;
            case 63:
                sprintf(name, "VarI4FromDate");
                break;
            case 64:
                sprintf(name, "VarI4FromStr");
                break;
            case 65:
                sprintf(name, "VarI4FromDisp");
                break;
            case 66:
                sprintf(name, "VarI4FromBool");
                break;
            case 67:
                sprintf(name, "SafeArrayGetIID");
                break;
            case 68:
                sprintf(name, "VarR4FromUI1");
                break;
            case 69:
                sprintf(name, "VarR4FromI2");
                break;
            case 70:
                sprintf(name, "VarR4FromI4");
                break;
            case 71:
                sprintf(name, "VarR4FromR8");
                break;
            case 72:
                sprintf(name, "VarR4FromCy");
                break;
            case 73:
                sprintf(name, "VarR4FromDate");
                break;
            case 74:
                sprintf(name, "VarR4FromStr");
                break;
            case 75:
                sprintf(name, "VarR4FromDisp");
                break;
            case 76:
                sprintf(name, "VarR4FromBool");
                break;
            case 77:
                sprintf(name, "SafeArrayGetVartype");
                break;
            case 78:
                sprintf(name, "VarR8FromUI1");
                break;
            case 79:
                sprintf(name, "VarR8FromI2");
                break;
            case 80:
                sprintf(name, "VarR8FromI4");
                break;
            case 81:
                sprintf(name, "VarR8FromR4");
                break;
            case 82:
                sprintf(name, "VarR8FromCy");
                break;
            case 83:
                sprintf(name, "VarR8FromDate");
                break;
            case 84:
                sprintf(name, "VarR8FromStr");
                break;
            case 85:
                sprintf(name, "VarR8FromDisp");
                break;
            case 86:
                sprintf(name, "VarR8FromBool");
                break;
            case 87:
                sprintf(name, "VarFormat");
                break;
            case 88:
                sprintf(name, "VarDateFromUI1");
                break;
            case 89:
                sprintf(name, "VarDateFromI2");
                break;
            case 90:
                sprintf(name, "VarDateFromI4");
                break;
            case 91:
                sprintf(name, "VarDateFromR4");
                break;
            case 92:
                sprintf(name, "VarDateFromR8");
                break;
            case 93:
                sprintf(name, "VarDateFromCy");
                break;
            case 94:
                sprintf(name, "VarDateFromStr");
                break;
            case 95:
                sprintf(name, "VarDateFromDisp");
                break;
            case 96:
                sprintf(name, "VarDateFromBool");
                break;
            case 97:
                sprintf(name, "VarFormatDateTime");
                break;
            case 98:
                sprintf(name, "VarCyFromUI1");
                break;
            case 99:
                sprintf(name, "VarCyFromI2");
                break;
            case 100:
                sprintf(name, "VarCyFromI4");
                break;
            case 101:
                sprintf(name, "VarCyFromR4");
                break;
            case 102:
                sprintf(name, "VarCyFromR8");
                break;
            case 103:
                sprintf(name, "VarCyFromDate");
                break;
            case 104:
                sprintf(name, "VarCyFromStr");
                break;
            case 105:
                sprintf(name, "VarCyFromDisp");
                break;
            case 106:
                sprintf(name, "VarCyFromBool");
                break;
            case 107:
                sprintf(name, "VarFormatNumber");
                break;
            case 108:
                sprintf(name, "VarBstrFromUI1");
                break;
            case 109:
                sprintf(name, "VarBstrFromI2");
                break;
            case 110:
                sprintf(name, "VarBstrFromI4");
                break;
            case 111:
                sprintf(name, "VarBstrFromR4");
                break;
            case 112:
                sprintf(name, "VarBstrFromR8");
                break;
            case 113:
                sprintf(name, "VarBstrFromCy");
                break;
            case 114:
                sprintf(name, "VarBstrFromDate");
                break;
            case 115:
                sprintf(name, "VarBstrFromDisp");
                break;
            case 116:
                sprintf(name, "VarBstrFromBool");
                break;
            case 117:
                sprintf(name, "VarFormatPercent");
                break;
            case 118:
                sprintf(name, "VarBoolFromUI1");
                break;
            case 119:
                sprintf(name, "VarBoolFromI2");
                break;
            case 120:
                sprintf(name, "VarBoolFromI4");
                break;
            case 121:
                sprintf(name, "VarBoolFromR4");
                break;
            case 122:
                sprintf(name, "VarBoolFromR8");
                break;
            case 123:
                sprintf(name, "VarBoolFromDate");
                break;
            case 124:
                sprintf(name, "VarBoolFromCy");
                break;
            case 125:
                sprintf(name, "VarBoolFromStr");
                break;
            case 126:
                sprintf(name, "VarBoolFromDisp");
                break;
            case 127:
                sprintf(name, "VarFormatCurrency");
                break;
            case 128:
                sprintf(name, "VarWeekdayName");
                break;
            case 129:
                sprintf(name, "VarMonthName");
                break;
            case 130:
                sprintf(name, "VarUI1FromI2");
                break;
            case 131:
                sprintf(name, "VarUI1FromI4");
                break;
            case 132:
                sprintf(name, "VarUI1FromR4");
                break;
            case 133:
                sprintf(name, "VarUI1FromR8");
                break;
            case 134:
                sprintf(name, "VarUI1FromCy");
                break;
            case 135:
                sprintf(name, "VarUI1FromDate");
                break;
            case 136:
                sprintf(name, "VarUI1FromStr");
                break;
            case 137:
                sprintf(name, "VarUI1FromDisp");
                break;
            case 138:
                sprintf(name, "VarUI1FromBool");
                break;
            case 139:
                sprintf(name, "VarFormatFromTokens");
                break;
            case 140:
                sprintf(name, "VarTokenizeFormatString");
                break;
            case 141:
                sprintf(name, "VarAdd");
                break;
            case 142:
                sprintf(name, "VarAnd");
                break;
            case 143:
                sprintf(name, "VarDiv");
                break;
            case 144:
                sprintf(name, "DllCanUnloadNow");
                break;
            case 145:
                sprintf(name, "DllGetClassObject");
                break;
            case 146:
                sprintf(name, "DispCallFunc");
                break;
            case 147:
                sprintf(name, "VariantChangeTypeEx");
                break;
            case 148:
                sprintf(name, "SafeArrayPtrOfIndex");
                break;
            case 149:
                sprintf(name, "SysStringByteLen");
                break;
            case 150:
                sprintf(name, "SysAllocStringByteLen");
                break;
            case 151:
                sprintf(name, "DllRegisterServer");
                break;
            case 152:
                sprintf(name, "VarEqv");
                break;
            case 153:
                sprintf(name, "VarIdiv");
                break;
            case 154:
                sprintf(name, "VarImp");
                break;
            case 155:
                sprintf(name, "VarMod");
                break;
            case 156:
                sprintf(name, "VarMul");
                break;
            case 157:
                sprintf(name, "VarOr");
                break;
            case 158:
                sprintf(name, "VarPow");
                break;
            case 159:
                sprintf(name, "VarSub");
                break;
            case 160:
                sprintf(name, "CreateTypeLib");
                break;
            case 161:
                sprintf(name, "LoadTypeLib");
                break;
            case 162:
                sprintf(name, "LoadRegTypeLib");
                break;
            case 163:
                sprintf(name, "RegisterTypeLib");
                break;
            case 164:
                sprintf(name, "QueryPathOfRegTypeLib");
                break;
            case 165:
                sprintf(name, "LHashValOfNameSys");
                break;
            case 166:
                sprintf(name, "LHashValOfNameSysA");
                break;
            case 167:
                sprintf(name, "VarXor");
                break;
            case 168:
                sprintf(name, "VarAbs");
                break;
            case 169:
                sprintf(name, "VarFix");
                break;
            case 170:
                sprintf(name, "OaBuildVersion");
                break;
            case 171:
                sprintf(name, "ClearCustData");
                break;
            case 172:
                sprintf(name, "VarInt");
                break;
            case 173:
                sprintf(name, "VarNeg");
                break;
            case 174:
                sprintf(name, "VarNot");
                break;
            case 175:
                sprintf(name, "VarRound");
                break;
            case 176:
                sprintf(name, "VarCmp");
                break;
            case 177:
                sprintf(name, "VarDecAdd");
                break;
            case 178:
                sprintf(name, "VarDecDiv");
                break;
            case 179:
                sprintf(name, "VarDecMul");
                break;
            case 180:
                sprintf(name, "CreateTypeLib2");
                break;
            case 181:
                sprintf(name, "VarDecSub");
                break;
            case 182:
                sprintf(name, "VarDecAbs");
                break;
            case 183:
                sprintf(name, "LoadTypeLibEx");
                break;
            case 184:
                sprintf(name, "SystemTimeToVariantTime");
                break;
            case 185:
                sprintf(name, "VariantTimeToSystemTime");
                break;
            case 186:
                sprintf(name, "UnRegisterTypeLib");
                break;
            case 187:
                sprintf(name, "VarDecFix");
                break;
            case 188:
                sprintf(name, "VarDecInt");
                break;
            case 189:
                sprintf(name, "VarDecNeg");
                break;
            case 190:
                sprintf(name, "VarDecFromUI1");
                break;
            case 191:
                sprintf(name, "VarDecFromI2");
                break;
            case 192:
                sprintf(name, "VarDecFromI4");
                break;
            case 193:
                sprintf(name, "VarDecFromR4");
                break;
            case 194:
                sprintf(name, "VarDecFromR8");
                break;
            case 195:
                sprintf(name, "VarDecFromDate");
                break;
            case 196:
                sprintf(name, "VarDecFromCy");
                break;
            case 197:
                sprintf(name, "VarDecFromStr");
                break;
            case 198:
                sprintf(name, "VarDecFromDisp");
                break;
            case 199:
                sprintf(name, "VarDecFromBool");
                break;
            case 200:
                sprintf(name, "GetErrorInfo");
                break;
            case 201:
                sprintf(name, "SetErrorInfo");
                break;
            case 202:
                sprintf(name, "CreateErrorInfo");
                break;
            case 203:
                sprintf(name, "VarDecRound");
                break;
            case 204:
                sprintf(name, "VarDecCmp");
                break;
            case 205:
                sprintf(name, "VarI2FromI1");
                break;
            case 206:
                sprintf(name, "VarI2FromUI2");
                break;
            case 207:
                sprintf(name, "VarI2FromUI4");
                break;
            case 208:
                sprintf(name, "VarI2FromDec");
                break;
            case 209:
                sprintf(name, "VarI4FromI1");
                break;
            case 210:
                sprintf(name, "VarI4FromUI2");
                break;
            case 211:
                sprintf(name, "VarI4FromUI4");
                break;
            case 212:
                sprintf(name, "VarI4FromDec");
                break;
            case 213:
                sprintf(name, "VarR4FromI1");
                break;
            case 214:
                sprintf(name, "VarR4FromUI2");
                break;
            case 215:
                sprintf(name, "VarR4FromUI4");
                break;
            case 216:
                sprintf(name, "VarR4FromDec");
                break;
            case 217:
                sprintf(name, "VarR8FromI1");
                break;
            case 218:
                sprintf(name, "VarR8FromUI2");
                break;
            case 219:
                sprintf(name, "VarR8FromUI4");
                break;
            case 220:
                sprintf(name, "VarR8FromDec");
                break;
            case 221:
                sprintf(name, "VarDateFromI1");
                break;
            case 222:
                sprintf(name, "VarDateFromUI2");
                break;
            case 223:
                sprintf(name, "VarDateFromUI4");
                break;
            case 224:
                sprintf(name, "VarDateFromDec");
                break;
            case 225:
                sprintf(name, "VarCyFromI1");
                break;
            case 226:
                sprintf(name, "VarCyFromUI2");
                break;
            case 227:
                sprintf(name, "VarCyFromUI4");
                break;
            case 228:
                sprintf(name, "VarCyFromDec");
                break;
            case 229:
                sprintf(name, "VarBstrFromI1");
                break;
            case 230:
                sprintf(name, "VarBstrFromUI2");
                break;
            case 231:
                sprintf(name, "VarBstrFromUI4");
                break;
            case 232:
                sprintf(name, "VarBstrFromDec");
                break;
            case 233:
                sprintf(name, "VarBoolFromI1");
                break;
            case 234:
                sprintf(name, "VarBoolFromUI2");
                break;
            case 235:
                sprintf(name, "VarBoolFromUI4");
                break;
            case 236:
                sprintf(name, "VarBoolFromDec");
                break;
            case 237:
                sprintf(name, "VarUI1FromI1");
                break;
            case 238:
                sprintf(name, "VarUI1FromUI2");
                break;
            case 239:
                sprintf(name, "VarUI1FromUI4");
                break;
            case 240:
                sprintf(name, "VarUI1FromDec");
                break;
            case 241:
                sprintf(name, "VarDecFromI1");
                break;
            case 242:
                sprintf(name, "VarDecFromUI2");
                break;
            case 243:
                sprintf(name, "VarDecFromUI4");
                break;
            case 244:
                sprintf(name, "VarI1FromUI1");
                break;
            case 245:
                sprintf(name, "VarI1FromI2");
                break;
            case 246:
                sprintf(name, "VarI1FromI4");
                break;
            case 247:
                sprintf(name, "VarI1FromR4");
                break;
            case 248:
                sprintf(name, "VarI1FromR8");
                break;
            case 249:
                sprintf(name, "VarI1FromDate");
                break;
            case 250:
                sprintf(name, "VarI1FromCy");
                break;
            case 251:
                sprintf(name, "VarI1FromStr");
                break;
            case 252:
                sprintf(name, "VarI1FromDisp");
                break;
            case 253:
                sprintf(name, "VarI1FromBool");
                break;
            case 254:
                sprintf(name, "VarI1FromUI2");
                break;
            case 255:
                sprintf(name, "VarI1FromUI4");
                break;
            case 256:
                sprintf(name, "VarI1FromDec");
                break;
            case 257:
                sprintf(name, "VarUI2FromUI1");
                break;
            case 258:
                sprintf(name, "VarUI2FromI2");
                break;
            case 259:
                sprintf(name, "VarUI2FromI4");
                break;
            case 260:
                sprintf(name, "VarUI2FromR4");
                break;
            case 261:
                sprintf(name, "VarUI2FromR8");
                break;
            case 262:
                sprintf(name, "VarUI2FromDate");
                break;
            case 263:
                sprintf(name, "VarUI2FromCy");
                break;
            case 264:
                sprintf(name, "VarUI2FromStr");
                break;
            case 265:
                sprintf(name, "VarUI2FromDisp");
                break;
            case 266:
                sprintf(name, "VarUI2FromBool");
                break;
            case 267:
                sprintf(name, "VarUI2FromI1");
                break;
            case 268:
                sprintf(name, "VarUI2FromUI4");
                break;
            case 269:
                sprintf(name, "VarUI2FromDec");
                break;
            case 270:
                sprintf(name, "VarUI4FromUI1");
                break;
            case 271:
                sprintf(name, "VarUI4FromI2");
                break;
            case 272:
                sprintf(name, "VarUI4FromI4");
                break;
            case 273:
                sprintf(name, "VarUI4FromR4");
                break;
            case 274:
                sprintf(name, "VarUI4FromR8");
                break;
            case 275:
                sprintf(name, "VarUI4FromDate");
                break;
            case 276:
                sprintf(name, "VarUI4FromCy");
                break;
            case 277:
                sprintf(name, "VarUI4FromStr");
                break;
            case 278:
                sprintf(name, "VarUI4FromDisp");
                break;
            case 279:
                sprintf(name, "VarUI4FromBool");
                break;
            case 280:
                sprintf(name, "VarUI4FromI1");
                break;
            case 281:
                sprintf(name, "VarUI4FromUI2");
                break;
            case 282:
                sprintf(name, "VarUI4FromDec");
                break;
            case 283:
                sprintf(name, "BSTR_UserSize");
                break;
            case 284:
                sprintf(name, "BSTR_UserMarshal");
                break;
            case 285:
                sprintf(name, "BSTR_UserUnmarshal");
                break;
            case 286:
                sprintf(name, "BSTR_UserFree");
                break;
            case 287:
                sprintf(name, "VARIANT_UserSize");
                break;
            case 288:
                sprintf(name, "VARIANT_UserMarshal");
                break;
            case 289:
                sprintf(name, "VARIANT_UserUnmarshal");
                break;
            case 290:
                sprintf(name, "VARIANT_UserFree");
                break;
            case 291:
                sprintf(name, "LPSAFEARRAY_UserSize");
                break;
            case 292:
                sprintf(name, "LPSAFEARRAY_UserMarshal");
                break;
            case 293:
                sprintf(name, "LPSAFEARRAY_UserUnmarshal");
                break;
            case 294:
                sprintf(name, "LPSAFEARRAY_UserFree");
                break;
            case 295:
                sprintf(name, "LPSAFEARRAY_Size");
                break;
            case 296:
                sprintf(name, "LPSAFEARRAY_Marshal");
                break;
            case 297:
                sprintf(name, "LPSAFEARRAY_Unmarshal");
                break;
            case 298:
                sprintf(name, "VarDecCmpR8");
                break;
            case 299:
                sprintf(name, "VarCyAdd");
                break;
            case 300:
                sprintf(name, "DllUnregisterServer");
                break;
            case 301:
                sprintf(name, "OACreateTypeLib2");
                break;
            case 303:
                sprintf(name, "VarCyMul");
                break;
            case 304:
                sprintf(name, "VarCyMulI4");
                break;
            case 305:
                sprintf(name, "VarCySub");
                break;
            case 306:
                sprintf(name, "VarCyAbs");
                break;
            case 307:
                sprintf(name, "VarCyFix");
                break;
            case 308:
                sprintf(name, "VarCyInt");
                break;
            case 309:
                sprintf(name, "VarCyNeg");
                break;
            case 310:
                sprintf(name, "VarCyRound");
                break;
            case 311:
                sprintf(name, "VarCyCmp");
                break;
            case 312:
                sprintf(name, "VarCyCmpR8");
                break;
            case 313:
                sprintf(name, "VarBstrCat");
                break;
            case 314:
                sprintf(name, "VarBstrCmp");
                break;
            case 315:
                sprintf(name, "VarR8Pow");
                break;
            case 316:
                sprintf(name, "VarR4CmpR8");
                break;
            case 317:
                sprintf(name, "VarR8Round");
                break;
            case 318:
                sprintf(name, "VarCat");
                break;
            case 319:
                sprintf(name, "VarDateFromUdateEx");
                break;
            case 322:
                sprintf(name, "GetRecordInfoFromGuids");
                break;
            case 323:
                sprintf(name, "GetRecordInfoFromTypeInfo");
                break;
            case 325:
                sprintf(name, "SetVarConversionLocaleSetting");
                break;
            case 326:
                sprintf(name, "GetVarConversionLocaleSetting");
                break;
            case 327:
                sprintf(name, "SetOaNoCache");
                break;
            case 329:
                sprintf(name, "VarCyMulI8");
                break;
            case 330:
                sprintf(name, "VarDateFromUdate");
                break;
            case 331:
                sprintf(name, "VarUdateFromDate");
                break;
            case 332:
                sprintf(name, "GetAltMonthNames");
                break;
            case 333:
                sprintf(name, "VarI8FromUI1");
                break;
            case 334:
                sprintf(name, "VarI8FromI2");
                break;
            case 335:
                sprintf(name, "VarI8FromR4");
                break;
            case 336:
                sprintf(name, "VarI8FromR8");
                break;
            case 337:
                sprintf(name, "VarI8FromCy");
                break;
            case 338:
                sprintf(name, "VarI8FromDate");
                break;
            case 339:
                sprintf(name, "VarI8FromStr");
                break;
            case 340:
                sprintf(name, "VarI8FromDisp");
                break;
            case 341:
                sprintf(name, "VarI8FromBool");
                break;
            case 342:
                sprintf(name, "VarI8FromI1");
                break;
            case 343:
                sprintf(name, "VarI8FromUI2");
                break;
            case 344:
                sprintf(name, "VarI8FromUI4");
                break;
            case 345:
                sprintf(name, "VarI8FromDec");
                break;
            case 346:
                sprintf(name, "VarI2FromI8");
                break;
            case 347:
                sprintf(name, "VarI2FromUI8");
                break;
            case 348:
                sprintf(name, "VarI4FromI8");
                break;
            case 349:
                sprintf(name, "VarI4FromUI8");
                break;
            case 360:
                sprintf(name, "VarR4FromI8");
                break;
            case 361:
                sprintf(name, "VarR4FromUI8");
                break;
            case 362:
                sprintf(name, "VarR8FromI8");
                break;
            case 363:
                sprintf(name, "VarR8FromUI8");
                break;
            case 364:
                sprintf(name, "VarDateFromI8");
                break;
            case 365:
                sprintf(name, "VarDateFromUI8");
                break;
            case 366:
                sprintf(name, "VarCyFromI8");
                break;
            case 367:
                sprintf(name, "VarCyFromUI8");
                break;
            case 368:
                sprintf(name, "VarBstrFromI8");
                break;
            case 369:
                sprintf(name, "VarBstrFromUI8");
                break;
            case 370:
                sprintf(name, "VarBoolFromI8");
                break;
            case 371:
                sprintf(name, "VarBoolFromUI8");
                break;
            case 372:
                sprintf(name, "VarUI1FromI8");
                break;
            case 373:
                sprintf(name, "VarUI1FromUI8");
                break;
            case 374:
                sprintf(name, "VarDecFromI8");
                break;
            case 375:
                sprintf(name, "VarDecFromUI8");
                break;
            case 376:
                sprintf(name, "VarI1FromI8");
                break;
            case 377:
                sprintf(name, "VarI1FromUI8");
                break;
            case 378:
                sprintf(name, "VarUI2FromI8");
                break;
            case 379:
                sprintf(name, "VarUI2FromUI8");
                break;
            case 401:
                sprintf(name, "OleLoadPictureEx");
                break;
            case 402:
                sprintf(name, "OleLoadPictureFileEx");
                break;
            case 411:
                sprintf(name, "SafeArrayCreateVector");
                break;
            case 412:
                sprintf(name, "SafeArrayCopyData");
                break;
            case 413:
                sprintf(name, "VectorFromBstr");
                break;
            case 414:
                sprintf(name, "BstrFromVector");
                break;
            case 415:
                sprintf(name, "OleIconToCursor");
                break;
            case 416:
                sprintf(name, "OleCreatePropertyFrameIndirect");
                break;
            case 417:
                sprintf(name, "OleCreatePropertyFrame");
                break;
            case 418:
                sprintf(name, "OleLoadPicture");
                break;
            case 419:
                sprintf(name, "OleCreatePictureIndirect");
                break;
            case 420:
                sprintf(name, "OleCreateFontIndirect");
                break;
            case 421:
                sprintf(name, "OleTranslateColor");
                break;
            case 422:
                sprintf(name, "OleLoadPictureFile");
                break;
            case 423:
                sprintf(name, "OleSavePictureFile");
                break;
            case 424:
                sprintf(name, "OleLoadPicturePath");
                break;
            case 425:
                sprintf(name, "VarUI4FromI8");
                break;
            case 426:
                sprintf(name, "VarUI4FromUI8");
                break;
            case 427:
                sprintf(name, "VarI8FromUI8");
                break;
            case 428:
                sprintf(name, "VarUI8FromI8");
                break;
            case 429:
                sprintf(name, "VarUI8FromUI1");
                break;
            case 430:
                sprintf(name, "VarUI8FromI2");
                break;
            case 431:
                sprintf(name, "VarUI8FromR4");
                break;
            case 432:
                sprintf(name, "VarUI8FromR8");
                break;
            case 433:
                sprintf(name, "VarUI8FromCy");
                break;
            case 434:
                sprintf(name, "VarUI8FromDate");
                break;
            case 435:
                sprintf(name, "VarUI8FromStr");
                break;
            case 436:
                sprintf(name, "VarUI8FromDisp");
                break;
            case 437:
                sprintf(name, "VarUI8FromBool");
                break;
            case 438:
                sprintf(name, "VarUI8FromI1");
                break;
            case 439:
                sprintf(name, "VarUI8FromUI2");
                break;
            case 440:
                sprintf(name, "VarUI8FromUI4");
                break;
            case 441:
                sprintf(name, "VarUI8FromDec");
                break;
            case 442:
                sprintf(name, "RegisterTypeLibForUser");
                break;
            case 443:
                sprintf(name, "UnRegisterTypeLibForUser");
                break;
            default:
                break;
        }
    }

    if (name[0] == '\0')
        sprintf(name, "ord%u", ord);

    return cli_safer_strdup(name);
}

static int validate_impname(const char *name, uint32_t length, int dll)
{
    uint32_t i    = 0;
    const char *c = name;

    if (!name || length == 0)
        return 1;

    while (i < length && *c != '\0') {
        if ((*c >= '0' && *c <= '9') ||
            (*c >= 'a' && *c <= 'z') ||
            (*c >= 'A' && *c <= 'Z') ||
            (*c == '_') ||
            (dll && *c == '.')) {

            c++;
            i++;
        } else
            return 0;
    }

    return 1;
}

static inline int hash_impfns(cli_ctx *ctx, void **hashctx, uint32_t *impsz, struct pe_image_import_descriptor *image, const char *dllname, struct cli_exe_info *peinfo, int *first)
{
    uint32_t thuoff = 0, offset;
    fmap_t *map     = ctx->fmap;
    size_t dlllen = 0, fsize = map->len;
    unsigned int err = 0;
    int num_fns = 0, ret = CL_SUCCESS;
    const char *buffer;
    cli_hash_type_t type;
    json_object *imptbl = NULL;

    if (image->u.OriginalFirstThunk)
        thuoff = cli_rawaddr(image->u.OriginalFirstThunk, peinfo->sections, peinfo->nsections, &err, fsize, peinfo->hdr_size);
    if (err || thuoff == 0)
        thuoff = cli_rawaddr(image->FirstThunk, peinfo->sections, peinfo->nsections, &err, fsize, peinfo->hdr_size);
    if (err) {
        cli_dbgmsg("scan_pe: invalid rva for image first thunk\n");
        return CL_EFORMAT;
    }

    if (ctx->wrkproperty) {
        imptbl = cli_jsonarray(ctx->wrkproperty, "ImportTable");
        if (!imptbl) {
            cli_dbgmsg("scan_pe: cannot allocate import table json object\n");
            return CL_EMEM;
        }
    }

#define UPDATE_IMPHASH()                                                            \
    do {                                                                            \
        if (funcname) {                                                             \
            size_t i, j;                                                            \
            char *fname;                                                            \
            size_t funclen;                                                         \
                                                                                    \
            if (dlllen == 0) {                                                      \
                char *ext = strstr(dllname, ".");                                   \
                                                                                    \
                if (ext && (strncasecmp(ext, ".ocx", 4) == 0 ||                     \
                            strncasecmp(ext, ".sys", 4) == 0 ||                     \
                            strncasecmp(ext, ".dll", 4) == 0))                      \
                    dlllen = ext - dllname;                                         \
                else                                                                \
                    dlllen = strlen(dllname);                                       \
            }                                                                       \
                                                                                    \
            funclen = strlen(funcname);                                             \
            if (validate_impname(funcname, funclen, 1) == 0) {                      \
                cli_dbgmsg("scan_pe: invalid name for imported function\n");        \
                ret = CL_EFORMAT;                                                   \
                break;                                                              \
            }                                                                       \
                                                                                    \
            fname = cli_max_calloc(funclen + dlllen + 3, sizeof(char));             \
            if (fname == NULL) {                                                    \
                cli_dbgmsg("scan_pe: cannot allocate memory for imphash string\n"); \
                ret = CL_EMEM;                                                      \
                break;                                                              \
            }                                                                       \
            j = 0;                                                                  \
            if (!*first)                                                            \
                fname[j++] = ',';                                                   \
            for (i = 0; i < dlllen; i++, j++)                                       \
                fname[j] = tolower(dllname[i]);                                     \
            fname[j++] = '.';                                                       \
            for (i = 0; i < funclen; i++, j++)                                      \
                fname[j] = tolower(funcname[i]);                                    \
                                                                                    \
            if (imptbl) {                                                           \
                char *jname = *first ? fname : fname + 1;                           \
                cli_jsonstr(imptbl, NULL, jname);                                   \
            }                                                                       \
                                                                                    \
            for (type = CLI_HASH_MD5; type < CLI_HASH_AVAIL_TYPES; type++)          \
                cl_update_hash(hashctx[type], fname, strlen(fname));                \
            *impsz += strlen(fname);                                                \
                                                                                    \
            *first = 0;                                                             \
            free(fname);                                                            \
        }                                                                           \
    } while (0)

    if (!peinfo->is_pe32plus) {
        struct pe_image_thunk32 thunk32;

        while ((num_fns < PE_MAXIMPORTS) && (fmap_readn(map, &thunk32, thuoff, sizeof(struct pe_image_thunk32)) == sizeof(struct pe_image_thunk32)) && (thunk32.u.Ordinal != 0)) {
            char *funcname = NULL;
            uint32_t temp;

            thuoff += sizeof(struct pe_image_thunk32);

            temp              = EC32(thunk32.u.Ordinal);
            thunk32.u.Ordinal = temp;

            if (!(thunk32.u.Ordinal & PE_IMAGEDIR_ORDINAL_FLAG32)) {
                offset = cli_rawaddr(thunk32.u.Function, peinfo->sections, peinfo->nsections, &err, fsize, peinfo->hdr_size);

                if (!ret) {
                    /* Hint field is a uint16_t and precedes the Name field */
                    if ((buffer = fmap_need_off_once(map, offset + sizeof(uint16_t), MIN(PE_MAXNAMESIZE, fsize - offset))) != NULL) {
                        funcname = CLI_STRNDUP(buffer, MIN(PE_MAXNAMESIZE, fsize - offset));
                        if (funcname == NULL) {
                            cli_dbgmsg("scan_pe: cannot duplicate function name\n");
                            return CL_EMEM;
                        }
                    }
                }
            } else {
                /* ordinal lookup */
                funcname = pe_ordinal(dllname, thunk32.u.Ordinal & 0xFFFF);
                if (funcname == NULL) {
                    cli_dbgmsg("scan_pe: cannot duplicate function name\n");
                    return CL_EMEM;
                }
            }

            UPDATE_IMPHASH();
            free(funcname);
            if (ret != CL_SUCCESS)
                return ret;
        }
    } else {
        struct pe_image_thunk64 thunk64;

        while ((num_fns < PE_MAXIMPORTS) && (fmap_readn(map, &thunk64, thuoff, sizeof(struct pe_image_thunk64)) == sizeof(struct pe_image_thunk64)) && (thunk64.u.Ordinal != 0)) {
            char *funcname = NULL;

            // Temporary variable so we don't have overlapping writes with the EC32 reads.
            uint64_t temp;

            thuoff += sizeof(struct pe_image_thunk64);

            temp              = EC64(thunk64.u.Ordinal);
            thunk64.u.Ordinal = temp;

            if (!(thunk64.u.Ordinal & PE_IMAGEDIR_ORDINAL_FLAG64)) {
                offset = cli_rawaddr(thunk64.u.Function, peinfo->sections, peinfo->nsections, &err, fsize, peinfo->hdr_size);

                if (!err) {
                    /* Hint field is a uint16_t and precedes the Name field */
                    if ((buffer = fmap_need_off_once(map, offset + sizeof(uint16_t), MIN(PE_MAXNAMESIZE, fsize - offset))) != NULL) {
                        funcname = CLI_STRNDUP(buffer, MIN(PE_MAXNAMESIZE, fsize - offset));
                        if (funcname == NULL) {
                            cli_dbgmsg("scan_pe: cannot duplicate function name\n");
                            return CL_EMEM;
                        }
                    }
                }
            } else {
                /* ordinal lookup */
                funcname = pe_ordinal(dllname, thunk64.u.Ordinal & 0xFFFF);
                if (funcname == NULL) {
                    cli_dbgmsg("scan_pe: cannot duplicate function name\n");
                    return CL_EMEM;
                }
            }

            UPDATE_IMPHASH();
            free(funcname);
            if (ret != CL_SUCCESS)
                return ret;
        }
    }

    return CL_SUCCESS;
}

static cl_error_t hash_imptbl(cli_ctx *ctx, unsigned char **digest, uint32_t *impsz, int *genhash, struct cli_exe_info *peinfo)
{
    cl_error_t status = CL_ERROR;
    cl_error_t ret;
    struct pe_image_import_descriptor image = {0};
    const struct pe_image_import_descriptor *impdes;
    fmap_t *map = ctx->fmap;
    size_t left, fsize = map->len;
    uint32_t impoff, offset;
    const char *buffer;
    void *hashctx[CLI_HASH_AVAIL_TYPES] = {0};
    cli_hash_type_t type;
    int nimps = 0;
    unsigned int err;
    int first          = 1;
    bool needed_impoff = false;

    /* If the PE doesn't have an import table then skip it. This is an
     * uncommon case but can happen. */
    if (peinfo->dirs[1].VirtualAddress == 0 || peinfo->dirs[1].Size == 0) {
        cli_dbgmsg("scan_pe: import table data dir does not exist (skipping .imp scanning)\n");
        status = CL_BREAK;
        goto done;
    }

    // TODO Add EC32 wrappers
    impoff = cli_rawaddr(peinfo->dirs[1].VirtualAddress, peinfo->sections, peinfo->nsections, &err, fsize, peinfo->hdr_size);
    if (err || impoff + peinfo->dirs[1].Size > fsize) {
        cli_dbgmsg("scan_pe: invalid rva for import table data\n");
        status = CL_BREAK;
        goto done;
    }

    // TODO Add EC32 wrapper
    impdes = (const struct pe_image_import_descriptor *)fmap_need_off(map, impoff, peinfo->dirs[1].Size);
    if (impdes == NULL) {
        cli_dbgmsg("scan_pe: failed to acquire fmap buffer\n");
        status = CL_EREAD;
        goto done;
    }
    needed_impoff = true;

    /* Safety: We can trust peinfo->dirs[1].Size only because `fmap_need_off()` (above)
     * would have failed if the size exceeds the end of the fmap. */
    left = peinfo->dirs[1].Size;

    if (genhash[CLI_HASH_MD5]) {
        hashctx[CLI_HASH_MD5] = cl_hash_init("md5");
        if (hashctx[CLI_HASH_MD5] == NULL) {
            status = CL_EMEM;
            goto done;
        }
    }
    if (genhash[CLI_HASH_SHA1]) {
        hashctx[CLI_HASH_SHA1] = cl_hash_init("sha1");
        if (hashctx[CLI_HASH_SHA1] == NULL) {
            status = CL_EMEM;
            goto done;
        }
    }
    if (genhash[CLI_HASH_SHA256]) {
        hashctx[CLI_HASH_SHA256] = cl_hash_init("sha256");
        if (hashctx[CLI_HASH_SHA256] == NULL) {
            status = CL_EMEM;
            goto done;
        }
    }

    while (left > sizeof(struct pe_image_import_descriptor) && nimps < PE_MAXIMPORTS) {
        char *dllname = NULL;

        // Temporary variable so we don't have overlapping writes with the EC32 reads.
        uint32_t temp;

        /* Get copy of image import descriptor to work with */
        memcpy(&image, impdes, sizeof(struct pe_image_import_descriptor));

        if (image.Name == 0) {
            // Name RVA is 0, which doesn't seem right. I guess we skip the rest?
            // TODO: Is that right?
            break;
        }

        /* Prepare for next iteration, in case we need to `continue;` */
        left -= sizeof(struct pe_image_import_descriptor);
        nimps++;
        impdes++;

        /* Endian Conversion */
        temp                       = EC32(image.u.OriginalFirstThunk);
        image.u.OriginalFirstThunk = temp;
        temp                       = EC32(image.TimeDateStamp);
        image.TimeDateStamp        = temp;
        temp                       = EC32(image.ForwarderChain);
        image.ForwarderChain       = temp;
        temp                       = EC32(image.Name);
        image.Name                 = temp;
        temp                       = EC32(image.FirstThunk);
        image.FirstThunk           = temp;

        /* DLL name acquisition */
        offset = cli_rawaddr(image.Name, peinfo->sections, peinfo->nsections, &err, fsize, peinfo->hdr_size);
        if (err || offset > fsize) {
            cli_dbgmsg("scan_pe: invalid rva for dll name\n");
            /* TODO: ignore or return? */
            /*
            continue;
             */
            status = CL_EFORMAT;
            goto done;
        }

        buffer = fmap_need_off_once(map, offset, MIN(PE_MAXNAMESIZE, fsize - offset));
        if (buffer == NULL) {
            cli_dbgmsg("scan_pe: failed to read name for dll\n");
            status = CL_EREAD;
            goto done;
        }

        if (validate_impname(dllname, MIN(PE_MAXNAMESIZE, fsize - offset), 1) == 0) {
            cli_dbgmsg("scan_pe: invalid name for imported dll\n");
            status = CL_EFORMAT;
            goto done;
        }

        dllname = CLI_STRNDUP(buffer, MIN(PE_MAXNAMESIZE, fsize - offset));
        if (dllname == NULL) {
            cli_dbgmsg("scan_pe: cannot duplicate dll name\n");
            status = CL_EMEM;
            goto done;
        }

        /* DLL function handling - inline function */
        ret = hash_impfns(ctx, hashctx, impsz, &image, dllname, peinfo, &first);
        free(dllname);
        dllname = NULL;
        if (ret != CL_SUCCESS) {
            status = ret;
            goto done;
        }
    }

    for (type = CLI_HASH_MD5; type < CLI_HASH_AVAIL_TYPES; type++) {
        cl_finish_hash(hashctx[type], digest[type]);
        hashctx[type] = NULL;
    }

    status = CL_SUCCESS;

done:
    if (needed_impoff) {
        fmap_unneed_off(map, impoff, peinfo->dirs[1].Size);
    }

    for (type = CLI_HASH_MD5; type < CLI_HASH_AVAIL_TYPES; type++) {
        if (NULL != hashctx[type]) {
            cl_hash_destroy(hashctx[type]);
        }
    }

    return status;
}

static cl_error_t scan_pe_imp(cli_ctx *ctx, struct cli_exe_info *peinfo)
{
    struct cli_matcher *imp = ctx->engine->hm_imp;
    unsigned char *hashset[CLI_HASH_AVAIL_TYPES];
    const char *virname = NULL;
    int genhash[CLI_HASH_AVAIL_TYPES];
    uint32_t impsz = 0;
    cli_hash_type_t type;
    cl_error_t ret = CL_CLEAN;

    /* pick hashtypes to generate */
    for (type = CLI_HASH_MD5; type < CLI_HASH_AVAIL_TYPES; type++) {
        genhash[type] = cli_hm_have_any(imp, type);
        if (genhash[type]) {
            hashset[type] = malloc(hashlen[type]);
            if (!hashset[type]) {
                cli_errmsg("scan_pe: malloc failed!\n");
                for (; type > 0;)
                    free(hashset[--type]);
                return CL_EMEM;
            }
        } else {
            hashset[type] = NULL;
        }
    }

    /* Force md5 hash generation for debug and preclass */
    if ((cli_debug_flag || ctx->wrkproperty) && !genhash[CLI_HASH_MD5]) {
        genhash[CLI_HASH_MD5] = 1;
        hashset[CLI_HASH_MD5] = calloc(hashlen[CLI_HASH_MD5], sizeof(char));
        if (!hashset[CLI_HASH_MD5]) {
            cli_errmsg("scan_pe: calloc failed!\n");
            for (type = CLI_HASH_MD5; type < CLI_HASH_AVAIL_TYPES; type++)
                free(hashset[type]);
            return CL_EMEM;
        }
    }

    /* Generate hashes */
    ret = hash_imptbl(ctx, hashset, &impsz, genhash, peinfo);
    if (ret != CL_SUCCESS) {
        for (type = CLI_HASH_MD5; type < CLI_HASH_AVAIL_TYPES; type++) {
            free(hashset[type]);
        }
        if (ret == CL_BREAK) {
            ret = CL_SUCCESS;
        }
        return ret;
    }

    /* Print hash */
    if (cli_debug_flag || ctx->wrkproperty) {
        char *dstr = cli_str2hex((char *)hashset[CLI_HASH_MD5], hashlen[CLI_HASH_MD5]);
        cli_dbgmsg("IMP: %s:%u\n", dstr ? (char *)dstr : "(NULL)", impsz);

        if (ctx->wrkproperty)
            cli_jsonstr(ctx->wrkproperty, "Imphash", dstr ? dstr : "(NULL)");

        if (dstr)
            free(dstr);
    }

    /* Do scans */
    for (type = CLI_HASH_MD5; type < CLI_HASH_AVAIL_TYPES; type++) {
        if (cli_hm_scan(hashset[type], impsz, &virname, imp, type) == CL_VIRUS) {
            ret = cli_append_virus(ctx, virname);
            if (ret != CL_SUCCESS) {
                break;
            }
        }
        if (cli_hm_scan_wild(hashset[type], &virname, imp, type) == CL_VIRUS) {
            cli_append_virus(ctx, virname);
            if (ret != CL_SUCCESS) {
                break;
            }
        }
    }

    for (type = CLI_HASH_MD5; type < CLI_HASH_AVAIL_TYPES; type++)
        free(hashset[type]);
    return ret;
}

static struct json_object *get_pe_property(cli_ctx *ctx)
{
    struct json_object *pe;

    if (!(ctx) || !(ctx->wrkproperty))
        return NULL;

    if (!json_object_object_get_ex(ctx->wrkproperty, "PE", &pe)) {
        pe = json_object_new_object();
        if (!(pe))
            return NULL;

        json_object_object_add(ctx->wrkproperty, "PE", pe);
    }

    return pe;
}

static void pe_add_heuristic_property(cli_ctx *ctx, const char *key)
{
    struct json_object *heuristics;
    struct json_object *pe;
    struct json_object *str;

    pe = get_pe_property(ctx);
    if (!(pe))
        return;

    if (!json_object_object_get_ex(pe, "Heuristics", &heuristics)) {
        heuristics = json_object_new_array();
        if (!(heuristics))
            return;

        json_object_object_add(pe, "Heuristics", heuristics);
    }

    str = json_object_new_string(key);
    if (!(str))
        return;

    json_object_array_add(heuristics, str);
}

static struct json_object *get_section_json(cli_ctx *ctx)
{
    struct json_object *pe;
    struct json_object *section;

    pe = get_pe_property(ctx);
    if (!(pe))
        return NULL;

    if (!json_object_object_get_ex(pe, "Sections", &section)) {
        section = json_object_new_array();
        if (!(section))
            return NULL;

        json_object_object_add(pe, "Sections", section);
    }

    return section;
}

static void add_section_info(cli_ctx *ctx, struct cli_exe_section *s)
{
    struct json_object *sections, *section, *obj;
    char address[16];

    sections = get_section_json(ctx);
    if (!(sections))
        return;

    section = json_object_new_object();
    if (!(section))
        return;

    obj = json_object_new_int((int32_t)(s->rsz));
    if (!(obj))
        return;

    json_object_object_add(section, "RawSize", obj);

    obj = json_object_new_int((int32_t)(s->raw));
    if (!(obj))
        return;

    json_object_object_add(section, "RawOffset", obj);

    snprintf(address, sizeof(address), "0x%08x", s->rva);

    obj = json_object_new_string(address);
    if (!(obj))
        return;

    json_object_object_add(section, "VirtualAddress", obj);

    obj = json_object_new_boolean((s->chr & 0x20000000) == 0x20000000);
    if ((obj))
        json_object_object_add(section, "Executable", obj);

    obj = json_object_new_boolean((s->chr & 0x80000000) == 0x80000000);
    if ((obj))
        json_object_object_add(section, "Writable", obj);

    obj = json_object_new_boolean(s->urva >> 31 || s->uvsz >> 31 || (s->rsz && s->uraw >> 31) || s->ursz >> 31);
    if ((obj))
        json_object_object_add(section, "Signed", obj);

    json_object_array_add(sections, section);
}

int cli_scanpe(cli_ctx *ctx)
{
    uint8_t polipos = 0;
    char epbuff[4096], *tempfile;
    size_t epsize;
    size_t bytes;
    unsigned int i, j, found, upx_success = 0, err;
    unsigned int ssize = 0, dsize = 0, corrupted_cur;
    int (*upxfn)(const char *, uint32_t, char *, uint32_t *, uint32_t, uint32_t, uint32_t) = NULL;
    const char *src                                                                        = NULL;
    char *dest                                                                             = NULL;
    int ndesc;
    cl_error_t ret = CL_SUCCESS;
    cl_error_t peheader_ret;
    int upack = 0;
    size_t fsize;
    struct cli_bc_ctx *bc_ctx;
    fmap_t *map;
    struct cli_pe_hook_data pedata;
#ifdef HAVE__INTERNAL__SHA_COLLECT
    int sha_collect = ctx->sha_collect;
#endif
    int toval                   = 0;
    struct json_object *pe_json = NULL;

    if (!ctx) {
        cli_errmsg("cli_scanpe: ctx == NULL\n");
        return CL_ENULLARG;
    }

    if (cli_json_timeout_cycle_check(ctx, &toval) != CL_SUCCESS) {
        return CL_ETIMEOUT;
    }

    if (SCAN_COLLECT_METADATA) {
        pe_json = get_pe_property(ctx);
    }

    map   = ctx->fmap;
    fsize = map->len;

    struct cli_exe_info _peinfo;
    struct cli_exe_info *peinfo = &_peinfo;

    uint32_t opts = CLI_PEHEADER_OPT_DBG_PRINT_INFO | CLI_PEHEADER_OPT_REMOVE_MISSING_SECTIONS;

    if (SCAN_COLLECT_METADATA) {
        opts |= CLI_PEHEADER_OPT_COLLECT_JSON;
    }

    if (DETECT_BROKEN_PE) {
        opts |= CLI_PEHEADER_OPT_STRICT_ON_PE_ERRORS;
    }

    cli_exe_info_init(peinfo, 0);

    peheader_ret = cli_peheader(map, peinfo, opts, ctx);

    // Warn the user if PE header parsing failed - if it's a binary that runs
    // successfully on Windows, we need to relax our PE parsing standards so
    // that we make sure the executable gets scanned appropriately

    switch (peheader_ret) {
        case CL_EFORMAT:
            ret = CL_SUCCESS;
            if (DETECT_BROKEN_PE) {
                ret = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Executable");
            }
            cli_dbgmsg("cli_scanpe: PE header appears broken - won't attempt .mdb / .imp / PE-specific BC rule matching or exe unpacking\n");
            cli_exe_info_destroy(peinfo);
            return ret;

        case CL_ERROR:
            ret = CL_SUCCESS;
            cli_dbgmsg("cli_scanpe: An error occurred when parsing the PE header - won't attempt .mdb / .imp / PE-specific BC rule matching or exe unpacking\n");
            cli_exe_info_destroy(peinfo);
            return ret;

        case CL_ETIMEOUT:
            ret = CL_ETIMEOUT;
            cli_dbgmsg("cli_scanpe: JSON creation timed out - won't attempt .mdb / .imp / PE-specific BC rule matching or exe unpacking\n");
            cli_exe_info_destroy(peinfo);
            return ret;

        default:
            break;
    }

    if (!peinfo->is_pe32plus) { /* PE */
        if (DCONF & PE_CONF_UPACK) {
            upack = (EC16(peinfo->file_hdr.SizeOfOptionalHeader) == 0x148);
        }
    }

    for (i = 0; i < peinfo->nsections; i++) {
        if (peinfo->sections[i].rsz) { /* Don't bother with virtual only sections */
            // TODO Regarding the commented out check below:
            // This used to check that the section name was NULL, but now that
            // header parsing is done in cli_peheader (and since we don't yet
            // make the section name available via peinfo->sections[]) it would
            // be a pain to fetch the name here.  Since this is the only place
            // in cli_scanpe that needs the section name, and since I verified
            // that detection still occurs for Polipos without this check,
            // let's leave it commented out for now.
            if (SCAN_HEURISTICS && (DCONF & PE_CONF_POLIPOS) && /*!*peinfo->sections[i].sname &&*/ peinfo->sections[i].vsz > 40000 && peinfo->sections[i].vsz < 70000 && peinfo->sections[i].chr == 0xe0000060) polipos = i;

            /* check hash section sigs */
            if ((DCONF & PE_CONF_MD5SECT) && ctx->engine->hm_mdb) {
                ret = scan_pe_mdb(ctx, &(peinfo->sections[i]));
                if (ret != CL_SUCCESS) {
                    if (ret != CL_VIRUS) {
                        cli_errmsg("cli_scanpe: scan_pe_mdb failed: %s!\n", cl_strerror(ret));
                    }
                    cli_dbgmsg("------------------------------------\n");
                    cli_exe_info_destroy(peinfo);
                    return ret;
                }
            }
        }
    }

    // TODO Don't bail out here
    if (peinfo->is_pe32plus) { /* Do not continue for PE32+ files */
        cli_exe_info_destroy(peinfo);
        return CL_CLEAN;
    }

    epsize = fmap_readn(map, epbuff, peinfo->ep, 4096);
    if ((size_t)-1 == epsize) {
        /* Do not continue, all future logic requires at least a partial read into epbuff */
        cli_exe_info_destroy(peinfo);
        return CL_CLEAN;
    }

    /* Disasm scan disabled since it's now handled by the bytecode */

    /* CLI_UNPTEMP("cli_scanpe: DISASM",(peinfo->sections,0)); */
    /* if(disasmbuf((unsigned char*)epbuff, epsize, ndesc)) */
    /*  ret = cli_scan_desc(ndesc, ctx, CL_TYPE_PE_DISASM, true, NULL, AC_SCAN_VIR); */
    /* close(ndesc); */
    /* if(ret == CL_VIRUS) { */
    /*  cli_exe_info_destroy(peinfo); */
    /*  CLI_TMPUNLK(); */
    /*  free(tempfile); */
    /*  return ret; */
    /* } */
    /* CLI_TMPUNLK(); */
    /* free(tempfile); */

    if (peinfo->overlay_start && peinfo->overlay_size > 0) {
        ret = cli_scanishield(ctx, peinfo->overlay_start, peinfo->overlay_size);
        if (ret != CL_SUCCESS) {
            cli_exe_info_destroy(peinfo);
            return ret;
        }
    }

    pedata.nsections = peinfo->nsections;
    pedata.ep        = peinfo->ep;
    pedata.offset    = 0;
    memcpy(&pedata.file_hdr, &(peinfo->file_hdr), sizeof(peinfo->file_hdr));
    // TODO no need to copy both of these for each binary
    memcpy(&pedata.opt32, &(peinfo->pe_opt.opt32), sizeof(peinfo->pe_opt.opt32));
    memcpy(&pedata.opt64, &(peinfo->pe_opt.opt64), sizeof(peinfo->pe_opt.opt64));
    memcpy(&pedata.dirs, &(peinfo->dirs), sizeof(peinfo->dirs));
    // Gross
    memcpy(&pedata.opt32_dirs, &(peinfo->dirs), sizeof(peinfo->dirs));
    memcpy(&pedata.opt64_dirs, &(peinfo->dirs), sizeof(peinfo->dirs));
    pedata.e_lfanew    = peinfo->e_lfanew;
    pedata.overlays    = peinfo->overlay_start;
    pedata.overlays_sz = peinfo->overlay_size;
    pedata.hdr_size    = peinfo->hdr_size;

    /* Bytecode BC_PE_ALL hook */
    bc_ctx = cli_bytecode_context_alloc();
    if (!bc_ctx) {
        cli_errmsg("cli_scanpe: can't allocate memory for bc_ctx\n");
        cli_exe_info_destroy(peinfo);
        return CL_EMEM;
    }

    cli_bytecode_context_setpe(bc_ctx, &pedata, peinfo->sections);
    cli_bytecode_context_setctx(bc_ctx, ctx);
    ret = cli_bytecode_runhook(ctx, ctx->engine, bc_ctx, BC_PE_ALL, map);
    switch (ret) {
        case CL_ENULLARG:
            cli_warnmsg("cli_scanpe: NULL argument supplied\n");
            break;
        case CL_VIRUS:
        case CL_BREAK:
            cli_exe_info_destroy(peinfo);
            cli_bytecode_context_destroy(bc_ctx);
            return ret == CL_VIRUS ? CL_VIRUS : CL_CLEAN;
        default:
            break;
    }
    cli_bytecode_context_destroy(bc_ctx);

    /* Attempt to run scans on import table */
    /* Run if there are existing signatures and/or preclassing */
    if (DCONF & PE_CONF_IMPTBL && (ctx->engine->hm_imp || ctx->wrkproperty)) {
        ret = scan_pe_imp(ctx, peinfo);
        switch (ret) {
            case CL_SUCCESS:
                break;
            case CL_ENULLARG:
                cli_warnmsg("cli_scanpe: NULL argument supplied\n");
                break;
            case CL_VIRUS:
            case CL_BREAK:
                cli_exe_info_destroy(peinfo);
                return ret == CL_VIRUS ? CL_VIRUS : CL_CLEAN;
            default:
                cli_exe_info_destroy(peinfo);
                return ret;
        }
    }
    /* Attempt to detect some popular polymorphic viruses */

    /* W32.Parite.B */
    if (SCAN_HEURISTICS && (DCONF & PE_CONF_PARITE) && !peinfo->is_dll && epsize == 4096 && peinfo->ep == peinfo->sections[peinfo->nsections - 1].raw) {
        const char *pt = cli_memstr(epbuff, 4040, "\x47\x65\x74\x50\x72\x6f\x63\x41\x64\x64\x72\x65\x73\x73\x00", 15);
        if (pt) {
            pt += 15;
            if ((((uint32_t)cli_readint32(pt) ^ (uint32_t)cli_readint32(pt + 4)) == 0x505a4f) && (((uint32_t)cli_readint32(pt + 8) ^ (uint32_t)cli_readint32(pt + 12)) == 0xffffb) && (((uint32_t)cli_readint32(pt + 16) ^ (uint32_t)cli_readint32(pt + 20)) == 0xb8)) {
                ret = cli_append_potentially_unwanted(ctx, "Heuristics.W32.Parite.B");
                if (ret != CL_SUCCESS) {
                    cli_exe_info_destroy(peinfo);
                    return ret;
                }
            }
        }
    }

    /* Kriz */
    if (SCAN_HEURISTICS && (DCONF & PE_CONF_KRIZ) && epsize >= 200 && CLI_ISCONTAINED(peinfo->sections[peinfo->nsections - 1].raw, peinfo->sections[peinfo->nsections - 1].rsz, peinfo->ep, 0x0fd2) && epbuff[1] == '\x9c' && epbuff[2] == '\x60') {
        enum { KZSTRASH,
               KZSCDELTA,
               KZSPDELTA,
               KZSGETSIZE,
               KZSXORPRFX,
               KZSXOR,
               KZSDDELTA,
               KZSLOOP,
               KZSTOP };
        uint8_t kzs[]    = {KZSTRASH, KZSCDELTA, KZSPDELTA, KZSGETSIZE, KZSTRASH, KZSXORPRFX, KZSXOR, KZSTRASH, KZSDDELTA, KZSTRASH, KZSLOOP, KZSTOP};
        uint8_t *kzstate = kzs;
        uint8_t *kzcode  = (uint8_t *)epbuff + 3;
        uint8_t kzdptr = 0xff, kzdsize = 0xff;
        int kzlen = 197, kzinitlen = 0xffff, kzxorlen = -1;
        cli_dbgmsg("cli_scanpe: in kriz\n");

        while (*kzstate != KZSTOP) {
            uint8_t op;
            if (kzlen <= 6)
                break;

            op = *kzcode++;
            kzlen--;

            switch (*kzstate) {
                case KZSTRASH:
                case KZSGETSIZE: {
                    int opsz = 0;
                    switch (op) {
                        case 0x81:
                            kzcode += 5;
                            kzlen -= 5;
                            break;
                        case 0xb8:
                        case 0xb9:
                        case 0xba:
                        case 0xbb:
                        case 0xbd:
                        case 0xbe:
                        case 0xbf:
                            if (*kzstate == KZSGETSIZE && cli_readint32(kzcode) == 0x0fd2) {
                                kzinitlen = kzlen - 5;
                                kzdsize   = op - 0xb8;
                                kzstate++;
                                op = 4; /* fake the register to avoid breaking out */

                                cli_dbgmsg("cli_scanpe: kriz: using #%d as size counter\n", kzdsize);
                            }
                            opsz = 4;
                            /* fall-through */
                        case 0x48:
                        case 0x49:
                        case 0x4a:
                        case 0x4b:
                        case 0x4d:
                        case 0x4e:
                        case 0x4f:
                            op &= 7;
                            if (op != kzdptr && op != kzdsize) {
                                kzcode += opsz;
                                kzlen -= opsz;
                                break;
                            }
                            /* fall-through */
                        default:
                            kzcode--;
                            kzlen++;
                            kzstate++;
                    }

                    break;
                }
                case KZSCDELTA:
                    if (op == 0xe8 && (uint32_t)cli_readint32(kzcode) < 0xff) {
                        kzlen -= *kzcode + 4;
                        kzcode += *kzcode + 4;
                        kzstate++;
                    } else {
                        *kzstate = KZSTOP;
                    }

                    break;
                case KZSPDELTA:
                    if ((op & 0xf8) == 0x58 && (kzdptr = op - 0x58) != 4) {
                        kzstate++;
                        cli_dbgmsg("cli_scanpe: kriz: using #%d as pointer\n", kzdptr);
                    } else {
                        *kzstate = KZSTOP;
                    }

                    break;
                case KZSXORPRFX:
                    kzstate++;
                    if (op == 0x3e) {
                        break;
                    }
                    /* fall-through */
                case KZSXOR:
                    if (op == 0x80 && *kzcode == kzdptr + 0xb0) {
                        kzxorlen = kzlen;
                        kzcode += +6;
                        kzlen -= +6;
                        kzstate++;
                    } else {
                        *kzstate = KZSTOP;
                    }

                    break;
                case KZSDDELTA:
                    if (op == kzdptr + 0x48)
                        kzstate++;
                    else
                        *kzstate = KZSTOP;

                    break;
                case KZSLOOP:
                    if (op == kzdsize + 0x48 && *kzcode == 0x75 && kzlen - (int8_t)kzcode[1] - 3 <= kzinitlen && kzlen - (int8_t)kzcode[1] >= kzxorlen) {
                        ret = cli_append_potentially_unwanted(ctx, "Heuristics.W32.Kriz");
                        if (ret != CL_SUCCESS) {
                            cli_exe_info_destroy(peinfo);
                            return ret;
                        }
                    }
                    cli_dbgmsg("cli_scanpe: kriz: loop out of bounds, corrupted sample?\n");
                    kzstate++;
            }
        }
    }

    /* W32.Magistr.A/B */
    if (SCAN_HEURISTICS && (DCONF & PE_CONF_MAGISTR) && !peinfo->is_dll && (peinfo->nsections > 1) && (peinfo->sections[peinfo->nsections - 1].chr & 0x80000000)) {
        uint32_t rsize, vsize, dam = 0;

        vsize = peinfo->sections[peinfo->nsections - 1].uvsz;
        rsize = peinfo->sections[peinfo->nsections - 1].rsz;
        if (rsize < peinfo->sections[peinfo->nsections - 1].ursz) {
            rsize = peinfo->sections[peinfo->nsections - 1].ursz;
            dam   = 1;
        }

        if (vsize >= 0x612c && rsize >= 0x612c && ((vsize & 0xff) == 0xec)) {
            int bw = rsize < 0x7000 ? rsize : 0x7000;
            const char *tbuff;

            if ((tbuff = fmap_need_off_once(map, peinfo->sections[peinfo->nsections - 1].raw + rsize - bw, 4096))) {
                if (cli_memstr(tbuff, 4091, "\xe8\x2c\x61\x00\x00", 5)) {
                    ret = cli_append_potentially_unwanted(ctx, dam ? "Heuristics.W32.Magistr.A.dam" : "Heuristics.W32.Magistr.A");
                    if (ret != CL_SUCCESS) {
                        cli_exe_info_destroy(peinfo);
                        return ret;
                    }
                }
            }
        } else if (rsize >= 0x7000 && vsize >= 0x7000 && ((vsize & 0xff) == 0xed)) {
            int bw = rsize < 0x8000 ? rsize : 0x8000;
            const char *tbuff;

            if ((tbuff = fmap_need_off_once(map, peinfo->sections[peinfo->nsections - 1].raw + rsize - bw, 4096))) {
                if (cli_memstr(tbuff, 4091, "\xe8\x04\x72\x00\x00", 5)) {
                    ret = cli_append_potentially_unwanted(ctx, dam ? "Heuristics.W32.Magistr.B.dam" : "Heuristics.W32.Magistr.B");
                    if (ret != CL_SUCCESS) {
                        cli_exe_info_destroy(peinfo);
                        return ret;
                    }
                }
            }
        }
    }

    /* W32.Polipos.A */
    // TODO Add endianness correction to SizeOfStackReserve access
    while (polipos && !peinfo->is_dll && peinfo->nsections > 2 && peinfo->nsections < 13 && peinfo->e_lfanew <= 0x800 && (EC16(peinfo->pe_opt.opt32.Subsystem) == 2 || EC16(peinfo->pe_opt.opt32.Subsystem) == 3) && EC16(peinfo->file_hdr.Machine) == 0x14c && peinfo->pe_opt.opt32.SizeOfStackReserve >= 0x80000) {
        uint32_t jump, jold, *jumps = NULL;
        const uint8_t *code;
        unsigned int xsjs = 0;

        if (peinfo->sections[0].rsz > CLI_MAX_ALLOCATION)
            break;
        if (peinfo->sections[0].rsz < 5)
            break;
        if (!(code = fmap_need_off_once(map, peinfo->sections[0].raw, peinfo->sections[0].rsz)))
            break;

        for (i = 0; i < peinfo->sections[0].rsz - 5; i++) {
            if ((uint8_t)(code[i] - 0xe8) > 1)
                continue;

            jump = cli_rawaddr(peinfo->sections[0].rva + i + 5 + cli_readint32(&code[i + 1]), peinfo->sections, peinfo->nsections, &err, fsize, peinfo->hdr_size);
            if (err || !CLI_ISCONTAINED(peinfo->sections[polipos].raw, peinfo->sections[polipos].rsz, jump, 9))
                continue;

            if (xsjs % 128 == 0) {
                if (xsjs == 1280)
                    break;

                if (!(jumps = (uint32_t *)cli_max_realloc_or_free(jumps, (xsjs + 128) * sizeof(uint32_t)))) {
                    cli_exe_info_destroy(peinfo);
                    return CL_EMEM;
                }
            }

            j = 0;
            for (; j < xsjs; j++) {
                if (jumps[j] < jump)
                    continue;
                if (jumps[j] == jump) {
                    xsjs--;
                    break;
                }

                jold     = jumps[j];
                jumps[j] = jump;
                jump     = jold;
            }

            jumps[j] = jump;
            xsjs++;
        }

        if (!xsjs)
            break;

        cli_dbgmsg("cli_scanpe: Polipos: Checking %d xsect jump(s)\n", xsjs);
        for (i = 0; i < xsjs; i++) {
            if (!(code = fmap_need_off_once(map, jumps[i], 9)))
                continue;

            if ((jump = cli_readint32(code)) == 0x60ec8b55 || (code[4] == 0x0ec && ((jump == 0x83ec8b55 && code[6] == 0x60) || (jump == 0x81ec8b55 && !code[7] && !code[8])))) {
                ret = cli_append_potentially_unwanted(ctx, "Heuristics.W32.Polipos.A");
                if (ret != CL_SUCCESS) {
                    free(jumps);
                    cli_exe_info_destroy(peinfo);
                    return ret;
                }
            }
        }

        free(jumps);
        break;
    }

    /* Trojan.Swizzor.Gen */
    if (SCAN_HEURISTICS && (DCONF & PE_CONF_SWIZZOR) && peinfo->nsections > 1 && fsize > 64 * 1024 && fsize < 4 * 1024 * 1024) {
        if (peinfo->dirs[2].Size) {
            struct swizz_stats *stats = calloc(1, sizeof(*stats));
            unsigned int m            = 1000;
            ret                       = CL_CLEAN;

            if (!stats) {
                cli_exe_info_destroy(peinfo);
                return CL_EMEM;
            } else {
                cli_parseres_special(peinfo->dirs[2].VirtualAddress, peinfo->dirs[2].VirtualAddress, map, peinfo, fsize, 0, 0, &m, stats);
                if ((ret = cli_detect_swizz(stats)) == CL_VIRUS) {
                    ret = cli_append_potentially_unwanted(ctx, "Heuristics.Trojan.Swizzor.Gen");
                    if (ret != CL_SUCCESS) {
                        free(stats);
                        cli_exe_info_destroy(peinfo);
                        return ret;
                    }
                }
            }
        }
    }

    /* !!!!!!!!!!!!!!    PACKERS START HERE    !!!!!!!!!!!!!! */
    corrupted_cur        = ctx->corrupted_input;
    ctx->corrupted_input = 2; /* caller will reset on return */

    /* UPX, FSG, MEW support */

    /* try to find the first section with physical size == 0 */
    found = 0;
    if (DCONF & (PE_CONF_UPX | PE_CONF_FSG | PE_CONF_MEW)) {
        for (i = 0; i < (unsigned int)peinfo->nsections - 1; i++) {
            if (!peinfo->sections[i].rsz && peinfo->sections[i].vsz && peinfo->sections[i + 1].rsz && peinfo->sections[i + 1].vsz) {
                found = 1;
                cli_dbgmsg("cli_scanpe: UPX/FSG/MEW: empty section found - assuming compression\n");

                if (pe_json != NULL)
                    cli_jsonbool(pe_json, "HasEmptySection", 1);

                break;
            }
        }
    }

    /* MEW support */
    if (found && (DCONF & PE_CONF_MEW) && epsize >= 16 && epbuff[0] == '\xe9') {
        uint32_t fileoffset;
        const char *tbuff;

        // TODO shouldn't peinfo->ep be used here instead?  ep is the file
        // offset, vep is the entry point RVA
        fileoffset = (peinfo->vep + cli_readint32(epbuff + 1) + 5);
        while (fileoffset == 0x154 || fileoffset == 0x158) {
            char *src;
            uint32_t offdiff, uselzma;

            cli_dbgmsg("cli_scanpe: MEW: found MEW characteristics %08X + %08X + 5 = %08X\n",
                       cli_readint32(epbuff + 1), peinfo->vep, cli_readint32(epbuff + 1) + peinfo->vep + 5);

            if (!(tbuff = fmap_need_off_once(map, fileoffset, 0xb0)))
                break;

            if (fileoffset == 0x154)
                cli_dbgmsg("cli_scanpe: MEW: Win9x compatibility was set!\n");
            else
                cli_dbgmsg("cli_scanpe: MEW: Win9x compatibility was NOT set!\n");

            offdiff = cli_readint32(tbuff + 1) - EC32(peinfo->pe_opt.opt32.ImageBase);
            if ((offdiff <= peinfo->sections[i + 1].rva) ||
                (offdiff >= peinfo->sections[i + 1].rva + peinfo->sections[i + 1].raw - 4)) {
                cli_dbgmsg("cli_scanpe: MEW: ESI is not in proper section\n");
                break;
            }

            offdiff -= peinfo->sections[i + 1].rva;

            if (!peinfo->sections[i + 1].rsz) {
                cli_dbgmsg("cli_scanpe: MEW: mew section is empty\n");
                break;
            }

            ssize = peinfo->sections[i + 1].vsz;
            dsize = peinfo->sections[i].vsz;

            /* Guard against integer overflow */
            if ((ssize + dsize < ssize) || (ssize + dsize < dsize)) {
                cli_dbgmsg("cli_scanpe: MEW: section size (%08x) + diff size (%08x) exceeds max size of unsigned int (%08x)\n", ssize, dsize, UINT32_MAX);
                break;
            }

            /* Verify that offdiff does not exceed the ssize + sdiff */
            if (offdiff >= ssize + dsize) {
                cli_dbgmsg("cli_scanpe: MEW: offdiff (%08x) exceeds section size + diff size (%08x)\n", offdiff, ssize + dsize);
                break;
            }

            cli_dbgmsg("cli_scanpe: MEW: ssize %08x dsize %08x offdiff: %08x\n", ssize, dsize, offdiff);

            CLI_UNPSIZELIMITS("cli_scanpe: MEW", MAX(ssize, dsize));
            CLI_UNPSIZELIMITS("cli_scanpe: MEW", MAX(ssize + dsize, peinfo->sections[i + 1].rsz));

            if (peinfo->sections[i + 1].rsz < offdiff + 12 || peinfo->sections[i + 1].rsz > ssize) {
                cli_dbgmsg("cli_scanpe: MEW: Size mismatch: %08x\n", peinfo->sections[i + 1].rsz);
                break;
            }

            /* allocate needed buffer */
            if (!(src = cli_max_calloc(ssize + dsize, sizeof(char)))) {
                cli_exe_info_destroy(peinfo);
                return CL_EMEM;
            }

            bytes = fmap_readn(map, src + dsize, peinfo->sections[i + 1].raw, peinfo->sections[i + 1].rsz);
            if (bytes != peinfo->sections[i + 1].rsz) {
                cli_dbgmsg("cli_scanpe: MEW: Can't read %u bytes [read: %zu]\n", peinfo->sections[i + 1].rsz, bytes);
                cli_exe_info_destroy(peinfo);
                free(src);
                return CL_EREAD;
            }

            cli_dbgmsg("cli_scanpe: MEW: %zu (%08zx) bytes read\n", bytes, bytes);

            /* count offset to lzma proc, if lzma used, 0xe8 -> call */
            if (tbuff[0x7b] == '\xe8') {
                if (!CLI_ISCONTAINED(peinfo->sections[1].rva, peinfo->sections[1].vsz, cli_readint32(tbuff + 0x7c) + fileoffset + 0x80, 4)) {
                    cli_dbgmsg("cli_scanpe: MEW: lzma proc out of bounds!\n");
                    free(src);
                    break; /* to next unpacker in chain */
                }

                uselzma = cli_readint32(tbuff + 0x7c) - (peinfo->sections[0].rva - fileoffset - 0x80);
            } else {
                uselzma = 0;
            }

            if (pe_json != NULL)
                cli_jsonstr(pe_json, "Packer", "MEW");

            CLI_UNPTEMP("cli_scanpe: MEW", (src, 0));
            CLI_UNPRESULTS("cli_scanpe: MEW", (unmew11(src, offdiff, ssize, dsize, EC32(peinfo->pe_opt.opt32.ImageBase), peinfo->sections[0].rva, uselzma, ndesc)), 1, (src, 0));
            break;
        }
    }

    // TODO Why do we bail here
    if (epsize < 168) {
        cli_exe_info_destroy(peinfo);
        return CL_CLEAN;
    }

    if (found || upack) {
        /* Check EP for UPX vs. FSG vs. Upack */

        /* Upack 0.39 produces 2 types of executables
         * 3 sections:           | 2 sections (one empty, I don't check found if !upack, since it's in OR above):
         *   mov esi, value      |   pusha
         *   lodsd               |   call $+0x9
         *   push eax            |
         *
         * Upack 1.1/1.2 Beta produces [based on 2 samples (sUx) provided by aCaB]:
         * 2 sections
         *   mov esi, value
         *   loads
         *   mov edi, eax
         *
         * Upack unknown [sample 0297729]
         * 3 sections
         *   mov esi, value
         *   push [esi]
         *   jmp
         *
         */
        /* upack 0.39-3s + sample 0151477*/
        while (((upack && peinfo->nsections == 3) && /* 3 sections */
                ((
                     epbuff[0] == '\xbe' && cli_readint32(epbuff + 1) - EC32(peinfo->pe_opt.opt32.ImageBase) > peinfo->min && /* mov esi */
                     epbuff[5] == '\xad' && epbuff[6] == '\x50'                                                               /* lodsd; push eax */
                     ) ||
                 /* based on 0297729 sample from aCaB */
                 (epbuff[0] == '\xbe' && cli_readint32(epbuff + 1) - EC32(peinfo->pe_opt.opt32.ImageBase) > peinfo->min && /* mov esi */
                  epbuff[5] == '\xff' && epbuff[6] == '\x36'                                                               /* push [esi] */
                  ))) ||
               ((!upack && peinfo->nsections == 2) &&                                            /* 2 sections */
                ((                                                                               /* upack 0.39-2s */
                  epbuff[0] == '\x60' && epbuff[1] == '\xe8' && cli_readint32(epbuff + 2) == 0x9 /* pusha; call+9 */
                  ) ||
                 (                                                                                                         /* upack 1.1/1.2, based on 2 samples */
                  epbuff[0] == '\xbe' && cli_readint32(epbuff + 1) - EC32(peinfo->pe_opt.opt32.ImageBase) < peinfo->min && /* mov esi */
                  cli_readint32(epbuff + 1) > (int32_t)EC32(peinfo->pe_opt.opt32.ImageBase) &&
                  epbuff[5] == '\xad' && epbuff[6] == '\x8b' && epbuff[7] == '\xf8' /* loads;  mov edi, eax */
                  )))) {
            uint32_t vma, off;
            int a, b, c;

            cli_dbgmsg("cli_scanpe: Upack characteristics found.\n");
            a = peinfo->sections[0].vsz;
            b = peinfo->sections[1].vsz;
            if (upack) {
                cli_dbgmsg("cli_scanpe: Upack: var set\n");

                c     = peinfo->sections[2].vsz;
                ssize = peinfo->sections[0].ursz + peinfo->sections[0].uraw;
                off   = peinfo->sections[0].rva;
                vma   = EC32(peinfo->pe_opt.opt32.ImageBase) + peinfo->sections[0].rva;
            } else {
                cli_dbgmsg("cli_scanpe: Upack: var NOT set\n");
                c     = peinfo->sections[1].rva;
                ssize = peinfo->sections[1].uraw;
                off   = 0;
                vma   = peinfo->sections[1].rva - peinfo->sections[1].uraw;
            }

            dsize = a + b + c;

            CLI_UNPSIZELIMITS("cli_scanpe: Upack", MAX(MAX(dsize, ssize), peinfo->sections[1].ursz));

            if (!CLI_ISCONTAINED_0_TO(dsize, peinfo->sections[1].rva - off, peinfo->sections[1].ursz) || (upack && !CLI_ISCONTAINED_0_TO(dsize, peinfo->sections[2].rva - peinfo->sections[0].rva, ssize)) || ssize > dsize) {
                cli_dbgmsg("cli_scanpe: Upack: probably malformed pe-header, skipping to next unpacker\n");
                break;
            }

            if ((dest = (char *)cli_max_calloc(dsize, sizeof(char))) == NULL) {
                cli_exe_info_destroy(peinfo);
                return CL_EMEM;
            }

            if (fmap_readn(map, dest, 0, ssize) != ssize) {
                cli_dbgmsg("cli_scanpe: Upack: Can't read raw data of section 0\n");
                free(dest);
                break;
            }

            if (upack)
                memmove(dest + peinfo->sections[2].rva - peinfo->sections[0].rva, dest, ssize);

            if (fmap_readn(map, dest + peinfo->sections[1].rva - off, peinfo->sections[1].uraw, peinfo->sections[1].ursz) != peinfo->sections[1].ursz) {
                cli_dbgmsg("cli_scanpe: Upack: Can't read raw data of section 1\n");
                free(dest);
                break;
            }

            if (pe_json != NULL)
                cli_jsonstr(pe_json, "Packer", "Upack");

            CLI_UNPTEMP("cli_scanpe: Upack", (dest, 0));
            CLI_UNPRESULTS("cli_scanpe: Upack", (unupack(upack, dest, dsize, epbuff, vma, peinfo->ep, EC32(peinfo->pe_opt.opt32.ImageBase), peinfo->sections[0].rva, ndesc)), 1, (dest, 0));

            break;
        }
    }

    while (found && (DCONF & PE_CONF_FSG) && epbuff[0] == '\x87' && epbuff[1] == '\x25') {
        const char *dst;
        uint32_t newesi, newedi, newebx, newedx;

        /* FSG v2.0 support - thanks to aCaB ! */

        ssize = peinfo->sections[i + 1].rsz;
        dsize = peinfo->sections[i].vsz;

        CLI_UNPSIZELIMITS("cli_scanpe: FSG", MAX(dsize, ssize));

        if (ssize <= 0x19 || dsize <= ssize) {
            cli_dbgmsg("cli_scanpe: FSG: Size mismatch (ssize: %d, dsize: %d)\n", ssize, dsize);
            cli_exe_info_destroy(peinfo);
            return CL_CLEAN;
        }

        newedx = cli_readint32(epbuff + 2) - EC32(peinfo->pe_opt.opt32.ImageBase);
        if (!CLI_ISCONTAINED(peinfo->sections[i + 1].rva, peinfo->sections[i + 1].rsz, newedx, 4)) {
            cli_dbgmsg("cli_scanpe: FSG: xchg out of bounds (%x), giving up\n", newedx);
            break;
        }

        if (!peinfo->sections[i + 1].rsz || !(src = fmap_need_off_once(map, peinfo->sections[i + 1].raw, ssize))) {
            cli_dbgmsg("cli_scanpe: Can't read raw data of section %d\n", i + 1);
            cli_exe_info_destroy(peinfo);
            return CL_ESEEK;
        }

        dst = src + newedx - peinfo->sections[i + 1].rva;
        if (newedx < peinfo->sections[i + 1].rva || !CLI_ISCONTAINED(src, ssize, dst, 4)) {
            cli_dbgmsg("cli_scanpe: FSG: New ESP out of bounds\n");
            break;
        }

        newedx = cli_readint32(dst) - EC32(peinfo->pe_opt.opt32.ImageBase);
        if (!CLI_ISCONTAINED(peinfo->sections[i + 1].rva, peinfo->sections[i + 1].rsz, newedx, 4)) {
            cli_dbgmsg("cli_scanpe: FSG: New ESP (%x) is wrong\n", newedx);
            break;
        }

        dst = src + newedx - peinfo->sections[i + 1].rva;
        if (!CLI_ISCONTAINED(src, ssize, dst, 32)) {
            cli_dbgmsg("cli_scanpe: FSG: New stack out of bounds\n");
            break;
        }

        newedi = cli_readint32(dst) - EC32(peinfo->pe_opt.opt32.ImageBase);
        newesi = cli_readint32(dst + 4) - EC32(peinfo->pe_opt.opt32.ImageBase);
        newebx = cli_readint32(dst + 16) - EC32(peinfo->pe_opt.opt32.ImageBase);
        newedx = cli_readint32(dst + 20);

        if (newedi != peinfo->sections[i].rva) {
            cli_dbgmsg("cli_scanpe: FSG: Bad destination buffer (edi is %x should be %x)\n", newedi, peinfo->sections[i].rva);
            break;
        }

        if (newesi < peinfo->sections[i + 1].rva || newesi - peinfo->sections[i + 1].rva >= peinfo->sections[i + 1].rsz) {
            cli_dbgmsg("cli_scanpe: FSG: Source buffer out of section bounds\n");
            break;
        }

        if (!CLI_ISCONTAINED(peinfo->sections[i + 1].rva, peinfo->sections[i + 1].rsz, newebx, 16)) {
            cli_dbgmsg("cli_scanpe: FSG: Array of functions out of bounds\n");
            break;
        }

        newedx = cli_readint32(newebx + 12 - peinfo->sections[i + 1].rva + src) - EC32(peinfo->pe_opt.opt32.ImageBase);
        cli_dbgmsg("cli_scanpe: FSG: found old EP @%x\n", newedx);

        if ((dest = (char *)cli_max_calloc(dsize, sizeof(char))) == NULL) {
            cli_exe_info_destroy(peinfo);
            return CL_EMEM;
        }

        if (pe_json != NULL)
            cli_jsonstr(pe_json, "Packer", "FSG");

        CLI_UNPTEMP("cli_scanpe: FSG", (dest, 0));
        CLI_UNPRESULTSFSG2("cli_scanpe: FSG", (unfsg_200(newesi - peinfo->sections[i + 1].rva + src, dest, ssize + peinfo->sections[i + 1].rva - newesi, dsize, newedi, EC32(peinfo->pe_opt.opt32.ImageBase), newedx, ndesc)), 1, (dest, 0));
        break;
    }

    while (found && (DCONF & PE_CONF_FSG) && epbuff[0] == '\xbe' && cli_readint32(epbuff + 1) - EC32(peinfo->pe_opt.opt32.ImageBase) < peinfo->min) {
        int sectcnt = 0;
        const char *support;
        uint32_t newesi, newedi, oldep, gp, t;
        struct cli_exe_section *sections;

        /* FSG support - v. 1.33 (thx trog for the many samples) */

        ssize = peinfo->sections[i + 1].rsz;
        dsize = peinfo->sections[i].vsz;

        CLI_UNPSIZELIMITS("cli_scanpe: FSG", MAX(dsize, ssize));

        if (ssize <= 0x19 || dsize <= ssize) {
            cli_dbgmsg("cli_scanpe: FSG: Size mismatch (ssize: %d, dsize: %d)\n", ssize, dsize);
            cli_exe_info_destroy(peinfo);
            return CL_CLEAN;
        }

        if (!(t = cli_rawaddr(cli_readint32(epbuff + 1) - EC32(peinfo->pe_opt.opt32.ImageBase), NULL, 0, &err, fsize, peinfo->hdr_size)) && err) {
            cli_dbgmsg("cli_scanpe: FSG: Support data out of padding area\n");
            break;
        }

        gp = peinfo->sections[i + 1].raw - t;

        CLI_UNPSIZELIMITS("cli_scanpe: FSG", gp);

        if (!(support = fmap_need_off_once(map, t, gp))) {
            cli_dbgmsg("cli_scanpe: Can't read %d bytes from padding area\n", gp);
            cli_exe_info_destroy(peinfo);
            return CL_EREAD;
        }

        /* newebx = cli_readint32(support) - EC32(peinfo->pe_opt.opt32.ImageBase);  Unused */
        newedi = cli_readint32(support + 4) - EC32(peinfo->pe_opt.opt32.ImageBase); /* 1st dest */
        newesi = cli_readint32(support + 8) - EC32(peinfo->pe_opt.opt32.ImageBase); /* Source */

        if (newesi < peinfo->sections[i + 1].rva || newesi - peinfo->sections[i + 1].rva >= peinfo->sections[i + 1].rsz) {
            cli_dbgmsg("cli_scanpe: FSG: Source buffer out of section bounds\n");
            break;
        }

        if (newedi != peinfo->sections[i].rva) {
            cli_dbgmsg("cli_scanpe: FSG: Bad destination (is %x should be %x)\n", newedi, peinfo->sections[i].rva);
            break;
        }

        /* Counting original sections */
        for (t = 12; t < gp - 4; t += 4) {
            uint32_t rva = cli_readint32(support + t);

            if (!rva)
                break;

            rva -= EC32(peinfo->pe_opt.opt32.ImageBase) + 1;
            sectcnt++;

            if (rva % 0x1000)
                cli_dbgmsg("cli_scanpe: FSG: Original section %d is misaligned\n", sectcnt);

            if (rva < peinfo->sections[i].rva || rva - peinfo->sections[i].rva >= peinfo->sections[i].vsz) {
                cli_dbgmsg("cli_scanpe: FSG: Original section %d is out of bounds\n", sectcnt);
                break;
            }
        }

        if (t >= gp - 4 || cli_readint32(support + t)) {
            break;
        }

        if ((sections = (struct cli_exe_section *)cli_max_malloc((sectcnt + 1) * sizeof(struct cli_exe_section))) == NULL) {
            cli_errmsg("cli_scanpe: FSG: Unable to allocate memory for sections %llu\n", (long long unsigned)((sectcnt + 1) * sizeof(struct cli_exe_section)));
            cli_exe_info_destroy(peinfo);
            return CL_EMEM;
        }

        sections[0].rva = newedi;
        for (t = 1; t <= (uint32_t)sectcnt; t++)
            sections[t].rva = cli_readint32(support + 8 + t * 4) - 1 - EC32(peinfo->pe_opt.opt32.ImageBase);

        if (!peinfo->sections[i + 1].rsz || !(src = fmap_need_off_once(map, peinfo->sections[i + 1].raw, ssize))) {
            cli_dbgmsg("cli_scanpe: Can't read raw data of section %d\n", i);
            cli_exe_info_destroy(peinfo);
            free(sections);
            return CL_EREAD;
        }

        if ((dest = (char *)cli_max_calloc(dsize, sizeof(char))) == NULL) {
            cli_exe_info_destroy(peinfo);
            free(sections);
            return CL_EMEM;
        }

        oldep = peinfo->vep + 161 + 6 + cli_readint32(epbuff + 163);
        cli_dbgmsg("cli_scanpe: FSG: found old EP @%x\n", oldep);

        if (pe_json != NULL)
            cli_jsonstr(pe_json, "Packer", "FSG");

        CLI_UNPTEMP("cli_scanpe: FSG", (dest, sections, 0));
        CLI_UNPRESULTSFSG1("cli_scanpe: FSG", (unfsg_133(src + newesi - peinfo->sections[i + 1].rva, dest, ssize + peinfo->sections[i + 1].rva - newesi, dsize, sections, sectcnt, EC32(peinfo->pe_opt.opt32.ImageBase), oldep, ndesc)), 1, (dest, sections, 0));
        break; /* were done with 1.33 */
    }

    while (found && (DCONF & PE_CONF_FSG) && epbuff[0] == '\xbb' && cli_readint32(epbuff + 1) - EC32(peinfo->pe_opt.opt32.ImageBase) < peinfo->min && epbuff[5] == '\xbf' && epbuff[10] == '\xbe' && peinfo->vep >= peinfo->sections[i + 1].rva && peinfo->vep - peinfo->sections[i + 1].rva > peinfo->sections[i + 1].rva - 0xe0) {
        int sectcnt = 0;
        uint32_t gp, t = cli_rawaddr(cli_readint32(epbuff + 1) - EC32(peinfo->pe_opt.opt32.ImageBase), NULL, 0, &err, fsize, peinfo->hdr_size);
        const char *support;
        uint32_t newesi = cli_readint32(epbuff + 11) - EC32(peinfo->pe_opt.opt32.ImageBase);
        uint32_t newedi = cli_readint32(epbuff + 6) - EC32(peinfo->pe_opt.opt32.ImageBase);
        uint32_t oldep  = peinfo->vep - peinfo->sections[i + 1].rva;
        struct cli_exe_section *sections;

        /* FSG support - v. 1.31 */

        ssize = peinfo->sections[i + 1].rsz;
        dsize = peinfo->sections[i].vsz;

        if (err) {
            cli_dbgmsg("cli_scanpe: FSG: Support data out of padding area\n");
            break;
        }

        if (newesi < peinfo->sections[i + 1].rva || newesi - peinfo->sections[i + 1].rva >= peinfo->sections[i + 1].raw) {
            cli_dbgmsg("cli_scanpe: FSG: Source buffer out of section bounds\n");
            break;
        }

        if (newedi != peinfo->sections[i].rva) {
            cli_dbgmsg("cli_scanpe: FSG: Bad destination (is %x should be %x)\n", newedi, peinfo->sections[i].rva);
            break;
        }

        CLI_UNPSIZELIMITS("cli_scanpe: FSG", MAX(dsize, ssize));

        if (ssize <= 0x19 || dsize <= ssize) {
            cli_dbgmsg("cli_scanpe: FSG: Size mismatch (ssize: %d, dsize: %d)\n", ssize, dsize);
            cli_exe_info_destroy(peinfo);
            return CL_CLEAN;
        }

        gp = peinfo->sections[i + 1].raw - t;

        CLI_UNPSIZELIMITS("cli_scanpe: FSG", gp)

        if (!(support = fmap_need_off_once(map, t, gp))) {
            cli_dbgmsg("cli_scanpe: Can't read %d bytes from padding area\n", gp);
            cli_exe_info_destroy(peinfo);
            return CL_EREAD;
        }

        /* Counting original sections */
        for (t = 0; t < gp - 2; t += 2) {
            uint32_t rva = support[t] | (support[t + 1] << 8);

            if (rva == 2 || rva == 1)
                break;

            rva = ((rva - 2) << 12) - EC32(peinfo->pe_opt.opt32.ImageBase);
            sectcnt++;

            if (rva < peinfo->sections[i].rva || rva - peinfo->sections[i].rva >= peinfo->sections[i].vsz) {
                cli_dbgmsg("cli_scanpe: FSG: Original section %d is out of bounds\n", sectcnt);
                break;
            }
        }

        if (t >= gp - 10 || cli_readint32(support + t + 6) != 2)
            break;

        if ((sections = (struct cli_exe_section *)cli_max_malloc((sectcnt + 1) * sizeof(struct cli_exe_section))) == NULL) {
            cli_errmsg("cli_scanpe: FSG: Unable to allocate memory for sections %llu\n", (long long unsigned)((sectcnt + 1) * sizeof(struct cli_exe_section)));
            cli_exe_info_destroy(peinfo);
            return CL_EMEM;
        }

        sections[0].rva = newedi;
        for (t = 0; t <= (uint32_t)sectcnt - 1; t++)
            sections[t + 1].rva = (((support[t * 2] | (support[t * 2 + 1] << 8)) - 2) << 12) - EC32(peinfo->pe_opt.opt32.ImageBase);

        if (!peinfo->sections[i + 1].rsz || !(src = fmap_need_off_once(map, peinfo->sections[i + 1].raw, ssize))) {
            cli_dbgmsg("cli_scanpe: FSG: Can't read raw data of section %d\n", i);
            cli_exe_info_destroy(peinfo);
            free(sections);
            return CL_EREAD;
        }

        if ((dest = (char *)cli_max_calloc(dsize, sizeof(char))) == NULL) {
            cli_exe_info_destroy(peinfo);
            free(sections);
            return CL_EMEM;
        }

        gp    = 0xda + 6 * (epbuff[16] == '\xe8');
        oldep = peinfo->vep + gp + 6 + cli_readint32(src + gp + 2 + oldep);
        cli_dbgmsg("cli_scanpe: FSG: found old EP @%x\n", oldep);

        if (pe_json != NULL)
            cli_jsonstr(pe_json, "Packer", "FSG");

        CLI_UNPTEMP("cli_scanpe: FSG", (dest, sections, 0));
        CLI_UNPRESULTSFSG1("cli_scanpe: FSG", (unfsg_133(src + newesi - peinfo->sections[i + 1].rva, dest, ssize + peinfo->sections[i + 1].rva - newesi, dsize, sections, sectcnt, EC32(peinfo->pe_opt.opt32.ImageBase), oldep, ndesc)), 1, (dest, sections, 0));

        break; /* were done with 1.31 */
    }

    if (found && (DCONF & PE_CONF_UPX)) {
        ssize = peinfo->sections[i + 1].rsz;
        dsize = peinfo->sections[i].vsz + peinfo->sections[i + 1].vsz;

        /*
         * UPX support
         * we assume (i + 1) is UPX1
         */

        /* cli_dbgmsg("UPX: ssize %u dsize %u\n", ssize, dsize); */

        CLI_UNPSIZELIMITS("cli_scanpe: UPX", MAX(dsize, ssize));

        if (ssize <= 0x19 || dsize <= ssize || dsize > CLI_MAX_ALLOCATION) {
            cli_dbgmsg("cli_scanpe: UPX: Size mismatch or dsize too big (ssize: %d, dsize: %d)\n", ssize, dsize);
            cli_exe_info_destroy(peinfo);
            return CL_CLEAN;
        }

        if (!peinfo->sections[i + 1].rsz || !(src = fmap_need_off_once(map, peinfo->sections[i + 1].raw, ssize))) {
            cli_dbgmsg("cli_scanpe: UPX: Can't read raw data of section %d\n", i + 1);
            cli_exe_info_destroy(peinfo);
            return CL_EREAD;
        }

        if ((dest = (char *)cli_max_calloc(dsize + 8192, sizeof(char))) == NULL) {
            cli_exe_info_destroy(peinfo);
            return CL_EMEM;
        }

        /* try to detect UPX code */
        if (cli_memstr(UPX_NRV2B, 24, epbuff + 0x69, 13) || cli_memstr(UPX_NRV2B, 24, epbuff + 0x69 + 8, 13)) {
            cli_dbgmsg("cli_scanpe: UPX: Looks like a NRV2B decompression routine\n");
            upxfn = upx_inflate2b;
        } else if (cli_memstr(UPX_NRV2D, 24, epbuff + 0x69, 13) || cli_memstr(UPX_NRV2D, 24, epbuff + 0x69 + 8, 13)) {
            cli_dbgmsg("cli_scanpe: UPX: Looks like a NRV2D decompression routine\n");
            upxfn = upx_inflate2d;
        } else if (cli_memstr(UPX_NRV2E, 24, epbuff + 0x69, 13) || cli_memstr(UPX_NRV2E, 24, epbuff + 0x69 + 8, 13)) {
            cli_dbgmsg("cli_scanpe: UPX: Looks like a NRV2E decompression routine\n");
            upxfn = upx_inflate2e;
        }

        if (upxfn) {
            int skew = cli_readint32(epbuff + 2) - EC32(peinfo->pe_opt.opt32.ImageBase) - peinfo->sections[i + 1].rva;

            if (epbuff[1] != '\xbe' || skew <= 0 || skew > 0xfff) {
                /* FIXME: legit skews?? */
                skew = 0;
            } else if ((unsigned int)skew > ssize) {
                /* Ignore suggested skew larger than section size */
                skew = 0;
            } else {
                cli_dbgmsg("cli_scanpe: UPX: UPX1 seems skewed by %d bytes\n", skew);
            }

            /* Try skewed first (skew may be zero) */
            if (upxfn(src + skew, ssize - skew, dest, &dsize, peinfo->sections[i].rva, peinfo->sections[i + 1].rva, peinfo->vep - skew) >= 0) {
                upx_success = 1;
            }
            /* If skew not successful and non-zero, try no skew */
            else if (skew && (upxfn(src, ssize, dest, &dsize, peinfo->sections[i].rva, peinfo->sections[i + 1].rva, peinfo->vep) >= 0)) {
                upx_success = 1;
            }

            if (upx_success)
                cli_dbgmsg("cli_scanpe: UPX: Successfully decompressed\n");
            else
                cli_dbgmsg("cli_scanpe: UPX: Preferred decompressor failed\n");
        }

        if (!upx_success && upxfn != upx_inflate2b) {
            if (upx_inflate2b(src, ssize, dest, &dsize, peinfo->sections[i].rva, peinfo->sections[i + 1].rva, peinfo->vep) == -1 && upx_inflate2b(src + 0x15, ssize - 0x15, dest, &dsize, peinfo->sections[i].rva, peinfo->sections[i + 1].rva, peinfo->vep - 0x15) == -1) {

                cli_dbgmsg("cli_scanpe: UPX: NRV2B decompressor failed\n");
            } else {
                upx_success = 1;
                cli_dbgmsg("cli_scanpe: UPX: Successfully decompressed with NRV2B\n");
            }
        }

        if (!upx_success && upxfn != upx_inflate2d) {
            if (upx_inflate2d(src, ssize, dest, &dsize, peinfo->sections[i].rva, peinfo->sections[i + 1].rva, peinfo->vep) == -1 && upx_inflate2d(src + 0x15, ssize - 0x15, dest, &dsize, peinfo->sections[i].rva, peinfo->sections[i + 1].rva, peinfo->vep - 0x15) == -1) {

                cli_dbgmsg("cli_scanpe: UPX: NRV2D decompressor failed\n");
            } else {
                upx_success = 1;
                cli_dbgmsg("cli_scanpe: UPX: Successfully decompressed with NRV2D\n");
            }
        }

        if (!upx_success && upxfn != upx_inflate2e) {
            if (upx_inflate2e(src, ssize, dest, &dsize, peinfo->sections[i].rva, peinfo->sections[i + 1].rva, peinfo->vep) == -1 && upx_inflate2e(src + 0x15, ssize - 0x15, dest, &dsize, peinfo->sections[i].rva, peinfo->sections[i + 1].rva, peinfo->vep - 0x15) == -1) {
                cli_dbgmsg("cli_scanpe: UPX: NRV2E decompressor failed\n");
            } else {
                upx_success = 1;
                cli_dbgmsg("cli_scanpe: UPX: Successfully decompressed with NRV2E\n");
            }
        }

        if (cli_memstr(UPX_LZMA2, 20, epbuff + 0x2f, 20)) {
            uint32_t strictdsize = cli_readint32(epbuff + 0x21), skew = 0;
            if (ssize > 0x15 && epbuff[0] == '\x60' && epbuff[1] == '\xbe') {
                // TODO Add EC32
                skew = cli_readint32(epbuff + 2) - peinfo->sections[i + 1].rva - peinfo->pe_opt.opt32.ImageBase;
                if (skew != 0x15)
                    skew = 0;
            }

            if (strictdsize <= dsize)
                upx_success = upx_inflatelzma(src + skew, ssize - skew, dest, &strictdsize, peinfo->sections[i].rva, peinfo->sections[i + 1].rva, peinfo->vep, 0x20003) >= 0;
        } else if (cli_memstr(UPX_LZMA1_FIRST, 8, epbuff + 0x39, 8) && cli_memstr(UPX_LZMA1_SECOND, 8, epbuff + 0x45, 8)) {
            uint32_t strictdsize = cli_readint32(epbuff + 0x2b), skew = 0;
            uint32_t properties = cli_readint32(epbuff + 0x41);
            if (ssize > 0x15 && epbuff[0] == '\x60' && epbuff[1] == '\xbe') {
                // TODO Add EC32
                skew = cli_readint32(epbuff + 2) - peinfo->sections[i + 1].rva - peinfo->pe_opt.opt32.ImageBase;
                if (skew != 0x15)
                    skew = 0;
            }

            if (strictdsize <= dsize)
                upx_success = upx_inflatelzma(src + skew, ssize - skew, dest, &strictdsize, peinfo->sections[i].rva, peinfo->sections[i + 1].rva, peinfo->vep, properties) >= 0;
        }

        if (!upx_success) {
            cli_dbgmsg("cli_scanpe: UPX: All decompressors failed\n");
            free(dest);
        }
    }

    if (upx_success) {
        cli_exe_info_destroy(peinfo);

        CLI_UNPTEMP("cli_scanpe: UPX/FSG", (dest, 0));

        if (pe_json != NULL)
            cli_jsonstr(pe_json, "Packer", "UPX");

        if ((unsigned int)write(ndesc, dest, dsize) != dsize) {
            cli_dbgmsg("cli_scanpe: UPX/FSG: Can't write %d bytes\n", dsize);
            free(tempfile);
            free(dest);
            close(ndesc);
            return CL_EWRITE;
        }

        free(dest);
        if (lseek(ndesc, 0, SEEK_SET) == -1) {
            cli_dbgmsg("cli_scanpe: UPX/FSG: lseek() failed\n");
            close(ndesc);
            SHA_RESET;
            CLI_TMPUNLK();
            free(tempfile);
            return CL_ESEEK;
        }

        if (ctx->engine->keeptmp)
            cli_dbgmsg("cli_scanpe: UPX/FSG: Decompressed data saved in %s\n", tempfile);

        cli_dbgmsg("***** Scanning decompressed file *****\n");
        SHA_OFF;
        ret = cli_magic_scan_desc(ndesc, tempfile, ctx, NULL, LAYER_ATTRIBUTES_NONE);
        if (CL_SUCCESS != ret) {
            close(ndesc);
            SHA_RESET;
            CLI_TMPUNLK();
            free(tempfile);
            return ret;
        }

        SHA_RESET;
        close(ndesc);
        CLI_TMPUNLK();
        free(tempfile);
        return ret;
    }

    /* Petite */

    if (epsize < 200) {
        cli_exe_info_destroy(peinfo);
        return CL_CLEAN;
    }

    found = 2;

    if (epbuff[0] != '\xb8' || (uint32_t)cli_readint32(epbuff + 1) != peinfo->sections[peinfo->nsections - 1].rva + EC32(peinfo->pe_opt.opt32.ImageBase)) {
        if (peinfo->nsections < 2 || epbuff[0] != '\xb8' || (uint32_t)cli_readint32(epbuff + 1) != peinfo->sections[peinfo->nsections - 2].rva + EC32(peinfo->pe_opt.opt32.ImageBase))
            found = 0;
        else
            found = 1;
    }

    if (found && (DCONF & PE_CONF_PETITE)) {
        cli_dbgmsg("cli_scanpe: Petite: v2.%d compression detected\n", found);

        if (cli_readint32(epbuff + 0x80) == 0x163c988d) {
            cli_dbgmsg("cli_scanpe: Petite: level zero compression is not supported yet\n");
        } else {
            dsize = peinfo->max - peinfo->min;

            CLI_UNPSIZELIMITS("cli_scanpe: Petite", dsize);

            if ((dest = (char *)cli_max_calloc(dsize, sizeof(char))) == NULL) {
                cli_dbgmsg("cli_scanpe: Petite: Can't allocate %d bytes\n", dsize);
                cli_exe_info_destroy(peinfo);
                return CL_EMEM;
            }

            for (i = 0; i < peinfo->nsections; i++) {
                if (peinfo->sections[i].raw) {
                    unsigned int r_ret;

                    if (!peinfo->sections[i].rsz)
                        goto out_no_petite;

                    if (!CLI_ISCONTAINED(dest, dsize,
                                         dest + peinfo->sections[i].rva - peinfo->min,
                                         peinfo->sections[i].ursz))
                        goto out_no_petite;

                    r_ret = fmap_readn(map, dest + peinfo->sections[i].rva - peinfo->min,
                                       peinfo->sections[i].raw,
                                       peinfo->sections[i].ursz);
                    if (r_ret != peinfo->sections[i].ursz) {
                    out_no_petite:
                        cli_exe_info_destroy(peinfo);
                        free(dest);
                        return CL_CLEAN;
                    }
                }
            }

            if (pe_json != NULL)
                cli_jsonstr(pe_json, "Packer", "Petite");

            CLI_UNPTEMP("cli_scanpe: Petite", (dest, 0));
            CLI_UNPRESULTS("Petite", (petite_inflate2x_1to9(dest, peinfo->min, peinfo->max - peinfo->min, peinfo->sections, peinfo->nsections - (found == 1 ? 1 : 0), EC32(peinfo->pe_opt.opt32.ImageBase), peinfo->vep, ndesc, found, peinfo->dirs[2].VirtualAddress, peinfo->dirs[2].Size)), 0, (dest, 0));
        }
    }

    /* PESpin 1.1 */

    if ((DCONF & PE_CONF_PESPIN) && peinfo->nsections > 1 &&
        peinfo->vep >= peinfo->sections[peinfo->nsections - 1].rva &&
        0x3217 - 4 <= peinfo->sections[peinfo->nsections - 1].rva + peinfo->sections[peinfo->nsections - 1].rsz &&
        peinfo->vep < peinfo->sections[peinfo->nsections - 1].rva + peinfo->sections[peinfo->nsections - 1].rsz - 0x3217 - 4 &&
        memcmp(epbuff + 4, "\xe8\x00\x00\x00\x00\x8b\x1c\x24\x83\xc3", 10) == 0) {

        char *spinned;

        CLI_UNPSIZELIMITS("cli_scanpe: PEspin", fsize);

        if ((spinned = (char *)cli_max_malloc(fsize)) == NULL) {
            cli_errmsg("cli_scanpe: PESping: Unable to allocate memory for spinned %lu\n", (unsigned long)fsize);
            cli_exe_info_destroy(peinfo);
            return CL_EMEM;
        }

        if (fmap_readn(map, spinned, 0, fsize) != fsize) {
            cli_dbgmsg("cli_scanpe: PESpin: Can't read %lu bytes\n", (unsigned long)fsize);
            free(spinned);
            cli_exe_info_destroy(peinfo);
            return CL_EREAD;
        }

        if (pe_json != NULL)
            cli_jsonstr(pe_json, "Packer", "PEspin");

        CLI_UNPTEMP("cli_scanpe: PESpin", (spinned, 0));
        CLI_UNPRESULTS_("cli_scanpe: PEspin", SPINCASE(), (unspin(spinned, fsize, peinfo->sections, peinfo->nsections - 1, peinfo->vep, ndesc, ctx)), 0, (spinned, 0));
    }

    /* yC 1.3 & variants */
    if ((DCONF & PE_CONF_YC) && peinfo->nsections > 1 &&
        (EC32(peinfo->pe_opt.opt32.AddressOfEntryPoint) == peinfo->sections[peinfo->nsections - 1].rva + 0x60)) {

        uint32_t ecx = 0;
        int16_t offset;

        /* yC 1.3 */
        if (!memcmp(epbuff, "\x55\x8B\xEC\x53\x56\x57\x60\xE8\x00\x00\x00\x00\x5D\x81\xED", 15) &&
            !memcmp(epbuff + 0x26, "\x8D\x3A\x8B\xF7\x33\xC0\xEB\x04\x90\xEB\x01\xC2\xAC", 13) &&
            ((uint8_t)epbuff[0x13] == 0xB9) &&
            ((uint16_t)(cli_readint16(epbuff + 0x18)) == 0xE981) &&
            !memcmp(epbuff + 0x1e, "\x8B\xD5\x81\xC2", 4)) {

            offset = 0;
            if (0x6c - cli_readint32(epbuff + 0xf) + cli_readint32(epbuff + 0x22) == 0xC6)
                ecx = cli_readint32(epbuff + 0x14) - cli_readint32(epbuff + 0x1a);
        }

        /* yC 1.3 variant */
        if (!ecx && !memcmp(epbuff, "\x55\x8B\xEC\x83\xEC\x40\x53\x56\x57", 9) &&
            !memcmp(epbuff + 0x17, "\xe8\x00\x00\x00\x00\x5d\x81\xed", 8) &&
            ((uint8_t)epbuff[0x23] == 0xB9)) {

            offset = 0x10;
            if (0x6c - cli_readint32(epbuff + 0x1f) + cli_readint32(epbuff + 0x32) == 0xC6)
                ecx = cli_readint32(epbuff + 0x24) - cli_readint32(epbuff + 0x2a);
        }

        /* yC 1.x/modified */
        if (!ecx && !memcmp(epbuff, "\x60\xe8\x00\x00\x00\x00\x5d\x81\xed", 9) &&
            ((uint8_t)epbuff[0xd] == 0xb9) &&
            ((uint16_t)cli_readint16(epbuff + 0x12) == 0xbd8d) &&
            !memcmp(epbuff + 0x18, "\x8b\xf7\xac", 3)) {

            offset = -0x18;
            if (0x66 - cli_readint32(epbuff + 0x9) + cli_readint32(epbuff + 0x14) == 0xae)
                ecx = cli_readint32(epbuff + 0xe);
        }

        if (ecx > 0x800 && ecx < 0x2000 &&
            !memcmp(epbuff + 0x63 + offset, "\xaa\xe2\xcc", 3) &&
            (fsize >= peinfo->sections[peinfo->nsections - 1].raw + 0xC6 + ecx + offset)) {

            size_t num_alerts;
            char *spinned;

            if ((spinned = (char *)cli_max_malloc(fsize)) == NULL) {
                cli_errmsg("cli_scanpe: yC: Unable to allocate memory for spinned %lu\n", (unsigned long)fsize);
                cli_exe_info_destroy(peinfo);
                return CL_EMEM;
            }

            if (fmap_readn(map, spinned, 0, fsize) != fsize) {
                cli_dbgmsg("cli_scanpe: yC: Can't read %lu bytes\n", (unsigned long)fsize);
                free(spinned);
                cli_exe_info_destroy(peinfo);
                return CL_EREAD;
            }

            if (pe_json != NULL)
                cli_jsonstr(pe_json, "Packer", "yC");

            // record number of alerts before unpacking and scanning
            num_alerts = evidence_num_alerts(ctx->evidence);

            cli_dbgmsg("%d,%d,%d,%d\n", peinfo->nsections - 1, peinfo->e_lfanew, ecx, offset);
            CLI_UNPTEMP("cli_scanpe: yC", (spinned, 0));
            CLI_UNPRESULTS("cli_scanpe: yC", (yc_decrypt(ctx, spinned, fsize, peinfo->sections, peinfo->nsections - 1, peinfo->e_lfanew, ndesc, ecx, offset)), 0, (spinned, 0));

            // Unpacking may have added new alerts if the bounds-check failed.
            // Compare number of alerts now with number of alerts before unpacking/scanning.
            // If the number of alerts has increased, then bail.
            //
            // This preserves the intention of https://github.com/Cisco-Talos/clamav/commit/771c23099893f02f1316960fbe84f62b115a3556
            // although that commit had it bailing if a match occurred even in allmatch-mode, which we do not want to do.
            if (!SCAN_ALLMATCHES && num_alerts != evidence_num_alerts(ctx->evidence)) {
                cli_exe_info_destroy(peinfo);
                return CL_VIRUS;
            }
        }
    }

    /* WWPack */

    while ((DCONF & PE_CONF_WWPACK) && peinfo->nsections > 1 &&
           peinfo->vep == peinfo->sections[peinfo->nsections - 1].rva &&
           memcmp(epbuff, "\x53\x55\x8b\xe8\x33\xdb\xeb", 7) == 0 &&
           memcmp(epbuff + 0x68, "\xe8\x00\x00\x00\x00\x58\x2d\x6d\x00\x00\x00\x50\x60\x33\xc9\x50\x58\x50\x50", 19) == 0) {
        uint32_t head = peinfo->sections[peinfo->nsections - 1].raw;
        uint8_t *packer;
        char *src;

        ssize = 0;
        for (i = 0;; i++) {
            if (peinfo->sections[i].raw < head)
                head = peinfo->sections[i].raw;

            if (i + 1 == peinfo->nsections)
                break;

            if (ssize < peinfo->sections[i].rva + peinfo->sections[i].vsz)
                ssize = peinfo->sections[i].rva + peinfo->sections[i].vsz;
        }

        if (!head || !ssize || head > ssize)
            break;

        CLI_UNPSIZELIMITS("cli_scanpe: WWPack", ssize);

        if (!(src = (char *)cli_max_calloc(ssize, sizeof(char)))) {
            cli_exe_info_destroy(peinfo);
            return CL_EMEM;
        }

        if (fmap_readn(map, src, 0, head) != head) {
            cli_dbgmsg("cli_scanpe: WWPack: Can't read %d bytes from headers\n", head);
            free(src);
            cli_exe_info_destroy(peinfo);
            return CL_EREAD;
        }

        for (i = 0; i < (unsigned int)peinfo->nsections - 1; i++) {
            if (!peinfo->sections[i].rsz)
                continue;

            if (!CLI_ISCONTAINED(src, ssize, src + peinfo->sections[i].rva, peinfo->sections[i].rsz))
                break;

            if (fmap_readn(map, src + peinfo->sections[i].rva, peinfo->sections[i].raw, peinfo->sections[i].rsz) != peinfo->sections[i].rsz)
                break;
        }

        if (i + 1 != peinfo->nsections) {
            cli_dbgmsg("cli_scanpe: WWpack: Probably hacked/damaged file.\n");
            free(src);
            break;
        }

        if ((packer = (uint8_t *)cli_max_calloc(peinfo->sections[peinfo->nsections - 1].rsz, sizeof(char))) == NULL) {
            free(src);
            cli_exe_info_destroy(peinfo);
            return CL_EMEM;
        }

        if (!peinfo->sections[peinfo->nsections - 1].rsz || fmap_readn(map, packer, peinfo->sections[peinfo->nsections - 1].raw, peinfo->sections[peinfo->nsections - 1].rsz) != peinfo->sections[peinfo->nsections - 1].rsz) {
            cli_dbgmsg("cli_scanpe: WWPack: Can't read %d bytes from wwpack sect\n", peinfo->sections[peinfo->nsections - 1].rsz);
            free(src);
            free(packer);
            cli_exe_info_destroy(peinfo);
            return CL_EREAD;
        }

        if (pe_json != NULL)
            cli_jsonstr(pe_json, "Packer", "WWPack");

        CLI_UNPTEMP("cli_scanpe: WWPack", (src, packer, 0));
        CLI_UNPRESULTS("cli_scanpe: WWPack", (wwunpack((uint8_t *)src, ssize, packer, peinfo->sections, peinfo->nsections - 1, peinfo->e_lfanew, ndesc)), 0, (src, packer, 0));
        break;
    }

    /* ASPACK support */
    while ((DCONF & PE_CONF_ASPACK) &&
           ((peinfo->ep + ASPACK_EP_OFFSET_212 < fsize) ||
            (peinfo->ep + ASPACK_EP_OFFSET_OTHER < fsize) ||
            (peinfo->ep + ASPACK_EP_OFFSET_242 < fsize)) &&
           (!memcmp(epbuff, "\x60\xe8\x03\x00\x00\x00\xe9\xeb", 8))) {
        char *src;
        aspack_version_t aspack_ver = ASPACK_VER_NONE;

        if (epsize < 0x3bf)
            break;

        if (0 == memcmp(epbuff + ASPACK_EPBUFF_OFFSET_212, "\x68\x00\x00\x00\x00\xc3", 6)) {
            aspack_ver = ASPACK_VER_212;
        } else if (0 == memcmp(epbuff + ASPACK_EPBUFF_OFFSET_OTHER, "\x68\x00\x00\x00\x00\xc3", 6)) {
            aspack_ver = ASPACK_VER_OTHER;
        } else if (0 == memcmp(epbuff + ASPACK_EPBUFF_OFFSET_242, "\x68\x00\x00\x00\x00\xc3", 6)) {
            aspack_ver = ASPACK_VER_242;
        } else {
            break;
        }
        ssize = 0;
        for (i = 0; i < peinfo->nsections; i++)
            if (ssize < peinfo->sections[i].rva + peinfo->sections[i].vsz)
                ssize = peinfo->sections[i].rva + peinfo->sections[i].vsz;

        if (!ssize)
            break;

        CLI_UNPSIZELIMITS("cli_scanpe: Aspack", ssize);

        if (!(src = (char *)cli_max_calloc(ssize, sizeof(char)))) {
            cli_exe_info_destroy(peinfo);
            return CL_EMEM;
        }
        for (i = 0; i < (unsigned int)peinfo->nsections; i++) {
            if (!peinfo->sections[i].rsz)
                continue;

            if (!CLI_ISCONTAINED(src, ssize, src + peinfo->sections[i].rva, peinfo->sections[i].rsz))
                break;

            if (fmap_readn(map, src + peinfo->sections[i].rva, peinfo->sections[i].raw, peinfo->sections[i].rsz) != peinfo->sections[i].rsz)
                break;
        }

        if (i != peinfo->nsections) {
            cli_dbgmsg("cli_scanpe: Aspack: Probably hacked/damaged Aspack file.\n");
            free(src);
            break;
        }

        if (pe_json != NULL)
            cli_jsonstr(pe_json, "Packer", "Aspack");

        CLI_UNPTEMP("cli_scanpe: Aspack", (src, 0));
        CLI_UNPRESULTS("cli_scanpe: Aspack", (unaspack((uint8_t *)src, ssize, peinfo->sections, peinfo->nsections, peinfo->vep - 1, EC32(peinfo->pe_opt.opt32.ImageBase), ndesc, aspack_ver)), 1, (src, 0));
        break;
    }

    /* NsPack */

    while (DCONF & PE_CONF_NSPACK) {
        uint32_t eprva = peinfo->vep;
        uint32_t start_of_stuff, rep = peinfo->ep;
        unsigned int nowinldr;
        const char *nbuff;

        src = epbuff;
        if (*epbuff == '\xe9') { /* bitched headers */
            eprva = cli_readint32(epbuff + 1) + peinfo->vep + 5;
            if (!(rep = cli_rawaddr(eprva, peinfo->sections, peinfo->nsections, &err, fsize, peinfo->hdr_size)) && err)
                break;

            if (!(nbuff = fmap_need_off_once(map, rep, 24)))
                break;

            src = nbuff;
        }

        if (memcmp(src, "\x9c\x60\xe8\x00\x00\x00\x00\x5d\xb8\x07\x00\x00\x00", 13))
            break;

        nowinldr = 0x54 - cli_readint32(src + 17);
        cli_dbgmsg("cli_scanpe: NsPack: Found *start_of_stuff @delta-%x\n", nowinldr);

        if (!(nbuff = fmap_need_off_once(map, rep - nowinldr, 4)))
            break;

        start_of_stuff = rep + cli_readint32(nbuff);
        if (!(nbuff = fmap_need_off_once(map, start_of_stuff, 20)))
            break;

        src = nbuff;
        if (!cli_readint32(nbuff)) {
            start_of_stuff += 4; /* FIXME: more to do */
            src += 4;
        }

        ssize = cli_readint32(src + 5) | 0xff;
        dsize = cli_readint32(src + 9);

        CLI_UNPSIZELIMITS("cli_scanpe: NsPack", MAX(ssize, dsize));

        if (!ssize || !dsize || dsize != peinfo->sections[0].vsz)
            break;

        if (!(dest = cli_max_malloc(dsize))) {
            cli_errmsg("cli_scanpe: NsPack: Unable to allocate memory for dest %u\n", dsize);
            break;
        }
        /* memset(dest, 0xfc, dsize); */

        if (!(src = fmap_need_off(map, start_of_stuff, ssize))) {
            free(dest);
            break;
        }
        /* memset(src, 0x00, ssize); */

        eprva += 0x27a;
        if (!(rep = cli_rawaddr(eprva, peinfo->sections, peinfo->nsections, &err, fsize, peinfo->hdr_size)) && err) {
            free(dest);
            break;
        }

        if (!(nbuff = fmap_need_off_once(map, rep, 5))) {
            free(dest);
            break;
        }

        fmap_unneed_off(map, start_of_stuff, ssize);
        eprva = eprva + 5 + cli_readint32(nbuff + 1);
        cli_dbgmsg("cli_scanpe: NsPack: OEP = %08x\n", eprva);

        if (pe_json != NULL)
            cli_jsonstr(pe_json, "Packer", "NsPack");

        CLI_UNPTEMP("cli_scanpe: NsPack", (dest, 0));
        CLI_UNPRESULTS("cli_scanpe: NsPack", (unspack(src, dest, ctx, peinfo->sections[0].rva, EC32(peinfo->pe_opt.opt32.ImageBase), eprva, ndesc)), 0, (dest, 0));
        break;
    }

    /* to be continued ... */

    /* !!!!!!!!!!!!!!    PACKERS END HERE    !!!!!!!!!!!!!! */
    ctx->corrupted_input = corrupted_cur;

    /* Bytecode BC_PE_UNPACKER hook */
    bc_ctx = cli_bytecode_context_alloc();
    if (!bc_ctx) {
        cli_errmsg("cli_scanpe: can't allocate memory for bc_ctx\n");
        return CL_EMEM;
    }

    cli_bytecode_context_setpe(bc_ctx, &pedata, peinfo->sections);
    cli_bytecode_context_setctx(bc_ctx, ctx);

    ret = cli_bytecode_runhook(ctx, ctx->engine, bc_ctx, BC_PE_UNPACKER, map);
    switch (ret) {
        case CL_VIRUS:
            cli_exe_info_destroy(peinfo);
            cli_bytecode_context_destroy(bc_ctx);
            return CL_VIRUS;
        case CL_SUCCESS:
            ndesc = cli_bytecode_context_getresult_file(bc_ctx, &tempfile);
            cli_bytecode_context_destroy(bc_ctx);
            if (ndesc != -1 && tempfile) {
                CLI_UNPRESULTS("cli_scanpe: bytecode PE hook", 1, 1, (0));
            }

            break;
        default:
            cli_bytecode_context_destroy(bc_ctx);
    }

    cli_exe_info_destroy(peinfo);

    if (cli_json_timeout_cycle_check(ctx, &toval) != CL_SUCCESS)
        return CL_ETIMEOUT;

    return CL_SUCCESS;
}

cl_error_t cli_pe_targetinfo(cli_ctx *ctx, struct cli_exe_info *peinfo)
{
    return cli_peheader(ctx->fmap, peinfo, CLI_PEHEADER_OPT_EXTRACT_VINFO, NULL);
}

/** Parse the PE header and, if successful, populate peinfo
 *
 * @param map The fmap_t backing the file being scanned
 * @param peinfo A structure to populate with info from the PE header. This
 *               MUST be initialized via cli_exe_info_init prior to calling
 * @param opts A bitfield indicating various options related to PE header
 *             parsing.  The options are (prefixed with CLI_PEHEADER_OPT_):
 *              - NONE - Do default parsing
 *              - COLLECT_JSON - Populate ctx's json obj with PE header
 *                               info
 *              - DBG_PRINT_INFO - Print debug information about the
 *                                 PE file. Right now, cli_peheader is
 *                                 called multiple times for a given PE,
 *                                 so you don't want to print out the
 *                                 same info each time.
 *              - EXTRACT_VINFO - Parse the PEs VERSION_INFO metadata
 *                                and store it in peinfo->vinfo
 *              - STRICT_ON_PE_ERRORS - If specified, some cases that
 *                                      might be considered a broken
 *                                      executable cause RET_BROKEN_PE
 *                                      to be returned, but otherwise
 *                                      these will be tolerated.
 *              - REMOVE_MISSING_SECTIONS - If a section exists outside of the
 *                                          file, remove it from
 *                                          peinfo->sections. Otherwise, the
 *                                          rsz is just set to 0 for it.
 * @param ctx The overarching cli_ctx.  This is required with certain opts, but
 *            optional otherwise.
 * @return If the PE header is parsed successfully, CL_SUCCESS is returned.
 *         If it seems like the PE is broken, CL_EFORMAT is returned.
 *         Otherwise, one of the other error codes is returned.
 *         The caller MUST destroy peinfo, regardless of what this function
 *         returns.
 *
 * TODO What constitutes a "broken PE" seems somewhat arbitrary in places.
 * I think a PE should only be considered broken if it will not run on
 * any version of Windows.  We should invest more time to ensure that our
 * broken PE detection more closely aligns with this.
 *
 * TODO Simplify and get rid of CLI_PEHEADER_OPT_STRICT_ON_PE_ERRORS if
 * possible.  We should either fail always or ignore always, IMO.
 *
 * TODO Simplify and get rid of CLI_PEHEADER_OPT_REMOVE_MISSING_SECTIONS if
 * possible.  I don't think it makes sense to have pieces of the code work
 * off of incomplete representations of the sections (for instance, I wonder
 * if this makes any of the bytecode APIs return unexpected values).  This
 * appears to have been implemented to prevent ClamAV from crashing, though,
 * (bb11155) so we need to ensure the underlying issues are addressed.
 *
 * TODO Consolidate when information about the PE is printed (after successful
 * PE parsing).  This will allow us to simplify the code.  Some fail cases,
 * then, will cause PE info to not be printed at all, but I think this is
 * acceptable.  The debug messages generated in the fail cases should point to
 * what happened, and that's enough to track down the cause of any issues.
 *
 * TODO Same as above but with JSON creation
 */
cl_error_t cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ctx *ctx)
{
    cl_error_t ret = CL_ERROR;

    uint16_t e_magic; /* DOS signature ("MZ") */
    const char *archtype = NULL, *subsystem = NULL;
    time_t timestamp;
    char timestr[32];
    uint32_t data_dirs_size;
    uint16_t opt_hdr_size;
    uint32_t stored_opt_hdr_size;
    struct pe_image_file_hdr *file_hdr;
    struct pe_image_optional_hdr32 *opt32;
    struct pe_image_optional_hdr64 *opt64     = NULL;
    struct pe_image_section_hdr *section_hdrs = NULL;
    size_t i, j, section_pe_idx;
    unsigned int err;
    uint32_t salign, falign;
    size_t fsize;
    ssize_t at;
    uint32_t is_dll = 0;
    uint32_t is_exe = 0;
    int native      = 0;
    size_t read;
    uint32_t temp;

    int toval                   = 0;
    struct json_object *pe_json = NULL;
    char jsonbuf[128];

    if (ctx == NULL &&
        (opts & CLI_PEHEADER_OPT_COLLECT_JSON ||
         opts & CLI_PEHEADER_OPT_DBG_PRINT_INFO)) {
        cli_errmsg("cli_peheader: ctx can't be NULL for options specified\n");
        goto done;
    }

    if (opts & CLI_PEHEADER_OPT_COLLECT_JSON) {
        pe_json = get_pe_property(ctx);
    }

    fsize = map->len - peinfo->offset;
    if (fmap_readn(map, &e_magic, peinfo->offset, sizeof(e_magic)) != sizeof(e_magic)) {
        cli_dbgmsg("cli_peheader: Can't read DOS signature\n");
        goto done;
    }

    if (EC16(e_magic) != PE_IMAGE_DOS_SIGNATURE && EC16(e_magic) != PE_IMAGE_DOS_SIGNATURE_OLD) {
        cli_dbgmsg("cli_peheader: Invalid DOS signature\n");
        goto done;
    }

    if (fmap_readn(map, &(peinfo->e_lfanew), peinfo->offset + 58 + sizeof(e_magic), sizeof(peinfo->e_lfanew)) != sizeof(peinfo->e_lfanew)) {
        /* truncated header? */
        cli_dbgmsg("cli_peheader: Unable to read e_lfanew - truncated header?\n");
        ret = CL_EFORMAT;
        goto done;
    }

    temp             = EC32(peinfo->e_lfanew);
    peinfo->e_lfanew = temp;
    if (opts & CLI_PEHEADER_OPT_DBG_PRINT_INFO) {
        cli_dbgmsg("e_lfanew == %d\n", peinfo->e_lfanew);
    }
    if (!peinfo->e_lfanew) {
        cli_dbgmsg("cli_peheader: Not a PE file - e_lfanew == 0\n");
        goto done;
    }

    if (fmap_readn(map, &(peinfo->file_hdr), peinfo->offset + peinfo->e_lfanew, sizeof(struct pe_image_file_hdr)) != sizeof(struct pe_image_file_hdr)) {
        /* bad information in e_lfanew - probably not a PE file */
        cli_dbgmsg("cli_peheader: Can't read file header\n");
        goto done;
    }

    file_hdr = &(peinfo->file_hdr);

    if (EC32(file_hdr->Magic) != PE_IMAGE_NT_SIGNATURE) {
        cli_dbgmsg("cli_peheader: Invalid PE signature (probably NE file)\n");
        goto done;
    }

    if (EC16(file_hdr->Characteristics) & 0x2000) {

        if (opts & CLI_PEHEADER_OPT_COLLECT_JSON)
            cli_jsonstr(pe_json, "Type", "DLL");

        if (opts & CLI_PEHEADER_OPT_DBG_PRINT_INFO) {
            cli_dbgmsg("File type: DLL\n");
        }

        is_dll = 1;
    } else if (EC16(file_hdr->Characteristics) & 0x0002) {

        if (opts & CLI_PEHEADER_OPT_COLLECT_JSON)
            cli_jsonstr(pe_json, "Type", "EXE");

        if (opts & CLI_PEHEADER_OPT_DBG_PRINT_INFO) {
            cli_dbgmsg("File type: Executable\n");
        }

        is_exe = 1;
    }

    if (!is_dll && !is_exe) {
        cli_dbgmsg("cli_peheader: Assumption Violated: PE is not a DLL or EXE\n");
        // TODO Don't continue if not an exe or dll?
    }

    peinfo->is_dll = is_dll;

    if (opts & CLI_PEHEADER_OPT_DBG_PRINT_INFO ||
        opts & CLI_PEHEADER_OPT_COLLECT_JSON) {
        switch (EC16(file_hdr->Machine)) {
            case 0x0:
                archtype = "Unknown";
                break;
            case 0x1:
                // New as of Windows 10, version 1607 and Windows Server 2016
                archtype = "Target Host";
                break;
            case 0x14c:
                archtype = "80386";
                break;
            case 0x14d:
                archtype = "80486";
                break;
            case 0x14e:
                archtype = "80586";
                break;
            case 0x160:
                archtype = "R3000 MIPS BE";
                break;
            case 0x162:
                archtype = "R3000 MIPS LE";
                break;
            case 0x166:
                archtype = "R4000 MIPS LE";
                break;
            case 0x168:
                archtype = "R10000 MIPS LE";
                break;
            case 0x169:
                archtype = "WCE MIPS LE";
                break;
            case 0x184:
                archtype = "DEC Alpha AXP";
                break;
            case 0x1a2:
                archtype = "Hitachi SH3 LE";
                break;
            case 0x1a3:
                archtype = "Hitachi SH3-DSP";
                break;
            case 0x1a4:
                archtype = "Hitachi SH3-E LE";
                break;
            case 0x1a6:
                archtype = "Hitachi SH4 LE";
                break;
            case 0x1a8:
                archtype = "Hitachi SH5";
                break;
            case 0x1c0:
                archtype = "ARM LE";
                break;
            case 0x1c2:
                archtype = "ARM Thumb/Thumb-2 LE";
                break;
            case 0x1c4:
                archtype = "ARM Thumb-2 LE";
                break;
            case 0x1d3:
                archtype = "AM33";
                break;
            case 0x1f0:
                archtype = "PowerPC LE";
                break;
            case 0x1f1:
                archtype = "PowerPC FP";
                break;
            case 0x200:
                archtype = "IA64";
                break;
            case 0x266:
                archtype = "MIPS16";
                break;
            case 0x268:
                archtype = "M68k";
                break;
            case 0x284:
                archtype = "DEC Alpha AXP 64bit";
                break;
            case 0x366:
                archtype = "MIPS+FPU";
                break;
            case 0x466:
                archtype = "MIPS16+FPU";
                break;
            case 0x520:
                archtype = "Infineon TriCore";
                break;
            case 0xcef:
                archtype = "CEF";
                break;
            case 0xebc:
                archtype = "EFI Byte Code";
                break;
            case 0x8664:
                archtype = "AMD64";
                break;
            case 0x9041:
                archtype = "M32R";
                break;
            case 0xaa64:
                archtype = "ARM64 LE";
                break;
            case 0xc0ee:
                archtype = "CEE";
                break;
            default:
                archtype = "Unknown";
        }

        if (opts & CLI_PEHEADER_OPT_DBG_PRINT_INFO)
            cli_dbgmsg("Machine type: %s\n", archtype);

        if (opts & CLI_PEHEADER_OPT_COLLECT_JSON)
            cli_jsonstr(pe_json, "ArchType", archtype);
    }

    peinfo->nsections = EC16(file_hdr->NumberOfSections);
    if (peinfo->nsections == 0) {

        if (opts & CLI_PEHEADER_OPT_COLLECT_JSON) {
            pe_add_heuristic_property(ctx, "BadNumberOfSections");
        }

        // TODO Investigate how corrupted_input is set and whether this
        // check is needed
        if (opts & CLI_PEHEADER_OPT_DBG_PRINT_INFO &&
            !ctx->corrupted_input) {
            if (peinfo->nsections == 0) {
                cli_dbgmsg("cli_peheader: Invalid NumberOfSections (0)\n");
            }
        }
        ret = CL_EFORMAT;
        goto done;
    }

    timestamp    = (time_t)EC32(file_hdr->TimeDateStamp);
    opt_hdr_size = EC16(file_hdr->SizeOfOptionalHeader);

    if (opts & CLI_PEHEADER_OPT_DBG_PRINT_INFO) {
        cli_dbgmsg("NumberOfSections: %d\n", peinfo->nsections);
        cli_dbgmsg("TimeDateStamp: %s", cli_ctime(&timestamp, timestr, sizeof(timestr)));
        cli_dbgmsg("SizeOfOptionalHeader: 0x%x\n", opt_hdr_size);
    }

    if (opts & CLI_PEHEADER_OPT_COLLECT_JSON) {
        cli_jsonint(pe_json, "NumberOfSections", peinfo->nsections);
        /* NOTE: the TimeDateStamp value will look like "Wed Dec 31 19:00:00 1969\n" */
        cli_jsonstr(pe_json, "TimeDateStamp", cli_ctime(&timestamp, timestr, sizeof(timestr)));
        cli_jsonint(pe_json, "SizeOfOptionalHeader", opt_hdr_size);
    }

    // Ensure there are enough bytes to cover the full optional header,
    // not including the data directory entries (which aren't all guaranteed
    // to be there)
    if (opt_hdr_size < sizeof(struct pe_image_optional_hdr32)) {
        cli_dbgmsg("cli_peheader: SizeOfOptionalHeader too small\n");

        if (opts & CLI_PEHEADER_OPT_COLLECT_JSON) {
            pe_add_heuristic_property(ctx, "BadOptionalHeaderSize");
        }

        ret = CL_EFORMAT;
        goto done;
    }

    at = peinfo->offset + peinfo->e_lfanew + sizeof(struct pe_image_file_hdr);
    if (fmap_readn(map, &(peinfo->pe_opt.opt32), at, sizeof(struct pe_image_optional_hdr32)) != sizeof(struct pe_image_optional_hdr32)) {
        cli_dbgmsg("cli_peheader: Can't read optional file header\n");
        ret = CL_EFORMAT;
        goto done;
    }
    stored_opt_hdr_size = sizeof(struct pe_image_optional_hdr32);
    at += stored_opt_hdr_size;

    opt32 = &(peinfo->pe_opt.opt32);

    if (EC16(opt32->Magic) == PE32P_SIGNATURE) { /* PE+ */
        // The PE32+ optional header is bigger by 16 bytes, so map in the
        // additional bytes here

        if (opt_hdr_size < sizeof(struct pe_image_optional_hdr64)) {
            cli_dbgmsg("cli_peheader: Incorrect SizeOfOptionalHeader for PE32+\n");

            if (opts & CLI_PEHEADER_OPT_COLLECT_JSON) {
                pe_add_heuristic_property(ctx, "BadOptionalHeaderSizePE32Plus");
            }

            ret = CL_EFORMAT;
            goto done;
        }

        if (fmap_readn(map, (void *)((size_t)&peinfo->pe_opt.opt64 + sizeof(struct pe_image_optional_hdr32)), at, OPT_HDR_SIZE_DIFF) != OPT_HDR_SIZE_DIFF) {
            cli_dbgmsg("cli_peheader: Can't read additional optional file header bytes\n");
            ret = CL_EFORMAT;
            goto done;
        }

        stored_opt_hdr_size += OPT_HDR_SIZE_DIFF;
        at += OPT_HDR_SIZE_DIFF;
        peinfo->is_pe32plus = 1;

        opt64 = &(peinfo->pe_opt.opt64);

        peinfo->vep       = EC32(opt64->AddressOfEntryPoint);
        peinfo->hdr_size  = EC32(opt64->SizeOfHeaders);
        peinfo->ndatadirs = EC32(opt64->NumberOfRvaAndSizes);

        if (opts & CLI_PEHEADER_OPT_DBG_PRINT_INFO) {
            cli_dbgmsg("File format: PE32+\n");
            cli_dbgmsg("MajorLinkerVersion: %d\n", opt64->MajorLinkerVersion);
            cli_dbgmsg("MinorLinkerVersion: %d\n", opt64->MinorLinkerVersion);
            cli_dbgmsg("SizeOfCode: 0x%x\n", EC32(opt64->SizeOfCode));
            cli_dbgmsg("SizeOfInitializedData: 0x%x\n", EC32(opt64->SizeOfInitializedData));
            cli_dbgmsg("SizeOfUninitializedData: 0x%x\n", EC32(opt64->SizeOfUninitializedData));
            cli_dbgmsg("AddressOfEntryPoint: 0x%x\n", peinfo->vep);
            cli_dbgmsg("BaseOfCode: 0x%x\n", EC32(opt64->BaseOfCode));
            cli_dbgmsg("SectionAlignment: 0x%x\n", EC32(opt64->SectionAlignment));
            cli_dbgmsg("FileAlignment: 0x%x\n", EC32(opt64->FileAlignment));
            cli_dbgmsg("MajorSubsystemVersion: %d\n", EC16(opt64->MajorSubsystemVersion));
            cli_dbgmsg("MinorSubsystemVersion: %d\n", EC16(opt64->MinorSubsystemVersion));
            cli_dbgmsg("SizeOfImage: 0x%x\n", EC32(opt64->SizeOfImage));
            cli_dbgmsg("SizeOfHeaders: 0x%x\n", peinfo->hdr_size);
            cli_dbgmsg("NumberOfRvaAndSizes: %u\n", peinfo->ndatadirs);
        }

        if (opts & CLI_PEHEADER_OPT_COLLECT_JSON) {
            cli_jsonint(pe_json, "MajorLinkerVersion", opt64->MajorLinkerVersion);
            cli_jsonint(pe_json, "MinorLinkerVersion", opt64->MinorLinkerVersion);
            cli_jsonint(pe_json, "SizeOfCode", EC32(opt64->SizeOfCode));
            cli_jsonint(pe_json, "SizeOfInitializedData", EC32(opt64->SizeOfInitializedData));
            cli_jsonint(pe_json, "SizeOfUninitializedData", EC32(opt64->SizeOfUninitializedData));
            cli_jsonint(pe_json, "NumberOfRvaAndSizes", EC32(opt64->NumberOfRvaAndSizes));
            cli_jsonint(pe_json, "MajorSubsystemVersion", EC16(opt64->MajorSubsystemVersion));
            cli_jsonint(pe_json, "MinorSubsystemVersion", EC16(opt64->MinorSubsystemVersion));

            snprintf(jsonbuf, sizeof(jsonbuf), "0x%x", peinfo->vep);
            cli_jsonstr(pe_json, "EntryPoint", jsonbuf);

            snprintf(jsonbuf, sizeof(jsonbuf), "0x%x", EC32(opt64->BaseOfCode));
            cli_jsonstr(pe_json, "BaseOfCode", jsonbuf);

            snprintf(jsonbuf, sizeof(jsonbuf), "0x%x", EC32(opt64->SectionAlignment));
            cli_jsonstr(pe_json, "SectionAlignment", jsonbuf);

            snprintf(jsonbuf, sizeof(jsonbuf), "0x%x", EC32(opt64->FileAlignment));
            cli_jsonstr(pe_json, "FileAlignment", jsonbuf);

            snprintf(jsonbuf, sizeof(jsonbuf), "0x%x", EC32(opt64->SizeOfImage));
            cli_jsonstr(pe_json, "SizeOfImage", jsonbuf);

            snprintf(jsonbuf, sizeof(jsonbuf), "0x%x", peinfo->hdr_size);
            cli_jsonstr(pe_json, "SizeOfHeaders", jsonbuf);
        }

    } else { /* PE */
        peinfo->is_pe32plus = 0;
        peinfo->vep         = EC32(opt32->AddressOfEntryPoint);
        peinfo->hdr_size    = EC32(opt32->SizeOfHeaders);
        peinfo->ndatadirs   = EC32(opt32->NumberOfRvaAndSizes);

        if (opts & CLI_PEHEADER_OPT_DBG_PRINT_INFO) {
            cli_dbgmsg("File format: PE\n");
            cli_dbgmsg("MajorLinkerVersion: %d\n", opt32->MajorLinkerVersion);
            cli_dbgmsg("MinorLinkerVersion: %d\n", opt32->MinorLinkerVersion);
            cli_dbgmsg("SizeOfCode: 0x%x\n", EC32(opt32->SizeOfCode));
            cli_dbgmsg("SizeOfInitializedData: 0x%x\n", EC32(opt32->SizeOfInitializedData));
            cli_dbgmsg("SizeOfUninitializedData: 0x%x\n", EC32(opt32->SizeOfUninitializedData));
            cli_dbgmsg("AddressOfEntryPoint: 0x%x\n", peinfo->vep);
            cli_dbgmsg("BaseOfCode: 0x%x\n", EC32(opt32->BaseOfCode));
            cli_dbgmsg("SectionAlignment: 0x%x\n", EC32(opt32->SectionAlignment));
            cli_dbgmsg("FileAlignment: 0x%x\n", EC32(opt32->FileAlignment));
            cli_dbgmsg("MajorSubsystemVersion: %d\n", EC16(opt32->MajorSubsystemVersion));
            cli_dbgmsg("MinorSubsystemVersion: %d\n", EC16(opt32->MinorSubsystemVersion));
            cli_dbgmsg("SizeOfImage: 0x%x\n", EC32(opt32->SizeOfImage));
            cli_dbgmsg("SizeOfHeaders: 0x%x\n", peinfo->hdr_size);
            cli_dbgmsg("NumberOfRvaAndSizes: %u\n", peinfo->ndatadirs);
        }

        if (opts & CLI_PEHEADER_OPT_COLLECT_JSON) {
            cli_jsonint(pe_json, "MajorLinkerVersion", opt32->MajorLinkerVersion);
            cli_jsonint(pe_json, "MinorLinkerVersion", opt32->MinorLinkerVersion);
            cli_jsonint(pe_json, "SizeOfCode", EC32(opt32->SizeOfCode));
            cli_jsonint(pe_json, "SizeOfInitializedData", EC32(opt32->SizeOfInitializedData));
            cli_jsonint(pe_json, "SizeOfUninitializedData", EC32(opt32->SizeOfUninitializedData));
            cli_jsonint(pe_json, "NumberOfRvaAndSizes", EC32(opt32->NumberOfRvaAndSizes));
            cli_jsonint(pe_json, "MajorSubsystemVersion", EC16(opt32->MajorSubsystemVersion));
            cli_jsonint(pe_json, "MinorSubsystemVersion", EC16(opt32->MinorSubsystemVersion));

            snprintf(jsonbuf, sizeof(jsonbuf), "0x%x", peinfo->vep);
            cli_jsonstr(pe_json, "EntryPoint", jsonbuf);

            snprintf(jsonbuf, sizeof(jsonbuf), "0x%x", EC32(opt32->BaseOfCode));
            cli_jsonstr(pe_json, "BaseOfCode", jsonbuf);

            snprintf(jsonbuf, sizeof(jsonbuf), "0x%x", EC32(opt32->SectionAlignment));
            cli_jsonstr(pe_json, "SectionAlignment", jsonbuf);

            snprintf(jsonbuf, sizeof(jsonbuf), "0x%x", EC32(opt32->FileAlignment));
            cli_jsonstr(pe_json, "FileAlignment", jsonbuf);

            snprintf(jsonbuf, sizeof(jsonbuf), "0x%x", EC32(opt32->SizeOfImage));
            cli_jsonstr(pe_json, "SizeOfImage", jsonbuf);

            snprintf(jsonbuf, sizeof(jsonbuf), "0x%x", peinfo->hdr_size);
            cli_jsonstr(pe_json, "SizeOfHeaders", jsonbuf);
        }
    }

    salign = (peinfo->is_pe32plus && opt64 != NULL) ? EC32(opt64->SectionAlignment) : EC32(opt32->SectionAlignment);
    falign = (peinfo->is_pe32plus && opt64 != NULL) ? EC32(opt64->FileAlignment) : EC32(opt32->FileAlignment);

    switch ((peinfo->is_pe32plus && opt64 != NULL) ? EC16(opt64->Subsystem) : EC16(opt32->Subsystem)) {
        case 0:
            subsystem = "Unknown";
            break;
        case 1:
            subsystem = "Native (svc)";
            native    = 1;
            break;
        case 2:
            subsystem = "Win32 GUI";
            break;
        case 3:
            subsystem = "Win32 console";
            break;
        case 5:
            subsystem = "OS/2 console";
            break;
        case 7:
            subsystem = "POSIX console";
            break;
        case 8:
            subsystem = "Native Win9x driver";
            break;
        case 9:
            subsystem = "WinCE GUI";
            break;
        case 10:
            subsystem = "EFI application";
            break;
        case 11:
            subsystem = "EFI driver";
            break;
        case 12:
            subsystem = "EFI runtime driver";
            break;
        case 13:
            subsystem = "EFI ROM image";
            break;
        case 14:
            subsystem = "Xbox";
            break;
        case 16:
            subsystem = "Boot application";
            break;
        default:
            subsystem = "Unknown";
    }

    if (opts & CLI_PEHEADER_OPT_DBG_PRINT_INFO) {
        cli_dbgmsg("Subsystem: %s\n", subsystem);
        cli_dbgmsg("------------------------------------\n");
    }

    if (opts & CLI_PEHEADER_OPT_COLLECT_JSON)
        cli_jsonstr(pe_json, "Subsystem", subsystem);

    if (!native && (!salign || (salign % 0x1000))) {
        cli_dbgmsg("cli_peheader: Bad section alignment\n");
        if (opts & CLI_PEHEADER_OPT_STRICT_ON_PE_ERRORS) {
            ret = CL_EFORMAT;
            goto done;
        }
    }

    if (!native && (!falign || (falign % 0x200))) {
        cli_dbgmsg("cli_peheader: Bad file alignment\n");
        if (opts & CLI_PEHEADER_OPT_STRICT_ON_PE_ERRORS) {
            ret = CL_EFORMAT;
            goto done;
        }
    }

    // Map in the optional header data directories.  The spec defines 16
    // directory entries, but NumberOfRvaAndSizes can be less than that
    // and the Windows loader will pretend that the data directory does
    // not exist. NumberOfRvaAndSizes can be larger than that too, which
    // the Windows loader is OK with.  To populate peinfo->dirs, we will
    // copy in as many data dirs are specified but for a max of 16 (and
    // adjust peinfo->ndatadirs accordingly)

    if (peinfo->ndatadirs > 0x10) {
        cli_dbgmsg("cli_peheader: Encountered NumberOfRvaAndSizes > 16 (suspicious)\n");
    }

    // In the case where we won't fully populate dirs with file data,
    // ensure that the underlying memory is zero so that existing code
    // can interact with peinfo->dirs without using peinfo->ndatadirs
    if (peinfo->ndatadirs < sizeof(peinfo->dirs) / sizeof(peinfo->dirs[0])) {
        memset(&(peinfo->dirs), '\0', sizeof(peinfo->dirs));
    }

    peinfo->ndatadirs = MIN(peinfo->ndatadirs, sizeof(peinfo->dirs) / sizeof(peinfo->dirs[0]));

    data_dirs_size = sizeof(struct pe_image_data_dir) * peinfo->ndatadirs;

    if (opt_hdr_size < (stored_opt_hdr_size + data_dirs_size)) {
        cli_dbgmsg("cli_peheader: SizeOfOptionalHeader too small (doesn't include data dir size)\n");
        ret = CL_EFORMAT;
        goto done;
    }

    read = fmap_readn(map, peinfo->dirs, at, data_dirs_size);
    if ((read == (size_t)-1) || (read != data_dirs_size)) {
        cli_dbgmsg("cli_peheader: Can't read optional file header data dirs\n");
        goto done;
    }

    for (i = 0; i < peinfo->ndatadirs; i++) {
        uint32_t tmp;
        struct pe_image_data_dir *dir = peinfo->dirs;

        tmp                   = EC32(dir[i].VirtualAddress);
        dir[i].VirtualAddress = tmp;

        tmp         = EC32(dir[i].Size);
        dir[i].Size = tmp;
    }

    at += data_dirs_size;

    if (opt_hdr_size != (stored_opt_hdr_size + data_dirs_size)) {
        /* Seek to the end of the long header */
        cli_dbgmsg("cli_peheader: Encountered case where SizeOfOptionalHeader appears bigger than required\n");
        at += opt_hdr_size - (stored_opt_hdr_size + data_dirs_size);
    }

    // TODO This level of processing might not be needed in all cases

    // Sanity checks
    // TODO Also check that salign >= falign
    if (peinfo->hdr_size != PESALIGN(peinfo->hdr_size, salign)) {
        cli_dbgmsg("cli_peheader: SizeOfHeader is not aligned to the SectionAlignment\n");
    }
    if (peinfo->hdr_size != PESALIGN(peinfo->hdr_size, falign)) {
        cli_dbgmsg("cli_peheader: SizeOfHeader is not aligned to the FileAlignment\n");
    }

    // TODO Why align here? -- /* Aligned headers virtual size */
    // hdr_size should already be rounded up
    // to a multiple of the file alignment.
    // TODO in cli_checkpe_fp this aligned to falign, elsewhere it aligned to salign
    peinfo->hdr_size = PESALIGN(peinfo->hdr_size, salign);

    peinfo->sections = (struct cli_exe_section *)cli_max_calloc(peinfo->nsections, sizeof(struct cli_exe_section));

    if (!peinfo->sections) {
        cli_dbgmsg("cli_peheader: Can't allocate memory for section headers\n");
        goto done;
    }

    section_hdrs = (struct pe_image_section_hdr *)cli_max_calloc(peinfo->nsections, sizeof(struct pe_image_section_hdr));

    if (!section_hdrs) {
        cli_dbgmsg("cli_peheader: Can't allocate memory for section headers\n");
        goto done;
    }

    read = fmap_readn(map, section_hdrs, at, peinfo->nsections * sizeof(struct pe_image_section_hdr));
    if ((read == (size_t)-1) || (read != peinfo->nsections * sizeof(struct pe_image_section_hdr))) {
        cli_dbgmsg("cli_peheader: Can't read section header - possibly broken PE file\n");
        ret = CL_EFORMAT;
        goto done;
    }
    at += sizeof(struct pe_image_section_hdr) * peinfo->nsections;

    // TODO Verify that this performs correctly
    // TODO I'm not sure why this is necessary since the specification says
    // that PointerToRawData is expected to be a multiple of the file
    // alignment.  Should we report this is as a PE with an error?

    for (i = 0; falign != 0x200 && i < (size_t)peinfo->nsections; i++) {
        /* file alignment fallback mode - blah */
        if (falign && section_hdrs[i].SizeOfRawData && EC32(section_hdrs[i].PointerToRawData) % falign && !(EC32(section_hdrs[i].PointerToRawData) % 0x200)) {
            cli_dbgmsg("cli_peheader: Encountered section with unexpected alignment - triggering fallback mode\n");
            falign = 0x200;
        }
    }

    fsize = (map->len - peinfo->offset);

    // TODO Why do we fix up these alignments?  This shouldn't be needed?
    for (i = 0, section_pe_idx = 0; i < peinfo->nsections; i++, section_pe_idx++) {

        struct cli_exe_section *section          = &(peinfo->sections[i]);
        struct pe_image_section_hdr *section_hdr = &(section_hdrs[i]);
        char sname[9];

        // TODO I don't see any documentation that says VirtualAddress and VirtualSize must be aligned
        section->rva  = PEALIGN(EC32(section_hdr->VirtualAddress), salign);
        section->vsz  = PESALIGN(EC32(section_hdr->VirtualSize), salign);
        section->raw  = PEALIGN(EC32(section_hdr->PointerToRawData), falign);
        section->rsz  = PESALIGN(EC32(section_hdr->SizeOfRawData), falign);
        section->chr  = EC32(section_hdr->Characteristics);
        section->urva = EC32(section_hdr->VirtualAddress); /* Just in case */
        section->uvsz = EC32(section_hdr->VirtualSize);
        section->uraw = EC32(section_hdr->PointerToRawData);
        section->ursz = EC32(section_hdr->SizeOfRawData);

        /* First, if a section exists totally outside of a file, remove the
         * section from the list or zero out its size. */
        if (section->rsz) { /* Don't bother with virtual only sections */
            if (section->raw >= fsize || section->uraw >= fsize) {
                cli_dbgmsg("cli_peheader: Broken PE file - Section %zu starts or exists beyond the end of file (Offset@ %lu, Total filesize %lu)\n", section_pe_idx, (unsigned long)section->raw, (unsigned long)fsize);

                if (opts & CLI_PEHEADER_OPT_REMOVE_MISSING_SECTIONS) {
                    if (peinfo->nsections == 1) {
                        ret = CL_EFORMAT;
                        goto done;
                    }

                    for (j = i; j < (size_t)(peinfo->nsections - 1); j++)
                        memcpy(&(peinfo->sections[j]), &(peinfo->sections[j + 1]), sizeof(struct cli_exe_section));

                    for (j = i; j < (size_t)(peinfo->nsections - 1); j++)
                        memcpy(&section_hdrs[j], &section_hdrs[j + 1], sizeof(struct pe_image_section_hdr));

                    peinfo->nsections--;

                    // Adjust i since we removed a section and continue on
                    i--;
                    continue;

                } else {
                    section->rsz  = 0;
                    section->ursz = 0;
                }
            } else {

                /* If a section is truncated, adjust its size value */
                if (!CLI_ISCONTAINED_0_TO(fsize, section->raw, section->rsz)) {
                    cli_dbgmsg("cli_peheader: PE Section %zu raw+rsz extends past the end of the file by %lu bytes\n", section_pe_idx, (section->raw + section->rsz) - fsize);
                    section->rsz = fsize - section->raw;
                }

                if (!CLI_ISCONTAINED_0_TO(fsize, section->uraw, section->ursz)) {
                    cli_dbgmsg("cli_peheader: PE Section %zu uraw+ursz extends past the end of the file by %lu bytes\n", section_pe_idx, (section->uraw + section->ursz) - fsize);
                    section->ursz = fsize - section->uraw;
                }
            }
        }

        strncpy(sname, (char *)section_hdr->Name, 8);
        sname[8] = '\0';

        if (opts & CLI_PEHEADER_OPT_COLLECT_JSON) {
            add_section_info(ctx, &peinfo->sections[i]);

            if (cli_json_timeout_cycle_check(ctx, &toval) != CL_SUCCESS) {
                ret = CL_ETIMEOUT;
                goto done;
            }
        }

        // TODO Why do we do this
        // TODO Should this be done before we dump the json
        if (!section->vsz && section->rsz)
            section->vsz = PESALIGN(section->ursz, salign);

        if (opts & CLI_PEHEADER_OPT_DBG_PRINT_INFO) {
            cli_dbgmsg("Section %zu\n", section_pe_idx);
            cli_dbgmsg("Section name: %s\n", sname);
            cli_dbgmsg("Section data (from headers - in memory)\n");
            cli_dbgmsg("VirtualSize: 0x%x 0x%x\n", section->uvsz, section->vsz);
            cli_dbgmsg("VirtualAddress: 0x%x 0x%x\n", section->urva, section->rva);
            cli_dbgmsg("SizeOfRawData: 0x%x 0x%x\n", section->ursz, section->rsz);
            cli_dbgmsg("PointerToRawData: 0x%x 0x%x\n", section->uraw, section->raw);

            if (section->chr & 0x20) {
                cli_dbgmsg("Section contains executable code\n");
            }

            if (section->vsz < section->rsz) {
                cli_dbgmsg("Section contains free space\n");
                /*
                cli_dbgmsg("Dumping %d bytes\n", section_hdr.SizeOfRawData - section_hdr.VirtualSize);
                ddump(desc, section_hdr.PointerToRawData + section_hdr.VirtualSize, section_hdr.SizeOfRawData - section_hdr.VirtualSize, cli_gentemp(NULL));
                */
            }

            if (section->chr & 0x20000000)
                cli_dbgmsg("Section's memory is executable\n");

            if (section->chr & 0x80000000)
                cli_dbgmsg("Section's memory is writeable\n");

            cli_dbgmsg("------------------------------------\n");
        }

        if (!salign || (section->urva % salign)) { /* Bad section alignment */
            cli_dbgmsg("cli_peheader: Broken PE - section's VirtualAddress is misaligned\n");
            if (opts & CLI_PEHEADER_OPT_STRICT_ON_PE_ERRORS) {
                ret = CL_EFORMAT;
                goto done;
            }
        }

        // TODO should we skip all of these checks if it's an empty
        // section? Why the exception for uraw?
        if (section->urva >> 31 || section->uvsz >> 31 || (section->rsz && section->uraw >> 31) || peinfo->sections[i].ursz >> 31) {
            cli_dbgmsg("cli_peheader: Found PE values with sign bit set\n");
            ret = CL_EFORMAT;
            goto done;
        }

        if (!i) {
            if (section->urva != peinfo->hdr_size) { /* Bad first section RVA */
                cli_dbgmsg("cli_peheader: First section doesn't start immediately after the header\n");
                if (opts & CLI_PEHEADER_OPT_STRICT_ON_PE_ERRORS) {
                    ret = CL_EFORMAT;
                    goto done;
                }
            }

            peinfo->min = section->rva;
            peinfo->max = section->rva + section->rsz;
        } else {
            if (section->urva - peinfo->sections[i - 1].urva != peinfo->sections[i - 1].vsz) { /* No holes, no overlapping, no virtual disorder */
                cli_dbgmsg("cli_peheader: Virtually misplaced section (wrong order, overlapping, non contiguous)\n");
                if (opts & CLI_PEHEADER_OPT_STRICT_ON_PE_ERRORS) {
                    ret = CL_EFORMAT;
                    goto done;
                }
            }

            if (section->rva < peinfo->min)
                peinfo->min = section->rva;

            if (section->rva + section->rsz > peinfo->max) {
                peinfo->max           = section->rva + section->rsz;
                peinfo->overlay_start = section->raw + section->rsz;
            }

            // TODO This case might be possible, which would lead to us
            // mislabelling the overlay
            if (section->raw + section->rsz > peinfo->max) {
                cli_dbgmsg("cli_peheader: Assumption Violated: Last section end RVA isn't tied to the last section\n");
            }
        }
    }

    peinfo->overlay_size = fsize - peinfo->overlay_start;

    // NOTE: For DLLs the entrypoint is likely to be zero
    // TODO Should this offset include peinfo->offset?
    if (!(peinfo->ep = cli_rawaddr(peinfo->vep, peinfo->sections, peinfo->nsections, &err, fsize, peinfo->hdr_size)) && err) {
        cli_dbgmsg("cli_peheader: Broken PE file - Can't map EntryPoint to a file offset\n");
        ret = CL_EFORMAT;
        goto done;
    }

    if (opts & CLI_PEHEADER_OPT_COLLECT_JSON) {
        cli_jsonint(pe_json, "EntryPointOffset", peinfo->ep);

        if (cli_json_timeout_cycle_check(ctx, &toval) != CL_SUCCESS) {
            ret = CL_ETIMEOUT;
            goto done;
        }
    }

    if (opts & CLI_PEHEADER_OPT_DBG_PRINT_INFO) {
        cli_dbgmsg("EntryPoint offset: 0x%x (%d)\n", peinfo->ep, peinfo->ep);
    }

    if (is_dll || peinfo->ndatadirs < 3 || !peinfo->dirs[2].Size)
        peinfo->res_addr = 0;
    else
        peinfo->res_addr = peinfo->dirs[2].VirtualAddress;

    while (opts & CLI_PEHEADER_OPT_EXTRACT_VINFO &&
           peinfo->ndatadirs >= 3 && peinfo->dirs[2].Size) {
        struct vinfo_list vlist;
        const uint8_t *vptr, *baseptr;
        uint32_t rva, res_sz;

        // TODO This code assumes peinfo->offset == 0, which might not always
        // be the case.
        if (0 != peinfo->offset) {
            cli_dbgmsg("cli_peheader: Assumption Violated: Looking for version info when peinfo->offset != 0\n");
        }

        memset(&vlist, 0, sizeof(vlist));
        findres(0x10, 0xffffffff, map, peinfo, versioninfo_cb, &vlist);
        if (!vlist.count)
            break; /* No version_information */

        if (cli_hashset_init(&peinfo->vinfo, 32, 80)) {
            cli_errmsg("cli_peheader: Unable to init vinfo hashset\n");
            goto done;
        }

        err = 0;
        for (i = 0; i < vlist.count; i++) { /* enum all version_information res - RESUMABLE */
            cli_dbgmsg("cli_peheader: parsing version info @ rva %x (%zu/%u)\n", vlist.rvas[i], i + 1, vlist.count);
            rva = cli_rawaddr(vlist.rvas[i], peinfo->sections, peinfo->nsections, &err, fsize, peinfo->hdr_size);
            if (err)
                continue;

            if (!(vptr = fmap_need_off_once(map, rva, 16)))
                continue;

            baseptr = vptr - rva;
            /* parse resource */
            rva    = cli_readint32(vptr);     /* ptr to version_info */
            res_sz = cli_readint32(vptr + 4); /* sizeof(resource) */
            rva    = cli_rawaddr(rva, peinfo->sections, peinfo->nsections, &err, fsize, peinfo->hdr_size);
            if (err)
                continue;
            if (!(vptr = fmap_need_off_once(map, rva, res_sz)))
                continue;

            while (res_sz > 4) { /* look for version_info - NOT RESUMABLE (expecting exactly one versioninfo) */
                uint32_t vinfo_sz, vinfo_val_sz, got_varfileinfo = 0;

                vinfo_sz = vinfo_val_sz = cli_readint32(vptr);
                vinfo_sz &= 0xffff;
                if (vinfo_sz > res_sz)
                    break; /* the content is larger than the container */

                vinfo_val_sz >>= 16;
                if (vinfo_sz <= 6 + 0x20 + 2 + 0x34 ||
                    vinfo_val_sz != 0x34 ||
                    memcmp(vptr + 6, "V\0S\0_\0V\0E\0R\0S\0I\0O\0N\0_\0I\0N\0F\0O\0\0\0", 0x20) ||
                    (unsigned int)cli_readint32(vptr + 0x28) != 0xfeef04bd) {
                    /* - there should be enough room for the header(6), the key "VS_VERSION_INFO"(20), the padding(2) and the value(34)
                     * - the value should be sizeof(fixedfileinfo)
                     * - the key should match
                     * - there should be some proper magic for fixedfileinfo */
                    break; /* there's no point in looking further */
                }

                /* move to the end of fixedfileinfo where the child elements are located */
                vptr += 6 + 0x20 + 2 + 0x34;
                vinfo_sz -= 6 + 0x20 + 2 + 0x34;

                while (vinfo_sz > 6) { /* look for stringfileinfo - NOT RESUMABLE (expecting at most one stringfileinfo) */
                    uint32_t sfi_sz = cli_readint32(vptr) & 0xffff;

                    if (sfi_sz > vinfo_sz)
                        break; /* the content is larger than the container */

                    if (!got_varfileinfo && sfi_sz > 6 + 0x18 && !memcmp(vptr + 6, "V\0a\0r\0F\0i\0l\0e\0I\0n\0f\0o\0\0\0", 0x18)) {
                        /* skip varfileinfo as it sometimes appear before stringtableinfo */
                        vptr += sfi_sz;
                        vinfo_sz -= sfi_sz;
                        got_varfileinfo = 1;
                        continue;
                    }

                    if (sfi_sz <= 6 + 0x1e || memcmp(vptr + 6, "S\0t\0r\0i\0n\0g\0F\0i\0l\0e\0I\0n\0f\0o\0\0\0", 0x1e)) {
                        /* - there should be enough room for the header(6) and the key "StringFileInfo"(1e)
                         * - the key should match */
                        break; /* this is an implicit hard fail: parent is not resumable */
                    }

                    /* move to the end of stringfileinfo where the child elements are located */
                    vptr += 6 + 0x1e;
                    sfi_sz -= 6 + 0x1e;

                    while (sfi_sz > 6) { /* enum all stringtables - RESUMABLE */
                        uint32_t st_sz           = cli_readint32(vptr) & 0xffff;
                        const uint8_t *next_vptr = vptr + st_sz;
                        uint32_t next_sfi_sz     = sfi_sz - st_sz;

                        if (st_sz > sfi_sz || st_sz <= 24) {
                            /* - the content is larger than the container
                               - there's no room for a stringtables (headers(6) + key(16) + padding(2)) */
                            break; /* this is an implicit hard fail: parent is not resumable */
                        }

                        /* move to the end of stringtable where the child elements are located */
                        vptr += 24;
                        st_sz -= 24;

                        while (st_sz > 6) { /* enum all strings - RESUMABLE */
                            uint32_t s_sz, s_key_sz, s_val_sz;

                            s_sz = (cli_readint32(vptr) & 0xffff) + 3;
                            s_sz &= ~3;
                            if (s_sz > st_sz || s_sz <= 6 + 2 + 8) {
                                /* - the content is larger than the container
                                 * - there's no room for a minimal string
                                 * - there's no room for the value */
                                st_sz  = 0;
                                sfi_sz = 0;
                                break; /* force a hard fail */
                            }

                            /* ~wcstrlen(key) */
                            for (s_key_sz = 6; s_key_sz + 1 < s_sz; s_key_sz += 2) {
                                if (vptr[s_key_sz] || vptr[s_key_sz + 1])
                                    continue;

                                s_key_sz += 2;
                                break;
                            }

                            s_key_sz += 3;
                            s_key_sz &= ~3;

                            if (s_key_sz >= s_sz) {
                                /* key overflow */
                                vptr += s_sz;
                                st_sz -= s_sz;
                                continue;
                            }

                            s_val_sz = s_sz - s_key_sz;
                            s_key_sz -= 6;

                            if (s_val_sz <= 2) {
                                /* skip unset value */
                                vptr += s_sz;
                                st_sz -= s_sz;
                                continue;
                            }

                            if (cli_hashset_addkey(&peinfo->vinfo, (uint32_t)(vptr - baseptr + 6))) {
                                cli_errmsg("cli_peheader: Unable to add rva to vinfo hashset\n");
                                goto done;
                            }

                            if (cli_debug_flag) {
                                char *k, *v, *s;

                                /* FIXME: skip too long strings */
                                k = cli_utf16toascii((const char *)vptr + 6, s_key_sz);
                                if (k) {
                                    v = cli_utf16toascii((const char *)vptr + s_key_sz + 6, s_val_sz);
                                    if (v) {
                                        s = cli_str2hex((const char *)vptr + 6, s_key_sz + s_val_sz);
                                        if (s) {
                                            cli_dbgmsg("VersionInfo (%x): '%s'='%s' - VI:%s\n", (uint32_t)(vptr - baseptr + 6), k, v, s);
                                            free(s);
                                        }
                                        free(v);
                                    }
                                    free(k);
                                }
                            }
                            vptr += s_sz;
                            st_sz -= s_sz;
                        } /* enum all strings - RESUMABLE */
                        vptr   = next_vptr;
                        sfi_sz = next_sfi_sz * (sfi_sz != 0);
                    } /* enum all stringtables - RESUMABLE */
                    break;
                } /* look for stringfileinfo - NOT RESUMABLE */
                break;
            } /* look for version_info - NOT RESUMABLE */
        }     /* enum all version_information res - RESUMABLE */
        break;
    } /* while(dirs[2].Size) */

    // Do final preparations for peinfo to be passed back
    peinfo->is_dll = is_dll;

    ret = CL_SUCCESS;

done:
    /* In the fail case, peinfo will get destroyed by the caller */

    if (NULL != section_hdrs) {
        free(section_hdrs);
    }

    return ret;
}

// TODO We should sort based on VirtualAddress instead, since PointerToRawData
// will be zero for sections where SizeOfRawData is zero.  This also aligns
// with what tools like pefile do.
static int sort_sects(const void *first, const void *second)
{
    const struct cli_exe_section *a = first, *b = second;
    return (a->raw - b->raw);
}

/* Check the given PE file for an authenticode signature and return whether
 * the signature is valid.  There are two cases that this function should
 * handle:
 * - A PE file has an embedded Authenticode section
 * - The PE file has no embedded Authenticode section but is covered by a
 *   catalog file that was loaded in via a -d
 *
 * If peinfo is NULL, one will be created internally and used
 *
 * CL_VERIFIED will be returned if the file was trusted based on its
 * signature.  CL_VIRUS will be returned if the file was blocked based on
 * its signature.  Otherwise, a cl_error_t error value will be returned.
 *
 * If CL_VIRUS is returned, cli_append_virus will get called, adding the
 * name associated with the block list CRB rules to the list of found viruses.*/
cl_error_t cli_check_auth_header(cli_ctx *ctx, struct cli_exe_info *peinfo)
{
    size_t at;
    unsigned int i, j, hlen;
    size_t fsize;
    fmap_t *map   = ctx->fmap;
    void *hashctx = NULL;
    struct pe_certificate_hdr cert_hdr;
    struct cli_mapped_region *regions = NULL;
    unsigned int nregions;
    cl_error_t ret = CL_EVERIFY;
    uint8_t authsha[SHA256_HASH_SIZE];
    uint32_t sec_dir_offset;
    uint32_t sec_dir_size;
    struct cli_exe_info _peinfo;

    // If Authenticode parsing has been disabled via DCONF or an engine
    // option, then don't continue on.
    if (!(DCONF & PE_CONF_CERTS))
        return CL_EVERIFY;

    if (ctx->engine->engine_options & ENGINE_OPTIONS_DISABLE_PE_CERTS)
        return CL_EVERIFY;

    // If peinfo is NULL, initialize one.  This makes it so that this function
    // can be used easily by sigtool
    if (NULL == peinfo) {
        peinfo = &_peinfo;
        cli_exe_info_init(peinfo, 0);

        if (CL_SUCCESS != cli_peheader(ctx->fmap, peinfo, CLI_PEHEADER_OPT_NONE, NULL)) {
            cli_exe_info_destroy(peinfo);
            return CL_EFORMAT;
        }
    }

    sec_dir_offset = peinfo->dirs[4].VirtualAddress;
    sec_dir_size   = peinfo->dirs[4].Size;

    // As an optimization, check the security DataDirectory here and if
    // it's less than 8-bytes (and we aren't relying on this code to compute
    // the section hashes), bail out if we don't have any Authenticode hashes
    // loaded from .cat files. The value 2 in these calls is the sentinel value
    // for the 'PE' .cat Authenticode hash file type.
    if (sec_dir_size < 8 &&
        !cli_hm_have_size(ctx->engine->hm_fp, CLI_HASH_SHA1, 2) &&
        !cli_hm_have_size(ctx->engine->hm_fp, CLI_HASH_SHA256, 2)) {
        ret = CL_BREAK;
        goto finish;
    }
    fsize = map->len;

    // We'll build a list of the regions that need to be hashed and pass it to
    // asn1_check_mscat to do hash verification there (the hash algorithm is
    // specified in the PKCS7 structure).  We need to hash up to 4 regions
    regions = (struct cli_mapped_region *)calloc(4, sizeof(struct cli_mapped_region));
    if (!regions) {
        ret = CL_EMEM;
        goto finish;
    }
    nregions = 0;

#define add_chunk_to_hash_list(_offset, _size) \
    do {                                       \
        regions[nregions].offset = (_offset);  \
        regions[nregions].size   = (_size);    \
        nregions++;                            \
    } while (0)

    // Pretty much every case below should return CL_EFORMAT
    ret = CL_EFORMAT;

    /* MZ to checksum */
    at   = 0;
    hlen = peinfo->e_lfanew + sizeof(struct pe_image_file_hdr) + (peinfo->is_pe32plus ? offsetof(struct pe_image_optional_hdr64, CheckSum) : offsetof(struct pe_image_optional_hdr32, CheckSum));
    add_chunk_to_hash_list(0, hlen);
    at = hlen + 4;

    /* Checksum to security */
    if (peinfo->is_pe32plus)
        hlen = sizeof(struct pe_image_optional_hdr64) - offsetof(struct pe_image_optional_hdr64, CheckSum) - 4;
    else
        hlen = sizeof(struct pe_image_optional_hdr32) - offsetof(struct pe_image_optional_hdr32, CheckSum) - 4;

    hlen += sizeof(struct pe_image_data_dir) * 4;
    add_chunk_to_hash_list(at, hlen);
    at += hlen + 8;

    if (at > peinfo->hdr_size) {
        goto finish;
    }

    if (sec_dir_offset) {

        // Verify that we have all the bytes we expect in the authenticode sig
        // and that the certificate table is the last thing in the file
        // (according to the MS13-098 bulletin, this is a requirement)
        if (fsize != sec_dir_size + sec_dir_offset) {
            cli_dbgmsg("cli_check_auth_header: expected authenticode data at the end of the file\n");
            goto finish;
        }

        // Hash everything else up to the start of the security section. Allow
        // the case where at == sec_dir_offset without adding another region
        // to hash, since this could technically be valid (although I haven't
        // verified this).
        if (at < sec_dir_offset) {
            hlen = sec_dir_offset - at;
            add_chunk_to_hash_list(at, hlen);
        } else if (at > sec_dir_offset) {
            cli_dbgmsg("cli_check_auth_header: security directory offset appears to overlap with the PE header\n");
            goto finish;
        }

        // Parse the security directory header

        if (fmap_readn(map, &cert_hdr, sec_dir_offset, sizeof(cert_hdr)) != sizeof(cert_hdr)) {
            goto finish;
        }

        if (EC16(cert_hdr.revision) != WIN_CERT_REV_2) {
            cli_dbgmsg("cli_check_auth_header: unsupported authenticode data revision\n");
            goto finish;
        }

        if (EC16(cert_hdr.type) != WIN_CERT_TYPE_PKCS7) {
            cli_dbgmsg("cli_check_auth_header: unsupported authenticode data type\n");
            goto finish;
        }

        hlen = sec_dir_size;

        if (EC32(cert_hdr.length) != hlen) {
            /* This is the case that MS13-098 aimed to address, but it got
             * pushback to where the fix (not allowing additional, non-zero
             * bytes in the security directory) is now opt-in via a registry
             * key.  Given that most machines will treat these binaries as
             * valid, we'll still parse the signature and just trust that
             * our trust signatures are tailored enough to where any
             * instances of this are reasonable (for instance, I saw one
             * binary that appeared to use this to embed a license key.) */
            cli_dbgmsg("cli_check_auth_header: MS13-098 violation detected, but continuing on to verify certificate\n");
        }

        at = sec_dir_offset + sizeof(cert_hdr);
        hlen -= sizeof(cert_hdr);

        ret = asn1_check_mscat((struct cl_engine *)(ctx->engine), map, at, hlen, regions, nregions, ctx);

        if (CL_VERIFIED == ret) {
            // We validated the embedded signature.  Hooray!
            goto finish;
        } else if (CL_VIRUS == ret) {
            // A block list rule hit - don't continue on to check hm_fp for a match
            goto finish;
        }

        // Otherwise, we still need to check to see whether this file is
        // covered by a .cat file (it's common these days for driver files
        // to have .cat files covering PEs with embedded signatures)

    } else {

        // Hash everything else
        if (at < fsize) {
            hlen = fsize - at;
            add_chunk_to_hash_list(at, hlen);
        }
    }

    // At this point we should compute the SHA1 authenticode hash to see
    // whether we've had any hashes added from external catalog files
    static const struct supported_hashes {
        const cli_hash_type_t hashtype;
        const char *hashctx_name;
    } supported_hashes[] = {
        {CLI_HASH_SHA1, "sha1"},
        {CLI_HASH_SHA256, "sha256"},
    };

    for (i = 0; i < (sizeof(supported_hashes) / sizeof(supported_hashes[0])); i++) {
        const cli_hash_type_t hashtype = supported_hashes[i].hashtype;
        const char *hashctx_name       = supported_hashes[i].hashctx_name;

        if (!cli_hm_have_size(ctx->engine->hm_fp, hashtype, 2)) {
            continue;
        }

        hashctx = cl_hash_init(hashctx_name);

        if (NULL == hashctx) {
            ret = CL_EMEM;
            goto finish;
        }

        for (j = 0; j < nregions; j++) {
            const uint8_t *hptr;
            if (0 == regions[j].size) {
                continue;
            }
            if (!(hptr = fmap_need_off_once(map, regions[j].offset, regions[j].size))) {
                break;
            }

            cl_update_hash(hashctx, hptr, regions[j].size);
        }

        if (j != nregions) {
            goto finish;
        }

        cl_finish_hash(hashctx, authsha);
        hashctx = NULL;

        if (cli_hm_scan(authsha, 2, NULL, ctx->engine->hm_fp, hashtype) == CL_VIRUS) {
            cli_dbgmsg("cli_check_auth_header: PE file trusted by catalog file (%s)\n", hashctx_name);
            ret = CL_VERIFIED;
            goto finish;
        }
    }

    ret = CL_EVERIFY;

finish:
    if (NULL != hashctx) {
        cl_hash_destroy(hashctx);
    }

    if (NULL != regions) {
        free(regions);
    }

    // If we created the peinfo, then destroy it.  Otherwise we don't own it
    if (&_peinfo == peinfo) {
        cli_exe_info_destroy(peinfo);
    }
    return ret;
}

/* Print out either the MD5, SHA1, or SHA256 associated with the imphash or
 * the individual sections. Also, this function computes the hashes of each
 * section (sorted based on the RVAs of the sections) if hashes is non-NULL.
 *
 * If the section hashes are to be computed and returned, this function
 * allocates memory for the section hashes, and it's up to the caller to free
 * it.  hashes->sections will be initialized to NULL at the beginning of the
 * function, and if after the call its value is non-NULL, the memory should be
 * freed.  Furthermore, if hashes->sections is non-NULL, the hashes can assume
 * to be valid regardless of the return code.
 *
 * Also, a few other notes:
 *  - If a section has a virtual size of zero, its corresponding hash value
 *    will not be computed and the hash contents will be all zeroes.
 *  - If a section extends beyond the end of the file, the section data and
 *    length will be truncated, and the hash generated accordingly
 *  - If a section exists completely outside of the file, it won't be included
 *    in the list of sections, and nsections will be adjusted accordingly.
 */
cl_error_t cli_genhash_pe(cli_ctx *ctx, unsigned int class, int type, stats_section_t *hashes)
{
    unsigned int i;
    struct cli_exe_info _peinfo;
    struct cli_exe_info *peinfo = &_peinfo;

    unsigned char *hash, *hashset[CLI_HASH_AVAIL_TYPES];
    int genhash[CLI_HASH_AVAIL_TYPES];
    int hlen = 0;

    if (hashes) {
        hashes->sections = NULL;

        if (class != CL_GENHASH_PE_CLASS_SECTION || type != 1) {
            cli_dbgmsg("`hashes` can only be populated with MD5 PE section data\n");
            return CL_EARG;
        }
    }

    if (class >= CL_GENHASH_PE_CLASS_LAST)
        return CL_EARG;

    // TODO see if peinfo can be passed in (or lives in ctx or something) and
    // if so, use that to avoid having to re-parse the header
    cli_exe_info_init(peinfo, 0);

    if (cli_peheader(ctx->fmap, peinfo, CLI_PEHEADER_OPT_NONE, NULL) != CL_SUCCESS) {
        cli_exe_info_destroy(peinfo);
        return CL_EFORMAT;
    }

    cli_qsort(peinfo->sections, peinfo->nsections, sizeof(*(peinfo->sections)), sort_sects);

    /* pick hashtypes to generate */
    memset(genhash, 0, sizeof(genhash));
    memset(hashset, 0, sizeof(hashset));
    switch (type) {
        case 1:
            genhash[CLI_HASH_MD5] = 1;
            hlen                  = hashlen[CLI_HASH_MD5];
            hash = hashset[CLI_HASH_MD5] = calloc(hlen, sizeof(char));
            break;
        case 2:
            genhash[CLI_HASH_SHA1] = 1;
            hlen                   = hashlen[CLI_HASH_SHA1];
            hash = hashset[CLI_HASH_SHA1] = calloc(hlen, sizeof(char));
            break;
        default:
            genhash[CLI_HASH_SHA256] = 1;
            hlen                     = hashlen[CLI_HASH_SHA256];
            hash = hashset[CLI_HASH_SHA256] = calloc(hlen, sizeof(char));
            break;
    }

    if (!hash) {
        cli_errmsg("cli_genhash_pe: calloc failed!\n");
        cli_exe_info_destroy(peinfo);
        return CL_EMEM;
    }

    if (hashes) {
        hashes->nsections = peinfo->nsections;
        hashes->sections  = cli_max_calloc(peinfo->nsections, sizeof(struct cli_section_hash));

        if (!(hashes->sections)) {
            cli_exe_info_destroy(peinfo);
            free(hash);
            return CL_EMEM;
        }
    }

    if (class == CL_GENHASH_PE_CLASS_SECTION) {
        char *dstr;

        for (i = 0; i < peinfo->nsections; i++) {
            /* Generate hashes */
            if (cli_hashsect(ctx->fmap, &peinfo->sections[i], hashset, genhash, genhash) == 1) {
                if (cli_debug_flag) {
                    dstr = cli_str2hex((char *)hash, hlen);
                    cli_dbgmsg("Section{%u}: %u:%s\n", i, peinfo->sections[i].rsz, dstr ? (char *)dstr : "(NULL)");
                    if (dstr != NULL) {
                        free(dstr);
                    }
                }
                if (hashes) {
                    memcpy(hashes->sections[i].md5, hash, sizeof(hashes->sections[i].md5));
                    hashes->sections[i].len = peinfo->sections[i].rsz;
                }
            } else if (peinfo->sections[i].rsz) {
                cli_dbgmsg("Section{%u}: failed to generate hash for section\n", i);
            } else {
                cli_dbgmsg("Section{%u}: section contains no data\n", i);
            }
        }
    } else if (class == CL_GENHASH_PE_CLASS_IMPTBL) {
        char *dstr;
        uint32_t impsz = 0;
        cl_error_t ret;

        /* Generate hash */
        ret = hash_imptbl(ctx, hashset, &impsz, genhash, peinfo);
        if (ret == CL_SUCCESS) {
            if (cli_debug_flag) {
                dstr = cli_str2hex((char *)hash, hlen);
                cli_dbgmsg("Imphash: %s:%u\n", dstr ? (char *)dstr : "(NULL)", impsz);
                if (dstr != NULL) {
                    free(dstr);
                }
            }
        } else {
            cli_dbgmsg("Imphash: failed to generate hash for import table (%d)\n", ret);
        }
    } else {
        cli_dbgmsg("cli_genhash_pe: unknown pe genhash class: %u\n", class);
    }

    free(hash);
    cli_exe_info_destroy(peinfo);
    return CL_SUCCESS;
}
