10

INTRODUCTION


I'm using the next C# code example shared by David Heffernan' for loading a .NET assembly from the application's resources and run It from memory:

Assembly a = Assembly.Load(bytes);
MethodInfo method = a.EntryPoint;
if (method != null)
    method.Invoke(a.CreateInstance(method.Name), null);

Here I just share an adaptation in VB.NET that I am using too:

Public Shared Sub Execute(ByVal resource As Byte(), ByVal parameters As Object())

    Dim ass As Assembly = Assembly.Load(resource)
    Dim method As MethodInfo = ass.EntryPoint

    If (method IsNot Nothing) Then
        Dim instance As Object = ass.CreateInstance(method.Name)
        method.Invoke(instance, parameters)
        If (instance IsNot Nothing) AndAlso (instance.GetType().GetInterfaces.Contains(GetType(IDisposable))) Then
            DirectCast(instance, IDisposable).Dispose()
        End If
        instance = Nothing
        method = Nothing
        ass = Nothing

    Else
        Throw New EntryPointNotFoundException("Entrypoint not found in the specified resource. Are you sure it is a .NET assembly?")

    End If

End Sub

PROBLEM


The problem is that if the executed assembly has an application exit instruction, then it also terminates my main/host application too. For example:

ConsoleApplication1.exe compiled from this source-code:

Module Module1
    Sub Main()
        Environment.Exit(0)
    End Sub
End Module

When I add ConsoleApplication1.exe to the application resources, and then I load it and run it with the Assembly.Load methodology, it also terminates my application because the call to Environment.Exit.

QUESTION


How can I prevent this, without modifying the source code of the executed assembly?.

Maybe I could do somehting like associate a kind of exit event handler to the executed assembly to handle/ignore it properly?. What are my options at this point?.

PS: For me no matter if the given solution is written in C# or VB.NET.

Please note two things, the first is that my intention is to resolve this issue in an automated/abstracted way, I mean the final result should just need to call the "Execute" method passing the resource and the arguments and don't worry about the rest; and secondly, I want the executed assembly to be ran synchronuslly, not async... in case of that could matter for a possible solution.

Community
  • 1
  • 1
