Exploiting Buffer Overflows: Stored IP, Func Pointer, and Write Corruption, Lecture notes of Computer Science

An algorithm designed to exploit three common classes of security vulnerabilities: stack-based buffer overflows that corrupt a stored instruction pointer, buffer overflows that corrupt a function pointer, and buffer overflows that corrupt the destination address used by instructions that write to memory. how user input can corrupt these pointers and write instruction destinations, and how to use Pin to intercept and analyze the execution flow to detect and prevent such vulnerabilities.

Typology: Lecture notes

2021/2022

Uploaded on 09/12/2022

deffstar
deffstar 🇬🇧

4.6

(17)

240 documents

1 / 110

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
University of Oxford
Computing Laboratory
MSc Computer Science Dissertation
Automatic Generation of Control Flow Hijacking
Exploits for Software Vulnerabilities
Author:
Sean Heelan
Supervisor:
Dr. Daniel Kroening
September 3, 2009
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24
pf25
pf26
pf27
pf28
pf29
pf2a
pf2b
pf2c
pf2d
pf2e
pf2f
pf30
pf31
pf32
pf33
pf34
pf35
pf36
pf37
pf38
pf39
pf3a
pf3b
pf3c
pf3d
pf3e
pf3f
pf40
pf41
pf42
pf43
pf44
pf45
pf46
pf47
pf48
pf49
pf4a
pf4b
pf4c
pf4d
pf4e
pf4f
pf50
pf51
pf52
pf53
pf54
pf55
pf56
pf57
pf58
pf59
pf5a
pf5b
pf5c
pf5d
pf5e
pf5f
pf60
pf61
pf62
pf63
pf64

Partial preview of the text

Download Exploiting Buffer Overflows: Stored IP, Func Pointer, and Write Corruption and more Lecture notes Computer Science in PDF only on Docsity!

University of Oxford

Computing Laboratory

MSc Computer Science Dissertation

Automatic Generation of Control Flow Hijacking

Exploits for Software Vulnerabilities

Author:

Sean Heelan

Supervisor:

Dr. Daniel Kroening

September 3, 2009

Contents

List of Figures v

List of Tables vii

List of Code Listings ix

iv

vi

List of Tables

