mirror of
https://github.com/DMaroo/GhidRust.git
synced 2025-10-06 04:53:57 +02:00
Add RustStdAnalyzer
analyzer extension
* This extension uses an existing Function ID database to apply function signatures to Rust binaries (very useful in case of stripped binaries) * So this extension basically detects a Rust binary and auto-applies the correct `libstd` Rust function signatures, pretty cool * Plus, you can also add your own Function ID databases in the `data/` subdirectory if you want to apply even more signatures or, say, custom signatures
This commit is contained in:
225
src/main/java/ghidrust/RustStdAnalyzer.java
Normal file
225
src/main/java/ghidrust/RustStdAnalyzer.java
Normal file
@@ -0,0 +1,225 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidrust;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.services.AbstractAnalyzer;
|
||||
import ghidra.app.services.AnalysisPriority;
|
||||
import ghidra.app.services.AnalyzerType;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.MemoryBlock;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import ghidra.feature.fid.db.FidFileManager;
|
||||
import ghidra.framework.Application;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class RustStdAnalyzer extends AbstractAnalyzer {
|
||||
private static final String filePath = "/home/dhruv/Education/CS4900/Work/GhidRust/tmp/logs";
|
||||
private static BufferedWriter writer;
|
||||
private final byte[][] rust_artifacts = {
|
||||
"run with `RUST_BACKTRACE=1` environment variable".getBytes(),
|
||||
"called `Option::unwrap()` on a `None` value".getBytes(),
|
||||
"called `Result::unwrap()` on an `Err` value".getBytes()
|
||||
};
|
||||
private static final String ENABLED_PROPERTY = "DecompilerParameterAnalyzer.enabled";
|
||||
|
||||
public RustStdAnalyzer() {
|
||||
super("Detect Rust libstd functions",
|
||||
"Detects Rust standard library functions from saved signatures and saves analysis time.\n\nProvided by GhidRust",
|
||||
AnalyzerType.FUNCTION_ANALYZER);
|
||||
|
||||
/*
|
||||
* This is just one above the priority at which the Function ID analyzer runs
|
||||
* (FUNCTION_ID_ANALYSIS - 1)
|
||||
* We need to run before the Function ID analyzer runs because we are populating
|
||||
* the Function ID analyzer
|
||||
* with our own Function ID databases.
|
||||
*/
|
||||
setPriority(AnalysisPriority.FUNCTION_ID_ANALYSIS.before().before());
|
||||
|
||||
try {
|
||||
writer = new BufferedWriter(new FileWriter(filePath, true));
|
||||
} catch (IOException exc) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean contains(byte[] hay, byte[] needle) {
|
||||
if (hay.length < needle.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i <= hay.length - needle.length; i++) {
|
||||
int match_len = 0;
|
||||
for (int j = 0; j < needle.length; j++) {
|
||||
if (needle[j] == hay[i + j]) {
|
||||
match_len++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match_len == needle.length) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void logToFile(String s) {
|
||||
try {
|
||||
writer.append(s);
|
||||
writer.append("\n");
|
||||
writer.flush();
|
||||
} catch (IOException exc) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
static private boolean getNextChunk(InputStream stream, byte[] dest, int size) {
|
||||
byte end;
|
||||
|
||||
try {
|
||||
if (stream.available() <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
end = (byte) stream.read();
|
||||
} catch (IOException exc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < size - 1; i++) {
|
||||
dest[i] = dest[i + 1];
|
||||
}
|
||||
|
||||
dest[size - 1] = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getDefaultEnablement(Program program) {
|
||||
// Make sure the property has not been disabled
|
||||
String default_enabled = System.getProperty(ENABLED_PROPERTY);
|
||||
if (default_enabled != null && !Boolean.parseBoolean(default_enabled)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Be enabled by default so that we can make sure the analysis of Rust functions
|
||||
* takes place
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAnalyze(Program program) {
|
||||
/*
|
||||
* Taken from
|
||||
* https://github.com/mandiant/capa-rules/blob/master/compiler/rust/compiled-
|
||||
* with-rust.yml
|
||||
*/
|
||||
|
||||
/* We know that the strings would be found in the .rodata section */
|
||||
MemoryBlock rodata = program.getMemory().getBlock(".rodata");
|
||||
|
||||
InputStream stream = rodata.getData();
|
||||
|
||||
int search_len = 0;
|
||||
for (byte[] artifact : rust_artifacts) {
|
||||
int artifact_len = artifact.length;
|
||||
|
||||
if (artifact_len > search_len) {
|
||||
search_len = artifact_len;
|
||||
}
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
byte[] chunk = new byte[search_len];
|
||||
|
||||
try {
|
||||
while (stream.available() > 0 && i < chunk.length) {
|
||||
chunk[i] = (byte) stream.read();
|
||||
i++;
|
||||
}
|
||||
} catch (IOException exc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
while (stream.available() > 0) {
|
||||
if (!getNextChunk(stream, chunk, search_len)) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (byte[] artifact : rust_artifacts) {
|
||||
if (contains(chunk, artifact)) {
|
||||
/* This is a Rust binary */
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} catch (IOException exc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
|
||||
throws CancelledException {
|
||||
FidFileManager ffm = FidFileManager.getInstance();
|
||||
if (ffm == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ResourceFile data_dir;
|
||||
try {
|
||||
data_dir = Application.getModuleDataSubDirectory("");
|
||||
} catch (IOException exc) {
|
||||
log.appendException(exc);
|
||||
return false;
|
||||
}
|
||||
|
||||
ResourceFile[] libs = data_dir.listFiles();
|
||||
for (ResourceFile lib : libs) {
|
||||
monitor.checkCanceled();
|
||||
ffm.addUserFidFile(lib.getFile(true));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void analysisEnded(Program program) {
|
||||
super.analysisEnded(program);
|
||||
|
||||
try {
|
||||
writer.close();
|
||||
} catch (IOException exc) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user