/* * Copyright (C) 2005 aCaB * * 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 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, 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 #include #include #include #include #include #include "cltypes.h" #include "pe.h" #include "rebuildpe.h" #include "others.h" #if WORDS_BIGENDIAN == 0 #define EC32(v) (v) #else static inline uint32_t EC32(uint32_t v) { return ((v >> 24) | ((v & 0x00FF0000) >> 8) | ((v & 0x0000FF00) << 8) | (v << 24)); } #endif #define ROL(a,b) a = ( a << (b % (sizeof(a)<<3) )) | (a >> ( (sizeof(a)<<3) - (b % (sizeof(a)<<3 )) ) ) #define ROR(a,b) a = ( a >> (b % (sizeof(a)<<3) )) | (a << ( (sizeof(a)<<3) - (b % (sizeof(a)<<3 )) ) ) /* FIXME: poly block is fixed size */ static char exec86(uint8_t aelle, uint8_t cielle, char *curremu) { while (*curremu!='\xaa') { uint8_t opcode = *curremu, support; curremu++; switch (opcode) { case 0xeb: curremu++; case 0x0a: curremu++; case 0x90: case 0xf8: case 0xf9: break; case 0x02: /* add al, cl */ aelle+=cielle; curremu++; break; case 0x2a: /* sub al, cl */ aelle-=cielle; curremu++; break; case 0x04: /* add al, ?? */ aelle+=*curremu; curremu++; break; case 0x2c: /* sub al, ?? */ aelle-=*curremu; curremu++; break; case 0x32: /* xor al, cl */ aelle^=cielle; curremu++; break; case 0x34: /* xor al, ?? */ aelle^=*curremu; curremu++; break; case 0xfe: /* inc/dec al */ if ( *curremu == '\xc0' ) aelle++; else aelle--; curremu++; break; case 0xc0: /* ror/rol al, ?? */ support = *curremu; curremu++; if ( support == 0xc0 ) ROL(aelle, *curremu); else ROR(aelle, *curremu); curremu++; break; default: cli_dbgmsg("Bogus opcode %x\n", opcode); } } return aelle; } static int doubledl(char **scur, uint8_t *mydlptr, char *buffer, int 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; } static int unfsg(char *source, char *dest, int ssize, int dsize) { uint8_t mydl=0x80; uint32_t backbytes, backsize, oldback = 0; char *csrc = source, *cdst = dest; int oob, lostbit = 1; /* I assume buffers size is >0 - No checking! */ *cdst++=*csrc++; while ( 1 ) { if ((oob=doubledl(&csrc, &mydl, source, ssize))) { if (oob == -1) return -1; /* 164 */ backsize = 0; if ((oob=doubledl(&csrc, &mydl, source, ssize))) { if (oob == -1) return -1; /* 16a */ backbytes = 0; if ((oob=doubledl(&csrc, &mydl, source, ssize))) { if (oob == -1) return -1; /* 170 */ lostbit = 1; backsize++; backbytes = 0x10; while ( backbytes < 0x100 ) { if ((oob=doubledl(&csrc, &mydl, source, ssize)) == -1) return -1; backbytes = backbytes*2+oob; } backbytes &= 0xff; if ( ! backbytes ) { if (cdst >= dest+dsize) return -1; *cdst++=0x00; continue; } else { /* repne movsb - FIXME dont remove for now */ } } else { /* 18f */ if (csrc >= source+ssize) return -1; backbytes = *(unsigned char*)csrc; backsize = backsize * 2 + (backbytes & 1); backbytes = (backbytes & 0xff)>>1; csrc++; if (! backbytes) break; backsize+=2; oldback = backbytes; lostbit = 0; } } else { /* 180 */ backsize = 1; do { if ((oob=doubledl(&csrc, &mydl, source, ssize)) == -1) return -1; backsize = backsize*2+oob; if ((oob=doubledl(&csrc, &mydl, source, ssize)) == -1) return -1; } while (oob); backsize = backsize - 1 - lostbit; if (! backsize) { /* 18a */ backsize = 1; do { if ((oob=doubledl(&csrc, &mydl, source, ssize)) == -1) return -1; backsize = backsize*2+oob; if ((oob=doubledl(&csrc, &mydl, source, ssize)) == -1) return -1; } while (oob); backbytes = oldback; } else { /* 198 */ if (csrc >= source+ssize) return -1; backbytes = *(unsigned char*)csrc; backbytes += (backsize-1)<<8; backsize = 1; csrc++; do { if ((oob=doubledl(&csrc, &mydl, source, ssize)) == -1) return -1; backsize = backsize*2+oob; if ((oob=doubledl(&csrc, &mydl, source, ssize)) == -1) return -1; } while (oob); if (backbytes >= 0x7d00) backsize++; if (backbytes >= 0x500) backsize++; if (backbytes <= 0x7f) backsize += 2; oldback = backbytes; } lostbit = 0; } if ((backsize >= dest + dsize - cdst) || (backbytes > cdst - dest)) return -1; while(backsize--) { *cdst=*(cdst-backbytes); cdst++; } } else { /* 15d */ if (cdst < dest || cdst >= dest+dsize || csrc < source || csrc >= source+ssize) return -1; *cdst++=*csrc++; lostbit=1; } } return 0; } 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; } int unspin(char *src, int ssize, struct pe_image_section_hdr *sections, int sectcnt, uint32_t nep, int desc) { 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; // ep = src + nep + sections[sectcnt].PointerToRawData - sections[sectcnt].VirtualAddress; // Just a helper curr = ep+0xdb; // HELP: as a general rule, can i do char* math or should use monsters like "&ep[0xdb]" instead? 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 ( ep - spinned >= EC32(sections[sectcnt].SizeOfRawData) - len - 0x1fe5 ) { free(spinned); cli_dbgmsg("spin: len out of bounds, giving up\n"); return 1; // Outta bounds - HELP: i suppose i should check for wraps.. not sure though } curr = ep+0x1fe5+len-1; while ( len-- ) { *curr=(*curr)^(key8--); curr--; } 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; // FIXME: apparently static } curr = ep+0x2d5; // 0x2d5+5a0 < 0x3217 - still within bounds (checked by caller) cli_dbgmsg("spin: Key is %x, Len is %x\n", key32, len); while ( len-- ) { if ( key32 & 1 ) { key32 = key32>>1&0x7fffffff; key32 ^= 0x8c328834; } else { key32 = key32>>1 & 0x7fffffff; } *curr = *curr ^ (key32 & 0xff); curr++; } cli_dbgmsg("spin: here\n"); len = ssize - cli_readint32(ep+0x429); // sub size, value if ( len >= ssize ) { free(spinned); cli_dbgmsg("spin: crc out of bounds, giving up\n"); return 1; // We wrapped } key32 = cli_readint32(ep+0x3217) - summit(src,len); memcpy(src + EC32(sections[sectcnt].PointerToRawData), spinned, EC32(sections[sectcnt].SizeOfRawData)); free(spinned); // done CRC'ing - can have a dirty buffer now ep = src + nep + sections[sectcnt].PointerToRawData - sections[sectcnt].VirtualAddress; // Fix the helper cli_dbgmsg("spin: Key32 is %x\n", key32); bitmap = cli_readint32(ep+0x3207); cli_dbgmsg("spin: XORbitmap is %x\n", bitmap); for (j=0; j= ssize ) { cli_dbgmsg("spin: sect %d out of file, giving up\n", j); return 1; // sect outta bounds - HELP: i suppose i should check for wraps.. not sure though } while (size--) { if (! (keydup & 1)) { keydup = keydup>>1&0x7fffffff; /* HELP: clear sign bit for unsigned values too? */ keydup ^= 0xed43af31; } else { keydup = keydup>>1 & 0x7fffffff; /* HELP: clear sign bit for unsigned values too? */ } *ptr = *ptr ^ (keydup & 0xff); ptr++; } bitmap = bitmap >>1 & 0x7fffffff; /* HELP: clear sign bit for unsigned values too? */ } } cli_dbgmsg("spin: done\n"); curr = ep+0x644; // 0x28d3+0x180 < 0x3217 - still within bounds (checked by caller) 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; while ( len-- ) { if ( key32 & 1 ) { key32 = key32>>1&0x7fffffff; key32 ^= 0xed43af32; } else { key32 = key32>>1 & 0x7fffffff; } *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; // Still within bounds while (len) { *emu=exec86(*emu, len-- & 0xff, curr); // unlame POLY1 emu++; } bitmap = cli_readint32(ep+0x6f1); cli_dbgmsg("spin: POLYbitmap is %x\n", bitmap); curr = ep+0x755; for (j=0; j= ssize) { // HELP: Is this enough for me to be within bounds? cli_dbgmsg("spin: code to exec is out of file?\n"); return 1; } while (len) { *emu=exec86(*emu, len-- & 0xff, curr); emu++; } bitmap = bitmap >>1 & 0x7fffffff; } } bitmap = cli_readint32(ep+0x3061); bitman = bitmap; 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>1 & 0x7fffffff; } if ( len ) { int t; for (t=0 ; t key32) // HELP: wraps? break; } cli_dbgmsg("spin: --- %x < %x < %x %d / %d\n", EC32(sections[j].VirtualAddress), key32, EC32(sections[j].VirtualAddress)+EC32(sections[j].SizeOfRawData), j, sectcnt); if (j!=sectcnt && ((bitman & (1<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]); bitmap = bitmap >>1 & 0x7fffffff; } if ( (to = rebuildpe(ep, rebhlp, sectcnt, 0x400000, 0x1000, 0, 0))) { // HELP: should i bother fixing those values? the rebuilt exe is completely broken anyway. write(desc, to, 0x148+0x80+0x28*j+rebhlp[j-1].raw+rebhlp[j-1].rsz); free(to); } else { 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>1 & 0x7fffffff; } free(sects); return 1; // :( }