web-dev-qa-db-ja.com

バイナリでスタック保護はどのように適用されますか?

Intel CPUを搭載したLinuxボックスで、-fstack-protect-allを使用してバイナリでコンパイルしたとしましょう。

  • これはどのようにバイナリにエンコードされますか? (readelfを使用してこの情報を表示できますか?)。すべてのページ/セグメントにエンコードされていますか、それとも1つの場所でバイナリに追加され、ローダーがそれを取得しますか?

  • カーネル/ローダーは、ページをロードするときにこの情報をどのように取得しますか?それはどのように使用しますか?

  • ページを読み取り専用に設定するために使用されるx86命令は何ですか?

8
SFlow

Intel CPUを搭載したLinuxボックスで、-fstack-protect-allを使用してバイナリでコンパイルしたとしましょう。

明示的に述べられていないため、これはGCCを使用して-fstack-protector-all引数を指定してコンパイルされたELFバイナリを指すと想定されます。

GCCスタック保護メカニズム

-fstack-protector-all-fstack-protectorの拡張です:

-fstack-protector

スタックスマッシング攻撃などのバッファオーバーフローをチェックする追加のコードを発行します。 これは、脆弱なオブジェクトを含む関数にガード変数を追加することによって行われます。これには、allocaを呼び出す関数と、8バイトを超えるバッファを含む関数が含まれます。 ガードは、関数に入るときに初期化され、関数が出るときにチェックされます。ガードチェックが失敗すると、エラーメッセージが出力され、プログラムが終了します。

-fstack-protector-all

すべての関数が保護されていることを除いて、-fstack-protectorと同様です。

前述の「ガード変数」は一般に canary と呼ばれます。

スタック保護の背後にある基本的な考え方は、関数の戻りポインターがプッシュされた直後に、スタックに「カナリア」(ランダムに選択された整数)をプッシュすることです。次に、関数が戻る前にカナリア値がチェックされます。変更されている場合、プログラムは中止されます。一般に、スタックバッファオーバーフロー(別名 "スタックスマッシング")攻撃は、リターンポインタに到達する前に、バッファの終わりを超えて書き込むときにカナリアの値を変更する必要があります。カナリアの値は攻撃者には不明であるため、攻撃によって置き換えることはできません。したがって、スタック保護により、プログラムは、攻撃者が行きたい場所に戻るのではなく、それが起こったときにプログラムを中止することができます。1


さて、最初の一連の質問です。

これはどのようにバイナリにエンコードされますか? (readelfを使用してこの情報を表示できますか?)。すべてのページ/セグメントにエンコードされていますか、それとも1つの場所でバイナリに追加され、ローダーがそれを取得しますか?

-fstack-protector-allは、ガード変数がすべての関数にプッシュされ、返される前にチェックされるコードをコンパイラーに生成させます。つまり、スタックカナリアのプッシュ、チェック、ポップに関係する追加の機械語命令が生成されます。

これは、同じソースから生成された2つのサンプルバイナリを使用して説明できます。1つは-fstack-protector-all引数でコンパイルされ、もう1つはそうではありませんでした。

ソースコード:

int test(int i) {
    return i;
}

int main(void) {
    int x;
    int i = 10;
    x = test(i);
    return x;
}

バイナリコンパイルされた関数なし-fstack-protector-all

$ objdump -dj .text test | grep -A7 "<test>:"
00000000004004ed <test>:
  4004ed:   55                      Push   %rbp
  4004ee:   48 89 e5                mov    %rsp,%rbp
  4004f1:   89 7d fc                mov    %edi,-0x4(%rbp)
  4004f4:   8b 45 fc                mov    -0x4(%rbp),%eax
  4004f7:   5d                      pop    %rbp
  4004f8:   c3                      retq   

バイナリからコンパイルされた関数with-fstack-protector-all

$ objdump -dj .text protected_test | grep -A20 "<test>:"
000000000040055d <test>:
  40055d:   55                      Push   %rbp
  40055e:   48 89 e5                mov    %rsp,%rbp
  400561:   48 83 ec 20             sub    $0x20,%rsp
  400565:   89 7d ec                mov    %edi,-0x14(%rbp)
  400568:   64 48 8b 04 25 28 00    mov    %fs:0x28,%rax     <- get guard variable value
  40056f:   00 00 
  400571:   48 89 45 f8             mov    %rax,-0x8(%rbp)   <- save guard variable on stack
  400575:   31 c0                   xor    %eax,%eax
  400577:   8b 45 ec                mov    -0x14(%rbp),%eax
  40057a:   48 8b 55 f8             mov    -0x8(%rbp),%rdx   <- move it to register
  40057e:   64 48 33 14 25 28 00    xor    %fs:0x28,%rdx     <- check it against original
  400585:   00 00 
  400587:   74 05                   je     40058e <test+0x31>
  400589:   e8 b2 fe ff ff          callq  400440 <__stack_chk_fail@plt> 
  40058e:   c9                      leaveq 
  40058f:   c3                      retq   

