/*
 *  Copyright (C) 2006 Sensory Networks, Inc.
 *             Written by aCaB <acab@clamav.net>
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *  MA 02110-1301, USA.
 */

/*
** wwunpack.c
**
** 09/07/2k6 - Campioni del mondo!!!
** 14/07/2k6 - RCE'ed + standalone sect unpacker
** 15/07/2k6 - Merge started
** 17/07/2k6 - Rebuild
** 18/07/2k6 - Secured (well, hopefully...)
**
*/

/*
** Unpacks+rebuilds WWPack32 1.20
**
** Just boooooring stuff, blah.
**
*/


/*
** TODO:
**
** review
** check eax vs al
** (check for dll's)
** (have a look at older versions)
**
*/


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

#include <stdlib.h>
#include <string.h>

#include "cltypes.h"
#include "others.h"
#include "wwunpack.h"

#define VAALIGN(s) (((s)/0x1000+((s)%0x1000!=0))*0x1000)
#define FIXVS(v, r) (VAALIGN((r>v)?r:v))


static int getbitmap(uint32_t *bitmap, char **src, uint8_t *bits, char *buf, unsigned int size) {
  if (! CLI_ISCONTAINED(buf, size, *src, 4)) return 1;
  *bitmap=cli_readint32(*src);
  *src+=4;
  *bits=32;
  return 0;
}

static int getbits(uint8_t X, uint32_t *eax, uint32_t *bitmap, uint8_t *bits, char **src, char *buf, unsigned int size) {
  *eax=*bitmap>>(32-X);
  if (*bits>X) {
    *bitmap<<=X;
    *bits-=X;
  } else if (*bits<X) {
    X-=*bits;
    *eax>>=X;
    if (getbitmap(bitmap, src, bits, buf, size)) return 1;
    *eax<<=X;
    *eax|=*bitmap>>(32-X);
    *bitmap<<=X;
    *bits-=X;
  } else {
    if (getbitmap(bitmap, src, bits, buf, size)) return 1;
  }
  return 0;
}

