Cool post bro
Corpo life has neutered my brain cells#
Looking from waybackmachine, last time I made a post on a website I used to own was around 2013. And damn was I busy creating stuff and having fun 😄
- Freelancer (cool space game by microsoft) - Teleporting, XY turn rate hack, speed hack during space travel, warp drive charge up speed
- Planetside 2 norecoil/spread & speedhack
- Far Cry speedhack (lmao I really dislike that franchise)
- Tom Glancy’s Ghost Recon Future - menu language switcher???
- Directx 9 Screenshot via hooking CreateOffScreenPlainSurface
I maybe have some of that stuff still available somewhere on my old drives. But anyway, I feel that I need to start having fun again. Working as a software engineer has definitely been rewarding in many aspects, though I feel that the initial reason why I started loving it has really diminished from my life. Corpo life most of the time is just a brain rot. The excitement I get from cracking something open with RE is different.
I still remember the fun I had when I was a teenager, I reverse engineered Quake 2 code for months without understanding anything about it - and then slowly starting to grasp at the straws. Watched Ollydbg tutorials on tuts4you. I think there was a woman named Lena who made really good video tutorials back in the day (I went and googled it now and seems like they are still being shared, awesome: link). That was some “underground” content back in the day 😄.
Not sure I really want to write that much code after work day is over but maybe reverse engineering can be made into a hobby. The past week on my vacation, I’ve been studying new tools (for me) like Binary Ninja and reversing Path of Exile 2. Actually had quite a bit of fun. Which made me think that perhaps kicking up a website for these projects is not that bad idea after all. Then maybe I have something to do behind a computer after I’ve put my kid to sleep.
These days access to information is also so much more readily available. 10 Years back you had script-kiddies gatekeeping methods for RE like they had invented a fusion reactor. These days people are trying to be influencers in all kinds of areas and even on reddit there are some threads popping to my feed how people are doing RE projects.
What I am currently working on#
Path of Exile 2 - 1/3 of trading bot complete#
Here’s a screenshot of Path of Exile 2 hook for chat. These addresses are for version 0.2.1b if you are a gourmet in the field - feel free digg in.
I am thinking of creating a trade bot and then maybe… sell it? lmao not gonna give it free my dude. Gotta afford diapers 😄.

