Initial commit: full decompilation and activation patches for T-Deck firmwares
This commit is contained in:
390
scripts/DecompilerExporter.java
Normal file
390
scripts/DecompilerExporter.java
Normal file
@@ -0,0 +1,390 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
|
||||
import generic.cache.CachingPool;
|
||||
import generic.cache.CountingBasicFactory;
|
||||
import generic.concurrent.QCallback;
|
||||
import ghidra.app.decompiler.*;
|
||||
import ghidra.app.decompiler.DecompileOptions.CommentStyleEnum;
|
||||
import ghidra.app.decompiler.parallel.ChunkingParallelDecompiler;
|
||||
import ghidra.app.decompiler.parallel.ParallelDecompiler;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.program.database.symbol.SymbolManager;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.mem.Memory;
|
||||
import ghidra.program.model.mem.MemoryAccessException;
|
||||
import ghidra.program.model.symbol.Symbol;
|
||||
import ghidra.program.model.symbol.SymbolType;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.*;
|
||||
|
||||
public class DecompilerExporter extends GhidraScript {
|
||||
|
||||
private static String EOL = System.getProperty("line.separator");
|
||||
private boolean isUseCppStyleComments = true;
|
||||
private DecompileOptions options;
|
||||
private String odirpath = "/tmp/decomp_exporter";
|
||||
private String header_filename;
|
||||
private String glob_filename;
|
||||
|
||||
@Override
|
||||
protected void run() throws Exception {
|
||||
String[] args = getScriptArgs();
|
||||
if (args.length == 0) {
|
||||
System.err.println("Using " + odirpath + " as default path");
|
||||
} else {
|
||||
odirpath = args[0];
|
||||
}
|
||||
|
||||
Files.createDirectories(Paths.get(odirpath));
|
||||
header_filename = odirpath + "/" + currentProgram.getName() + ".h";
|
||||
glob_filename = odirpath + "/glob.c";
|
||||
|
||||
Program program = currentProgram;
|
||||
|
||||
options = new DecompileOptions();
|
||||
options.setCommentStyle(CommentStyleEnum.CPPStyle);
|
||||
|
||||
AddressSetView addrSet = program.getMemory();
|
||||
|
||||
CachingPool<DecompInterface> decompilerPool = new CachingPool<>(new DecompilerFactory(program));
|
||||
ParallelDecompilerCallback callback = new ParallelDecompilerCallback(decompilerPool);
|
||||
ChunkingTaskMonitor chunkingMonitor = new ChunkingTaskMonitor(monitor);
|
||||
ChunkingParallelDecompiler<CPPResult> parallelDecompiler = ParallelDecompiler
|
||||
.createChunkingParallelDecompiler(callback, chunkingMonitor);
|
||||
|
||||
PrintWriter headerWriter = new PrintWriter(header_filename);
|
||||
|
||||
try {
|
||||
writeProgramDataTypes(program, headerWriter, chunkingMonitor);
|
||||
writeProgramGlobalVariables(program, headerWriter, chunkingMonitor);
|
||||
chunkingMonitor.checkCanceled();
|
||||
|
||||
decompileAndExport(addrSet, program, headerWriter, parallelDecompiler, chunkingMonitor);
|
||||
} catch (CancelledException e) {
|
||||
Msg.error(this, "Operation Cancelled");
|
||||
} catch (Exception e) {
|
||||
Msg.error(this, "Error exporting C/C++", e);
|
||||
} finally {
|
||||
decompilerPool.dispose();
|
||||
parallelDecompiler.dispose();
|
||||
|
||||
if (headerWriter != null) {
|
||||
headerWriter.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void decompileAndExport(AddressSetView addrSet, Program program, PrintWriter headerWriter,
|
||||
ChunkingParallelDecompiler<CPPResult> parallelDecompiler, ChunkingTaskMonitor chunkingMonitor)
|
||||
throws InterruptedException, Exception, CancelledException {
|
||||
|
||||
int functionCount = program.getFunctionManager().getFunctionCount();
|
||||
chunkingMonitor.doInitialize(functionCount);
|
||||
|
||||
Listing listing = program.getListing();
|
||||
FunctionIterator iterator = listing.getFunctions(addrSet, true);
|
||||
List<Function> functions = new ArrayList<>();
|
||||
for (int i = 0; iterator.hasNext(); i++) {
|
||||
if (i % 10000 == 0) {
|
||||
List<CPPResult> results = parallelDecompiler.decompileFunctions(functions);
|
||||
writeResults(results, headerWriter, chunkingMonitor);
|
||||
functions.clear();
|
||||
}
|
||||
|
||||
Function currentFunction = iterator.next();
|
||||
if (excludeFunction(currentFunction)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
functions.add(currentFunction);
|
||||
}
|
||||
|
||||
List<CPPResult> results = parallelDecompiler.decompileFunctions(functions);
|
||||
writeResults(results, headerWriter, chunkingMonitor);
|
||||
}
|
||||
|
||||
private boolean excludeFunction(Function currentFunction) {
|
||||
return currentFunction.isExternal() || currentFunction.isThunk();
|
||||
}
|
||||
|
||||
private void writeResults(List<CPPResult> results, PrintWriter headerWriter, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
monitor.checkCanceled();
|
||||
|
||||
Collections.sort(results);
|
||||
|
||||
StringBuilder headers = new StringBuilder();
|
||||
for (CPPResult result : results) {
|
||||
monitor.checkCanceled();
|
||||
if (result == null) {
|
||||
continue;
|
||||
}
|
||||
String headerCode = result.getHeaderCode();
|
||||
if (headerCode != null) {
|
||||
headers.append(headerCode);
|
||||
headers.append(EOL);
|
||||
}
|
||||
|
||||
String bodyCode = result.getBodyCode();
|
||||
String function_filename = odirpath + "/" + result.name + ".c";
|
||||
try {
|
||||
PrintWriter functionWriter = new PrintWriter(function_filename);
|
||||
functionWriter.write("#include \"" + Paths.get(header_filename).getFileName().toString() + "\"\n");
|
||||
functionWriter.write(bodyCode);
|
||||
functionWriter.close();
|
||||
} catch (IOException e) {
|
||||
Msg.error(this, "Unable to write function " + result.name);
|
||||
}
|
||||
}
|
||||
|
||||
monitor.checkCanceled();
|
||||
|
||||
if (headerWriter != null) {
|
||||
headerWriter.println(headers.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void writeProgramDataTypes(Program program, PrintWriter headerWriter, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
DataTypeManager dtm = program.getDataTypeManager();
|
||||
DataTypeWriter dataTypeWriter = new DataTypeWriter(dtm, headerWriter, isUseCppStyleComments);
|
||||
headerWriter.write(getFakeCTypeDefinitions(dtm.getDataOrganization()));
|
||||
dataTypeWriter.write(dtm, monitor);
|
||||
|
||||
headerWriter.println("");
|
||||
headerWriter.println("");
|
||||
}
|
||||
|
||||
private static String bytes_to_array_str(byte[] data) {
|
||||
StringBuilder cbuf = new StringBuilder();
|
||||
for (byte b : data) {
|
||||
cbuf.append(String.format("0x%02x, ", b & 0xFF));
|
||||
}
|
||||
return cbuf.toString();
|
||||
}
|
||||
|
||||
public static String escape_byte_to_str(byte[] data) {
|
||||
StringBuilder cbuf = new StringBuilder();
|
||||
for (byte b : data) {
|
||||
if (b >= 0x20 && b <= 0x7e) {
|
||||
cbuf.append((char) b);
|
||||
} else {
|
||||
cbuf.append(String.format("\\x%02x", b & 0xFF));
|
||||
}
|
||||
}
|
||||
return cbuf.toString();
|
||||
}
|
||||
|
||||
private void writeProgramGlobalVariables(Program program, PrintWriter headerWriter, TaskMonitor monitor)
|
||||
throws FileNotFoundException {
|
||||
PrintWriter globWriter = new PrintWriter(glob_filename);
|
||||
globWriter.write("#include \"" + Paths.get(header_filename).getFileName().toString() + "\"\n\n");
|
||||
|
||||
SymbolManager smgr = (SymbolManager) program.getSymbolTable();
|
||||
for (Symbol sym : smgr.getAllSymbols(true)) {
|
||||
SymbolType st = sym.getSymbolType();
|
||||
if (st != SymbolType.LABEL)
|
||||
continue;
|
||||
DataType dataType;
|
||||
int data_size;
|
||||
Object dataObj = sym.getObject();
|
||||
if (dataObj instanceof Data) {
|
||||
dataType = ((Data) dataObj).getDataType();
|
||||
data_size = dataType.getLength();
|
||||
} else {
|
||||
dataType = DataType.DEFAULT;
|
||||
data_size = 1;
|
||||
}
|
||||
if (data_size < 0)
|
||||
data_size = 1;
|
||||
|
||||
boolean is_string = dataType.getName().equals("string") || dataType.getName().equals("TerminatedCString");
|
||||
if (is_string) {
|
||||
data_size = 0;
|
||||
Memory memory = program.getMemory();
|
||||
while (true) {
|
||||
byte b;
|
||||
try {
|
||||
b = memory.getByte(sym.getAddress().add(data_size));
|
||||
} catch (MemoryAccessException e) {
|
||||
break;
|
||||
}
|
||||
if (b == 0)
|
||||
break;
|
||||
data_size += 1;
|
||||
}
|
||||
}
|
||||
|
||||
String name_suffix = "";
|
||||
String data_type_name = dataType.getName();
|
||||
if (data_type_name.endsWith("]")) {
|
||||
String[] tokens = data_type_name.split("\\[");
|
||||
data_type_name = tokens[0];
|
||||
name_suffix = "[" + tokens[1];
|
||||
}
|
||||
|
||||
String normalized_name = sym.getName().replaceAll("[^0-9a-zA-Z_]", "_");
|
||||
headerWriter.write("extern " + data_type_name + " " + normalized_name + name_suffix + ";\n");
|
||||
|
||||
Memory memory = program.getMemory();
|
||||
byte[] bytes = new byte[data_size];
|
||||
try {
|
||||
int count = memory.getBytes(sym.getAddress(), bytes);
|
||||
if (count != data_size)
|
||||
Msg.error(this, "unable to read all data from " + sym.getAddress().toString());
|
||||
} catch (MemoryAccessException e) {
|
||||
Msg.error(this, "unable to read data from " + sym.getAddress().toString());
|
||||
}
|
||||
|
||||
globWriter.write(data_type_name + " " + normalized_name + name_suffix + " = "
|
||||
+ (is_string ? ("\"" + escape_byte_to_str(bytes) + "\"")
|
||||
: ("{ " + bytes_to_array_str(bytes) + " }"))
|
||||
+ " ;\n");
|
||||
}
|
||||
globWriter.close();
|
||||
}
|
||||
|
||||
private static String getBuiltInDeclaration(String typeName, String ctypeName) {
|
||||
return "#define " + typeName + " " + ctypeName + EOL;
|
||||
}
|
||||
|
||||
private static String getBuiltInDeclaration(String typeName, int typeLen, boolean signed,
|
||||
DataOrganization dataOrganization) {
|
||||
return getBuiltInDeclaration(typeName, dataOrganization.getIntegerCTypeApproximation(typeLen, signed));
|
||||
}
|
||||
|
||||
private static String getFakeCTypeDefinitions(DataOrganization dataOrganization) {
|
||||
StringWriter writer = new StringWriter();
|
||||
for (int n = 9; n <= 16; n++) {
|
||||
writer.write(getBuiltInDeclaration("unkbyte" + n, n, false, dataOrganization));
|
||||
}
|
||||
writer.write(EOL);
|
||||
for (int n = 9; n <= 16; n++) {
|
||||
writer.write(getBuiltInDeclaration("unkuint" + n, n, false, dataOrganization));
|
||||
}
|
||||
writer.write(EOL);
|
||||
for (int n = 9; n <= 16; n++) {
|
||||
writer.write(getBuiltInDeclaration("unkint" + n, n, true, dataOrganization));
|
||||
}
|
||||
writer.write(EOL);
|
||||
writer.write(getBuiltInDeclaration("unkfloat1", "float"));
|
||||
writer.write(getBuiltInDeclaration("unkfloat2", "float"));
|
||||
writer.write(getBuiltInDeclaration("unkfloat3", "float"));
|
||||
writer.write(getBuiltInDeclaration("unkfloat5", "double"));
|
||||
writer.write(getBuiltInDeclaration("unkfloat6", "double"));
|
||||
writer.write(getBuiltInDeclaration("unkfloat7", "double"));
|
||||
writer.write(getBuiltInDeclaration("unkfloat9", "long double"));
|
||||
writer.write(getBuiltInDeclaration("unkfloat11", "long double"));
|
||||
writer.write(getBuiltInDeclaration("unkfloat12", "long double"));
|
||||
writer.write(getBuiltInDeclaration("unkfloat13", "long double"));
|
||||
writer.write(getBuiltInDeclaration("unkfloat14", "long double"));
|
||||
writer.write(getBuiltInDeclaration("unkfloat15", "long double"));
|
||||
writer.write(getBuiltInDeclaration("unkfloat16", "long double"));
|
||||
writer.write(EOL);
|
||||
writer.write(getBuiltInDeclaration("BADSPACEBASE", "void"));
|
||||
writer.write(getBuiltInDeclaration("code", "void"));
|
||||
writer.write(EOL);
|
||||
writer.write(getBuiltInDeclaration("string", "char*"));
|
||||
writer.write(getBuiltInDeclaration("TerminatedCString", "char*"));
|
||||
writer.write(getBuiltInDeclaration("pointer", "unsigned long"));
|
||||
writer.write(getBuiltInDeclaration("bool", "int"));
|
||||
writer.write(getBuiltInDeclaration("true", "1"));
|
||||
writer.write(getBuiltInDeclaration("false", "0"));
|
||||
writer.write(EOL);
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
private class CPPResult implements Comparable<CPPResult> {
|
||||
private String name;
|
||||
private Address address;
|
||||
private String bodyCode;
|
||||
private String headerCode;
|
||||
|
||||
CPPResult(String name, Address address, String headerCode, String bodyCode) {
|
||||
this.name = name + "@" + address.toString();
|
||||
this.address = address;
|
||||
this.headerCode = headerCode;
|
||||
this.bodyCode = bodyCode;
|
||||
}
|
||||
|
||||
String getHeaderCode() { return headerCode; }
|
||||
String getBodyCode() { return bodyCode; }
|
||||
|
||||
@Override
|
||||
public int compareTo(CPPResult other) {
|
||||
return address.compareTo(other.address);
|
||||
}
|
||||
}
|
||||
|
||||
private class DecompilerFactory extends CountingBasicFactory<DecompInterface> {
|
||||
private Program program;
|
||||
DecompilerFactory(Program program) { this.program = program; }
|
||||
@Override
|
||||
public DecompInterface doCreate(int itemNumber) throws IOException {
|
||||
DecompInterface decompiler = new DecompInterface();
|
||||
decompiler.setOptions(options);
|
||||
decompiler.openProgram(program);
|
||||
decompiler.toggleSyntaxTree(false);
|
||||
return decompiler;
|
||||
}
|
||||
@Override
|
||||
public void doDispose(DecompInterface decompiler) { decompiler.dispose(); }
|
||||
}
|
||||
|
||||
private class ParallelDecompilerCallback implements QCallback<Function, CPPResult> {
|
||||
private CachingPool<DecompInterface> pool;
|
||||
ParallelDecompilerCallback(CachingPool<DecompInterface> decompilerPool) { this.pool = decompilerPool; }
|
||||
@Override
|
||||
public CPPResult process(Function function, TaskMonitor monitor) throws Exception {
|
||||
if (monitor.isCancelled()) return null;
|
||||
DecompInterface decompiler = pool.get();
|
||||
try {
|
||||
return doWork(function, decompiler, monitor);
|
||||
} finally {
|
||||
pool.release(decompiler);
|
||||
}
|
||||
}
|
||||
private CPPResult doWork(Function function, DecompInterface decompiler, TaskMonitor monitor) {
|
||||
Address entryPoint = function.getEntryPoint();
|
||||
monitor.setMessage("Decompiling " + function.getName());
|
||||
DecompileResults dr = decompiler.decompileFunction(function, options.getDefaultTimeout(), monitor);
|
||||
if (!dr.decompiledSuccessfully()) {
|
||||
return new CPPResult(function.getName(), entryPoint, function.getPrototypeString(false, false) + ';', null);
|
||||
}
|
||||
DecompiledFunction decompiledFunction = dr.getDecompiledFunction();
|
||||
return new CPPResult(function.getName(), entryPoint, decompiledFunction.getSignature(), decompiledFunction.getC());
|
||||
}
|
||||
}
|
||||
|
||||
private class ChunkingTaskMonitor extends TaskMonitorAdapter {
|
||||
private TaskMonitor monitor;
|
||||
ChunkingTaskMonitor(TaskMonitor monitor) { this.monitor = monitor; }
|
||||
void doInitialize(long value) { monitor.initialize(value); }
|
||||
@Override public void setProgress(long value) { monitor.setProgress(value); }
|
||||
@Override public void checkCanceled() throws CancelledException { monitor.checkCanceled(); }
|
||||
@Override public void setMessage(String message) { monitor.setMessage(message); }
|
||||
}
|
||||
}
|
||||
11
scripts/analyze_firmware.sh
Executable file
11
scripts/analyze_firmware.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
FIRMWARE="$1"
|
||||
[[ -z "$FIRMWARE" ]] && echo "Usage: $0 <firmware.bin>" && exit 1
|
||||
|
||||
echo "=== ESP32 Firmware Analysis ==="
|
||||
echo "File: $FIRMWARE"
|
||||
echo ""
|
||||
echo "[1] File Info:"; file "$FIRMWARE"
|
||||
echo ""; echo "[2] Strings:"; strings "$FIRMWARE" | head -50
|
||||
echo ""; echo "[3] Binwalk:"; binwalk "$FIRMWARE"
|
||||
echo ""; echo "[4] ESP32 Image Info:"; esptool.py image_info "$FIRMWARE" 2>/dev/null || echo "Not ESP32 image"
|
||||
76
scripts/create_elf_s3.py
Normal file
76
scripts/create_elf_s3.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import sys
|
||||
from makeelf.elf import ELF, EM, ELFDATA, ELFCLASS
|
||||
|
||||
|
||||
def create_elf(output_path, segments, entry_point):
|
||||
elf = ELF(
|
||||
e_machine=EM.EM_XTENSA, e_data=ELFDATA.ELFDATA2LSB, e_class=ELFCLASS.ELFCLASS32
|
||||
)
|
||||
elf.Elf.Ehdr.e_entry = entry_point
|
||||
|
||||
for name, addr, data_path in segments:
|
||||
with open(data_path, "rb") as f:
|
||||
data = f.read()
|
||||
elf.append_section(name, data, addr)
|
||||
|
||||
print(f"Writing ELF to {output_path}...")
|
||||
with open(output_path, "wb") as f:
|
||||
f.write(bytes(elf))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ultra_segments = [
|
||||
(
|
||||
".flash.rodata",
|
||||
0x3C0E0020,
|
||||
"/home/sapient/esp32-analysis/extracted/seg0_DROM.bin",
|
||||
),
|
||||
(
|
||||
".dram0.data",
|
||||
0x3FC96440,
|
||||
"/home/sapient/esp32-analysis/extracted/seg1_DRAM.bin",
|
||||
),
|
||||
(
|
||||
".iram0.text",
|
||||
0x40374000,
|
||||
"/home/sapient/esp32-analysis/extracted/seg2_IRAM.bin",
|
||||
),
|
||||
(
|
||||
".flash.text",
|
||||
0x42000020,
|
||||
"/home/sapient/esp32-analysis/extracted/seg3_IROM.bin",
|
||||
),
|
||||
]
|
||||
create_elf(
|
||||
"/home/sapient/esp32-analysis/output_dumps/Ultra-TDeck-v9.2.elf",
|
||||
ultra_segments,
|
||||
0x4037736C,
|
||||
)
|
||||
|
||||
meshos_segments = [
|
||||
(
|
||||
".flash.rodata",
|
||||
0x3C190020,
|
||||
"/home/sapient/esp32-analysis/MeshOS_1.1.8/extracted/seg0_DROM.bin",
|
||||
),
|
||||
(
|
||||
".dram0.data",
|
||||
0x3FC98F50,
|
||||
"/home/sapient/esp32-analysis/MeshOS_1.1.8/extracted/seg1_DRAM.bin",
|
||||
),
|
||||
(
|
||||
".iram0.text",
|
||||
0x40374000,
|
||||
"/home/sapient/esp32-analysis/MeshOS_1.1.8/extracted/seg2_IRAM.bin",
|
||||
),
|
||||
(
|
||||
".flash.text",
|
||||
0x42000020,
|
||||
"/home/sapient/esp32-analysis/MeshOS_1.1.8/extracted/seg3_IROM.bin",
|
||||
),
|
||||
]
|
||||
create_elf(
|
||||
"/home/sapient/esp32-analysis/output_dumps/MeshOS-TDeck-1.1.8.elf",
|
||||
meshos_segments,
|
||||
0x40377A10,
|
||||
)
|
||||
1
scripts/esp32_image_parser
Submodule
1
scripts/esp32_image_parser
Submodule
Submodule scripts/esp32_image_parser added at 99376447ae
Reference in New Issue
Block a user