20

How can I quickly tell if a EXE or DLL I have is managed code or not?

I spent some time recently trying to disassemble a file and then later learned through some traces in the code that I could have skipped all that work and just used ILspy. How can I avoid repeating that experience in the future?

9 Answers 9

14

A managed DLL / Application will have a primary dependency on MSCOREE.dll... So, if you open the DLL in Dependency Walker you have no problems in telling a managed library from an unmanaged one.

    http://www.dependencywalker.com/

Quoted from here.

Other useful MSDN links:

3
  • 5
    See answers from broadway and peter ferrie. Checking for an mscoree.dll dependency is somewhat reliable, but not 100%.
    – dyasta
    Apr 8, 2013 at 22:01
  • 3
    To confirm, I made PoCs that imports mscoree.dll and even have fake .NET-looking EntryPoint, but no .COM directory.
    – Ange
    Apr 10, 2013 at 8:26
  • 2
    That's not a very constructive comment. I'm just trying to prove that COM directory is a more reliable indicator than imports or EntryPoint for this question.
    – Ange
    Apr 13, 2013 at 5:23
27

Check the dword at offset 0xE8 (32-bit) or 0xF8 (64-bit) in the PE header. If it's non-zero, it's the pointer to the CLR header. That's a managed file (you can't put random data there because direct .NET parsing support is built into XP and later, so the file won't load if the data aren't valid). The presence of mscoree.dll is not enough in itself, because the application might be doing things with managed files but not be managed itself.

5
  • 2
    This answer deserves top billing. Allows you to get the answer with NO extra tools besides your brain and a knowledge of the PE header.
    – avgvstvs
    Apr 1, 2014 at 23:00
  • 2
    The offset can be different depending whether it is a PE32 or PE32+. May 14, 2014 at 8:39
  • @Veitch, I finally edited my answer to include that. May 14, 2014 at 15:47
  • FYI This is equivalent to @broadway's answer and Karsten Hahn's answer: offset 0xe8 or 0xf8 is of the VirtualAddress of IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR.
    – pts
    Apr 2, 2020 at 16:47
  • @pts there is a significant difference: using the #define means a 32-bit program will recognise only a 32-bit program, 64-bit will only recognise 64-bit. This is why I suggested using the hard-coded values. Apr 9, 2020 at 3:48
13

Checking DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress in the data directory portion of the PE header for a nonzero value is probably the fastest way.

#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor

References:

3
  • This is the method I use. The 14th member of the Data Directory array is IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR , since it isn't listed in the post. We should discourage absolute references like that.
    – dyasta
    Apr 7, 2013 at 1:03
  • a hexeditor has no concept of data directories, though, hence the absolute offset. Apr 7, 2013 at 23:57
  • 1
    depends on the hex editor, really. 010 has PE templates.
    – broadway
    Apr 9, 2013 at 18:49
7

Manually, i prefer to observe the following signs in the PE header:

1- Presence of mscoree!_CorExeMain in the import table.

