3F / DllExport

.NET DllExport with .NET Core support (aka 3F/DllExport aka DllExport.bat)
MIT License
937 stars 131 forks source link

Manual Map issue #173

Closed frostiest closed 3 years ago

frostiest commented 3 years ago

Just curious if you know why all manual map libraries seem to fail and a possible way to make them work? The goal is embed the .net dll inside an unmanaged exe and load the dll from bye array/memory instead of file.

an example library is https://github.com/strivexjun/MemoryModulePP

3F commented 3 years ago

As I can see they use DllMain like (*DllEntry)((HINSTANCE)code, DLL_PROCESS_ATTACH, 0); that will not work because you need init CLR first to init your managed module: https://github.com/3F/DllExport/issues/45#issuecomment-317802099

frostiest commented 3 years ago

Thank you for responding, I want to elaborate is I may

I modified MemoryModulePP library (and others) to supposedly successfully map the dll in memory, namely I removed the dllentry call and commented out this check `

GetNativeSystemInfo(&sysInfo);
alignedImageSize = AlignValueUp(lastSectionEnd, sysInfo.dwPageSize);
//if (alignedImageSize != AlignValueUp(lastSectionEnd, sysInfo.dwPageSize)) {

`

which provided a valid base address/return. Then used their export function "MemoryGetProcAddress" to get the export from the C# dll I want to call, and that's where things crash. The address appears valid, The debugger says it crashes somewhere in mscoree.dll.

I also tried the old school method of loading the clr like you mentioned before doing the above

int loadclr()
{
    ICLRMetaHost* metaHost = NULL; //Declare our CLR Meta Host value as a NULL
    ICLRRuntimeInfo* runtimeInfo = NULL; //Declare our CLR Runtime Info as a Null
    ICLRRuntimeHost* runtimeHost = NULL; //Delcare our CLR HOST as a NULL

    if (CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&metaHost) == S_OK) //If Creating CLR Instance with follow parameters is successful
        if (metaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (LPVOID*)&runtimeInfo) == S_OK) //If getting Runtime version is successful
            if (runtimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID*)&runtimeHost) == S_OK) //If getting the interface with the follow parameters is successful
                if (runtimeHost->Start() == S_OK) //Start the CLR (If it is successful)
                {
                    DWORD pReturnValue; //Declare our return value as a DWORD

                    //Invoke our method through CLR host using following parameters
                    //runtimeHost->ExecuteInDefaultAppDomain(L"Path\\To\\Our\\DLL", L"TestDLL.Class1", L"TestMethod", L"It works!!", &pReturnValue);

                    //OPTIONAL: You can keep the CLR Opened depending on your needs
                    runtimeInfo->Release();
                    metaHost->Release();
                    runtimeHost->Release();
                }
    return 0;
}

that didn't do much. So my question is what is LoadLibrary doing that these mapping librabries aren't that's making loading the C# dll unsuccessful? They're

and is there a way I can preload the CLR or do anything to avoid having to load from disk first(or use LoadLibrary for that matter) ?

Thanks

frostiest commented 3 years ago

Guess not, ok thank you for share and project, have good day.

3F commented 3 years ago

Not sure about completeness for the mentioned equivalent to the LoadLibrary(), and unfortunately I don't have time/health to review it today, but first of all,

RVAs in export table will point exactly to export stubs --> that points to --> v-table slots !

Each slot contains method token which will be replaced with the address of a marshaling point providing unmanaged entry to managed method. All this must be processed with actual addresses at runtime.

What about unmanaged starter for CLR,

It must be located in .text section where vtfixup, that is ~

clr header + ... + export stubs + vtfixup + clr starter

Its RVA should be assigned to PE AddressOfEntryPoint and my highlights above looks correct:

((DllEntryProc)(LPVOID)(code + result->headers->OptionalHeader.AddressOfEntryPoint))

