Security Testing Hard to Reach Code Mathias Payer - - PowerPoint PPT Presentation

security testing hard to reach code
SMART_READER_LITE
LIVE PREVIEW

Security Testing Hard to Reach Code Mathias Payer - - PowerPoint PPT Presentation

Security Testing Hard to Reach Code Mathias Payer <mathias.payer@epfl.ch> https://hexhive.github.io 1 Vulnerabilities everywhere? 2 Challenge: broken abstractions C/C++ void log( int a) { printf("A: %d", a); } void vuln(


slide-1
SLIDE 1

1

Security Testing Hard to Reach Code

Mathias Payer <mathias.payer@epfl.ch> https://hexhive.github.io

slide-2
SLIDE 2

2

Vulnerabilities everywhere?

slide-3
SLIDE 3

3

Challenge: broken abstractions

C/C++ void log(int a) { printf("A: %d", a); } void vuln(char *str) { char *buf[4]; void (*fun)(int) = &log; strcpy(buf, str); ... fun(15); } ASM log: ... fun: .quad log vuln: movq log(%rip), 16(%rsp) ... call strcpy ... call 16(%rsp)

slide-4
SLIDE 4

4

Challenge: software complexity

Google Chrome: 76 MLoC Gnome: 9 MLoC Xorg: 1 MLoC glibc: 2 MLoC Linux kernel: 17 MLoC

Margaret Hamilton with code for Apollo Guidance Computer (NASA, ‘69) Brian Kernighan holding Lion’s commentary on BSD 6 (Bell Labs, ‘77) Chrome and OS ~100 mLoC, 27 lines/page, 0.1mm/page ≈ 370m

slide-5
SLIDE 5

5

Defense: Testing OR Mitigating?

Mitigations Software Testing

C/C++ void log(int a) { printf("A: %d", a); } void vuln(char *str) { char *buf[4]; void (*fun)(int) = &log; strcpy(buf, str); fun(15); } CHECK(fun, tgtSet); vuln("AAA"); vuln("ABC"); vuln("AAAABBBB"); strcpy_chk(buf, 4, str);

slide-6
SLIDE 6

6

Status of deployed defenses

  • Data Execution Prevention (DEP)
  • Address Space Layout

Randomization (ASLR)

  • Stack canaries
  • Safe exception handlers
  • Control-Flow Integrity (CFI):

Guard indirect control-flow

Memory

text data stack 0x4?? R-X 0x8?? RW- 0xf?? RW-

slide-7
SLIDE 7

7

Software testing: discover bugs security

slide-8
SLIDE 8

Fuzz testing

  • A random testing technique that mutates input

to improve test coverage

  • State-of-art fuzzers use coverage as feedback

to evolutionarily mutate the input

Input Generation

Tests Debug Exe Coverage Crashes

slide-9
SLIDE 9

Fuzz testing

  • A random testing technique that mutates input

to improve test coverage

  • State-of-art fuzzers use coverage as feedback

to evolutionarily mutate the input

Input Generation

Tests Debug Exe Coverage Crashes

slide-10
SLIDE 10

Fuzzing as bug finding approach

  • Fuzzing finds bugs effectively (CVEs)

– Proactive defense, part of testing – Preparing offense, part of exploit development

slide-11
SLIDE 11

Academic fuzzing research

slide-12
SLIDE 12

Fuzzing frontiers

slide-13
SLIDE 13

Fuzzing frontiers

Explore Explore new new paths paths Mine Mine existing existing code code Cross Cross unknown unknown borders borders

slide-14
SLIDE 14

Exploring hidden program paths

slide-15
SLIDE 15

Challenges for Fuzzers

start end

check1 check2 check3

bug

Shallow code paths Shallow code paths Deep code paths Deep code paths

  • Challenges

– Shallow coverage – Hard to find “deep” bugs

  • Root cause

– Fuzzer-generated inputs

cannot bypass complex sanity checks in the target program

slide-16
SLIDE 16

Limitations of existing approaches

  • Existing approaches focus on input generation

– AFL improvements (seed corpus generation) – Driller (selective concolic execution) – VUzzer (taint analysis, data-/control-flow analysis) – QSYM, Angora (symbolic/concolic analysis)

  • Limitations: high overhead, not scalable
  • Unable to bypass “hard” checks

– Checksum values – Crypto-hash values

slide-17
SLIDE 17

Non-Critical Checks (NCC)

  • Some checks are not intended to prevent bugs

– Checks on magic values, checksum, or hashes

  • Removing NCCs

– Won’t incur erroneous bugs, simplifies fuzzing

