1 of 58

Decker: Attack Surface Reduction

via On-Demand Code Mapping

Chris Porter, Sharjeel Khan, Santosh Pande

ASPLOS 2023

2 of 58

Problem: Code reuse attacks

  • Software continues to be susceptible to existing and new code reuse attacks
  • Code reuse attacks are software attacks that leverage existing code in programs to perform some malicious action
  • They’re commonly built today with gadgets
  • Gadgets are code snippets that can be stitched together at runtime to form gadget chains that execute malicious behavior

3 of 58

ROP chain example

0x500000

...

0x5001f9

...

0x52e1fe

...

0x55720a

...

0x600000

pop rcx

ret

mov qword ptr [rcx], rax

ret

pop rax

ret

.text

ROP gadgets within the .text section

4 of 58

ROP chain example

0x55720a

0x2a

0x5001f9

0x800000

0x52e1fe

0x500000

...

0x5001f9

...

0x52e1fe

...

0x55720a

...

0x600000

pop rcx

ret

mov qword ptr [rcx], rax

ret

pop rax

ret

.text

Stack payload

5 of 58

ROP chain example

0x55720a

0x2a

0x5001f9

0x800000

0x52e1fe

0x500000

...

0x5001f9

...

0x52e1fe

...

0x55720a

...

0x600000

pop rcx

ret

mov qword ptr [rcx], rax

ret

pop rax

ret

.text

Stack payload

gadget 1 address -> pop rax; ret;

6 of 58

ROP chain example

0x55720a

0x2a

0x5001f9

0x800000

0x52e1fe

0x500000

...

0x5001f9

...

0x52e1fe

...

0x55720a

...

0x600000

pop rcx

ret

mov qword ptr [rcx], rax

ret

pop rax

ret

.text

Stack payload

7 of 58

ROP chain example

0x55720a

0x2a

0x5001f9

0x800000

0x52e1fe

0x500000

...

0x5001f9

...

0x52e1fe

...

0x55720a

...

0x600000

pop rcx

ret

mov qword ptr [rcx], rax

ret

pop rax

ret

.text

Stack payload

8 of 58

ROP chain example

0x55720a

0x2a

0x5001f9

0x800000

0x52e1fe

0x500000

...

0x5001f9

...

0x52e1fe

...

0x55720a

...

0x600000

pop rcx

ret

mov qword ptr [rcx], rax

ret

pop rax

ret

.text

Stack payload

9 of 58

ROP chain example

0x55720a

0x2a

0x5001f9

0x800000

0x52e1fe

0x500000

...

0x5001f9

...

0x52e1fe

...

0x55720a

...

0x600000

pop rcx

ret

mov qword ptr [rcx], rax

ret

pop rax

ret

.text

Stack payload

10 of 58

ROP chain example

0x55720a

0x2a

0x5001f9

0x800000

0x52e1fe

0x500000

...

0x5001f9

...

0x52e1fe

...

0x55720a

...

0x600000

pop rcx

ret

mov qword ptr [rcx], rax

ret

pop rax

ret

.text

Stack payload

11 of 58

ROP chain example

0x55720a

0x2a

0x5001f9

0x800000

0x52e1fe

0x500000

...

0x5001f9

...

0x52e1fe

...

0x55720a

...

0x600000

pop rcx

ret

mov qword ptr [rcx], rax

ret

pop rax

ret

.text

Stack payload

12 of 58

ROP chain example

  • Summary: gadget chaining works by leveraging multiple snippets across the existing code.

13 of 58

Debloating as defense

  • We know that software is bloated with unused code
    • The unneeded code contains gadgets which could be chained.
    • Why not remove it?
  • Debloating is a proposed defense against code reuse attacks
  • Shortcomings with current approaches
    • Too conservative, leaving too much code available to attackers
    • Or they compromise soundness by specializing the application for only certain inputs or features, which can lead to crashes or incorrect output

14 of 58

Soundness and may-use code

Definition

sound transformation: a program transformation that does not change the semantics of a program. Program transformations that induce crashes or cause incorrect output are unsound.

Definition

may-use code: code that may be used by the program under certain inputs or execution conditions.

15 of 58

Properties of debloating frameworks

Piece-wise

Chisel

Razor

BlankIt

Works on application

Works on library

Works on binary

No user input needed

No training needed

Is sound

Can debloat may-use code

16 of 58

Properties of debloating frameworks

Piece-wise

Chisel

Razor

BlankIt

Works on application

Works on library

Works on binary

No user input needed

No training needed

Is sound

Can debloat may-use code

17 of 58

Properties of debloating frameworks

Piece-wise

Chisel

Razor

BlankIt

Works on application

Works on library

Works on binary

No user input needed

No training needed

Is sound

Can debloat may-use code

18 of 58

Motivation

To the best of our knowledge, there is no general technique today that:

  • Works on the applications as a whole instead of libraries
  • Is sound
  • Can effectively debloat may-use code using dynamic contexts

19 of 58

Outline

  • Introduction
  • Overview
  • Decking
  • Evaluation
  • Conclusion

20 of 58

Decker overview

  • Decker is a constructive attack surface reduction technique
  • “Decks” are active sections of code that the program can effectively stand on. When a deck is unneeded, it can be removed.
  • Map only code that is currently needed by a running program
  • Disable all other code so accesses trigger a runtime exception
  • Granularity: implement with system code pages (4KB)
  • Compiler and runtime component to it

21 of 58

Threat model

  • Focus is on attack surface reduction
    • Assume the attacker can initiate and propagate the attack
    • Decker’s main goal
      • Incrementally expose the executable surface of a program by following its interprocedural control flow.
      • Breaks chains of gadgets, because all the gadgets that compose a chain are never dynamically exposed at the same time.

  • Integrity of indirect call targets is out of scope (orthogonal schemes like CFI and CPI are designed specifically to tackle it)

22 of 58

Decker Example

Image source: Wikipedia (deck)

23 of 58

Decker Example

A deck

Image source: Wikipedia (deck)

24 of 58

Decker Example

main()

foo()

bar()

25 of 58

Decker Example

main()

...

if(x)

foo()

bar()

foo()

...

bar()

...

main()

foo()

bar()

26 of 58

Decker Example

main()

...

if(x)

foo()

bar()

foo()

...

bar()

...

main()

foo()

bar()

Program counter

27 of 58

Decker Example

main()

...

if(x)

foo()

bar()

foo()

...

bar()

...

main()

foo()

bar()

28 of 58

Decker Example

main()

...

if(x)

foo()

bar()

foo()

...

bar()

...

main()

foo()

bar()

29 of 58

Decker Example

main()

...

if(x)

foo()

bar()

foo()

...

bar()

...

main()

foo()

bar()

30 of 58

Decker Example

main()

...

if(x)

foo()

bar()

foo()

...

bar()

...

main()

foo()

bar()

31 of 58

Decker Example

main()

...

if(x)

foo()

bar()

foo()

...

bar()

...

main()

foo()

bar()

32 of 58

Decker Example

main()

...

if(x)

foo()

bar()

foo()

...

bar()

...

main()

foo()

bar()

33 of 58

Decker Example

main()

...

if(x)

foo()

bar()

foo()

...

bar()

...

main()

foo()

bar()

34 of 58

Decker Example

main()

...

if(x)

foo()

bar()

foo()

...

bar()

...

main()

foo()

bar()

35 of 58

Example (Unprotected Program)

main()

foo()

bar()

Gadget chain components:

Gadget chain possible?

36 of 58

Example (Decker-Protected Program)

main()

foo()

bar()

Gadget chain components:

Gadget chain possible?

37 of 58

Example (Decker-Protected Program)

main()

foo()

bar()

Gadget chain components:

Gadget chain possible?

38 of 58

Example (Decker-Protected Program)

main()

foo()

bar()

Gadget chain components:

Gadget chain possible?

39 of 58

Outline

  • Introduction
  • Overview
  • Decking
  • Evaluation
  • Conclusion

40 of 58

Program structure and performance

  • Ideally, decking should be done at the entrances and exits of callsites
    • But such instrumentation can cause substantial runtime overhead
  • Loops in particular are problematic
    • Instrumentation for functions invoked inside of loops will execute repeatedly and kill performance
  • Leads us to 4 types of decks

41 of 58

