Using Powershell and Reflection API to invoke methods from .NET Assemblies
During application assessments, I have stumbled upon several cases when I need to call out a specific function embedded in a .NET assembly (be it .exe or .dll extension). For example, an encrypted database password is found in a configuration file. Using .NET Decompiler, I am able to see and identify the function used to encrypt the database password. The encryption key appears to be static, so if I could call the corresponding decrypt function, I would be able to recover that password.
Classic solution: using Visual Studio to create new project, import encryption library, call out that function if it’s public or use .NET Reflection API if it’s private (or just copy the class to the new workspace, change method accessibility modifier to public and call out the function too if it is self-contained).
Alternative (and hopeful less-time consuming) solution: Powershell could be used in conjunction with .NET Reflection API to invoke methods directly from the imported assemblies, bypassing the need of an IDE and the grueling process of compiling source code.
Requirements
Powershell and .NET framework, available at https://www.microsoft.com/en-us/download/details.aspx?id=34595
Note that Powershell version 3 is used in the below examples, and the assembly is developed in C#.
Walkthrough
First, identify the fully qualified class name (typically in the form of Namespace.Classname ), method name, accessibility level, member modifier and method arguments. This can easily be done with any available .NET Decompiler (dotPeek, JustDecompile, Reflector)
Scenario 1: Public static class – Call public static method
namespace AesSample { public class AesLibStatic { ... public static string DecryptString(string cipherText) { return DecryptStringPrivate(StringToByteArray(cipherText)); }
This is the vanilla case, essentially in powershell you just need to call [Namespace].[Classname]::(params[]) And it only took 2 lines of code to do it:
Load all .NET binaries in the folder Get-ChildItem -recurse "D:DocumentsVisual Studio 2010ProjectsAesSampleAesSamplebinDebug"|Where-Object {($_.Extension -EQ ".dll") -or ($_.Extension -eq ".exe")} | ForEach-Object { $AssemblyName=$_.FullName; Try {[Reflection.Assembly]::LoadFile($AssemblyName)} Catch{ "***ERROR*** Not .NET assembly: " + $AssemblyName}} #Call public static method [AesSample.AesLibStatic]::DecryptString("8E3C5A3088CEA26B634CFDA09D13A7DB")
Scenario 2: Public static class – Call private static method
Let’s say you want to call this private static method, assuming the method name is unique within the class
private static string DecryptStringSecret(string cipherText) { return DecryptStringPrivate(StringToByteArray(cipherText)); }
Private methods can’t be accessed directly from Powershell object, instead you will need to find it by name and correct binding flags. More information about binding flags could be found here: https://msdn.microsoft.com/en-us/library/4ek9c21e.aspx
#Load all .NET binaries in the folder Get-ChildItem -recurse "D:DocumentsVisual Studio 2010ProjectsAesSampleAesSamplebinDebug"|Where-Object {($_.Extension -EQ ".dll") -or ($_.Extension -eq ".exe")} | ForEach-Object { $AssemblyName=$_.FullName; Try {[Reflection.Assembly]::LoadFile($AssemblyName)} Catch{ "***ERROR*** Not .NET assembly: " + $AssemblyName}} #Only retrieve static private method $BindingFlags= [Reflection.BindingFlags] "NonPublic,Static" #Load method based on name $PrivateMethod = [AesSample.AesLibStatic].GetMethod("DecryptStringSecret",$bindingFlags) #Invoke $PrivateMethod.Invoke($null,"8E3C5A3088CEA26B634CFDA09D13A7DB")
Scenario 2 Extension: Function Overloading: Public static class – Call private static method
In some cases, programmer takes advantage of function overloading feature of Object-Oriented languages – i.e multiple methods can have the same name as long as they have different argument list. For example:
private static string DecryptStringPrivate(string cipherText) { return DecryptStringFromBytes_Aes(StringToByteArray(cipherText), key, iv); } private static string DecryptStringPrivate(byte[] cipherText) { return DecryptStringFromBytes_Aes(cipherText, key, iv); }
Note that the two DecryptStringPrivate methods have the same name, but one takes a string as input, while another takes a bytearray as input. In this case, to look up the right method, you will need method name and method signature. The snippet below will invoke DecryptStringPrivate(byte[] cipherText)
#Load all .NET binaries in the folder Get-ChildItem -recurse "D:DocumentsVisual Studio 2010ProjectsAesSampleAesSamplebinDebug"|Where-Object {($_.Extension -EQ ".dll") -or ($_.Extension -eq ".exe")} | ForEach-Object { $AssemblyName=$_.FullName; Try {[Reflection.Assembly]::LoadFile($AssemblyName)} Catch{ "***ERROR*** Not .NET assembly: " + $AssemblyName}} #Search for private method based on name $PrivateMethods = [AesSample.AesLibStatic].GetMethods($bindingFlags) | Where-Object Name -eq DecryptStringPrivate $PrivateMethods | ForEach-Object{ $PrivateMethod=$_ $MethodParams=$PrivateMethod.GetParameters() $MemberSignature = $MethodParams | Select -First 1 | Select-Object Member #This will list all the method signatures $MemberSignature.Member.ToString() #Choose the correct method based on parameter list If ($MemberSignature.Member.ToString() -eq "System.String DecryptStringPrivate(Byte[])"){ [byte[]]$Bytes =@(70,1,65,70,155,197,95,238,85,79,190,34,158,69,125,233,53,212,111,19,248,209,147,180,19,172,150,25,97,41,127,175) [Object[]] $Params=@(,$Bytes) #Call with the right arguments $PrivateMethod.Invoke($null,$Params) } }
Scenario 3: Public class – Call nonstatic public method
If a class is not declared with “static” keyword, its methods can’t be invoked directly from the class itself but from an instance of the class with the following snippet:
Classname a = new Classname(); a.methodName(args[]);
For example:
namespace AesSample { public class AesLib {...public string DecryptString(string cipherText) { return DecryptStringPrivate(StringToByteArray(cipherText)); }
Sample solution:
#Load all .NET binaries in the folder Get-ChildItem -recurse "D:DocumentsVisual Studio 2010ProjectsAesSampleAesSamplebinDebug"|Where-Object {($_.Extension -EQ ".dll") -or ($_.Extension -eq ".exe")} | ForEach-Object { $AssemblyName=$_.FullName; Try {[Reflection.Assembly]::LoadFile($AssemblyName)} Catch{ "***ERROR*** Not .NET assembly: " + $AssemblyName}} #Call default constructor (no argument) $AesSample= New-Object "AesSample.AesLib" #Call constructor with arguments using this syntax: $AesSample= New-Object "AesSample.AesLib" ("a","b") #Invoke public method $AesSample.DecryptString("8E3C5A3088CEA26B634CFDA09D13A7DB")
Scenario 4: Public class: Function Overloading – Call nonstatic private method
This is very similar to Scenario 2: extension above. Again you will need both method name and argument list to call the right method.
private string DecryptStringPrivate(string cipherText) { return DecryptStringFromBytes_Aes(StringToByteArray(cipherText), key, iv); } private string DecryptStringPrivate(byte[] cipherText) { return DecryptStringFromBytes_Aes(cipherText, key, iv); }
Solution:
#Load all .NET binaries in the folder Get-ChildItem -recurse "D:DocumentsVisual Studio 2010ProjectsAesSampleAesSamplebinDebug"|Where-Object {($_.Extension -EQ ".dll") -or ($_.Extension -eq ".exe")} | ForEach-Object { $AssemblyName=$_.FullName; Try {[Reflection.Assembly]::LoadFile($AssemblyName)} Catch{ "***ERROR*** Not .NET assembly: " + $AssemblyName}} #Call constructor $Instance= New-Object "AesSample.AesLib" ("a","b") # Find private nonstatic method. If you want to invoke static private method, replace Instance with Static $BindingFlags= [Reflection.BindingFlags] "NonPublic,Instance" $Instance.GetType().GetMethods($BindingFlags) | Where-Object Name -eq DecryptStringPrivate| ForEach-Object{ $PrivateMethod=$_ $MethodParams=$PrivateMethod.GetParameters() $MemberSignature = $MethodParams | Select -First 1 | Select-Object Member $MemberSignature.Member.ToString() If ($MemberSignature.Member.ToString() -eq "System.String DecryptStringPrivate(Byte[])"){ [byte[]]$Bytes =@(70,1,65,70,155,197,95,238,85,79,190,34,158,69,125,233,53,212,111,19,248,209,147,180,19,172,150,25,97,41,127,175) [Object[]] $Params=@(,$Bytes) # You will need to pass the Instance here instead of $null $PrivateMethod.Invoke($Instance,$Params) } }
Closing thoughts:
- I didn’t include code to call out methods from private class in this post. Mainly because usually you can find a public class that reference to private class if it needs to use some methods of the private class, and then you can just invoke the calling method of the public class instead.
- Those snippets work under assumption that all necessary .NET assemblies are located in the same folder. If other externally-linked .NET assemblies are required, add additional code to load them into memory.
- Same with externally-linked native assemblies: either set them in your PATH environment variable, manually copy them to C:Windowssystem32 (not recommended) or load them with Powershell’s DllImport: https://blogs.msdn.com/b/mattbie/archive/2010/02/23/how-to-call-net-and-win32-methods-from-powershell-and-your-troubleshooting-packs.aspx
- This method may also be useful in situations where you can’t decompile the application’s assemblies due to legal constraints. Consult with client or your contact before doing this, but it may be OK to list assembly’s methods and call them when necessary. This snippet will import all assemblies found in a folder and list all the constructors, methods along with argument list in a Powershell GridView (it’s kind of like mini-Excel so you have built-in search and filter features)
$Results=@() Get-ChildItem -recurse "D:DocumentsVisual Studio 2010ProjectsAesSampleAesSamplebinDebug"| Where-Object { ($_.Extension -EQ ".dll") -or ($_.Extension -eq ".exe")} | ForEach-Object { $AssemblyName= $_.FullName; try {$Assembly = [Reflection.Assembly]::LoadFile($AssemblyName);} catch{ "***ERROR*** Error when loading assembly: " + $AssemblyName} $Assembly | Format-Table; $Assembly.GetTypes() | %{ $Type=$_;$_.GetMembers() | Where-Object {$_.MemberType -eq "Constructor"-or $_.MemberType -EQ "Method" } | %{ $ObjectProperties = @{ 'Assembly' = $AssemblyName; 'ClassName' = $Type.Name; 'ClassPublic' = $Type.IsPublic; 'ClassStatic' = $Type.IsAbstract -and $Type.IsSealed; 'MemberType' = $_.MemberType; 'Member' = $_.ToString(); 'Changed' = $Changed; 'MemberPublic' = $_.IsPublic; 'MemberStatic' =$_.IsStatic; } $ResultsObject = New-Object -TypeName PSObject -Property $ObjectProperties $Results+=$ResultsObject } } } $Results | Select-Object Assembly,ClassPublic,ClassStatic,ClassName,MemberType,Member,MemberPublic,MemberStatic | Sort-Object Assembly,ClassName,MemberType,Member| Out-GridView -Title "Reflection"
Sample output:
Reference:
- Reflection in the .NET Framework: https://msdn.microsoft.com/en-us/library/f7ykdhsy.aspx
- Access Modifiers (C# Reference): https://msdn.microsoft.com/en-us/library/wxh6fsc7.aspx
- Binding flags: https://msdn.microsoft.com/en-us/library/kyaxdd3x.aspx
- Use PowerShell to Work with the .NET Framework Classes: https://blogs.technet.com/b/heyscriptingguy/archive/2010/11/11/use-powershell-to-work-with-the-net-framework-classes.aspx
- AES Sample Code: https://msdn.microsoft.com/en-us/magazine/cc164055.aspx
- How to check with Reflection if a class is static: https://dotneteers.net/blogs/divedeeper/archive/2008/08/04/QueryingStaticClasses.aspx
Thanks Scott for feedbacks and help testing them out. You can also find the snippets mentioned in this post at https://github.com/NetSPI/PS_Reflector
Explore more blog posts
CTEM Defined: The Fundamentals of Continuous Threat Exposure Management
Learn how continuous threat exposure management (CTEM) boosts cybersecurity with proactive strategies to assess, manage, and reduce risks.
Balancing Security and Usability of Large Language Models: An LLM Benchmarking Framework
Explore the integration of Large Language Models (LLMs) in critical systems and the balance between security and usability with a new LLM benchmarking framework.
From Informational to Critical: Chaining & Elevating Web Vulnerabilities
Learn about administrative access and Remote Code Execution (RCE) exploitation from a recent Web Application Pentest.