[EXPERIMENTAL] Metamorphic obfuscator

Posts 1–15 of 17 · Page 1 of 2
[EXPERIMENTAL] Metamorphic obfuscator
First things first

This is a very experimental release. This code is only five minutes old at the time of writing and not finished, I've only tested it once and it hasn't been commented or re-factored. It's really very hackery in some places and I'll take zero responsibility for damage or errors. Next, I've cobbled together this project from many different sources, some of the code is (I admit) directly c+p'ed from other sources. I've also used a few libraries that are available here, here and here.

You'll need:

The boost C++ libraries, fasm.dll and beaengine.dll.

What does this thing do?

I realize that this is not actually a metamorphic obfuscator, since the obfuscated executable will not be able to self-modify. However lacking a name for this (since it's unlike other obfuscators out there but like techniques used in metamorphic viruses) I named it thus. A normal obfuscator will "hide" the "real" code by surrounding it by superfluous instructions or encrypting them. The real code will still execute in it's normal form however, after the decryption or in between junk instructions. More advanced obfuscators will perhaps reassemble jump and call offsets to account for the increase in executable size, but that's about it.

This code is different. This engine 'morphs' instructions to (a) different, but functionally identical, other instruction(s). This process is 100% recursive, so you could theoretically morph one instruction into thousands of others that all combine to be functionally equivalent to the original instruction.

An example:

Suppose that you start with this:

Code:
mov eax, ebx

The engine can transform that to:

Code:
push ebx
pop eax
Which can be transformed into:

Code:
sub esp, 4
mov dword[esp], ebx

pop eax
Which can become:

Code:
xchg eax, ebx

sub esp, 4
mov dword[esp], eax
pop ebx

xchg eax, ebx
So in the end:

Code:
 

mov eax, ebx

; Is equivalent to:

xchg eax, ebx
sub esp, 4
mov dword[esp], eax
pop ebx
xchg eax, ebx
Now imagine doing this to an entire executable. It's size inflated a thousand times. It'll be impossible to distinguish where one functional instruction begins and where the next ends. Every new instruction will only complete 1000th of an old instruction, and yet you can't see or understand what exactly it's doing.

Sources
Like I said before, this is a very hackery/experimental build. To use it you'll need to put an executable named test.exe into the directory of the mutator. It'll also need beaengine.dll and fasm.dll in the same directory

cFasmWrapper.h:

Code:
#ifndef C_FASM_WRAPPER_H
#define C_FASM_WRAPPER_H

#include <string>
#include <vector>

#include <Windows.h>

using namespace std;

#define FASM_OK 				  0	//; #define FASM_STATE points to output
#define FASM_WORKING			  1
#define FASM_ERROR				  2	//; #define FASM_STATE contains error code
#define FASM_INVALID_PARAMETER   -1
#define FASM_OUT_OF_MEMORY		 -2
#define FASM_STACK_OVERFLOW		 -3
#define FASM_SOURCE_NOT_FOUND		  	  -4
#define FASM_UNEXPECTED_END_OF_SOURCE	  -5
#define FASM_CANNOT_GENERATE_CODE	      -6
#define FASM_FORMAT_LIMITATIONS_EXCEDDED  -7
#define FASM_WRITE_FAILED		  -		   8

#define FASMERR_FILE_NOT_FOUND			    -101
#define FASMERR_ERROR_READING_FILE		    -102
#define FASMERR_INVALID_FILE_FORMAT		    -103
#define FASMERR_INVALID_MACRO_ARGUMENTS 	-104
#define FASMERR_INCOMPLETE_MACRO		    -105
#define FASMERR_UNEXPECTED_CHARACTERS		-106
#define FASMERR_INVALID_ARGUMENT		    -107
#define FASMERR_ILLEGAL_INSTRUCTION		    -108
#define FASMERR_INVALID_OPERAND 		    -109
#define FASMERR_INVALID_OPERAND_SIZE		    -110
#define FASMERR_OPERAND_SIZE_NOT_SPECIFIED	    -111
#define FASMERR_OPERAND_SIZES_DO_NOT_MATCH	    -112
#define FASMERR_INVALID_ADDRESS_SIZE		    -113
#define FASMERR_ADDRESS_SIZES_DO_NOT_AGREE			-114
#define FASMERR_DISALLOWED_COMBINATION_OF_REGISTERS -115
#define FASMERR_LONG_IMMEDIATE_NOT_ENCODABLE	    -116
#define FASMERR_RELATIVE_JUMP_OUT_OF_RANGE			-117
#define FASMERR_INVALID_EXPRESSION		    -118
#define FASMERR_INVALID_ADDRESS 		    -119
#define FASMERR_INVALID_VALUE			    -120
#define FASMERR_VALUE_OUT_OF_RANGE		    -121
#define FASMERR_UNDEFINED_SYMBOL		    -122
#define FASMERR_INVALID_USE_OF_SYMBOL		-123
#define FASMERR_NAME_TOO_LONG			    -124
#define FASMERR_INVALID_NAME						 -125
#define FASMERR_RESERVED_WORD_USED_AS_SYMBOL	     -126
#define FASMERR_SYMBOL_ALREADY_DEFINED				 -127
#define FASMERR_MISSING_END_QUOTE				-128
#define FASMERR_MISSING_END_DIRECTIVE		    -129
#define FASMERR_UNEXPECTED_INSTRUCTION		    -130
#define FASMERR_EXTRA_CHARACTERS_ON_LINE	    -131
#define FASMERR_SECTION_NOT_ALIGNED_ENOUGH	    -132
#define FASMERR_SETTING_ALREADY_SPECIFIED	    -133
#define FASMERR_DATA_ALREADY_DEFINED		    -134
#define FASMERR_TOO_MANY_REPEATS		    -135
#define FASMERR_SYMBOL_OUT_OF_SCOPE		    -136
#define FASMERR_USER_ERROR					-140
#define FASMERR_ASSERTION_FAILED		    -141

struct FASM_STATE
{
  DWORD Condition;
  union
  {
    DWORD OutputLength;
    DWORD ErrorCode;
  };
  union
  {
    DWORD pOutput;
    DWORD ErrorLine;
  };
};

class cFasmWrapper
{
	HANDLE _hFasm;
	
	unsigned int (__cdecl *fAssemble)( const char * Source, const unsigned char * Buffer, int BufferSize, int NumberOfPasses); 

public:
	cFasmWrapper();

	bool Assemble( const string &Source, const unsigned char* pBuffer, int BufferSize );
};

#endif
cFasmWrapper.cpp:

Code:
#include <string>
#include <vector>

#include <Windows.h>

#include "cFasmWrapper.h"

cFasmWrapper::cFasmWrapper()
{
	_hFasm = LoadLibrary( L"FASM.dll" );
	
	if( _hFasm == INVALID_HANDLE_VALUE )
		throw "Error: cFasmWrapper::cFasmWrapper: 'FASM.dll' not found";

	fAssemble = (unsigned int (__cdecl*)(const char *, const unsigned char *, int, int))GetProcAddress( (HMODULE)_hFasm, "fasm_Assemble" );

	if( !fAssemble )
		throw "Error: cFasmWrapper::cFasmWrapper: Symbol 'fasm_Assemble' not exported";
}

bool cFasmWrapper::Assemble( const string &Source, const unsigned char* pBuffer, int BufferSize )
{
	const unsigned char* cpBuffer = pBuffer;
	const char* sPointer = Source.c_str();

	const unsigned int* fPointer = (const unsigned int*)fAssemble;
	int iBufferSize = BufferSize;

	unsigned int Result;

	__asm
	{
		mov eax, iBufferSize
		mov ecx, cpBuffer
		mov edx, sPointer
		mov edi, fPointer

		push ebp
		mov ebp, esp

		push 100
		push eax
		push ecx
		push edx
		call edi
		
		mov esp, ebp
		pop ebp

		mov Result, eax
	}

	return FASM_OK == Result;
}
cMutator.h:

Code:
#ifndef C_MUTATOR_H
#define C_MUTATOR_H

#define BEA_ENGINE_STATIC
#define BEA_USE_STDCALL

#include <fstream>
#include <vector>
#include <string>

#include <bea/BeaEngine.h>

#include <Windows.h>

#include "cFasmWrapper.h"

#pragma comment( lib, "BeaEngine.lib" )
#pragma comment( lib, "peBliss.lib" )

using namespace std;

class cDisassembler
{
	DISASM _Disasm;

public:
	cDisassembler();

	void Disassmble( const unsigned char* pCode, const size_t Size, vector< pair< size_t, string > > &Result );
	size_t LengthDisassemble( const unsigned char* pCode, const size_t Size, const char LastInstruction );
};

class cMutator
{
	size_t _Index;
	size_t _MutationIndex;
	
	size_t _AppendageIndex;
	vector< vector< string > > _PtrPrefix;

	string GetRandomRegister( string NotOf );

	vector<string> MutatePop( const vector< string > &Assembler );
	vector<string> MutatePush( const vector< string > &Assembler );
	vector<string> MutateMove( const vector< string > &Assembler );

	void Regswap( vector< string > &SectionDisassembly, const string &Reg1, const string &Reg2 );

public:
	cMutator( int MutationIndex = 3 );

	void Mutate( const vector< string > &Assembler, vector< string > &Result, vector< vector< string > > &Appendages );

	string VectorToFasmSource( const vector< string > &Source );
};

class cPE
{
	string _Filename;

	unsigned char* _File;
	size_t _Size;

	PIMAGE_SECTION_HEADER NewSection;
	PIMAGE_DOS_HEADER dos_header;
    PIMAGE_NT_HEADERS nt_headers;

	DWORD AlignToBoundary(DWORD size, DWORD alignment);

	PIMAGE_SECTION_HEADER AddSection(const char *section_name, size_t section_size, PIMAGE_DOS_HEADER image_addr, size_t &NewSize );
	
	void ReloadHeaders();

public:
	cPE( const string& Filename );
	~cPE();

	PIMAGE_SECTION_HEADER AddNewSection( unsigned int Size );
	PIMAGE_SECTION_HEADER GetExecutableSection( size_t Index );

	void SaveFile();

	void Write( size_t Offset, unsigned char* Data, unsigned int Size );
	void WriteNop( size_t Offset, size_t Size );
	void WriteJump( size_t OffsetFrom, size_t OffsetTo );

	const unsigned char* GetFilePointer();
	DWORD GetVirtualAddress( DWORD Offset );
};

#endif
cMutator.cpp

Code:
#include <fstream>
#include <vector>
#include <string>
#include <ctime>

#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>

#include <bea/BeaEngine.h>

#include <Windows.h>

#include "cMutator.h"

using namespace boost;

cDisassembler::cDisassembler()
{
	memset( (void*)&_Disasm, 0, sizeof( _Disasm ) );

	_Disasm.Options = NasmSyntax + PrefixedNumeral + ShowSegmentRegs;
}

void cDisassembler::Disassmble( const unsigned char* pCode, const size_t Size, vector< pair< size_t, string > > &Result )
{
	UInt64 EndCodeSection = (UInt64) ((DWORD) pCode + (DWORD) Size );

	Result.clear();

	memset( (void*)&_Disasm, 0, sizeof( _Disasm ) );

	_Disasm.Options = NasmSyntax + PrefixedNumeral + ShowSegmentRegs;
	_Disasm.EIP = (UInt64) pCode;

	while( true )
	{
		_Disasm.SecurityBlock = (UIntPtr)EndCodeSection - (UIntPtr)_Disasm.EIP;

		int Length = Disasm( &_Disasm );

		if ( Length == OUT_OF_BLOCK) 
			break;
		else if ( Length == UNKNOWN_OPCODE )
			throw "Error: cDisassembler::Dissasmble: UNKNOWN_OPCODE";
		else if( _Disasm.EIP >= EndCodeSection)
			break;
		else
		{
			char Number[16] = { NULL };
			sprintf_s( Number, 16, "%.8x", _Disasm.VirtualAddr );
			for( unsigned int i = 0; i < 16; i++ )
				if( Number[i] )
					Number[i] = (char)toupper( (int)Number[i] );

			string Temp = "lbl_0x";
			Temp += Number;
			Temp += ": ";
			Temp += _Disasm.CompleteInstr;

			Result.push_back( pair< size_t, string > ( _Disasm.EIP, Temp ) );

			_Disasm.EIP += Length;
			_Disasm.VirtualAddr += Length;
		}
	}
}

size_t cDisassembler::LengthDisassemble( const unsigned char* pCode, const size_t Size, const char LastInstruction )
{
	UInt64 EndCodeSection = (UInt64) ((DWORD) pCode + (DWORD) Size );

	memset( (void*)&_Disasm, 0, sizeof( _Disasm ) );

	_Disasm.Options = NasmSyntax + PrefixedNumeral + ShowSegmentRegs;
	_Disasm.EIP = (UInt64) pCode;

	size_t Result = 0;

	while( true )
	{
		if( LastInstruction != -1 )
			if( *(char*)_Disasm.EIP == LastInstruction )
				return Result;

		_Disasm.SecurityBlock = (UIntPtr)EndCodeSection - (UIntPtr)_Disasm.EIP;

		int Length = Disasm( &_Disasm );

		if ( Length == OUT_OF_BLOCK) 
			break;
		else if ( Length == UNKNOWN_OPCODE )
			throw "Error: cDisassembler::Dissasmble: UNKNOWN_OPCODE";
		else if( _Disasm.EIP >= EndCodeSection)
			break;
		else
		{
			Result += Length;
			_Disasm.EIP += Length;
			_Disasm.VirtualAddr += Length;
		}
	}

return Result;
}

cMutator::cMutator( int MutationIndex ) : _MutationIndex( MutationIndex )
{
	_AppendageIndex = 0;
	_Index = 0;

	_PtrPrefix.push_back( vector< string > () );

	srand( time( NULL ) );
}

string cMutator::GetRandomRegister( string NotOf )
{
	const string Registers[] =
	{
		"eax",
		"ecx",
		"edx",
		"ebx",
		"esi",
		"edi"
	};
	string Choice = "";

	do
	{
		Choice = Registers[ rand() % 5 ];
	}
	while( NotOf.find( Choice ) != std::string::npos );

	return Choice;
}

vector<string> cMutator::MutatePop( const vector< string > &Assembler )
{
	vector< string > Result;
	vector< string > Line;
	
	bool IsSrcPtr = false;

	string Type = "";

	split( Line,  Assembler[ _Index ], is_any_of(" ,"), token_compress_on );

	if( Line.size() > 3 )
	{
		if( Line[3][0] == '[' )
		{
			Type = Line[2];
			IsSrcPtr = true;
			Line.erase( Line.begin()+2 );
		}
	}
	// 0:lbl_xxx 1:pop 2:dest

	try
	{
		if( IsSrcPtr )
		{
			string SrcPart = (IsSrcPtr ? Type : "") + Line[2];

			string RandRegister = GetRandomRegister( Line[2] );
			Result.push_back( "push " + RandRegister );
			Result.push_back( "mov " + RandRegister + ",dword[esp+4]" ); 
			Result.push_back( "mov " + SrcPart + "," + RandRegister );
			Result.push_back( "pop " + RandRegister );
			Result.push_back( "add esp, 4 " );
		}
		else
		{
			// not an integer and not a poitner == register
			Result.push_back( "mov " + Line[2] + ",dword[esp]" );
			Result.push_back( "add esp, 4" );
		}
	}
	catch( ... )
	{
	
	}

	return Result;
}

vector<string> cMutator::MutatePush( const vector< string > &Assembler )
{
	vector< string > Result;
	vector< string > Line;

	bool IsSrcPtr = false;

	string Type = "";

	split( Line,  Assembler[ _Index ], is_any_of(" ,"), token_compress_on );

	if( Line.size() > 3 )
	{
		if( Line[3][0] == '[' )
		{
			Type = Line[2];
			IsSrcPtr = true;
			Line.erase( Line.begin()+2 );
		}
	}
	// 0:lbl_xxx 1:push 2:src

	try
	{
		if( rand() % 2 )
		{
			int ToAchieve = boost::lexical_cast< int > ( Line[2] );

			if( rand() % 2 )
			{
				// use +;
				int rInt1 = rand() % ToAchieve;
				int rInt2 = ToAchieve - rInt1;

				string DestPart = "dword[esp],";
				string SrcPart = (IsSrcPtr ? Type : "") + boost::lexical_cast< string > ( rInt1 );
				string SrcPart2 = (IsSrcPtr ? Type : "") + boost::lexical_cast< string > ( rInt2 );

				Result.push_back( "sub esp, 4" );
				Result.push_back( "mov " + DestPart + SrcPart );
				Result.push_back( "add " + DestPart + SrcPart2 );
			}
			else
			{
				// use - 
				int rInt1 = rand() % ToAchieve;
				int rInt2 = ToAchieve + rInt1;

				string DestPart = "dword[esp],";
				string SrcPart = (IsSrcPtr ? Type : "") + boost::lexical_cast< string > ( rInt1 );
				string SrcPart2 = (IsSrcPtr ? Type : "") + boost::lexical_cast< string > ( rInt2 );

				Result.push_back( "sub esp, 4" );
				Result.push_back( "mov " + DestPart + SrcPart2 );
				Result.push_back( "sub " + DestPart + SrcPart );
			}
		}
		else
		{
			if( IsSrcPtr )
			{
				string SrcPart = (IsSrcPtr ? Type : "") + Line[2];

				Result.push_back( "sub esp, 4" );
				Result.push_back( "push " + SrcPart );
				Result.push_back( "pop dword[esp]" );
			}
			else
			{
				// not an integer and not a poitner == register
				Result.push_back( "sub esp, 4" );
				Result.push_back( "mov dword[esp], " + Line[2] );
			}
		}
	}
	catch( ... )
	{
		if( IsSrcPtr )
		{
			string SrcPart = (IsSrcPtr ? Type : "") + Line[2];

			Result.push_back( "sub esp, 4" );
			Result.push_back( "push " + SrcPart );
			Result.push_back( "pop dword[esp]" );
		}
		else
		{
			// not an integer and not a poitner == register
			Result.push_back( "sub esp, 4" );
			Result.push_back( "mov dword[esp], " + Line[2] );
		}
	}

	return Result;
}


vector<string> cMutator::MutateMove( const vector< string > &Assembler )
{
	vector< string > Result;
	vector< string > Line;

	bool IsDestPtr = false;
	bool IsSrcPtr = false;

	string Type = "";

	split( Line,  Assembler[ _Index ], is_any_of(" ,"), token_compress_on );

	if( Line.size() > 4 )
	{
		if( Line[3][0] == '[' )
		{
			Type = Line[2];
			IsDestPtr = true;
			Line.erase( Line.begin()+2 );
		}
		else if( Line[4][0] == '[' )
		{
			Type = Line[3];
			IsSrcPtr = true;
			Line.erase( Line.begin()+3 );
		}
	}

	// 0:lbl_xxx 1:mov 2:dest 3:src == line 
	try
	{
		if( rand() % 2 )
		{
			int ToAchieve = boost::lexical_cast< int > ( Line[3] );

			if( rand() % 2 )
			{
				// use +;
				int rInt1 = rand() % ToAchieve;
				int rInt2 = ToAchieve - rInt1;

				string DestPart = (IsDestPtr ? Type : "") + Line[2] + ",";
				string SrcPart = (IsSrcPtr ? Type : "") + boost::lexical_cast< string > ( rInt1 );

				Result.push_back( "mov " + DestPart + SrcPart );
				Result.push_back( "add " + DestPart + SrcPart );
			}
			else
			{
				// use - 
				int rInt1 = rand() % ToAchieve;
				int rInt2 = ToAchieve + rInt1;

				string DestPart = (IsDestPtr ? Type : "") + Line[2] + ",";
				string SrcPart = (IsSrcPtr ? Type : "") + boost::lexical_cast< string > ( rInt2 );
				string SrcPart2 = (IsSrcPtr ? Type : "") + boost::lexical_cast< string > ( rInt1 );

				Result.push_back( "mov " + DestPart + SrcPart );
				Result.push_back( "sub " + DestPart + SrcPart2 );
			}
		}
		else
		{
			string SrcPart = (IsSrcPtr ? Type : "") + Line[3];
			string DestPart = (IsDestPtr ? Type : "") + Line[2];

			if( IsDestPtr && Type == "byte" )
				return Assembler;

			Result.push_back( "push " + SrcPart);
			Result.push_back( "pop " + DestPart);

			if( IsDestPtr && Type == "word" )
				Result.push_back( "add esp, 2" );
		}
	}
	catch( ... )
	{
			string SrcPart = (IsSrcPtr ? Type : "") + Line[3];
			string DestPart = (IsDestPtr ? Type : "") + Line[2];

			if( IsDestPtr && Type == "byte" )
				return Assembler;

			Result.push_back( "push " + SrcPart);
			Result.push_back( "pop " + DestPart);

			if( IsDestPtr && Type == "word" )
				Result.push_back( "add esp, 2" );
	}

	return Result;
}

void cMutator::Regswap( vector< string > &SectionDisassembly, const string &Reg1, const string &Reg2 )
{
	unsigned int Location = 0;
	unsigned int PrevLocation = 0;
	unsigned int NumChanges = 0;

	for( unsigned int i = 0; i < SectionDisassembly.size(); i++ )
	{
		NumChanges = 0;
		Location = 0;
		string CurrLine = SectionDisassembly[ i ];
		do
		{
			PrevLocation = Location;
			Location = CurrLine.find( Reg1, Location );
		
			if( Location != std::string::npos )
			{
				CurrLine.replace( Location, Reg2.size(), Reg2 );
				if( CurrLine.find( Reg2 ) == Location )
					Location += Reg2.size();
				else
					Location = 0;

				NumChanges++;
			}
			else
			{
				Location = PrevLocation;
				Location = CurrLine.find( Reg2, Location );

				if( Location != std::string::npos )
				{
					CurrLine.replace( Location, Reg1.size(), Reg1 );
					Location += Reg1.size();
					NumChanges++;
				}
			}
		}
		while( Location != std::string::npos && NumChanges <= 1 );
		SectionDisassembly[ i ] = CurrLine;
	}
}

void cMutator::Mutate( const vector< string > &Assembler, vector< string > &Result, vector< vector< string > > &Appendages )
{
	vector< string > NormalisedAssembler = Assembler; 
	int ApendageOffset = 0;

	const string Registers[] 
		=
	{
		"eax",
		"ecx",
		"edx",
		"ebx",
		"esi",
		"edi"
	};

	if( rand() % _MutationIndex )
	{
		int Res = rand();
		string Reg1 = Registers[ Res % 5 ];
		string Reg2 = Registers[ (Res + 1 * rand()) % 5 ];

		Regswap( NormalisedAssembler, Reg1, Reg2 );

		NormalisedAssembler.insert( NormalisedAssembler.begin(), "xchg " + Reg1 + "," + Reg2 );
		NormalisedAssembler.insert( NormalisedAssembler.end(), "xchg " + Reg1 + "," + Reg2 );
	}

	for( _Index = 0; _Index < NormalisedAssembler.size(); _Index++ )
	{
		if( NormalisedAssembler[_Index].find( "mov" ) != std::string::npos && rand() % _MutationIndex )
		{
			Appendages.push_back( MutateMove( NormalisedAssembler ) );
			Result.push_back( boost::lexical_cast< string > ( ApendageOffset++ ) );
		}
		else if( NormalisedAssembler[_Index].find( "push" ) != std::string::npos && rand() % _MutationIndex )
		{
			Appendages.push_back( MutatePush( NormalisedAssembler ) );
			Result.push_back( boost::lexical_cast< string > ( ApendageOffset++ ) );
		}
		else if( NormalisedAssembler[_Index].find( "pop" ) != std::string::npos && rand() % _MutationIndex )
		{
			Appendages.push_back( MutatePop( NormalisedAssembler ) );
			Result.push_back( boost::lexical_cast< string > ( ApendageOffset++ ) );
		}
		else
		{
			Result.push_back( NormalisedAssembler[ _Index ] );
		}
	}
}

string cMutator::VectorToFasmSource( const vector< string > &Source )
{
	string Result;

	for( unsigned int i = 0; i < Source.size(); i++ )
	{
		Result += Source[i] + "\r\n";
	}

	return Result;
}

cPE::cPE( const string& Filename )
	: _Filename( Filename )
{
	fstream f( _Filename, ios::in | ios::binary );

	if( f.good() )
	{
		f.seekg (0, f.end);
		_Size = f.tellg();
		f.seekg (0, f.beg);

		_File = new unsigned char [_Size];

		f.read ((char*)_File, _Size);
	}
	
	f.close();
}

cPE::~cPE()
{
	if( _File )
		delete [] _File;

	_File = NULL;
}

DWORD cPE::GetVirtualAddress( DWORD Offset )
{
	for( size_t i = 0; i < nt_headers->FileHeader.NumberOfSections; i++ )
	{
		PIMAGE_SECTION_HEADER CurrentSection = IMAGE_FIRST_SECTION(nt_headers) + i;
		if( CurrentSection->PointerToRawData <= Offset && Offset < CurrentSection->PointerToRawData + CurrentSection->SizeOfRawData )
		{
			return ( Offset - CurrentSection->PointerToRawData ) + CurrentSection->VirtualAddress + nt_headers->OptionalHeader.ImageBase;
		}
	}
return NULL;
}

DWORD cPE::AlignToBoundary(DWORD size, DWORD alignment)
{	
	DWORD alignedSize;
	if(size % alignment == 0)
		alignedSize = size;
	else
	{
		alignedSize = size / alignment;
		++alignedSize;
		alignedSize = alignedSize * alignment;
	}
return alignedSize;
}

PIMAGE_SECTION_HEADER cPE::AddSection(const char *section_name, size_t section_size, PIMAGE_DOS_HEADER pImageAddress, size_t &NewSize)
{
    dos_header = pImageAddress;

    if(dos_header->e_magic != 0x5A4D) 
	{
        throw "ERROR: cPE::AddSection: Could not retrieve DOS header from pImageAddress";
        return NULL;
    }

    nt_headers = (PIMAGE_NT_HEADERS)((DWORD_PTR)dos_header + dos_header->e_lfanew);

    if(nt_headers->OptionalHeader.Magic != 0x010B) 
	{
        throw "ERROR: cPE::AddSection: Could not retrieve NT header from PIMAGE_DOS_HEADER";
        return NULL;
    }

    const int name_max_length = 8;

    PIMAGE_SECTION_HEADER last_section = IMAGE_FIRST_SECTION(nt_headers) + (nt_headers->FileHeader.NumberOfSections - 1);
    PIMAGE_SECTION_HEADER new_section = IMAGE_FIRST_SECTION(nt_headers) + (nt_headers->FileHeader.NumberOfSections);

    memset(new_section, 0, sizeof(IMAGE_SECTION_HEADER));

    new_section->Characteristics = IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_CODE;

    memcpy(new_section->Name, section_name, name_max_length);

    new_section->Misc.VirtualSize = section_size;
    new_section->PointerToRawData = AlignToBoundary(last_section->PointerToRawData + last_section->SizeOfRawData, nt_headers->OptionalHeader.FileAlignment);
    new_section->SizeOfRawData = AlignToBoundary(section_size, nt_headers->OptionalHeader.SectionAlignment);
    new_section->VirtualAddress = AlignToBoundary(last_section->VirtualAddress + last_section->Misc.VirtualSize, nt_headers->OptionalHeader.SectionAlignment);

	NewSize = new_section->SizeOfRawData + ( new_section->PointerToRawData - last_section->PointerToRawData );

    nt_headers->OptionalHeader.SizeOfImage = new_section->VirtualAddress + new_section->Misc.VirtualSize;
    nt_headers->FileHeader.NumberOfSections++;

return new_section;
}

void cPE::ReloadHeaders()
{
    dos_header = (PIMAGE_DOS_HEADER)_File;

    if(dos_header->e_magic != 0x5A4D) 
	{
        throw "ERROR: cPE::AddSection: Could not retrieve DOS header from pImageAddress";
    }

    nt_headers = (PIMAGE_NT_HEADERS)((DWORD_PTR)dos_header + dos_header->e_lfanew);

    if(nt_headers->OptionalHeader.Magic != 0x010B) 
	{
        throw "ERROR: cPE::AddSection: Could not retrieve NT header from PIMAGE_DOS_HEADER";
    }

	NewSection = IMAGE_FIRST_SECTION(nt_headers) + (nt_headers->FileHeader.NumberOfSections-1);
}

void cPE::SaveFile()
{
	fstream f( _Filename +"-new.exe" , ios::out | ios::binary );

	if( f.good() )
	{
		f.write( (char*)_File, _Size );
	}
	else
	{
		 throw "ERROR: cPE::SaveFile: Failed to save new file";
	}

	f.close();
}

PIMAGE_SECTION_HEADER cPE::AddNewSection( unsigned int Size )
{
	size_t NewSize;

	NewSection = AddSection( "    ", Size, (PIMAGE_DOS_HEADER)_File, NewSize );
	if( NewSection == NULL )
		throw "ERROR: cPE::AddNewSection: Failed to add new section!";

	unsigned char* Temp = _File;
	_File = new unsigned char[ (_Size+NewSize) ];

	memset( _File, 0, (_Size+NewSize) );
	copy( Temp, Temp+_Size, _File );
	ReloadHeaders();

	delete [] Temp;

	_Size += NewSize;

	SaveFile();

	return NewSection;
}

PIMAGE_SECTION_HEADER cPE::GetExecutableSection( size_t Index )
{
	PIMAGE_SECTION_HEADER Result = NULL;
	size_t SectionsFound = 0;

	for( size_t i = 0; i < nt_headers->FileHeader.NumberOfSections; i++ )
	{
		PIMAGE_SECTION_HEADER CurrentSection = IMAGE_FIRST_SECTION(nt_headers) + i;

		if( ( CurrentSection->Characteristics & (IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE) ) == (IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE) )
		{
			SectionsFound++;	
		}
		else
			continue;

		if( SectionsFound == Index+1 )
			return CurrentSection;
	}
return NULL;
}

void cPE::Write( size_t Offset, unsigned char* Data, unsigned int Size )
{
	copy( Data, Data+Size, &_File[Offset] );
}

void cPE::WriteNop( size_t Offset, size_t Size )
{
	fill_n( _File+Offset, Size, 0x90 );
}

void cPE::WriteJump( size_t OffsetFrom, size_t OffsetTo )
{
	size_t vAddressFrom = GetVirtualAddress( OffsetFrom );
	size_t vAddressTo = GetVirtualAddress( OffsetTo );

	size_t Address = vAddressTo - vAddressFrom - 5;

	_File[ OffsetFrom ] = 0xE9;
	*(size_t*)&_File[ OffsetFrom + 1 ] = Address;
}

const unsigned char* cPE::GetFilePointer()
{
	return _File;
}
main.cpp:

Code:
#include <iostream>
#include <string>

#include <boost\lexical_cast.hpp>

#include "cMutator.h"

using namespace std;

int main( int argc, char* argv[] )
{	
	size_t SizeLeft = 0x4000, Offset = 0, Index = 0, BufferSize = 100000;
		
	cDisassembler		Disassembler;
	cMutator			Mutator;
	cFasmWrapper		Reassembler;

	cPE c("target.exe");

	PIMAGE_SECTION_HEADER pNewSection = c.AddNewSection( SizeLeft );
	PIMAGE_SECTION_HEADER pCurrentSection = NULL;

	vector< pair< size_t, string > >	Disassembly;
	vector< string >					Result;
	vector< vector< string > >			Appendages;

	while( ( pCurrentSection = c.GetExecutableSection( Index++ ) )/* && pCurrentSection != pNewSection */ )
	{
		Disassembly.clear();
		Result.clear();
		Appendages.clear();

		Disassembler.Disassmble( (const unsigned char*) c.GetFilePointer()+ pCurrentSection->PointerToRawData, pCurrentSection->SizeOfRawData, Disassembly );

		for( size_t y = 0; y < 1/*rand() % 128*/; y++ )
		{
			vector< string > PartDissassembly;
			bool Success = false;
			size_t RandBase, RandSize = 0;

			do
			{
				Success = true;
				PartDissassembly.clear();

				RandBase = 0; //rand() % (Disassembly.size() - 10);
				RandSize = 5; //rand() % ( 10 - 6 ) + 5;

				for( size_t Lines = 0; Lines < RandSize; Lines++ )
				{
					if( !RandSize
						||   Disassembly[ RandBase + Lines ].second.find( "j" ) != std::string::npos
						|| Disassembly[ RandBase + Lines ].second.find( "call" ) != std::string::npos
						|| Disassembly[ RandBase + Lines ].second.find( "ret" ) != std::string::npos
						)
					{
						Success = false;
						break;
					}

					PartDissassembly.push_back( Disassembly[ RandBase + Lines ].second );
				}	
			} while( !Success );

			c.WriteJump( Disassembly[ RandBase ].first - (size_t)c.GetFilePointer(), pNewSection->PointerToRawData + Offset );
			c.SaveFile();

			Mutator.Mutate( PartDissassembly, Result, Appendages );

			unsigned char* Buffer = new unsigned char[ BufferSize ];

			for( size_t i = 0; i < Result.size(); i++ )
			{
				memset( Buffer, 0x90, BufferSize );
				string Source;
				try
				{
					int AppendageIndex = boost::lexical_cast< int > ( Result[i] );

					Source = "use32\n" + Mutator.VectorToFasmSource( Appendages[ AppendageIndex ] );
				}
				catch( boost::bad_lexical_cast )
				{
					Source = "use32\n" + Result[i];
				}

				if( Reassembler.Assemble( Source, Buffer, BufferSize ) )
				{
					FASM_STATE* Ptr = (FASM_STATE*)Buffer;

					size_t NewCodeSize = Ptr->OutputLength;

					c.Write( pNewSection->PointerToRawData + Offset, (unsigned char*)Ptr->pOutput, NewCodeSize );
					
					Offset += NewCodeSize;
					SizeLeft -= NewCodeSize;
				}
				else
				{
					cout << "ERROR: main: Failed to assemble: " << Source << endl;
				}
			}

			delete [] Buffer;
						
			size_t NopSize = Disassembly[ RandBase + RandSize ].first - Disassembly[ RandBase ].first; 

			c.WriteJump( pNewSection->PointerToRawData + Offset, Disassembly[ RandBase ].first - (size_t)c.GetFilePointer() + NopSize );
			
			Offset += 5;
			SizeLeft -= 5;

			c.SaveFile();
		}
	}

	c.SaveFile();

return 0;
}
Damn. Great release. I'll definately try this out.
I nearly got lost in that assembly

Good job. +rep'd
Found some bugs tho

Old code might get jumped into, word mov's are not properly mutated. I'm currently debugging and checking where exactly it all goes wrong.
You have too much spare time.
Quote Originally Posted by Hell_Demon View Post
You have too much spare time.
What Wesley said. Nevertheless, great job as usual .::SCHiM::.
Nice, Cheer's!
Good job! This sets a high standard for the rest of the programming forum. Of course, there are some areas that this obfuscator could improve on, if you'd be willing to read some suggestions.

Quote Originally Posted by .::SCHiM::. View Post
What does this thing do?

I realize that this is not actually a metamorphic obfuscator, since the obfuscated executable will not be able to self-modify. However lacking a name for this (since it's unlike other obfuscators out there but like techniques used in metamorphic viruses) I named it thus.
This is a mistake. Metamorphic is an adjective that applies to viruses, which, to avoid detection by antivirus, behave like quines when they reproduce, only instead of exactly reproducing the machine code of itself, it produces something that is logically equivalent. This way, every copy of the virus is completely different -- this is genius, and hard, especially because this process also applies to the engine that conducts the process -- and antivirus programs can't simply look for a pattern to detect it. The similarity is that they both produce logically equivalent code.

The word you're looking for is self-modifying, and exclusively self modifying- not metamorphic.

Quote Originally Posted by .::SCHiM::. View Post
A normal obfuscator will "hide" the "real" code by surrounding it by superfluous instructions or encrypting them. The real code will still execute in it's normal form however, after the decryption or in between junk instructions. More advanced obfuscators will perhaps reassemble jump and call offsets to account for the increase in executable size, but that's about it.
Normal obfuscation will probably be creating logically equivalent instructions, too. And, yes, every tool used to obfuscate machine code will certainly correct jumps of any kind to be pointing to the correct addresses.

The idea of obfuscation is to create functionally equivalent code that is not only difficult for humans to read, but difficult for computers to create human-readable code from as well. Now, you have the first down, perhaps (I'll go into more detail on that later), but the second, not so much. In a program easily smaller than the one shown, you could substitute back the original mov, push, and pop instructions by looking for simple patterns.

I can't claim to be extremely knowledgeable, but some of the techniques are more obvious than others. One, for example, is creating chunks of dead memory that are unreachable at runtime. You can do this by putting a jmp instruction before it, and simply jumping over. A naive, linear-sweep based disassembler will screw up here, thinking those bits of memory are instructions (because it has no way of knowing otherwise). However, this is not your only, nor your worst adversary when it comes to obfuscation of code. Another method, recursive-traversal based disassembly, will attempt to follow your control structure and disassemble based upon that. Your dead-code jump technique will not fool these. A better way to screw with these disassemblers is to replace that jump with a logically equivalent conditional jump- one that always jumps, I mean. There's other things to try, too- call instructions that never return, self-modifying code, et cetera. Even smarter disassemblers will not be phased by this, either. They may not assume code after jumps will be valid, and use very complex algorithms (that are beyond my knowledge) to deduce the control flow of your executable. They may simplify bloated instructions, such as your mov/push/pop replacements, using algorithms that don't even make any assumptions about the substitutions your obfuscation process made.

Quote Originally Posted by .::SCHiM::. View Post
This code is different. This engine 'morphs' instructions to (a) different, but functionally identical, other instruction(s). This process is 100% recursive, so you could theoretically morph one instruction into thousands of others that all combine to be functionally equivalent to the original instruction.
While what you created does do the job (provided it fixes issues such as incorrect jump locations- I didn't check the code) in a sense, it only confuses humans, and the control flow can still be deduced absolutely by even a simple disassembler, and the instructions are 100% valid. Also, while mov/push/pop are common instructions, there are a lot more than just those three.

Quote Originally Posted by .::SCHiM::. View Post
Code:
xchg eax, ebx
sub esp, 4
mov dword[esp], eax
pop ebx
xchg eax, ebx
Now imagine doing this to an entire executable. It's size inflated a thousand times. It'll be impossible to distinguish where one functional instruction begins and where the next ends. Every new instruction will only complete 1000th of an old instruction, and yet you can't see or understand what exactly it's doing.
This might be an exaggeration of the extent to which that obfuscates the code.

Anyway, nice work, and it's certainly a step in the right direction. Good job!
Metamorphism doesn't just apply to viruses Shanks, SCHiM is correct in calling this a metamorphic obfuscator.
Quote Originally Posted by Hell_Demon View Post
Metamorphism doesn't just apply to viruses Shanks, SCHiM is correct in calling this a metamorphic obfuscator.
While it is true that other definitions of the word exist in the context of computer science, I'm fairly certain they are not what he is looking for. What meaning are you referring to?
Metamorphic code is code that when run outputs a logical equivalent version of its own code under some interpretation.

"The mutated code will do exactly the same thing (under the interpretation used), but the children's binary representation will typically be completely different from the parent's. Mutation can be achieved using techniques like inserting NOP instructions (brute force), changing what registers to use, changing flow control with jumps, changing machine instructions to equivalent ones or reordering independent instructions."

Since it is capable of obfuscating itself(the obfuscating code), it is metamorphic. If it wouldn't be able to obfuscate itself it would be considered polymorphic.
Quote Originally Posted by Hell_Demon View Post
Metamorphic code is code that when run outputs a logical equivalent version of its own code under some interpretation.

"The mutated code will do exactly the same thing (under the interpretation used), but the children's binary representation will typically be completely different from the parent's. Mutation can be achieved using techniques like inserting NOP instructions (brute force), changing what registers to use, changing flow control with jumps, changing machine instructions to equivalent ones or reordering independent instructions."

Since it is capable of obfuscating itself(the obfuscating code), it is metamorphic. If it wouldn't be able to obfuscate itself it would be considered polymorphic.
This is patently incorrect.

Is the OP's program a virus that reproduces itself? First, of all, no; though, this isn't the definition:
Quote Originally Posted by Wikipedia
Metamorphic code is code that when run outputs a logical equivalent version of its own code under some interpretation. This is similar to a quine, except that the quine's source code is exactly equivalent to its own output. Metamorphic code also usually output machine code and not its own source code.
"When run outputs a logical equivalent version of its own code"

Please note that this is almost exclusively applicable to viruses. Is the default behavior of this "experimental metamorphic obfuscator" to read itself and output a logically equivalent copy in an undefined manner? No, but even if it did that wouldn't be right either; part of the idea of a metamorphic program is that it doesn't read itself; AKA it is similar to a quine.

Even if it did that too, it still wouldn't be right, because the OP's program generates mangled executables that don't account for the offsets in addresses produced by blowing up the code to be multiple times larger, programs that will not work as intended unless someone carefully designed a executable filled with NOPs that would work.

And yet even if it did that too, it might trivially fit the definition, but it still wouldn't be right, because of yet another reason. Ask yourself- what would happen if you recursively ran said program multiple times? Each copy would increase in size by roughly a factor of four or so. Exponential growth of executable size is not sustainable in the slightest.

This is not metamorphic in any way, please review the definition and related concepts again.

#chek #urslef #b4 #u #rek #urslef #rekd
Well, it proves me that I don't know shits about programming.
Really great job, i wish I was that good.
something is wrong with the cFasmWrapper.cpp please check it up again
Metamorphic code is code that when run outputs a logical equivalent version of its own code under some interpretation.
Metamorphic code also usually output machine code and not its own source code.

Which is exactly what this code is capable of. It is able to create a logical equivalent of itself, and bind it into another program for example(its just not used for this purpose in the sample he posted)

Quote Originally Posted by Shanks_
Please note that this is almost exclusively applicable to viruses.
Just because other software doesn't really benefit from changing their own instructions into something equivalent doesn't mean the term metamorphism can only be applied to virusses.
Quote Originally Posted by Hell_Demon View Post
Metamorphic code is code that when run outputs a logical equivalent version of its own code under some interpretation.
Metamorphic code also usually output machine code and not its own source code.

Which is exactly what this code is capable of. It is able to create a logical equivalent of itself, and bind it into another program for example(its just not used for this purpose in the sample he posted)


Just because other software doesn't really benefit from changing their own instructions into something equivalent doesn't mean the term metamorphism can only be applied to virusses.
Could you please reread my post? This isn't even about viruses, necessarily. Your statements still rely on incorrect assumptions, including what is perhaps a fundamental misunderstanding of what a quine is, and a lack of understanding what a metamorphic program is, which does not include programs that read themselves to accomplish this due to the requirements of the definition. You did not address the numerous other arguments in my post as well.
Posts 1–15 of 17 · Page 1 of 2

Post a Reply

Similar Threads

Tags for this Thread

None

Need help?