ElektroStudios
  • 19,105
  • 33
  • 200
  • 417
  • 1
    Have you tried to load and execute it in another appdomain? – Legends Jan 08 '17 at 01:17
  • @Legends Yes, I tried it. First I researched for what you suggested me, then I found this MSDN article: **https://msdn.microsoft.com/en-us/library/ms173139%28v=vs.80%29.aspx** then I created the new application domain with `AppDomain.CreateDomain`, I replaced the instruction `Assembly.Load(byte())` for `myNewAppDomain.Load(byte())` but at the moment to call my method the `myNewAppDomain.Load()` throws a `FileNotFoundException` (how the hell it can throw that kind of exception, since I'm passing it a byte array and not a filepath?). Maybe I did something wrong. Thanks for comment! – ElektroStudios Jan 08 '17 at 03:11
  • 2
    Why do you want to execute a console application like this, do you have a special reason for this? – Legends Jan 08 '17 at 15:20
  • Just a stupid question...Why can't you just start a new process and use WaitForExit? – George Vovos Jan 12 '17 at 16:40
  • Can you write the in memory executable to disk(temporarily )? – George Vovos Jan 12 '17 at 16:48
  • @George Vovos Because the **Process** class expects a path to a existing file. Evidently, extracting the resource to disk to execute it "as normaly" is not the purpose of this question. – ElektroStudios Jan 12 '17 at 18:36
  • 1
    @Legends Jan Yes, I have a good reason, but I think if I give explanations from my side are irrelevant to the question itself. Anyways: for example this way the included program/resource is more protected for crackers; if I extract temporaly the application to disk to execute it, then anyone with the lowest knowledges can copy/usurp it. Thankyou everyone for comment! – ElektroStudios Jan 12 '17 at 18:37
  • @ElektroStudios Have you encrypted your embedded resource?If not ,just embedding the file as resource won't help much.Nothing prevents anyone from disassembling your dll and getting the file.To solve a similar issue in the past I used an (commercial )encryption tool – George Vovos Jan 12 '17 at 18:56
  • As you have the assembly as a byte array, why not load the assembly its IL with Mono.Cecil, change the method bodies which have an Environment.Exit() and save the modified IL back to a new bytearray assembly. Then you have a program that won't terminate the environment. – Niels V Jan 12 '17 at 21:50
  • @Niels Because what you are suggesting is hardcoded only to solve the given source-code **example** with `Environment.Exit()` call (and because honestlly I don't know how to do what you suggested), but remember that is only an example to reproduce the issue. Please note that the loaded assembly could call `Environment.Exit()`, or maybe `Application.Exit()`, or `Environment.FailFast()`, or a P/Invoke to call `ExitProcess` or `TerminateProcess` functions or just any other way that maybe could cause my application to terminate at the same time that the loaded app is terminated.Thanks for comment. – ElektroStudios Jan 12 '17 at 22:08
  • The @Ignaus solution for example works ok **only** with the provided source-code example, it prevents my application from termination when the loaded assembly calls `Environment.Exit`,however,it also prevents the loaded assembly from calling unmanaged code,so I can't P/invoke,and I can't use lots of .NET class library members that calls unmanaged code...so its non-viable solution.Please take into account that obviouslly a real source-code/loaded assembly (regardless of which is) will be more than a single method with the purpose to call `Environment.Exit`,I just provided a example to simplify. – ElektroStudios Jan 12 '17 at 22:18
  • Just a crazy idea (to avoid all the corner case scenarios with calling exit/failfast etc from unmanaged code) - haw about having separate stub executable, that would have very simple remoting contract with your calling code - loading the assembly from shared memory - and than waiting for the process to exit from your calling code. The code won't be probably much more complicated than whatever is suggested here and you have 100% buletproof solution. You can also use JobObjects to prevent your child object to accidentally survive the parent process – Jan Jan 17 '17 at 13:57
  • @Jan Thanks for comment, every idea is welcome. I didn't understand your suggestion, please could you explain it in other easier words?. What I think I understood is that you mean firstly I must create a shared memory block (IPC technique) then I must wrote in that block the bytes of the resource/assembly to be loaded, and at this point what I must do to load and run the assembly? (because calling Assembly.Load will terminate the parent process), there is where I think I'm lost in your suggestion. – ElektroStudios Jan 17 '17 at 14:53
  • Also about the Job Object, no idea about how to do it, I know what is a unit of processes, when a parent process loads another process, if the parent process is killed then the child processes are killed too, but this is one thing that a developer assumes when working with processes in his application because is a concept managed in background so he/me never cared about and didn't thinked about the need to implement it from scratch by himself/myself, so I don't know how to start to implementing it, using the CreateJobObject Win32 function maybe?. – ElektroStudios Jan 17 '17 at 14:54
  • Something to keep in mind - what you're doing is not secure. The only thing you're doing is removing the code from disk, but any decent hacker is going to do a memory dump anyway. To make matters worse, the nature of .NET makes it easier to locate the memory of interest, thanks to all the metadata packed in assemblies. If you're looking for something truly secure, I'd recommend looking into a Trusted Execution Environment. https://en.wikipedia.org/wiki/Trusted_execution_environment – Robear Jan 17 '17 at 16:44
  • 1
    @ElektroStudios Do not worry about JobObjects now - until you decide for separate process it's off topic thing. Also - my suggestion is only a complement to other solutions here. I'm not advising how to prevent Environemnt.Exit and others from terminating your process - I'm just suggesting how to isolate the effects. There are numerous scenarios that you basicaly can *not* prevent to tear down entire process even if happening in separate AppDomain (e.g. StackOverflowException). So the best way is to isolate it in separate process and being able to message those – Jan Jan 17 '17 at 16:44

4 Answers4

7

Update: My first solution doesn't work for assemblies contained in the resources of a program like OP asked; instead it loads it from the disk. The solution for loading from a byte array will follow (in progress). Note that the following points apply to both solutions:

  • As the Environment.Exit() method throws an exception because of lack of permissions, execution of the method will not continue after it is encountered.

  • You're going to need all the permissions that your Main method needs, but you can find those quickly by just typing in "Permission" in intellisense, or by checking the SecurityException's TargetSite property (which is an instance of MethodBase and will tell you which method failed).

  • If another method in your Main needs the UnmanagedCode permission, you're out of luck, at least using this solution.

  • Note that I found that the UnmanagedCode permission was the one that Environment.Exit() needs purely by trial and error.

Solution 1: When the assembly is on the disk

Okay, here's what I've found so far, bear with me. We're going to create a sandboxed AppDomain:

AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
// This is where the main executable resides. For more info on this, see "Remarks" in
// https://msdn.microsoft.com/en-us/library/system.appdomainsetup.applicationbase(v=vs.110).aspx#Anchor_1

PermissionSet permission = new PermissionSet(PermissionState.None);
// Permissions of the AppDomain/what it can do

permission.AddPermission(new SecurityPermission(SecurityPermissionFlag.AllFlags & ~SecurityPermissionFlag.UnmanagedCode));
// All SecurityPermission flags EXCEPT UnmanagedCode, which is required by Environment.Exit()
// BUT the assembly needs SecurityPermissionFlag.Execution to be run;
// otherwise you'll get an exception.

permission.AddPermission(new FileIOPermission(PermissionState.Unrestricted));
permission.AddPermission(new UIPermission(PermissionState.Unrestricted));
// the above two are for Console.WriteLine() to run, which is what I had in the Main method

var assembly = Assembly.LoadFile(exePath); // path to ConsoleApplication1.exe

var domain = AppDomain.CreateDomain("SomeGenericName", null, adSetup, permission, null); // sandboxed AppDomain

try
{
    domain.ExecuteAssemblyByName(assembly.GetName(), new string[] { });
}
// The SecurityException is thrown by Environment.Exit() not being able to run
catch (SecurityException e) when (e.TargetSite == typeof(Environment).GetMethod("Exit"))
{
    Console.WriteLine("Tried to run Exit");
}
catch (SecurityException e)
{
    // Some other action in your method needs SecurityPermissionFlag.UnmanagedCode to run,
    // or the PermissionSet is missing some other permission
}
catch
{
    Console.WriteLine("Something else failed in ConsoleApplication1.exe's main...");
}

Solution 2: When the assembly is a byte array

Warning: cancerous solution follows.

When changing my solution to load a byte array, OP and I discovered a weird exception file not found exception: even if you pass in a byte array to Assembly.Load(), domain.ExecuteAssemblyByName() still searches the disk for the assembly, for some weird reason. Apparently we weren't the only ones with the issue: Loading Byte Array Assembly.

First, we have a Helper class:

public class Helper : MarshalByRefObject
{
    public void LoadAssembly(Byte[] data)
    {
        var a = Assembly.Load(data);
        a.EntryPoint.Invoke(null, null);
    }
}

which as you can see, loads the assembly using Assembly.Load() and calls it's entry point. This is the code we'll be loading into the AppDomain:

AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
// This is where the main executable resides. For more info on this, see "Remarks" in
// https://msdn.microsoft.com/en-us/library/system.appdomainsetup.applicationbase(v=vs.110).aspx#Anchor_1

PermissionSet permission = new PermissionSet(PermissionState.None);
// Permissions of the AppDomain/what it can do

permission.AddPermission(new SecurityPermission(SecurityPermissionFlag.AllFlags & ~SecurityPermissionFlag.UnmanagedCode));
// All SecurityPermission flags EXCEPT UnmanagedCode, which is required by Environment.Exit()
// BUT the assembly needs SecurityPermissionFlag.Execution to be run;
// otherwise you'll get an exception.

permission.AddPermission(new FileIOPermission(PermissionState.Unrestricted));
permission.AddPermission(new UIPermission(PermissionState.Unrestricted));
// the above two are for Console.WriteLine() to run, which is what I had in the Main method

var domain = AppDomain.CreateDomain("SomeGenericName", null, adSetup, permission, null); // sandboxed AppDomain

try
{
    Helper helper = (Helper)domain.CreateInstanceAndUnwrap(typeof(Helper).Assembly.FullName, typeof(Helper).FullName);
    // create an instance of Helper in the new AppDomain
    helper.LoadAssembly(bytes); // bytes is the in-memory assembly
}
catch (TargetInvocationException e) when (e.InnerException.GetType() == typeof(SecurityException))
{
    Console.WriteLine("some kind of permissions issue here");
}
catch (Exception e)
{
    Console.WriteLine("Something else failed in ConsoleApplication1.exe's main... " + e.Message);
}

Note that in the second solution, the SecurityException becomes a TargetInvocationException with it's InnerException property being a SecurityException. Unfortunately, this means that you cannot use e.TargetSite to see which method threw the exception.

Conclusion/Things to keep in mind

This solution isn't perfect. It would be much better to somehow go through the IL of the method and artificially remove the call to Environment.Exit().

Community
  • 1
  • 1
squill25
  • 1,198
  • 6
  • 20
  • Thanks for the answer but I think you've missunderstood the question, ot I'm missunderstanding your code, because it seems that in `Assembly.LoadFile(exePath)` you are expecting a physical/local file to load?. – ElektroStudios Jan 08 '17 at 03:17
  • @ElektroStudios yes, the path of your `ConsoleApplication1.exe`, but using `Assembly.Load(bytes...)` works just the same. I was just using an external `exe` for testing. – squill25 Jan 08 '17 at 03:18
  • @ElektroStudios also, you'll see that I used `adSetup.ApplicationBase` which is needed for my solution to run, but you can just set this to your main build/debug folder. – squill25 Jan 08 '17 at 03:22
  • I've made a slight mistake but corrected it, see lines 1-4 in my code. – squill25 Jan 08 '17 at 03:25
  • Sorry for your patience but I'm having troubles with the solution provided. The exception handled is the last, the base `Exception` class instead of the `SecurityException` (so I don't know what to do because the expected exception is not thrown). Also, in `adSetup.ApplicationBase` I must define a valid existing folder? why then? it can be the temporary folder path? – ElektroStudios Jan 08 '17 at 03:32
  • In any case, even if this solution covers the Environment.Exit() call, I must add additional exception handlers for Application.Exit, Process.Kill, and/or even for win32 api calls that can close/kill an application in other manners? – ElektroStudios Jan 08 '17 at 03:35
  • I was initially wrong about `adSetup.ApplicationBase`. I ended up just setting it to `AppDomain.CurrentDomain.BaseDirectory`, which is the directory in which the main exe resides, but you shouldn't worry about that, it's just a technicality. Also, can you give me some more details about the Exception you're getting? Type, `Message` property, `InnerException` property if it has one, etc? – squill25 Jan 08 '17 at 03:35
  • I just tested with `Application.Exit()`, and the solution still works. Let me inquire about Windows API calls. Do you want to block all native calls, or just the ones that could close the application? – squill25 Jan 08 '17 at 03:38
  • Yes sorry, I'll give the info. The exception message is this: **Could not load file or assembly 'ConsoleApplication1 (version info here...)' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference.** I must say that if I just use Assembly.Load() without messing with app domain then I don't have this kind of exception and all works ok... but my application closes. – ElektroStudios Jan 08 '17 at 03:39
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/132605/discussion-between-ignaus-and-elektrostudios). – squill25 Jan 08 '17 at 03:42
  • `even if you pass in a byte array to Assembly.Load(), domain.ExecuteAssemblyByName() still searches the disk for the assembly, for some weird reason` - I have just been looking through the Reference Source, and I can confirm this behaviour. Each existing overload of `AppDomain.ExecuteAssemblyByName()` internally calls `Assembly.Load()` which causes it to attempt to load the assembly from the disk (as no byte array was specified, merely the name that you passed to `ExecuteAssemblyByName()`). -- I'm tagging @ElektroStudios too. – Visual Vincent Jan 08 '17 at 10:55
  • @Visual Vincent I didn't analyzed the .NET source reference, but I see there is the `AppDomain.Load()` on which you can pass in a byte array, however, calling that method (which I imagine it should internally call `Assembly.Load()` as you said, and passing the specified **byte array**) causes the same behaviour that you've described, it searchs for a file/string so it will throw a `FileNotFoundException`. I really start to think this is a "undiscovered" bug in .NET Framework Class library... and maybe someone in contact with the devs should report this! – ElektroStudios Jan 08 '17 at 14:12
  • @ElektroStudios : I wouldn't call it a "bug", actually... It's more of a coding-mistake. Someone wrote those calls to the regular `Assembly.Load()`, but forgot/wasn't aware of that they're trying to load it from disk. I might have a workaround to this, however. I'll be in touch when I know more. – Visual Vincent Jan 08 '17 at 16:11
  • @ElektroStudios : As for `AppDomain.Load()` it just returns an `Assembly`. It should not thrown an exception, but it's not really any different from calling `Assembly.Load()` either. It still leaves you with having to execute the assembly (which is the problem here). – Visual Vincent Jan 08 '17 at 16:33
  • The new (2nd) solution that you published it gives me an error. The last `catch (Exception e)` block is thrown giving this exception message: `Attempt by Method 'MyNamespace.blahblahblah.Helper.LoadAssembly(Byte[])' to access method 'ConsoleAplication1.Module1.Main()' failed.` are you sure permissions are the way to go? (just asking from my ignorance, is a kind of solution that seems can create new problems that would need to be resolved... and then). I just copied your code as is without modifications to try it first. – ElektroStudios Jan 11 '17 at 01:06
  • I discovered by trial-and-error that it also needs to add a `New ReflectionPermission(PermissionState.Unrestricted)`, then now it throws the expected `SecurityException`... well, I ended adding all the (18) permissions except the `SecurityPermission`, because remember that this method should be used for generic purposes, and I can't predict which kind of permissions will require all the programs that can be ran through this methodology. – ElektroStudios Jan 11 '17 at 01:31
  • My last question to this solution is: How can affect to a loaded program/resource the `SecurityPermission` restriction? I didn't found anything about the purpose of this permission, the MSDN doesn't explain anything about it, but I'm sure the `SecurityPermission` will not restrict only application exit signals... It should do more, I mean, about what things I must be worried when loading an assembly using this methodology? What can go wrong due to restricting the `SecurityPermission`?, what is the downside of restricting `SecurityPermission`? – ElektroStudios Jan 11 '17 at 01:32
  • I see... I discovered the downside of this solution is that the loaded/ran assembly is unable to call unmanaged-code directlly (a `System.MethodAccessException` will be thrown), so any P/Invoking is allowed!!. I really need an alternative for this kind of permission focused solution. I mean, when the solution to prevent an application termination is disabling entirely the ability to call unmanaged code by the loaded assembly then... that cannot be called a solution, because it is a patch that will create new problems to resolve!. – ElektroStudios Jan 11 '17 at 02:31
  • Just tried to call a random win32 method from the loaded assembly, and this was the exception message: **Attempt by security transparent method 'ConsoleApplication1.Module1.Main()' to call native code through method 'ConsoleApplication1.NativeMethods.ShowCursor(Boolean)' failed. Methods must be security critical or security safe-critical to call native code.** – ElektroStudios Jan 11 '17 at 02:33
