Introduction to ARM64v8
AWSハッキングの学習と実践:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングの学習と実践:
HackTricks Training GCP Red Team Expert (GRTE)
例外レベル - EL(ARM64v8)
ARMv8アーキテクチャでは、実行レベルである例外レベル(ELs)が、実行環境の特権レベルと機能を定義します。EL0からEL3までの4つの例外レベルがあり、それぞれ異なる目的を果たしています:
EL0 - ユーザーモード:
これは最も特権のないレベルであり、通常のアプリケーションコードの実行に使用されます。
EL0で実行されるアプリケーションは、お互いやシステムソフトウェアから分離されており、セキュリティと安定性が向上しています。
EL1 - オペレーティングシステムカーネルモード:
ほとんどのオペレーティングシステムカーネルはこのレベルで実行されます。
EL1はEL0よりも特権があり、システムリソースにアクセスできますが、システムの整合性を確保するためにいくつかの制限があります。
EL2 - ハイパーバイザーモード:
このレベルは仮想化に使用されます。EL2で実行されるハイパーバイザーは、同じ物理ハードウェア上で実行されている複数のオペレーティングシステム(それぞれが独自のEL1で)を管理できます。
EL2には仮想化環境の分離と制御の機能が提供されています。
EL3 - セキュアモニターモード:
これは最も特権のあるレベルであり、セキュアブートや信頼された実行環境によく使用されます。
EL3はセキュア状態と非セキュア状態の間のアクセスを管理および制御できます(セキュアブート、信頼されたOSなど)。
これらのレベルの使用により、ユーザーアプリケーションから最も特権のあるシステムソフトウェアまで、システムの異なる側面を構造化して安全に管理する方法が提供されます。ARMv8の特権レベルへのアプローチは、異なるシステムコンポーネントを効果的に分離することで、システムのセキュリティと堅牢性を向上させます。
レジスタ(ARM64v8)
ARM64には31個の汎用レジスタがあり、x0
からx30
までラベル付けされています。それぞれが64ビット(8バイト)の値を格納できます。32ビットの値のみを必要とする操作の場合、同じレジスタにはw0
からw30
という名前を使用して32ビットモードでアクセスできます。
x0
からx7
- これらは通常、スクラッチレジスタとサブルーチンにパラメータを渡すために使用されます。
x0
は関数の戻りデータも保持します。
x8
- Linuxカーネルでは、x8
はsvc
命令のシステムコール番号として使用されます。macOSではx16が使用されます!x9
からx15
- さらなる一時レジスタであり、ローカル変数によく使用されます。x16
とx17
- 手続き内呼び出しレジスタ。即値のための一時レジスタ。間接関数呼び出しやPLT(手続きリンクテーブル)スタブにも使用されます。
x16
はmacOSで**svc
命令のシステムコール番号**として使用されます。
x18
- プラットフォームレジスタ。汎用レジスタとして使用できますが、一部のプラットフォームでは、このレジスタはプラットフォーム固有の用途に予約されています:Windowsでは現在のスレッド環境ブロックへのポインタ、Linuxカーネルでは現在の実行中のタスク構造体へのポインタ。x19
からx28
- これらは呼び出し元保存レジスタです。関数はこれらのレジスタの値を呼び出し元のために保存する必要があり、それらはスタックに保存され、呼び出し元に戻る前に回復されます。x29
- スタックフレームを追跡するためのフレームポインタ。新しいスタックフレームが関数が呼び出されるために作成されるとき、x29
レジスタはスタックに保存され、新しいフレームポインタアドレス(sp
アドレス)がこのレジスタに保存されます。
このレジスタは一般目的レジスタとしても使用できますが、通常はローカル変数への参照として使用されます。
x30
またはlr
- リンクレジスタ。BL
(リンク付きブランチ)またはBLR
(レジスタへのリンク付きブランチ)命令が実行されるときに**pc
の値をこのレジスタに格納することで戻りアドレス**を保持します。
他のレジスタと同様に使用することもできます。
現在の関数が新しい関数を呼び出す予定であり、したがって
lr
を上書きする場合、最初にスタックに保存し、これがエピローグ(stp x29, x30 , [sp, #-48]; mov x29, sp
->fp
とlr
を保存し、スペースを生成し、新しいfp
を取得)で、最後に回復します。これがプロローグ(ldp x29, x30, [sp], #48; ret
->fp
とlr
を回復して戻る)です。
sp
- スタックポインタ。スタックの先頭を追跡するために使用されます。
sp
の値は常に少なくともクワッドワードのアライメントを保持する必要があり、それ以外の場合はアライメント例外が発生する可能性があります。
pc
- 次の命令を指すプログラムカウンタ。このレジスタは例外生成、例外リターン、およびブランチを介してのみ更新できます。このレジスタを読み取ることができる通常の命令は、pc
アドレスをlr
(リンクレジスタ)に格納するためのブランチリンク命令(BL、BLR)だけです。xzr
- ゼロレジスタ。32ビットレジスタ形式では**wzr
とも呼ばれます。ゼロ値を簡単に取得するために使用できます(一般的な操作)またはsubs
を使用して比較を行うために使用できます。subs XZR, Xn, #10
のように結果データをどこにも保存しないで(xzr
**に)データを保存することができます。
Wn
レジスタは**Xn
** レジスタの32ビットバージョンです。
SIMDおよび浮動小数点レジスタ
さらに、最適化された単一命令複数データ(SIMD)操作や浮動小数点演算を実行するために使用できる、長さが128ビットの別の32個のレジスタがあります。これらはVnレジスタと呼ばれますが、64ビット、32ビット、16ビット、8ビットで動作することもでき、その場合は**Qn
、Dn
、Sn
、Hn
、Bn
**と呼ばれます。
システムレジスタ
数百のシステムレジスタ、または特殊用途レジスタ(SPR)は、プロセッサの動作を監視および制御するために使用されます。
これらは、専用の特殊命令 mrs
と msr
を使用してのみ読み取りまたは設定できます。
特殊レジスタ TPIDR_EL0
と TPIDDR_EL0
は、リバースエンジニアリング時に一般的に見られます。EL0
接尾辞は、レジスタにアクセスできる最小例外を示します(この場合、EL0は通常の例外(特権)レベルで通常のプログラムが実行されます)。
これらは通常、メモリのスレッドローカルストレージ領域のベースアドレスを格納するために使用されます。通常、最初のものはEL0で実行されるプログラムに対して読み書き可能ですが、2番目はEL0から読み取り、EL1から書き込むことができます(カーネルのような)。
mrs x0, TPIDR_EL0 ; TPIDR_EL0 を x0 に読み込む
msr TPIDR_EL0, X0 ; x0 を TPIDR_EL0 に書き込む
PSTATE
PSTATE には、オペレーティングシステムでシリアル化されたいくつかのプロセスコンポーネントが含まれており、SPSR_ELx
特殊レジスタに格納されます。ここで、X はトリガーされた例外の権限 レベルを示します(これにより、例外が終了したときにプロセスの状態を回復できます)。
これらはアクセス可能なフィールドです:
![](https://book.hacktricks.xyz/~gitbook/image?url=https%3A%2F%2F4226785378-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FpWAf1y9UWEMh8PEO1vPV%252Fuploads%252Fgit-blob-c3669e1a2c10b38f71e5d409a3165970f2620d12%252Fimage%2520%281196%29.png%3Falt%3Dmedia&width=768&dpr=4&quality=100&sign=ad30fe37&sv=1)
N
、Z
、C
、V
条件フラグ:N
は操作が負の結果を生じたことを意味しますZ
は操作がゼロを生じたことを意味しますC
は操作がキャリーしたことを意味しますV
は操作が符号オーバーフローを生じたことを意味します:2つの正の数の合計は負の結果を生じます。
2つの負の数の合計は正の結果を生じます。
減算では、大きな負の数が小さな正の数から引かれる場合(またはその逆)、結果が与えられたビットサイズの範囲内に表現できない場合。
明らかに、プロセッサは操作が符号付きかどうかを知らないため、操作を符号付きかどうかをチェックし、符号付きまたは符号なしの場合に発生したキャリーを示します。
すべての命令がこれらのフラグを更新するわけではありません。CMP
や TST
のような一部の命令は、および ADDS
のような s 接尾辞を持つ他の命令もそれを行います。
現在のレジスタ幅 (
nRW
) フラグ:フラグが値 0 を保持している場合、プログラムは再開後に AArch64 実行状態で実行されます。現在の例外レベル (
EL
):EL0 で実行される通常のプログラムは値 0 を持ちますシングルステップ フラグ (
SS
):デバッガがSPSR_ELx
内の SS フラグを 1 に設定してシングルステップを行うために使用されます。プログラムはステップを実行し、シングルステップ例外を発行します。不正例外 状態フラグ (
IL
):特権ソフトウェアが無効な例外レベル転送を実行するときにマークされ、このフラグが 1 に設定され、プロセッサは不正な状態例外をトリガーします。DAIF
フラグ:これらのフラグにより、特権プログラムが特定の外部例外を選択的にマスクできます。A
が 1 の場合、非同期中断 がトリガーされます。I
は外部ハードウェア 割り込みリクエスト(IRQ)に応答するように構成され、F は Fast Interrupt Requests(FIRs)に関連しています。スタックポインタ選択 フラグ (
SPS
):EL1 およびそれ以上で実行される特権プログラムは、自分自身のスタックポインタレジスタとユーザーモデルのレジスタ(たとえばSP_EL1
とEL0
の間)を切り替えることができます。この切り替えは、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
は実行される操作(add
、sub
、mov
など)を、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 + 8
をx2
にロードし、x1 + 8
の結果をx1
に格納しますstr lr, [sp, #-4]!
は、リンクレジスタを sp に格納し、レジスタ sp を更新しますポストインデックスモード: これは前のモードと同様ですが、メモリアドレスにアクセスしてからオフセットを計算して格納します。
ldr x0, [x1], #8
は、x1
をx0
にロードし、x1 + 8
で x1 を更新しますPC相対アドレッシング: この場合、ロードするアドレスは PC レジスタに対して相対的に計算されます
ldr x1, =_start
は、_start
シンボルが開始するアドレスを、現在の PC に関連して x1 にロードします。str
: メモリからレジスタに値を格納します。例:
str x0, [x1]
— これはx0
の値をx1
が指すメモリ位置に格納します。ldp
: 2 つのレジスタを連続するメモリ位置からロードします。通常、メモリアドレスは、別のレジスタの値にオフセットを追加して形成されます。例:
ldp x0, x1, [x2]
は、それぞれx2
とx2 + 8
のメモリ位置からx0
とx1
をロードします。stp
: 2 つのレジスタを連続するメモリ位置に格納します。通常、メモリアドレスは、別のレジスタの値にオフセットを追加して形成されます。例:
stp x0, x1, [sp]
は、それぞれsp
とsp + 8
のメモリ位置にx0
とx1
を格納します。stp x0, x1, [sp, #16]!
は、それぞれsp+16
とsp + 24
のメモリ位置にx0
とx1
を格納し、sp
をsp+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
— これはx1
とx2
の値を加算し、結果をx0
に格納します。add x5, x5, #1, lsl #12
— これは4096に等しい(1を12回シフト) -> 1 0000 0000 0000 0000adds
これはadd
を実行し、フラグを更新しますsub
: 2つのレジスタの値を引き算し、結果をレジスタに格納します。add
構文を確認します。例:
sub x0, x1, x2
— これはx1
からx2
の値を引き、結果をx0
に格納します。subs
これはsubと同じですが、フラグを更新しますmul
: 2つのレジスタの値を掛け算し、結果をレジスタに格納します。例:
mul x0, x1, x2
— これはx1
とx2
の値を掛け算し、結果をx0
に格納します。div
: 1つのレジスタの値をもう1つで割り、結果をレジスタに格納します。例:
div x0, x1, x2
— これはx1
をx2
で割り、結果をx0
に格納します。lsl
,lsr
,asr
,ror
,rrx
:論理左シフト: 末尾に0を追加し、他のビットを前に移動します(n回2を掛ける)
論理右シフト: 先頭に1を追加し、他のビットを後ろに移動します(符号なしの場合はn回2で割る)
算術右シフト: **
lsr
**と同様ですが、最上位ビットが1の場合は0を追加する代わりに、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から始まる4ビットを抽出し、ゼロ拡張された結果をX1に配置します。Xに符号拡張: 値の符号を拡張します(または符号なしバージョンでは単に0を追加します):
SXTB X1, W2
バイトの符号を拡張します W2からX1 (W2
はX2
の半分) 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
— これはx0
とx1
の値を比較し、条件フラグを適切に設定します。cmn
: 負の比較オペランド。この場合、adds
のエイリアスで、同じ構文をサポートします。m == -n
かどうかを知るのに便利です。ccmp
: 条件付き比較、前の比較が真の場合にのみ実行され、特定のnzcvビットを設定します。cmp x1, x2; ccmp x3, x4, 0, NE; blt _func
-> x1 != x2かつx3 < x4の場合、funcにジャンプしますこれは前の
cmp
がNE
だった場合にのみccmp
が実行されるためです。そうでない場合、ビットnzcv
は0に設定されます(これはblt
の比較を満たしません)。これは
ccmn
としても使用できます(cmp
とcmn
のように)。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 label
— もしx0
とx1
の値が等しくない場合、label
にジャンプします。cbz
: ゼロの場合分岐。この命令はレジスタをゼロと比較し、等しい場合、指定されたラベルやアドレスに分岐します。例:
cbz x0, label
— もしx0
の値がゼロの場合、label
にジャンプします。cbnz
: ゼロでない場合分岐。この命令はレジスタをゼロと比較し、等しくない場合、指定されたラベルやアドレスに分岐します。例:
cbnz x0, label
— もしx0
の値がゼロでない場合、label
にジャンプします。tbnz
: ビットをテストして非ゼロの場合分岐例:
tbnz x0, #8, label
tbz
: ビットをテストしてゼロの場合分岐例:
tbz x0, #8, label
条件付き選択演算: 条件ビットによって挙動が異なる演算です。
csel Xd, Xn, Xm, cond
->csel X0, X1, X2, EQ
-> 真の場合、X0 = X1、偽の場合、X0 = X2csinc Xd, Xn, Xm, cond
-> 真の場合、Xd = Xn、偽の場合、Xd = Xm + 1cinc Xd, Xn, cond
-> 真の場合、Xd = Xn + 1、偽の場合、Xd = Xncsinv Xd, Xn, Xm, cond
-> 真の場合、Xd = Xn、偽の場合、Xd = NOT(Xm)cinv Xd, Xn, cond
-> 真の場合、Xd = NOT(Xn)、偽の場合、Xd = Xncsneg Xd, Xn, Xm, cond
-> 真の場合、Xd = Xn、偽の場合、Xd = - Xmcneg Xd, Xn, cond
-> 真の場合、Xd = - Xn、偽の場合、Xd = Xncset Xd, Xn, Xm, cond
-> 真の場合、Xd = 1、偽の場合、Xd = 0csetm Xd, Xn, Xm, cond
-> 真の場合、Xd = <all 1>、偽の場合、Xd = 0adrp
: シンボルのページアドレスを計算してレジスタに格納します。例:
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 x29, sp
(現在の関数のために新しいフレームポインタを設定する)ローカル変数用のスタック上のスペースを割り当てる(必要な場合):
sub sp, sp, <size>
(ここで<size>
は必要なバイト数です)
関数エピローグ
ローカル変数を解放する(割り当てられている場合):
add sp, sp, <size>
リンクレジスタとフレームポインタを復元する:
Return:
ret
(リンクレジスタ内のアドレスを使用して呼び出し元に制御を返します)
AARCH32 実行状態
Armv8-A は 32 ビットプログラムの実行をサポートします。AArch32 は A32
と T32
の 2 つの命令セットのいずれかで実行でき、interworking
を介してそれらの間を切り替えることができます。
特権を持つ 64 ビットプログラムは、例外レベルの転送を実行することで、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=0
は A32
を意味し、J=0
かつ T=1
は T32 を意味します。これは基本的に、命令セットが T32 であることを示すために 最下位ビットを 1 に設定することを意味します。
これは interworking 分岐命令中に設定されますが、PC が宛先レジスタとして設定されている場合に他の命令で直接設定することもできます。例:
別の例:
レジスタ
32ビットのレジスタが16個あります(r0-r15)。r0からr14まで、どんな操作にも使用できますが、一部は通常予約されています:
r15
:プログラムカウンタ(常に)。次の命令のアドレスが格納されます。A32では現在+8、T32では現在+4。r11
:フレームポインタr12
:手続き内呼び出しレジスタr13
:スタックポインタr14
:リンクレジスタ
さらに、レジスタは**バンク付きレジスタ
にバックアップされます。これは、例外処理や特権操作で高速なコンテキスト切り替え**を実行するために、レジスタの値を保存しておく場所です。
これは、例外が発生したプロセッサモードのCPSR
からプロセッサのSPSR
にプロセッサ状態を保存することで行われます。例外が返されると、CPSR
はSPSR
から復元されます。
CPSR - 現在のプログラムステータスレジスタ
AArch32では、CPSRはAArch64の**PSTATE
と同様に機能し、例外が発生すると後で実行を復元するためにSPSR_ELx
**にも保存されます:
![](https://book.hacktricks.xyz/~gitbook/image?url=https%3A%2F%2F4226785378-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FpWAf1y9UWEMh8PEO1vPV%252Fuploads%252Fgit-blob-c31c79e9c30b4ff84b9f85d47ad7ae374214e85d%252Fimage%2520%281197%29.png%3Falt%3Dmedia&width=768&dpr=4&quality=100&sign=9bc421bd&sv=1)
フィールドはいくつかのグループに分かれています:
アプリケーションプログラムステータスレジスタ(APSR):算術フラグとEL0からアクセス可能
実行状態レジスタ:プロセスの動作(OSによって管理される)。
アプリケーションプログラムステータスレジスタ(APSR)
N
、Z
、C
、V
フラグ(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つは、使用中の例外モード(例外が発生し処理されている場合)を表します。設定された数値は、これが処理されている間に別の例外が発生した場合の現在の優先度を示します。
![](https://book.hacktricks.xyz/~gitbook/image?url=https%3A%2F%2F4226785378-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FpWAf1y9UWEMh8PEO1vPV%252Fuploads%252Fgit-blob-23ab4ba8cd085769cf6cfa1627f5f5d7c2a747b6%252Fimage%2520%281200%29.png%3Falt%3Dmedia&width=768&dpr=4&quality=100&sign=1594540b&sv=1)
AIF
:特定の例外は、A
、I
、F
ビットを使用して無効にできます。A
が1の場合、非同期中断がトリガーされます。I
は外部ハードウェアの割り込みリクエスト(IRQ)に応答するように構成され、Fは高速割り込みリクエスト(FIR)に関連しています。
時々、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
からのオブジェクトを呼び出し、コマンドを実行します)。
NSObjCMessageLoggingEnabled=1
環境変数を設定すると、この関数が呼び出されたときに /tmp/msgSends-pid
のようなファイルにログを取ることが可能です。
さらに、OBJC_HELP=1
を設定し、任意のバイナリを呼び出すと、特定の Objc-C アクションが発生したときにログを取るために使用できる他の環境変数を確認できます。
この関数が呼び出されると、指定されたインスタンスの呼び出されたメソッドを見つける必要があります。これには、異なる検索が行われます:
楽観的なキャッシュ検索を実行:
成功した場合、完了
runtimeLock を取得 (読み取り)
(realize && !cls->realized) の場合、クラスを実現
(initialize && !cls->initialized) の場合、クラスを初期化
クラス固有のキャッシュを試す:
成功した場合、完了
クラスメソッドリストを試す:
見つかった場合、キャッシュを埋めて完了
親クラスのキャッシュを試す:
成功した場合、完了
親クラスのメソッドリストを試す:
見つかった場合、キャッシュを埋めて完了
(resolver) メソッドリゾルバを試し、クラス検索から繰り返す
まだここにいる場合 (= すべてが失敗した場合) フォワーダを試す
シェルコード
コンパイルするには:
バイトを抽出するには:
新しい macOS 向け:
Last updated