This plugin provides three new options in the right-click menu of the Listing Window in Ghidra:
This extension supports both function-level hooking (when hooking the first address of a function), as well as arbitrary address hooking (when hooking inside a function).
It should be noted that the right-click should be done when hovering over the instruction address in the Listing Window. If for example it is done when hovering over a function name that the current instruction calls, the hook will be generated for that function, and not the current instruction.
frida -f <binary> -l script.js
The Advanced Frida Hooks dialog offers multiple useful options, for generation of hooks for multiple related addresses at once:
The "Copy Script" option introduces a 2-second delay for registering the interceptors. The reason for that is that a typical mobile application will not have all the dynamic libraries readily available at launch, for frida to hook into. As a quick fix, the script waits for a little and then tries to register the interceptors. In certain cases, this means that the functions may be executed before frida hooks on them. If that is not desirable, it is possible to create code that registers the hooks when a particular library is loaded though dlopen() or LoadLibrary() through the Advanced Options.
The generated script attempts to display a hooked function's parameters when it is called. However, usually the "Listing" window which contains the assembly code, does not reflect that parameter number. As such, the generated script will not contain code to print the correct number of parameters (usually it does not recognize any parameter, and as such it does not print anything). The solution to fix this is to right click on a function's name on the Ghidra "Decompiler" window, and select "Commit Params/Return". Then, the "Listing" window will recognize the correct number of parameters.
The plugin also offers the ability to generate hooks for multiple addresses, as they have been identified from Ghidra's Search or other grouping options . First a selection must be created (Right click -> Make Selection), and then a new option is provided that can generate hooks for all the selected addresses. In effect, an Advanced Hook Generation dialog will be spawned that takes all the selected addresses as inputs. If for example the option "Generate hooks for addresses (statically) referencing the current address" is selected, then hooks for all the references of all the selected addresses will be generated.
Another option provided by the plugin is the generation of javascript code that describes structs, so as to make their fields easily accessible by later hook code. Ghidra must know about the structs, this is typically done by parsing debug symbols but it is outside the scope of the plugin.
As an example of the output of the plugin, here's a sample of the output code, for a function that starts at the offset 0x32450. The option "Copy Frida Hook Script" was used:
var module_name_vlc_exe='vlc.exe';
function start_timer_for_intercept() {
setTimeout(
function() {
console.log("Registering interceptors...");
var offset_of_FUN_00432450_00432450=0x32450;
var dynamic_address_of_FUN_00432450_00432450=Module.findBaseAddress(module_name_vlc_exe).add(offset_of_FUN_00432450_00432450);
Interceptor.attach(dynamic_address_of_FUN_00432450_00432450, {
onEnter: function(args) {
console.log("Entered FUN_00432450_00432450");
console.log('args[0]='+args[0]+' , args[1]='+args[1]+' , args[2]='+args[2]+' , args[3]='+args[3]);
// this.context.x0=0x1;
},
onLeave: function(retval) {
console.log("Exited FUN_00432450_00432450, retval:"+retval);
// retval.replace(0x1);
}
});
Interceptor.flush();
console.log("Registered interceptors.");
}, 2000);//milliseconds
}
start_timer_for_intercept();
If the option "Copy Frida Hook Snippet" is used, only the part in the middle will be returned (between the first console.log()
and the Interceptor.flush()
).
The code when hooking a random address that is not the first address in a function looks like the following:
var offset_of_0043246c=0x3246c;
var dynamic_address_of_0043246c=Module.findBaseAddress(module_name_vlc_exe).add(offset_of_0043246c);
function function_to_call_when_code_reaches_0043246c(){
console.log('Reached address 0x0043246c, which is inside function FUN_00432450');
//this.context.x0=0x1;
}
Interceptor.attach(dynamic_address_of_0043246c, function_to_call_when_code_reaches_0043246c);
The generated code that provides easy access to a struct's fields looks like the following:
class struct__time_h_timespec {
constructor(baseaddr) {
this.alignment = 8
this.is_packed = true
this.base = baseaddr
this.total_size = 16
this.layout = {
tv_sec : this.base.add(0), //__time_t, size:8 - Signed Long Integer (compiler-specific size)
tv_nsec : this.base.add(8) //long, size:8 - Signed Long Integer (compiler-specific size)
}
this.offsets = {
tv_sec : 0, //__time_t, size:8
tv_nsec : 8 //long, size:8
}
}
}
class struct__time_h_itimerspec {
constructor(baseaddr) {
this.alignment = 8
this.is_packed = true
this.base = baseaddr
this.total_size = 32
this.layout = {
it_interval : this.base.add(0), //timespec, size:16 -
it_value : this.base.add(16) //timespec, size:16 -
}
this.offsets = {
it_interval : 0, //timespec, size:16
it_value : 16 //timespec, size:16
}
this.members = {
it_interval : new struct__time_h_timespec(this.layout.it_interval),
it_value : new struct__time_h_timespec(this.layout.it_value)
}
}
}