Low-Level-Sec-Reading

Low-level Software Security: Attacks and Defenses

The paper drives home three important facts before moving into particular attacks and defenses:

  1. Low-level software security is not just a concern for C/C++. While there are invariants assumed by programmers in high level languages (e.g. private variables only accessible by the owning class), many such invariants are not preserved at lower level representations (e.g. when Java is compiled to JVML bytecode).
  2. Large classes of errors can be avoided with known, simple techniques: maintaining additional information, validating that information at runtime, and halting execution if the information is invalid. However, fixing old legacy code to employ these techniques is not always simple.
  3. Attacks are usually very specific to the underlying architecture: instruction sets, memory layout, etc.

Attack 1: Corruption of return address

This attack is pretty simple to describe: if an attacker controls the data gets passed into a function, and that function sticks the data into an array without checking that the data will fit into the memory allocated for that array, then the data contents can overwrite critical addresses in the stack; in particular, the return address. If the attacker can stick arbitrary bytes into the return address, instead of the function call returning as the programmer would expect, the machine will start executing the instructions placed there by the attacker.

  • Constraints
    • stack is executable
    • no null bytes or zeroes in attack payload

Attack 2: Corruption of function pointers in the heap

This attack concerns data types that hold buffers and pointers; programmers assume that the data values wouldn't affect the pointer values, but overflowing buffers in the type can easily overwrite pointer values that are stored next to the buffers in memory. The example given is a struct holding a data buffer and a pointer to a function. The attacker overflows the data buffer in such a way that the function address points to the start of the data buffer, which contains malicious code. C++ frequently stores such object instances on the heap with vtable pointers, and thus is particularly vulnerable to these attacks.

  • Constraints
    • ability to determine the address of the heap memory being corrupted

Attack 3: Execution of existing code via corrupt pointers

The last two attacks allowed attackers to have the machine execute arbitrary machine code, provided by the attackers, aka direct code injection. However, there are other attacks in which the attacker may corrupt a function pointer to code in an "unreachable" library function, or a normal function "in scope" to the program, but in an unexpected order or with unexpected data arguments.
Such attacks are called jump-to-libc or return-to-libc attacks. These are preferred attacks for targets on architectures that disallow execution of machine code that resides in input data. They are part of a larger class of attacks involving indirect code injection.

  • Constraints
    • attackers need to know precise address locations of code useful to the attack

Attack 4: Corruption of data values that determine behavior

Attacks that operate solely through data corruption, without diverting target software from the expected path of machine-code execution, are referred to as data-only or non-control-data attacks. For example, programmers that initialize some global variable and then never write to it may assume its value is static throughout the program execution; but of course, an attacker may be able to modify the value. Suppose that variable is a string that is the name of an external command to execute!

  • Constraints
    • target software many only allow a certain amount of data to be corrupted
    • inherently constrained by the abilities of the target software; e.g. a calculator can only be corrupted so much (an invalid value perhaps). However, if target software embeds an interpreter for more general operations, then potentially dangerous operations can be injected.

Paper Review

  • Problem: The author claims that current literature lacks full, detailed descriptions of low-level attacks and their known defenses.
  • Approach: The paper aims to remedy this problem by providing full, detailed descriptions of four classes of attacks and six classes of practical defenses to guard against them.
  • Conclusions: The author’s conclusion is that while each individual defense only offers partial protection against certain attacks, the combination of these defenses forms an effective barrier against a wide swath of low-level attacks.
  • New ideas: The paper itself is largely a repackaging of existing research in a form that is highly detailed; so I would say there are not necessarily new ideas. However, I found two ideas that were at least noteworthy: (a) low-level security is not just a concern for C/C++. High level languages that have intermediate representations (e.g. Java compiled to JVML bytecode) is subject to the same kinds of low-level attacks at the bytecode level. (b) both attacks and defenses are extremely architecture specific. Attacks are very specific to low-level details like instruction sets, memory layout, etc., and the smallest of tweaks to such attacker assumptions can thwart their intentions.
  • Improvements: 1. Although the paper includes many figures detailing code and stack/heap memory snapshots, I found some of the succinct descriptions rather confusing. This style of paper would benefit from having an accompanying open source codebase with complete examples that could be compiled. 2. An extension of this idea would be to show how different attacks change depending on architecture, or to include other language examples.
  • New directions: 1. The paper claims at the start that low-level security is also a concern for high level languages, and yet all of the examples focus on C/C++. A new direction would be to explore how programmers using higher-level languages can protect themselves. 2. The paper also only focuses on thwarting specific intelligent attacks, subverting machines to execute malicious behavior. Defenses seem to only include those that would halt execution, resulting in a denial of service. But, this could be disastrous, depending on the threat model. So, a new direction would also be to detail defenses that don’t halt execution.