mirror of https://github.com/Cisco-Talos/clamav
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
513 lines
13 KiB
513 lines
13 KiB
/*
|
|
* Copyright (C) 2007-2008 Sourcefire, Inc.
|
|
*
|
|
* Authors: Alberto Wu
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/*
|
|
** 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"
|
|
#include "execs.h"
|
|
#include "others.h"
|
|
#include "packlibs.h"
|
|
#include "spin.h"
|
|
|
|
|
|
static char exec86(uint8_t aelle, uint8_t cielle, char *curremu, int *retval) {
|
|
int len = 0;
|
|
*retval=0;
|
|
while (len <0x24) {
|
|
uint8_t opcode = curremu[len], support;
|
|
len++;
|
|
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;
|
|
}
|
|
}
|
|
if ( len!=0x24 || curremu[len]!='\xaa' ) {
|
|
cli_dbgmsg("spin: bad emucode\n");
|
|
*retval=1;
|
|
}
|
|
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;
|
|
CLI_ROR(eax, ebx&0xff);
|
|
swap = eax;
|
|
eax = ebx;
|
|
ebx = swap;
|
|
}
|
|
size--;
|
|
}
|
|
return ebx;
|
|
}
|
|
|
|
|
|
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");
|
|
|
|
if ((spinned = (char *) cli_malloc(sections[sectcnt].rsz)) == NULL ) {
|
|
cli_dbgmsg("spin: Unable to allocate memory for spinned\n");
|
|
return 1;
|
|
}
|
|
|
|
memcpy(spinned, src + sections[sectcnt].raw, sections[sectcnt].rsz);
|
|
ep = spinned + nep - sections[sectcnt].rva;
|
|
|
|
curr = ep+0xdb;
|
|
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);
|
|
|
|
if (!CLI_ISCONTAINED(spinned, sections[sectcnt].rsz, ep, len+0x1fe5-1)) {
|
|
free(spinned);
|
|
cli_dbgmsg("spin: len out of bounds, giving up\n");
|
|
return 1;
|
|
}
|
|
|
|
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");
|
|
|
|
curr = ep+0x1fe5+len-1;
|
|
while ( len-- ) {
|
|
*curr=(*curr)^(key8--);
|
|
curr--;
|
|
}
|
|
|
|
if (!CLI_ISCONTAINED(spinned, sections[sectcnt].rsz, ep+0x3217, 4)) {
|
|
free(spinned);
|
|
cli_dbgmsg("spin: key out of bounds, giving up\n");
|
|
return 1;
|
|
}
|
|
|
|
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");
|
|
return 1;
|
|
}
|
|
|
|
curr = ep+0x2d5;
|
|
cli_dbgmsg("spin: Key is %x, Len is %x\n", key32, len);
|
|
|
|
while ( len-- ) {
|
|
if ( key32 & 1 ) {
|
|
key32 = key32>>1;
|
|
key32 ^= 0x8c328834;
|
|
} else {
|
|
key32 = key32>>1;
|
|
}
|
|
*curr = *curr ^ (key32 & 0xff);
|
|
curr++;
|
|
}
|
|
|
|
len = ssize - cli_readint32(ep+0x429); /* sub size, value */
|
|
if ( len >= (uint32_t)ssize ) {
|
|
free(spinned);
|
|
cli_dbgmsg("spin: crc out of bounds, giving up\n");
|
|
return 1;
|
|
}
|
|
key32 = cli_readint32(ep+0x3217) - summit(src,len);
|
|
|
|
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 */
|
|
|
|
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;
|
|
}
|
|
bitmap = cli_readint32(ep+0x3207);
|
|
cli_dbgmsg("spin: Key32 is %x - XORbitmap is %x\n", key32, bitmap);
|
|
|
|
cli_dbgmsg("spin: Decrypting sects (xor)\n");
|
|
for (j=0; j<sectcnt; j++) {
|
|
|
|
if (bitmap&1) {
|
|
uint32_t size = sections[j].rsz;
|
|
char *ptr = src + sections[j].raw;
|
|
uint32_t keydup = key32;
|
|
|
|
if (!CLI_ISCONTAINED(src, ssize, ptr, size)) {
|
|
cli_dbgmsg("spin: sect %d out of file, giving up\n", j);
|
|
return 1; /* FIXME: Already checked in pe.c? */
|
|
}
|
|
|
|
while (size--) {
|
|
if (! (keydup & 1)) {
|
|
keydup = keydup>>1;
|
|
keydup ^= 0xed43af31;
|
|
} else {
|
|
keydup = keydup>>1;
|
|
}
|
|
*ptr = *ptr ^ (keydup & 0xff);
|
|
ptr++;
|
|
}
|
|
}
|
|
bitmap = bitmap >>1;
|
|
}
|
|
|
|
cli_dbgmsg("spin: done\n");
|
|
|
|
|
|
curr = ep+0x644;
|
|
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;
|
|
|
|
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;
|
|
}
|
|
while ( len-- ) {
|
|
if ( key32 & 1 ) {
|
|
key32 = key32>>1;
|
|
key32 ^= 0xed43af32;
|
|
} else {
|
|
key32 = key32>>1;
|
|
}
|
|
*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);
|
|
curr+=0xf; /* POLY1 */
|
|
emu = ep+0x6d4;
|
|
if (!CLI_ISCONTAINED(src, ssize, emu, len)) {
|
|
cli_dbgmsg("spin: poly1 out of bounds\n");
|
|
return 1;
|
|
}
|
|
while (len) {
|
|
int xcfailure=0;
|
|
*emu=exec86(*emu, len-- & 0xff, curr, &xcfailure); /* unlame POLY1 */
|
|
if (xcfailure) {
|
|
cli_dbgmsg("spin: cannot exec poly1\n");
|
|
return 1;
|
|
}
|
|
emu++;
|
|
}
|
|
|
|
|
|
bitmap = cli_readint32(ep+0x6f1);
|
|
cli_dbgmsg("spin: POLYbitmap is %x - decrypting sects (poly)\n", bitmap);
|
|
curr = ep+0x755;
|
|
|
|
for (j=0; j<sectcnt; j++) {
|
|
if (bitmap&1) {
|
|
uint32_t notthesamelen = sections[j].rsz;
|
|
|
|
emu = src + sections[j].raw;
|
|
|
|
if (!CLI_ISCONTAINED(src,ssize,curr,0x24)) { /* section bounds already checked twice now */
|
|
cli_dbgmsg("spin: poly1 emucode is out of file?\n");
|
|
return 1;
|
|
}
|
|
|
|
while (notthesamelen) {
|
|
int xcfailure=0;
|
|
*emu=exec86(*emu, notthesamelen-- & 0xff, curr, &xcfailure);
|
|
if (xcfailure) {
|
|
cli_dbgmsg("spin: cannot exec section\n");
|
|
return 1;
|
|
}
|
|
emu++;
|
|
}
|
|
}
|
|
bitmap = bitmap >>1;
|
|
}
|
|
|
|
cli_dbgmsg("spin: done\n");
|
|
|
|
bitmap = cli_readint32(ep+0x3061);
|
|
bitman = bitmap;
|
|
|
|
/* FIXMELIMITS: possibly rewrite to use the limits api */
|
|
if(ctx->engine->maxfilesize) {
|
|
unsigned long int filesize = 0;
|
|
|
|
for (j=0; j<sectcnt; j++) {
|
|
if (bitmap&1) {
|
|
if ( filesize > ctx->engine->maxfilesize || sections[j].vsz > ctx->engine->maxfilesize - filesize ) return 2;
|
|
filesize += sections[j].vsz;
|
|
}
|
|
bitmap>>=1;
|
|
}
|
|
|
|
bitmap = bitman;
|
|
}
|
|
|
|
cli_dbgmsg("spin: Compression bitmap is %x\n", bitmap);
|
|
if ( (sects= (char **) cli_malloc(sectcnt*sizeof(char *))) == NULL ) {
|
|
cli_dbgmsg("spin: malloc(%d) failed\n", sectcnt*sizeof(char *));
|
|
return 1;
|
|
}
|
|
|
|
len = 0;
|
|
for (j=0; j<sectcnt; j++) {
|
|
if (bitmap&1) {
|
|
if ( (sects[j] = (char *) cli_malloc(sections[j].vsz) ) == NULL ) {
|
|
cli_dbgmsg("spin: malloc(%d) failed\n", sections[j].vsz);
|
|
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);
|
|
if ( cli_unfsg(src + sections[j].raw, sects[j], sections[j].rsz, sections[j].vsz, NULL, NULL) == -1 ) {
|
|
len++;
|
|
cli_dbgmsg("spin: Unpack failure\n");
|
|
}
|
|
} else {
|
|
blobsz+=sections[j].rsz;
|
|
sects[j] = src + sections[j].raw;
|
|
cli_dbgmsg("spin: Not growing sect%d\n", j);
|
|
}
|
|
bitmap>>=1;
|
|
}
|
|
|
|
cli_dbgmsg("spin: decompression complete\n");
|
|
|
|
if ( len ) {
|
|
int t;
|
|
for (t=0 ; t<j ; t++) {
|
|
if (bitman&1)
|
|
free(sects[t]);
|
|
bitman = bitman >>1 & 0x7fffffff;
|
|
}
|
|
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++) {
|
|
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;
|
|
}
|
|
|
|
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 */
|
|
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));
|
|
|
|
if ( (curr=(char *)cli_malloc(sections[j].vsz)) != NULL ) {
|
|
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 */
|
|
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) ) {
|
|
|
|
free(curr);
|
|
cli_dbgmsg("spin: Failed to grow resources, continuing anyway\n");
|
|
blobsz+=sections[j].rsz;
|
|
} else {
|
|
sects[j]=curr;
|
|
bitman|=1<<j;
|
|
cli_dbgmsg("spin: Resources grown\n");
|
|
blobsz+=sections[j].vsz;
|
|
}
|
|
} else {
|
|
/* 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;
|
|
}
|
|
} else {
|
|
cli_dbgmsg("spin: No res?!\n");
|
|
}
|
|
}
|
|
|
|
|
|
bitmap=bitman; /* save as a free() bitmap */
|
|
|
|
if ( (ep = (char *) cli_malloc(blobsz)) != NULL ) {
|
|
struct cli_exe_section *rebhlp;
|
|
if ( (rebhlp = (struct cli_exe_section *) cli_malloc(sizeof(struct cli_exe_section)*(sectcnt))) != NULL ) {
|
|
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):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;
|
|
}
|
|
|
|
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. */
|
|
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);
|
|
return 1; /* :( */
|
|
}
|
|
|