Files
GhidRust/src/main/java/ghidrust/analyzer/RustStdAnalyzer.java
2023-04-22 07:38:25 +05:30

221 lines
6.6 KiB
Java

/* ###
* 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.analyzer;
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 static 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;
}
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) {
return isRustBinary(program);
}
@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
}
}
/* For exposing the Rust checking code */
public static boolean isRustBinary(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;
}
}