List of Code Listings

  • Abstract Acknowledgements xi
  • 1 Introduction
    • 1.1 Introduction
    • 1.2 Motivation
    • 1.3 Related Work
    • 1.4 Thesis
    • 1.5 Contributions of this Work
    • 1.6 Overview
  • 2 Problem Definition
    • 2.1 Operating System and Architecture Details
      • 2.1.1 CPU Architecture
      • 2.1.2 Operating system
    • 2.2 Run-time protection mechanisms
      • 2.2.1 Address Space Layout Randomisation
      • 2.2.2 Non-Executable Memory Regions
      • 2.2.3 Stack Hardening
      • 2.2.4 Heap Hardening
      • 2.2.5 Protection Mechanisms Considered
    • 2.3 Computational Model
    • 2.4 Security Vulnerabilities
      • 2.4.1 When is a Bug a Security Vulnerability?
      • 2.4.2 Definition of an Exploit
    • 2.5 Direct Exploits
      • 2.5.1 Manually Building Direct Exploits
      • 2.5.2 Components Required to Generate a Direct Exploit
    • 2.6 Indirect Exploits
      • 2.6.1 Manually Building Indirect Exploits
      • 2.6.2 Components Required to Generate an Indirect Exploit
    • 2.7 The Problem we Aim to Solve
  • 3 Algorithms for Automatic Exploit Generation
    • 3.1 Stage 1: Instrumentation and run-time Analysis
      • 3.1.1 Dynamic Binary Instrumentation
      • 3.1.2 Taint Analysis
      • 3.1.3 Building the Path Condition
    • 3.2 Stage 2: Building the Exploit Formula
      • 3.2.1 Γ: An Exploit Generation Algorithm
      • 3.2.2 Processing Taint Analysis Information to Find Suitable Shellcode Buffers
      • 3.2.3 Gaining Control of the Programs Execution
      • 3.2.4 Building the Exploit Formula
    • 3.3 Stage 3: Solving the Exploit Formula
      • 3.3.1 Quantifier-free, Finite-precision, Bit-vector Arithmetic
      • 3.3.2 Decision Procedures for Bit-vector Logic
  • 4 System Implementation
    • 4.1 Binary Instrumentation
      • 4.1.1 Hooking System Calls
      • 4.1.2 Hooking Thread Creation and Signals
      • 4.1.3 Hooking Instructions for Taint Analysis
      • 4.1.4 Hooking Instructions to Detect Potential Vulnerabilities
      • 4.1.5 Hooking Instructions to Gather Conditional Constraints
    • 4.2 Run-time Analysis
      • 4.2.1 Taint Analysis
      • 4.2.2 Gathering Conditional Constraints
      • 4.2.3 Integrity Checking of Stored Instruction and Function Pointers
      • 4.2.4 Integrity Checking for Write Instructions
    • 4.3 Exploit Generation
      • 4.3.1 Shellcode and Register Trampolines
      • 4.3.2 Locating Potential Shellcode Buffers
      • 4.3.3 Controlling the EIP
      • 4.3.4 Constructing the Path Condition
      • 4.3.5 From Formula to Exploit
  • 5 Experimental Results
    • 5.1 Testing Environment
      • 5.1.1 Shellcode Used
    • 5.2 Stored Instruction Pointer Corruption
      • 5.2.1 Generating an Exploit for a strcpy based stack overflow
      • 5.2.2 Generating an Exploit for the XBox Media Center Application
    • 5.3 Stored Pointer Corruption
      • 5.3.1 Generating an Exploit in the Presence of Arithmetic Modifications
    • 5.4 Write Operand Corruption
  • 6 Conclusion and Further Work
    • 6.1 Automatic Memory-Layout Manipulation
    • 6.2 Multi-Path Analysis
    • 6.3 Identifying Memory Corruption Side-Effects
    • 6.4 Write-N-Bytes-Anywhere Vulnerabilities
    • 6.5 Automatic Shellcode Generation
    • 6.6 Defeating Protection Mechanisms
    • 6.7 Assisted Exploit Generation
    • 6.8 Conclusion
  • Bibliography
  • Appendices
  • A Sample Vulnerabilities
    • A.1 Vulnerability
    • A.2 XBMC Vulnerability
    • A.3 Function Pointer Vulnerability (No Arithmetic Modification of Input)
    • A.4 Function Pointer Vulnerability (Arithmetic Modification of Input)
    • A.5 Corrupted Write Vulnerability
  • B Sample Exploits
    • B.1 Stack overflow (strcpy) Exploit
    • B.2 XBMC Exploit
    • B.3 Function Pointer Exploit (No Arithmetic Modification of Input)
    • B.4 Function Pointer Exploit (Arithmetic Modification of Input)
    • B.5 Write Operand Corruption Exploit
    • B.6 AXGEN Sample Run
  • 2.1 Stack convention diagram
  • 2.2 Updating M : mov DWORD PTR [eax], 0x00000000
  • 2.3 Updating M : mov DWORD PTR [eax], 0x01020304
  • 2.4 Stack configuration before/after the vulnerable strcpy
  • 2.5 Returning to shellcode via a register trampoline
  • 3.1 High level algorithm for automatic exploit generation
  • 3.2 A simple lattice describing taintedness
  • 3.3 A taint lattice ordered on arithmetic complexity
  • 3.4 A taint lattice ordered on arithmetic and conditional complexity
  • 5.1 Run-time Analysis Information
  • 5.2 Taint Buffer Analysis
  • 5.3 Exploit Formula Statistics
  • 5.4 Run-time Analysis Information
  • 5.5 Taint Buffer Analysis (Top 5 Shellcode Buffers, ordered by size)
  • 5.6 Exploit Formula Statistics
  • 5.7 Run-time Analysis Information
  • 5.8 Taint Buffer Analysis
  • 5.9 Exploit Formula Statistics
  • 5.10 Run-time Analysis Information
  • 5.11 Taint Buffer Analysis
  • 5.12 Exploit Formula Statistics
  • 2.1 “Stack-based overflow vulnerability”
  • 2.2 “Stack-based overflow vulnerability (write offset corruption)”
  • 4.1 “Filtering x86 instructions”
  • 4.2 “Determining the operand types for a mov instruction”
  • 4.3 “Inserting the analysis routine callbacks for a mov instruction”
  • 4.4 “Inserting a callback on EFLAGS modification”
  • 4.5 “Inserting callbacks on a conditional jump”
  • 4.6 “Simulating a mov instruction”
  • 4.7 “The variables defined in a TaintByte object”
  • 4.8 “A structure for storing the operands involved in modifying the EFLAGS”
  • 4.9 “Updating the operands associated with EFLAGS indices”
  • 4.10 “Checking if the value at ESP is tainted for a ret instruction”
  • 4.11 “Constructing a set of shellcode buffers”
  • 4.12 “Gaining control of the EIP for a direct exploit”
  • 4.13 “Recursively building the path condition”
  • 4.14 “Encoding conditional jump instructions”
  • 5.1 “A strcpy based stack overflow”
  • 5.2 “A function pointer overflowz“
  • 5.3 “Arithmetic modification of the input”
  • 5.4 “A function containing a write corruption vulnerability”
  • 6.1 “Single-path analysis leads to incomplete transition relations”
  • A.1 “A strcpy vulnerability”
  • A.2 “XBMC vulnerable function”
  • A.3 “A function pointer overflow”
  • A.4 “A function pointer overflow with linear arithmetic”
  • A.5 “A write-based vulnerability”
  • B.1 “A stack overflow exploit”
  • B.2 “An exploit for XBMC”
  • B.3 “A function pointer overflow exploit”
  • B.4 “A function pointer overflow exploit with linear arithmetic”
  • B.5 “An exploit for write operand corruption”

