libclamav/spin.c
c2dfe70e
 /*
e1cbc270
  *  Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
  *  Copyright (C) 2007-2013 Sourcefire, Inc.
2023340a
  *
  *  Authors: Alberto Wu
c2dfe70e
  *
  *  This program is free software; you can redistribute it and/or modify
632be7ba
  *  it under the terms of the GNU General Public License version 2 as
  *  published by the Free Software Foundation.
c2dfe70e
  *
  *  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
48b7b4a7
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
c2dfe70e
  */
 
 /*
 ** spin.c
 ** 
 ** 19/07/2k5 - Finally started coding something
 ** 21/07/2k5 - Works, started clearing the mess
 ** 31/07/2k5 - Porting to libclamav
 */
 
 /*
 ** Unpacks pespin v1.1
 **
 ** Funny thing to reverse
 **
 ** [ A big fat thank to christoph for not letting me give up ]
 */
 
 /*
 ** TODO ( a fat one ):
 **
 ** OEP restore and unhijacking
 ** code redir handling (at least near OEP)
 ** passwd protection (didn't really look at it)
 **
 ** All this stuff really needs a way better emu and a hell of unlaming
 ** ATM not worth the effort... and pespin v1.3 is out :@
 **
 */
 
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
 #include <stdlib.h>
 #include <string.h>
 
60d8d2c3
 #include "clamav.h"
c2dfe70e
 #include "pe.h"
 #include "rebuildpe.h"
57866af1
 #include "execs.h"
c2dfe70e
 #include "others.h"
5d048883
 #include "packlibs.h"
fc83da82
 #include "spin.h"
c2dfe70e
 
f59443aa
 static char exec86(uint8_t aelle, uint8_t cielle, char *curremu, int *retval)
 {
     int len = 0;
     *retval = 0;
288057e9
     while (len < 0x24) {
f59443aa
         uint8_t opcode = curremu[len], support;
fa799269
         len++;
288057e9
         switch (opcode) {
             case 0xeb:
                 len++;
             case 0x0a:
                 len++;
             case 0x90:
             case 0xf8:
             case 0xf9:
                 break;
 
             case 0x02: /* add al, cl */
                 aelle += cielle;
                 len++;
                 break;
             case 0x2a: /* sub al, cl */
                 aelle -= cielle;
                 len++;
                 break;
             case 0x04: /* add al, ?? */
                 aelle += curremu[len];
                 len++;
                 break;
             case 0x2c: /* sub al, ?? */
                 aelle -= curremu[len];
                 len++;
                 break;
             case 0x32: /* xor al, cl */
                 aelle ^= cielle;
                 len++;
                 break;
             case 0x34: /* xor al, ?? */
                 aelle ^= curremu[len];
                 len++;
                 break;
 
             case 0xfe: /* inc/dec al */
                 if (curremu[len] == '\xc0')
                     aelle++;
                 else
                     aelle--;
                 len++;
                 break;
 
             case 0xc0: /* ror/rol al, ?? */
                 support = curremu[len];
                 len++;
                 if (support == 0xc0)
                     CLI_ROL(aelle, curremu[len]);
                 else
                     CLI_ROR(aelle, curremu[len]);
                 len++;
                 break;
 
             default:
                 cli_dbgmsg("spin: bogus opcode %x\n", opcode);
                 *retval = 1;
                 return aelle;
f59443aa
         }
     }
288057e9
     if (len != 0x24 || curremu[len] != '\xaa') {
f59443aa
         cli_dbgmsg("spin: bad emucode\n");
         *retval = 1;
c2dfe70e
     }
f59443aa
     return aelle;
c2dfe70e
 }
 
f59443aa
 static uint32_t summit(char *src, int size)
c2dfe70e
 {
f59443aa
     uint32_t eax = 0xffffffff, ebx = 0xffffffff;
     int i;
 
288057e9
     while (size) {
f59443aa
         eax ^= *src++ << 8 & 0xff00;
         eax = eax >> 3 & 0x1fffffff;
288057e9
         for (i = 0; i < 4; i++) {
f59443aa
             uint32_t swap;
             eax ^= ebx >> 8 & 0xff;
             eax += 0x7801a108;
             eax ^= ebx;
             CLI_ROR(eax, ebx & 0xff);
             swap = eax;
288057e9
             eax  = ebx;
             ebx  = swap;
f59443aa
         }
         size--;
c2dfe70e
     }
f59443aa
     return ebx;
c2dfe70e
 }
 
