This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert
Student-ID: SLAE-1154
In contrast with the shellcodes in shell-storm.org and exploit-db.com, this one is using the well known socket functions. According to socketcall(2) these are also available as system calls since Linux version 4.3.
I’ve also decided not to call setsockopt(2) to reuse the port and relied on some implementation-specific behaviour to keep the shellcode smaller.
To perform a network I/O operation a process needs to first call socket(2) by specifying the desired protocol and to obtain a file descriptor:
socket(2) prototype
1
intsocket(intdomain,inttype,intprotocol);
The domain (also known as family) is implementation-defined, but most of the time it is one of:
All of the asm- auxiliary shell scripts used throughout this series that are under the bin directory, should obviously be in the PATH environment variable. With this in mind the registers will contain the following before and after the system call:
The bind shell will use the AF_INET family with a SOCK_STREAM type. Beware that not all combinations of family and type are valid. Then, one needs to use bind(2) to assign a local protocol address to the socket:
The socket address structure, in this case, has to be manually packed and will be IPv4 specific. Nowadays, one should use getaddrinfo(3) and sockaddr_storage which is large enough for any socket structure, instead:
Per the UNP book, it isn’t mandatory to choose a port nor an address to bind to. In that case, the kernel picks an ephemeral port and uses the destination IP address of the client’s SYN as the server’s source IP address.
IPv4 socket structure
12345678910
structin_addr{in_addr_ts_addr;/* 32-bit IPv4 address */};/* used to be a union on 4.2BSD */structsockaddr_in{sa_family_tsin_family;/* 16-bit family (AF_INET) */in_port_tsin_port;/* 16-bit port number */structin_addrsin_addr;/* 32-bit address */charsin_zero[8];/* unused */};
According to Stevens, the sin_zero member was added so that all socket address structures are at least 16 bytes in size and isn’t really required when it is going to be used with the wildcard address.
The socket structures are casted to the generic socket address structure, due to the functions having to deal with a multitude of protocol families (IPv4, IPv6, Unix and datalink, for instance). One could have used void from ANSI C instead of the generic structure, however that was non-existent back in 1982.
IPv6 socket structure
1234567891011
structin6_addr{uint8_ts6_addr[16];/* 128-bit IPv6 address */};structsockaddr_in6{sa_family_tsin6_family;/* 16-bit family (AF_INET6) */in_port_tsin6_port;/* 16-bit port number */uint32_tsin6_flowinfo;/* 32-bit flow information */structin6_addrsin6_addr;/* 128-bit address */uint32_tsin6_scope_id;/* 32-bit scope ID */};
The important part here is that the family struct members match, so that it can be freely accessed when casted to the generic socket structure.
This is the point where setsockopt(2) could be used to prevent the address is already in use error, which might occur if there are still previously established connections lying around.
For the listen(2) system call, according to SUSv4 a backlog argument of 0 may allow the socket to accept connections, in which case the length of the listen queue may be set to an implementation-defined minimum value. This works if SYN cookies are enabled, which is usually the case with recent versions.
Next and finally in the flow is the accept4(2) call which is used to return a completed connection from the queue, if any. On success the return value is a new file descriptor created by the kernel to be used with the recently established connection:
The second and third arguments are used by the kernel to fill the address structure of the newly connected peer. According to the manual page these aren’t really needed and can be both NULL.
For the execve(2) system call there’s no need to specify argv and envp, since according to the manual page on Linux, argv and envp can be specified as NULL. This is obviously non-standard and might result in an error on other systems.
The standard input, output and error file descriptors need to be redirected with dup2(2) beforehand to the recently accept4(2) file descriptor in order to have interaction with the created shell.
Running the shellcode is only a matter of configuring the desired listening port with the optional -l command-line argument (4242/tcp by default) and running make. This process also ensures that it is \x00 free and automatically compiles the usual C file that executes the shellcode:
--- ../../0x01-bind/bind.asm.in 2018-05-21 00:35:48.689635600 +0100+++ setsockopt.asm 2018-05-21 00:41:40.340303200 +0100@@ -1,9 +1,12 @@+; Like the bind shell but with a call to setsockopt(2). Allows a server to bind+; to a port even if there are previously established connections. Also useful+; when there are multiple local aliased IP addresses listening on the same port.+ global _start
section .text
_start:
xor eax, eax
- xor esi, esi ; will always hold the value 0 cdq ; set edx to zero as well, by sign extending eax
mov ax, 0x167 ; use mov instead of push imm16 and pop r16
@@ -15,7 +18,18 @@ ; edx (protocol) initialized to zero by the cdq instruction above
int 0x80
+ xchg edx, ebx ; SO_REUSEADDR xchg ebx, eax ; fd on ebx from this point below
+ mov ax, 0x16e ; use mov instead of push imm16 and pop r16+ ; int setsockopt(int, int, int, const void *, socklen_t)+ ; ecx (level) is already 1 from the above socket(2) call+ push 0x1 ; enabled+ mov esi, esp ; optval+ push 0x4 ; 4 bytes+ pop edi ; optlen+ int 0x80++ xor esi, esi ; will always hold the value 0 mov ax, 0x169 ; use mov instead of push imm16 and pop r16
; int bind(int, const struct sockaddr *, socklen_t)
push esi ; INADDR_ANY