static int wunpsect(char *packed, char *unpacked, unsigned int psize, unsigned int usize) {
  char *src=packed, *dst=unpacked;
  uint32_t bitmap, eax;
  uint8_t bits;
  unsigned int lostbit, getmorestuff;
  uint16_t backbytes;
  uint16_t backsize;
  uint8_t oal;

  if (getbitmap(&bitmap, &src, &bits, packed, psize)) return 1;
  eax=bitmap;

  while (1) {
    lostbit=bitmap>>31;
    bitmap<<=1;
    bits--;
    if (!lostbit && bits) {
      if (!(CLI_ISCONTAINED(packed, psize, src, 1) && CLI_ISCONTAINED(unpacked, usize, dst, 1))) return 1;
      *dst++=*src++;
      continue;
    }
    
    if (!bits) {
      if (getbitmap(&bitmap, &src, &bits, packed, psize)) return 1;
      eax=bitmap;
      if (!lostbit) {
	if (!(CLI_ISCONTAINED(packed, psize, src, 1) && CLI_ISCONTAINED(unpacked, usize, dst, 1))) return 1;
	*dst++=*src++;
	continue;
      }
    }
    
    if (getbits(2, &eax, &bitmap, &bits, &src, packed, psize)) return 1;
    
    if ((eax&0xff)>=3) {
      /* 50ff - two_bytes */
      uint8_t fetchbits;
      
      if (getbits(2, &eax, &bitmap, &bits, &src, packed, psize)) return 1;
      fetchbits=(eax&0xff)+5;
      eax--;
      if ((int16_t)(eax&0xffff)<=0) {
	/* 5113 */
	backbytes=1<<fetchbits;
	backbytes=(backbytes&0xff00)|((backbytes-31)&0xff);
      } else {
	/* 511b */
	fetchbits++;
	backbytes=1<<fetchbits;
	backbytes-=0x9f;
      }
      /* 5125 */
      if (getbits(fetchbits, &eax, &bitmap, &bits, &src, packed, psize)) return 1;
      if ((eax&0xffff)==0x1ff) break;
      eax&=0xffff;
      backbytes+=eax;
      if (!(CLI_ISCONTAINED(unpacked, usize, dst-backbytes, 2) && CLI_ISCONTAINED(unpacked, usize, dst, 2))) return 1;
      *dst=*(dst-backbytes);
      dst++;
      *dst=*(dst-backbytes);
      dst++;
      continue;
    }

    /* 5143 - more_backbytes */      
    oal=eax&0xff;
    getmorestuff=1;

    
    if (getbits(3, &eax, &bitmap, &bits, &src, packed, psize)) return 1;
    if ((eax&0xff)<=3) {
      lostbit=0;
      if ((eax&0xff)==3) {
	/* next_bit_or_reseed */
	lostbit=bitmap>>31;
	bitmap<<=1;
	bits--;
	if (!bits) {
	  if (getbitmap(&bitmap, &src, &bits, packed, psize)) return 1; 
	}
      }
      eax=eax+lostbit+5;
      /* jmp more_bb_commondock */
    } else { /* >3 */
      /* 5160 - more_bb_morethan3 */
      if ((eax&0xff)==4) {
	/* next_bit_or_reseed */
	lostbit=bitmap>>31;
	bitmap<<=1;
	bits--;
	if (!bits) {
	  if (getbitmap(&bitmap, &src, &bits, packed, psize)) return 1;  
	}
	eax=eax+lostbit+6;
	/* jmp more_bb_commondock */
      } else { /* !=4 */
	eax+=7;
	if ((eax&0xff)>=0x0d) {
	  getmorestuff=0; /* jmp more_bb_PASTcommondock */
	  if ((eax&0xff)==0x0d) {
	    /* 5179  */
	    if (getbits(0x0e, &eax, &bitmap, &bits, &src, packed, psize)) return 1;
	    eax+=0x1fe1;
	  } else {
	    /* 516c */
	    if (getbits(0x0f, &eax, &bitmap, &bits, &src, packed, psize)) return 1;
	    eax+=0x5fe1;
	  }
	  /* jmp more_bb_PASTcommondock */
	} /* al >= 0d */
      } /* al != 4 */
    } /* >3 */
    
    if (getmorestuff) {
      /* 5192 - more_bb_commondock */
      uint16_t bk=(1<<(eax&0xff))-0x1f;
      if (getbits((eax&0xff), &eax, &bitmap, &bits, &src, packed, psize)) return 1;
      eax+=bk;
    }
    
    /* 51a7 - more_bb_pastcommondock */
    eax&=0xffff;
    backbytes=eax;
    backsize=3+(oal!=1);
    
    if (oal<1) { /* overrides backsize */
      /* 51bb - more_bb_again */
      
      /* next_bit_or_reseed */
      lostbit=bitmap>>31;
      bitmap<<=1;
      bits--;
      if (!bits) {
	if (getbitmap(&bitmap, &src, &bits, packed, psize)) return 1;  
      }
      if (!lostbit) {
	/* 51c2 */
	/* next_bit_or_reseed */
	lostbit=bitmap>>31;
	bitmap<<=1;
	bits--;
	if (!bits) {
	  if (getbitmap(&bitmap, &src, &bits, packed, psize)) return 1;   
	}
	eax=5+lostbit;
	/* jmp setsize_and_backcopy */
      } else {
	/* 51ce - more_bb_again_and_again */
	if (getbits(3, &eax, &bitmap, &bits, &src, packed, psize)) return 1;
	if (eax&0xff) {
	  /* 51e6 */
	  eax+=6;
	  /* jmp setsize_and_backcopy */
	} else {
	  if (getbits(4, &eax, &bitmap, &bits, &src, packed, psize)) return 1;
	  if (eax&0xff) {
	    /* 51e4 */
	    eax+=7+6;
	    /* jmp setsize_and_backcopy */
	  } else {
	    /* 51ea - OMGWTF */
	    uint8_t c=4;
	    uint16_t d=0x0d;
	    
	    while ( 1 ) {
	      if (c!=7){
		d+=2;
		d<<=1;
		d--;
		
		/* next_bit_or_reseed */
		lostbit=bitmap>>31;
		bitmap<<=1;
		bits--;
		if (!bits) {
		  if (getbitmap(&bitmap, &src, &bits, packed, psize)) return 1;    
		}
		c++;
		if (!lostbit) continue;
		if (getbits(c, &eax, &bitmap, &bits, &src, packed, psize)) return 1;
		d+=eax&0xff;
		eax&=0xffffff00;
		eax|=d&0xff;
	      } else {
		if (getbits(14, &eax, &bitmap, &bits, &src, packed, psize)) return 1;
	      }
	      break;
	    } /* while */
	  } /* OMGWTF */
	} /* eax&0xff */
      } /* lostbit */
	/* 521b - setsize_and_backcopy */
      backsize=eax&0xffff;
    }

    /* 521e - backcopy */
    if (!(CLI_ISCONTAINED(unpacked, usize, dst-backbytes, backsize) && CLI_ISCONTAINED(unpacked, usize, dst, backsize))) return 1;
    while(backsize--){
      *dst=*(dst-backbytes);
      dst++;
    }

  } /* while true */

  return 0;
}

