
ENPM809V
Vulnerability Research
Agenda
- What is Vulnerability Research?
- Code Auditing/Reverse Engineering
- Fuzzing
- Symbolic Execution
- Code Property Graphs


Overview

What is Vulnerability Research?
- Vulnerability research is the process of finding bugs/vulnerabilities.
- It is not related to exploitation, though relies on many exploitation primitives
- These bugs/vulnerabilities are known as bug classes
- Relies on understanding what tools are needed to complete a job.
- Automated vs. Manual introspection
- Fuzzing vs. Symbolic Execution
- ML????

What Vulnerability Research is NOT
- Automated exploitation writing
- Targeted exploitation writing (that is what the beginning of the course is for).
- Sometimes, the same bug can appear in Windows and Linux, but has to be exploited in different ways

Why is this important?
- It is not just for finding bugs to exploit.
- Helpful for finding bugs to fix
- Bug bounty programs
- Google Project 0
- Reverse engineers/code auditors
- It is better to find the bug before an adversary does.
- Helpful for finding bugs to fix

What can you expect?
- Expect days, weeks, months, where you don't find a vulnerability.
- Very Tedious Work
- Overwhelming
- Frustrating
- Competitive
- Exciting
- Discovery
- Rewarding

Vulnerability Research Principals

What are we looking for?
- We are looking for bugs! We aren't necessarily looking to exploit them.
- We have been through many different kind of bug classes throughout this class
- Buffer Overflow (stack and heap)
- Race Condition
- Integer overflow
- etc.

How do we find them?
- Automated or Manual
-
- Tooling
- Code Auditing
- etc.

What is the best way to do it?
- Define objectives!
- Determine the goals of the vulnerability research effort
- Begin by understanding the requirements
- Are you protecting from anyone with low-permissions?
- Does the user have root?
- Are you trying to strictly find zero days?
- This can be open-ended, but a goal is super important!

Scope
- Understanding the scope is important to determine what bugs to find
- Do you have a method to execute arbitrary code?
- What privileges do you have?
- Persistence?
- Security Defeats?
- root/Administrator?
- Local vs. Remote

Example: Arbitrary Code Execution
Find a method to gain arbitrary execution via:
- Obtaining credentials
- A bug in the web-server hosted by the device
- A bug in a kernel module
- WLAN?
- Bluetooth?
- Custom?
- A bug in the (userspace) Bluetooth driver
- A bug in any Internet-facing attack surface
- A bug in the device’s browser that can be exploited by malicious JavaScript

Example: Local
General Goals
- Persistence
- Privilege Escalation
- Circumvent Security Measures
Specific Goal:
- Finding a kernel bug that allows privilege escalation
- Finding a method of writing data to the Windows Registry
- Finding a way to use a known kernel bug to escape seccomp

Specificity
The more specific your goals are, the better!
This will help you narrow where you need to look to find bugs.

How to be more specific?
- If you need to figure out how to be more specific, perform Surface Area Enumeration
- Get Acquainted With The Target
- Review Source Code
- Preform Preliminary Reverse Engineering.
- This will give you starting points.

Tooling

What Kind of Tools Will I need?
- Some of the tools we already covered
- Debuggers (GDB, WinDBG, x64DBG)
- Disassemblers
- Decompilers

When to use tools?
- Based on the target environment:
- What debugger would you use for Windows?
- Is Ghidra the best tool to reverse engineer Java?
- How do we reverse engineer .NET files?

When to use tools?
- Java/Android
- Dex2Jar
- Jadx GUI
- fernflower
- etc.
- .NET
- IL Spy
- DotNet IL Editor
- dotPeek

When to use tools?
- Hex Editors
- 010 Editor - GUI Editor, Has templates to make parsing easier
- XXD/HxD - Free
- Modifying ELF/PE Files
- objcopy
- Using Disassembler to do the same thing is also helpful too
- Radare2 has excellent patching capabilities
- Some disassemblers like Ghidra require a plugin to do this.

Tracing Program Flow
- Use various tracing mechanism and tools to track program flow
- Helps us understand what the behavior is based on various input
- Can also help to generate stack traces.
- Some tools include:
- strace, ltrace, ftrace, eBPF - Linux
- DTrace - Solaris, MacOS, FreeBSD
- Event Tracing for Windows, Procmon - WIndows
- Debuggers can also do the same thing

Creating Your Own Tool
- Disassembler Plugins
- Standalone plugins
- Ghidra/Binary Ninja Headless Mode
- Few goals the creating these:
- Automate repetitive Tasks
- Parse custom software
- Non standard file formats (e.g. not a PDF or ELF)

Code Auditing

Code Auditing
- The most boring yet extremely effective way of finding vulnerabilities
- The process of reading and reviewing software to gain a deep fundamental understanding.
- Nothing is as effective as reading source code for vulnerabilities
- Open Source Software
- Find the source code online somewhere
- What if we don't the the source code?
- We analyze with Ghidra

When to do Code Auditing
- Do not do it initially. Use techniques like Fuzzing, tracing, etc. to narrow scope.
- Sometimes, code is huge.
- Dynamic Execution and fuzzing can point out bugs.
- When you need to determine how code is behaving.
- What is the data being used?
- How is data being used

Classifying Bugs
- We spent time finding bugs, but how does a vulnerability engineer classify them?
- First Order Bugs - Bug occurs directly from input data
- We directly control the length of the memory copy
- Second Order Bugs - Bugs that occur indirectly from from attacker data
- Reusing memory that is freed
- The attacker data influences control flow, leading to a problematic control flow

How to make it efficient
- Automate automate automate
- This will help narrow down user-input
- Not feasible review all of the code for a large project.
- Prioritize certain bug classes
- Do we care about buffer overflows, use-after-free, etc.
- Build a model of how the data flows
- Write a program that generates Input for you
-
Focus on how data and input is used and keep notes.
- Read source code/reverse engineer in targeted areas

From here on out...
- We are going to talk about automating bug finding
- We have learned many bug classes and how to analyze source code.
- This is not a reverse engineering course - Take ENPM696.
- We have done a lot of static and dynamic analysis

Static Analysis
Scripting

Reasons to Use Static Analysis
- Perform preliminary analysis to look for low-hanging fruit
- Found interesting strings in a string search and want to see how they are used
- Found a potential bug using other methods such as fuzzing
- Quickly scan for known bugs

Problems with this?
- Very tedious and inefficient
- For large code-bases, need a lot of resources to perform static analysis manually (not generally possible)
- Scripting can solve many of these issues!
- Automates repetitive tasks
- Batch analysis
- Running the same analysis or extraction on multiple files
- Custom analysis or decompilation
- Deobfuscation
- Creating custom tooling not built in

Scripting
- All reverse engineering tools has a way of extending their platform
- IDA Pro - Scripting API
- Python
- Ghidra - Ghidra Scripting
- Java, jython, and recently added native Python
- Binary Ninja
- C++, Python, and Rust (still unstable)
- Rradare2
- C++ and Python

Scripting
- All reverse engineering tools has a way of extending their platform
- IDA Pro - Scripting API
- Python
- Ghidra - Ghidra Scripting
- Java, jython, and recently added native Python
- Binary Ninja
- C++, Python, and Rust (still unstable)
- Rradare2
- C++ and Python

PyGhidra
- Native Python scripting for Ghidra (from 11.3 forward)
- Installation
- Download Ghidra
- python3 -m pip install pyghidra

Java
- Download Eclipse
- Open Ghidra
-
- Create a new script and edit in Eclipse
- Not so easy - https://www.sentinelone.com/labs/a-guide-to-ghidra-scripting-development-for-malware-researchers/

Main Components
- GhidraScriptAPI - https://ghidra.re/ghidra_docs/api/ghidra/app/script/GhidraScript.html
- FlatProgramAPI - https://ghidra.re/ghidra_docs/api/ghidra/program/flatapi/FlatProgramAPI.html

Ghidra Script API
- Contains many functions to do some get basic information and define user input
- Go to a function
- Ask for an integer
- Get version of Ghidra
- Open program
- Run

FlatProgramAPI
- Contains many of the functions you need to do analysis on a binary
- FindBytes
- createQWrod, createSymbol, etc (very similar to that in GUI)
- disassemble
- getBytes

Example/Demo

Your Turn!
- You are going to make a Ghidra script on the binary we are going to do some vulnerability research on!
- Objectives of the script:
- List all functions
- Disassemble a function
- Decompile a function
- List important parameters
- Afterwards, practice with GhidraGolf! https://github.com/ghidragolf/putting_green
- https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Features/PyGhidra/README.md

Symbolic Execution

What is it?
- A program analysis technique used to explore possible execution paths.
- Tracks various code paths based on variable input data being passed in.
- Tracks outputs as well
- Not just used for vulnerability research
- Test case generation
- Program verification

Components of Symbolic Executors
- SAT Solvers (Boolean Satisfiability Solver) - Used to determine if there exists an assignment of true/false to variable that make a given boolean formula.
- SMT Solver - Satisfiability Modulo Theory
- Built on SAT Solvers to extend Boolean logic to higher classical first-order equations
- BitVectors
- Arrays
- Floating Point
- Regular Expression
- Example SMT solver is Z3
- Built on SAT Solvers to extend Boolean logic to higher classical first-order equations

BitVectors
- Fixed Size Array of Bytes (can be variable or constant)
- BitVecVal(0x1000, 32) - constant
- BitVec("x", 32) - variable
- Great to model bytes, words, registers, etc.
- Well defined operations:
-
bvadd, bvsub, bvmul
-
bvult, bvgt, bveq
-
and, or, not
-

Arrays
- Think of C arrays but much more powerful.
- Array('x', IntSort(), IntSort()) - example
- Maps a domain to a range.
- Domain is address size (first parameter) (32Bit, 64Bit, etc)
- Range is machine byte size - Almost always 8 bytes
- Generally has two operators to write/load elements to it
- Select - loading from the array (read)
- Store - writing to the array

Constraints
- Constraints help to bound a solver to look only for certain conditions
- E.g. Numbers less than 100
- Array only containing numbers from 1000-5000
- The solver then goes through all possible values to see if a solution exists.
-
If a solution exists, the constraint is considered ‘satisfiable’ or if no solution exists, ‘unsatisfiable’
-
SAT/UNSAT are more commonly used
-
Constraint may be unknown
-

Example
# Create two BitVector Variables
x = BitVector("x", 64)
y = BitVector("y", 64)
# Constraints
Solver.add(x == 4)
Solver.add(y == 3)
Solver.check(x+y == 100)
# What will happen here?
Example
# Create two BitVector Variables
x = BitVector("x", 64)
y = BitVector("y", 64)
# Constraints
Solver.add(x > 4)
Solver.add(y == 3)
Solver.check(x+y == 100)
# What will happen here?
Components of Symbolic Executors
- Intermediate Representation - Represents binary code before translating to machine code.
- Commonly represent programming semantics
- Don't have to reason about multiple archtitectures
- Utilized by symbolic executors to convert program semantics to SMT semantics
- IR needs to represent the binary code accurately
- Different amount of detail is given depending on the IR

Common IR Representations
- Valgrind VEX - Utilized by angr
- Binary Ninja IR
- PCode in Ghidra
- etc.

Symbolic Execution with ANGR
- Allows analysis of what inputs cause each part of the program to execute
- Attempts to encode an entire program as a logical expression
- Program control flow, loops, etc.
- The constructed expression is evaluated to generate solutions to symbolic input values (generally from SMT solvers)
- Solution results are the inputs needed to get to a specific program point
- Very hard to scale

Symbolic Execution with ANGR (cont.)
- Dynamic symbolic execution evalutes program statements in order, dynamically constructing symbolic expressions.
- At conditional statements, the feasibility of each branch is checked.
- Evaluates feasible branches, ignores non-feasible branches

Symbolic Execution with ANGR (cont.)
- Large project - Main angr repo is ~135,000 lines of Python
-
High level modules:
- angr– Main binary analysis platform
- CLE (CLE Loads Everything) – Binary format parser and loader
- Archinfo– Architecture-specific information (registers, endian, etc)
- PyVEX– Python bindings for VEX IR
- Claripy– SMT solver abstraction layer
- Others!

Symbolic Execution with ANGR (cont.)
- SimState is angr's mechanism for encapsolating program analysis state
- Memory, registers, operating system files & sockets, solver, etc.
- Some of the SimState can be more abstract than others
- Raw byte arrays for memory at address 0x1000 vs. GNU Libc Python Stubs
- Almost all specific SimState Functionality is implemented via plugins
- Different plugins address different configurations or analysis concerns.

Fuzzing

What is it?
- Automated way to find bugs at scale
- Attempts many different types of inputs to determine what outputs are generated.
- Tries to find code paths.
- Great at finding bugs in hard targets (requiring novelty approach) or focused efforts
- New Algorithms
- Grammar/Protocol Support
- Harnessing

