ghidra2frida – The new bridge between Ghidra and Frida

Hi!

Today I’m publishing a new Ghidra extension based on the same idea of Brida, a Burp Suite extension created with my colleague Piergiovanni. The idea is simple: everything is better if it can take advantage of Frida‘s super powers! 😀

ghidra2frida is a Ghidra extension that, working as a bridge between Ghidra and Frida, lets you create powerful scripts that take advantage of Frida’s dynamic analysis engine to improve Ghidra’s statical analysis features. It supports all platforms supported by Frida (Windows, macOS, Linux, iOS, Android, and QNX).

The extension adds a control panel to Ghidra with all the tools needed to create the bridge between Ghidra and Frida. When the bridge is up, a service is offered to all Ghidra scripts and extensions. Thanks to this service, Ghidra scripts and extensions can take advantage of the dynamic instrumentation features of Frida with a couple lines of code.

As with the other tools I’ve written, the idea came from practical needs, such as:

  1. A binary I was analyzing had some heavily obfuscated and encrypted strings. The quick and dirty solution I thought about was to decrypt/deobfuscate those strings using the same functions used by the target binary. Frida was a great candidate for that!
  2. An iOS mobile application I was analyzing had many mangled Swift functions. Ghidra Ninja released a Ghidra script that demangles Swift functions, but it uses a binary not available on all operating systems. Furthermore, demangling functions may vary between different versions of Swift. Demangling directly on the mobile phone with Frida seemed to be a good solution to both problems.

Let’s have a closer look of how it works: ghidra2frida is a Ghidra extension that can be imported from the “File” -> “Install Extensions” menu option. Once installed, ghidra2frida graphical pane can be opened from the “Window” -> “ghidra2frida” menu of Ghidra CodeBrowser and can be positioned anywhere, as any other Ghidra tool.

The interface is very similar to Brida’s interface. In the upper section of the “Configuration” tab it is necessary to fill in some configuration data used by ghidra2frida and in the lower section there are the buttons that control actions.

Like Brida, ghidra2frida uses a RPC library (Pyro4) to allow Ghidra to call Frida methods because unfortunately it is not possible to import Frida directly in a Ghidra extension (neither Java nor Python). A small Python server based on Pyro is executed on the supplied host and port and acts as a bridge between Ghidra and Frida.

Digging in more detail in the configuration options:

  • “Use virtual env” flag allows to use Python virtual environments (see below).
  • “Python binary path” or “Virtual env folder” require the path of the Python binary or of a Python virtual environment folder, depending on the value of “Use virtual env” flag.
  • “Pyro host” and “Pyro port” are the host and port on which the Pyro server (used by ghidra2frida to bridge Ghidra and Frida) will be started. In most cases you can leave the default values and Pyro server will be started on localhost:9999.
  • “Frida JS file” is the path of the Frida instrumentation JavaScript file, containing all Frida and ghidra2frida hooks and exports. An example JS file can be easily created in a chosen location using the button “Create skeleton JS file” at the right of the “Frida JS file” text field (the path is automatically updated). You can add your own Frida hooks and exports directly in this file, which contains also some useful encoding/decoding JavaScript functions (hex2bytes, hex2string, Base64).
  • “Application ID / PID” requires the ID of the application to spawn (e.g. “org.test.testApplication”) or the binary name/PID of the running process to attach to.
  • “Frida Remote” / “Frida USB” / “Frida Local” allows to choose how to contact the Frida server. The “Frida Remote” flag corresponds to the “-R” option of Frida and tries to connect to a Frida server on TCP port 27042.

To show an example of how it works, let’s look at the SwiftDemangle script, based on Ghidra Ninja’s swift_demangles.py.

After properly configuring the tool, the Pyro server can be started by clicking on the “Start” button of the “Server” section. The first red light in the top-right section of the ghidra2frida pane will become green if the server starts correctly.

The “Check” button in the “Server” section can be used to verify if the ghidra2frida extension is able to contact the Pyro server. The output is printed in the Ghidra Console.

After that, the target application can be spawned or attached to. In this example, I inserted the PID of a running application and attached to it using the “Attach” button in the “Application” section. If the spawning/attaching process works correctly, the second red light in the top-right section of the ghidra2frida pane will become green.

The “Check” button in the “Application” section can be used to verify if the script can talk properly with the target application using Frida. The output is printed in the Ghidra Console.

Now the bridge is up and we can use it from our Ghidra scripts or extensions. Taking as an example our SwiftDemangler script, we created a Frida exported function that demangles the function name supplied as argument on our iOS phone (we will skip now the body of the “demangle” function because it is not important for understanding ghidra2frida):

