/*
 * nologin
 *
 * http://www.nologin.org
 * ----------
 *
 * shellcode text encoding utility for 7bit shellcode.
 *
 * 04/06/2003
 * skape
 *
 * gcc -Wall -O3 encode.c -o encode
 *
 * Many props to the makers of this article:
 *
 * 	http://community.core-sdi.com/~juliano/bypass-msb.txt
 *
 * How to test:
 *
 * .text
 * .globl main
 *
 * main:
 *   push %ebp
 *   mov %esp, %ebp
 *   .ascii "text-encoded-shellcode-here"
 *   jmp %esp
 *   leave
 *   ret
 *
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

// The chunk size is the size of each individual 4 byte chunk converted to opcodes:
//
// sub $0x41414141, %eax    ; 5 bytes
// sub $0x41414141, %eax    ; 5 bytes
// sub $0x41414141, %eax    ; 5 bytes
// push %eax                ; 1 byte
#define CHUNK_SIZE 16        // 5 * 3 + 1

typedef struct _character {

	char              character;
	struct _character *next;

} CHARACTER;

typedef struct _character_enum {

	char          bucket;
	CHARACTER     *curr;

} CHARACTER_ENUM;

typedef struct _context {

	// Character set information
	CHARACTER     *characters[16];

	// Current counters
	unsigned long currentEax;

	// Output buffer
	char          *outputBuffer;
	unsigned long outputBufferLength;
	unsigned long outputBufferOffset;

	FILE          *shellCodeFile;
	unsigned long shellCodeFileSize;

	FILE          *outputFile;

} CONTEXT;

unsigned long encodeInitialize(CONTEXT *c, const char *characterSet);
unsigned long encodeProcess(CONTEXT *c);
unsigned long encodeCalculate(CONTEXT *c, unsigned long desired, unsigned long current, unsigned long numberSet[3]);
unsigned long encodeGetEax(CONTEXT *c, unsigned long initial, unsigned long *zero);

unsigned long characterSetAdd(CONTEXT *c, char character);
unsigned long characterSetEnumReset(CONTEXT *c, CHARACTER_ENUM *e);
unsigned long characterSetEnumNext(CONTEXT *c, CHARACTER_ENUM *e, char *letter);
unsigned long characterSetEnumNext4(CONTEXT *c, CHARACTER_ENUM *e, unsigned long *combo);
unsigned long characterSetIsValid(CONTEXT *c, char character);

int main(int argc, char **argv)
{
	CONTEXT c;
	const char defaultCharacterSet[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-%!#$^&";
	const char *characterSet = defaultCharacterSet;
	int let;

	memset(&c, 0, sizeof(c));

	if (argc == 1)
	{
		fprintf(stderr, "Usage: %s [-c character set] [-o output file] [-s raw_shellcode_file]\n", argv[0]);
		return 0;
	}

	while ((let = getopt(argc, argv, "c:o:s:")) != EOF)
	{
		switch (let)
		{
			case 'c':
				characterSet = optarg;
				break;
			case 'o':
				c.outputFile = fopen(optarg, "w");

				if (!c.outputFile)
				{
					fprintf(stderr, "Couldn't open '%s', using stdout...\n", optarg);
					c.outputFile = stdout;
				}
				break;
			case 's':
				{
					struct stat statbuf;

					c.shellCodeFile = fopen(optarg, "r");
	
					if (!c.shellCodeFile)
					{
						fprintf(stderr, "Couldn't open shellcode file, aborting.\n");
						return 0;
					}

					if (stat(optarg, &statbuf) < 0)
					{
						fprintf(stderr, "Couldn't stat shellcode file, aborting.\n");
						return 0;
					}

					c.shellCodeFileSize = statbuf.st_size;

					// Align it properly.
					c.shellCodeFileSize += (c.shellCodeFileSize % 4 != 0) ? 4 - (c.shellCodeFileSize % 4) : 0;
				}
				break;
			default:
				break;
		}
	}

	if ((!strchr(characterSet, '-')) || (!strchr(characterSet, 'P')))
	{
		fprintf(stderr, "Missing required control characters from character set [-,P,%%]\n");
		return 0;
	}

	encodeInitialize(&c, characterSet);

	return 1;
}

unsigned long encodeInitialize(CONTEXT *c, const char *characterSet)
{
	CHARACTER_ENUM e;
	unsigned long ret = 0;
	const char *curr = characterSet;
	unsigned long initial = 0, zero = 0;

	// Initialize
	while (curr && *curr)
	{
		characterSetAdd(c, *curr);
		curr++;
	}

	do
	{
		characterSetEnumReset(c, &e);

		if (!characterSetEnumNext4(c, &e, &initial))
			break;

		if (!encodeGetEax(c, initial, &zero))
		{
			fprintf(stderr, "Could not get eax to zero with current character set.\n");
			break;
		}

		// Current eax is always initialized to initial eax.
		c->currentEax   = 0;
		
		// Initialize our output buffer.
		c->outputBufferLength = 10 + ((c->shellCodeFileSize / 4) * CHUNK_SIZE);
		c->outputBuffer       = (char *)malloc(c->outputBufferLength + 1);

		sprintf(c->outputBuffer, "%%%.4s%%%.4s", (char *)&initial, (char *)&zero);

		// Move along and process.
		ret = encodeProcess(c);

	} while (0);

	if (c->outputFile != stdout)
		fclose(c->outputFile);

	return ret;
}

unsigned long encodeProcess(CONTEXT *c)
{
	unsigned char *raw = (unsigned char *)malloc(c->shellCodeFileSize);
	unsigned long numberSet[3], ret = 1;
	char          chunk[CHUNK_SIZE + 1];
	unsigned long *curr;

	numberSet[0] = numberSet[1] = numberSet[2] = 0;

	memset(raw, 0x90, c->shellCodeFileSize);

	if (!fread(raw, 1, c->shellCodeFileSize, c->shellCodeFile))
		return 0;

	c->outputBufferOffset = 10;

	for (curr = (unsigned long *)(raw + c->shellCodeFileSize - 4); 
			curr != (unsigned long *)(raw - 4); 
			curr--, c->outputBufferOffset += CHUNK_SIZE)
	{
		if (!encodeCalculate(c, *curr, c->currentEax, numberSet))
		{
			fprintf(stderr, "Character set is not sufficient enough to generate encoded buffer.\n");

			ret = 0;

			break;
		}

		// Write the chunk to the output buffer
		sprintf(chunk, "-%.4s-%.4s-%.4sP", (char *)&numberSet[0], (char *)&numberSet[1], (char *)&numberSet[2]);

		memcpy(c->outputBuffer + c->outputBufferOffset, chunk, sizeof(chunk) - 1);
	}

	// Null terminate our string.
	c->outputBuffer[c->outputBufferLength] = 0;

	fprintf(c->outputFile, "%s\n", c->outputBuffer);

	return ret;
}

unsigned long encodeCalculate(CONTEXT *c, unsigned long desired, unsigned long current, unsigned long numberSet[3])
{
	CHARACTER_ENUM first, second, third;
	unsigned long  bit = 0, currentByte = 0, desiredByte = 0;
	unsigned long  rollover, out, actual, carry = 0, final = 0;

	numberSet[0] = numberSet[1] = numberSet[2] = 0;

	for (bit = 0; bit < 4; bit++)
	{
		currentByte = (current) >> (bit * 8) & 0xff;
		desiredByte = (desired) >> (bit * 8) & 0xff;
		out         = 0;

		for (characterSetEnumReset(c, &first);
				!out && characterSetEnumNext(c, &first, NULL);
				)
		{
			for (characterSetEnumReset(c, &second);
					!out && characterSetEnumNext(c, &second, NULL);
					)
			{
				for (characterSetEnumReset(c, &third);
						!out && characterSetEnumNext(c, &third, NULL);
					   )
				{
					rollover  = first.curr->character;
					rollover += second.curr->character;
					rollover += third.curr->character;
					rollover += desiredByte;
					rollover += carry;

					actual    = (rollover & 0xff);

					if (actual == currentByte)
					{
						numberSet[0] += first.curr->character << (bit * 8);
						numberSet[1] += second.curr->character << (bit * 8);
						numberSet[2] += third.curr->character << (bit * 8);

						carry = (rollover & 0xff00) >> 8;
						out   = 1;
					}
				}
			}
		}
	}

	final = c->currentEax - numberSet[0] - numberSet[1] - numberSet[2];

	if (final != desired)
		return 0;

	c->currentEax = final;

	return 1;
}

unsigned long encodeGetEax(CONTEXT *c, unsigned long initial, unsigned long *zero)
{
	CHARACTER_ENUM bit1, bit2, bit3, bit4;
	unsigned long out = 0;

	for (characterSetEnumReset(c, &bit1);
			!out && characterSetEnumNext(c, &bit1, NULL);
			)
	{
		for (characterSetEnumReset(c, &bit2);
				!out && characterSetEnumNext(c, &bit2, NULL);
				)
		{
			for (characterSetEnumReset(c, &bit3);
					!out && characterSetEnumNext(c, &bit3, NULL);
					)
			{
				for (characterSetEnumReset(c, &bit4);
						!out && characterSetEnumNext(c, &bit4, NULL);
					   )
				{
					*zero  = bit1.curr->character & 0xff;
					*zero += (bit2.curr->character <<  8) & 0x0000ff00;
					*zero += (bit3.curr->character << 16) & 0x00ff0000;
					*zero += (bit4.curr->character << 24) & 0xff000000;

					if ((initial & *zero) == 0)
						out = 1;
				}
			}
		}
	}

	return out;
}

unsigned long characterSetAdd(CONTEXT *c, char character)
{
	CHARACTER *curr = (CHARACTER *)malloc(sizeof(CHARACTER)), *temp;
	char bucket = (character & 0xf0) >> 4;

	if (!(temp = c->characters[(int)bucket]))
		c->characters[(int)bucket] = curr;
	else
	{
		while (temp->next)
			temp = temp->next;

		temp->next = curr;
	}

	curr->character = character;
	curr->next      = NULL;

	return 1;
}

unsigned long characterSetEnumReset(CONTEXT *c, CHARACTER_ENUM *e)
{
	e->bucket    = 0;
	e->curr      = NULL;

	return 1;
}

unsigned long characterSetEnumNext(CONTEXT *c, CHARACTER_ENUM *e, char *letter)
{
	unsigned long ret = 0;

	do
	{
		// We reached the last bucket.
		if (e->bucket >= 16)
			break;
	
		// If we have a valid current position, go to the next one.
		if (e->curr)
			e->curr = e->curr->next;
	
		while (e->bucket < 15 && e->curr == NULL)
			e->curr = c->characters[(int)++e->bucket];		
	
		if (!e->curr)
			break;

		// Success.
		if (letter)
			*letter = e->curr->character;

		ret     = 1;

	} while (0);

	return ret;
}

unsigned long characterSetEnumNext4(CONTEXT *c, CHARACTER_ENUM *e, unsigned long *combo)
{
	unsigned long index = 0;
	char letter = 0;

	*combo = 0;

	for (index = 0; index < 4 && characterSetEnumNext(c, e, &letter); index++)
		*combo += (letter & 0xff) << (8 * index);

	return 1;
}

unsigned long characterSetIsValid(CONTEXT *c, char character)
{
	CHARACTER_ENUM e;
	unsigned long ret = 0;

	for (characterSetEnumReset(c, &e);
			!ret && characterSetEnumNext(c, &e, NULL);
		   )
	{
		if (e.curr->character == character)
			ret = 1;
	}

	return ret;
}