Types Of Fuzzing
- Black Box Fuzzing
- Limited/no way for fuzzer to determine how much it has made progress
- Easier to setup
- For soft targets
- Black box fuzzing with simple protocol specification might be all you need.
- Examples: SPIKE, Sulley, BooFuzz, Peach

Types of Fuzzing (cont.)
- Able to use symbolic math to systematically explore the program search space
- Detailed introspection, but slow
- Examples:
- Angr
- KLEE
- Triton
- Effective for small sections of code.

Types of Fuzzing (cont.)
- Grey-Box Fuzzing
- Fast with some lightweight introspection
- Typically uses coverage feedback (block edges, etc) to explore deeper code paths
- Favored Approach
- Examples include AFLPlusPlus

Coverage Based Fuzzing

Coverage Based Fuzzing
Input = TestCase1

Coverage Based Fuzzing
Input = TestCase2
Saved TestCase1

Coverage Based Fuzzing

Components of Fuzzer
- Corpus
- The test cases (or seeds) to start the fuzzing campaign
- Often collected from packet captures, online sources, other campaigns, etc.
- Scheduler
- Determines which test cases to prioritize and what kind of mutations to perform
- Uses metrics such as unique coverage and execution time to prioritize most effective seeds
- Mutators
- Modifies the seeds/test cases to reach more code and trigger bugs
- Ranges from simple (bit flips, byte stomps, etc.) to complex (grammar manipulation, immediate harvesting, splices, etc.)
- Best mutations are target specific

Components of Fuzzer
- Runner
- Executes the test case and coverage metrics
- Analysis
- Uses information from test runs to create new test cases to modify priorities
- De-duplicate and minimize crashing test cases

Running a Campaign
- Gathering the initial corpus is key to a succesful fuzzing campaign
- Can't strictly rely on mutations to always find what you are looking for.
- Be as target specific as popular!
- Be on the lookout for program s that have been fuzzed previously - may have large corpses already.
- Use afl-cmin and afl-tmin to minimize your test cases.
- afl-cmin removes test cases that do not provide unique coverage
- afl-timin reduces the size and complexity of each test case while preserving coverage
- Both are extremely helpful!

Developing a Harnes
- The harness is the way you provide input to the program
- Often programs don't just use stdin to pass input
- Otherwise this would be easy
- Think of this as code that is used to help the fuzzer understand how to pass input
- Authentication, network services, shared libraries, etc.
- Create wrapper code to interact with these services

Mutators
- AFLPlusPlus provides many different mutators already
- bitflips
- addition/subtract9ion
- Insert/overwrite values/dictionary words
- etc.
- Sometimes you might need to be more target specific
- Mutate based on a specification/protocol (this is called Grammar)
- Important if you cannot generate a diverse initial corpus
- Look at AFL++ grammar mutator - usese specification files to perform targeted mutations based on reserach papers such as Nautilus.

Instrumentation - Open Source
- Static Instrumentation
- Hooks into basic code blocks
- Checking comparisons
- Adds negligible overhead time to run
- Utilize LLVM's instrumentation
- Adds in compile-time instrumentation to improve fuzzing
- All modern fuzzers use this approach
- afl-clang-fast++ is what is generally used in AFL++

Instrumentation - Closed Source
- Emulation
- QEMU - Allows for block-level instrumentation and code coverage
- Supports cross-architecture fuzzing
- Can also apply laf-intel, cmplog, qasan, libdslocator (complex instrumentation/sanitization)
- Supports Linux usermode programs
- Unicorn
- Support for non-linux systems
- Can fuzz very specific sections of code
- Typically require more harnessing in order to achieve properly

Instrumentation - Closed Source
- Binary Rewriting - Recompiling the binary via Syzgy or Retrowrite
- Does not work on all targets (achieve near native speeds)
- Dynamic instrumentation
- JIT compilers that perform run time transformation that hook-code
- Cross-platform and cross-architecfture
- Allows for custom hook code, but slow.
- Breakpoint tracing
- Allows for detection of new coverage by enabling breakpoints only on unreached code
- Near native speed since debug traps are only triggered on new branches
- Does not allow for detecting uncommon edges and loop counts.

Practice
- https://aflplus.plus/docs/tutorials/
ENPM809V Vulnerability Research
By Ragnar Security
ENPM809V Vulnerability Research
- 89