Skip to content

Commit b7de3fc

Browse files
committed
Add signature-based RTR directory locator
1 parent 9bd4244 commit b7de3fc

File tree

6 files changed

+129
-28
lines changed

6 files changed

+129
-28
lines changed

README.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,21 @@ Install the plugin using `File > Install Extensions` menu in the main project wi
5757
Steps:
5858

5959
- Make sure you have the plugin installed (see [Building](#building) and [Installing](#installing)).
60-
- Make sure that you have the ReadyToRun directory annotated in your binary with the `__ReadyToRunHeader` label.
60+
- Open a .NET NativeAOT binary in the code browser.
61+
- Optionally, if symbols are stripped, locate and markup the ReadyToRun header with the name `__ReadyToRunHeader` (see below).
6162
- Run the Native AOT Analyzer (Either as a One-Shot Analysis or part of Auto-Analysis).
6263
- Open the Native AOT Metadata Browser from the Windows Menu.
6364

6465

65-
### How to Find the ReadyToRun Directory?
66+
## Locating the ReadyToRun Directory
6667

6768
The ReadyToRun data directory is the root of all .NET Native AOT metadata.
68-
Currently, the plugin does not support automatically locating this, however, the directory is referenced in a call to `S_P_CoreLib_Internal_Runtime_CompilerHelpers_StartupCodeHelpers__InitializeModules` in `wmain`.
69+
As the directory is not a normal PE data directory specified in its header, the plugin tries to heuristically find it in the following manner:
70+
- First, it will prefer using symbols `__ReadyToRunHeader` or the symbol pair `__modules_a` and `__modules_z` which mark a list of directories.
71+
- If no valid headers are found at these symbol names, it will heuristically scan all non-executable memory blocks for a known pattern (i.e., the `RTR\0` signature and a few expected fields in its header).
72+
73+
If you find that this process does not work for you (e.g., no matches or too many matches), the RTR directory list (i.e., `__modules_a`) is also referenced in the startup code of any NativeAOT binary.
74+
Specifically it is referenced as **the second argument** in a call to `S_P_CoreLib_Internal_Runtime_CompilerHelpers_StartupCodeHelpers__InitializeModules` in `wmain`.
6975

7076
You can find this call by e.g., compiling a simple Hello World application using Native AOT with symbols, and using Ghidra's Version Tracking Tool to compare functions.
7177

@@ -91,8 +97,14 @@ Any editor supporting Gradle should work, including Eclipse, IntelliJ and Visual
9197

9298
For quickly reinstalling the plugin as well as starting ghidra, use a command like the following (change file paths accordingly):
9399

100+
Linux (Bash):
94101
```sh
95-
$ gradle && unzip -o dist/ghidra_11.3.1_PUBLIC_XXXXXXXX_ghidra-nativeaot.zip -d ~/.config/ghidra/ghidra_11.3.1_PUBLIC/Extensions/ && ghidra
102+
$ gradle && unzip -o dist/*.zip -d ~/.config/ghidra/ghidra_X.X_PUBLIC/Extensions/ && ghidra
103+
```
104+
105+
Windows (Powershell):
106+
```pwsh
107+
gradle; Expand-Archive .\dist\*.zip -DestinationPath $env:APPDATA\ghidra\ghidra_X.X_PUBLIC\Extensions -Force; Z:\Path\To\Ghidra\ghidraRun.bat
96108
```
97109

98110
## License

src/main/java/nativeaot/NativeAotAnalyzer.java

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,18 @@
3838
import nativeaot.rehydration.MetadataRehydrator;
3939
import nativeaot.rehydration.MetadataRehydratorNet80;
4040
import nativeaot.rehydration.PointerScanResult;
41-
import nativeaot.rtr.ReadyToRunDirectory;
42-
import nativeaot.rtr.ReadyToRunLocator;
43-
import nativeaot.rtr.ReadyToRunSection;
44-
import nativeaot.rtr.SymbolReadyToRunLocator;
41+
import nativeaot.rtr.*;
4542

4643
/**
4744
* Provide class-level documentation that describes what this analyzer does.
4845
*/
4946
public class NativeAotAnalyzer extends AbstractAnalyzer {
5047

5148
public static final String MARKUP_REHYDRATION_CODE = "Markup rehydration code";
49+
private static final ReadyToRunLocator[] READY_TO_RUN_LOCATORS = new ReadyToRunLocator[] {
50+
new SymbolReadyToRunLocator(),
51+
new SignatureReadyToRunLocator(),
52+
};
5253

5354
private boolean _markupRehydrationCode = false;
5455

@@ -93,14 +94,9 @@ public void optionsChanged(Options options, Program program) {
9394
}
9495

9596
@Override
96-
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
97-
throws CancelledException {
98-
99-
// TODO: make configurable which locator is used..
100-
ReadyToRunLocator locator = new SymbolReadyToRunLocator();
101-
102-
var moduleHeaders = locator.locateModules(program, monitor, log);
103-
97+
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) throws CancelledException {
98+
// Locate modules.
99+
var moduleHeaders = locateModules(program, monitor, log);
104100
if (moduleHeaders.length == 0) {
105101
log.appendMsg(Constants.TAG, String.format(
106102
"Symbols `%s` or `%s` and `%s` not found.",
@@ -129,16 +125,26 @@ public boolean added(Program program, AddressSetView set, TaskMonitor monitor, M
129125
return true;
130126
}
131127

128+
private Address[] locateModules(Program program, TaskMonitor monitor, MessageLog log) throws CancelledException {
129+
for (var locator : READY_TO_RUN_LOCATORS) {
130+
var moduleHeaders = locator.locateModules(program, monitor, log);
131+
if (moduleHeaders.length > 0) {
132+
return moduleHeaders;
133+
}
134+
}
135+
136+
return new Address[0];
137+
}
138+
132139
private void processModule(Program program, Address moduleHeader, TaskMonitor monitor, MessageLog log) throws Exception {
133140
ReadyToRunDirectory directory;
134141
try {
135142
directory = readRtrDirectory(program, moduleHeader);
136143
} catch (Exception ex) {
137-
throw new Exception("Failed to read rtr directory.", ex);
144+
throw new Exception("Failed to read RTR directory at %s.".formatted(moduleHeader), ex);
138145
}
139146

140-
// NOTE: Method table managers should be changed if the file format changes per rtr version.
141-
var manager = new MethodTableManagerNet80(program);
147+
var manager = createMethodTableManagerForDirectory(program, directory);
142148

143149
// Restore first from DB to allow for the analyzer to be run multiple times.
144150
manager.restoreFromDB();
@@ -208,6 +214,11 @@ private ReadyToRunDirectory readRtrDirectory(Program program, Address moduleHead
208214
return directory;
209215
}
210216

217+
private static MethodTableManagerNet80 createMethodTableManagerForDirectory(Program program, ReadyToRunDirectory directory) {
218+
// NOTE: Method table managers should be changed if the file format changes per RTR version.
219+
return new MethodTableManagerNet80(program);
220+
}
221+
211222
private PointerScanResult rehydrateData(Program program, ReadyToRunSection rehydratedData, MetadataRehydrator rehydrator, TaskMonitor monitor, MessageLog log) throws Exception {
212223
var symbolTable = program.getSymbolTable();
213224

@@ -241,10 +252,7 @@ private PointerScanResult scanForPointers(Program program, Address moduleHeader,
241252
}
242253

243254
// We use the block containing the module header as the scanning range
244-
var scanningRange = new AddressRangeImpl(
245-
moduleBlock.getStart(),
246-
moduleBlock.getEnd()
247-
);
255+
var scanningRange = moduleBlock.getAddressRange();
248256

249257
monitor.setMessage(Constants.TAG + ": Scanning for pointers...");
250258
monitor.setMaximum(moduleBlock.getSize());

src/main/java/nativeaot/rtr/ReadyToRunDirectory.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
import ghidra.util.exception.DuplicateNameException;
1414
import nativeaot.Constants;
1515

16-
1716
public class ReadyToRunDirectory implements StructConverter {
17+
private static final byte EXPECTED_NUMBER_OF_SECTIONS_UPPER_BOUND = 0x50;
1818

1919
private final short _majorVersion;
2020
private final short _minorVersion;
@@ -33,8 +33,8 @@ public ReadyToRunDirectory(BinaryReader reader) throws IOException {
3333
byte entrySize = reader.readNextByte();
3434
byte entryType = reader.readNextByte();
3535

36-
if (sectionCount > 100) {
37-
throw new IOException("Unexpected number of sections $d".formatted(sectionCount));
36+
if (sectionCount > EXPECTED_NUMBER_OF_SECTIONS_UPPER_BOUND) {
37+
throw new IOException("Unexpected number of sections %d".formatted(sectionCount));
3838
}
3939

4040
_sections = new ReadyToRunSection[sectionCount];

src/main/java/nativeaot/rtr/ReadyToRunLocator.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
import ghidra.app.util.importer.MessageLog;
55
import ghidra.program.model.address.Address;
66
import ghidra.program.model.listing.Program;
7+
import ghidra.util.exception.CancelledException;
78
import ghidra.util.task.TaskMonitor;
89

910
public interface ReadyToRunLocator {
10-
Address[] locateModules(Program program, TaskMonitor monitor, MessageLog log);
11+
Address[] locateModules(Program program, TaskMonitor monitor, MessageLog log) throws CancelledException;
1112
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package nativeaot.rtr;
2+
3+
import ghidra.app.util.importer.MessageLog;
4+
import ghidra.program.model.address.Address;
5+
import ghidra.program.model.listing.Program;
6+
import ghidra.program.model.mem.Memory;
7+
import ghidra.program.model.mem.MemoryAccessException;
8+
import ghidra.util.exception.CancelledException;
9+
import ghidra.util.task.TaskMonitor;
10+
import nativeaot.Constants;
11+
12+
import java.util.ArrayList;
13+
14+
public class SignatureReadyToRunLocator implements ReadyToRunLocator {
15+
private static final byte EXPECTED_ENTRY_SIZE = 0x18;
16+
private static final byte EXPECTED_ENTRY_TYPE = 0x01;
17+
private static final byte EXPECTED_NUMBER_OF_SECTIONS_UPPER_BOUND = 0x50;
18+
19+
@Override
20+
public Address[] locateModules(Program program, TaskMonitor monitor, MessageLog log) throws CancelledException {
21+
var result = new ArrayList<Address>();
22+
var memory = program.getMemory();
23+
24+
monitor.setMessage(Constants.TAG + ": Scanning for RTR signatures...");
25+
monitor.setMaximum(memory.getSize());
26+
monitor.setIndeterminate(false);
27+
monitor.setProgress(0);
28+
29+
for (var block : memory.getBlocks()) {
30+
// We assume the modules are stored in an initialized data section.
31+
if (!block.isRead() || !block.isInitialized() || block.isExecute()) {
32+
continue;
33+
}
34+
35+
var current = block.getStart();
36+
var end = block.getEnd();
37+
38+
// Align start to 8 bytes
39+
long offset = current.getOffset();
40+
if (offset % 8 != 0) {
41+
current = current.add(8 - (offset % 8));
42+
}
43+
44+
try{
45+
while (current.compareTo(end) <= 0) {
46+
monitor.checkCancelled();
47+
monitor.setProgress(current.getOffset() - block.getStart().getOffset());
48+
49+
if (isLikelyValidRtrHeader(memory, current)) {
50+
result.add(current);
51+
}
52+
53+
current = current.add(8);
54+
}
55+
} catch (MemoryAccessException e) {
56+
continue;
57+
}
58+
}
59+
60+
return result.toArray(new Address[0]);
61+
}
62+
63+
private static boolean isLikelyValidRtrHeader(Memory memory, Address address) throws MemoryAccessException {
64+
// Expected pattern:
65+
// dOff Type Expected Value Description
66+
// +0x00 ddw 00525452h Signature
67+
// +0x04 dw ?? MajorVersion
68+
// +0x06 dw ?? MinorVersion
69+
// +0x08 ddw ?? Flags
70+
// +0x0c dw < 50h NumberOfSections
71+
// +0x0e db 18h EntrySize
72+
// +0x0f db 1h EntryType
73+
74+
return memory.getInt(address) == Constants.READY_TO_RUN_SIGNATURE
75+
&& memory.getByte(address.add(0x0C)) < EXPECTED_NUMBER_OF_SECTIONS_UPPER_BOUND
76+
&& memory.getByte(address.add(0x0E)) == EXPECTED_ENTRY_SIZE
77+
&& memory.getByte(address.add(0x0F)) == EXPECTED_ENTRY_TYPE;
78+
}
79+
}

src/main/java/nativeaot/rtr/SymbolReadyToRunLocator.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
import ghidra.program.model.address.Address;
99
import ghidra.program.model.listing.Program;
1010
import ghidra.program.model.mem.MemoryAccessException;
11+
import ghidra.util.exception.CancelledException;
1112
import ghidra.util.task.TaskMonitor;
1213
import nativeaot.Constants;
1314

1415
public class SymbolReadyToRunLocator implements ReadyToRunLocator {
1516

1617
@Override
17-
public Address[] locateModules(Program program, TaskMonitor monitor, MessageLog log) {
18+
public Address[] locateModules(Program program, TaskMonitor monitor, MessageLog log) throws CancelledException {
1819
var memory = program.getMemory();
1920
try {
2021
var candidates = findCandidates(program, monitor);

0 commit comments

Comments
 (0)