x

xii

Abstract

Software bugs that result in memory corruption are a common and dangerous feature of systems developed in certain programming languages. Such bugs are security vulnerabilities if they can be leveraged by an attacker to trigger the execution of malicious code. Determining if such a possibility exists is a time consuming process and requires technical expertise in a number of areas. Often the only way to be sure that a bug is in fact exploitable by an attacker is to build a complete exploit. It is this process that we seek to automate. We present a novel algorithm that integrates data-flow analysis and a decision procedure with the aim of automatically building exploits. The exploits we generate are constructed to hijack the control flow of an application and redirect it to malicious code. Our algorithm is designed to build exploits for three common classes of security vulnerability; stack-based buffer overflows that corrupt a stored instruction pointer, buffer overflows that corrupt a function pointer, and buffer overflows that corrupt the destination address used by instructions that write to memory. For these vulnerability classes we present a system capable of generating functional exploits in the presence of complex arithmetic modification of inputs and arbitrary constraints. Exploits are generated using dynamic data-flow analysis in combination with a decision procedure. To the best of our knowledge the resulting implementation is the first to demonstrate exploit generation using such techniques. We illustrate its effectiveness on a number of benchmarks including a vulnerability in a large, real-world server application.

Chapter 1

Introduction

1.1 Introduction

In this work we will consider the problem of automatic generation of exploits for software vulnerabilities. We provide a formal definition for the term “exploit” in Chapter 2 but, informally, we can describe an exploit as a program input that results in the execution of malicious code^1. We define malicious code as a sequence of bytes injected by an attacker into the program that subverts the security of the targeted system. This is typically called shellcode. Exploits of this kind often take advantage of programmer errors relating to memory management or variable typing in applications developed in C and C++. These errors can lead to buffer overflows in which too much data is written to a memory buffer, resulting in the corruption of unintended memory locations. An exploit will leverage this corruption to manipulate sensitive memory locations with the aim of hijacking the control flow of the application. Such exploits are typically built by hand and require manual analysis of the control flow of the appli- cation and the manipulations it performs on input data. In applications that perform complex arithmetic modifications or impose extensive conditions on the input this is a very difficult task. The task resembles many problems to which automated program analysis techniques have been already been successfully applied [38, 27, 14, 43, 29, 9, 10, 15]. Much of this research describes systems that consist of data-flow analysis in combination with a decision procedure. Our approach extends techniques previously used in the context of other program analysis problems and also encompasses a number of new algorithms for situations unique to exploit generation.

1.2 Motivation

