コンパイラを読む
-
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
SecHack365
SecHack365は学生(と社会人)が、1年間指導を受けながらセキュリティに関連したり関連しなかったりするテーマで何かをつくる長期ハッカソン。無料で、交通費も全部出る。筆者は2018年度に、「ライブラリ・リンカ・ローダ・コンパイラetcを連携させたセキュリティ機能の開発」というテーマで参加した。