Slide 1

Slide 1 text

あなたの知らないnopたち @ラボユース合宿(2024年版) 2024/8/21 光成滋生

Slide 2

Slide 2 text

• よく使われるループの先頭アドレスは 16byteアライメントされているとよいことが多い • そろえるために隙間を埋める • 隙間の命令は何もして欲しくない 何もしない(no operation)は何のため? アドレス 命令コード 命令コード 命令1 命令2 LP: 0000xx11h 0000xx20h jnz LP 命令1 命令2 LP: jnz LP 何もしない アライメント 2 / 14

Slide 3

Slide 3 text

• 何もしない1byteの命令 • xchg eax, eaxのalias(32bit時代) • AArch64 • NOP = hint #0 • 昔のArmはmov x0, x0だったことも • RISC-V • nop = addi x0, x0, 0 # x0はゼロレジスタ 90h(=0x90) 3 / 14

Slide 4

Slide 4 text

• 何もしない2byte命令(32bit) • lea eax, [eax] • 1byte nopを2個実行するより効率がよい(よかった) • 89 c0h • 別の何もしない2byte命令 • mov eax, eax • 8d 04 05 00 00 00 00h • 何もしない7byte命令 • lea eax, [eax * 1 + 0x00000000] • ... 8d 00h 4 / 14

Slide 5

Slide 5 text

• これらのnopたちは何もしないわけではなくなった • 32bitレジスタ操作命令は64bitレジスタの上位を クリアする • パーシャルレジスタストールを避けるために 値を保持する戦略はとらなかった(推測) • xchg eax, eax / lea eax, [eax]などもnopでなくなった • mov eax, eaxはxxxxxxxxを0にする • movzx rax, eaxと同じ(というかこの命令は存在しない) 64bit環境では rax(64) eax(32) ax(16) xxxxxxxx 5 / 14

Slide 6

Slide 6 text

• 90hは本当のnopになった • xchg eax, eaxではない • それではxchg eax, eaxはどうなった? • 87, c0h ; xchgの冗長表現 • xchg rax, raxは48 90h • 意味的には32bitのnopに対応するが内部的にはnopではない 64bitでのnop 6 / 14

Slide 7

Slide 7 text

• いつからnopが拡張された(2000年頃?) • nop r/m • nopがレジスタやメモリアドレッシングを引数に取る • 32/64bit両対応で同一コード • AMD CPUでも公式にサポートされてるので安心 • 大昔はIntelとAMDで異なる長いnopを準備したことも multi byte nop 66 nop 66 90h nop dword ptr [eax] 0f 1f 00h nop dword ptr [eax + 00h] 0f 1f 40 00h nop dword ptr [eax + eax*1 + 00h] 0f 1f 44 00 00h 66 nop dword ptr [eax + eax*1 + 00h] 66 0f 1f 44 00 00h nop dword ptr [eax + 00000000h] 0f 1f 80 00 00 00 00h nop dword ptr [eax + eax*1 + 00000000h] 0f 1f 84 00 00 00 00 00h 66 nop dword ptr [eax + eax*1 + 00000000h] 66 0f 1f 84 00 00 00 00 00h 7 / 14

Slide 8

Slide 8 text

• multi byte nopはeax以外の引数も取れる • nop ecx, eax ; 0f 1f c1h • nop esp, [rax + rax] ; 0f 1a 04 00h • Intelはこの余ってるnopを拡張した multi byte nopの拡張 8 / 14

Slide 9

Slide 9 text

• バッファオーバーフローをOS/libレベルでチェック • 4個の128bit boundレジスタbnd0, bnd1, bnd2, bnd3 • 上位64bitは境界の上限, 下位64bitは境界の下限の値が入る • bndmk bnd, mem • memの値をbndレジスタに設定する • bndcl bnd, r/m • r/mの値がbndの下限より大きいかを確認 • bndcu bnd, r/m • r/mの値がbndの上限より小さいかを確認 • 範囲外ならbounds violationが発生 MPX(Memory Protection Extensions) 9 / 14

Slide 10

Slide 10 text

• MPX非対応CPU/OSで影響が出ないように拡張 • 対応していないとこう見える MPXエンコーディング bndmk bnd0, [rax] ; f3 0f 1b 00h bndcl bnd0, rax ; f3 0f 1a c0h bndcu bnd0, rax ; f2 0f 1a c0h bndldx bnd0, [rax+rax] ; 0f 1a 04 00h bndstx [rax+rax], bnd0 ; 0f 1b 04 00h nop eax, [rax] nop eax, eax nop eax, eax nop esp, [rax+rax] nop esp, [rax+rax] [rax*2]と等しくない 省略可能な3引数オペランド の一部としてSIBを利用 10 / 14

Slide 11

Slide 11 text

• コンパイラが対応してくれなかった • 2024年Intel APX登場! • 3op/32レジスタ拡張 add, r20, r21, r23 (まるでRISC-V) • xsave/xsaveopt • ユーザモードでレジスタの退避・復元をするための命令 • MPXのための領域128バイトがあった • 4個の128bitレジスタ+状態 • これはもう使われることはない • それを拡張レジスタの保存に使うことになった 2019年MPXはremoved 11 / 14

Slide 12

Slide 12 text

• 単純な関数で見てみる • endbr64って何? 関数の先頭のnop id: endbr64 mov eax, edi # eax = x; ret 12 / 14 int id(int x) { return x; } gcc –O2 –S t.c

Slide 13

Slide 13 text

• Control-flow Enforcement Technology • ROP攻撃を防ぐ • 実行ファイルの中にある細切れの命令列を実行する攻撃手法 • バイト列の途中にジャンプしてくる • CETが有効だと • 正しいコードの先頭にはendbr64を入れてマークしておく • マークされたバイト列にしかジャンプできない • おかしなところにジャンプできない • CETが無効の古い環境では長いnopと解釈される • バイトコード F3 0F 1E FB = rep nop ebx • F3 : 大昔は繰り返し(rep)を意味することが多かったが 今はなんでもあり CET 13 / 14

Slide 14

Slide 14 text

• キャッシュ制御命令 • 指定された領域をL1やL2キャッシュからL3などの下位のレベ ルに移動する • 近い将来に使われないと分かっているメモリを下位のレベル に移行することでL1やL2キャッシュの領域を広げる • AlderLakeなどの比較的新しいCPUなどで利用可能 • バイトコード 0F 1C ... • やっぱり古いCPUではNOPに見える cldemote 14 / 14