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.