Packaging Apps into Single Files

May 9, 2017

tags: , , , ,
no comments

One of the hallmarks of easy-to-use tools is simple installation, preferably no installation at all. The classic example is the Sysinternals tools. Each tool is a single executable, self contained, and can be run from anywhere, including network shares and web locations.
These tools have no dependencies (except for built-in Windows DLLs), or so it seems.
One canonical example is Process Explorer that hides within it two binaries. The first is a kernel driver, used to extract information from the system that cannot be done from user mode (such as reading values of kernel variables), and the other is a 64 bit version of itself that is spawned if running on a 64-bit system.

The method to hide such dependencies is fairly well known and involves using embedded custom resources. If we open procexp.exe within PE Explorer or another Portable Executable Viewer tool or a resource viewer tool such as Resource Hacker, we would find two custom resources under the name BINRES:

The first resource (with a value of 150) is 29664 bytes long and a good guess would be that it’s the driver. The hint that its even a PE onto itself is the “MZ” magic sequence at the start, and the “This program cannot be run in DOS mode” string embedded within it. We can even use the Export button to save the resource to file (such as procexp.sys) and examine it with PE Explorer.

The second resource is much larger, around 1.4 MB in size. That would be the 64 bit version of Process Explorer. Again, we can export and verify that it’s the same one extracted from Process Explorer at runtime.

If we run Process Explorer on a 64-bit system and find the procexp.exe process within Process Explorer, and then switch to process tree mode, we would see the following:

Clearly, the master procexp.exe extracts the 64-bit version of Process Explorer from its resource and launches it. Looking at the properties of the 64-bit extracted process we see:

Clearly, the master procexp.exe extracted the 64-bit image to the user’s temp directory and launched it from there.

If Process Explorer is executed with admin privileges, then – the first time it does – it installs its device driver in much the same way. It extracts it into the System32\Drivers directory and installs it from there (using a call to the CreateService API), and then loads the driver with a call to StartService.

Extracting Resources

Extracting custom resources from binaries can be accomplished with a few relatively straightforward Windows API calls.

  1. First, a call to FindResource is made to locate the resource within the executable (or technically another DLL loaded into the process, but the idea here is to avoid other DLLs).
  2. Next, determine the size of the resource with SizeofResource.
  3. Call LoadResource followed by LockResource to finally obtain a normal pointer to the resource data.

What about .NET?

In the .NET world, applications depend on two basic types of assemblies. The first are framework-installed assemblies, available on any Windows machine and stored in the Global Assembly Cache (GAC). These are not normally an issue, assuming the required .NET version is installed. The other type of assemblies are custom ones, built specifically to support the application, or delivered from some 3rd party source, such as Nuget.

Normally, a reference added for such assemblies copies them to the output folder for the application, so they are available at runtime. Our purpose here is to eliminate these dependencies and just be left with the executable itself. Of course just setting the property Copy Local to false, thus preventing the copy to the target folder, would make the project build just fine, but throw an exception at runtime, since the required DLLs are not there. Can we do th same as with the native case and store the assemblies in resources and extract at runtime? It turns out that we can.

Assemblies in Resources

The first step to embedding such assemblies as resources, is to add them to the project by selecting “Add an Existing Item” from the Solution Explorer Project’s context menu in Visual Studio. Navigate to the already built assemblies (might be local DLLs in another project or from nuget packages, for example). Then set their Build Action property to Embedded Resource. Here’s a screenshot from the GflagsX tool that uses this technique:

I’m using 4 custom assemblies and I’ve placed them in an Assemblies folder for convenience. The “original” referenced assemblies can now be changed to Copy Local to be false. At runtime, we’ll make the added assemblies available to the CLR as we’ll see in a moment. Note that we cannot remove the references themselves, otherwise any usage of these will fail compilation.

To make this work we must load these assemblies dynamically to somehow provide them to the CLR when needed. Loading must be done very early in the applciation before these assemblies may be needed (otherwise, the CLR wouldF throw an exception). Here’s the code from the GFlags project (WPF application) that calls a LoadAssemblies method in the App class constructor:

[MethodImpl(MethodImplOptions.NoInlining)]
private void LoadAssemblies() {
    var appAssembly = typeof(App).Assembly;
	foreach (var resourceName in appAssembly.GetManifestResourceNames()) {
		if (resourceName.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase)) {
			using (var stream = appAssembly.GetManifestResourceStream(resourceName)) {
				var assemblyData = new byte[(int)stream.Length];
				stream.Read(assemblyData, 0, assemblyData.Length);
				var assembly = Assembly.Load(assemblyData);
				_assemblies.Add(assembly.GetName().Name, assembly);
			}
		}
	}
	AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
}

LoadAssemblies extracts all resources that end with a DLL extension, and loads the assemblies with Assembly.Load straight from memory – no need to save to a temporary file! Finally, it stores them in a dictionary keyed by the assembly name.

The loaded assemblies will not be used automatically by the CLR, as the CLR has no idea they are “connected” to the soon-to-be missing assemblies.

The last piece of the puzzle uses a CLR feature that allows our code to provide a missing assembly by handling the AppDomain.AssemblyResolve event. This event is raised when the CLR cannot locate a needed assembly, right before it throws an exception. This event handler is the “last chance” to provide the required assembly. Here is the implementation:

Assembly OnAssemblyResolve(object sender, ResolveEventArgs args) {
	var shortName = new AssemblyName(args.Name).Name;
	if (_assemblies.TryGetValue(shortName, out var assembly)) {
		return assembly;
	}
	return null;
}

The code looks up the assembly name that the CLR expects to exist and returns the requested one we stored in the dictionary.

That’s it! .NET can provided dependency-free applications.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*