@frostiest, but as I voiced, you need init CLR ~"environment" (I do not mean clr hosting way like from your "old school" example above, it's different to the task) before processing/calculating related to it.

Also note v-table should be located in other .sdata section because of RW

tapika commented 3 years ago

@frostiest I'm not sure what exactly you're doing, but suspect easiest approach would be to make small C# library "loader", which in a turn would use Assembly.Load and perform assembly loading from ram - I guess this is equivalent to C++ MemoryModulePP. This approach however will not work on all assemblies - some assemblies might need to be loaded from disk in order to work.

I'm also not sure about debugging of such in-ram loaded dll's, as debugger will not be aware of any newly loaded dll unless you explicitly specify it somehow to debugger.

But I'm actually interested on what you want to achieve and what was the last problem.

I by myself want to fully release .net framework assembly without any good result. Don't understand why there aren't any FreeLibrary for C#. I've slightly played around with .net core - situation seems to be better, but I haven't yet tried C++ / mixed mode clr dlls.

Can you tell me bit more on where you've ended up with your experiments ?

tapika commented 3 years ago

Btw, MemoryModulePP perform something similar to https://github.com/nickcano/ReloadLibrary

One approach is to actually allow PE loader perform what it wants to perform, but intercept windows API calls, like LoadLibrary, GetProcAddress, FreeLibrary to manually control what you do with these dlls, and remove file locking.

Using minhook in similar manner to: https://github.com/tapika/stacktrace/blob/develop/src/exception_handler.cpp#L204

You can intercept windows api.

But main question - is what loader is performing, and how to "reset" loader state back to "not loaded" state.

I guess with C++ it's easy - existence of MemoryModulePP and ReloadLibrary tells me that this is something that was already achieved. With .net it's bit more complex, as need to interspect what loader / clr includes.

At the moment clrmd - https://github.com/microsoft/clrmd knows everything about state of C# - I was thinking maybe later on try to debug what it knows that I don't know.

Either way, please inform me if you know something more that I don't know.

tapika commented 3 years ago

Here are bit more links on IAT problem, which I have tried to analyze by myself without any good result:

https://stackoverflow.com/questions/5005409/exception-with-resolving-assemblies-attempt-to-load-an-unverifiable-executable

https://stackoverflow.com/questions/2945080/how-do-i-dynamically-load-raw-assemblies-that-contains-unmanaged-codebypassing

https://web.archive.org/web/20130906220206/https://connect.microsoft.com/VisualStudio/feedback/details/97801/loading-mixed-assembly-with-assembly-load-byte-throw-exception-changed-behaviour

https://stackoverflow.com/questions/5005409/exception-with-resolving-assemblies-attempt-to-load-an-unverifiable-executable

https://tech-zealots.com/malware-analysis/journey-towards-import-address-table-of-an-executable-file/

To my best understanding IAT table refers to external dll. I'm not sure why which reference is needed, but apparently C++ linker generates it if you have C++/cli in use and you use some sort of global symbols. There are some attempts to make data as appdomain specific (now it's .net framework specific, not necessarily applicable to .net core) using __declspec(appdomain) - but this goes deeper in .net framework appdomain non-sense. (C# has globals per appdomain, C++ has globals per process, and if you start second appdomain - you will need to re-initialize all C# stuff again - eats RAM and produces a lot of complexities)

I probably need to try how this works on .net core level, as things are different in there.

(.net framework is officially deprecated by Microsoft by now).

tapika commented 3 years ago

FYI also: https://dev.to/thebuzzsaw/building-a-better-library-loader-in-c-part-1-1446

Interesting reading, but suspect Conary somehow walks in that direction.

frostiest commented 3 years ago

I no find solution but I wanted to link you to similar project that also doesn't fix my issue at all but DOES have the feature of DllEntry so can simply use LoadLibrary("test.dll"); and ability to export other methods.

https://github.com/seanrussell2/DotNetExport

example use ClassLibrary1.dll C# `

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ClassLibrary1
{
    public class Class1
    {
        [System.Runtime.InteropServices.DllMain]
        static void DllMain(IntPtr hModule)
        {
            MessageBox.Show("Hi!");
        }
    }
}

Tool DotNetExport.exe ClassLibrary1.dll

Test (c++) HMODULE A = LoadLibraryA("ClassLibrary1.dll");

maybe can copy if care about DllEntry.