Due to constraints on time and programmer effort it is necessary to triage software bugs into those that are serious versus those that are relatively benign. In many cases security vulnerabilities are of critical importance but it can be difficult to decide whether a bug is usable by an attacker for malicious purposes or not. Crafting an exploit for a bug is often the only way to reliably determine if it is a security vulnerability. This is not always feasible though as it can be a time consuming activity and requires low-level knowledge of file formats, assembly code, operating system internals and CPU architecture. Without a mechanism to create exploits developers risk misclassifying bugs. Classifying a security-relevant bug incorrectly could result in customers being exposed to the risk for an extended period of time. On the other hand, classifying a benign bug as security-relevant could slow down the development process and cause extensive delays as it is investigated. As a result, there has been an increasing interest into techniques applicable to Automatic Exploit Generation (AEG).

(^1) We consider exploits for vulnerabilities resulting from memory corruption. Such vulnerabilities are among the most common encountered in modern software. They are typically exploited by injecting malicious code and then redirecting execution to that code. Other vulnerabililty types, such as those relating to design flaws or logic problems, are not considered here.

The challenge of AEG is to construct a program input that results in the execution of shellcode. As the starting point for our approach we have decided to use a program input that is known to cause a crash. Modern automated testing methods routinely generate many of these inputs in a testing session, each of which must be manually inspected in order to determine the severity of the underlying bug. Previous research on automated exploit generation has addressed the problem of generating inputs that corrupt the CPU’s instruction pointer. This research is typically criticised by pointing out that crashing a program is not the same as exploiting it [1]. Therefore, we believe it is necessary to take the AEG process a step further and generate inputs that not only corrupt the instruction pointer but result in the execution of shellcode. The primary aim of this work is to clarify the problems that are encountered when automatically generating exploits that fit this description and to present the solutions we have developed. We perform data-flow analysis over the path executed as a result of supplying a crash-causing input to the program under test. The information gathered during data-flow analysis is then used to generate propositional formulae that constrain the input to values that result in the execution of shellcode. We motivate this approach by the observation that at a high level we are trying to answer the question “Is it possible to change the test input in such a way that it executes attacker specified code?”. At its core, this problem involves analysing how data is moved through program memory and what constraints are imposed on it by conditional statements in the code.

1.3 Related Work

Previous work can be categorised by their approaches to data-flow analysis and their final result. On one side is research based on techniques from program analysis and verification. These projects typically use dynamic run-time instrumentation to perform data-flow analysis and then build formulae describing the programs execution. While several papers have discussed how to use such techniques to corrupt the CPU’s instruction pointer they do not discuss how this corruption is exploited to execute shellcode. Significant challenges are encountered when one attempts to take this step from crashing the program to execution of shellcode. Alternatives to the above approach are demonstrated in tools from the security community [37, 28] that use ad-hoc pattern matching in memory to relate the test input to the memory layout of the program at the time of the crash. An exploit is then typically generated by using this information to complete a template. This approach suffers from a number of problems as it ignores modifications and constraints applied to program input. As a result it can produce both false positives and false negatives, without any information as to why the exploit failed to work or failed to be generated. The following are papers that deal directly with the problem of generating exploits:

(i) Automatic Patch-Based Exploit Generation is Possible: Techniques and Implications - This paper [11] is the closest academic paper, in terms of subject matter, to our work. An approach is proposed and demonstrated that takes a program P and a patched version P ′, and produces a sample input for P that exercises the vulnerability patched in P ′. Using the assumption that any new constraints added by the patched version relate to the vulnerability they generate an input that violates these constraints but passes all others along a path to the vulnerability point (e.g. the first out of bounds write). The expected result of providing such an input to P is that it will trigger the vulnerability. Their approach works on binary executables, using data-flow analysis to derive a path condition and then solving such conditions using the decision procedure STP to produce a new program input. As the generated program input is designed to violate the added constraints it will likely cause a crash due to some form of memory corruption. The possibility of generating an exploit that results in shellcode execution is largely ignored. In the evaluation a specific case in which the control flow was successfully hijacked is given, but no description of how this would be automatically achieved is described.

(ii) Convicting Exploitable Software Vulnerabilities: An Efficient Input Provenance Based Approach - This paper [35] again focuses on exploit generation but uses a “suspect input” as its starting point instead

exploited, it is necessary to limit our research to a subset of the possible exploit types. In our investigation we impose the following practical limits^2 :

  1. Data derived from user input corrupts a stored instruction pointer, function pointer or the destination location and source value of a write instruction.
  2. Address space layout randomisation may be enabled on the system but no other exploit prevention mechanisms are in place.
  3. Shellcode is not automatically generated and must be provided to the exploit generation algorithm.

