1
votes

I have created this dll in Go with an exported function which works fine while I'm calling it with rundll32 or from a c++ executable with the loadlbraryA. But I get an error when calling from c++ dll which I tried to read GetLastError() And it says cannot find the module you are calling. I tried every possible way to give the full path to loadlibraryA but the same error happens and the handle to Go DLL is null every time. Also, I examined the Go DLL, and the Test function is exported properly.

In short, It works in calling Test():

  • From rundll32
  • From c++ main() and loadlibrary

And doesn't work in Test():

  • From c++ DllMain() and loadlibrary
  • From c++ DllMain() and loadlibrary directly called from kernel32.dll
  • From c++ DllMain() and a ThreadFunction created in DllMain()

Folder structure

ProjectFolder
-- main.go
-- dllmain.h
-- dllmain.go

Build with

go build -buildmode=c-shared -o my-go-library.dll main.go

Call example dll

rundll32 my-go-library.dll Test

Working source

main.go:

package main

import "C"

import (
    "syscall"
    "time"
    "unsafe"
)

//export Test
func Test() {
    MessageBoxPlain("TestMsgbox","Test")
    main()
}

//export OnProcessAttach
func OnProcessAttach() {
    MessageBoxPlain("OnAttachMsgbox","OnAttach")
    Test()
}

// MessageBox of Win32 API.
func MessageBox(hwnd uintptr, caption, title string, flags uint) int {
    ret, _, _ := syscall.NewLazyDLL("user32.dll").NewProc("MessageBoxW").Call(
        uintptr(hwnd),
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(caption))),
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))),
        uintptr(flags))

    return int(ret)
}

// MessageBoxPlain of Win32 API.
func MessageBoxPlain(title, caption string) int {
    const (
        NULL  = 0
        MB_OK = 0
    )
    return MessageBox(NULL, caption, title, MB_OK)
}

func main() {}

dllmain.h:

#include <windows.h>

void OnProcessAttach(HINSTANCE, DWORD, LPVOID);

typedef struct {
    HINSTANCE hinstDLL;  // handle to DLL module
    DWORD fdwReason;     // reason for calling function // reserved
    LPVOID lpReserved;   // reserved
} MyThreadParams;

DWORD WINAPI MyThreadFunction(LPVOID lpParam) {
    MyThreadParams params = *((MyThreadParams*)lpParam);
    OnProcessAttach(params.hinstDLL, params.fdwReason, params.lpReserved);
    free(lpParam);
    return 0;
}

BOOL WINAPI DllMain(
    HINSTANCE _hinstDLL,  // handle to DLL module
    DWORD _fdwReason,     // reason for calling function
    LPVOID _lpReserved)   // reserved
{
    switch (_fdwReason) {
    case DLL_PROCESS_ATTACH:
            // Initialize once for each new process.
        // Return FALSE to fail DLL load.
        {
            MyThreadParams* lpThrdParam = (MyThreadParams*)malloc(sizeof(MyThreadParams));
            lpThrdParam->hinstDLL = _hinstDLL;
            lpThrdParam->fdwReason = _fdwReason;
            lpThrdParam->lpReserved = _lpReserved;
            HANDLE hThread = CreateThread(NULL, 0, MyThreadFunction, lpThrdParam, 0, NULL);
            // CreateThread() because otherwise DllMain() is highly likely to deadlock.
        }
        break;
    case DLL_PROCESS_DETACH:
        // Perform any necessary cleanup.
        break;
    case DLL_THREAD_DETACH:
        // Do thread-specific cleanup.
        break;
    case DLL_THREAD_ATTACH:
        // Do thread-specific initialization.
        break;
    }
    return TRUE; // Successful.
}

dllmain.go:

package main

//#include "dllmain.h"
import "C"

C++ DLL

Now the c++ dll source which calls Test from my-go-library.dll:

#include "pch.h"
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <winternl.h>
#include <malloc.h>
#include <intrin.h>
#include <Windows.h>
#include <winternl.h>
#include <malloc.h>
#include <ostream>
#include <iostream>
#include <string>

using namespace std;

typedef int(__stdcall* TestFromGo)();

void Error()
{
    wchar_t err[256];
    memset(err, 0, 256);
    FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), err, 255, NULL);
    int msgboxID = MessageBoxW(NULL,err,(LPCWSTR)L"Error",MB_OK);
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    MessageBoxA(0, "The C++ DLL", "title", 0);
    
    HINSTANCE hGetProcIDDLL = LoadLibraryA("C:\\User\\.....path to go dll.....\\my-go-library.dll");
    if (!hGetProcIDDLL) {
        Error();
        MessageBoxA(0, "could not load the dynamic library", "Test", 0);
    }
    TestFromGo TestFunction = (TestFromGo)GetProcAddress(hGetProcIDDLL, "Test");
    if (!TestFunction) {
        MessageBoxA(0, "could not locate the function", "Test", 0);
    }

    TestFromGo();
}

extern "C" __declspec(dllexport) void jojo()
{
    DllMain(0, 1, 0);
}

Calling the c++ dll

Now this one supposed to call jojo then DllMain then calling my-go-library.dll and Test

rundll32 theCiplasplas.dll jojo
1
You cannot and should not call LoadLibrary from DllMain.dxiv
@dxiv I also have tried calling loadlibrary directly from kernel32.dll in DllMain and the same thing happens. Should I create a thread in DllMain and call loadlibrary in that thread function?0_o
Are you mixing 32 and 64 bit DLL by any chance?rustyx
@rustyx I have examined the NT_HEADER of Go dll with PPEE and it says AMD64 also my visual studio setting is on x64.0_o
Then the only thing remains is what was discussed. Calling certain things while inside DllMain can lead to undefined behavior, which means anything can happen, including the error you've experienced.rustyx

1 Answers

4
votes

I can't help with Golang. But there are problems in your DllMain. There are only a limited set of API functions allowed in DllMain.

The Dynamic-Link Library Best Practices explicitly states that you should not create a thread in DllMain:

You should never perform the following tasks from within DllMain:

  • Call CreateThread. Creating a thread can work if you do not synchronize with other threads, but it is risky.
  • Call LoadLibrary or LoadLibraryEx (either directly or indirectly). This can cause a deadlock or a crash

So it might work when you use rundll32, it might not work if you load your DLL in another way.

Edit : Added excerpt about LoadLibrary