2- VirtualAddress and Size of the CLR Header/*Com Descriptor* data directory are set. Size set to 0x48.

3- Size of the Base Relocation data directory set to 0x0C i.e. Only one fixup.

Also, one minor sign is:

4- SectionAlignment set to 0x2000.

6

Try opening it in ILSpy first. It should tell you if the assembly isn't managed.

3
  • This implies that ILSpy is able to open it, which is not always the case.
    – Ange
    Apr 10, 2013 at 19:32
  • ILSpy has a specific message if the assembly isn't managed. If it fails to load for another reason I'm sure there's a different message :) Could you show me a managed assembly which ILSpy says isn't managed? Apr 10, 2013 at 19:39
  • If it fails to load the PE (with managed code or not), it gives some Mono.Cecil.PE.ImageReader error. TinyNet is using the common malware trick of having a small NumOfRVA to hide its COM directory - which fails ILSpy - even though it's using managed code.
    – Ange
    Apr 10, 2013 at 19:47
5

In addition to peter ferries answer, some python code to check for the same:

import sys

def unpack(byte):
    return sum([
        ord(b) << (8 * i) for i, b in enumerate(byte)
    ])

if len(sys.argv) == 1:
    print >> sys.stderr, "No input file given!"
else:
    myfile = sys.argv[1]

    with open(myfile, "r") as f:
        f.seek(0x3c)
        peoffset = unpack(f.read(2))
        optoffset = peoffset + 24
        f.seek(optoffset)
        magic = f.read(2)
        offset = -1
        if magic == '\x0b\x01':
            offset = 208 + optoffset
        else:
            offset = 224 + optoffset
        f.seek(offset)
        clr_address = unpack(f.read(4))
        if clr_address == 0:
            print "no managed executable"
        else:
            print "managed executable"
4

The fastest heuristic method I found so far was to check whether it imports mscoree.dll.

In the version resource of mscoree.dll Microsoft calls it: Microsoft .NET Runtime Execution Engine.

5
  • 1
    No, this won't work, as many other executables also import from mscoree.dll that are not managed assemblies.
    – Abel
    Dec 19, 2013 at 15:05
  • @Abel examples please.
    – 0xC0000022L
    Apr 22, 2021 at 14:50
  • 1
    it's 8 years ago :D, but let's try: mscoree.dll could be linked by any compiler (just create a C++ prj) without it becoming a CLR binary.The inverse is also true, some .NET binaries do not have a link to mscoree.dll. And apparently, it may be mscoreei.dll in some cases. I'm not sure about reference assemblies. I'd rely on PE headers, which will give you the .NET version info and CLR.
    – Abel
    Apr 26, 2021 at 13:37
  • Thanks @Abel ... much appreciated. I am actually really interested (again) in this topic, that's why I asked.
    – 0xC0000022L
    Apr 26, 2021 at 13:40
  • 1
    I see :). That said, I just checked all loaded binaries that reference mscoree.dll in-memory. The exception on my machine seems to be procexp64.exe, which has a reference to mscoree.dll but is not a CLI assembly. Everything else follows the rule "if it has mscoree.dll, then it is .NET".
    – Abel
    Apr 26, 2021 at 13:42
4

CorFlags.exe is a utility that ships with the Windows SDK. Execute that passing in the name of the file in question and if it is a managed file it will tell you what version of the Dotnet framework it uses and whether the file is all managed code or mixed mode, whether it is signed, and a few other useful bits of info.

1

Cross-platform C# solution

Use PEReader.HasMetadata:

using System;
using System.Linq;
using System.IO;
using System.Reflection.PortableExecutable;

public class Program
{
    public static void Main()
    {
        Console.WriteLine(Environment.OSVersion);
        
        var currentAssemblyIsManaged = IsManaged(typeof(Program).Assembly.Location) ?? false;
        Console.WriteLine($"The currently executing assembly is {(currentAssemblyIsManaged ? "" : "not ")}managed");
        
        Console.WriteLine("Here are some unmanaged assemblies:");
        var unmanagedAssemblies = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES"))
            .Split(OperatingSystem.IsWindows() ? ';' : ':')
            .Select(path => Path.GetDirectoryName(path))
            .Distinct()
            .SelectMany(directory => Directory.GetFiles(directory))
            .Where(file => IsManaged(file) == false);
        foreach (var path in unmanagedAssemblies)
        {
            Console.Write('\t');
            Console.WriteLine(path);
        }
    }
    
    static bool? IsManaged(string path)
    {
        try
        {
            using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
            using var peReader = new PEReader(stream);
            return peReader.HasMetadata;
        }
        catch
        {
            // Probably not an assembly, but maybe the file is merely inaccessible at the moment?
            return null;
        }
    }
}

.NET Fiddle

Output from .NET Fiddle:

Unix 5.15.0.1024
The currently executing assembly is managed
Here are some unmanaged assemblies:
    /usr/share/dotnet/shared/Microsoft.NETCore.App/7.0.5/libmscordbi.so
    /usr/share/dotnet/shared/Microsoft.NETCore.App/7.0.5/libcoreclrtraceptprovider.so
    /usr/share/dotnet/shared/Microsoft.NETCore.App/7.0.5/libcoreclr.so
    /usr/share/dotnet/shared/Microsoft.NETCore.App/7.0.5/libmscordaccore.so
    /usr/share/dotnet/shared/Microsoft.NETCore.App/7.0.5/libSystem.IO.Compression.Native.so
    /usr/share/dotnet/shared/Microsoft.NETCore.App/7.0.5/libclrgc.so
    /usr/share/dotnet/shared/Microsoft.NETCore.App/7.0.5/libclrjit.so

Output on a Windows machine:

Microsoft Windows NT 10.0.22621.0
The currently executing assembly is managed
Here are some unmanaged assemblies:
        C:\Users\____\bin\Debug\net7.0\ConsoleApp22.exe
        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.5\clretwrc.dll
        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.5\clrgc.dll
        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.5\clrjit.dll
        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.5\coreclr.dll
        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.5\createdump.exe
        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.5\hostpolicy.dll
        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.5\Microsoft.DiaSymReader.Native.amd64.dll
        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.5\mscordaccore.dll
        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.5\mscordaccore_amd64_amd64_7.0.523.17405.dll
        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.5\mscordbi.dll
        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.5\mscorrc.dll
        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.5\msquic.dll
        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.5\System.IO.Compression.Native.dll

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.