rpc.exports = {
  
  // Export used by ghidra2frida to check if the application has been attached correctly
  ping: function() {
    return true;
  },

  // Demangle Swift function name
  demangle: function(name) {
    ...
  }
}

The exported function can be called by arbitrary Ghidra scripts and extensions but, before that, it is possible to try the function from the “Run export” tab of the ghidra2frida extension:

Then, our SwiftDemangler Java script can call the “demangle” exported function as follows:

import ghidra2frida.Ghidra2FridaService;

public class SwiftDemangler extends GhidraScript {
    
  Ghidra2FridaService ghidra2FridaService;
  
  public String callGhidra2FridaFunction(String name, String[] parameters) throws Exception {
    if(ghidra2FridaService == null)
      ghidra2FridaService = state.getTool().getService(Ghidra2FridaService.class);
    return ghidra2FridaService.callExportedFunction("demangle",parameters);		
  }
    
  @Override
  protected void run() throws Exception {
    ...
    try {
      ...
      // f_name contains the name of the function to demangle using Frida
      String signature_full = callGhidra2FridaFunction("demangle",new String[] {f_name});
      ...		    
    } catch(Exception e) {
      ...		    	
    }
  }		    
}			

This snippet contains the portion of code necessary to understand how to use ghidra2frida. The full SwiftDeamangle example can be found in my GitHub Ghidra scripts repository. In this example, the output of the demangle function is inserted as comment to all Swift functions, in order to speed-up the reversing process.

As it can be seen, in the first line of the Java snippet of our Ghidra script, it is necessary to import the Ghidra2FridaService class offered by ghidra2frida extension because Ghidra services work this way. If the script is opened in Eclipse (or in another Java IDE), the IDE will show an exception because that class is missing. When the script will be executed in a Ghidra instance with the ghidra2frida extension installed, the script will work correctly without any error, but this IDE error can be annoying. To solve this little issue I found a couple of ways:

  1. Copy the “Ghidra2FridaService” interface in a package named “ghidra2frida” inside the “src” folder of the GhidraScripts directory (the interface can be found in the GitHub repository of the SwiftDemangler script and in the sources of the ghidra2frida extension).
  2. Alternatively, use Java reflection to dynamically obtain a reference to “Ghidra2FridaService” class without importing it (this solution can be a little hard to understand for those who are not familiar with Java language reflection mechanisms).

The following code snippet implements this second solution:

public class SwiftDemangler2 extends GhidraScript {	
  
  Object ghidra2FridaService;
  
  public String callGhidra2FridaFunction(String name, String[] parameters) throws Exception {
    if(ghidra2FridaService == null) {
      PluginTool pluginTool = state.getTool();
      ghidra2FridaService = pluginTool.getService(Class.forName("ghidra2frida.Ghidra2FridaService"));
    }
    Method ghidra2FridaCallExportedFunction = Class.forName("ghidra2frida.Ghidra2FridaService").getMethod("callExportedFunction", java.lang.String.class, java.lang.String[].class);		
    return (String)ghidra2FridaCallExportedFunction.invoke(ghidra2FridaService, name, parameters);
  }
    
  @Override
  protected void run() throws Exception {
    ...		
    try {
      String signature_full = callGhidra2FridaFunction("demangle",new String[] {f_name});
      ...			    
    } catch(Exception e) {
      ...
    }		    	
  }
}

The Ghidra script can then be executed from the Ghidra Script Manager tool:

The script will process each function found in the binary and will call the “demangle” Frida exported function on all the Swift function names, writing the result as a plate comment to each function:

The same script can also be coded in Python. Our swift_demangler.py Python script can use ghidra2frida as follows:

from ghidra2frida import Ghidra2FridaService

ghidra2FridaService = None

def callGhidra2FridaFunction(name, parameters):
  global ghidra2FridaService
  if ghidra2FridaService is None:
    ghidra2FridaService = state.getTool().getService(Ghidra2FridaService)
    println("ghidra2FridaService initialized")

  return ghidra2FridaService.callExportedFunction(name,parameters);

functionManager = currentProgram.getFunctionManager()

fns = functionManager.getFunctions(True)
for f in fns:
  [...]
  try:
    signature_full = callGhidra2FridaFunction("demangle",[f_name]);
    [...]      
  except Exception as exc:
    [...]

ghidra2frida and the examples shown in this tutorial can be found on GitHub:

Cheers!