nneonneo / ghidra-wasm-plugin

Ghidra Wasm plugin with disassembly and decompilation support
GNU General Public License v3.0
243 stars 12 forks source link

Failed to analyze exports although there are exports #18

Closed shazgames1 closed 8 months ago

shazgames1 commented 9 months ago

Hey! I get errors when try to run analyze_dyncalls.py script. It can't find function exports although there are exports.

Failed to analyze export::dynCall_jiji: call_indirect not found
Failed to analyze export::dynCall_jii: call_indirect not found
Failed to analyze export::dynCall_jiii: call_indirect not found
Failed to analyze export::dynCall_jjii: call_indirect not found
Failed to analyze export::dynCall_jjji: call_indirect not found
Failed to analyze export::dynCall_jiiii: call_indirect not found
Failed to analyze export::dynCall_jji: call_indirect not found
Failed to analyze export::dynCall_jijiii: call_indirect not found
Failed to analyze export::dynCall_j: call_indirect not found
Failed to analyze export::dynCall_ji: call_indirect not found
Failed to analyze export::dynCall_jidi: call_indirect not found
Failed to analyze export::dynCall_jiiijii: call_indirect not found
Failed to analyze export::dynCall_jiiji: call_indirect not found
Failed to analyze export::dynCall_jdi: call_indirect not found
Failed to analyze export::dynCall_jijii: call_indirect not found
Failed to analyze export::dynCall_jiiiii: call_indirect not found
Failed to analyze export::dynCall_jjjji: call_indirect not found
Failed to analyze export::dynCall_jijji: call_indirect not found
Failed to analyze export::dynCall_jjjii: call_indirect not found
Failed to analyze export::dynCall_jiijii: call_indirect not found
Failed to analyze export::dynCall_jiidi: call_indirect not found
Failed to analyze export::dynCall_jjiiii: call_indirect not found
Failed to analyze export::dynCall_jjiiiii: call_indirect not found
Failed to analyze export::dynCall_jijjji: call_indirect not found
Failed to analyze export::dynCall_jijjjii: call_indirect not found
Failed to analyze export::dynCall_jjiii: call_indirect not found
Failed to analyze export::dynCall_jfi: call_indirect not found
Failed to analyze export::dynCall_jidii: call_indirect not found
Failed to analyze export::dynCall_jiiiiii: call_indirect not found
Failed to analyze export::dynCall_jdii: call_indirect not found
Failed to analyze export::dynCall_jiiiiiiiii: call_indirect not found
Failed to analyze export::dynCall_jiiiiiiiiii: call_indirect not found
Failed to analyze export::dynCall_jiiiiiii: call_indirect not found
Failed to analyze export::dynCall_jiiiiji: call_indirect not found
Traceback (most recent call last):
  File "/home/x/Downloads/dodo_dump/Il2CppDumper-win-v6.7.40/ghidra_wasm.py", line 11, in <module>
    runScript("analyze_dyncalls.py")
  File "/home/x/.ghidra/.ghidra_10.3.2_PUBLIC/Extensions/ghidra-wasm-plugin/ghidra_scripts/analyze_dyncalls.py", line 120, in <module>
    renameDyncalls(calltype)
  File "/home/x/.ghidra/.ghidra_10.3.2_PUBLIC/Extensions/ghidra-wasm-plugin/ghidra_scripts/analyze_dyncalls.py", line 100, in renameDyncalls
    for i in range(mask+1):
OverflowError: range() result has too many items
nneonneo commented 9 months ago

This script is probably fairly sensitive to the exact version of the compiler used, so it’s likely failing because the compiler is different.

Can you provide a code sample, or at the very minimum the disassembly of a dynCall function?

shazgames1 commented 9 months ago

I used script from il2cppdumper project to restore function names of Unity webgl game. Here's script:

# -*- coding: utf-8 -*-
import json

from wasm import WasmLoader
from wasm.analysis import WasmAnalysis
from ghidra.util.task import ConsoleTaskMonitor

monitor = ConsoleTaskMonitor()
WasmLoader.loadElementsToTable(currentProgram, WasmAnalysis.getState(currentProgram).module, 0, 0, 0, monitor)

runScript("analyze_dyncalls.py")

processFields = [
    "ScriptMethod",
    "ScriptString",
    "ScriptMetadata",
    "ScriptMetadataMethod",
    "Addresses",
]

