libclamav/spin.c
7c53d74b
 /*
  *  Copyright (C) 2005 aCaB <acab@clamav.net>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *  the Free Software Foundation; either version 2 of the License, or
  *  (at your option) any later version.
  *
  *  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
30738099
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
7c53d74b
  */
 
 /*
 ** 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>
 
 #include "cltypes.h"
 #include "pe.h"
 #include "rebuildpe.h"
e6bea1aa
 #include "execs.h"
7c53d74b
 #include "others.h"
87a4323f
 #include "packlibs.h"
7c53d74b
 
70ea6092
 #define EC32(x) le32_to_host(x) /* Convert little endian to host */
7c53d74b
 
6c611447
 static char exec86(uint8_t aelle, uint8_t cielle, char *curremu, int *retval) {
6910cc7f
   int len = 0;
6c611447
   *retval=0;
6910cc7f
   while (len <0x24) {
     uint8_t opcode = curremu[len], support;
     len++;
7c53d74b
     switch (opcode) {
       case 0xeb:
6910cc7f
         len++;
7c53d74b
       case 0x0a:
6910cc7f
         len++;
7c53d74b
       case 0x90:
       case 0xf8:
       case 0xf9:
         break;
 
       case 0x02: /* add al, cl */
         aelle+=cielle;
6910cc7f
 	len++;
7c53d74b
         break;
       case 0x2a: /* sub al, cl */
         aelle-=cielle;
6910cc7f
 	len++;
7c53d74b
         break;
       case 0x04: /* add al, ?? */
6910cc7f
         aelle+=curremu[len];
 	len++;
7c53d74b
         break;
       case 0x2c: /* sub al, ?? */
6910cc7f
         aelle-=curremu[len];
 	len++;
7c53d74b
         break;
       case 0x32: /* xor al, cl */
         aelle^=cielle;
6910cc7f
 	len++;
7c53d74b
         break;
       case 0x34: /* xor al, ?? */
6910cc7f
         aelle^=curremu[len];
 	len++;
7c53d74b
         break;
 
       case 0xfe: /* inc/dec al */
6910cc7f
         if ( curremu[len] == '\xc0' ) aelle++;
7c53d74b
 	else aelle--;
6910cc7f
         len++;
7c53d74b
         break;
 
       case 0xc0: /* ror/rol al, ?? */
6910cc7f
 	support = curremu[len];
         len++;
         if ( support == 0xc0 ) ROL(aelle, curremu[len]);
         else ROR(aelle, curremu[len]);
         len++;
7c53d74b
         break;
 
       default:
6c611447
         cli_dbgmsg("spin: bogus opcode %x\n", opcode);
 	*retval=1;
 	return aelle;
7c53d74b
     }
   }
6c611447
   if ( len!=0x24 || curremu[len]!='\xaa' ) {
     cli_dbgmsg("spin: bad emucode\n");
     *retval=1;
   }
7c53d74b
   return aelle;
 }
 
 
 static uint32_t summit (char *src, int size) 
 {
   uint32_t eax=0xffffffff, ebx=0xffffffff;
   int i;
 
   while(size) {
     eax ^= *src++<<8 & 0xff00;
     eax = eax>>3 & 0x1fffffff;
     for (i=0; i<4; i++) {
       uint32_t swap;
       eax ^= ebx>>8 & 0xff;
       eax += 0x7801a108;
       eax ^= ebx;
       ROR(eax, ebx&0xff);
       swap = eax;
       eax = ebx;
       ebx = swap;
     }
     size--; 
   }
   return ebx;
 }
 
 
