/* * Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved. * Copyright (C) 2007-2013 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 #include #include "clamav.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, addsize; unsigned int backsize; 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; /* Unpack 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; } if (backbytes >= INT_MAX / 2) { free(usects); cli_dbgmsg("Petite: probably invalid file\n"); 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; } if (backbytes >= INT_MAX / 2) { free(usects); cli_dbgmsg("Petite: probably invalid file\n"); 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 */ }