0x01 SLAE - Bind TCP
Introduction
The first assignment for the Shellcoding Linux Assembly Language x86 is a Shell Bind TCP in shellcode. Before having a working shellcode, I have taken different steps to understand better the structure and how assembly work.
Foremost, I have developped a working tcp bind shell in C language, since C is the closest programming language before assembly. Then, I translated the C code in assembly. I divided each section by syscall. Finally, I created a simple python script that generate our shellcode from a custom port.
C Program
First thing first, we have to open a socket using the socket function in C. We also need to initialize our parameter for the ip and port that we want to open the socket. Since, it's a bind tcp, we are listening for remote connection. So, we will use the local ip.
Create a socket
In the following block of code, we are initializing arguments and creating a socket from the function socket in C. The first argument AF_INET
is the domain which mean that we are using IPv4. The second argument is the type of communication. Here we are using TCP and this correspond to SOCK_STREAM
. The last argument correspond to the protocol value. Thus, we are using 0 as IP protocol.
Bind the socket
The next step is to bind the socket that use the ip address and the port. The first argument is the return value of the socket creation. The &bind_addr
is the struct of the value that we have initialize upper. The last argument is the size of the struct.
Listen and accept connection
In this step, we need to use the function listen
for listening incoming connection and put it in a queue. The listen function takes 2 arguments. The first one is the return value of the socket function. The last argument specify the maximum length of the queue. Since, we don't want more than 1 connection, we can put this value at 0.
The next step is to accept connection. We will use the accept function with three arguments. The first one is the return value of the socket. The second and third one can be set at NULL because we don't need it.
Redirect stdin, stdout and stderr
Indeed, we will need to redirect the input, output and error of the remote shell. To do this, we will use the dup2 function which duplicate the file descriptor.
0 : stdin
1 : stdout
2 : stderr
Open a shell
The last system call is execve which can execute a program. In the following line of code, we are calling /bin/sh
to open a shell to the client.
The final C program
Assembly version
The assembly version is more complex when you start coding assembly. I have divided each function call by section as I did for the C prototype.
Create a socket
Creating a socket in assembly is a bit different since the most compatible syscall for using socket is socketcall
. Indeed, from the man page of socketcall:
On x86-32, socketcall() was historically the only entry point for the sockets API. However, starting in Linux 4.3, direct system calls are pro‐ vided on x86-32 for the sockets API.
Thus, to make sure that our shellcode is compatible with every linux kernel, I will stay with the syscall socketcall.
Before creating our socket, we will clean our registers to make sure they are null and we can use it later.
Then, we need to translated the C version in assembly. As I learned in this SLAE, to do a syscall we need to get the syscall # of the socketcall from /usr/include/x86_64-linux-gnu/asm/unistd_32.h
.
In that way, we can write our first line of assembly which will contain the integer value 102. Also, it is important to store the value in the 8 bytes registers to avoid null byte. This is why we will store it in al
.
Then, socketcall can be aslo used to bind, listen and accept. So, we need ot specify which type of call we want to use. From the definition :
From the file /usr/include/linux/net.h
, we can see that the value for the SYS_SOCKET
is 1. Thus, our next register will contain that value to indicate that we want to create a socket.
The third line of assembly will push our 3 arguments to the stack (i.e : AF_INET, SOCK_STREAM, 0). Since, the stack work from first in last out, we need to push our last argument in first. We also cleaned our register at the start. In this way, the register ecx already contain 0. The push ebx
, will push the argument SOCK_STREAM
. Since the value of SOCK_STREAM
from the definition is 0, we can push ebx straight because the current value is 0. The final push is the value 2 because the value of AF_INET is 2 from the definition in the file /usr/include/x86_64-linux-gnu/bits/socket.h
. Finally, we can move the 3 arguments value in the stack in esp and do the syscall. We also need to store the return value of the socket because we will use it later.
Bind the socket
Before binding the socket, I initialize the structure value for the bind_addr
. To do this, I pushed to the stack the following :
The first line is pushing 0 to the stack because the value for INADDR_ANY
from the definition is 0. Then, hex(8888)
is 0x22b8
, we push it to the stack. We also see earlier that AF_INET
, so we push 2 to the stack. And finally, we move our 3 arguments from the stack in ecx because we will use theses arguments later.
The next step is to do the bind syscall. We will start by using socketcall with SYS_BIND
. The value of SYS_BIND
is 2. Then, we push 0x10
which is 16 bytes because we have 3 arguments upper (8+8+8). We aslo push ecx which contains our 3 arguments struct. Then, we push edi which contains the return value of the creation of the socket.
Listen and accept connection
The listen and accept is pretty straight forward. We will also use the socketcall syscall function. However, we will use SYS_LISTEN
and SYS_ACCEPT
. We will push our arguments to the stack and save the return value of the accept call.
Redirect stdin, stdout and stderr
For this syscall, we will use the dup2 call which the value is 63
. We only need to push 2 arguments. The last one is the stdin for the first call. The first argument is the return value of the accept call which is stored in ebx.
And the two others dup2 call for stdout and stderr.
Open a shell
Finally, we have our execve syscall from the SLAE documentation.
The final assembly version
We can see in the following image that we can successfully execute remote command from the remote shell.
The configurable port number script
Before running the script, we need to set our desired port in the script in hex as it is described in the comment. Then, we can run the shellcode by running the following python script.
Conclusion
I hope you enjoy this blog post. I have to mention that strace
and gdb
has been really useful to debug my assembly program. If you never had the chance to play with, I highly suggest to play with it.
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert Certification
http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student ID: SLAE-1374
Last updated
Was this helpful?