Find .Net Functions to use with Frida and Fermion.
Blog Posts
- https://watson0x90.com/net-hooking-with-frida-and-fermion-c14d4f19c823 - (Part 1)
- How to enumerate, find and hook functions
- https://watson0x90.com/net-hooking-with-frida-and-fermion-part-2-206f96524380 - (Part 2)
- How to read variables after you have hooked a function
-
Create a new Visual Studio project:
- Type: Class Library (.NET Framework) - A Project for creating a C# class library (.dll)
- Name:
FunctionEnumerator
-
Add the following Nuget Package to the project:
-
You wil then see a new popup window to configure the project using the DLLExport GUI. Set the following options:
- Note: Ensure that your options look exact
-
Copy and Past the code from the FunctionEnumerator.cs code into Visual Studio
-
Build the project!
- It is fine to leave the buid as "Debug"
-
Open the lovely Fermion tool, and paste in the
fermion_dotnet_hooking_script.js
script -
Change the approrpiate values for the
dllPath
,targetAssemblyPath
, andfuncName
-
Attach to the process and the script will run
-
The output in the text area will include the address of the function
-
Now you can comment out the
findFunc()
and uncommenthookAtAddr()
-
Change the address with the acutally memory address from your output
-
Watch the beautiful interception happen!!
- Instance Function
- Static Function
It turns out that when intercepting a .Net instance function and attempting to read .Net variables, we are dealing with multiple variables, not just what is presented in a tool like DnSpy.
Lets consider the following example function:
public void updateUsername(string username)
{
this.MyAppUsername.Text = username;
}
The breakdown:
- The function takes one parameter of type
string
namedusername
. - The function body sets the
Text
property of theMyAppUsername
object to the value of theusername
parameter. - The function is an instance method, meaning it operates on an instance of a class (this).
Calling Convention on x64 Windows:
- Microsoft x64 Calling Convention: The first four arguments are passed in the registers RCX, RDX, R8, and R9.
- RCX (args[0]): Used for the
this
pointer in instance methods. - RDX (args[1]): First argument (if this is present). Which is the string username parameter.
- R8: Second argument.
- R9: Third argument.
- RCX (args[0]): Used for the
When attempting to intercept a similar function before, I assumed that I was only dealing with one argument. So, I intercepted the function with Frida; I attempted to read the value at args[0] with no success. I attempted to dereference the pointer and read values, but I had no success. It was only when I was chatting with ChatGPT that I ended up getting an unexpected answer.
I descrbed the function I was trying to access and provided its example layout, I was surpised by the response:
Given this information, we can proceed to determine what args[0] and args[1] are in your hook and how to extract the string parameter text from args[1].
I thought it was interesting that I was told to read the value of args[1]
because I was under the impression I was dealing with only ONE argument being passed. It turns out that the text string is accessible via args[1]
while the object instance is stored within args[0]
.
Adding in a helper function to read the dotnet string, I was able to return the string value:
The code is reference in read_dotnet_variable.js
I will still need to research to determine what happens if I take in multiple parameters of different types, but this is still very interesting.
In Static Functions, there is no this
pointer because the method belongs to the class itself rather than an instance of the class. This affects how the arguments are passed and how they should be accessed in a Frida script.
- First Argument (args[0]): Passed in the RCX register.
- Second Argument (args[1]): Passed in the RDX register.
- Third Argument (args[2]): Passed in the R8 register.
- Fourth Argument (args[3]): Passed in the R9 register.
- Additional Arguments: Passed on the stack.
For Static Functions:
- Since there is no this pointer, args[0] corresponds to the first parameter of the method.
Here is an example C# static function
public static bool updatePolicy(string jsonReply)
{
// Function body...
}
To read the variable, we will use custom Frida function, referenced below, readDotNetString
to read args[0]
.
// Hook the function at the given address
Interceptor.attach(functionAddress, {
onEnter: function (args) {
var textPtr = args[0];
send("args[0] (textPtr): " + textPtr);
// Read the 'jsonReply' parameter
var textValue = readDotNetString(textPtr);
send("Original jsonReply parameter value: " + textValue);
},
onLeave: function (retval) {
// Additional handling if needed
}
});
- Need to figure out how to unload the DLL after it is used. Right now it is loaded into the process and will stay there.