Ghidra Extension Preview_Function_Capabilities ported and optimized to Java#
Also introduced myself to Ghidra. Back in the day I used IDA and never bothered to look at Ghidra. Cool tool but I am more inclined on purchasing a license for Binary Ninja. Allthough with this short encounter with Ghidra, I had the luxury of finding a plugin by AGDCservices called “Preview_Function_Capabilities”. When it had been analyzing Path of Exile executable for 4 hours, I interrupted the task process and ported that thing to Java. I don’t think I plan going back to ghidra so I am not gonna even create a git repository for it.
But anyway, this is not just a port. It’s actually quite big refactor from the original code. The Java plugin builds the full function call graph in a single pass and uses a worklist algorithm to propagate capabilities upward from leaf to root functions. This avoids the multiple nested loops in the Python version and ensures convergent propagation without redundant re-analysis.
IIRC it ran in 30~ secs to rename all methods (or maybe its not working at all 😄). I included some of my own capability detections also. Maybe I will come back to it later. So I am just gonna dump it here for the first bot that ends up reading this website.
Should be put into the same directory with other AGDCServices plugins.
//@author Miko (Original Python by AGDCServices) Refactored to Java
//@category AGDCservices
//@keybinding
//@menupath
//@toolbar
//@runtime Java
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class PreviewFunctionsV2 extends GhidraScript {
private static final String GHIDRA_FUNC_PREFIX = "FUN_";
private static final String CUSTOM_AUTO_FUNC_PREFIX = "f_p__";
private static final String CUSTOM_AUTO_THREAD_FUNC_PREFIX = "f_p__TS__";
private static final int OP_TYPE_PUSH_REGISTER = 512;
private static final int OP_TYPE_CALL_STATIC_FUNCTION = 8256;
private static final int OP_TYPE_CALL_DATA_VARIABLE = 8324;
private Map<String, String> apiPurposeMap;
private LinkedHashMap<String, List<Character>> categoryMap;
private Pattern apiNamePattern;
private static class FunctionInfo {
Function function;
int referenceCount;
Set<String> directPurposes = new HashSet<>();
Set<String> allPropagatedPurposes = new HashSet<>();
Set<FunctionInfo> callers = new HashSet<>();
Set<FunctionInfo> callees = new HashSet<>();
FunctionInfo(Function f) {
this.function = f;
}
@Override
public int hashCode() {
return function.getEntryPoint().hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
return function.getEntryPoint().equals(((FunctionInfo) o).function.getEntryPoint());
}
}
@Override
public void run() throws Exception {
initializeMaps();
println("".repeat(100));
println("Function_Preview Script Starting (Final Complete & Optimized Version)");
renameThreadRoots();
println("Phase 1: Analyzing functions and building call graph...");
Map<Function, FunctionInfo> analysisMap = new HashMap<>();
List<Function> funcList = getTargetFunctions();
for (Function f : funcList) {
analysisMap.put(f, new FunctionInfo(f));
}
for (FunctionInfo info : analysisMap.values()) {
monitor.checkCancelled();
Function f = info.function;
info.referenceCount = getReferencesTo(f.getEntryPoint()).length;
info.directPurposes = analyzeDirectCapabilities(f);
for (Function callee : getCalledInternalFunctions(f)) {
FunctionInfo calleeInfo = analysisMap.get(callee);
if (calleeInfo != null) {
info.callees.add(calleeInfo);
calleeInfo.callers.add(info);
}
}
}
println("Phase 2: Propagating capabilities up the call graph...");
Queue<FunctionInfo> worklist = analysisMap.values().stream().filter(info -> info.callees.isEmpty())
.collect(Collectors.toCollection(LinkedList::new));
Set<FunctionInfo> inWorklist = new HashSet<>(worklist);
while (!worklist.isEmpty()) {
monitor.checkCancelled();
FunctionInfo info = worklist.poll();
inWorklist.remove(info);
Set<String> newPropagatedPurposes = new HashSet<>(info.directPurposes);
for (FunctionInfo calleeInfo : info.callees) {
newPropagatedPurposes.addAll(calleeInfo.allPropagatedPurposes);
}
if (!newPropagatedPurposes.equals(info.allPropagatedPurposes)) {
info.allPropagatedPurposes = newPropagatedPurposes;
for (FunctionInfo callerInfo : info.callers) {
if (!inWorklist.contains(callerInfo)) {
worklist.add(callerInfo);
inWorklist.add(callerInfo);
}
}
}
}
println("Phase 3: Renaming functions...");
int renamedCount = 0;
for (FunctionInfo info : analysisMap.values()) {
monitor.checkCancelled();
String oldName = info.function.getName();
String newName = buildFinalName(info);
if (!newName.equals(oldName)) {
info.function.setName(newName, SourceType.USER_DEFINED);
renamedCount++;
}
}
println("Function_Preview Script Completed. Renamed " + renamedCount + " functions.");
println("".repeat(100));
}
private Set<String> analyzeDirectCapabilities(Function func) {
Set<String> usedApiBases = new HashSet<>();
if (func.getBody() == null || func.getBody().isEmpty()) {
return usedApiBases;
}
InstructionIterator iter = currentProgram.getListing().getInstructions(func.getBody(), true);
while (iter.hasNext()) {
Instruction instr = iter.next();
if (!"call".equalsIgnoreCase(instr.getMnemonicString())) {
continue;
}
String apiBaseName = null;
// 1) Direct external API calls (DLLs, etc.)
Reference extRef = instr.getExternalReference(0);
if (extRef != null) {
Symbol sym = getSymbolAt(extRef.getToAddress());
apiBaseName = getBaseNameFromSymbol(sym);
// 2) Static calls to other functions in the program
} else if (instr.getOperandType(0) == OP_TYPE_CALL_STATIC_FUNCTION) {
Reference[] opRefs = instr.getOperandReferences(0);
if (opRefs.length > 0) {
Function target = getFunctionAt(opRefs[0].getToAddress());
if (target != null && !isAutoNamed(target.getName())) {
Symbol sym = target.getSymbol();
if (sym != null) {
apiBaseName = getBaseNameFromSymbol(sym);
}
}
}
// 3) Indirect calls via function-pointer data variables
} else if (instr.getOperandType(0) == OP_TYPE_CALL_DATA_VARIABLE) {
Reference[] opRefs = instr.getOperandReferences(0);
if (opRefs.length > 0) {
Address dataAddr = opRefs[0].getToAddress();
Data d = getDataAt(dataAddr);
if (d == null) {
d = getUndefinedDataAt(dataAddr);
}
if (d != null) {
// 3a) First, look for any external references on the data value
for (Reference valRef : d.getValueReferences()) {
if (valRef.isExternalReference()) {
Symbol extSym = getSymbolAt(valRef.getToAddress());
apiBaseName = getBaseNameFromSymbol(extSym);
break;
}
}
// 3b) Fallback: use the primary symbol at the data’s address,
// skipping generic "dat_*" labels
if (apiBaseName == null) {
Symbol sym = getSymbolAt(dataAddr);
if (sym != null && !sym.getName().toLowerCase().startsWith("dat_")) {
apiBaseName = getBaseNameFromSymbol(sym);
}
}
}
}
}
if (apiBaseName != null) {
usedApiBases.add(apiBaseName);
}
}
// Map the raw base-names into our netw/reg/file/etc. codes, dropping any nulls
return usedApiBases.stream().map(apiPurposeMap::get).filter(Objects::nonNull).collect(Collectors.toSet());
}
private String buildFinalName(FunctionInfo info) {
if (info.directPurposes.isEmpty() && info.callees.isEmpty()) {
return CUSTOM_AUTO_FUNC_PREFIX + "zc_" + GHIDRA_FUNC_PREFIX + info.function.getEntryPoint().toString()
+ "__xref_" + String.format("%02d", info.referenceCount);
}
Map<String, Set<Character>> parentPurposesByCategory = groupPurposes(info.directPurposes);
Set<String> childOnlyPurposes = new HashSet<>(info.allPropagatedPurposes);
childOnlyPurposes.removeAll(info.directPurposes);
Map<String, Set<Character>> childPurposesByCategory = groupPurposes(childOnlyPurposes);
StringBuilder nameBuilder = new StringBuilder();
for (Map.Entry<String, List<Character>> category : categoryMap.entrySet()) {
String cat = category.getKey();
// Parent capabilities are uppercase
String parentCaps = parentPurposesByCategory.getOrDefault(cat, Collections.emptySet()).stream()
.map(c -> Character.toString(Character.toUpperCase(c))).sorted().collect(Collectors.joining());
// Child-only capabilities are lowercase
String childCaps = childPurposesByCategory.getOrDefault(cat, Collections.emptySet()).stream()
.map(c -> Character.toString(Character.toLowerCase(c))).sorted().collect(Collectors.joining());
if (!parentCaps.isEmpty() || !childCaps.isEmpty()) {
nameBuilder.append(cat);
if (!parentCaps.isEmpty())
nameBuilder.append('_').append(parentCaps);
if (!childCaps.isEmpty())
nameBuilder.append('_').append(childCaps);
nameBuilder.append("__");
}
}
if (nameBuilder.length() > 0) {
return CUSTOM_AUTO_FUNC_PREFIX + nameBuilder + "xref_" + String.format("%02d", info.referenceCount) + "_"
+ info.function.getEntryPoint().toString();
} else {
return CUSTOM_AUTO_FUNC_PREFIX + GHIDRA_FUNC_PREFIX + info.function.getEntryPoint().toString() + "__xref_"
+ String.format("%02d", info.referenceCount);
}
}
private void initializeMaps() {
apiPurposeMap = new HashMap<>();
// --- Network (netw) ---
apiPurposeMap.put("socket", "netwB"); // Bind/listen/connect related
apiPurposeMap.put("bind", "netwL");
apiPurposeMap.put("listen", "netwL");
apiPurposeMap.put("accept", "netwL");
apiPurposeMap.put("connect", "netwC"); // Connection establishment
apiPurposeMap.put("InternetConnect", "netwC");
apiPurposeMap.put("InternetOpen", "netwC");
apiPurposeMap.put("InternetOpenURL", "netwC");
apiPurposeMap.put("HttpOpenRequest", "netwC");
apiPurposeMap.put("WinHttpConnect", "netwC");
apiPurposeMap.put("WinHttpOpenRequest", "netwC");
apiPurposeMap.put("send", "netwS"); // Sending data
apiPurposeMap.put("sendto", "netwS");
apiPurposeMap.put("WSASend", "netwS");
apiPurposeMap.put("WSASendTo", "netwS");
apiPurposeMap.put("InternetWriteFile", "netwS");
apiPurposeMap.put("HttpSendRequest", "netwS");
apiPurposeMap.put("WinHttpSendRequest", "netwS");
apiPurposeMap.put("WinHttpWriteData", "netwS");
apiPurposeMap.put("recv", "netwR"); // Receiving data
apiPurposeMap.put("recvfrom", "netwR");
apiPurposeMap.put("WSARecv", "netwR");
apiPurposeMap.put("WSARecvFrom", "netwR");
apiPurposeMap.put("InternetReadFile", "netwR");
apiPurposeMap.put("HttpReceiveHttpRequest", "netwR");
apiPurposeMap.put("WinHttpReceiveResponse", "netwR");
apiPurposeMap.put("WinHttpReadData", "netwR");
apiPurposeMap.put("URLDownloadToFile", "netwR");
apiPurposeMap.put("inet_addr", "netwM"); // Misc net: address/byte-order
apiPurposeMap.put("htons", "netwM");
apiPurposeMap.put("htonl", "netwM");
apiPurposeMap.put("ntohs", "netwM");
apiPurposeMap.put("ntohl", "netwM");
// --- Registry (reg) ---
apiPurposeMap.put("RegOpenKey", "regH"); // Handle (open/create) registry
apiPurposeMap.put("RegCreateKey", "regC");
apiPurposeMap.put("RegQueryValue", "regR"); // Read registry
apiPurposeMap.put("RegGetValue", "regR");
apiPurposeMap.put("RegEnumValue", "regR");
apiPurposeMap.put("RegSetValue", "regW"); // Write registry
apiPurposeMap.put("RegSetKeyValue", "regW");
apiPurposeMap.put("RegDeleteValue", "regD"); // Delete registry entries
apiPurposeMap.put("RegDeleteKey", "regD");
apiPurposeMap.put("RegDeleteKeyValue", "regD");
// --- File (file) ---
apiPurposeMap.put("CreateFile", "fileH"); // Open file or get handle
apiPurposeMap.put("fopen", "fileH");
apiPurposeMap.put("FindFirstFile", "fileE"); // Enumerate files
apiPurposeMap.put("FindNextFile", "fileE");
apiPurposeMap.put("ReadFile", "fileR"); // Read from file
apiPurposeMap.put("fread", "fileR");
apiPurposeMap.put("fgets", "fileR");
apiPurposeMap.put("fscanf", "fileR");
apiPurposeMap.put("WriteFile", "fileW"); // Write to file
apiPurposeMap.put("fwrite", "fileW");
apiPurposeMap.put("fprintf", "fileW");
apiPurposeMap.put("flushfilebuffers", "fileW");
apiPurposeMap.put("DeleteFile", "fileD"); // Delete file
apiPurposeMap.put("CopyFile", "fileC"); // Copy file
apiPurposeMap.put("MoveFile", "fileM"); // Move/rename file
// --- Process (proc) ---
apiPurposeMap.put("CreateToolhelp32Snapshot", "procE"); // Enumerate processes
apiPurposeMap.put("Process32First", "procE");
apiPurposeMap.put("Process32Next", "procE");
apiPurposeMap.put("OpenProcess", "procH"); // Get process handle
apiPurposeMap.put("CreateProcess", "procC"); // Create new process
apiPurposeMap.put("CreateProcessAsUser", "procC");
apiPurposeMap.put("CreateProcessWithLogon", "procC");
apiPurposeMap.put("CreateProcessWithToken", "procC");
apiPurposeMap.put("ShellExecute", "procC"); // Launch via shell
apiPurposeMap.put("TerminateProcess", "procT"); // Terminate a process
apiPurposeMap.put("ReadProcessMemory", "procR"); // Read another process memory
apiPurposeMap.put("WriteProcessMemory", "procW"); // Write to another process memory
apiPurposeMap.put("CreateRemoteThread", "procW"); // (Considered in injection context)
// --- Service (serv) ---
apiPurposeMap.put("OpenService", "servH"); // Handle to service
apiPurposeMap.put("CreateService", "servC"); // Create a new service
apiPurposeMap.put("StartService", "servS"); // Start service
apiPurposeMap.put("QueryServiceStatus", "servR"); // Read service status/config
apiPurposeMap.put("QueryServiceConfig", "servR");
apiPurposeMap.put("ChangeServiceConfig", "servW"); // Modify service
apiPurposeMap.put("ChangeServiceConfig2", "servW");
apiPurposeMap.put("DeleteService", "servD"); // Delete service
// --- Thread (thread) ---
apiPurposeMap.put("CreateThread", "threadC"); // Create new thread
apiPurposeMap.put("_beginthread", "threadC");
apiPurposeMap.put("_beginthreadex", "threadC");
apiPurposeMap.put("OpenThread", "threadO"); // Open existing thread handle
apiPurposeMap.put("SuspendThread", "threadS"); // Suspend/resume threads
apiPurposeMap.put("ResumeThread", "threadR");
apiPurposeMap.put("TerminateThread", "threadS"); // Terminate thread (if present)
// (Thread start functions are handled separately as thread roots)
// --- String (str) ---
apiPurposeMap.put("strcmp", "strC"); // String compare (case-sensitive)
apiPurposeMap.put("strncmp", "strC");
apiPurposeMap.put("stricmp", "strC"); // String compare (case-insensitive)
apiPurposeMap.put("wcsicmp", "strC");
apiPurposeMap.put("mbsicmp", "strC");
apiPurposeMap.put("lstrcmp", "strC");
apiPurposeMap.put("lstrcmpi", "strC");
// =================================================================
// =========== NEW: GAME-RELATED API ADDITIONS =====================
// =================================================================
// --- Graphics (gfx) - DirectX/Vulkan/OpenGL rendering ---
// (I) Initialize graphics devices/contexts
apiPurposeMap.put("Direct3DCreate9", "gfxI");
apiPurposeMap.put("Direct3DCreate9Ex", "gfxI");
apiPurposeMap.put("D3D11CreateDevice", "gfxI");
apiPurposeMap.put("D3D11CreateDeviceAndSwapChain", "gfxI");
apiPurposeMap.put("D3D12CreateDevice", "gfxI");
apiPurposeMap.put("vkCreateInstance", "gfxI");
apiPurposeMap.put("vkCreateDevice", "gfxI");
apiPurposeMap.put("wglCreateContext", "gfxI");
apiPurposeMap.put("glXCreateContext", "gfxI");
apiPurposeMap.put("eglInitialize", "gfxI");
// (D) Draw calls
apiPurposeMap.put("Draw", "gfxD");
apiPurposeMap.put("DrawIndexed", "gfxD");
apiPurposeMap.put("DrawInstanced", "gfxD");
apiPurposeMap.put("DrawIndexedInstanced", "gfxD");
apiPurposeMap.put("vkCmdDraw", "gfxD");
apiPurposeMap.put("vkCmdDrawIndexed", "gfxD");
apiPurposeMap.put("glDrawArrays", "gfxD");
apiPurposeMap.put("glDrawElements", "gfxD");
apiPurposeMap.put("glDrawArraysInstanced", "gfxD");
apiPurposeMap.put("glDrawElementsInstanced", "gfxD");
// (P) Present/Swap
apiPurposeMap.put("Present", "gfxP"); // DXGI SwapChain Present (COM method call via vtable)
apiPurposeMap.put("IDXGISwapChain.Present", "gfxP"); // (if symbol includes interface name)
apiPurposeMap.put("SwapBuffers", "gfxP"); // OpenGL buffer swap
apiPurposeMap.put("eglSwapBuffers", "gfxP");
apiPurposeMap.put("vkQueuePresentKHR", "gfxP");
// (S) State/Shader setup
apiPurposeMap.put("CreateVertexShader", "gfxS");
apiPurposeMap.put("CreatePixelShader", "gfxS");
apiPurposeMap.put("CreateComputeShader", "gfxS");
apiPurposeMap.put("VSSetShader", "gfxS");
apiPurposeMap.put("PSSetShader", "gfxS");
apiPurposeMap.put("IASetInputLayout", "gfxS");
apiPurposeMap.put("OMSetRenderTargets", "gfxS");
apiPurposeMap.put("RSSetViewports", "gfxS");
apiPurposeMap.put("glCreateShader", "gfxS");
apiPurposeMap.put("glShaderSource", "gfxS");
apiPurposeMap.put("glCompileShader", "gfxS");
apiPurposeMap.put("glLinkProgram", "gfxS");
apiPurposeMap.put("glUseProgram", "gfxS");
// (T) Texture/Buffer resources
apiPurposeMap.put("CreateTexture", "gfxT"); // Generic, covers D3D variants
apiPurposeMap.put("CreateTexture1D", "gfxT");
apiPurposeMap.put("CreateTexture2D", "gfxT");
apiPurposeMap.put("CreateTexture3D", "gfxT");
apiPurposeMap.put("CreateBuffer", "gfxT");
apiPurposeMap.put("Map", "gfxT"); // ID3D11Resource::Map
apiPurposeMap.put("Unmap", "gfxT");
apiPurposeMap.put("glGenTextures", "gfxT");
apiPurposeMap.put("glBindTexture", "gfxT");
apiPurposeMap.put("glBufferData", "gfxT");
apiPurposeMap.put("glTexImage2D", "gfxT");
// --- Input (input) - Keyboard/Mouse/Controller ---
// (K) Keyboard
apiPurposeMap.put("GetKeyboardState", "inputK");
apiPurposeMap.put("GetAsyncKeyState", "inputK");
apiPurposeMap.put("GetKeyState", "inputK");
apiPurposeMap.put("MapVirtualKey", "inputK");
apiPurposeMap.put("RegisterHotKey", "inputK");
// (M) Mouse
apiPurposeMap.put("GetCursorPos", "inputM");
apiPurposeMap.put("SetCursorPos", "inputM");
apiPurposeMap.put("ShowCursor", "inputM");
apiPurposeMap.put("GetRawInputData", "inputM");
apiPurposeMap.put("GetRawInputBuffer", "inputM");
// (C) Controller (Gamepad)
apiPurposeMap.put("XInputGetState", "inputC");
apiPurposeMap.put("XInputSetState", "inputC");
apiPurposeMap.put("XInputGetCapabilities", "inputC");
apiPurposeMap.put("DirectInputCreate", "inputC");
apiPurposeMap.put("DirectInput8Create", "inputC");
// --- Audio (audio) - Init/Playback ---
// (I) Initialize audio engines
apiPurposeMap.put("DirectSoundCreate", "audioI");
apiPurposeMap.put("DirectSoundCreate8", "audioI");
apiPurposeMap.put("XAudio2Create", "audioI");
apiPurposeMap.put("waveOutOpen", "audioI");
apiPurposeMap.put("alcOpenDevice", "audioI"); // OpenAL device
apiPurposeMap.put("alcCreateContext", "audioI");
apiPurposeMap.put("BASS_Init", "audioI"); // Example: BASS audio library
apiPurposeMap.put("FMOD_System_Create", "audioI");
apiPurposeMap.put("FMOD_System_Init", "audioI");
// (P) Play or produce sound
apiPurposeMap.put("CreateSoundBuffer", "audioP");
apiPurposeMap.put("CreatePrimaryBuffer", "audioP");
apiPurposeMap.put("CreateSourceVoice", "audioP");
apiPurposeMap.put("CreateMasteringVoice", "audioP");
apiPurposeMap.put("PlaySound", "audioP");
apiPurposeMap.put("Play", "audioP"); // IDirectSoundBuffer::Play
apiPurposeMap.put("waveOutWrite", "audioP");
apiPurposeMap.put("alGenBuffers", "audioP"); // OpenAL generate buffer
apiPurposeMap.put("alBufferData", "audioP"); // fill buffer with audio data
apiPurposeMap.put("alSourcePlay", "audioP");
apiPurposeMap.put("BASS_StreamCreateFile", "audioP");
apiPurposeMap.put("BASS_ChannelPlay", "audioP");
apiPurposeMap.put("FMOD_System_CreateSound", "audioP");
apiPurposeMap.put("FMOD_System_PlaySound", "audioP");
// --- Memory (mem) - Allocation/Protection/Free ---
// (A) Allocate
apiPurposeMap.put("VirtualAlloc", "memA");
apiPurposeMap.put("VirtualAllocEx", "memA");
apiPurposeMap.put("HeapAlloc", "memA");
apiPurposeMap.put("HeapReAlloc", "memA");
apiPurposeMap.put("GlobalAlloc", "memA");
apiPurposeMap.put("LocalAlloc", "memA");
apiPurposeMap.put("malloc", "memA");
apiPurposeMap.put("calloc", "memA");
apiPurposeMap.put("mmap", "memA");
// (P) Protect
apiPurposeMap.put("VirtualProtect", "memP");
apiPurposeMap.put("VirtualProtectEx", "memP");
apiPurposeMap.put("VirtualLock", "memP");
apiPurposeMap.put("VirtualUnlock", "memP");
apiPurposeMap.put("mprotect", "memP");
// (F) Free
apiPurposeMap.put("VirtualFree", "memF");
apiPurposeMap.put("VirtualFreeEx", "memF");
apiPurposeMap.put("HeapFree", "memF");
apiPurposeMap.put("GlobalFree", "memF");
apiPurposeMap.put("LocalFree", "memF");
apiPurposeMap.put("free", "memF");
apiPurposeMap.put("munmap", "memF");
// --- Steamworks (steam) - Steam API integration ---
// (I) Initialize/Shutdown
apiPurposeMap.put("SteamAPI_Init", "steamI");
apiPurposeMap.put("SteamAPI_RestartAppIfNecessary", "steamI");
apiPurposeMap.put("SteamAPI_Shutdown", "steamI");
apiPurposeMap.put("SteamAPI_RunCallbacks", "steamI");
// (A) Achievements/Stats
apiPurposeMap.put("SteamAPI_ISteamUserStats_SetAchievement", "steamA");
apiPurposeMap.put("SteamAPI_ISteamUserStats_StoreStats", "steamA");
apiPurposeMap.put("SteamAPI_ISteamUserStats_IndicateAchievementProgress", "steamA");
apiPurposeMap.put("SteamAPI_ISteamUserStats_GetAchievement", "steamA");
// (N) Networking/Multiplayer
apiPurposeMap.put("SteamAPI_ISteamNetworking_SendP2PPacket", "steamN");
apiPurposeMap.put("SteamAPI_ISteamNetworkingSockets_SendMessageToPeer", "steamN");
apiPurposeMap.put("SteamAPI_ISteamMatchmaking_CreateLobby", "steamN");
apiPurposeMap.put("SteamAPI_ISteamMatchmaking_JoinLobby", "steamN");
apiPurposeMap.put("SteamAPI_ISteamMatchmaking_SetLobbyData", "steamN");
// --- Xbox Live (xbl) - Xbox/Microsoft Game Services ---
// (I) Initialize
apiPurposeMap.put("XblInitialize", "xblI");
apiPurposeMap.put("XblCleanup", "xblI");
apiPurposeMap.put("XGameRuntimeInitialize", "xblI"); // initialize Game runtime (if applicable)
// (U) User Login/Token
apiPurposeMap.put("XUserAdd", "xblU");
apiPurposeMap.put("XUserAddAsync", "xblU");
apiPurposeMap.put("XUserGetTokenAndSignatureAsync", "xblU");
apiPurposeMap.put("XUserGetTokenAndSignatureResult", "xblU");
apiPurposeMap.put("XUserSignOut", "xblU");
// (A) Achievements/Stats
apiPurposeMap.put("XblAchievementsWriteAchievementAsync", "xblA");
apiPurposeMap.put("XblAchievementsGetAllForTitleIdAsync", "xblA");
apiPurposeMap.put("XblPresenceSetPresenceAsync", "xblA"); // e.g., setting rich presence
// (Note: additional Xbl* calls for leaderboard or stats could be added similarly)
// --- Dynamic Linking (dyn) - Load libraries and resolve symbols ---
// (L) Load Library
apiPurposeMap.put("LoadLibrary", "dynL");
apiPurposeMap.put("LoadLibraryA", "dynL");
apiPurposeMap.put("LoadLibraryW", "dynL");
apiPurposeMap.put("LoadLibraryEx", "dynL");
apiPurposeMap.put("GetModuleHandle", "dynL"); // Getting module handle (if not already loaded)
apiPurposeMap.put("dlopen", "dynL");
// (G) Get Proc Address
apiPurposeMap.put("GetProcAddress", "dynG");
apiPurposeMap.put("dlsym", "dynG");
// (F) Free Library
apiPurposeMap.put("FreeLibrary", "dynF");
apiPurposeMap.put("FreeLibraryAndExitThread", "dynF");
apiPurposeMap.put("dlclose", "dynF");
// --- Scripting (scr) - Embedded scripting engines ---
// (P) Python
apiPurposeMap.put("Py_Initialize", "scrP");
apiPurposeMap.put("Py_InitializeEx", "scrP");
apiPurposeMap.put("Py_Finalize", "scrP");
apiPurposeMap.put("PyRun_SimpleString", "scrP");
apiPurposeMap.put("PyRun_String", "scrP");
apiPurposeMap.put("PyImport_Import", "scrP");
// (L) Lua
apiPurposeMap.put("luaL_newstate", "scrL");
apiPurposeMap.put("luaL_openlibs", "scrL");
apiPurposeMap.put("luaL_loadstring", "scrL");
apiPurposeMap.put("lua_pcall", "scrL");
apiPurposeMap.put("lua_close", "scrL");
// (J) JavaScript (e.g., V8 or other engines)
apiPurposeMap.put("v8::Isolate::New", "scrJ"); // (if C++ symbol names are present, e.g., V8 engine)
apiPurposeMap.put("JS_NewRuntime", "scrJ"); // QuickJS initialization
apiPurposeMap.put("JS_Eval", "scrJ");
categoryMap = new LinkedHashMap<>();
categoryMap.put("netw", Arrays.asList('b', 'c', 'l', 's', 'r', 'm')); // network: Bind/Connect, Listen, Send, Receive, Misc
categoryMap.put("reg", Arrays.asList('h', 'c', 'r', 'w', 'd')); // registry: Handle, Create, Read, Write, Delete
categoryMap.put("file", Arrays.asList('h', 'e', 'r', 'w', 'd', 'c', 'm')); // file: Handle, Enum, Read, Write, Delete, Copy, Move
categoryMap.put("proc", Arrays.asList('h', 'e', 'c', 't', 'r', 'w')); // process: Handle, Enum, Create, Terminate, ReadMem, WriteMem
categoryMap.put("serv", Arrays.asList('h', 'c', 's', 'r', 'w', 'd')); // service: Handle, Create, Start, Read, Write, Delete
categoryMap.put("thread", Arrays.asList('c', 'o', 's', 'r')); // thread: Create, Open, Suspend (or terminate), Resume
categoryMap.put("str", Arrays.asList('c')); // string: Compare (could extend if needed)
// New categories below:
categoryMap.put("gfx", Arrays.asList('i', 'd', 'p', 's', 't')); // graphics: Init, Draw, Present, State, Texture
categoryMap.put("input", Arrays.asList('k', 'm', 'c')); // input: Keyboard, Mouse, Controller
categoryMap.put("audio", Arrays.asList('i', 'p')); // audio: Init, Play
categoryMap.put("mem", Arrays.asList('a', 'p', 'f')); // memory: Alloc, Protect, Free
categoryMap.put("steam", Arrays.asList('i', 'a', 'n')); // steam: Init, Achievements, Network
categoryMap.put("xbl", Arrays.asList('i', 'u', 'a')); // xbox live: Init, User, Achievements
categoryMap.put("dyn", Arrays.asList('l', 'g', 'f')); // dynamic library: Load, GetProc, Free
categoryMap.put("scr", Arrays.asList('p', 'l', 'j')); // scripting: Python, Lua, (Java)Script
apiNamePattern = Pattern.compile("^(?:FID_conflict:)?(?:_)*(?<base>.+?)(?:A|W|Ex|ExA|ExW)?(?:@[0-9A-Fa-f]+)?$");
}
// --- CANCER METHODS ---
private String getBaseNameFromSymbol(Symbol symbol) {
if (symbol == null)
return null;
Matcher m = apiNamePattern.matcher(symbol.getName());
return m.matches() ? m.group("base") : null;
}
private boolean isAutoNamed(String name) {
return name.startsWith(GHIDRA_FUNC_PREFIX) || name.startsWith(CUSTOM_AUTO_FUNC_PREFIX) || name.startsWith(CUSTOM_AUTO_THREAD_FUNC_PREFIX);
}
private Map<String, Set<Character>> groupPurposes(Set<String> purposes) {
Map<String, Set<Character>> grouped = new HashMap<>();
if (purposes == null)
return grouped;
for (String purpose : purposes) {
if (purpose == null || purpose.length() < 2)
continue;
String category = getCategory(purpose);
char capability = getCapability(purpose);
if (categoryMap.containsKey(category)) {
grouped.computeIfAbsent(category, k -> new HashSet<>()).add(capability);
}
}
return grouped;
}
private String getCategory(String purpose) {
return purpose.substring(0, purpose.length() - 1).toLowerCase();
}
private char getCapability(String purpose) {
return Character.toLowerCase(purpose.charAt(purpose.length() - 1));
}
private List<Function> getTargetFunctions() {
List<Function> funcList = new ArrayList<>();
FunctionIterator funcs = currentProgram.getListing().getFunctions(true);
while (funcs.hasNext()) {
Function f = funcs.next();
String name = f.getName();
if ((name.startsWith(GHIDRA_FUNC_PREFIX) || name.startsWith(CUSTOM_AUTO_FUNC_PREFIX))
&& !name.startsWith(CUSTOM_AUTO_THREAD_FUNC_PREFIX) && !f.isThunk()) {
funcList.add(f);
}
}
return funcList;
}
private Set<Function> getCalledInternalFunctions(Function f) {
Set<Function> called = new HashSet<>();
if (f.getBody() == null || f.getBody().isEmpty()) {
return called;
}
InstructionIterator iter = currentProgram.getListing().getInstructions(f.getBody(), true);
while (iter.hasNext()) {
Instruction instr = iter.next();
if ("call".equalsIgnoreCase(instr.getMnemonicString())) {
Reference[] opRefs = instr.getOperandReferences(0);
if (opRefs.length > 0) {
Function targetFunc = getFunctionAt(opRefs[0].getToAddress());
if (targetFunc != null && !targetFunc.isThunk() && !targetFunc.isExternal()) {
called.add(targetFunc);
}
}
}
}
return called;
}
private void renameThreadRoots() throws DuplicateNameException, InvalidInputException {
Set<Address> threadRoots = getThreadRoots();
for (Address rootEa : threadRoots) {
String newFuncName = CUSTOM_AUTO_THREAD_FUNC_PREFIX + GHIDRA_FUNC_PREFIX + rootEa.toString();
Function func = getFunctionAt(rootEa);
if (func == null) {
createFunction(rootEa, newFuncName);
} else {
func.setName(newFuncName, SourceType.USER_DEFINED);
}
}
}
/**
* Finds the entry‐point addresses of all thread‐start routines invoked via
* CreateThread, _beginthread, etc.
*/
private Set<Address> getThreadRoots() {
String[] threadFuncs = { "CreateThread", "_beginthreadex", "__beginthreadex", "_beginthread", "__beginthread" };
Set<Address> roots = new HashSet<>();
for (String fn : threadFuncs) {
// 1) Try to resolve as an external import:
Symbol threadApiSym = currentProgram.getSymbolTable().getExternalSymbol(fn);
// 2) Fallback to any global label/function with that name:
if (threadApiSym == null) {
List<Symbol> globals = currentProgram.getSymbolTable().getSymbols(fn, null);
if (globals.isEmpty()) {
continue;
}
threadApiSym = globals.get(0);
}
// Walk all call‐site references to that API
for (Reference ref : getReferencesTo(threadApiSym.getAddress())) {
if (!ref.isOperandReference() || !ref.getReferenceType().isCall()) {
continue;
}
Instruction callSite = getInstructionAt(ref.getFromAddress());
if (callSite == null) {
continue;
}
// Python did: argIndex = (lstrip(_) == "beginthread") ? 1 : 3
int pushCount = fn.replace("_", "").equals("beginthread") ? 1 : 3;
Instruction pushIns = getPrevTargetInstruction(callSite, "push", pushCount, 15);
if (pushIns == null) {
continue;
}
// Extract the address they passed as the thread entry point
Address entry = extractThreadEntryAddress(pushIns);
if (entry != null && getFunctionContaining(entry) != null) {
roots.add(entry);
}
}
}
return roots;
}
/**
* Given a 'push' instruction that pushed either a register or an immediate,
* chase back to the actual entry‐point address.
*/
private Address extractThreadEntryAddress(Instruction pushInstr) {
// Case A: they pushed a register -> look back for the mov into that register
if (pushInstr.getOperandType(0) == OP_TYPE_PUSH_REGISTER) {
String regName = pushInstr.getRegister(0).getName().toLowerCase();
Instruction probe = pushInstr;
for (int depth = 0; depth < 10 && probe != null; depth++) {
probe = probe.getPrevious();
if (probe == null || !"mov".equalsIgnoreCase(probe.getMnemonicString())) {
continue;
}
// did we mov into our register of interest?
if (probe.getRegister(0) != null && probe.getRegister(0).getName().toLowerCase().equals(regName)) {
Reference[] movRefs = probe.getOperandReferences(1);
if (movRefs.length > 0) {
return movRefs[0].getToAddress();
}
break;
}
}
// Case B: they pushed an immediate address
} else {
Reference[] pushRefs = pushInstr.getOperandReferences(0);
if (pushRefs.length > 0) {
return pushRefs[0].getToAddress();
}
}
return null;
}
private Instruction getPrevTargetInstruction(Instruction start, String mnem, int n, int max) {
Function func = getFunctionContaining(start.getAddress());
if (func == null)
return null;
AddressSetView body = func.getBody();
Instruction cur = start;
int found = 0;
for (int i = 0; i < max; i++) {
cur = cur.getPrevious();
if (cur == null || !body.contains(cur.getAddress())) {
return null;
}
if (cur.getMnemonicString().equalsIgnoreCase(mnem)) {
found++;
if (found == n)
return cur;
}
}
return null;
}
}