Introduction to ARM64v8
Last updated
Last updated
AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE) GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
ARMv8アーキテクチャでは、例外レベル(EL)は、実行環境の特権レベルと機能を定義します。EL0からEL3までの4つの例外レベルがあり、それぞれ異なる目的に使用されます。
EL0 - ユーザーモード:
これは最も特権の低いレベルで、通常のアプリケーションコードを実行するために使用されます。
EL0で実行されるアプリケーションは互いに、またシステムソフトウェアから隔離されており、セキュリティと安定性が向上します。
EL1 - オペレーティングシステムカーネルモード:
ほとんどのオペレーティングシステムカーネルはこのレベルで実行されます。
EL1はEL0よりも多くの特権を持ち、システムリソースにアクセスできますが、システムの整合性を確保するためにいくつかの制限があります。
EL2 - ハイパーバイザーモード:
このレベルは仮想化に使用されます。EL2で実行されるハイパーバイザーは、同じ物理ハードウェア上で複数のオペレーティングシステム(それぞれ独自のEL1で)を管理できます。
EL2は仮想化環境の隔離と制御のための機能を提供します。
EL3 - セキュアモニターモード:
これは最も特権の高いレベルで、通常はセキュアブートや信頼された実行環境に使用されます。
EL3は、セキュア状態と非セキュア状態(セキュアブート、信頼されたOSなど)間のアクセスを管理および制御できます。
これらのレベルを使用することで、ユーザーアプリケーションから最も特権の高いシステムソフトウェアまで、システムのさまざまな側面を構造化された安全な方法で管理できます。ARMv8の特権レベルへのアプローチは、異なるシステムコンポーネントを効果的に隔離し、システムのセキュリティと堅牢性を向上させるのに役立ちます。
ARM64には、x0
からx30
までの31の汎用レジスタがあります。それぞれは64ビット(8バイト)の値を格納できます。32ビットの値のみを必要とする操作の場合、同じレジスタは32ビットモードでw0
からw30
の名前でアクセスできます。
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
- プログラムカウンタ。次の命令を指します。このレジスタは、例外生成、例外戻り、および分岐を通じてのみ更新できます。このレジスタを読み取ることができる唯一の通常の命令は、リンク付き分岐命令(BL、BLR)で、pc
アドレスをlr
(リンクレジスタ)に保存します。
xzr
- ゼロレジスタ。32ビットレジスタ形式では**wzr
とも呼ばれます。ゼロ値を簡単に取得するために使用できます(一般的な操作)またはsubs
を使用して比較を行うために使用できます(例:subs XZR, Xn, #10
、結果データをどこにも保存しません(xzr
**に))。
Wn
レジスタはXn
レジスタの32ビットバージョンです。
さらに、最適化された単一命令複数データ(SIMD)操作や浮動小数点演算に使用できる128ビット長のレジスタが32個あります。これらはVnレジスタと呼ばれますが、64ビット、32ビット、16ビット、8ビットでも動作でき、その場合は**Qn
、Dn
、Sn
、Hn
、Bn
**と呼ばれます。
数百のシステムレジスタ、特別目的レジスタ(SPRs)とも呼ばれ、プロセッサの動作を監視および制御するために使用されます。
これらは専用の特別命令**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は、オペレーティングシステムが可視化できる**SPSR_ELx
特別レジスタに直列化された複数のプロセスコンポーネントを含み、Xはトリガーされた例外の権限** レベルを示します(これにより、例外が終了したときにプロセス状態を回復できます)。
これらはアクセス可能なフィールドです:
N
、Z
、C
、および**V
**条件フラグ:
**N
**は、操作が負の結果をもたらしたことを意味します。
**Z
**は、操作がゼロをもたらしたことを意味します。
**C
**は、操作がキャリーしたことを意味します。
**V
**は、操作が符号付きオーバーフローをもたらしたことを意味します:
2つの正の数の合計が負の結果をもたらします。
2つの負の数の合計が正の結果をもたらします。
減算では、大きな負の数が小さな正の数から引かれた場合(またはその逆)、結果が与えられたビットサイズの範囲内で表現できない場合。
明らかに、プロセッサは操作が符号付きかどうかを知らないため、CとVを操作でチェックし、符号付きまたは符号なしの場合にキャリーが発生したかどうかを示します。
すべての命令がこれらのフラグを更新するわけではありません。**CMP
やTST
のようなものはそうし、ADDS
**のようにsサフィックスを持つ他のものもそうします。
現在のレジスタ幅(nRW
)フラグ:フラグが0の値を保持している場合、プログラムは再開時にAArch64実行状態で実行されます。
現在の例外レベル(EL
):EL0で実行される通常のプログラムは値0を持ちます。
単一ステップフラグ(SS
):デバッガによって使用され、例外を通じて**SPSR_ELx
**内でSSフラグを1に設定することで単一ステップを実行します。プログラムは1ステップ実行し、単一ステップ例外を発生させます。
不正例外状態フラグ(IL
):特権ソフトウェアが無効な例外レベル転送を実行したときにマークするために使用され、このフラグは1に設定され、プロセッサは不正状態例外をトリガーします。
**DAIF
**フラグ:これらのフラグは、特権プログラムが特定の外部例外を選択的にマスクできるようにします。
A
が1の場合、非同期中断がトリガーされることを意味します。I
は外部ハードウェア割り込み要求(IRQ)に応答するように設定します。Fは高速割り込み要求(FIR)に関連しています。
スタックポインタ選択フラグ(SPS
):EL1以上で実行される特権プログラムは、独自のスタックポインタレジスタとユーザーモデルのスタックポインタ(例:SP_EL1
とEL0
の間)を切り替えることができます。この切り替えは、**SPSel
**特別レジスタに書き込むことで行われます。これはEL0からは行えません。
ARM64の呼び出し規約では、関数への最初の8つのパラメータはレジスタ**x0
からx7
に渡されます。追加のパラメータはスタックに渡されます。戻り値はレジスタx0
に返され、x1
にも返されます(128ビット長の場合)。x19
からx30
およびsp
レジスタは、関数呼び出しの間に保持**されなければなりません。
アセンブリで関数を読むときは、関数のプロローグとエピローグを探してください。プロローグは通常、フレームポインタ(x29
)の保存、新しいフレームポインタの設定、およびスタックスペースの割り当てを含みます。エピローグは通常、保存されたフレームポインタの復元と関数からの戻りを含みます。
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
にロードします。
オフセットモード:元のポインタに影響を与えるオフセットが示されます。例えば:
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
にx1 + 8
の結果を保存します。
str lr, [sp, #-4]!
、リンクレジスタをsp
に保存し、レジスタsp
を更新します。
ポストインデックスモード:これは前のものと似ていますが、メモリアドレスにアクセスし、その後オフセットが計算されて保存されます。
ldr x0, [x1], #8
、x1
をx0
にロードし、x1
をx1 + 8
で更新します。
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]!
— これはx0
とx1
をsp+16
およびsp + 24
のメモリ位置に保存し、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 0000
adds
これはadd
を実行し、フラグを更新します。
sub
:2つのレジスタの値を減算し、結果をレジスタに保存します。
add
の構文を確認してください。
例:sub x0, x1, x2
— これはx2
の値をx1
から引き、結果をx0
に保存します。
subs
これはsub
のようですが、フラグを更新します。
mul
:2つのレジスタの値を乗算し、結果をレジスタに保存します。
例:mul x0, x1, x2
— これはx1
とx2
の値を乗算し、結果をx0
に保存します。
div
: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
X1の3ビット目からX2の4ビットを挿入します。
BFXIL X1, X2, #3, #4
X2の3ビット目から4ビットを抽出し、X1にコピーします。
SBFIZ X1, X2, #3, #4
X2から4ビットを符号拡張し、ビット位置3からX1に挿入し、右のビットをゼロにします。
SBFX X1, X2, #3, #4
X2のビット3から始まる4ビットを抽出し、符号拡張し、結果をX1に配置します。
UBFIZ X1, X2, #3, #4
X2から4ビットをゼロ拡張し、ビット位置3からX1に挿入し、右のビットをゼロにします。
UBFX X1, X2, #3, #4
X2のビット3から始まる4ビットを抽出し、ゼロ拡張された結果をX1に配置します。
符号拡張Xへの:値の符号を拡張(または符号なしバージョンでは単に0を追加)して、操作を実行できるようにします:
SXTB X1, W2
W2からX1にバイトの符号を拡張します(W2
はX2
の半分)。
SXTH X1, W2
W2からX1に16ビット数の符号を拡張します。
SXTW X1, W2
W2からX1にバイトの符号を拡張します。
UXTB X1, W2
W2からX1にバイトに0を追加します(符号なし)。
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にジャンプします。
これは**ccmp
が前のcmp
がNE
であった場合にのみ実行されるため**です。そうでない場合、ビット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 = 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 = <すべて1>、偽の場合、Xd = 0
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
:システムコールを行います。「スーパーバイザコール」を意味します。この命令をプロセッサが実行すると、ユーザーモードからカーネルモードに切り替わり、カーネルのシステムコール処理コードがあるメモリの特定の位置にジャンプします。
例:
リンクレジスタとフレームポインタをスタックに保存します:
新しいフレームポインタを設定: mov x29, sp
(現在の関数のために新しいフレームポインタを設定)
ローカル変数のためにスタックにスペースを割り当てる(必要な場合): sub sp, sp, <size>
(ここで <size>
は必要なバイト数)
ローカル変数を解放する(割り当てられている場合): add sp, sp, <size>
リンクレジスタとフレームポインタを復元する:
Return: ret
(呼び出し元に制御を戻すためにリンクレジスタのアドレスを使用)
Armv8-Aは32ビットプログラムの実行をサポートしています。AArch32は2つの命令セットのいずれかで実行できます:A32
とT32
で、interworking
を介してそれらの間を切り替えることができます。
特権のある64ビットプログラムは、低特権の32ビットプログラムへの例外レベル転送を実行することによって32ビットプログラムの実行をスケジュールできます。
64ビットから32ビットへの遷移は、例外レベルの低下とともに発生することに注意してください(例えば、EL1の64ビットプログラムがEL0のプログラムをトリガーする)。これは、AArch32
プロセススレッドが実行される準備ができたときに**SPSR_ELx
特別レジスタのビット4を1に設定することによって行われ、SPSR_ELx
の残りはAArch32
プログラムのCPSRを格納します。その後、特権プロセスはERET
命令を呼び出し、プロセッサはCPSRに応じてAArch32
**に遷移し、A32またはT32に入ります。**
interworking
はCPSRのJビットとTビットを使用して行われます。J=0
およびT=0
はA32
を意味し、J=0
およびT=1
はT32を意味します。これは基本的に、命令セットがT32であることを示すために最下位ビットを1に設定することに相当します。
これはinterworking分岐命令の間に設定されますが、PCが宛先レジスタとして設定されているときに他の命令で直接設定することもできます。例:
別の例:
16個の32ビットレジスタ(r0-r15)があります。r0からr14は任意の操作に使用できますが、そのうちいくつかは通常予約されています:
r15
: プログラムカウンタ(常に)。次の命令のアドレスを含みます。A32では現在 + 8、T32では現在 + 4。
r11
: フレームポインタ
r12
: 手続き内呼び出しレジスタ
r13
: スタックポインタ
r14
: リンクレジスタ
さらに、レジスタは**バンクレジスタ
にバックアップされます。これは、例外処理や特権操作において高速コンテキストスイッチを実行するためにレジスタの値を保存する場所であり、毎回手動でレジスタを保存および復元する必要を回避します。
これは、例外が発生したプロセッサモードのCPSR
からSPSR
にプロセッサ状態を保存することによって行われます。例外から戻ると、CPSR
はSPSR
**から復元されます。
AArch32ではCPSRはAArch64の**PSTATE
と似たように機能し、例外が発生したときに後で実行を復元するためにSPSR_ELx
**に保存されます:
フィールドはいくつかのグループに分かれています:
アプリケーションプログラムステータスレジスタ(APSR):算術フラグとEL0からアクセス可能
実行状態レジスタ:プロセスの動作(OSによって管理される)。
N
、Z
、C
、**V
**フラグ(AArch64と同様)
Q
フラグ:特化した飽和算術命令の実行中に整数飽和が発生した場合に1に設定されます。一度**1
**に設定されると、手動で0に設定されるまでその値を保持します。さらに、その値を暗黙的にチェックする命令はなく、手動で読み取る必要があります。
GE
(以上または等しい)フラグ:これはSIMD(単一命令、複数データ)操作で使用され、「並列加算」や「並列減算」などの操作を可能にします。これらの操作は、単一の命令で複数のデータポイントを処理します。
例えば、UADD8
命令は4つのバイトペア(2つの32ビットオペランドから)を並列に加算し、結果を32ビットレジスタに格納します。その後、これらの結果に基づいて**APSR
内のGE
フラグを設定します**。各GEフラグは、バイトペアの加算がオーバーフローしたかどうかを示します。
**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
:特定の例外は**A
、I
、F
ビットを使用して無効にできます。A
が1の場合、非同期中止がトリガーされることを意味します。I
は外部ハードウェア割り込み要求**(IRQ)に応答するように設定します。Fは高速割り込み要求(FIR)に関連しています。
syscalls.masterを確認してください。BSDシステムコールはx16 > 0になります。
mach_trap_tableのmach_trap_table
と、mach_traps.hのプロトタイプを確認してください。Machトラップの最大数はMACH_TRAP_TABLE_COUNT
= 128です。Machトラップはx16 < 0になるため、前のリストからマイナスを付けて番号を呼び出す必要があります:**_kernelrpc_mach_vm_allocate_trap
は-10
**です。
これら(およびBSD)システムコールを呼び出す方法を見つけるために、ディスアセンブラで**libsystem_kernel.dylib
**を確認することもできます:
注意してほしいのは、Ida と Ghidra はキャッシュを通過させるだけで 特定の dylibs をデコンパイルできるということです。
時には、libsystem_kernel.dylib
の デコンパイルされた コードをチェックする方が ソースコード を確認するよりも簡単です。なぜなら、いくつかのシステムコール(BSD と Mach)のコードはスクリプトを介して生成されているため(ソースコードのコメントを確認)、dylib では何が呼び出されているかを見つけることができます。
XNU は機械依存の呼び出しと呼ばれる別のタイプの呼び出しをサポートしています。これらの呼び出しの数はアーキテクチャによって異なり、呼び出しや数が一定であることは保証されていません。
これはカーネル所有のメモリページで、すべてのユーザープロセスのアドレス空間にマッピングされています。これは、ユーザーモードからカーネル空間への遷移を、非常に効率的でないこの遷移が行われるほど頻繁に使用されるカーネルサービスのために、syscalls を使用するよりも速くすることを目的としています。
例えば、呼び出し gettimeofdate
は、comm ページから直接 timeval
の値を読み取ります。
Objective-C または Swift プログラムでこの関数が使用されるのを見つけるのは非常に一般的です。この関数は、Objective-C オブジェクトのメソッドを呼び出すことを可能にします。
パラメータ(ドキュメントの詳細):
x0: self -> インスタンスへのポインタ
x1: op -> メソッドのセレクタ
x2... -> 呼び出されたメソッドの残りの引数
したがって、この関数への分岐の前にブレークポイントを置くと、lldb で何が呼び出されているかを簡単に見つけることができます(この例では、オブジェクトがコマンドを実行する NSConcreteTask
からオブジェクトを呼び出します):
環境変数 NSObjCMessageLoggingEnabled=1
を設定することで、この関数が呼び出されたときに /tmp/msgSends-pid
のようなファイルにログを記録することができます。
さらに、OBJC_HELP=1
を設定し、任意のバイナリを呼び出すことで、特定のObjc-Cアクションが発生したときに log するために使用できる他の環境変数を見ることができます。
この関数が呼び出されると、指定されたインスタンスの呼び出されたメソッドを見つける必要があります。そのために、さまざまな検索が行われます:
楽観的キャッシュ検索を実行:
成功した場合、完了
runtimeLockを取得(読み取り)
If (realize && !cls->realized) クラスを実現
If (initialize && !cls->initialized) クラスを初期化
クラス自身のキャッシュを試す:
成功した場合、完了
クラスメソッドリストを試す:
見つかった場合、キャッシュを埋めて完了
スーパークラスキャッシュを試す:
成功した場合、完了
スーパークラスメソッドリストを試す:
見つかった場合、キャッシュを埋めて完了
If (resolver) メソッドリゾルバを試し、クラス検索から繰り返す
まだここにいる場合(= 他のすべてが失敗した場合)フォワーダーを試す
コンパイルするには:
バイトを抽出するには:
新しいmacOSの場合:
AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE) GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)