Skip to content

Instantly share code, notes, and snippets.

@cualquiercosa327
Forked from ethylamine/nativeUpdater.java
Created February 5, 2024 22:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cualquiercosa327/490d637e73c492ce46898739a22dc8b0 to your computer and use it in GitHub Desktop.
Save cualquiercosa327/490d637e73c492ce46898739a22dc8b0 to your computer and use it in GitHub Desktop.
Analyzes and updates native addresses for GTA V on PS4, based on 2much4u's IDA script
//Analyzes and updates native addresses for GTA V on PS4, based on 2much4u's IDA script
//@author ethylamine
//@category Analysis
import ghidra.app.script.GhidraScript;
import ghidra.program.model.util.*;
import ghidra.program.model.reloc.*;
import ghidra.program.model.data.*;
import ghidra.program.model.block.*;
import ghidra.program.model.symbol.*;
import ghidra.program.model.scalar.*;
import ghidra.program.model.mem.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.pcode.*;
import ghidra.program.model.address.*;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.program.model.address.AddressFormatException;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;
public class nativeUpdater extends GhidraScript
{
private static final String CROSSMAP_PATH = "crossmap.h";
private static Address registerNative;
private static HashMap<BigInteger, BigInteger> crossmap = new HashMap<>();
private static HashMap<BigInteger, Function> functionMap = new HashMap<>();
private static HashMap<BigInteger, Function> mergedMap = new HashMap<>();
// Predefined system native hashes
// System native hashes do not change between updates
private static final BigInteger[] SYSTEM_NATIVES =
{
new BigInteger("4EDE34FBADD967A6", 16),
new BigInteger("E81651AD79516E48", 16),
new BigInteger("B8BA7F44DF1575E1", 16),
new BigInteger("EB1C67C3A5333A92", 16),
new BigInteger("C4BB298BD441BE78", 16),
new BigInteger("83666F9FB8FEBD4B", 16),
new BigInteger("C9D9444186B5A374", 16),
new BigInteger("C1B1E9A034A63A62", 16),
new BigInteger("5AE11BC36633DE4E", 16),
new BigInteger("0000000050597EE2", 16),
new BigInteger("0BADBFA3B172435F", 16),
new BigInteger("D0FFB162F40A139C", 16),
new BigInteger("71D93B57D07F9804", 16),
new BigInteger("E3621CC40F31FE2E", 16),
new BigInteger("652D2EEEF1D3E62C", 16),
new BigInteger("A8CEACB4F35AE058", 16),
new BigInteger("2A488C176D52CCA5", 16),
new BigInteger("B7A628320EFF8E47", 16),
new BigInteger("EDD95A39E5544DE8", 16),
new BigInteger("97EF1E5BCE9DC075", 16),
new BigInteger("F34EE736CF047844", 16),
new BigInteger("11E019C8F43ACC8A", 16),
new BigInteger("F2DB717A73826179", 16),
new BigInteger("BBDA792448DB5A89", 16)
};
// Read crossmap file into hashmap
private void parseCrossMap() throws FileNotFoundException, CancelledException, IOException
{
File file = new File(CROSSMAP_PATH);
int nativeCount = 0;
String[] values;
BigInteger hash1, hash2;
if (!file.exists())
{
file = askFile("Choose a crossmap file", "Open");
}
@SuppressWarnings("unchecked")
List<String> rawLines = FileUtils.readLines(file);
for (String line : rawLines)
{
values = line.replace('\n', ' ').trim().split(",");
hash1 = new BigInteger(values[0].substring(2), 16);
hash2 = new BigInteger(values[1].substring(2), 16);
crossmap.put(hash2, hash1);
nativeCount++;
}
println("Found " + String.valueOf(nativeCount) + " natives in crossmap");
}
// Read native hashes and functions into hashmap from binary
private void findNativeFunctions(Program program) throws CancelledException, AddressFormatException
{
registerNative = askAddress("Native Address", "Select registerNative function:");
Address addr, addr2;
Instruction instr;
int nativeCount = 0;
ReferenceManager refMgr = program.getReferenceManager();
ReferenceIterator xrefs = refMgr.getReferencesTo(registerNative);
Object[] hash, func;
while (xrefs.hasNext())
{
addr = xrefs.next().getFromAddress();
if (!(getMemoryBlock(addr).getName().equals(".text")))
continue;
instr = getInstructionBefore(addr);
// Start at xref to function and work backwards
while (true)
{
if (instr.getMnemonicString().equalsIgnoreCase("mov"))
{
if (instr.getRegister(0).getName().equalsIgnoreCase("edi") || instr.getRegister(0).getName().equalsIgnoreCase("rdi"))
break;
}
instr = getInstructionBefore(instr);
}
// Function address is always the line before the hash
hash = instr.getOpObjects(1);
func = getInstructionBefore(instr).getOpObjects(1);
addr2 = parseAddress(func[0].toString());
functionMap.put(new Scalar(64, parseLong(hash[0].toString()), false).getBigInteger(), getFunctionAt(addr2));
nativeCount++;
}
println("Found " + String.valueOf(nativeCount) + " natives in EBOOT");
}
// Merge the crossmap and function map hashmaps
private void mergeMaps()
{
int nativeCount = 0;
BigInteger oldHash = null;
for (Map.Entry<BigInteger, Function> entry : functionMap.entrySet())
{
if (crossmap.get(entry.getKey()) == null)
continue;
oldHash = crossmap.get(entry.getKey());
if (!oldHash.equals(null))
{
mergedMap.put(oldHash, entry.getValue());
nativeCount++;
}
}
println("Merged " + String.valueOf(nativeCount) + " natives in the maps");
}
// Find system natives (usually excluded from crossmap)
private void findSystemNatives()
{
// Find registerNativeInTable address from registerNative address
Address addr = registerNative;
Address registerNativeInTable = null;
Instruction instr = getInstructionAt(addr);;
Object[] objs, hash = {0}, func = {0};
Reference[] xrefs;
boolean flag = false;
while (true)
{
if (instr.getMnemonicString().equalsIgnoreCase("jmp"))
{
objs = instr.getOpObjects(0);
registerNativeInTable = (Address)objs[0];
break;
}
instr = getInstructionAfter(instr);
}
// Search xrefs to registerNativeInTable
xrefs = getReferencesTo(registerNativeInTable);
for (Reference ref : xrefs)
{
addr = ref.getFromAddress();
// Start at xref to function and work backwards
hash[0] = Integer.valueOf(0);
func[0] = Integer.valueOf(0);
if (!(getMemoryBlock(addr).getName().equals(".text")))
continue;
instr = getInstructionBefore(addr);
while (true)
{
if (hash[0] != Integer.valueOf(0) && func[0] != Integer.valueOf(0))
break;
if (instr.getMnemonicString().equalsIgnoreCase("mov") && hash[0] == Integer.valueOf(0))
{
if (instr.getRegister(0).getName().equalsIgnoreCase("esi") || instr.getRegister(0).getName().equalsIgnoreCase("rsi"))
{
hash = instr.getOpObjects(1);
if (hash[0].toString().length() <= 3)
{
hash[0] = Integer.valueOf(0);
break;
}
for (BigInteger i : SYSTEM_NATIVES)
{
if (!(new Scalar(64, parseLong(hash[0].toString()), false).getBigInteger().equals(i)))
{
flag = true;
}
else
{
flag = false;
break;
}
}
if (flag)
{
hash[0] = Integer.valueOf(0);
break;
}
}
}
else if (instr.getMnemonicString().equalsIgnoreCase("lea") && func[0] == Integer.valueOf(0))
{
if (instr.getRegister(0).getName().equalsIgnoreCase("rdx"))
func = instr.getOpObjects(1);
}
instr = getInstructionBefore(instr);
}
if (hash[0] != Integer.valueOf(0))
mergedMap.putIfAbsent(new Scalar(64, parseLong(hash[0].toString()), false).getBigInteger(), getFunctionAt(parseAddress(func[0].toString())));
}
}
// Overwrite native hashes in clean header with function addresses
private void createHeader() throws FileNotFoundException, IOException, CancelledException
{
File file = askFile("Choose a native header file", "Open");
int missedNatives = 0;
String[] openParenthSplit, closeParenthSplit, commaSplit = {""};
String hash, newLine;
Function func;
Address funcAddr;
@SuppressWarnings("unchecked")
List<String> rawLines = FileUtils.readLines(file);
List<String> fileLines = new ArrayList<String>();
for (String line : rawLines)
{
if (line.contains("invoke"))
{
openParenthSplit = line.split("\\>\\(");
closeParenthSplit = openParenthSplit[1].split("\\)");
hash = "";
commaSplit[0] = "";
newLine = "";
if (closeParenthSplit[0].length() > 10)
{
commaSplit = closeParenthSplit[0].split("\\,");
hash = commaSplit[0];
}
else
hash = closeParenthSplit[0];
func = mergedMap.get(new BigInteger(hash.substring(2), 16));
newLine = openParenthSplit[0] + ">(";
if (!(func == null))
{
funcAddr = func.getEntryPoint();
newLine += funcAddr.toString("0x").toUpperCase();
}
else
{
// Write DEADBEEF for hashes not found
newLine += "0xDEADBEEF";
missedNatives++;
printerr("Failed to find address for " + hash);
}
if (closeParenthSplit[0].split(hash).length == 0)
newLine += ")";
else
newLine += closeParenthSplit[0].split(hash)[1] + ")";
newLine += closeParenthSplit[1];
fileLines.add(newLine);
}
else
fileLines.add(line);
}
file = new File("newHeader.h");
FileUtils.writeLines(file, fileLines, "\n");
println("newHeader.h created (ghidra install dir)");
printerr("Unable to replace " + String.valueOf(missedNatives) + " natives");
}
public void run() throws Exception
{
parseCrossMap();
findNativeFunctions(currentProgram);
mergeMaps();
// Feel free to comment out this line if your crossmap includes system natives
findSystemNatives();
createHeader();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment