Unveiling Dragon Dance: A Ghidra Plugin for Code Coverage Visualization
Dragon Dance is a powerful plugin designed for Ghidra, the popular reverse engineering framework. This plugin brings binary code coverage data to life, allowing users to visualize and interact with it directly within Ghidra. By importing coverage data from various sources, Dragon Dance offers invaluable insights into the execution paths of binary code. Currently, it seamlessly integrates with Dynamorio and Intel Pin, two leading binary instrumentation tools.
For Intel Pin users, Dragon Dance provides ddph (Dragon Dance Pin Helper), a custom-built coverage collection module addressing the absence of a native module in Pin. The source code for ddph is readily available here. Pre-compiled binaries for Windows, macOS, and Linux are also provided for ease of use.
Dragon Dance Plugin Main Interface
Dragon Dance stands out by supporting the import and simultaneous utilization of multiple coverage datasets within a single Ghidra session. Users can effortlessly switch between these datasets or perform operations like intersection, difference, distinct, and sum to analyze coverage from different perspectives.
The plugin visually represents the intensity of instruction execution, offering hints about frequently executed code paths. Furthermore, coverage visualization is integrated into Ghidra’s function graph window, providing a contextual understanding of code execution within function boundaries.
Dragon Dance Coverage Visualization on Function Graph
Scripting Capabilities for Advanced Coverage Analysis
Dragon Dance extends its functionality with a built-in scripting system, empowering users with flexible control over coverage data manipulation.
Dragon Dance Scripting System Interface
This scripting environment allows for loading, deleting, displaying, and performing set operations (intersect, diff, distinct, sum) on coverage data. The scripting system and its API are detailed in the following sections. Scripts are executed by pressing Alt + Enter.
Built-in Functions: Core Coverage Operations
Built-in functions are the foundation of Dragon Dance’s scripting, providing access to internal coverage operations. These functions, which may have aliases, can return coverage objects or perform actions without returning a value. They accept “Built-in Arg” parameters, which can be coverage data objects, strings, or integers.
Understanding Built-in Arg
A “Built-in Arg” acts as a versatile parameter type, capable of holding:
- Coverage Data Objects (returned by built-in functions)
- Strings
- Integers (decimal, hexadecimal, or octal formats)
Variables: Managing Coverage Objects
Variables in Dragon Dance are specifically designed to store coverage objects. These objects, loaded via built-in functions, can then be used as parameters in other built-in function calls.
There are two primary types of coverage objects:
-
Physical Coverage Objects: These represent coverage data loaded directly from files and are visible in the plugin’s GUI coverage table. They can be interacted with through GUI operations.
-
Logical Coverage Objects: These are the result of operations performed by built-in functions. They are not directly visible in the GUI but can be stored in variables for further scripting operations.
Dragon Dance automatically manages both types of coverage objects through variables.
Example Scenarios:
cov1 = load("firstcoverage.out")
cov2 = load("secondcov.out")
cov1 = diff(cov1,cov2)
In this example, cov1
and cov2
are variables holding physical coverage objects. The diff
function calculates the difference between cov1
and cov2
, and the result is assigned back to cov1
. The original physical coverage object associated with cov1
remains in the session and GUI table, even though the variable cov1
now points to the result of the diff
operation.
Consider a more complex example:
cov1 = load("first.out")
cov2 = load("second.out")
cov3 = load("third.out")
rvar = sum(cov1,cov2,cov3)
rvar = diff(rvar, cov2)
Here, cov1
, cov2
, and cov3
hold physical coverage objects. The sum
function combines these into a logical coverage object, assigned to rvar
. Subsequently, diff
calculates the difference between this logical object and cov2
, again updating rvar
. In this case, the previous logical coverage object held by rvar
is automatically destroyed to prevent memory leaks. To explicitly remove a physical coverage object and its associated variable, the discard
built-in function must be used.
Nested built-in function calls are also supported, enabling complex, concise scripts:
cres = diff(intersect(a, load("another.log"), c, d), sum(e,f) )
Built-in Function Reference
The following section details the available built-in functions in Dragon Dance’s scripting system. Note that API and behaviors are subject to change in future versions.
clear()
Property | Description |
---|---|
Return Value | None |
Minimum Parameter Count | 0 |
Maximum Parameter Count | 0 |
Description | Clears the currently visualized coverage and resets the active coverage to null, effectively removing the coverage overlay. |
Aliases | None |
cwd( String : workingDirectory )
Property | Description |
---|---|
Return Value | None |
Minimum Parameter Count | 1 |
Maximum Parameter Count | 1 |
Description | Sets the current working directory for subsequent file operations. Relative paths used in import calls will be resolved against this directory. |
Aliases | None |
diff( Variable : var1, var2, ….. varN )
Property | Description |
---|---|
Return Value | Variable |
Minimum Parameter Count | 2 |
Maximum Parameter Count | Unlimited |
Description | Calculates the set difference between the provided coverage variables. Returns a new coverage variable representing the result. |
Aliases | None |
discard( Variable : var1, var2, ….. varN )
Property | Description |
---|---|
Return Value | None |
Minimum Parameter Count | 1 |
Maximum Parameter Count | Unlimited |
Description | Removes the specified coverage variables. This destroys the associated coverage objects (physical or logical) and unregisters the variable names, making them undefined. |
Aliases | del |
distinct( Variable : var1, var2, ….. varN )
Property | Description |
---|---|
Return Value | Variable |
Minimum Parameter Count | 2 |
Maximum Parameter Count | Unlimited |
Description | Performs a distinct (XOR – exclusive OR) operation on the given coverage variables. Returns a new coverage variable containing the result. |
Aliases | xor |
goto( Integer : offset )
Property | Description |
---|---|
Return Value | None |
Minimum Parameter Count | 1 |
Maximum Parameter Count | 1 |
Description | Jumps to the address calculated by adding the provided offset to the image base address in Ghidra, allowing for quick navigation within the binary. |
Aliases | None |
import( String : filePathOrCoverageName )
Property | Description |
---|---|
Return Value | Variable |
Minimum Parameter Count | 1 |
Maximum Parameter Count | 1 |
Description | Loads coverage data from a file. Accepts absolute or relative file paths, or the name of a previously loaded physical coverage. If the data is already loaded, it returns the cached coverage variable. |
Aliases | get, load |
intersect( Variable : var1, var2, ….. varN )
Property | Description |
---|---|
Return Value | Variable |
Minimum Parameter Count | 2 |
Maximum Parameter Count | Unlimited |
Description | Computes the intersection of the provided coverage variables. Returns a new variable holding the resulting coverage. |
Aliases | and |
show( Variable : var )
Property | Description |
---|---|
Return Value | None |
Minimum Parameter Count | 1 |
Maximum Parameter Count | 1 |
Description | Visualizes the coverage data associated with the given variable. If another coverage is currently visualized and it is logical, it will be replaced by the new one. |
Aliases | None |
sum( Variable : var1, var2, ….. varN )
Property | Description |
---|---|
Return Value | Variable |
Minimum Parameter Count | 2 |
Maximum Parameter Count | Unlimited |
Description | Calculates the union (sum) of the given coverage variables. Returns a new coverage variable representing the combined coverage. |
Aliases | or, union |
Fix Ups: Addressing Ghidra Analysis Limitations
Dragon Dance incorporates a “Fix Ups” mechanism to handle situations where Ghidra’s analysis might be incomplete. This is particularly useful when dealing with binaries where compilers generate unexpected code, leading to decompilation failures. During coverage data import, Dragon Dance validates the loaded image and coverage data. If inconsistencies are detected, and executable sections lack decompiled instructions, the plugin offers to attempt a fix by decompiling the raw section.
Dragon Dance Fix Up Prompt Example
Future versions of Dragon Dance are planned to include more advanced fix-up strategies and workarounds to enhance compatibility and analysis accuracy.
Installation Guide
Installing Dragon Dance is a straightforward process:
- Launch Ghidra.
- Navigate to File > Install Extensions…
- Click the Green Plus icon in the top-right corner.
- Select the Dragon Dance plugin zip package and click Ok.
- Choose dragondance from the extension list.
- Click Ok and restart Ghidra.
Launching Dragon Dance Plugin
Upon the first binary load in Ghidra after installation, you will be prompted to configure the newly installed plugin.
- Clicking Yes will automatically launch Dragon Dance.
- Clicking No requires manual activation:
- Go to File > Configure within the Disassembly Window (CodeBrowser).
- Click the plug icon at the top-right of the Configure Tool window.
- Locate DragonDance in the plugin list, enable its checkbox, and click Ok.
- The Dragon Dance window should now appear.
After activation, “Dragon Dance” will be accessible in the Window menu.
Collecting Coverage Data for Dragon Dance
Dragon Dance supports importing coverage data generated by Dynamorio and Intel Pin. These tools require specific modules to collect coverage information.
Using Dynamorio for Coverage Collection
Use the following command to collect coverage data with Dynamorio:
drrun -t drcov -logdir [COVERAGE_OUTPUT_DIRECTORY_PATH] -- [EXECUTABLE_PATH_TO_EXAMINE] [EXECUTABLE_ARGUMENTS]
Coverage logs will be saved in the specified directory with the format drcov.[EXECUTABLE_NAME].[ID].proc.log
.
Using Intel Pin and ddph for Coverage Collection
Intel Pin requires a custom module for coverage collection. ddph (Dragon Dance Pin Helper) is provided for this purpose, offering flexibility and potential for future feature additions.
Important: While later versions may work, Intel PIN 3.7 is the officially supported version. Download links are provided below:
Access the ddph source code here. Pre-built binaries are available, or you can build your own using the provided build script.
Collect coverage data using Intel Pin with the following command:
pin -t ddph.[so,dylib,dll] [ddph options] -- [EXECUTABLE_PATH_TO_EXAMINE] [EXECUTABLE_ARGUMENTS]
ddph Options:
-o
: Specify the coverage output filename (default:ddph.out
).-l
: Specify the operation log filename. Use-l no
to disable logging (default:ddph.log
).-p
: Set the capture detail level:reduced
(faster, trace blocks) orhigh
(slower, instruction-by-instruction, pre-process execution blocks) (default:reduced
).
macOS SIP (System Integrity Protection) Configuration:
macOS users may need to disable System Integrity Protection (SIP) to use binary instrumentation tools like Intel Pin.
Disabling SIP:
- Restart macOS.
- Hold Command + R during boot to enter Recovery Mode.
- Open Terminal from the Utilities menu.
- Check SIP status:
csrutil status
- Disable SIP:
csrutil disable
- Verify status again:
csrutil status
- Restart the OS.
macOS Recovery Mode Terminal
macOS SIP Status Check
macOS SIP Disable Command
Build Instructions for Dragon Dance Plugin
To build the Dragon Dance Ghidra plugin from source:
-
Download Ghidra (latest version, e.g., 9.1.2) and Dragon Dance source code:
wget https://ghidra-sre.org/ghidra_9.1.2_PUBLIC_20200212.zip wget https://github.com/0ffffffffh/dragondance/archive/master.zip unzip ghidra_9.1.2_PUBLIC_20200212.zip unzip master.zip
-
Install Gradle and JDK:
sudo apt install openjdk-11-jdk wget https://services.gradle.org/distributions/gradle-5.2.1-bin.zip sudo unzip -d /opt/gradle gradle-5.2.1-bin.zip
-
Configure Gradle Environment: Create
/etc/profile.d/gradle.sh
with the following content:export GRADLE_HOME=/opt/gradle/gradle-5.2.1 export PATH=${GRADLE_HOME}/bin:${PATH}
Apply the changes:
source /etc/profile.d/gradle.sh
-
Build Dragon Dance:
cd dragondance-master/ gradle -PGHIDRA_INSTALL_DIR=/path/to/ghidra_9.1.2_PUBLIC > Task :buildExtension
Replace
/path/to/ghidra_9.1.2_PUBLIC
with the actual path to your Ghidra installation directory.The built extension zip file (
ghidra_9.1.2_PUBLIC_20200506_dragondance-master.zip
) will be located in thedragondance-master/dist
directory.
Future Enhancements for Dragon Dance
Planned features for future versions of Dragon Dance include:
- Command prompt style, line-by-line script execution within the plugin.
- Context change awareness for more dynamic coverage analysis.
- Function (Routine) based coverage visualization for function-level insights.
- Execution flow awareness to understand code execution paths in detail.
- Expansion of the scripting API with more built-in functions.
- A dedicated coverage database format for faster loading and session persistence.
- Pseudo-code painting (pending Ghidra API availability or workaround development).
- UI enhancements for improved user experience.
Project Author
Dragon Dance is developed by Oğuz Kartal (@0ffffffffh).