-
-
Save illusion0001/ea4048933a33b18b305d8b0b6640efd6 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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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") || getMemoryBlock(addr).getName().equals(".text.split"))) | |
continue; | |
instr = getInstructionBefore(addr); | |
// Start at xref to function and work backwards | |
while (true) | |
{ | |
if (instr.getMnemonicString().equalsIgnoreCase("mov")) | |
{ | |
if (instr.getRegister(0) != null) | |
{ | |
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