4

All credits go to Kirill Osenkov - MSFT

I can successfully load the assembly into another AppDomain and call its entry point. Environment.Exit always shuts down the hosting process.
Workaround for this would be, to return an int from Main of the loaded console application. Zero for success and other numbers for errors.

Instead of this:

Module Module1
    Sub Main()
        // your code
        Environment.Exit(0)
    End Sub
End Module

write: (I hope this is valid VB.NET :-))

Module Module1
 Function Main() As Integer
    // your code
    Return 0 // 0 == no error
 End Function
End Module

Demo - C#

class Program
{
    static void Main(string[] args)
    {
        Launcher.Start(@"C:\Users\path\to\your\console\app.exe");
    }
}    

public class Launcher : MarshalByRefObject
{
    public static void Start(string pathToAssembly)
    {
        TextWriter originalConsoleOutput = Console.Out;
        StringWriter writer = new StringWriter();
        Console.SetOut(writer);

        AppDomain appDomain = AppDomain.CreateDomain("Loading Domain");
        Launcher program = (Launcher)appDomain.CreateInstanceAndUnwrap(
            typeof(Launcher).Assembly.FullName,
            typeof(Launcher).FullName);

        program.Execute(pathToAssembly);
        AppDomain.Unload(appDomain);

        Console.SetOut(originalConsoleOutput);
        string result = writer.ToString();
        Console.WriteLine(result);
    }

