Basic shellcoding for linux on x86
Beginning
Writing shellcode is an excellent way to learn more about assembly language and how a program communicates with the underlying OS. Put simply shellcode is code that is injected into a running program to make it do something it was not made to do. Normally this is to spawn a shell, but any code made to run after a bug in a program is exploited counts as shellcode.
Before you begin writing shellcode it is a good idea to read a few tutorials on writing assembly programs. A good reference would be tutorial points. To compile the assembly code for this tutorial I used nasm. To make the process of compiling the shellcode and extracting the op codes easier I have included a makefile to aid in the process.
Hello world
Lets begin with a shellcode that prints out to the screen hello world.
Here is the end shellcode. Save it in a file named shellcode.asm
.
The make file is as follows
To compile this shellcode run make all
then run ./shellcode
. You should see Hello world outputted to the screen.
This is a shellcode that writes hello world. We start out by XORing eax to zero out the register. We then push eax onto the stack as a null byte. Then we push hello
world onto the stack. Hello world is pushed onto the stack in reverse because x86 is little endian. Next comes the part that makes the shellcode a little more involved.
When we move hex 0x1 into what would normally be the ebx we instead use bl. We are using the 8 bit register portion of ebx so we do not have null bytes in our shellcode.
Why wouldn’t we want null bytes in our shellcode? The reason, put simply, is functions like strncpy()
will stop copping a string when they reach a nullbyte. This would result in our shellcode being cut off and not being executed
correctly. We then copy the address of hello world into ecx and the length of our shellcode into dl. After this we move 0x4 into al. This sets the syscall we are using to the write syscall. We then use int 0x80 which tells the
kernel to call our syscall. After this we set al to 0x1(The exit syscall) which we then use int 0x80 again to tell the kernel we want this process to be “exited”. If you are confused don’t worry I will explain in the upcoming
section.
Syscalls, op-codes, and registers. Oh my (featuring the stack)
Syscalls
In the explanation of the hello world shellcode above you may have been wondering what a syscall is. A syscall is a way for a process to communicate with the underlying operating system. This makes it easier for programmers to say
write to a file or change the permissions of a file. Instead of having to spend time implementing their own solution programmers were able to relay on the operating system to handle certain tasks. Syscalls are called in x86 assembly by setting the eax register to the syscall number. The syscall number is just a number that is associated with a certain syscall. For example the syscall sys_exit
has the hex value of 0x1.
Syscalls are used in shellcode because the process dose not have to find and load in a shared object or have statically linked code to obtain functionality outside of the program. Syscalls are always there for our shellcode to call. In the hello world shellcode
I use two syscalls of interest sys_write
and sys_exit
. sys_write
writes a string to a file descriptor(in our case 1 for stdout) and sys_exit
simply “exits” the program like exit();
in c.
A great reference for syscalls on linux and their corresponding numbers can be found here.
Opcodes
Lets talk about op-codes. Op-codes are the hexadecimal representation of the instructions that we write in assembly. You can extract the opcode for our shell code using the make raw
command. This is just a recipe inside of the make file I added to make the process easier to understand.
The op-codes that are extracted are the final payload that gets sent to a target that is being exploited. In shellcode you will notice that (for the most part) you will never see 0x00 in them. 0x00 is a null byte and null bytes in shellcode can lead to unreliable shellcode because
shellcode with null bytes might have opcodes cut off by functions like strcpy()
. If our shellcode has null bytes and is cut off before the ending it could lose crucial functionality. This brings us to our next section.
Registers
Now to talk about registers. Registers are essentially tiny variables that exist on the cpu. They can be used to store data or addresses that point to data.
On x86 there are 7 general purpose registers. Of that 7 only 4 are normally used by the programmer(ESP, EBP and ESI have their own special uses). The other 4 are EAX, EBX, ECX, and EDX. Each one can store 32 bits(or 4 bytes) of data. Each of those registers has three smaller registers that can be used to access the lower bits
of the registers. For example the EAX register has AX, AH, and AL. AX is used to access the lower 16 bits of EAX. AL is used to access the lower 8 bits of EAX and AH is used to access the higher 8 bits.
So why is this important for writing shellcode? Remember back to why null bytes are a bad thing. Using the smaller portions of a register allow us to use mov al, 0x1
and not produce a null byte.
If we would have done mov eax, 0x1
it would have produced null bytes in our shellcode. EBP, ESP and EIP are each used for a special purpose. EBP is used to point to the base of the stack(explained below), ESP is used to point to the top of the stack(also explained below) and EIP is the instruction pointer. The instruction pointer just points to the address of the next instruction to be executed.
The stack
The stack is a portion of memory that programmers can use to store large amounts of data. When a programmer wants to put data onto the stack they use the push <data>
instruction. If they want to retrieve data from the stack they would use the pop <dest>
instruction. The stack is a first in last out(FILO) data structure. A simple way of visualizing this is to think of a pile of books.
The books on bottom of the pile where placed there first. To get to the book on the bottom of the pile of books you would have to take off the books on top of it. The base of the stack(most recent thing that is pushed on to the stack) is pointed to by the address ebp and the top of the stack is pointed to by ESP. In our hello world shellcode we can see the instruction mov ecx,esp
. Here we are copying the address of the top of the stack into ECX. If you look at the push instructions we push the newline character then d on to the stack first.
This is because of the Endienness of x86 and the orientation of the stack. You still maybe wondering why it is that the stack is used in shellcode to store data. The reason is that shellcode do not have access to the data section that normal assembly programs would have. To be able to have our own data we use the push
instruction along with the hexadecimal representation of our characters to store data that would need to be used by our shellcode.
Putting it all together
Okay so now that we have a hold on how to write shellcode. Lets write a shell code that calls sys_execve
to run /bin/sh
. So here is the assembly code.
Save this code into shellcode.asm
and then use make all
to compile it. To test the shellcode you can run ./shellcode
like before. You might wonder why we are using /bin//sh
instead of /bin/sh
. We use /bin//sh
because we want our push - es to have a number divisible by 4 so we can push our data on the stack with out null bytes.
We then use ebx to point to our shellcode. After that we set the args to null and the number of args to null because we are calling /bin//sh without any arguments. Then after that we set al to hex 11 and finish off with an int 0x80
to run our shellcode.
Useful links
I am a firm believer that the more sources of knowledge that one person has at their fingers makes it easier to learn. So here is a list of excellent tutorials other than mine to continue or reaffirm your shellcoding journey.
- 0x00sec a different x86 linux shellcoding tutorial.
- Exploit db Exploitdb’s tutorial on linux shellcoding. Nice visuals and talks more about the commands I use in
make raw
.
All the shellcode will be up on my github. Thanks for reading and as always happy hacking.