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.
510 lines
14 KiB
510 lines
14 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.
|
|
*/
|
|
|
|
/*
|
|
** petitep.c
|
|
**
|
|
** 09/07/2k4 - Dumped and reversed
|
|
** 10/07/2k4 - Very 1st approach
|
|
** 10/07/2k4 - PE stuff and main loop
|
|
** 11/07/2k4 - Porting finished, tracking my bugs...
|
|
** 12/07/2k4 - ARRRRRGHHH :D
|
|
** 14/07/2k4 - Code cleaned
|
|
** 15/07/2k4 - Securing && ClamAV porting
|
|
** 21/07/2k4 - Unmangled imports now supported
|
|
** 22/07/2k4 - Unstripped .relocs now supported
|
|
**
|
|
*/
|
|
|
|
/*
|
|
** Unpacks a buffer containing a petite 2.2 compressed
|
|
** file. Doesn't perform Import Table unmangling. Doesn't
|
|
** fixup call/jumps. Tries to "guess" the original sections
|
|
** structure and entrypoint.
|
|
**
|
|
** Lotta phanx to Micky for patiently bearing my screams :P
|
|
** Greets to Ian Luck: the SEH MOVSB thingy almost got me :O
|
|
** TODO: Cope with level 0 and older petite versions.
|
|
*/
|
|
|
|
|
|
#if HAVE_CONFIG_H
|
|
#include "clamav-config.h"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "cltypes.h"
|
|
#include "rebuildpe.h"
|
|
#include "execs.h"
|
|
#include "others.h"
|
|
#include "petite.h"
|
|
|
|
|
|
static int doubledl(char **scur, uint8_t *mydlptr, char *buffer, uint32_t buffersize)
|
|
{
|
|
unsigned char mydl = *mydlptr;
|
|
unsigned char olddl = mydl;
|
|
|
|
mydl*=2;
|
|
if ( !(olddl & 0x7f)) {
|
|
if ( *scur < buffer || *scur >= buffer+buffersize-1 )
|
|
return -1;
|
|
olddl = **scur;
|
|
mydl = olddl*2+1;
|
|
*scur=*scur + 1;
|
|
}
|
|
*mydlptr = mydl;
|
|
return (olddl>>7)&1;
|
|
}
|
|
|
|
int petite_inflate2x_1to9(char *buf, uint32_t minrva, uint32_t bufsz, struct cli_exe_section *sections, unsigned int sectcount, uint32_t Imagebase, uint32_t pep, int desc, int version, uint32_t ResRva, uint32_t ResSize)
|
|
{
|
|
char *adjbuf = buf - minrva;
|
|
char *packed = NULL;
|
|
uint32_t thisrva=0, bottom = 0, enc_ep=0, irva=0, workdone=0, grown=0x355, skew=0x35;
|
|
int j = 0, oob, mangled = 0, check4resources=0;
|
|
struct cli_exe_section *usects = NULL;
|
|
void *tmpsct = NULL;
|
|
|
|
/*
|
|
-] The real thing [-
|
|
*/
|
|
|
|
/* NOTE: (435063->4350a5) Petite kernel32!imports and error strings */
|
|
|
|
/* Here we adjust the start of packed blob, the size of petite code,
|
|
* the difference in size if relocs were stripped
|
|
* See below...
|
|
*/
|
|
|
|
if ( version == 2 )
|
|
packed = adjbuf + sections[sectcount-1].rva + 0x1b8;
|
|
if ( version == 1 ) {
|
|
packed = adjbuf + sections[sectcount-1].rva + 0x178;
|
|
grown=0x323; /* My name is Harry potter */
|
|
skew=0x34;
|
|
}
|
|
|
|
while (1) {
|
|
char *ssrc, *ddst;
|
|
uint32_t size, srva;
|
|
int backbytes, oldback, backsize, addsize;
|
|
|
|
if ( ! CLI_ISCONTAINED(buf, bufsz, packed, 4)) {
|
|
if (usects)
|
|
free(usects);
|
|
return 1;
|
|
}
|
|
srva = cli_readint32(packed);
|
|
|
|
if (! srva) {
|
|
/* WERE DONE !!! :D */
|
|
int t, upd = 1;
|
|
|
|
if ( j <= 0 ) /* Some non petite compressed files will get here */
|
|
return 1;
|
|
|
|
/* Select * from sections order by rva asc; */
|
|
while ( upd ) {
|
|
upd = 0;
|
|
for (t = 0; t < j-1 ; t++) {
|
|
uint32_t trva, trsz, tvsz;
|
|
|
|
if ( usects[t].rva <= usects[t+1].rva )
|
|
continue;
|
|
trva = usects[t].rva;
|
|
trsz = usects[t].rsz;
|
|
tvsz = usects[t].vsz;
|
|
usects[t].rva = usects[t+1].rva;
|
|
usects[t].rsz = usects[t+1].rsz;
|
|
usects[t].vsz = usects[t+1].vsz;
|
|
usects[t+1].rva = trva;
|
|
usects[t+1].rsz = trsz;
|
|
usects[t+1].vsz = tvsz;
|
|
upd = 1;
|
|
}
|
|
}
|
|
|
|
/* Computes virtualsize... we try to guess, actually :O */
|
|
for (t = 0; t < j-1 ; t++) {
|
|
if ( usects[t].vsz != usects[t+1].rva - usects[t].rva )
|
|
usects[t].vsz = usects[t+1].rva - usects[t].rva;
|
|
}
|
|
|
|
/*
|
|
* Our encryption is pathetic and out software is lame but
|
|
* we need to claim it's unbreakable.
|
|
* So why dont we just mangle the imports and encrypt the EP?!
|
|
*/
|
|
|
|
/* Decrypts old entrypoint if we got enough clues */
|
|
if (enc_ep) {
|
|
uint32_t virtaddr = pep + 5 + Imagebase, tmpep;
|
|
int rndm = 0, dummy = 1;
|
|
char *thunk = adjbuf+irva;
|
|
char *imports;
|
|
|
|
if ( version == 2 ) { /* 2.2 onley */
|
|
|
|
while ( dummy && CLI_ISCONTAINED(buf, bufsz, thunk, 4) ) {
|
|
uint32_t api;
|
|
|
|
if (! cli_readint32(thunk)) {
|
|
workdone = 1;
|
|
break;
|
|
}
|
|
|
|
imports = adjbuf + cli_readint32(thunk);
|
|
thunk+=4;
|
|
dummy = 0;
|
|
|
|
while ( CLI_ISCONTAINED(buf, bufsz, imports, 4)) {
|
|
dummy = 0;
|
|
|
|
imports+=4;
|
|
if ( ! (api = cli_readint32(imports-4)) ) {
|
|
dummy = 1;
|
|
break;
|
|
}
|
|
if ( (api != (api | 0x80000000)) && mangled && --rndm < 0) {
|
|
api = virtaddr;
|
|
virtaddr +=5; /* EB + 1 double */
|
|
rndm = virtaddr & 7;
|
|
} else {
|
|
api = 0xbff01337; /* KERNEL32!leet */
|
|
}
|
|
if (sections[sectcount-1].rva+Imagebase < api )
|
|
enc_ep--;
|
|
if ( api < virtaddr )
|
|
enc_ep--;
|
|
tmpep = (enc_ep & 0xfffffff8)>>3 & 0x1fffffff;
|
|
enc_ep = (enc_ep & 7)<<29 | tmpep;
|
|
}
|
|
}
|
|
} else
|
|
workdone = 1;
|
|
enc_ep = pep+5+enc_ep;
|
|
if ( workdone == 1 ) {
|
|
cli_dbgmsg("Petite: Old EP: %x\n", enc_ep);
|
|
} else {
|
|
enc_ep = usects[0].rva;
|
|
cli_dbgmsg("Petite: In troubles while attempting to decrypt old EP, using bogus %x\n", enc_ep);
|
|
}
|
|
}
|
|
|
|
/* Let's compact data */
|
|
for (t = 0; t < j ; t++) {
|
|
usects[t].raw = (t>0)?(usects[t-1].raw + usects[t-1].rsz):0;
|
|
if (usects[t].rsz != 0) {
|
|
if(CLI_ISCONTAINED(buf, bufsz, buf + usects[t].raw, usects[t].rsz)) {
|
|
memmove(buf + usects[t].raw, adjbuf + usects[t].rva, usects[t].rsz);
|
|
} else {
|
|
cli_dbgmsg("Petite: Skipping section %d, Raw: %x, RSize:%x\n", t, usects[t].raw, usects[t].rsz);
|
|
usects[t].raw = t>0 ? usects[t-1].raw : 0;
|
|
usects[t].rsz = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Showtime!!! */
|
|
cli_dbgmsg("Petite: Sections dump:\n");
|
|
for (t = 0; t < j ; t++)
|
|
cli_dbgmsg("Petite: .SECT%d RVA:%x VSize:%x ROffset: %x, RSize:%x\n", t, usects[t].rva, usects[t].vsz, usects[t].raw, usects[t].rsz);
|
|
if (! cli_rebuildpe(buf, usects, j, Imagebase, enc_ep, ResRva, ResSize, desc)) {
|
|
cli_dbgmsg("Petite: Rebuilding failed\n");
|
|
free(usects);
|
|
return 1;
|
|
}
|
|
free(usects);
|
|
return 0;
|
|
}
|
|
|
|
|
|
size = srva & 0x7fffffff;
|
|
if ( srva != size ) { /* Test and clear bit 31 */
|
|
check4resources=0;
|
|
/*
|
|
Enumerates each petite data section
|
|
I should get here once ot twice:
|
|
- 1 time for the resource section (if present)
|
|
- 1 time for the all_the_rest section
|
|
*/
|
|
|
|
if ( ! CLI_ISCONTAINED(buf, bufsz, packed+4, 8) ) {
|
|
if (usects)
|
|
free(usects);
|
|
return 1;
|
|
}
|
|
/* Save the end of current packed section for later use */
|
|
bottom = cli_readint32(packed+8) + 4;
|
|
ssrc = adjbuf + cli_readint32(packed+4) - (size-1)*4;
|
|
ddst = adjbuf + cli_readint32(packed+8) - (size-1)*4;
|
|
|
|
if ( !CLI_ISCONTAINED(buf, bufsz, ssrc, size*4) || !CLI_ISCONTAINED(buf, bufsz, ddst, size*4) ) {
|
|
if (usects)
|
|
free(usects);
|
|
return 1;
|
|
}
|
|
|
|
/* Copy packed data to the end of the current packed section */
|
|
memmove(ddst, ssrc, size*4);
|
|
packed += 0x0c;
|
|
} else {
|
|
uint32_t check1, check2;
|
|
uint8_t mydl = 0;
|
|
uint8_t goback;
|
|
unsigned int q;
|
|
|
|
/* Unpak each original section in turn */
|
|
|
|
if ( ! CLI_ISCONTAINED(buf, bufsz, packed+4, 8)) {
|
|
if (usects)
|
|
free(usects);
|
|
return 1;
|
|
}
|
|
|
|
size = cli_readint32(packed+4); /* How many bytes to unpack */
|
|
thisrva=cli_readint32(packed+8); /* RVA of the original section */
|
|
packed += 0x10;
|
|
|
|
if ( j >= 96 ) {
|
|
cli_dbgmsg("Petite: maximum number of sections exceeded, giving up.\n");
|
|
free(usects);
|
|
return 1;
|
|
}
|
|
/* Alloc 1 more struct */
|
|
if ( ! (tmpsct = cli_realloc(usects, sizeof(struct cli_exe_section) * (j+1))) ) {
|
|
if (usects)
|
|
free(usects);
|
|
return 1;
|
|
}
|
|
|
|
usects = (struct cli_exe_section *) tmpsct;
|
|
/* Save section spex for later rebuilding */
|
|
usects[j].rva = thisrva;
|
|
usects[j].rsz = size;
|
|
if ( (int)(bottom - thisrva) >0 )
|
|
usects[j].vsz = bottom - thisrva;
|
|
else
|
|
usects[j].vsz = size;
|
|
usects[j].raw = 0; /* Cheaper than memset */
|
|
|
|
if (!size) { /* That's a ghost section! reloc any1? :P */
|
|
j++;
|
|
continue;
|
|
}
|
|
|
|
ssrc = adjbuf + srva;
|
|
ddst = adjbuf + thisrva;
|
|
|
|
/* Last petite section (unpacked 1st) could contain unpacked data
|
|
* (eg the icon): let's fix the rva
|
|
*/
|
|
|
|
for ( q = 0 ; q < sectcount ; q++ ) {
|
|
if(!CLI_ISCONTAINED(sections[q].rva, sections[q].vsz, usects[j].rva, usects[j].vsz))
|
|
continue;
|
|
if (!check4resources) {
|
|
usects[j].rva = sections[q].rva;
|
|
usects[j].rsz = thisrva - sections[q].rva + size;
|
|
}
|
|
break;
|
|
}
|
|
if (q == sectcount) {
|
|
free(usects);
|
|
return 1;
|
|
}
|
|
|
|
/* Increase count of unpacked sections */
|
|
j++;
|
|
|
|
|
|
/* Setup some crap for later checks */
|
|
if ( size < 0x10000 ) {
|
|
check1 = 0x0FFFFC060;
|
|
check2 = 0x0FFFFFC60;
|
|
goback = 5;
|
|
} else if ( size < 0x40000 ) {
|
|
check1 = 0x0FFFF8180;
|
|
check2 = 0x0FFFFF980;
|
|
goback = 7;
|
|
} else {
|
|
check1 = 0x0FFFF8300;
|
|
check2 = 0x0FFFFFB00;
|
|
goback = 8;
|
|
}
|
|
|
|
/*
|
|
* NOTE: on last loop we get esi=edi=ImageBase (which is not writeable)
|
|
* The movsb on the next line causes the iat_rebuild_and_decrypt_oldEP()
|
|
* func to get called instead... ehehe very smart ;)
|
|
*/
|
|
|
|
if ( !CLI_ISCONTAINED(buf, bufsz, ssrc, 1) || !CLI_ISCONTAINED(buf, bufsz, ddst, 1)) {
|
|
free(usects);
|
|
return 1;
|
|
}
|
|
|
|
size--;
|
|
*ddst++=*ssrc++; /* eheh u C gurus gotta luv these monsters :P */
|
|
backbytes=0;
|
|
oldback = 0;
|
|
|
|
/* No surprises here... NRV any1??? ;) */
|
|
while (size > 0) {
|
|
oob = doubledl(&ssrc, &mydl, buf, bufsz);
|
|
if ( oob == -1 ) {
|
|
free(usects);
|
|
return 1;
|
|
}
|
|
if (!oob) {
|
|
if ( !CLI_ISCONTAINED(buf, bufsz, ssrc, 1) || !CLI_ISCONTAINED(buf, bufsz, ddst, 1) ) {
|
|
free(usects);
|
|
return 1;
|
|
}
|
|
*ddst++ = (char)((*ssrc++)^(size & 0xff));
|
|
size--;
|
|
} else {
|
|
addsize = 0;
|
|
backbytes++;
|
|
while (1) {
|
|
if ( (oob = doubledl(&ssrc, &mydl, buf, bufsz)) == -1 ) {
|
|
free(usects);
|
|
return 1;
|
|
}
|
|
backbytes = backbytes*2 + oob;
|
|
if ( (oob = doubledl(&ssrc, &mydl, buf, bufsz)) == -1 ) {
|
|
free(usects);
|
|
return 1;
|
|
}
|
|
if (!oob)
|
|
break;
|
|
}
|
|
backbytes -= 3;
|
|
if ( backbytes >= 0 ) {
|
|
backsize = goback;
|
|
do {
|
|
if ( (oob = doubledl(&ssrc, &mydl, buf, bufsz)) == -1 ) {
|
|
free(usects);
|
|
return 1;
|
|
}
|
|
backbytes = backbytes*2 + oob;
|
|
backsize--;
|
|
} while (backsize);
|
|
backbytes^=0xffffffff;
|
|
addsize += 1 + ( backbytes < (int) check2 ) + ( backbytes < (int) check1 );
|
|
oldback = backbytes;
|
|
} else {
|
|
backsize = backbytes+1;
|
|
backbytes = oldback;
|
|
}
|
|
|
|
if ( (oob = doubledl(&ssrc, &mydl, buf, bufsz)) == -1 ) {
|
|
free(usects);
|
|
return 1;
|
|
}
|
|
backsize = backsize*2 + oob;
|
|
if ( (oob = doubledl(&ssrc, &mydl, buf, bufsz)) == -1 ) {
|
|
free(usects);
|
|
return 1;
|
|
}
|
|
backsize = backsize*2 + oob;
|
|
if (!backsize) {
|
|
backsize++;
|
|
while (1) {
|
|
if ( (oob = doubledl(&ssrc, &mydl, buf, bufsz)) == -1 ) {
|
|
free(usects);
|
|
return 1;
|
|
}
|
|
backsize = backsize*2 + oob;
|
|
if ( (oob = doubledl(&ssrc, &mydl, buf, bufsz)) == -1 ) {
|
|
free(usects);
|
|
return 1;
|
|
}
|
|
if (!oob)
|
|
break;
|
|
}
|
|
backsize+=2;
|
|
}
|
|
backsize+=addsize;
|
|
size-=backsize;
|
|
if(!CLI_ISCONTAINED(buf, bufsz, ddst, backsize) || !CLI_ISCONTAINED(buf, bufsz, ddst+backbytes, backsize)) {
|
|
free(usects);
|
|
return 1;
|
|
}
|
|
while(backsize--) {
|
|
*ddst=*(ddst+backbytes);
|
|
ddst++;
|
|
}
|
|
backbytes=0;
|
|
backsize=0;
|
|
} /* else */
|
|
} /* while(ebx) */
|
|
|
|
/* Any lame petite code here? If so let's strip it
|
|
* We've done version adjustments already, see above
|
|
*/
|
|
|
|
if ( j ) {
|
|
int strippetite=0;
|
|
uint32_t reloc;
|
|
|
|
/* LONG MAGIC = 33C05E64 8B188B1B 8D63D65D */
|
|
if ( usects[j-1].rsz > grown &&
|
|
CLI_ISCONTAINED(buf, bufsz, ddst-grown+5+0x4f, 8) &&
|
|
cli_readint32(ddst-grown+5+0x4f) == 0x645ec033 &&
|
|
cli_readint32(ddst-grown+5+0x4f+4) == 0x1b8b188b
|
|
) {
|
|
reloc = 0;
|
|
strippetite = 1;
|
|
}
|
|
if ( !strippetite &&
|
|
usects[j-1].rsz > grown+skew &&
|
|
CLI_ISCONTAINED(buf, bufsz, ddst-grown+5+0x4f-skew, 8) &&
|
|
cli_readint32(ddst-grown+5+0x4f-skew) == 0x645ec033 &&
|
|
cli_readint32(ddst-grown+5+0x4f+4-skew) == 0x1b8b188b
|
|
) {
|
|
reloc = skew; /* If the original exe had a .reloc were skewed */
|
|
strippetite = 1;
|
|
}
|
|
|
|
if (strippetite && CLI_ISCONTAINED(buf, bufsz, ddst-grown+0x0f-8-reloc, 8)) {
|
|
uint32_t test1, test2;
|
|
|
|
/* REMINDER: DON'T BPX IN HERE U DUMBASS!!!!!!!!!!!!!!!!!!!!!!!! */
|
|
test1 = cli_readint32(ddst-grown+0x0f-8-reloc)^0x9d6661aa;
|
|
test2 = cli_readint32(ddst-grown+0x0f-4-reloc)^0xe908c483;
|
|
|
|
cli_dbgmsg("Petite: Found petite code in sect%d(%x). Let's strip it.\n", j-1, usects[j-1].rva);
|
|
if (test1 == test2 && CLI_ISCONTAINED(buf, bufsz, ddst-grown+0x0f-reloc, 0x1c0-0x0f+4)) {
|
|
irva = cli_readint32(ddst-grown+0x121-reloc);
|
|
enc_ep = cli_readint32(ddst-grown+0x0f-reloc)^test1;
|
|
mangled = ((uint32_t) cli_readint32(ddst-grown+0x1c0-reloc) != 0x90909090); /* FIXME: Magic's too short??? */
|
|
cli_dbgmsg("Petite: Encrypted EP: %x | Array of imports: %x\n",enc_ep, irva);
|
|
}
|
|
usects[j-1].rsz -= grown+reloc;
|
|
}
|
|
}
|
|
check4resources++;
|
|
} /* outer else */
|
|
} /* while true */
|
|
}
|
|
|