f59443aa
 int unspin(char *src, int ssize, struct cli_exe_section *sections, int sectcnt, uint32_t nep, int desc, cli_ctx *ctx)
 {
     char *curr, *emu, *ep, *spinned;
     char **sects;
     int blobsz = 0, j;
     uint32_t key32, bitmap, bitman;
     uint32_t len;
     uint8_t key8;
 
     cli_dbgmsg("in unspin\n");
 
288057e9
     if ((spinned = (char *)cli_malloc(sections[sectcnt].rsz)) == NULL) {
f59443aa
         cli_dbgmsg("spin: Unable to allocate memory for spinned\n");
         return 1;
     }
c2dfe70e
 
f59443aa
     memcpy(spinned, src + sections[sectcnt].raw, sections[sectcnt].rsz);
     ep = spinned + nep - sections[sectcnt].rva;
 
     curr = ep + 0xdb;
288057e9
     if (*curr != '\xbb') {
f59443aa
         free(spinned);
         cli_dbgmsg("spin: Not spinned or bad version\n");
         return 1;
c2dfe70e
     }
f59443aa
 
     key8 = (uint8_t) * ++curr;
     curr += 4;
288057e9
     if (*curr != '\xb9') {
f59443aa
         free(spinned);
         cli_dbgmsg("spin: Not spinned or bad version\n");
         return 1;
c2dfe70e
     }
f59443aa
 
288057e9
     if ((len = cli_readint32(curr + 1)) != 0x11fe) {
f59443aa
         free(spinned);
         cli_dbgmsg("spin: Not spinned or bad version\n");
         return 1;
62012ff1
     }
c2dfe70e
 
f59443aa
     cli_dbgmsg("spin: Key8 is %x, Len is %x\n", key8, len);
fa799269
 
288057e9
     if (!CLI_ISCONTAINED(spinned, sections[sectcnt].rsz, ep, len + 0x1fe5 - 1)) {
f59443aa
         free(spinned);
         cli_dbgmsg("spin: len out of bounds, giving up\n");
         return 1;
     }
c2dfe70e
 
f59443aa
     if (ep[0x1e0] != '\xb8')
         cli_dbgmsg("spin: prolly not spinned, expect failure\n");
c2dfe70e
 
f59443aa
     if ((cli_readint32(ep + 0x1e1) & 0x00200000))
         cli_dbgmsg("spin: password protected, expect failure\n");
c2dfe70e
 
f59443aa
     curr = ep + 0x1fe5 + len - 1;
288057e9
     while (len--) {
f59443aa
         *curr = (*curr) ^ (key8--);
         curr--;
     }
 
288057e9
     if (!CLI_ISCONTAINED(spinned, sections[sectcnt].rsz, ep + 0x3217, 4)) {
f59443aa
         free(spinned);
         cli_dbgmsg("spin: key out of bounds, giving up\n");
         return 1;
     }
 
288057e9
     curr  = ep + 0x26eb;
f59443aa
     key32 = cli_readint32(curr);
288057e9
     if ((len = cli_readint32(curr + 5)) != 0x5a0) {
f59443aa
         free(spinned);
         cli_dbgmsg("spin: Not spinned or bad version\n");
         return 1;
     }
 
     curr = ep + 0x2d5;
     cli_dbgmsg("spin: Key is %x, Len is %x\n", key32, len);
 
288057e9
     while (len--) {
         if (key32 & 1) {
f59443aa
             key32 = key32 >> 1;
             key32 ^= 0x8c328834;
288057e9
         } else {
f59443aa
             key32 = key32 >> 1;
         }
         *curr = *curr ^ (key32 & 0xff);
         curr++;
     }
 
     len = ssize - cli_readint32(ep + 0x429); /* sub size, value */
288057e9
     if (len >= (uint32_t)ssize) {
f59443aa
         free(spinned);
         cli_dbgmsg("spin: crc out of bounds, giving up\n");
         return 1;
     }
     key32 = cli_readint32(ep + 0x3217) - summit(src, len);
c2dfe70e
 
f59443aa
     memcpy(src + sections[sectcnt].raw, spinned, sections[sectcnt].rsz);
     free(spinned);                                                  /* done CRC'ing - can have a dirty buffer now */
     ep = src + nep + sections[sectcnt].raw - sections[sectcnt].rva; /* Fix the helper */
 
288057e9
     if (!CLI_ISCONTAINED(src, ssize, ep + 0x3207, 4)) { /* this one holds all ep based checks */
f59443aa
         cli_dbgmsg("spin: key out of bounds, giving up\n");
         return 1;
     }
     bitmap = cli_readint32(ep + 0x3207);
     cli_dbgmsg("spin: Key32 is %x - XORbitmap is %x\n", key32, bitmap);
 
     cli_dbgmsg("spin: Decrypting sects (xor)\n");
288057e9
     for (j = 0; j < sectcnt; j++) {
f59443aa
 
288057e9
         if (bitmap & 1) {
             uint32_t size   = sections[j].rsz;
             char *ptr       = src + sections[j].raw;
f59443aa
             uint32_t keydup = key32;
 
288057e9
             if (!CLI_ISCONTAINED(src, ssize, ptr, size)) {
f59443aa
                 cli_dbgmsg("spin: sect %d out of file, giving up\n", j);
                 return 1; /* FIXME: Already checked in pe.c? */
             }
 
288057e9
             while (size--) {
                 if (!(keydup & 1)) {
f59443aa
                     keydup = keydup >> 1;
                     keydup ^= 0xed43af31;
288057e9
                 } else {
f59443aa
                     keydup = keydup >> 1;
                 }
                 *ptr = *ptr ^ (keydup & 0xff);
                 ptr++;
             }
         }
         bitmap = bitmap >> 1;
     }
 
     cli_dbgmsg("spin: done\n");
 
     curr = ep + 0x644;
288057e9
     if ((len = cli_readint32(curr)) != 0x180) {
f59443aa
         cli_dbgmsg("spin: Not spinned or bad version\n");
         return 1;
     }
 
     key32 = cli_readint32(curr + 0x0c);
     cli_dbgmsg("spin: Key is %x, Len is %x\n", key32, len);
     curr = ep + 0x28d3;
 
288057e9
     if (!CLI_ISCONTAINED(src, ssize, curr, len)) { /* always true but i may decide to remove the previous check */
f59443aa
         cli_dbgmsg("spin: key out of bounds, giving up\n");
         return 1;
     }
288057e9
     while (len--) {
         if (key32 & 1) {
f59443aa
             key32 = key32 >> 1;
             key32 ^= 0xed43af32;
288057e9
         } else {
f59443aa
             key32 = key32 >> 1;
         }
         *curr = *curr ^ (key32 & 0xff);
         curr++;
     }
 
     curr = ep + 0x28dd;
288057e9
     if ((len = cli_readint32(curr)) != 0x1a1) {
f59443aa
         cli_dbgmsg("spin: Not spinned or bad version\n");
         return 1;
     }
 
     cli_dbgmsg("spin: POLY1 len is %x\n", len);
     curr += 0xf; /* POLY1 */
     emu = ep + 0x6d4;
288057e9
     if (!CLI_ISCONTAINED(src, ssize, emu, len)) {
f59443aa
         cli_dbgmsg("spin: poly1 out of bounds\n");
         return 1;
     }
288057e9
     while (len) {
f59443aa
         int xcfailure = 0;
288057e9
         *emu          = exec86(*emu, len-- & 0xff, curr, &xcfailure); /* unlame POLY1 */
         if (xcfailure) {
f59443aa
             cli_dbgmsg("spin: cannot exec poly1\n");
             return 1;
         }
c2dfe70e
         emu++;
     }
f59443aa
 
     bitmap = cli_readint32(ep + 0x6f1);
     cli_dbgmsg("spin: POLYbitmap is %x - decrypting sects (poly)\n", bitmap);
     curr = ep + 0x755;
 
288057e9
     for (j = 0; j < sectcnt; j++) {
         if (bitmap & 1) {
f59443aa
             uint32_t notthesamelen = sections[j].rsz;
 
             emu = src + sections[j].raw;
 
288057e9
             if (!CLI_ISCONTAINED(src, ssize, curr, 0x24)) { /* section bounds already checked twice now */
f59443aa
                 cli_dbgmsg("spin: poly1 emucode is out of file?\n");
                 return 1;
             }
 
288057e9
             while (notthesamelen) {
f59443aa
                 int xcfailure = 0;
288057e9
                 *emu          = exec86(*emu, notthesamelen-- & 0xff, curr, &xcfailure);
                 if (xcfailure) {
f59443aa
                     cli_dbgmsg("spin: cannot exec section\n");
                     return 1;
                 }
                 emu++;
             }
         }
         bitmap = bitmap >> 1;
f377e052
     }
f59443aa
 
     cli_dbgmsg("spin: done\n");
 
     bitmap = cli_readint32(ep + 0x3061);
     bitman = bitmap;
 
     /* FIXMELIMITS: possibly rewrite to use the limits api */
288057e9
     if (ctx->engine->maxfilesize) {
f59443aa
         unsigned long int filesize = 0;
 
288057e9
         for (j = 0; j < sectcnt; j++) {
             if (bitmap & 1) {
f59443aa
                 if (filesize > ctx->engine->maxfilesize || sections[j].vsz > ctx->engine->maxfilesize - filesize)
                     return 2;
                 filesize += sections[j].vsz;
             }
             bitmap >>= 1;
         }
 
         bitmap = bitman;
c2dfe70e
     }
f59443aa
 
     cli_dbgmsg("spin: Compression bitmap is %x\n", bitmap);
288057e9
     if ((sects = (char **)cli_malloc(sectcnt * sizeof(char *))) == NULL) {
7e64560c
         cli_dbgmsg("spin: malloc(%zu) failed\n", (size_t)sectcnt * sizeof(char *));
f59443aa
         return 1;
fa799269
     }
c2dfe70e
 
f59443aa
     len = 0;
288057e9
     for (j = 0; j < sectcnt; j++) {
         if (bitmap & 1) {
             if ((sects[j] = (char *)cli_malloc(sections[j].vsz)) == NULL) {
7e64560c
                 cli_dbgmsg("spin: malloc(%u) failed\n", sections[j].vsz);
f59443aa
                 len = 1;
                 break;
             }
             blobsz += sections[j].vsz;
             memset(sects[j], 0, sections[j].vsz);
             cli_dbgmsg("spin: Growing sect%d: was %x will be %x\n", j, sections[j].rsz, sections[j].vsz);
288057e9
             if (cli_unfsg(src + sections[j].raw, sects[j], sections[j].rsz, sections[j].vsz, NULL, NULL) == -1) {
f59443aa
                 len++;
                 cli_dbgmsg("spin: Unpack failure\n");
             }
288057e9
         } else {
f59443aa
             blobsz += sections[j].rsz;
             sects[j] = src + sections[j].raw;
             cli_dbgmsg("spin: Not growing sect%d\n", j);
         }
         bitmap >>= 1;
     }
c2dfe70e
 
f59443aa
     cli_dbgmsg("spin: decompression complete\n");
 
288057e9
     if (len) {
f59443aa
         int t;
288057e9
         for (t = 0; t < j; t++) {
f59443aa
             if (bitman & 1)
                 free(sects[t]);
             bitman = bitman >> 1 & 0x7fffffff;
         }
         free(sects);
         return 1;
     }
c2dfe70e
 
f59443aa
     key32 = cli_readint32(ep + 0x2fee);
288057e9
     if (key32) {
f59443aa
         /*    len = cli_readint32(ep+0x2fc8); -- Using vsizes instead */
 
288057e9
         for (j = 0; j < sectcnt; j++) {
f59443aa
             if (sections[j].rva <= key32 && key32 - sections[j].rva < sections[j].vsz && CLI_ISCONTAINED(src + sections[j].raw, sections[j].rsz, src + sections[j].raw, key32 - sections[j].rva))
                 break;
         }
 
288057e9
         if (j != sectcnt && ((bitman & (1 << j)) == 0)) { /* FIXME: not really sure either the res sect is lamed or just compressed, but this'll save some major headaches */
f59443aa
             cli_dbgmsg("spin: Resources (sect%d) appear to be compressed\n\tuncompressed offset %x, len %x\n\tcompressed offset %x, len %x\n", j, sections[j].rva, key32 - sections[j].rva, key32, sections[j].vsz - (key32 - sections[j].rva));
 
288057e9
             if ((curr = (char *)cli_malloc(sections[j].vsz)) != NULL) {
f59443aa
                 memcpy(curr, src + sections[j].raw, key32 - sections[j].rva);                           /* Uncompressed part */
                 memset(curr + key32 - sections[j].rva, 0, sections[j].vsz - (key32 - sections[j].rva)); /* bzero */
288057e9
                 if (cli_unfsg(src + sections[j].raw + key32 - sections[j].rva, curr + key32 - sections[j].rva, sections[j].rsz - (key32 - sections[j].rva), sections[j].vsz - (key32 - sections[j].rva), NULL, NULL)) {
f59443aa
 
                     free(curr);
                     cli_dbgmsg("spin: Failed to grow resources, continuing anyway\n");
                     blobsz += sections[j].rsz;
288057e9
                 } else {
f59443aa
                     sects[j] = curr;
                     bitman |= 1 << j;
                     cli_dbgmsg("spin: Resources grown\n");
                     blobsz += sections[j].vsz;
                 }
288057e9
             } else {
f59443aa
                 /* malloc failed but i'm too deep into this crap to quit without leaking more :( */
                 cli_dbgmsg("spin: memory allocation failed, continuing anyway\n");
                 blobsz += sections[j].rsz;
             }
288057e9
         } else {
f59443aa
             cli_dbgmsg("spin: No res?!\n");
         }
c2dfe70e
     }
 
f59443aa
     bitmap = bitman; /* save as a free() bitmap */
 
288057e9
     if ((ep = (char *)cli_malloc(blobsz)) != NULL) {
f59443aa
         struct cli_exe_section *rebhlp;
288057e9
         if ((rebhlp = (struct cli_exe_section *)cli_malloc(sizeof(struct cli_exe_section) * (sectcnt))) != NULL) {
             char *to   = ep;
f59443aa
             int retval = 0;
 
288057e9
             for (j = 0; j < sectcnt; j++) {
f59443aa
                 rebhlp[j].raw = (j > 0) ? (rebhlp[j - 1].raw + rebhlp[j - 1].rsz) : 0;
                 rebhlp[j].rsz = (bitmap & 1) ? sections[j].vsz : sections[j].rsz;
                 rebhlp[j].rva = sections[j].rva;
                 rebhlp[j].vsz = sections[j].vsz;
 
                 memcpy(to, sects[j], rebhlp[j].rsz);
                 to += rebhlp[j].rsz;
                 if (bitmap & 1)
                     free(sects[j]);
                 bitmap = bitmap >> 1;
             }
 
288057e9
             if (!cli_rebuildpe(ep, rebhlp, sectcnt, 0x400000, 0x1000, 0, 0, desc)) { /* can't be bothered fixing those values: the rebuilt exe is completely broken anyway. */
f59443aa
                 cli_dbgmsg("spin: Cannot write unpacked file\n");
                 retval = 1;
             }
             free(rebhlp);
             free(ep);
             free(sects);
             return retval;
         }
         free(ep);
c2dfe70e
     }
f59443aa
 
     cli_dbgmsg("spin: free bitmap is %x\n", bitman);
288057e9
     for (j = 0; j < sectcnt; j++) {
f59443aa
         if (bitmap & 1)
             free(sects[j]);
         bitman = bitman >> 1 & 0x7fffffff;
c2dfe70e
     }
f59443aa
     free(sects);
     return 1; /* :( */
c2dfe70e
 }