taviso / loadlibrary

Porting Windows Dynamic Link Libraries to Linux
GNU General Public License v2.0
4.34k stars 378 forks source link

Use the tool to port custom dll #28

Open j4inam opened 7 years ago

j4inam commented 7 years ago

Hi. I'm trying to use this tool to port EPANET's programming toolkit (epanet2.dll) to linux using Ubuntu. Any guidelines on how to go about with it ? Find the dll here: https://www.epa.gov/sites/production/files/2014-06/en2toolkit.zip

taviso commented 7 years ago

I took a quick look, this looks like a good candidate to get working on Linux, the imports and api is simple, and the header looks gcc compatible. I think this wouldn't be too difficult.

You would need to do something like this...

#include "epanet2.h"

int __stdcall (*ENopenPtr)(char *, char *, char *);

int main(int argc, char **argv)
{
    struct pe_image image = {
        .entry  = NULL,
        .name   = "epanet2.dll",
    };

    if (pe_load_library(image.name, &image.image, &image.size) == false) {
        return 1;
    }

    // Handle relocations, imports, etc.
    link_pe_images(&image, 1);

    // Now you can lookup exports...
    if (get_export("ENopen", &ENopenPtr) == -1) {
        errx(EXIT_FAILURE, "failed to resolve function");
    }
    setup_nt_threadinfo(NULL);

    // Call DllMain()
    image.entry((HANDLE) 'EPAN', DLL_PROCESS_ATTACH, NULL);
...

There may be some windows apis you might need to create stubs for, it's really easy once you've done one and see how it works.

j4inam commented 7 years ago

I made these changes:

mpscript.c

#include "epanet2.h"

const char header[] =
    "function log(msg) { parseFloat('__log: ' + msg); }\n"
    "function dump(obj) { for (i in obj) { log(i); log('\\t' + obj[i]); }; }\n";
const char footer[] = {
    #include "script.h" // Generated by Makefile
    0,                  // String terminator
};

DWORD (* __rsignal)(PHANDLE KernelHandle, DWORD Code, PVOID Params, DWORD Size);

double JavaScriptLog(const char *nptr, char **endptr)
{
    if (strncmp(nptr, "__log: ", 7) == 0) {
        LogMessage("%s", nptr + 7);
        return 0;
    }
    return strtod(nptr, endptr);
}

static DWORD EngineScanCallback(PSCANSTRUCT Scan)
{
    return 0;
}

static DWORD ReadStream(PVOID this, QWORD Offset, PVOID Buffer, DWORD Size, PDWORD SizeRead)
{
    memcpy(Buffer, this + Offset, *SizeRead = MIN(strlen(this+Offset), Size));
    return TRUE;
}

static DWORD GetStreamSize(PVOID this, PQWORD FileSize)
{
    *FileSize = strlen(this);
    return TRUE;
}

int __stdcall (*ENopenPtr)(char *, char *, char *);

int main(int argc, char **argv, char **envp)
{
    PVOID StrtodPtr;
    PIMAGE_DOS_HEADER DosHeader;
    PIMAGE_NT_HEADERS PeHeader;
    HANDLE KernelHandle;
    SCAN_REPLY ScanReply;
    BOOTENGINE_PARAMS BootParams;
    SCANSTREAM_PARAMS ScanParams;
    STREAMBUFFER_DESCRIPTOR ScanDescriptor;
    ENGINE_INFO EngineInfo;
    ENGINE_CONFIG EngineConfig;
    struct pe_image image = {
        .entry  = NULL,
        .name   = "engine/epanet2.dll",
    };

    // Load the mpengine module.
    if (pe_load_library(image.name, &image.image, &image.size) == false) {
        return 1;
    }

    // Handle relocations, imports, etc.
    link_pe_images(&image, 1);

    // Fetch the headers to get base offsets.
    DosHeader   = (PIMAGE_DOS_HEADER) image.image;
    PeHeader    = (PIMAGE_NT_HEADERS)(image.image + DosHeader->e_lfanew);

    // Load any additional exports.
   // if (!process_extra_exports(image.image, PeHeader->OptionalHeader.BaseOfCode, "engine/mpengine.map")) {
     //   LogMessage("A map file is required to intercept interpreter output. See documentation.");
    //}

    // Calculate the commands needed to get export and map symbols visible in gdb.
    if (IsDebuggerPresent()) {
        LogMessage("GDB: add-symbol-file %s %#x+%#x",
                   image.name,
                   image.image,
                   PeHeader->OptionalHeader.BaseOfCode);
        LogMessage("GDB: shell bash genmapsym.sh %#x+%#x symbols_%d.o < %s",
                   image.image,
                   PeHeader->OptionalHeader.BaseOfCode,
                   getpid(),
                   "engine/mpengine.map");
        LogMessage("GDB: add-symbol-file symbols_%d.o 0", getpid());
        __debugbreak();
    }

     if (get_export("ENopen", &ENopenPtr) == -1) {
        errx(EXIT_FAILURE, "failed to resolve function");
    }
    setup_nt_threadinfo(NULL);

    // Call DllMain()
    image.entry((HANDLE) 'EPAN', DLL_PROCESS_ATTACH, NULL);
...

mpclient.c

#include "epanet2.h"

// Any usage limits to prevent bugs disrupting system.
const struct rlimit kUsageLimits[] = {
    [RLIMIT_FSIZE]  = { .rlim_cur = 0x20000000, .rlim_max = 0x20000000 },
    [RLIMIT_CPU]    = { .rlim_cur = 3600,       .rlim_max = RLIM_INFINITY },
    [RLIMIT_CORE]   = { .rlim_cur = 0,          .rlim_max = 0 },
    [RLIMIT_NOFILE] = { .rlim_cur = 32,         .rlim_max = 32 },
};

DWORD (* __rsignal)(PHANDLE KernelHandle, DWORD Code, PVOID Params, DWORD Size);

static DWORD EngineScanCallback(PSCANSTRUCT Scan)
{
    if (Scan->Flags & SCAN_MEMBERNAME) {
        LogMessage("Scanning archive member %s", Scan->VirusName);
    }
    if (Scan->Flags & SCAN_FILENAME) {
        LogMessage("Scanning %s", Scan->FileName);
    }
    if (Scan->Flags & SCAN_PACKERSTART) {
        LogMessage("Packer %s identified.", Scan->VirusName);
    }
    if (Scan->Flags & SCAN_ENCRYPTED) {
        LogMessage("File is encrypted.");
    }
    if (Scan->Flags & SCAN_CORRUPT) {
        LogMessage("File may be corrupt.");
    }
    if (Scan->Flags & SCAN_FILETYPE) {
        LogMessage("File %s is identified as %s", Scan->FileName, Scan->VirusName);
    }
    if (Scan->Flags & 0x08000022) {
        LogMessage("Threat %s identified.", Scan->VirusName);
    }
    return 0;
}

static DWORD ReadStream(PVOID this, ULONGLONG Offset, PVOID Buffer, DWORD Size, PDWORD SizeRead)
{
    fseek(this, Offset, SEEK_SET);
    *SizeRead = fread(Buffer, 1, Size, this);
    return TRUE;
}

static DWORD GetStreamSize(PVOID this, PULONGLONG FileSize)
{
    fseek(this, 0, SEEK_END);
    *FileSize = ftell(this);
    return TRUE;
}

static PWCHAR GetStreamName(PVOID this)
{
    return L"input";
}

// These are available for pintool.
BOOL __noinline InstrumentationCallback(PVOID ImageStart, SIZE_T ImageSize)
{
    // Prevent the call from being optimized away.
    asm volatile ("");
    return TRUE;
}

int __stdcall (*ENopenPtr)(char *, char *, char *);

int main(int argc, char **argv, char **envp)
{
    PIMAGE_DOS_HEADER DosHeader;
    PIMAGE_NT_HEADERS PeHeader;
    HANDLE KernelHandle;
    SCAN_REPLY ScanReply;
    BOOTENGINE_PARAMS BootParams;
    SCANSTREAM_PARAMS ScanParams;
    STREAMBUFFER_DESCRIPTOR ScanDescriptor;
    ENGINE_INFO EngineInfo;
    ENGINE_CONFIG EngineConfig;
    struct pe_image image = {
        .entry  = NULL,
        .name   = "engine/epanet2.dll",
    };

    // Load the mpengine module.
    if (pe_load_library(image.name, &image.image, &image.size) == false) {
        LogMessage("You must add the dll and vdm files to the engine directory");
        return 1;
    }

    // Handle relocations, imports, etc.
    link_pe_images(&image, 1);

    // Fetch the headers to get base offsets.
    DosHeader   = (PIMAGE_DOS_HEADER) image.image;
    PeHeader    = (PIMAGE_NT_HEADERS)(image.image + DosHeader->e_lfanew);

    // Load any additional exports.
    if (get_export("ENopen", &ENopenPtr) == -1) {
        errx(EXIT_FAILURE, "failed to resolve function");
    }

    EXCEPTION_DISPOSITION ExceptionHandler(struct _EXCEPTION_RECORD *ExceptionRecord,
            struct _EXCEPTION_FRAME *EstablisherFrame,
            struct _CONTEXT *ContextRecord,
            struct _EXCEPTION_FRAME **DispatcherContext)
    {
        LogMessage("Toplevel Exception Handler Caught Exception");
        abort();
    }

    VOID ResourceExhaustedHandler(int Signal)
    {
        errx(EXIT_FAILURE, "Resource Limits Exhausted, Signal %s", strsignal(Signal));
    }

    setup_nt_threadinfo(ExceptionHandler);

    // Call DllMain()
    image.entry((HANDLE) 'EPAN', DLL_PROCESS_ATTACH, NULL);
...

I built the project using make command and tried running ./mpclient

I get this:

mpclient: function at 0xf74c73a3 attempted to call an unknown symbol Trace/breakpoint trap (core dumped)

j4inam commented 7 years ago

Actually, I'm trying to embed EPANET- Software That Models the Hydraulic and Water Quality Behavior of Water Distribution Piping Systems, on to a linux based web server so I need the epanet's dll ported to linux

jtlz2 commented 6 years ago

@j4inam Did you ever get this working?

j4inam commented 6 years ago

@jtlz2 Nope. Not yet :(

jtlz2 commented 6 years ago

@j4inam Do you by any chance have a simple Hello World example? I am finding the supplied demo a bit over my head :\ cc @taviso

jtlz2 commented 6 years ago

@j4inam Just came across https://github.com/pleiszenburg/zugbruecke - any use?

Samuel-Bie commented 3 years ago

Hi EveryOne did u succeded integrating this lib into linux. as @jrmuizel said and example with a simple Helloworld.dll I guess would make our lives easier.

taviso commented 3 years ago

This is a tricky tool to use, but you'll have to spell out which part needs to be explained, because I don't understand the issue with this example (this is copy and pasted from earlier in the thread):

#include "epanet2.h"

int __stdcall (*ENopenPtr)(char *, char *, char *);

int main(int argc, char **argv)
{
    struct pe_image image = {
        .entry  = NULL,
        .name   = "epanet2.dll",
    };

    if (pe_load_library(image.name, &image.image, &image.size) == false) {
        return 1;
    }

    // Handle relocations, imports, etc.
    link_pe_images(&image, 1);

    // Now you can lookup exports...
    if (get_export("ENopen", &ENopenPtr) == -1) {
        errx(EXIT_FAILURE, "failed to resolve function");
    }
    setup_nt_threadinfo(NULL);

    // Call DllMain()
    image.entry((HANDLE) 'EPAN', DLL_PROCESS_ATTACH, NULL);
...

This demonstrates the absolute minimal code to use the library.

You need to know the types, prototypes and exports of the dll you want to use (i.e. you need a header file, or know how to make your own with a disassembler).

Then you do this:

  1. Call pe_load_library with a struct pe_image describing your dll.
  2. Call link_pe_images to process relocations, etc.
  3. Now you can lookup exported functions with get_export(), and call them as you would a function pointer in any native program.
  4. Some dlls will need constructors to run, so for that you need to call image.entry().

Now, if those exports have any dependencies not already handled, you might need to do some work to handle that, but these are usually pretty easy and there are lots of examples!

Happy to explain any step that's unclear!

Samuel-Bie commented 3 years ago

OK. Maybe I'm not good at c and I'm asking too much of you. But the thing is, our team wants to integrate a .dll developed in C/C++. We are thinking about generating or compiling a shared object (.so) so that it can be integrated with our environment. Any idea how to use this pack to achieve this effect.

taviso commented 3 years ago

Yes, you could use this library.

The general guidelines are that this should be a self-contained DLL, and not something very windows specific. For example, good candidates would be a math library, a video codec, a compression library, and so on.

You also need to know the prototypes and types the library uses, so you either need a header file or you know how to figure them out with a disassembler.

If this sounds like what you have, then you're in the right place.

Then I guess you just need to take the sample above, change it to match your dll, and then open a new issue if something goes wrong. Without errors or specific questions, I just don't know what to suggest.

The kind of questions I might expect are things like:

Samuel-Bie commented 3 years ago

@taviso please help me with this issue