Introduction
LINQPad is a code snippet IDE that instantly executes C#, F# and VB expressions, statement blocks or programs dynamically - e.g. without a Visual Studio project or solution. There are paid versions that enhance the functionality by providing the user with Intellisense and a debugger. You can find out more information about the application on the website.Disclaimer
I am not responsible for your use of the information provided here. At the time of this writing, the following information is not against the End User License Agreement as we will not be distributing modifications to LINQPad, sub-licensing it, or circumventing any paid edition usage restrictions. In this series; however, we will be discussing what protections the software has and what methods we could take to bypass them.What You Need
To complete this segment, we will need a .NET decompiler such as .NET Reflector, dotPeek, JustDecompile, or ILSpy. Additionally, you will need de4dot with modifications made to deobfuscate .NET Reactor. You may want to check out dnSpy as well as it has better editing capabilities (thank you XenocodeRCE@rtn-team!).Getting Started
Picking up from last time we need to find the WSAgent class. Depending on your decompiler you may be able to click on WSAgent - which could prompt you to locate the Resources.dll for it to decompile.If we go back to the InternalAssemblyResolver in the LINQPad.Reflection namespace, we can see that this is a dll retrieved from the resources section.
Now that we know where the assembly is, we can figure out a way to retrieve it. I tried Resource extractors, but was unable to successfully retrieve it. Until kao@rtn-team pointed out that I could use the same method as LINQPad. Conveniently, there is even a DecompressResourceToFile static method!
public static bool DecompressResourceToFile(string resourceName, string outPath, bool overwrite = false)
{
bool flag;
try
{
if ((!File.Exists(outPath) ? true : overwrite))
{
string directoryName = Path.GetDirectoryName(outPath);
if (!Directory.Exists(directoryName))
{
Directory.CreateDirectory(directoryName);
}
using (Stream stream = File.Create(outPath))
{
if (!InternalAssemblyResolver.DecompressResource(resourceName, stream))
{
throw new InvalidOperationException(string.Concat("Resource '", resourceName, "' not found"));
}
}
flag = true;
}
else
{
flag = false;
}
}
catch (Exception exception)
{
Log.Write(exception, "DecompressResourceToFile");
flag = false;
}
return flag;
}
Now if we could only load LINQPad and be able to access the resources... sounds like a good time for Reflection!
string LinqpadExePath = Path.Combine(string.Empty, "LINQPad.exe");
string OutputPath = Path.Combine(string.Empty, "LINQPad.Resources.dll");
Assembly linqPad = Assembly.LoadFile(LinqpadExePath);
MethodInfo decompressResourceToFileMethod =
linqPad.GetType("LINQPad.Reflection.InternalAssemblyResolver").GetMethod("DecompressResourceToFile");
bool result =
(bool)
decompressResourceToFileMethod.Invoke(
null,
new object[]
{
"LINQPad.assemblies.Resources.bin",
OutputPath,
false
});
Replace both string.Empty with the correct path values (one to the LINQPad.exe file and one to where the program should output the Resources.dll) and run the code snippet. This will load the assembly and use reflection to get the DecompressResourceToFile - we then invoke it passing it the arguments it needs that we found with our decompiler. The first argument is the resource name to find, the second argument is the output path, and the third argument is whether or not this call should overwrite it if it already exists.
That is all it takes - to get the assembly. If you try to load the assembly though your decompiler may crash or if you are fortunate you may get something that looks like the following because the Resources assembly is obfuscated:
This is where de4dot steps in. Drag the LINQPad.Resources.dll (or whatever you named it) from your output directory onto de4dot to start the program.
You can now load the LINQPad.Resources-cleaned.dll in your decompiler and start analyzing. Just note that not everything could be deobfuscated - so take your time to understand what is going on.
WSAgent is located in the LINQPad.LanguageServices namespace. When you analyze it you can see methods for Register, RegisterOffline, Remove and utility methods that could not be unobfuscated.
Register Online
I am going to look at the Register function first from a high level. Even if you register offline, you must come through this code first (as per the LINQPad website instructions).
public static void Register(string code, bool allUsers, DoWorkEventArgs e)
{
e.Result = WSAgent.smethod_3(code, allUsers, e);
}
The fun begins once you get into smethod_3. Now you have to decipher and maintain mangled variable names, method names, and class names.
smethod_3 is the "RegisterLicenseOnline" function to me - and obviously the parameters are the code, allUsers, and the worker event args. This function bundles everything up, encrypts it and sends it to a web service for verification. Depending on the response it gets it may return suggestions to resolve the issue, tell you the license is wrong, or provide you with an Offline registration code.
Register Offline
The RegisterOffline does a little more - it even starts a new thread. This is probably because it has to do some decrypting which could otherwise make the application unresponsive while it is performing calculations.
public static void RegisterOffline(string code, bool allUsers)
{
try
{
code = code.Replace(" ", "").Replace("\r", "").Replace("\n", "").Replace("\t", "");
Class11.smethod_16(Convert.FromBase64String(code), allUsers);
Thread thread = new Thread(new ParameterizedThreadStart(Class11.smethod_18))
{
Name = "FLD",
IsBackground = true
};
Thread thread1 = thread;
thread1.Start(true);
thread1.Join(3000);
}
catch (Exception exception)
{
Log.Write(exception);
}
}
smethod_18 is probably the thread_DoWork function. This calls smethod_20 that validates the license and is the "RegisterLicenseOffline" function to me - where the variables that get passed are just the value true. The smethod_16 writes the code and other parameter values to IsolatedStorage that the thread then reads to get the code and other parameters back. After reading the data it performs checks on the system to get the hardware and environment variables it needs. Once it has everything it validates the buffer and signature of the code entered match - otherwise it fails. If it matches the program then decrypts the code and calls the LicenseManager to Register the code to this machine. The LicenseManager writes the code to a location on the drive for use later. After this is done the code invokes Program.Go again (which will make a call to validate the license) with a message that the software is licensed to the user.
In the next post, we will see if we can rebuild and understand the RegisterLicenseOnline and RegisterLicenseOffline methods to see if there are any loopholes we may have missed with our overhead view. We will see if we can understand exactly what is sent and determine if there are any vulnerabilities that expose themselves.
No comments:
Post a Comment