Writing a Minimalistic “Hello, World!” Shellcode with Position Independent Code

I wanted to make a simple shellcode from a "Hello, World!" AMD64 assembly file which I wrote before. Here is the code:

It contains just two system calls (write and exit). Assembling and linking yields an ELF-64 executable which prints the "Hello, World!" message:

It contains both the code and data sections:

A shellcode payload, which will be embedded into a C file as a string, should not contain the data section. The message string should be moved into the code (.text) section.
While doing so, in order to prevent the moved data from execution,  a bridge with a relative jump instruction can be used. This code works fine when compiled with NASM:

The problem arises when the raw binary opcodes of this assembly code are put into a C file as a string. Due to some relocation procedure during execution of the shellcode binary, the address  of the message string "msg" points to an irrelevant location and the program prints nothing.

In order to overcome this problem I borrowed the idea of Position Independent Code (PIC) and I applied it here. In such scenario, all references to variables should be replaced with relative addressing. This means that all absolute addresses have to be recalculated with respect to the current value of the instruction pointer register RIP ("program counter"). NASM allows using [rel variable] macro to calculate the offset.  Overall, just one line should be changed in this code. Let's replace


Assembler will generate the following opcode:

So the final version of the assembly file which works both when compiled with NASM and when compiled via a wrapper C file:

Assembling and extracting opcodes is simple:

Embedding generated opcodes as a string into a wrapper C file:

Since it is a minimalistic shellcode, we don't need the "main" function and replace standard system start-up files with our code. In this case, the execution start from the "_start" function, which is the entry point of the executable. Compiling without including libc library and start-up files:

The string may be moved into the "_start" function

but two more compilation flags should be added in this case:

One last thing which should be mentioned is that shellcodes can be copied using "strcopy" function. This implies that the shellcodes cannot have null-bytes. Taking that into account, we can rewrite the above shellcode by using different instructions with no null-byte opcodes which perform the same function. The generated machine code is null-byte free:


Leave a Reply

Your email address will not be published. Required fields are marked *