Klaus' Log

Fr 13 Januar 2017

A simple, yet complete shellcode loader for linux x64 shellcode - Part 1

Posted by Klaus Eisentraut in assembly   

This is a series consisting of Part 1, Part 2.

Introduction

Since a long time, I always wanted to write a simple encrypted shellcode loader which can encode known shellcode like meterpreter and evades anti-virus. Of course, there are already many more feature-complete crypters like UPX and Veil, but it's more fun to write one for fun by yourself, isn't it? Furthermore, the publicly known ones are often detected by anti-virus, too.

It's actually not hard and I did it for fun on two evenings around a year ago, but never made it public. Since I now have a blog, I can publish my crypter in multiple parts.

Hello World Linux x64 shellcode

First of all, we have to start with a simple example shellcode, which we can encrypt later. So we write the following "Hello World"-Shellcode for x64 which was inspired by this shellcode for x86. For a 64bit version, we only need to change the 32 bit registers like (eax, ebx, ...) to the corresponding 64bit ones (rax, rdi, ...).

global _start

section .text

_start:
    jmp msg     ; jmp to shellcode

goback:
    mov rax, 1  ; sys_write
    mov rdi, 1  ; stdout
    pop rsi     ; pop address of msg
    mov rdx, 12 ; length of string
    syscall

    mov rax, 60 ; sys_exit
    mov rdi, 0  ; exitcode
    syscall

msg:
    call goback
    db "Hello World",0x0a

We assemble this shellcode and dump it with objdump:

$ nasm -f elf64 -o helloworld helloworld.nasm
$ objdump -S -M intel helloworld

helloworld.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <_start>:
   0:   eb 1e                   jmp    20 <msg>

0000000000000002 <goback>:
   2:   b8 01 00 00 00          mov    eax,0x1
   7:   bf 01 00 00 00          mov    edi,0x1
   c:   5e                      pop    rsi
   d:   ba 0c 00 00 00          mov    edx,0xc
  12:   0f 05                   syscall 
  14:   b8 3c 00 00 00          mov    eax,0x3c
  19:   bf 00 00 00 00          mov    edi,0x0
  1e:   0f 05                   syscall 

0000000000000020 <msg>:
  20:   e8 dd ff ff ff          call   2 <goback>
  25:   48                      rex.W
  26:   65 6c                   gs ins BYTE PTR es:[rdi],dx
  28:   6c                      ins    BYTE PTR es:[rdi],dx
  29:   6f                      outs   dx,DWORD PTR ds:[rsi]
  2a:   20 57 6f                and    BYTE PTR [rdi+0x6f],dl
  2d:   72 6c                   jb     9b <msg+0x7b>
  2f:   64                      fs
  30:   0a                      .byte 0xa

objdump interprets the string "Hello world\n" as instructions (starting at byte 25), too, but this doesn't bother us at all. We dump the raw shellcode to a file:

$ objcopy -O binary helloworld helloworld.raw
$ hexdump -C helloworld.raw
00000000  eb 1e b8 01 00 00 00 bf  01 00 00 00 5e ba 0c 00  |............^...|
00000010  00 00 0f 05 b8 3c 00 00  00 bf 00 00 00 00 0f 05  |.....<..........|
00000020  e8 dd ff ff ff 48 65 6c  6c 6f 20 57 6f 72 6c 64  |.....Hello World|
00000030  0a                                                |.|
00000031

Finally, we use xxd convert this shellcode to C format:

$ xxd -i helloworld.raw 
unsigned char helloworld_raw[] = {
  0xeb, 0x1e, 0xb8, 0x01, 0x00, 0x00, 0x00, 0xbf, 0x01, 0x00, 0x00, 0x00,
  0x5e, 0xba, 0x0c, 0x00, 0x00, 0x00, 0x0f, 0x05, 0xb8, 0x3c, 0x00, 0x00,
  0x00, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x05, 0xe8, 0xdd, 0xff, 0xff,
  0xff, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64,
  0x0a
};
unsigned int helloworld_raw_len = 49;

Now we are able to test our code by packing it into a simple C loader:

#include<stdio.h>
#include<stdlib.h>

unsigned char helloworld_raw[] = {
  0xeb, 0x1e, 0xb8, 0x01, 0x00, 0x00, 0x00, 0xbf, 0x01, 0x00, 0x00, 0x00,
  0x5e, 0xba, 0x0c, 0x00, 0x00, 0x00, 0x0f, 0x05, 0xb8, 0x3c, 0x00, 0x00,
  0x00, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x05, 0xe8, 0xdd, 0xff, 0xff,
  0xff, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64,
  0x0a
};
unsigned int helloworld_raw_len = 49;


int main(int argc, char* argv[])
{
    int (*s)() = (int(*)()) helloworld_raw;
    s();
    return 0;
}

However, our first naive try fails.

$ gcc -o helloworld1 ./helloworld1.c
$ ./helloworld1
Segmentation fault (core dumped)
$ dmesg | tail -n1
[  488.862803] helloworld1[1265]: segfault at 561afc6ad040 ip 0000561afc6ad040 sp 00007ffc223c80a8 error 15 in helloworld1[561afc6ad000+1000]

Why? The array helloworld_raw is of type unsigned char[] and thus which will be created on the stack, but the stack is non-executable on modern Linux systems. This is a protection measure called "Data Execution Protection" (DEP).

Working around DEP

For now, we have two possible simple solutions to work around the Data Execution Policy resp. the non-executable stack.

First, we can declare our shellcode as string:

char *helloworld_raw = "\xeb\x1e\xb8\x01\x00\x00\x00\xbf\x01\x00\x00\x00\x5e\xba\x0c\x00\x00\x00\x0f\x05\xb8\x3c\x00\x00\x00\xbf\x00\x00\x00\x00\x0f\x05\xe8\xdd\xff\xff\xff\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x0a";

This will trigger the compiler to store our shellcode as a string in the executable itself. The memory region which maps the executable itself must (of course) be executable, so we are not blocked and it works.

Another workaround is that we can declare it as a global variable of type const unsigned char*:

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

const unsigned char helloworld_raw[] = {
  0xeb, 0x1e, 0xb8, 0x01, 0x00, 0x00, 0x00, 0xbf, 0x01, 0x00, 0x00, 0x00,
  0x5e, 0xba, 0x0c, 0x00, 0x00, 0x00, 0x0f, 0x05, 0xb8, 0x3c, 0x00, 0x00,
  0x00, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x05, 0xe8, 0xdd, 0xff, 0xff,
  0xff, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64,
  0x0a
};

int main(int argc, char* argv[])
{
        int (*s)() = (int(*)()) helloworld_raw;
        s();
        return 0;
}

However there is a drawback: It only works as long as helloworld_raw is a global variable. Only global const variables are stored in the executable itself. If we declare helloworld_raw locally -- for instance in the main function -- the array will again be created on the stack and our shellcode will not be executable.

So far, so good. We wrote an example shellcode and are able to run it, but we didn't do any obfuscation of the shellcode yet. If we use this for a known bad shellcode like meterpreter, most antivirus solution will still be able to detect it.

In part 2, we will cover a simple XOR-obfuscation of our shellcode.