Example

main()

A(func_ptr)

while {

B()

}

A(func_ptr)

B()

func_ptr()

B()

C()

main()

A()

B()

C()

42 of 58

Example

main()

A(func_ptr)

while {

B()

}

A(func_ptr)

B()

func_ptr()

B()

C()

main()

A()

B()

C()

43 of 58

Example

main()

A(func_ptr)

while {

B()

}

A(func_ptr)

B()

func_ptr()

B()

C()

main()

A()

B()

C()

44 of 58

Example

main()

A(func_ptr)

while {

B()

}

A(func_ptr)

B()

func_ptr()

B()

C()

main()

A()

B()

C()

45 of 58

Example

main()

A(func_ptr)

while {

B()

}

A(func_ptr)

B()

func_ptr()

B()

C()

main()

A()

B()

C()

Let’s discuss the 4 deck types!

46 of 58

1. Single deck - Occurs in a non-loop region

main()

A(func_ptr)

while {

B()

}

A(func_ptr)

B()

func_ptr()

B()

C()

main()

A()

B()

DECK-START

DECK-END

C()

map(RX, A)

47 of 58

2. Loop deck - Entrance to loops

main()

A(func_ptr)

while {

B()

}

A(func_ptr)

B()

func_ptr()

B()

C()

main()

A()

B()

DECK-START

DECK-END

C()

map(RX, B, C)

48 of 58

3. Reachable deck - Enter loop region via non-loop

main()

A(func_ptr)

while {

B()

}

A(func_ptr)

B()

func_ptr()

B()

C()

main()

A()

B()

DECK-START

DECK-END

C()

map(RX, B, C)

49 of 58

4. Indirect deck - Occurs at indirect calls

main()

A(func_ptr)

while {

B()

}

A(func_ptr)

B()

func_ptr()

B()

C()

main()

A()

B()

DECK-START

DECK-END

C()

map(RX, ?)

50 of 58

Indirect deck

  • Static function pointer analysis?
    • Narrows possible targets but is an overapproximation
    • Limits attack surface reduction
  • Instead, resolve this at runtime by
    • Passing function pointer to Decker runtime
    • Mapping the appropriate pages
  • We will violate the rule of thumb and instrument inside of loops!
    • Caching optimization needed

51 of 58

Linker

  • Problem: Functions in one deck can occupy the same code page as functions in another deck, and we do not want to inadvertently include gadgets from other functions when we map a code page as active.
  • Solution: Separate deck sets into disjoint sets (separate pages) and use a linker script to enforce it.

52 of 58

Outline

  • Introduction
  • Overview
  • Decking
  • Evaluation
  • Conclusion

53 of 58

Evaluation goals

  • What is the performance slowdown due to Decker?
  • What is the gadget reduction for applications that use Decker?
  • Can Decker
    • break real gadget chains in the benchmarks and real-world applications to be able to stop gadget-based attacks?
    • render JOP gadgets ineffective in practical scenarios, including Windows?

54 of 58

Evaluation summary

  • Performance
    • ~5% average overhead for SPEC 2017, GNU coreutils, and nginx
  • Security
    • 70-87% total gadget reduction
    • Equals or (in many cases) improves on comparable prior work
    • Achieves this without compromising soundness

55 of 58

Evaluation summary

  • Gadget chain-breaking
    • An unexplored metric that we introduce and report for Decker
    • Decker fully breaks a popular syscall ROP chain in all cases
    • Decker removes functionality which is critical for JOP chains to be successful from nginx in both Linux and Windows

56 of 58

Evaluation details

  • Please see paper for detailed results and analysis!

57 of 58

Conclusion

  • Decker
    • an attack surface reduction technique for applications
    • is sound and enables may-use code on-demand
    • requires zero training, user inputs, or specifications
  • Acceptable performance slowdowns and strong total gadget reductions across two benchmark suites and nginx
  • Gadget chain-breaking study
    • Breaks automatically generated syscall ROP chains
    • Strong evidence that useful JOP chains are substantially hampered or impossible to build for both Linux and Windows

58 of 58

Thank you!

  • Check out our artifact!
    • https://zenodo.org/record/7319957
    • Link is available in the paper, too

  • Questions?