    /// <summary>
    /// This gets executed in the temporary appdomain.
    /// No error handling to simplify demo.
    /// </summary>
    public void Execute(string pathToAssembly)
    {
        // load the bytes and run Main() using reflection
        // working with bytes is useful if the assembly doesn't come from disk
        byte[] bytes = File.ReadAllBytes(pathToAssembly); //"Program.exe"
        Assembly assembly = Assembly.Load(bytes);
        MethodInfo main = assembly.EntryPoint;
        main.Invoke(null, new object[] { null });
    }
}

Also to note:

Also, note that if you use LoadFrom you'll likely get a FileNotFound exception because the Assembly resolver will attempt to find the assembly you're loading in the GAC or the current application's bin folder. Use LoadFile to load an arbitrary assembly file instead--but note that if you do this you'll need to load any dependencies yourself.

Legends
  • 21,202
  • 16
  • 97
  • 123
  • Yeah this was the solution I'd gotten... It was just too late at night and I couldnt post it. I can confirm that it works. – squill25 Jan 08 '17 at 17:05
  • Unfortunately, this does not work too... Environment.Exit also exits the calling app. – Legends Jan 08 '17 at 17:10
  • yes but all you have to do for your solution to work is add the permissions like I did. See my solution. – squill25 Jan 08 '17 at 17:12
1

More AppDomain code could help in finding a solution. Code can be found at LoadUnload

The small applications included in the project LoadUnload contain AppDomain code that you maybe able to adapt in your solution.

D.Zadravec
  • 647
  • 3
  • 7
1

There is only one way to do this. You have to dynamically instrument all the code that the assembly is going to get executed. This boils down to intercepting system calls. There is no easy way to do this. Note that this does not require modifying the source code.

Why can't the .NET security system do this? While the system could have provided you with a security permission that you can use to control calls to Environment.Exit, that would not really solve the problem. The assembly could still call into unmanaged code. Other answers have pointed out that this can be done by creating an AppDomain and revoking SecurityPermissionFlag.UnmanagedCode. Indeed, this works, but you indicated in the comments that you to enable the assembly to call unmanaged code.

That's it if you want to run the code in the same process.You could also run the code in another process, but then you have to do interprocess communication.

Hadi Brais
  • 22,259
  • 3
  • 54
  • 95