int wwunpack(char *exe, uint32_t exesz, uint32_t headsize, uint32_t min, uint32_t wwprva, uint32_t e_lfanew, char *wwp, uint32_t wwpsz, uint16_t sects) {
  char *stuff=wwp+0x2a1, *packed, *unpacked;
  uint32_t rva, csize;

  cli_dbgmsg("in wwunpack\n");


  while(1) {
    if (!CLI_ISCONTAINED(wwp, wwpsz, stuff, 17)) {
      cli_dbgmsg("WWPack: next chunk out ouf file, giving up.\n");
      return 1;
    }
    if ((csize=cli_readint32(stuff+8)*4)!=(uint32_t)cli_readint32(stuff+12)+4) {
      cli_dbgmsg("WWPack: inconsistent/hacked data, go figure!\n");
      return 1;
    }
    rva = wwprva-cli_readint32(stuff);
    if((packed = (char *) cli_calloc(csize, sizeof(char))) == NULL) {
      cli_dbgmsg("WWPack: Can't allocate %d bytes\n", csize);
      return 1;
    }
    unpacked=exe+headsize+rva-min;
    if (!CLI_ISCONTAINED(exe, exesz, unpacked, csize)) {
      free(packed);
      cli_dbgmsg("WWPack: packed data out of bounds, giving up.\n");
      return 1;
    }
    memcpy(packed, unpacked, csize);
    if (wunpsect(packed, unpacked, csize, exesz-(unpacked-exe))) {
      free(packed);
      cli_dbgmsg("WWPack: unpacking failed.\n");
      return 1;
    }
    free(packed);
    if (!stuff[16]) break;
    stuff+=17;
  }

  stuff=exe+e_lfanew;
  stuff[6]=sects&0xff;
  stuff[7]=sects>>8;

  csize=cli_readint32(wwp+0x295)+wwprva+0x299;
  cli_dbgmsg("WWPack: found OEP @%x\n", csize);
  cli_writeint32(stuff+0x28, csize);

  csize=cli_readint32(stuff+0x50)-VAALIGN(wwpsz);
  cli_writeint32(stuff+0x50, csize);


  stuff+=0x18+(cli_readint32(stuff+0x14)&0xffff);
  while (sects--) {
    uint32_t v=cli_readint32(stuff+8);
    uint32_t r=cli_readint32(stuff+16);
    csize=FIXVS(v, r);
    cli_writeint32(stuff+8, csize);
    cli_writeint32(stuff+16, csize);
    cli_writeint32(stuff+20, cli_readint32(stuff+12)-min+headsize);
    stuff+=0x28;
  }
  memset(stuff, 0, 0x28);

  return 0;
}