スタック保護はアセンブリコードを介して実装されるため、フラグの-fstack-protectorファミリの結果は、objdumpのような逆アセンブラを使用して最もよく調査されます。 readelfは、技術的にこの情報を表示します。-x引数は、選択されたセクションの16進ダンプを生成するためです。

スタック保護なしでコンパイルされたバイナリのreadelf 16進ダンプ:

$ readelf -x .text test

Hex dump of section '.text':
  0x00400400 31ed4989 d15e4889 e24883e4 f0505449 1.I..^H..H...PTI
  0x00400410 c7c09005 400048c7 c1200540 0048c7c7 [email protected].. [email protected]..
  0x00400420 f9044000 e8b7ffff fff4660f 1f440000 [email protected]..
  0x00400430 b83f1060 0055482d 38106000 4883f80e .?.`.UH-8.`.H...
  0x00400440 4889e577 025dc3b8 00000000 4885c074 H..w.]......H..t
  0x00400450 f45dbf38 106000ff e00f1f80 00000000 .].8.`..........
  0x00400460 b8381060 0055482d 38106000 48c1f803 .8.`.UH-8.`.H...
  0x00400470 4889e548 89c248c1 ea3f4801 d048d1f8 H..H..H..?H..H..
  0x00400480 75025dc3 ba000000 004885d2 74f45d48 u.]......H..t.]H
  0x00400490 89c6bf38 106000ff e20f1f80 00000000 ...8.`..........
  0x004004a0 803d910b 20000075 11554889 e5e87eff .=.. ..u.UH...~.
  0x004004b0 ffff5dc6 057e0b20 0001f3c3 0f1f4000 ..]..~. ......@.
  0x004004c0 48833d58 09200000 741eb800 00000048 H.=X. ..t......H
  0x004004d0 85c07414 55bf200e 60004889 e5ffd05d ..t.U. .`.H....]
  0x004004e0 e97bffff ff0f1f00 e973ffff ff554889 .{.......s...UH.
  0x004004f0 e5897dfc 8b45fc5d c3554889 e54883ec ..}..E.].UH..H..
  0x00400500 10c745f8 0a000000 8b45f889 c7e8dbff ..E......E......
  0x00400510 ffff8945 fc8b45fc c9c3660f 1f440000 ...E..E...f..D..
  0x00400520 41574189 ff415649 89f64155 4989d541 AWA..AVI..AUI..A
  0x00400530 544c8d25 d8082000 55488d2d d8082000 TL.%.. .UH.-.. .
  0x00400540 534c29e5 31db48c1 fd034883 ec08e855 SL).1.H...H....U
  0x00400550 feffff48 85ed741e 0f1f8400 00000000 ...H..t.........
  0x00400560 4c89ea4c 89f64489 ff41ff14 dc4883c3 L..L..D..A...H..
  0x00400570 014839eb 75ea4883 c4085b5d 415c415d .H9.u.H...[]A\A]
  0x00400580 415e415f c366662e 0f1f8400 00000000 A^A_.ff.........
  0x00400590 f3c3                                ..

readelfスタック保護付きでコンパイルされたバイナリの16進ダンプ:

$ readelf -x .text protected_test

Hex dump of section '.text':
  0x00400470 31ed4989 d15e4889 e24883e4 f0505449 1.I..^H..H...PTI
  0x00400480 c7c05006 400048c7 c1e00540 0048c7c7 [email protected][email protected]..
  0x00400490 90054000 e8b7ffff fff4660f 1f440000 [email protected]..
  0x004004a0 b8471060 0055482d 40106000 4883f80e .G.`.UH-@.`.H...
  0x004004b0 4889e577 025dc3b8 00000000 4885c074 H..w.]......H..t
  0x004004c0 f45dbf40 106000ff e00f1f80 00000000 .].@.`..........
  0x004004d0 b8401060 0055482d 40106000 48c1f803 .@.`.UH-@.`.H...
  0x004004e0 4889e548 89c248c1 ea3f4801 d048d1f8 H..H..H..?H..H..
  0x004004f0 75025dc3 ba000000 004885d2 74f45d48 u.]......H..t.]H
  0x00400500 89c6bf40 106000ff e20f1f80 00000000 ...@.`..........
  0x00400510 803d290b 20000075 11554889 e5e87eff .=). ..u.UH...~.
  0x00400520 ffff5dc6 05160b20 0001f3c3 0f1f4000 ..].... ......@.
  0x00400530 48833de8 08200000 741eb800 00000048 H.=.. ..t......H
  0x00400540 85c07414 55bf200e 60004889 e5ffd05d ..t.U. .`.H....]
  0x00400550 e97bffff ff0f1f00 e973ffff ff554889 .{.......s...UH.
  0x00400560 e54883ec 20897dec 64488b04 25280000 .H.. .}.dH..%(..
  0x00400570 00488945 f831c08b 45ec488b 55f86448 .H.E.1..E.H.U.dH
  0x00400580 33142528 00000074 05e8b2fe ffffc9c3 3.%(...t........
  0x00400590 554889e5 4883ec10 64488b04 25280000 UH..H...dH..%(..
  0x004005a0 00488945 f831c0c7 45f00a00 00008b45 .H.E.1..E......E
  0x004005b0 f089c7e8 a5ffffff 8945f48b 45f4488b .........E..E.H.
  0x004005c0 55f86448 33142528 00000074 05e86efe U.dH3.%(...t..n.
  0x004005d0 ffffc9c3 662e0f1f 84000000 00006690 ....f.........f.
  0x004005e0 41574189 ff415649 89f64155 4989d541 AWA..AVI..AUI..A
  0x004005f0 544c8d25 18082000 55488d2d 18082000 TL.%.. .UH.-.. .
  0x00400600 534c29e5 31db48c1 fd034883 ec08e8f5 SL).1.H...H.....
  0x00400610 fdffff48 85ed741e 0f1f8400 00000000 ...H..t.........
  0x00400620 4c89ea4c 89f64489 ff41ff14 dc4883c3 L..L..D..A...H..
  0x00400630 014839eb 75ea4883 c4085b5d 415c415d .H9.u.H...[]A\A]
  0x00400640 415e415f c366662e 0f1f8400 00000000 A^A_.ff.........
  0x00400650 f3c3                                ..

明らかに、これはあまり役に立ちません。

ELFバイナリで実行可能としてマークされたすべてのセクションが同じセグメント(textセグメント)にマップされるため、ページとセグメントの説明は関係ありません。


カーネル/ローダーは、ページをロードするときにこの情報をどのように取得しますか?それはどのように使用しますか?

-fstack-protector-allの使用は、セクションまたはセグメントの権限には影響しません。

カーネルプログラムローダーの詳細については、以下を参照してください。


ページを読み取り専用に設定するために使用されるx86命令は何ですか?

セグメントのアクセス許可は、リンクエディターによって設定されます。 セグメントのアクセス許可


1。GCCの「強力な」スタック保護
13
julian

GCCのスタック保護はソフトウェアベースであり、DEPのハードウェアベースの保護とは関係ありません。 OSがDEPを有効にすると、OSで実行されているすべてのプログラム(またはユーザーが定義したサブセット)は、バイナリのビルドに使用されたコンパイラフラグに関係なく、ハードウェアフラグによって自動的に保護されます。

スタック保護フラグがGCCで有効になっている場合、追加のガードを配置して保護を提供し、関数が呼び出されるか戻るときにチェックして、プログラムが整合性に違反するのではなくアボートすることを確認します。これにより、実行可能ファイルのサイズがわずかに増加し、コードの実行速度がわずかに遅くなります(CPU時間をより多く使用)。

ローダーもOSもGCCのスタック保護を認識していませんし、認識する必要もありません。 OSに関する限り、これは単なる通常の実行可能コードです。 OSはスタック保護を有効にせずにDEPを提供でき、スタック保護はDEPを必要としません。

DEPを使用すると、IP(命令ポインター)がCS(コードセグメント)の外側に到達すると、例外が発生し、OSがプログラムを終了します。 GCCのスタック保護では、スタックは特定の値がロードされた余分なスペースでバッファーされ、それらのバッファーされた領域が変更されていないことを確認するためにチェックされます。関数がガードチェックに失敗した場合、プログラムは終了し、それ以上の損傷を防ぎます。

2
phyrfox