void main() { int fd = open(...); char *hdr = read_header(fd); if (strncmp(hdr, “ELF", 3) == 0) { // main program logic // ... } else { error(); } }

slide-18
SLIDE 18

Fuzzing by Program Transformation

  • Fuzzer fuzzes
  • When stuck

– Detect NCC candidates – Remove NCCs – Repeat

  • Verify crashes in
  • riginal program

Fuzzer (AFL) Program Transformer Crash Analyzer

Bug Reports False Positives Crashing inputs Inputs Transformed Programs

slide-19
SLIDE 19

Detecting NCCs: imprecision

  • Approximate NCCs as

edges connecting covered and uncovered nodes in CFG

– Over approximate, may

contain false positives

– Lightweight and simple to

implement

19

Covered Node Uncovered Node NCC Candidate

slide-20
SLIDE 20

Program transformation

  • Our approach: negate JCCs

– Easy to implement: static binary patching – Zero runtime overhead in resulting target program – CFG/trace/path constraints remains the same

start end A == B True branch False branch start end A != B True branch False branch Flipped Check

slide-21
SLIDE 21

Crash analysis: false positives?

Collect path constraints of

  • riginal program

(concolic tracing

  • n crashing input)

Path constraints SAT? False Positive Input to crash original program Timeout

slide-22
SLIDE 22

NCC example

int main (){ int x = read_input(); int y = read_input(); if (x > 0) { if (y == 0xdeadbeef) bug(); } } int main (){ int x = read_input(); int y = read_input(); if (x > 0) { if (y != 0xdeadbeef) bug(); } }

Collected path constraints Original Program Transformed Program { x > 0, y == 0xdeadbeef }

slide-23
SLIDE 23

NCC example

int main (){ int x = read_input(); int y = read_input(); if (x > 0) { if (y == 0xdeadbeef) bug(); } } int main (){ int x = read_input(); int y = read_input(); if (x > 0) { if (y != 0xdeadbeef) bug(); } }

Flipped check { x > 0, y != 0xdeadbeef } Collected path constraints

SAT

True BUG

flip Original Program Transformed Program

slide-24
SLIDE 24

NCC example 2

int main (){ int i = read_input(); if (i > 0) { func(i); } } void func(int i) { if (i <= 0) { bug(); } //... } int main (){ int i = read_input(); if (i > 0) { func(i); } } void func(int i) { if (i > 0) { bug(); } //... }

{ i > 0, i <= 0 } Collected path constraints Original Program Transformed Program

slide-25
SLIDE 25

NCC example 2

int main (){ int i = read_input(); if (i > 0) { func(i); } } void func(int i) { if (i <= 0) { bug(); } //... } int main (){ int i = read_input(); if (i > 0) { func(i); } } void func(int i) { if (i > 0) { bug(); } //... }

Flipped check Collected path constraints

UNSAT

False BUG

flip Original Program Transformed Program { i > 0, i > 0 }

slide-26
SLIDE 26

Comparison to Driller

  • Fuzzing explores code paths
  • Concolic execution explores

new code paths when “stuck”

  • Limitations

– Constraints solving is slow – Unable to bypass “hard” checks

Fuzzer Inputs Mutation program Crashes Constraint solving

slide-27
SLIDE 27

T-Fuzzing

  • Constraint solving and

fuzzing are decoupled

  • Constraint solving
  • nly for crashes
  • T-Fuzz detects bug for

“hard” checks, but cannot verify it

Fuzzer Inputs program Crashes Program Transformation Constraint solving program program program

slide-28
SLIDE 28

Limitations

  • NCC selection: transformation explosion
  • False bugs: fault before bug
  • Crash analyzer: constraint solving is hard

– Length of trace – Number of constraints – Non-termination

slide-29
SLIDE 29

Case study: CROMU_00030 (CGC)

void main() { int step = 0; Packet packet; while (1) { memset(packet, 0, sizeof(packet)); if (step >= 9) { char name[5]; int len = read(stdin, name, 128); printf("Well done, %s\n", name); return SUCCESS; } read(stdin, &packet, sizeof(packet)); if(strcmp((char *)&packet, "1212") == 0) return FAIL; if (compute_checksum(&packet) != packet.checksum) return FAIL; if (handle_packet(&packet) != 0) return FAIL; step ++; } }

Stack Buffer overflow bug C1: check on magic C2: checksum C3: authenticate user info Total time to find the bug: ~4h

slide-30
SLIDE 30

T-Fuzz summary

  • Core idea: mutate both program and input
  • T-Fuzz outperforms state-of-art fuzzers

– Improvement over Driller/AFL by 45%/58% – Bugs: 1 in LAVA-M and 3 in real-world programs

  • T-Fuzz future work

– LLVM-based program transformation – Crash analyzer: optimize constraint solving – NCC detection through static analysis

slide-31
SLIDE 31

Security-testing binary-only code

slide-32
SLIDE 32

RetroWrite: static binary rewriting

Processing Symbolization Reassemblable Assembly

  • Reg. Allocation

Optimization

slide-33
SLIDE 33

afl-retrowrite

  • Instrument basic blocks to update coverage map
  • To show interoperability, we reuse afl-gcc

– afl-gcc / afl-clang instruments assembly files – Our symbolized assembly files follow the format of

compiler-generated ASM files

– Enables reuse of existing transformations!

slide-34
SLIDE 34

Binary-only ASan (retrowrite-asan)

  • RetroWrite API to identify instrumentation sites
  • Two kinds of instrumentation:

– Allocation Instrumentation – Memory Check Instrumentation

If 0x100 is poisoned: terminate(); var = access(0x100);

slide-35
SLIDE 35

RetroWrite: static binary rewriting

Processing Symbolization Reassemblable Assembly

  • Reg. Allocation

Optimization

slide-36
SLIDE 36

Two-ended peripheral testing

slide-37
SLIDE 37

USBFuzz: explore peripheral space

Fake USB Device User-mode agent Linux Kernel Fuzzer: Input Generation

QEMU/KVM Virtual Environment

slide-38
SLIDE 38

USBFuzz Evaluation

  • ~60 new bugs discovered in recent kernels
  • 36 memory bugs (UaF / BoF)
  • 8 bugs fixed (with CVEs)
  • Bug reporting in progress
slide-39
SLIDE 39

Security testing hard-to-reach code

  • Fuzzing is an effective way to automatically test

programs for security violations (crashes)

– Key idea: optimize for throughput – Coverage guides mutation

  • T-Fuzz: mutate code and input
  • RetroWrite: efficient static rewriting
  • USBFuzz: enable fuzzing of peripherals

https://github.com/HexHive