CS 240 Lab 10: Buffer Launch

Peter Mawhorter

Outline

  • x86 Stack (again)
  • Data Structures

x86 Stack (again)

Caller- vs. Callee-saved Registers

  • Callee saved:
    • %rbx, %rsp, %rbp, plus %r12-15
    • Push these at start and pop at end if you use them
    • Remember to push & pop in reverse order
  • Caller saved:
    • %rax, %rcx, %rdx, %rsi, %rdi, plus %r8-11
    • Push these before a function call if you’re using them
    • Pop them afterwards

call and ret

  • %rip is the Instruction Pointer register (not general)
  • call does the following:
    • Computes the normal next %rip address
    • Stores that address on the stack, moving %rsp down 8 bytes
    • Jumps to the provided address, updating %rip
      (Can be “near” or “far” but we’ll let gcc worry about that)
  • ret does the following:
    • Reads the value in memory at %rsp into %rip
    • Adds 8 to %rsp to finish popping that value

call and ret

  • Neither call nor ret modifies registers except %rsp/%rip.
    • Neither interacts with %rax
      (using it to hold the return value is just a convention)
  • Need to design functions so that anyone can call them safely
    • Hence the caller/callee save conventions and %rax
    • Also: %rsp must be a multiple of 16 when call happens
    • You can write working assembly that violates these conventions, but it won’t be working for long

Note: in 64-bit mode (which we are always in) call/callq and ret/retq makes no difference.

Using the stack

  • Allocate space by subtracting from %rsp
  • Optionally, use %rbp as a base while %rsp moves around
  • Use push and pop to store values easily
    • push moves %rsp down + stores a value
    • pop moves %rsp up + loads a value
    • Variants like pushq store different # of bytes
  • Moving %rsp and/or %rbp into registers like %rsi can be a sign that a pointer to stack-allocated memory is being sent to another function that will fill it in.

Buffer Overflows

  • Rocket Software UniData versions prior to 8.2.4 build 3003 and UniVerse versions prior to 11.3.5 build 1001 or 12.2.1 build 2002 suffer from a stack-based buffer overflow that can lead to remote code execution as the root user.
  • NETGEAR Nighthawk WiFi6 Router prior to V1.0.10.94 contains a buffer overflow vulnerability in various CGI mechanisms that could allow an attacker to execute arbitrary code on the device.
  • IBM Aspera Cargo 4.2.5 and IBM Aspera Connect 4.2.5 are vulnerable to a buffer overflow, caused by improper bounds checking. An attacker could overflow a buffer and execute arbitrary code on the system.
  • Adobe Dimension versions 3.4.7 (and earlier) is affected by a Stack-based Buffer Overflow vulnerability that could result in arbitrary code execution in the context of the current user. Exploitation of this issue requires user interaction in that a victim must open a malicious file.
  • All versions of the package node-bluetooth are vulnerable to Buffer Overflow via the findSerialPortChannel method due to improper user input length validation.
  • TensorFlow is an open source platform for machine learning. Prior to versions 2.12.0 and 2.11.1, there is a heap buffer overflow in TAvgPoolGrad. A fix is included in TensorFlow 2.12.0 and 2.11.1.
  • A stack-based buffer overflow in Fortinet FortiWeb 6.4 all versions, FortiWeb versions 6.3.17 and earlier, FortiWeb versions 6.2.6 and earlier, FortiWeb versions 6.1.2 and earlier, FortiWeb versions 6.0.7 and earlier, FortiWeb versions 5.9.1 and earlier, FortiWeb 5.8 all versions, FortiWeb 5.7 all versions, FortiWeb 5.6 all versions allows attacker to execute unauthorized code or commands via specially crafted command arguments.
  • sprintf in the GNU C Library (glibc) 2.37 has a buffer overflow (out-of-bounds write) in some situations with a correct buffer size. This is unrelated to CWE-676. It may write beyond the bounds of the destination buffer when attempting to write a padded, thousands-separated string representation of a number, if the buffer is allocated the exact size required to represent that number as a string. For example, 1,234,567 (with padding to 13) overflows by two bytes.
  • etc.

Don’t use C

  • Even the U.S. federal government now says not to use C.
  • Why teach C?
    • Many other languages are either implemented in C or modeled after C.
    • You get to learn how these issues work by interacting with them directly.
    • C and C++ are still very popular languages, so you may be forced to work with them.

Buffers

  • Any time user input happens, you need a buffer
    • Even just numbers are typed as digits first
    • Any time you have a buffer, you need to limit the size
    • But humans aren’t used to working with limited characters
  • Compromises happen, and if we’re not careful, excess data gets read in, corrupting memory after the buffer
  • If the buffer is on the stack, then the return address can become corrupted

Buffer Example

.data
prompt: .string "Enter a number: "
unused: .string "this will never appear\n"

.text
bad:
  sub $0x10, %rsp
  mov $prompt, %rdi
  call printf
  movq $0, (%rsp)
  mov %rsp, %rdi
  call gets
  mov (%rsp), %rax
  addq $0, %rsp // not sure why we need this
  addq $0x10, %rsp
  ret

.global main
main:
  call bad
  mov $0, %rax
  ret

never:
  sub $8, %rsp
  mov $unused, %rdi
  call printf
  add $8, %rsp
  ret

Buffer Example

  • Setup with gdb:
    • Use disas to get the address of the function you want
    • Use x /8gx $rsp to see what’s on the stack
    • Use info reg rsp to see %rsp address directly
    • Look at disas for current function to see how far %rsp has moved from return address = how much padding to include

Buffer Example

  • Exploit: 0123456789abcdefp^Q@
    • 0f = 16 bytes
    • p0x70 in ASCII
    • ^Q0x11
    • @0x40
Dump of assembler code for function never:
   0x0000000000401170 <+0>: sub    $0x8,%rsp

(401170 in little-endian order)

Buffer Example

  • On tempest:
    • never was now assembled at 0x40116c
    • l0x6c
    • ^Q0x11
    • @0x40
  • So we get: 0123456789abcdefl^Q@

Use hex2raw.bin instead of trying to type in your exploits!

Buffer Example

.data
prompt: .string "Enter a number: "
unused: .string "this will never appear\n"
.text

bad:
  sub $0x10, %rsp
  mov $prompt, %rdi
  call printf
  movq $0, (%rsp)
  mov %rsp, %rdi
  call gets
  mov (%rsp), %rax
  addq $0, %rsp // not sure why we need this
  addq $0x10, %rsp
  ret

.global main
main:
  call bad
  mov $0, %rax
  ret

never:
  sub $8, %rsp
  mov $unused, %rdi
  call printf
  add $8, %rsp
  ret

The initial empty stack diagram showing %rsp at 0x7ffff900 The second stack diagram showing %rsp at 0x7ffff898 with a return address of 0x4005ab occupying that 8-byte stack slot The third stack diagram showing %rsp at 0x7ffff888 (two slots = 16 bytes down) from before. The two new slots are empty. The fourth stack diagram showing %rsp still at 0x7ffff888. Now the two slots have been filled in with junk values 0x3736353433323130 and 0xc265646362613938 (bottom and top respectively). Additionally, the return address is now 0x4005b3 instead of 0x4005ab. The fifth stack diagram with the same contents as previously, but now %rsp is back up at 0x7ffff898 and the text “ret pops into %rip at this %point” is shown.