コンパイラを読む

  • libcとつながっている部分も多々あるのでとりあえずわかるまではごっちゃにして後から分け方は再検討します

  • glibc: セキュリティ系の機能はdebug/に実装されてそう – “debug”ってなんかおかしいなと思ったらやっぱりここではなくてgccのlibssp/に置かれていた.

FORTIFY_SOURCEはどこで実装されているか?

  • libssp/strcpy_chk.c: strcpyを置き換える関数
    • 単に文字列サイズ(src, dst)をチェックしてからコピーするようにしただけ
  • libssp/ssp/string.h で #define strcpy(...) して置き換えているみたい

  • 以下をFORTIFYしてみる
#include <string.h>

int main(int argc, char **argv) {
  char buf[10];
  char aaa[] = "abcdefghijklmnopqeraiojae";
  strcpy(buf, aaa);
  
  return 0;
}
  • FORTIFYなし (-O2)
    400481:       48 89 e7                mov    rdi,rsp
    400484:       48 89 44 24 20          mov    QWORD PTR [rsp+0x20],rax
    400489:       b8 65 00 00 00          mov    eax,0x65
    40048e:       66 89 44 24 28          mov    WORD PTR [rsp+0x28],ax
    400493:       e8 88 ff ff ff          call   400420 <strcpy@plt>
    
  • FORTIFY_SOURCE=1 (-O2)
    4004b6:       48 89 e7                mov    rdi,rsp
    4004b9:       48 89 44 24 20          mov    QWORD PTR [rsp+0x20],rax
    4004be:       b8 65 00 00 00          mov    eax,0x65
    4004c3:       66 89 44 24 28          mov    WORD PTR [rsp+0x28],ax
    4004c8:       e8 93 ff ff ff          call   400460 <__strcpy_chk@plt>
    
  • FORTIFY_SOURCE=2 (-O2)
  4004b6:       48 89 e7                mov    rdi,rsp
  4004b9:       48 89 44 24 20          mov    QWORD PTR [rsp+0x20],rax
  4004be:       b8 65 00 00 00          mov    eax,0x65
  4004c3:       66 89 44 24 28          mov    WORD PTR [rsp+0x28],ax
  4004c8:       e8 93 ff ff ff          call   400460 <__strcpy_chk@plt>
  • 単純に関数だけを置き換えている
  • 確実にオーバーフローしないようにバッファの大きさを変えても置換されたのでそういうチェックはしていないのかな
  • どうして -O2 を付けないとダメなんだろう ?

SSP, Canaryはどこで実装されているか?

  • clang
    • $ rg __stack_chk_fail → 該当なし
    • $ rg SSP → stack protectorが引っかかる
    • $ rg stack_protector
    • $ rg SSPOn – lib/CodeGen/CodeGenModule.cpp: CodeGenModule::SetLLVMFunctionAttributes…() — LLVMに任せていそう(clang=フロントエンドの範囲ではない?) {code} if (LangOpts.getStackProtector() == LangOptions::SSPOn) B.addAttribute(llvm::Attribute::StackProtect); {/code} – LLVM: lib/CodeGen/StackProtector.cpp: StackProtector::InsertStackProtectors() — Selection DAGとか出てきて複雑そうだなあと思ったらそうでもなかった — それぞれの関数に対して以下を適用する — 関数プロローグをCreatePrologue()から変更 — 関数エピローグで: (GuardCheck=nullptrなので), getStackGuard()で取得したcanary値を挿入 — BB = Basic Block – lib/CodeGen/StackProtector.cpp: getStackGuard() → getIRStackGuard() / insertSSPDeclarations() – lib/CodeGen/StackProtector.cpp: CreatePrologue(): canary値を配置し、Intrinsic::stackprotectorを呼び出すIRを配置 — stackprotectorの処理はおそらく CodeGen/GlobalISel/IRTranslator.cpp にあるが、読んでもよくわからないのでパス — Target/X86/X86ISelLowering.cpp: ::getIRStackGuard() —- glibcはtcbhead_t構造体にcanaryを保存できる→fs segment registerにTLS=tcbhead_t構造体へのアドレスが保存されているのでfs:0x28のようにしてオフセットからtcbhead_t->stack_guardにアクセス —- それ以外の場合はグローバル変数に保存されている — IR StackGuardが使えない場合→ Target/X86/X86ISelLowering.cpp: ::insertSSPDeclarations() —- MSVC CRT向けの処理(後述) —- glibc, bionic, Fuchsiaに対してはただreturnするだけ – StackProtector.cpp: CreateFailBB() → __stack_smash_handler(OpenBSD)/__stack_chk_fail(それ以外)を呼ぶだけ – stack guard = canary, security cookie
  • Intrinsicってなんだろう?

  • GCC – libssp/ssp.c: __stack_chk_fail — エラーメッセージを表示(stack smashing detected) – libssp/ssp-local.c: __stack_chk_fail_local — __stack_chk_failを呼び出すだけ — PIC registerを保存するのに使えたっぽい

– gcc: $ rg _stack_chk_fail – targhooks.c: default(external|hidden)_stack_protect_fail — __stack_chk_failを呼び出すtree(ASTのノードのことか?)を返す
— x86ならhidden, x86_64ならexternalが呼ばれる – config/i386/i386.c (config = アーキテクチャ依存のファイル): ix86_stack_protect_fail() — 上の関数はここから呼ばれるが、この関数自体がUNUSEDになっており謎 – config/i386/i386.md — これがビルド時に変換されるみたい — stack_protect_set — stack_protect_test → canary値の検証?

** MSVC CRTのsecurity cookie, EHGuardは何をしているか?

  • Target/X86

  • X86ISelLowering.cpp: ::insertSSPDeclarations() – MSVC CRT (Visual Studio系が提供するCランタイム?)が独自でSSP機構(security cookie)を提供している – __security_cookieグローバル変数と__security_check_cookie関数を挿入する – → MSVC CRTは(libcではなく)スタートアップでSSPを提供しているということか? – MSVC CRTのソースがあるなら読みたい ?
  • X86WinEHState.cpp

– MSVC CRT

  • Personality?
  • _except_handler4?

** そのほか

  • glibc: debug/fortify_fail.c – __fortify_fail() → __fortify_fail_abort() — ここで実際にエラーメッセージを表示するが、argv[0] leakにつながるおそれがあるので普段 は表示しない —- buffer overflowでargv[0]の指すアドレスを書き換えると任意のアドレスをleak可能

Backlinks