Chapter 1 – Android – Debugging and Analyzing Vulnerabilities
It’s very difficult—arguably impossible—to create programs that are free of
bugs. Whether the goal is to extinguish bugs or to exploit them, liberal application
of debugging tools and techniques is the best path to understanding what
went wrong. Debuggers allow researchers to inspect running programs, check
hypotheses, verify data flow, catch interesting program states, or even modify
behavior at runtime.
In the information security industry, debuggers are essential
to analyzing vulnerability causes and judging just how severe issues are.
Here we explore the various facilities and tools available for debugging
on the Android operating system. It provides guidance on how to set up an
environment to achieve maximum efficiency when debugging. Using some
example code and a real vulnerability, you walk through the debugging process
and see how to analyze crashes to determine their root cause and exploitability.
Getting All Available Information
The first step to any successful debugging or vulnerability analysis session is
to gather all available information. Examples of valuable information include
documentation, source code, binaries, symbol files, and applicable tools. This
section explains why these pieces of information are important and how you
use them to achieve greater efficacy when debugging.
Look for documentation about the specific target, protocols that the target
uses, file formats the target supports, and so on. In general, the more you know
going in, the better chance of a successful outcome. Also, having easily accessible
documentation during analysis often helps overcome unexpected difficulties
The source code to the target can be invaluable during analysis. Reading
source code is usually much more efficient than reverse-engineering assembly
code, which is often very tedious. Further, access to source code gives you the
ability to rebuild the target with symbols. As discussed in the “Debugging with
Symbols” section later in this chapter, symbols makes it possible to debug at
the source-code level. If source code for the target itself is not available, look
for source code to competing products, derivative works, or ancient precursors.
Though they probably will not match the assembly, sometimes you get lucky.
Different programmers, even with wildly different styles, tend to approach
certain problems the same way. In the end, every little bit of information helps.
Binaries are useful for two reasons. First, the binaries from some devices
contain partial symbols. Symbols provide valuable function information such
as function names, as well as parameter names and types. Symbols bridge
the gap between source code and binary code. Second, even without symbols,
binaries provide a map to the program. Using static analysis tools to reverse
engineer binaries yields a wealth of information. For example, disassemblers
reconstruct the data and control fl ow from the binary. They facilitate navigating
the program based on control fl ow, which makes it easier to get oriented in the
debugger and find interesting program locations.
Symbols are more important on ARM-based systems than on x86 systems.
As discussed, ARM processors have several execution modes. In
addition to names and types, symbols are also used to encode the processor
mode used to execute each function.
Finally, having the right tools for the job always makes the job easier.
Disassemblers such as IDA Pro and radare2 provide a window into binary code.
Most disassemblers are extensible using plug-ins or scripts. For example, IDA
Pro has a plug-in application programming interface (API) and two scripting
engines (IDC and Python), and radare2 is embeddable and provides bindings
for several programming languages.
Tools that extend these disassemblers may prove to be indispensable during analysis, especially when symbols are not available. Depending on the particular target program, other tools may also apply. Utilities that expose what’s happening at the network, file system, system call, or library API level provide valuable perspectives on a program’s execution.
Choosing a Toolchain
A toolchain is a collection of tools that are used to develop a product. Usually,
a toolchain includes a compiler, linker, debugger, and any necessary system
libraries. Simply put, building a toolchain or choosing an existing one is the
fi rst step to building your code. For the purpose of this chapter, the debugger
is the most interesting component. As such, you need to choose a workable
For Android, the entity that builds a particular device selects the toolchain
during development. As a researcher trying to debug the compiler’s output, the
choice affects you directly. Each toolchain represents a snapshot of the tools it
contains. In some cases, different versions of the same toolchain are incompatible.
For example, using a debugger from version A on a binary produced by
a compiler from version B may not work, or it may even cause the debugger to
Further, many toolchains have various bugs. To minimize compatibility
issues, it is recommended that you use the same toolchain that the manufacturer
used. Unfortunately, determining exactly which toolchain the manufacturer
used can be difficult.
In the Android and ARM Linux ecosystems, there are a variety of debuggers
from which to choose. This includes open source projects, as well as commercial
products. Table 7-1 describes several of the tools that include an ARM Linux
In next chapter we will talk about Debugging with Crash Dumps, System Logs and Tombstones.