functionManager = currentProgram.getFunctionManager()
progspace = currentProgram.addressFactory.getAddressSpace("ram")
USER_DEFINED = ghidra.program.model.symbol.SourceType.USER_DEFINED

def get_addr(addr):
    return progspace.getAddress(addr)

def set_name(addr, name):
    name = name.replace(' ', '-')
    createLabel(addr, name, True, USER_DEFINED)

def make_function(start):
    func = getFunctionAt(start)
    if func is None:
        createFunction(start, None)

f = askFile("script.json from Il2cppdumper", "Open")
data = json.loads(open(f.absolutePath, 'rb').read().decode('utf-8'))

if "ScriptMethod" in data and "ScriptMethod" in processFields:
    scriptMethods = data["ScriptMethod"]
    dynCallNamespace =  currentProgram.symbolTable.getNamespace("dynCall", None)
    monitor.initialize(len(scriptMethods))
    monitor.setMessage("Methods")
    for scriptMethod in scriptMethods:
        offset = scriptMethod["Address"]
        sig = scriptMethod["TypeSignature"]
        symbolName = "func_%s_%d" % (sig, offset)
        symbol = currentProgram.symbolTable.getSymbols(symbolName, dynCallNamespace)
        if len(symbol) > 0:
            addr = symbol[0].address
            name = scriptMethod["Name"].encode("utf-8")
            set_name(addr, name)
        else:
            print "Warning at %s:" % scriptMethod["Name"]
            print "Symbol %s not found!" % symbolName
        monitor.incrementProgress(1)

if "ScriptString" in data and "ScriptString" in processFields:
    index = 1
    scriptStrings = data["ScriptString"]
    monitor.initialize(len(scriptStrings))
    monitor.setMessage("Strings")
    for scriptString in scriptStrings:
        addr = get_addr(scriptString["Address"])
        value = scriptString["Value"].encode("utf-8")
        name = "StringLiteral_" + str(index)
        createLabel(addr, name, True, USER_DEFINED)
        setEOLComment(addr, value)
        index += 1
        monitor.incrementProgress(1)

if "ScriptMetadata" in data and "ScriptMetadata" in processFields:
    scriptMetadatas = data["ScriptMetadata"]
    monitor.initialize(len(scriptMetadatas))
    monitor.setMessage("Metadata")
    for scriptMetadata in scriptMetadatas:
        addr = get_addr(scriptMetadata["Address"])
        name = scriptMetadata["Name"].encode("utf-8")
        set_name(addr, name)
        setEOLComment(addr, name)
        monitor.incrementProgress(1)

if "ScriptMetadataMethod" in data and "ScriptMetadataMethod" in processFields:
    scriptMetadataMethods = data["ScriptMetadataMethod"]
    monitor.initialize(len(scriptMetadataMethods))
    monitor.setMessage("Metadata Methods")
    for scriptMetadataMethod in scriptMetadataMethods:
        addr = get_addr(scriptMetadataMethod["Address"])
        name = scriptMetadataMethod["Name"].encode("utf-8")
        methodAddr = get_addr(scriptMetadataMethod["MethodAddress"])
        set_name(addr, name)
        setEOLComment(addr, name)
        monitor.incrementProgress(1)

if "Addresses" in data and "Addresses" in processFields:
    pass

print 'Script finished!'
nneonneo commented 9 months ago

Sorry, I meant code from your binary, not the script you're using.

shazgames1 commented 9 months ago

ab4d62b90182afef84508c89118bd1a8.wasm.br.zip

nneonneo commented 8 months ago

OK, I took a look at the code.

The program you provided is using a different implementation of dynCall. The analyze_dyncalls script assumes that the function table is segmented by type. Function pointers in that implementation are indices into the segment corresponding to the type of the target function. For example, function pointers to void f(int x) and int g(double y) could have the same index value, but refer to different segments of the table. analyze_dyncalls figures out the function-type-to-table-segment mapping, making it possible for il2cppdumper to rename the functions based on their index + type.

You can use the rename_table_funcs.py script instead. However, note that the names it produces might not be directly compatible with il2cppdumper; I recommend you file an issue with them to fix their script for the direct-index function pointer scheme.

Closed as a duplicate of #14.

shazgames1 commented 8 months ago

Thank you so much for your work and detailed explanation! 🫶