ce4d0aa2
 int unspin(char *src, int ssize, struct pe_image_section_hdr *sections, int sectcnt, uint32_t nep, int desc, cli_ctx *ctx) {
7c53d74b
   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");
 
   if ( (spinned = (char *) cli_malloc(EC32(sections[sectcnt].SizeOfRawData))) == NULL )
     return 1;
 
   memcpy(spinned, src + EC32(sections[sectcnt].PointerToRawData), EC32(sections[sectcnt].SizeOfRawData)); 
   ep = spinned + nep - sections[sectcnt].VirtualAddress;
 
d7903e09
   curr = ep+0xdb;
7c53d74b
   if ( *curr != '\xbb' ) {
     free(spinned);
     cli_dbgmsg("spin: Not spinned or bad version\n");
     return 1;
   }
   
   key8 = (uint8_t)*++curr;
   curr+=4;
   if ( *curr != '\xb9' ) {
     free(spinned);
     cli_dbgmsg("spin: Not spinned or bad version\n");
     return 1;
   }
 
   if ( (len = cli_readint32(curr+1)) != 0x11fe ) {
     free(spinned);
     cli_dbgmsg("spin: Not spinned or bad version\n");
     return 1;
   }
 
   cli_dbgmsg("spin: Key8 is %x, Len is %x\n", key8, len);
 
d7903e09
   if (!CLI_ISCONTAINED(spinned, EC32(sections[sectcnt].SizeOfRawData), ep, len+0x1fe5-1)) {
7c53d74b
     free(spinned);
     cli_dbgmsg("spin: len out of bounds, giving up\n");
d7903e09
     return 1;
7c53d74b
   }
 
6910cc7f
   if ( ep[0x1e0]!='\xb8' )
     cli_dbgmsg("spin: prolly not spinned, expect failure\n");
   
   if ( (cli_readint32(ep+0x1e1) & 0x00200000) )
     cli_dbgmsg("spin: password protected, expect failure\n");
 
7c53d74b
   curr = ep+0x1fe5+len-1;
   while ( len-- ) {
     *curr=(*curr)^(key8--);
     curr--;
   }
 
d7903e09
   if (!CLI_ISCONTAINED(spinned, EC32(sections[sectcnt].SizeOfRawData), ep+0x3217, 4)) {
     free(spinned);
     cli_dbgmsg("spin: key out of bounds, giving up\n");
     return 1;
   }
 
7c53d74b
   curr = ep+0x26eb;
   key32 = cli_readint32(curr);
   if ( (len = cli_readint32(curr+5)) != 0x5a0) {
     free(spinned);
     cli_dbgmsg("spin: Not spinned or bad version\n");
6910cc7f
     return 1;
7c53d74b
   }
 
d7903e09
   curr = ep+0x2d5;
7c53d74b
   cli_dbgmsg("spin: Key is %x, Len is %x\n", key32, len);
 
   while ( len-- ) {
     if ( key32 & 1 ) {
d7903e09
       key32 = key32>>1;
7c53d74b
       key32 ^= 0x8c328834;
     } else {
d7903e09
       key32 = key32>>1;
7c53d74b
     }
     *curr = *curr ^ (key32 & 0xff);
     curr++;
   }
 
154f9113
   len = ssize - cli_readint32(ep+0x429); /* sub size, value */
   if ( len >= (uint32_t)ssize ) {
7c53d74b
     free(spinned);
     cli_dbgmsg("spin: crc out of bounds, giving up\n");
d7903e09
     return 1;
7c53d74b
   }
   key32 = cli_readint32(ep+0x3217) - summit(src,len);
 
   memcpy(src + EC32(sections[sectcnt].PointerToRawData), spinned, EC32(sections[sectcnt].SizeOfRawData)); 
154f9113
   free(spinned); /* done CRC'ing - can have a dirty buffer now */
   ep = src + nep + sections[sectcnt].PointerToRawData - sections[sectcnt].VirtualAddress; /* Fix the helper */
7c53d74b
 
d7903e09
   if (!CLI_ISCONTAINED(src, ssize, ep+0x3207, 4)) { /* this one holds all ep based checks */
     cli_dbgmsg("spin: key out of bounds, giving up\n");
     return 1;
   }
7c53d74b
   bitmap = cli_readint32(ep+0x3207);
6910cc7f
   cli_dbgmsg("spin: Key32 is %x - XORbitmap is %x\n", key32, bitmap);
   
   cli_dbgmsg("spin: Decrypting sects (xor)\n");
7c53d74b
   for (j=0; j<sectcnt; j++) {
3e9d9006
 
7c53d74b
     if (bitmap&1) {
       uint32_t size = EC32(sections[j].SizeOfRawData);
       char *ptr = src + EC32(sections[j].PointerToRawData);
       uint32_t keydup = key32;
       
d7903e09
       if (!CLI_ISCONTAINED(src, ssize, ptr, size)) {
7c53d74b
 	cli_dbgmsg("spin: sect %d out of file, giving up\n", j);
d7903e09
 	return 1; /* FIXME: Already checked in pe.c? */
7c53d74b
       }
 
       while (size--) {
 	if (! (keydup & 1)) {
d7903e09
 	  keydup = keydup>>1;
7c53d74b
 	  keydup ^= 0xed43af31;
 	} else {
d7903e09
 	  keydup = keydup>>1;
7c53d74b
 	}
 	*ptr = *ptr ^ (keydup & 0xff);
 	ptr++;
       }
3e9d9006
     } 
d7903e09
     bitmap = bitmap >>1;
7c53d74b
   }
6910cc7f
   
7c53d74b
   cli_dbgmsg("spin: done\n");
 
6910cc7f
   
d7903e09
   curr = ep+0x644;
7c53d74b
   if ( (len = cli_readint32(curr)) != 0x180) {
     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;
 
d7903e09
   if (!CLI_ISCONTAINED(src, ssize, curr, len)) { /* always true but i may decide to remove the previous check */
     cli_dbgmsg("spin: key out of bounds, giving up\n");
     return 1;
   }
7c53d74b
   while ( len-- ) {
     if ( key32 & 1 ) {
d7903e09
       key32 = key32>>1;
7c53d74b
       key32 ^= 0xed43af32;
     } else {
d7903e09
       key32 = key32>>1;
7c53d74b
     }
     *curr = *curr ^ (key32 & 0xff);
     curr++;
   }
 
 
   curr = ep+0x28dd;
   if ( (len = cli_readint32(curr)) != 0x1a1 ) {
     cli_dbgmsg("spin: Not spinned or bad version\n");
     return 1;
   }
 
   cli_dbgmsg("spin: POLY1 len is %x\n", len);
154f9113
   curr+=0xf; /* POLY1 */
d7903e09
   emu = ep+0x6d4;
   if (!CLI_ISCONTAINED(src, ssize, emu, len)) {
     cli_dbgmsg("spin: poly1 out of bounds\n");
     return 1;
   }
7c53d74b
   while (len) {
6c611447
     int xcfailure=0;
     *emu=exec86(*emu, len-- & 0xff, curr, &xcfailure); /* unlame POLY1 */
     if (xcfailure) {
       cli_dbgmsg("spin: cannot exec poly1\n");
       return 1;
     }
7c53d74b
     emu++;
   }
 
6910cc7f
 
7c53d74b
   bitmap = cli_readint32(ep+0x6f1);
6910cc7f
   cli_dbgmsg("spin: POLYbitmap is %x - decrypting sects (poly)\n", bitmap);
7c53d74b
   curr = ep+0x755;
 
   for (j=0; j<sectcnt; j++) {
     if (bitmap&1) {
154f9113
       uint32_t notthesamelen = EC32(sections[j].SizeOfRawData);
7c53d74b
 
       emu = src + EC32(sections[j].PointerToRawData);
 
d7903e09
       if (!CLI_ISCONTAINED(src,ssize,curr,0x24)) { /* section bounds already checked twice now */
 	cli_dbgmsg("spin: poly1 emucode is out of file?\n");
7c53d74b
 	return 1;
       }
 
154f9113
       while (notthesamelen) {
6c611447
 	int xcfailure=0;
         *emu=exec86(*emu, notthesamelen-- & 0xff, curr, &xcfailure);
 	if (xcfailure) {
 	  cli_dbgmsg("spin: cannot exec section\n");
 	  return 1;
 	}
7c53d74b
         emu++;
       }
     }
d7903e09
       bitmap = bitmap >>1;
7c53d74b
   }
6910cc7f
   
   cli_dbgmsg("spin: done\n");
7c53d74b
 
   bitmap = cli_readint32(ep+0x3061);
   bitman = bitmap;
ce4d0aa2
 
   if(ctx->limits && ctx->limits->maxfilesize) {
     unsigned long int filesize = 0;
     
     for (j=0; j<sectcnt; j++) {
       if (bitmap&1) {
 	if ( filesize > ctx->limits->maxfilesize || (uint32_t)EC32(sections[j].VirtualSize) > ctx->limits->maxfilesize - filesize ) return 2;
 	filesize += (uint32_t)EC32(sections[j].VirtualSize);
       }
       bitmap>>=1;
     }
     
     bitmap = bitman;
   }
 
7c53d74b
   cli_dbgmsg("spin: Compression bitmap is %x\n", bitmap);
   if ( (sects= (char **) cli_malloc(sectcnt*sizeof(char *))) == NULL )
     return 1;
 
   len = 0;
   for (j=0; j<sectcnt; j++) {
     if (bitmap&1) {
d7903e09
        if ( (sects[j] = (char *) cli_malloc(EC32(sections[j].VirtualSize)) ) == NULL ) {
6910cc7f
 	 cli_dbgmsg("spin: malloc(%d) failed\n", EC32(sections[j].VirtualSize));
7c53d74b
 	 len = 1;
 	 break;
        }
        blobsz+=EC32(sections[j].VirtualSize);
        memset(sects[j], 0, EC32(sections[j].VirtualSize));
        cli_dbgmsg("spin: Growing sect%d: was %x will be %x\n", j, EC32(sections[j].SizeOfRawData), EC32(sections[j].VirtualSize));
87a4323f
        if ( cli_unfsg(src + EC32(sections[j].PointerToRawData), sects[j], EC32(sections[j].SizeOfRawData), EC32(sections[j].VirtualSize), NULL, NULL) == -1 ) {
6910cc7f
 	 len++;
          cli_dbgmsg("spin: Unpack failure\n");
        }
7c53d74b
     } else {
       blobsz+=EC32(sections[j].SizeOfRawData);
       sects[j] = src + EC32(sections[j].PointerToRawData);
       cli_dbgmsg("spin: Not growing sect%d\n", j);
     }
ce4d0aa2
     bitmap>>=1;
7c53d74b
   }
6910cc7f
   
   cli_dbgmsg("spin: decompression complete\n");
  
7c53d74b
   if ( len ) {
     int t;
6910cc7f
     for (t=0 ; t<j ; t++) {
7c53d74b
       if (bitman&1)
 	free(sects[t]);
6910cc7f
       bitman = bitman >>1 & 0x7fffffff;
     }
7c53d74b
     free(sects);
     return 1;
   }
 
 
   key32 = cli_readint32(ep+0x2fee);
   if (key32) {
     /*    len = cli_readint32(ep+0x2fc8); -- Using vsizes instead */
 
     for (j=0; j<sectcnt; j++) {
d7903e09
       if (EC32(sections[j].VirtualAddress) <= key32 && EC32(sections[j].VirtualAddress)+EC32(sections[j].SizeOfRawData) > key32)
7c53d74b
 	break;
     }
 
154f9113
     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 headakes */
6910cc7f
       cli_dbgmsg("spin: Resources (sect%d) appear to be compressed\n\tuncompressed offset %x, len %x\n\tcompressed offset %x, len %x\n", j, EC32(sections[j].VirtualAddress), key32 - EC32(sections[j].VirtualAddress), key32, EC32(sections[j].VirtualSize) - (key32 - EC32(sections[j].VirtualAddress)));
7c53d74b
 
       if ( (curr=(char *)cli_malloc(EC32(sections[j].VirtualSize))) != NULL ) {
154f9113
 	memcpy(curr, src + EC32(sections[j].PointerToRawData), key32 - EC32(sections[j].VirtualAddress)); /* Uncompressed part */
 	memset(curr + key32 - EC32(sections[j].VirtualAddress), 0, EC32(sections[j].VirtualSize) - (key32 - EC32(sections[j].VirtualAddress))); /* bzero */
87a4323f
 	if ( cli_unfsg(src + EC32(sections[j].PointerToRawData) + key32 - EC32(sections[j].VirtualAddress), curr + key32 - EC32(sections[j].VirtualAddress), EC32(sections[j].SizeOfRawData) - (key32 - EC32(sections[j].VirtualAddress)), EC32(sections[j].VirtualSize) - (key32 - EC32(sections[j].VirtualAddress)), NULL, NULL) ) {
7c53d74b
       
 	  free(curr);
 	  cli_dbgmsg("spin: Failed to grow resources, continuing anyway\n");
 	  blobsz+=EC32(sections[j].SizeOfRawData);
 	} else {
d7903e09
 	  sects[j]=curr;
7c53d74b
 	  bitman|=1<<j;
 	  cli_dbgmsg("spin: Resources grown\n");
 	  blobsz+=EC32(sections[j].VirtualSize);
 	}
       } else {
d7903e09
 	/* malloc failed but i'm too deep into this crap to quit without leaking more :( */
7c53d74b
 	blobsz+=EC32(sections[j].SizeOfRawData);
       }
     } else {
       cli_dbgmsg("spin: No res?!\n");
     }
   }
   
 
154f9113
   bitmap=bitman; /* save as a free() bitmap */
7c53d74b
 
   if ( (ep = (char *) cli_malloc(blobsz)) != NULL ) {
e6bea1aa
     struct cli_exe_section *rebhlp;
     if ( (rebhlp = (struct cli_exe_section *) cli_malloc(sizeof(struct cli_exe_section)*(sectcnt))) != NULL ) {
7c53d74b
       char *to = ep;
       int retval = 0;
 
       for (j = 0; j < sectcnt; j++) {
 	rebhlp[j].raw = (j>0)*(rebhlp[j-1].raw + rebhlp[j-1].rsz);
 	rebhlp[j].rsz = (bitmap &1) ? EC32(sections[j].VirtualSize) : EC32(sections[j].SizeOfRawData);
 	rebhlp[j].rva = EC32(sections[j].VirtualAddress);
 	rebhlp[j].vsz = EC32(sections[j].VirtualSize);
 
 	memcpy(to, sects[j], rebhlp[j].rsz);
 	to+=rebhlp[j].rsz;
 	if ( bitmap & 1 ) free(sects[j]);
d7903e09
 	bitmap = bitmap >>1;
7c53d74b
       }
 
e6bea1aa
       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. */
7c53d74b
 	cli_dbgmsg("spin: Cannot write unpacked file\n");
 	retval = 1;
       }
       free(rebhlp);
       free(ep);
       free(sects);
       return retval;
     }
     free(ep);
   }
 
   cli_dbgmsg ("spin: free bitmap is %x\n", bitman);
   for (j=0; j<sectcnt; j++) {
     if (bitmap&1) free(sects[j]);
     bitman = bitman >>1 & 0x7fffffff;
   }
   free(sects);
154f9113
   return 1; /* :( */
7c53d74b
 }