Here's a little assembler and a polymorphic engine I made a while ago. You can use this to generate dynamic decryptors for your hacks, so cheat engines can't detect your hacks via signature scanning.
Definition:
Code:
A polymorphic engine (sometimes called mutation engine or mutating engine) is a computer program that can be used to transform another program into a version that consists of different code with the same functionality. A typical polymorphic engine works by encrypting the target program in various ways and providing a decryption module that can vary widely
This engine:
This engine packs two features: Regswap and permutation. The decryptor itself is very simple and uses a basic DWORD-by-DWORD xor encoding. If you use this engine you must use a layered or overlapping encryption to destroy signatures completely (generate multiple decryptors for multiple layers of encryption). Another great (and relatively simple) improvement would be a junk generator. Generating junk would, I think, triple the efficiency of the decryptor against human analysis. and if done properly it could also heighten the decryptor's resilience against emulation.
The example program only generates a decryptor, and does not actually encrypt anything. The actual encryption is left as an exercise to the reader. The micro assembler includes more instructions then are needed for the engine, so there's room to expand on the decryptor used.
The code is big and clumsy by design, I've done this so that you don't need to figure out complex program flow on top of learning how the engine and the assembler work.
Strong points:
*Proper regswap
*Real permutation
*Strong against signature based detection
Weak points:
* No junk generation
* Weak against static analysis
* Weak against emulation
* Weak against human analysis
Next:
I don't think I'm going to update this program, but you never know.
Notes:
Tested on windows xp sp2, there is a very small chance that the engine crashes due to an off-by-one bug in the IsLocationValid() function, to lazy to fix.
The engine would be improved greatly if a (realistic) junk generator was added.
The engine would be improved greatly if cstd's rand() was replaced by the mersene twister, or another high quality rng.
Usage:
A decryptor can be generated by calling: unsigned char* GenerateDecryptor( DWORD Key, DWORD Start, DWORD End, DWORD Return, DWORD &Size );
Arg1 = The key to use
Arg2 = The start location (in memory) to decrypt
Arg3 = The location (again in memory) to stop decrypting
Arg4 = The location the decryptor should return to when it's done encrypting
Arg5 = Returns the size of the decryptor (it can vary between 0x128 and 0x1024 )
Sources:
Engine.h:
Code:
#ifndef ENGINE_H
#define ENGINE_H
#include <Windows.h>
#include "sinstr.h"
#include "sasm.h"
unsigned char* GenerateDecryptor( DWORD Key, DWORD Start, DWORD End, DWORD Return, DWORD &Size );
#endif
sasm.h:
Code:
#ifndef S_ASH_H
#define S_ASM_H
#include "sinstr.h"
bool SASM( sInstr* instr, unsigned char* instruction );
#endif
sinstr.h:
Code:
#ifndef SINSTR_H
#define SINSTR_H
#include <Windows.h>
struct sInstr{
union{
DWORD Src;
DWORD hWord;
};
union{
DWORD Dest;
DWORD lWord;
};
DWORD Type;
DWORD Len;
};
#define TYPEPUSHM 0x0 // not done
#define TYPEPUSHR 0x1
#define TYPEJUMP 0x2
#define TYPECALL 0x3
#define TYPEMOVR 0x4
#define TYPEMOVM 0x5
#define TYPEMMOV 0x6
#define TYPEMOVI 0xA // care
#define TYPEXORR 0x7
#define TYPEPOPM 0x8 // not done
#define TYPEPOPR 0x9
#define TYPECMPR 0xB
#define TYPEJE 0xC
#define TYPEADDR 0xD
#define TYPEINCR 0xE
#define TYPEADDI 0xF
#define REGEAX 0x0
#define REGECX 0x1
#define REGEDX 0x2
#define REGEBX 0x3
#define REGESP 0x4
#define REGEBP 0x5
#define REGESI 0x6
#define REGEDI 0x7
extern const unsigned char PUSHRbase;// = 0x50;
extern const unsigned char POPRbase; //= 0x58;
extern const unsigned char JMPMbase; // = 0xE9;
extern const unsigned char CALLMbase; // = 0xE8;
extern const unsigned char MOVRbase; // = 0x89;
extern const unsigned char MOVRbase2; //= 0xC0;
extern const unsigned char MOVMbase; // = 0x8B;
extern const unsigned char MOVMbase2; // = 0x0;
extern const unsigned char MMOVbase; // = 0x89;
extern const unsigned char MMOVbase2; // = 0x0
extern const unsigned char MOVIbase; // = 0xB8;
extern const unsigned char XORRbase; // = 0x30;
#define XORRbase2 MOVRbase2;
extern const unsigned char CMPRbase; // = 0x39;
#define CMPRbase2 MOVRbase2;
extern const unsigned char JEbase; // = 0x74;
extern const unsigned char ADDbase; // = 0x01;
#define ADDbase2 MOVRbase2;
extern const unsigned char INCbase; // = 0x40;
extern const unsigned char ADDIbase; // = 0x83;
#define ADDIbase2 MOVRbase2;
#endif
Engine.cpp:
Code:
#include <Windows.h>
#include <time.h>
#include <iostream>
using namespace std;
#include "sasm.h"
#include "sinstr.h"
struct DecryptorPlans{
DWORD KeyRegister;
DWORD CountRegister;
DWORD SizeRegiser;
DWORD GetsRegister;
DWORD PutsRegister;
DWORD FREE1;
DWORD FREE2;
DWORD FREE3;
};
#define REGNOCANDIDATE -1
char FreeRegisterCandidates [] = { REGEAX, REGECX, REGEDX, REGEBX, REGNOCANDIDATE, REGNOCANDIDATE, REGESI, REGEDI };
bool SetRegisters( DecryptorPlans *dp ){
srand( time( NULL ) );
do{
dp->KeyRegister = FreeRegisterCandidates[ rand() % sizeof( FreeRegisterCandidates ) ];
} while( dp->KeyRegister == REGNOCANDIDATE );
FreeRegisterCandidates[ dp->KeyRegister ] = REGNOCANDIDATE;
cout << "Key register selected" << endl;
do{
dp->CountRegister = FreeRegisterCandidates[ rand() % sizeof( FreeRegisterCandidates ) ];
} while ( dp->CountRegister == REGNOCANDIDATE );
FreeRegisterCandidates[ dp->CountRegister ] = REGNOCANDIDATE;
cout << "Count register selected" << endl;
do{
dp->SizeRegiser = FreeRegisterCandidates[ rand() % sizeof( FreeRegisterCandidates ) ];
} while ( dp->SizeRegiser == REGNOCANDIDATE );
FreeRegisterCandidates[ dp->SizeRegiser ] = REGNOCANDIDATE;
cout << "Size register selected" << endl;
do{
dp->GetsRegister = FreeRegisterCandidates[ rand() % sizeof( FreeRegisterCandidates ) ];
} while ( dp->GetsRegister == REGNOCANDIDATE );
FreeRegisterCandidates[ dp->GetsRegister ] = REGNOCANDIDATE;
cout << "Gets register selected" << endl;
do{
dp->PutsRegister = FreeRegisterCandidates[ rand() % sizeof( FreeRegisterCandidates ) ];
} while ( dp->PutsRegister == REGNOCANDIDATE );
FreeRegisterCandidates[ dp->GetsRegister ] = REGNOCANDIDATE;
cout << "Puts register selected" << endl;
return true;
}
DWORD GetRandomLocation( DWORD min, DWORD max ){
return ( min + ( rand() % max ) );
}
bool IsValidLocation( unsigned char* Location ){
unsigned char* p = Location - 0x6;
do{
if( *p )
return false;
} while ( ++p < Location + 0x6 );
return true;
}
DWORD GetNextInstruction( unsigned char* Buffer, DWORD Size ){
DWORD result = 0;
do{
result = GetRandomLocation( (DWORD)Buffer, Size-0x6 );
} while( !IsValidLocation( (unsigned char*)result ) );
return result;
}
bool PerformJump( unsigned char* Buffer, DWORD To, DWORD From ){
sInstr SCHiMInstr = { NULL };
SCHiMInstr.Dest = To;
SCHiMInstr.Src = From;
SCHiMInstr.Type = TYPEJUMP;
if( !SASM( &SCHiMInstr, (unsigned char*) From ) )
return false;
return true;
}
unsigned char* GenerateDecryptor( DWORD Key, DWORD Start, DWORD End, DWORD Return, DWORD &Size ){
unsigned char* Buffer = NULL;
DecryptorPlans dpl = { NULL };
sInstr SCHiMInstr = { NULL };
DWORD CurrentInstructionLocation = NULL;
DWORD NextInstructionLocation = NULL;
DWORD LoopLocation = NULL, OutLocation = NULL, CNTLocation = NULL;
srand( time( NULL ) );
Size = GetRandomLocation( 0x128, 0x1024 );
if( !( Buffer = new (std::nothrow) unsigned char[ Size ] ) )
return NULL;
memset( Buffer, 0, Size );
CurrentInstructionLocation = (DWORD)Buffer;
do{
NextInstructionLocation = GetRandomLocation( (DWORD)Buffer, Size - 0x6 );
} while ( NextInstructionLocation < CurrentInstructionLocation + 5 );
SetRegisters( &dpl );
cout << "To: " << std::hex << NextInstructionLocation << " From: " << std::hex << CurrentInstructionLocation << endl;
/*******************************
Starup fase
initialises critical registers
basic decryptor blueprint
*******************************/
//instruction 0x01: Jump into the decryptor stub
cout << "instruction 0x01: Jump into the decryptor stub" << endl;
if( !PerformJump( Buffer, NextInstructionLocation, CurrentInstructionLocation ) )
return NULL;
CurrentInstructionLocation = NextInstructionLocation;
NextInstructionLocation = GetNextInstruction( Buffer, Size );
cout << "instructuion 0x02: mov KeyRegister, Key" << endl;
//instructuion 0x02: mov KeyRegister, Key
SCHiMInstr.Dest = dpl.KeyRegister;
SCHiMInstr.Src = Key;
SCHiMInstr.Type = TYPEMOVI;
if( !SASM( &SCHiMInstr, (unsigned char*) CurrentInstructionLocation ) )
return NULL;
CurrentInstructionLocation = CurrentInstructionLocation + SCHiMInstr.Len;
NextInstructionLocation = GetNextInstruction( Buffer, Size );
cout << "instruction 0x03: Jump to gets register initialisation" << endl;
//instruction 0x03: Jump to gets register initialisation
if( !PerformJump( Buffer, NextInstructionLocation, CurrentInstructionLocation ) )
return NULL;
CurrentInstructionLocation = NextInstructionLocation;
NextInstructionLocation = GetNextInstruction( Buffer, Size );
cout << "instruction 0x04: Initialise gets register" << endl;
//instruction 0x04: Initialise gets register
SCHiMInstr.Dest = dpl.GetsRegister;
SCHiMInstr.Src = Start;
SCHiMInstr.Type = TYPEMOVI;
if( !SASM( &SCHiMInstr, (unsigned char*) CurrentInstructionLocation ) )
return NULL;
CurrentInstructionLocation = CurrentInstructionLocation + SCHiMInstr.Len; // NextInstructionLocation;
NextInstructionLocation = GetNextInstruction( Buffer, Size );
cout << "instruction 0x05: Jump to count register initialisation" << endl;
//instruction 0x05: Jump to count register initialisation
if( !PerformJump( Buffer, NextInstructionLocation, CurrentInstructionLocation ) )
return NULL;
CurrentInstructionLocation = NextInstructionLocation;
NextInstructionLocation = GetNextInstruction( Buffer, Size );
cout << "instruction 0x06: Initialize count register" << endl;
//Instruction 0x06: Initialize count register
SCHiMInstr.Dest = dpl.CountRegister;
SCHiMInstr.Src = 0;
SCHiMInstr.Type = TYPEMOVI;
if( !SASM( &SCHiMInstr, (unsigned char*) CurrentInstructionLocation ) )
return NULL;
CurrentInstructionLocation = CurrentInstructionLocation + SCHiMInstr.Len; // NextInstructionLocation;
NextInstructionLocation = GetNextInstruction( Buffer, Size );
cout << "instruction 0x07: Jump to size register initialization" << endl;
//instruction 0x07: Jump to size register initialization
if( !PerformJump( Buffer, NextInstructionLocation, CurrentInstructionLocation ) )
return NULL;
CurrentInstructionLocation = NextInstructionLocation;
NextInstructionLocation = GetNextInstruction( Buffer, Size );
cout << "instruction 0x08: Initialize size register" << endl;
//instruction 0x08: Initialize size register
SCHiMInstr.Dest = dpl.SizeRegiser;
SCHiMInstr.Src = ( End - Start );
SCHiMInstr.Type = TYPEMOVI;
if( !SASM( &SCHiMInstr, (unsigned char*) CurrentInstructionLocation ) )
return NULL;
CurrentInstructionLocation = CurrentInstructionLocation + SCHiMInstr.Len; // NextInstructionLocation;
NextInstructionLocation = GetNextInstruction( Buffer, Size );
cout << "instruction 0x09: Jump to cmp instruction" << endl;
//instruction 0x09: Jump to cmp instruction
if( !PerformJump( Buffer, NextInstructionLocation, CurrentInstructionLocation ) )
return NULL;
CurrentInstructionLocation = NextInstructionLocation;
NextInstructionLocation = GetNextInstruction( Buffer, Size );
LoopLocation = CurrentInstructionLocation;
cout << "instruction 0x0a: Cmp count, size register" << endl;
//instruction 0x0a: cmp count, size register
SCHiMInstr.Dest = dpl.CountRegister;
SCHiMInstr.Src = dpl.SizeRegiser;
SCHiMInstr.Type = TYPECMPR;
if( !SASM( &SCHiMInstr, (unsigned char*) CurrentInstructionLocation ) )
return NULL;
cout << "instruction 0x0b: Je out" << endl;
//instruction 0x0b: je out
CurrentInstructionLocation = CurrentInstructionLocation + SCHiMInstr.Len;
NextInstructionLocation = CurrentInstructionLocation + 5 + 2;
CNTLocation = CurrentInstructionLocation + 2;
OutLocation = NextInstructionLocation;
SCHiMInstr.Dest = 0x5;
SCHiMInstr.Type = TYPEJE;
if( !SASM( &SCHiMInstr, (unsigned char*) CurrentInstructionLocation ) )
return NULL;
CurrentInstructionLocation = NextInstructionLocation;
cout << "instruction 0x0C: Jump outside the decryptor stub" << endl;
//instruction 0x0C: Jump outside the decryptor stub
if( !PerformJump( Buffer, Return, CurrentInstructionLocation ) )
return NULL;
CurrentInstructionLocation = CNTLocation;
NextInstructionLocation = GetNextInstruction( Buffer, Size );
cout << "instruction 0x0D: Jump to mov puts, [gets]" << endl;
//instruction 0x0D: Jump to mov puts, [gets]
if( !PerformJump( Buffer, NextInstructionLocation, CurrentInstructionLocation ) )
return NULL;
CurrentInstructionLocation = NextInstructionLocation;
NextInstructionLocation = GetNextInstruction( Buffer, Size );
cout << "instruction 0x0E: Mov puts, [gets]" << endl;
//instruction 0x0E: Mov puts, [gets]
SCHiMInstr.Dest = dpl.PutsRegister;
SCHiMInstr.Src = dpl.GetsRegister;
SCHiMInstr.Type = TYPEMOVM;
if( !SASM( &SCHiMInstr, (unsigned char*) CurrentInstructionLocation ) )
return NULL;
CurrentInstructionLocation = CurrentInstructionLocation + SCHiMInstr.Len; // NextInstructionLocation;
NextInstructionLocation = GetNextInstruction( Buffer, Size );
cout << "instruction 0x0F: Jump to xor puts, key" << endl;
//instruction 0x0F: Jump to xor puts, key
if( !PerformJump( Buffer, NextInstructionLocation, CurrentInstructionLocation ) )
return NULL;
CurrentInstructionLocation = NextInstructionLocation;
NextInstructionLocation = GetNextInstruction( Buffer, Size );
cout << "instruction 0x10: Xor puts, key" << endl;
//instruction 0x10: Xor puts, key
SCHiMInstr.Dest = dpl.PutsRegister;
SCHiMInstr.Src = dpl.KeyRegister;
SCHiMInstr.Type = TYPEXORR;
if( !SASM( &SCHiMInstr, (unsigned char*) CurrentInstructionLocation ) )
return NULL;
CurrentInstructionLocation = CurrentInstructionLocation + SCHiMInstr.Len; // NextInstructionLocation;
NextInstructionLocation = GetNextInstruction( Buffer, Size );
cout << "instruction 0x11: Jump to mov [gets], puts" << endl;
//instruction 0x11: Jump to mov [gets], puts
if( !PerformJump( Buffer, NextInstructionLocation, CurrentInstructionLocation ) )
return NULL;
CurrentInstructionLocation = NextInstructionLocation;
NextInstructionLocation = GetNextInstruction( Buffer, Size );
cout << "instruction 0x12: Mov [gets], puts" << endl;
//instruction 0x12: mov [gets], puts
SCHiMInstr.Dest = dpl.GetsRegister;
SCHiMInstr.Src = dpl.PutsRegister;
SCHiMInstr.Type = TYPEMMOV;
if( !SASM( &SCHiMInstr, (unsigned char*) CurrentInstructionLocation ) )
return NULL;
CurrentInstructionLocation = CurrentInstructionLocation + SCHiMInstr.Len; // NextInstructionLocation;
NextInstructionLocation = GetNextInstruction( Buffer, Size );
cout << "instruction 0x13: Jump to add gets, 4" << endl;
//instruction 0x13: Jump to add gets, 4
if( !PerformJump( Buffer, NextInstructionLocation, CurrentInstructionLocation ) )
return NULL;
CurrentInstructionLocation = NextInstructionLocation;
NextInstructionLocation = GetNextInstruction( Buffer, Size );
cout << "instruction 0x14: Add gets, 4" << endl;
//instruction 0x14: Add gets, 4
SCHiMInstr.Dest = dpl.GetsRegister;
SCHiMInstr.Src = 0x4;
SCHiMInstr.Type = TYPEADDI;
if( !SASM( &SCHiMInstr, (unsigned char*) CurrentInstructionLocation ) )
return NULL;
CurrentInstructionLocation = CurrentInstructionLocation + SCHiMInstr.Len; // NextInstructionLocation;
NextInstructionLocation = GetNextInstruction( Buffer, Size );
cout << "instruction 0x15: Jump to add count, 4" << endl;
//instruction 0x15: Jump to add count, 4t
if( !PerformJump( Buffer, NextInstructionLocation, CurrentInstructionLocation ) )
return NULL;
CurrentInstructionLocation = NextInstructionLocation;
NextInstructionLocation = GetNextInstruction( Buffer, Size );
cout << "instruction 0x16: Add count, 4" << endl;
//instruction 0x16: Add count,4
SCHiMInstr.Dest = dpl.CountRegister;
SCHiMInstr.Src = 0x4;
SCHiMInstr.Type = TYPEADDI;
if( !SASM( &SCHiMInstr, (unsigned char*) CurrentInstructionLocation ) )
return NULL;
cout << "instruction 0x17: Jump to loop" << endl;
//instruction 0x17: Jump to loop
if( !PerformJump( Buffer, LoopLocation, CurrentInstructionLocation + SCHiMInstr.Len ) )
return NULL;
cout << "Done" << endl;
/*******************************
Main generation fase
Generates junk to-do
Generates semi-realistic junk ( to-do )
*******************************/
return Buffer;
}
main.cpp:
Code:
#include <iostream>
#include <Windows.h>
#include "sasm.h"
#include "sinstr.h"
#include "Engine.h"
int main(){
DWORD OldProtect, Size;
// key start location stop location return size
unsigned char* Decryptor = GenerateDecryptor( 0xDEADBEEF, 0x401000, 0x401100, 0x401000, Size );
// Decryptor now points to a function that can be a or pre-pended to a piece of encrypted code.
VirtualProtect( (LPVOID)Decryptor, Size, 0x40, &OldProtect );
__asm{
INT 3h
mov eax, Decryptor
mov eax, eax
}
/*
unsigned char* Buffer = new unsigned char[ 16 ];
memset( Buffer, 0, 16 );
sInstr instr = { NULL };
instr.Dest = 0x401010;
instr.Src = 0x401000;
instr.Type = TYPEJUMP; // mov [eax], ecx
SASM( &instr, Buffer );
printf( "Instruction: %.2x %.2x %.2x %.2x %.2x\n", Buffer[0], Buffer[1], Buffer[2], Buffer[3], Buffer[4] );
*/
system("pause");
return 0;
}
sasm.cpp:
Code:
#include "sinstr.h"
bool SASM( sInstr* instr, unsigned char* instruction ){
instr->Len = 2;
switch( instr->Type ){
case TYPEPUSHR:
instruction[0] = PUSHRbase;
instruction[0] += instr->Src;
instr->Len = 1;
break;
case TYPEPOPR:
instruction[0] = POPRbase;
instruction[0] += instr->Dest;
instr->Len = 1;
break;
case TYPEJUMP:
instruction[0] = JMPMbase;
*(DWORD*)(&instruction[1]) = instr->Dest - instr->Src - 5;
instr->Len = 5;
break;
case TYPECALL:
instruction[0] = CALLMbase;
*(DWORD*)(&instruction[1]) = instr->Dest - instr->Src - 5;
instr->Len = 5;
break;
case TYPEMOVR:
instruction[0] = MOVRbase;
instruction[1] = MOVRbase2;
instruction[1] += 8 * instr->Src; // 3 = ebx = 8 *
instruction[1] += instr->Dest; // 2 = edx
break;
case TYPEMOVM:
// Not that advanced...
if( instr->Src == REGESP || instr->Src == REGEBP )
return false;
instruction[0] = MOVMbase;
instruction[1] = MOVMbase2;
instruction[1] += 8 * instr->Dest;
instruction[1] += instr->Src;
break;
case TYPEMMOV:
// Not that advanced...
if( instr->Dest == REGESP || instr->Dest == REGEBP )
return false;
instruction[0] = MMOVbase;
instruction[1] = MMOVbase2;
instruction[1] += 8 * instr->Src;
instruction[1] += instr->Dest;
break;
case TYPEMOVI:
instruction[0] = MOVIbase;
instruction[0] += instr->Dest;
*(DWORD*)(&instruction[1]) = instr->Src;
instr->Len = 5;
break;
case TYPEXORR:
instruction[0] = XORRbase;
instruction[1] = XORRbase2;
instruction[1] += 8 * instr->Src; // 3 = ebx = 8 *
instruction[1] += instr->Dest; // 2 = edx
break;
case TYPECMPR:
instruction[0] = CMPRbase;
instruction[1] = CMPRbase2;
instruction[1] += 8 * instr->Src; // 3 = ebx = 8 *
instruction[1] += instr->Dest; // 2 = edx
break;
case TYPEJE:
instruction[0] = JEbase;
instruction[1] = instr->Dest;
break;
case TYPEADDR:
instruction[0] = ADDbase;
instruction[1] = ADDbase2;
instruction[1] += 8 * instr->Src; // 3 = ebx = 8 *
instruction[1] += instr->Dest; // 2 = edx
break;
case TYPEINCR:
instruction[0] = INCbase;
instruction[0] += instr->Dest;
instr->Len = 1;
break;
case TYPEADDI:
instruction[0] = ADDIbase;
instruction[1] = ADDIbase2;
instruction[1] += instr->Dest;
instruction[2] = instr->Src;
instr->Len = 3;
break;
default:
return false;
break;
}
return true;
}
sinstr.cpp:
Code:
#include "sinstr.h"
const unsigned char PUSHRbase = 0x50;
const unsigned char POPRbase = 0x58;
const unsigned char JMPMbase = 0xE9;
const unsigned char CALLMbase = 0xE8;
const unsigned char MOVRbase = 0x89;
const unsigned char MOVRbase2 = 0xC0;
const unsigned char MOVMbase = 0x8B;
const unsigned char MOVMbase2 = 0x0;
const unsigned char MMOVbase = 0x89;
const unsigned char MMOVbase2 = 0x0;
const unsigned char MOVIbase = 0xB8;
const unsigned char XORRbase = 0x31;
const unsigned char CMPRbase = 0x39;
const unsigned char JEbase = 0x74;
const unsigned char ADDbase = 0x01;
const unsigned char INCbase = 0x40;
const unsigned char ADDIbase = 0x83;
-SCHiM