GP-7021: Add tenet++ pin tracer and docs

This commit is contained in:
ghidra_red
2026-08-27 17:37:25 +00:00
parent 52bb03d247
commit a19c2f4c2c
7 changed files with 909 additions and 10 deletions
@@ -1,3 +1,5 @@
##VERSION: 2.0
Module.manifest||GHIDRA||||END|
README.md||GHIDRA||||END|
data/TenetPlusPlus_PinTool/Makefile||GHIDRA||||END|
data/TenetPlusPlus_PinTool/Readme.md||GHIDRA||||END|
@@ -0,0 +1,11 @@
CONFIG_ROOT := $(PIN_ROOT)/source/tools/Config
include $(CONFIG_ROOT)/makefile.config
TOOL_ROOTS := tenetplusplus
$(OBJDIR)tenetplusplus$(PINTOOL_SUFFIX): $(OBJDIR)tenetplusplus$(OBJ_SUFFIX)
$(LINKER) $(TOOL_LDFLAGS) $(LINK_EXE)$@ $^ $(TOOL_LPATHS) $(TOOL_LIBS)
include $(TOOLS_ROOT)/Config/makefile.default.rules
all: $(OBJDIR)tenetplusplus$(PINTOOL_SUFFIX)
@@ -0,0 +1,157 @@
# Tenet++
The `tenetplusplus` pintool is an enhancement of the `pintenet` tracer found in
the [Tenet](https://github.com/gaasedelen/tenet) repo under the MIT license. With the Tenet++ tracer comes a new format
as well to enable some additional features when imported into Ghidra.
## New Format
Tenet++ expands the original tenet format to be better suited for additional features added to Ghidra.
### Example
```
Loaded image: 0x55b9ab65b000:0x55b9ab660d57 -> /usr/bin/whoami Bytes: 7f454c4602010100000000000000000003003e000100000020270000000000004000000000000000587600000000000000000000400038000d0040001e001d000600000004000000400000000000000040000000000000004000000000000000d802000000000000d80>
Loaded image: 0x7f78bd58d000:0x7f78bd5c38a7 -> /lib64/ld-linux-x86-64.so.2 Bytes: 7f454c4602010103000000000000000003003e000100000030ef0100000000004000000000000000684a0e000000000000000000400038000b004000230022000100000004000000000000000000000000000000000000000000000000000000301b000>
tid=0,rdi=0x7ffd3cbe5eb0,rsp=0x7ffd3cbe5eb0,rip=0x7f78bd5abf33
tid=0,rsp=0x7ffd3cbe5ea8,rip=0x7f78bd5acca0,mw=0x7ffd3cbe5ea8:38bf5abd787f0000
tid=0,rdi=0x6fffffff,rbp=0x7ffd3cbe5ea0,rsp=0x7ffd3cbe5e40,rbx=0x6ffffeff,rdx=0x7f78bd5c5e90,rcx=0x7f78bd5c6a70,rax=0xe,r8=0x70000022,r9=0x32,r10=0x6ffffdff,r11=0x6ffffe35,r12=0x7f78bd58d000,r13=0x6fffff41,r15=0x7ffd3cbe5eb0,rip=0x7f78bd5acd2d,ma=0x7f78bd5c5ac0:5ebb5be2a9520000,ma>
tid=0,rip=0x7f78bd5acd4a
tid=0,rsi=0x4c,rdx=0x7f78bd5c5eb0,rax=0x4c,r14=0xeffffef5,rip=0x7f78bd5ad089,ma=0x7f78bd5c5ea0:0400000000000000,ma=0x7f78bd5c5eb0:f5feff6f00000000,ma=0x7f78bd5c6a90:a05e5cbd787f0000,ma=0x7f78bd5c6ae0:905e5cbd787f0000
tid=0,rip=0x7f78bd5acd39
tid=0,rsi=0x29,rdx=0x7f78bd5c5f90,rax=0x7f78bd5c5eb0,rip=0x7f78bd5ad114,ma=0x7f78bd5c5ea8:f0d258bd787f0000,ma=0x7f78bd5c5eb8:38d458bd787f0000050000000000000090d958bd787f00000600000000000000a0d558bd787f00000a00000000000000,ma=0x7f78bd5c5ef0:0b00000000000000,ma=0x7f78bd5c5f00:070000>
tid=0,rip=0x7f78bd5acdaa
```
### Breakdown
A single line is a new snapshot/event and can be one of three types:
#### Instruction execution
* __tid=[thread id],[register name]=[value]...,m[r/w/a]=[address]:[bytes]...__
* __tid=[thread id]__
* Records which thread this event happens in
* Required for every instruction event
* eg. `tid=0`
* __[register name]=[value]__
* Records register assignments
* 0 or more per line
* eg. `rdi=0x7ffd3cbe5eb0`
* __m[r/w/a]=[address]:[bytes]__
* Records memory reads/writes as well as the coalesced memory from the `-rec_all_memory` option.
* 0 or more per line
* eg. `mw=0x7ffd3cbe5ea8:38bf5abd787f0000`
#### Image loading
* __Loaded image: [start address]:[end address] -> [module name] Bytes: [bytes starting from start address]__
* Records when images are loaded into memory as well as the bytes that were loaded
* eg. `Loaded image: 0x55b9ab65b000:0x55b9ab660d57 -> /usr/bin/whoami Bytes: 7f454c46020101000...`
#### Image unloading
* __Unloaded image: [start address]:[end address] -> [module name]__
* Records when images are unloaded from memory
* eg. `Unloaded image: 0x55b9ab65b000:0x55b9ab660d57 -> /usr/bin/whoami`
There can be a variable amount of actual time between events based on the recording granularity.
## Improvements made by Tenet++ pin tracer:
1. Trace start and stop addresses
- These allow the user to precisely trace only certain points of a program
2. Trace granularity
- These give the user an option to choose how granular the tracer is: Instruction, Block or Routine level
3. Record all memory
- Even if using routine level tracing, the user can record all memory events that get coalesced and read prior to
the
next routine event. This ensures the event has an updated snapshot of modified memory
4. Record image bytes at image load time
5. Record all threads in process in a single trace
# Usage
The pintool can be used to trace simple usermode applications on Windows or Linux. To use it, provide the path for a
compiled version of `tenetplusplus` to `pin` via the `-t` argument.
Example usage:
```
C:\pin\pin -t obj-ia32\tenetplusplus.dll -- "C:\Users\user\Desktop\calc.exe"
```
This pintool will generate a single trace containing trace data from all threads.
## Additional parameters
* `-s` Start tracing when an [image name]:[image base offset] is hit
* Example: `-s kernel32.dll:1234 -s app:2FFF`
* `-e ` Stop tracing when an [image name]:[image base offset] is hit
* Example: `-e kernel32.dll:1234 -e app:2FFF`
* `-g` Trace granularity level, default is 2
* `2` Instruction Level Tracing
* `4` Basic Block Level Tracing
* `8` Routine Level Tracing
* `-o` Prefix of the output .trace file, default is `trace`
* `-rec_all_memory` Record all memory r/w events regardless of granularity level until next log write
* `-w` Add a module to the whitelist. If none is specified, every module is white-listed
* Example: `-w calc.exe`, or `-w calc.exe,kernel32.dll`
# Compilation
To compile the pintool, you first will need
to [download](https://software.intel.com/en-us/articles/pin-a-binary-instrumentation-tool-downloads) and extract Pin.
Follow the build instructions below for your respective platform.
## Building for Linux
On Linux, one can compile the pintool using the following commands.
```
cd <location where tenetplusplus.cpp is>
# Location where you extracted Pin
export PIN_ROOT=~/pin
export PATH=$PATH:$PIN_ROOT
make TARGET=intel64
make TARGET=ia32
```
## Building for Windows
Install deps for building Pintools:
- Install Visual Studio Community 2019 Edition
from [https://visualstudio.microsoft.com/downloads/](https://visualstudio.microsoft.com/downloads/)
- Make sure to install the Desktop development for C++ workload
- Install GNU's make, version 4.2.1, using Cygwin's 64-bit installer. Cygwin installer link
here: [https://cygwin.com/install.html](https://cygwin.com/install.html)
### Building 32bit
1. Launch a new CMD window and paste the EXACT following:
```
set PIN_ROOT=C:\\pin
set PATH=%PATH%;C:\cygwin64\bin
"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars32.bat"
```
2. Change to the directory containing the `tenetplusplus` source, build the 32bit pin tool:
```
make TARGET=ia32
```
### Building 64bit
1. Launch a new CMD window and paste the EXACT following:
```
set PIN_ROOT=C:\\pin
set PATH=%PATH%;C:\cygwin64\bin
"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat"
```
2. Change to the directory containing the `tenetplusplus` source, and build the 64bit pin tool:
```
make TARGET=intel64
```
File diff suppressed because it is too large Load Diff
@@ -61,7 +61,7 @@ import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
public class TenetPlusPlusLoader implements Loader {
private record ModuleEvent(String path, String module, AddressRange range) {}
private record ModuleEvent(String path, String module, AddressRange range, byte[] bytes) {}
private static final String TRACE_SUFFIX = ".trace";
private static final String TENET_SUFFIX = ".tenet";
@@ -73,7 +73,7 @@ public class TenetPlusPlusLoader implements Loader {
private static final Pattern MEM_PATTERN =
Pattern.compile("(?:^|,)(mr|mw|ma)=0x([^:]+):([0-9a-fA-F]+)");
private static final Pattern MODULE_LOAD_PATTERN =
Pattern.compile("Loaded image: 0x([0-9a-fA-F]+):0x([0-9a-fA-F]+) -> (.*)$");
Pattern.compile("Loaded image: 0x([0-9a-fA-F]+):0x([0-9a-fA-F]+) -> (.*) Bytes: (.*)$");
private static final Pattern MODULE_UNLOAD_PATTERN =
Pattern.compile("Unloaded image: 0x([0-9a-fA-F]+):0x([0-9a-fA-F]+) -> (.*)$");
@@ -214,6 +214,8 @@ public class TenetPlusPlusLoader implements Loader {
if (!load.isEmpty()) {
for (final ModuleEvent event : load) {
modMan.addLoadedModule(event.path, event.module, event.range, snap);
trace.getMemoryManager()
.putBytes(snap, event.range.getMinAddress(), ByteBuffer.wrap(event.bytes));
}
load.clear();
@@ -512,7 +514,8 @@ public class TenetPlusPlusLoader implements Loader {
load.add(new ModuleEvent("Modules[%s]".formatted(mod), mod,
rng(Long.parseLong(modLoadMatcher.group(1), 16),
Long.parseLong(modLoadMatcher.group(2), 16))));
Long.parseLong(modLoadMatcher.group(2), 16)),
HexFormat.of().parseHex(modLoadMatcher.group(4))));
return true;
}
@@ -525,7 +528,8 @@ public class TenetPlusPlusLoader implements Loader {
unload.add(new ModuleEvent("Modules[%s]".formatted(mod), mod,
rng(Long.parseLong(modUnloadMatcher.group(1), 16),
Long.parseLong(modUnloadMatcher.group(2), 16))));
Long.parseLong(modUnloadMatcher.group(2), 16)),
null));
return true;
}
return false;
@@ -287,15 +287,17 @@ public class BreakpointTimelineProvider extends ComponentProvider {
for (final TraceReference reference : currentTrace.getReferenceManager()
.getReferencesToRange(Lifespan.ALL, range)) {
if ((reference.getReferenceType() == RefType.READ) &&
(kind == TraceBreakpointKind.READ)) {
if ((reference.getReferenceType() == RefType.READ &&
kind == TraceBreakpointKind.READ) ||
(reference.getReferenceType() == RefType.READ_WRITE)) {
breakpointHits.add(
new BreakpointHitEvent(reference.getStartSnap(), TraceBreakpointKind.READ,
breakpointLocation.getName(reference.getStartSnap())));
}
if ((reference.getReferenceType() == RefType.WRITE) &&
(kind == TraceBreakpointKind.WRITE)) {
if ((reference.getReferenceType() == RefType.WRITE &&
kind == TraceBreakpointKind.WRITE) ||
(reference.getReferenceType() == RefType.READ_WRITE)) {
breakpointHits.add(
new BreakpointHitEvent(reference.getStartSnap(), TraceBreakpointKind.WRITE,
breakpointLocation.getName(reference.getStartSnap())));
@@ -31,8 +31,8 @@ import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.pcode.*;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.exception.NotFoundException;
import ghidra.util.Msg;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
/**
@@ -99,6 +99,18 @@ public class SymPcodeExecutor extends PcodeExecutor<Sym> {
// TODO: Is there a way to know if a userop affects the stack?
}
@Override
public void stepOp(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary<Sym> library) {
// TODO: This function can probably be removed after GP-6707 is complete
try {
monitor.checkCancelled();
}
catch (CancelledException e) {
throw new PcodeExecutionException("Monitor was cancelled", frame, e);
}
super.stepOp(op, frame, library);
}
/**
* Attempt to figure the stack depth change for a given function
*