1.5 Contributions of this Work

In the previous work there is a gap between the practicality of systems like Byakugan and the reliability and theoretical soundness of systems like [11]. In an attempt to close this gap we present a novel system that uses data-flow analysis and constraint solving to generate control flow hijacking exploits. We extend previous research by describing and implementing algorithms to not only crash a program but to hijack its control flow and execute malicious code. This is crucial if we are to reliably categorise a bug as exploitable or not [1]. The contributions of this dissertation are as follows:

  1. We present the first formalisation of the core requirements for a program input to hijack the control flow of an application and execute malicious code. This contains a description of the conditions on the path taken by such an input for it to be an exploit, as well as the information required to generate such an input automatically. This formalisation is necessary if we are to discuss generating such exploits in the context of existing research on software verification and program analysis. The formalisation should also prove useful for future investigations in this area. (Chapter 2).
  2. Building on the previous definitions we present several algorithms to extract the required information from a program at run-time. First, we present instrumentation and taint analysis algorithms that are called as the program is executed. We then describe a number of algorithms to process the data gathered during run-time analysis and from this data build a propositional formula expressing the conditions required to generate an exploit. Finally, we illustrate how one can build an exploit from such a formula using a decision procedure. (Chapters 3 & 4).
  3. We present the results of applying the implementation of the above algorithms to a number of vul- nerabilities. These results highlight some of the differences between test-case generation and exploit generation. They also provide the test of our thesis and, to the best of our knowledge, are the first demonstration of exploit generation using data-flow analysis and a decision procedure. (Chapter 5).
  4. We outline a number of future research areas we believe are important to the process of automatic exploit generation. These areas may provide useful starting points for further research on the topic. (Chapter 6).

1.6 Overview

Chapter 2 consists of a description of how the exploit types we will consider function, followed by a formal- isation of the components required to build such exploits. Chapter 3 contains the main description of our algorithm and the theory it is built on. In Chapter 4 we outline the implementation details related to the algorithms described in Chapter 3. Chapter 5 contains the results of running our system on both test and real-world vulnerabilities. Finally, Chapter 6 discusses suggestions for further work and our conclusions.

(^2) The meaning and implications of these limits are explained in later chapters.

Chapter 2

Problem Definition

The aim of this Chapter is to introduce the vulnerability types that we will consider and describe the main problems involved in generating exploits for these vulnerability types. We will then formalise the relevant concepts so they can be used in later chapters. We begin by describing some system concepts that are necessary for the rest of the discussion.

2.1 Operating System and Architecture Details

2.1.1 CPU Architecture

CPU architectures vary greatly in their design and instruction sets. As a result, we will tailor our discussion and approach towards a particular standard. From this point onwards, it is assumed our targeted architecture is the 32-bit Intel x86 CPU. On this CPU a byte is 8 bits, a word is 2 bytes and a double word, which we will refer to as a dword, is 4 bytes. The x86 has a little-endian, Complex Instruction Set Computer (CISC) architecture. Each assembly level instruction on such an architecture can have multiple low-level side effects.

Registers

The 32-bit x86 processors define a number of general purpose and specialised registers. While the purpose of most of these registers is unimportant for our discussion we must consider four in particular. These are as follows

  1. Extended Instruction Pointer (EIP) - Holds the memory location of the next instruction to be executed by the CPU.
  2. Extended Base Pointer (EBP) - Holds the address of the current stack frame. This will be explained in our description of the stack memory region.
  3. Extended Stack Pointer (ESP) - Holds the address of the top of the stack. Again, this will be explained in our description of the stack.
  4. Extended Flags Register (EFLAGS) - This register represents 32 different flags that may be set or unset (usually as a side effect) by an instruction. Some common flags are the zero flag, sign flag, carry flag, parity flag and overflow flag, which indicate different properties of the last instruction to be executed. For example, if the operands to the sub instruction are equal then the zero flag will be set to true (a number of other flags may also be modified).

While registers are dword sized, some of their constituent bytes may be directly referenced. For example, a reference to EAX returns the full 4 byte register value, AX returns the first 2 bytes of the EAX register, AL returns the first byte of the EAX register, and AH returns the second byte of the EAX register. A full