12tall
12tall

Reputation: 101

Is it possible to load a go-dll in c-dll on Windows?

I want to inject a c-dll to a process. In the c-dll, it will load another dll which is written by golang. Here is my C code:

when the loader.dll is loaded, it will auto-load the worker.dll which is write in golang.

// loader.c  

#include <windows.h>
#include <stdio.h>
#include <memory.h>

typedef void (*StartWorker)();
HMODULE hWorker = NULL;


BOOL APIENTRY DllMain(HMODULE hModule,
                      DWORD ul_reason_for_call,
                      LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {

        hWorker = LoadLibrary("D:\\code\\toys\\worker.dll");
        if (hWorker == NULL)
        {
            exit(1);
        }
        
        StartWorker startWorker = (StartWorker)GetProcAddress(hWorker, "StartWorker");
        MessageBox(NULL, "worker starting", TEXT("Warning:"), MB_OK);
        if (startWorker == NULL)
        {
            MessageBox(NULL, "error", TEXT("Warning:"), MB_OK);
            exit(1);
        }
        startWorker();
        MessageBox(NULL, "worker started", TEXT("Warning:"), MB_OK);
        FreeLibrary(hWorker);
        break;
    }
    case DLL_PROCESS_DETACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    default:
        break;
    }
    return TRUE;
}

and the go code is:

// worker.go

package main

/*
#include <stdlib.h>
#include <windows.h>
*/
import "C"

//export StartWorker
func StartWorker {
    C.MessageBox(nil, C.CString("Hello Worker"), C.CString("Warning:"), 0)
}
func main() {
}

I have compiled them in MinGW-w64. When the loader.dll is trying to call StartWorker() in worker.dll, the process didn't show the MessageBox. After I rewrite the worker.dll in c, everything works well. And it is also ok when I call the StartWorker() by the follow code:

#include <windows.h>
#include <stdio.h>
#include <memory.h>

typedef void (*StartWorker)();

int main()
{
    char *dllPath = "D:\\code\\toys\\loader.dll";
    HMODULE hWorker = NULL;
    hWorker = LoadLibrary(dllPath);
    StartWorker startWorker = (StartWorker)GetProcAddress(hWorker, "StartWorker");
    if (startWorker == NULL)
    {
        MessageBox(NULL, "dllPath", TEXT("Warning:"), MB_OK);
        exit(1);
    }
    startWorker();
    MessageBox(NULL, "target", TEXT("Warning:"), MB_OK);
    FreeLibrary(hWorker);
    return 0;
}

I wonder if there is any thing conflict with go-runtime?

Upvotes: 2

Views: 2268

Answers (2)

12tall
12tall

Reputation: 101

I found a way to implement the function. I just wrote some new code as follows. They are working fine now.

http.go

package main

import "C"
import (
    "fmt"
    "net/http"
)

func sayHello(w http.ResponseWriter, r *http.Request) {
    _, _ = w.Write([]byte("Hello World!"))
}

//export StartHttp
func StartHttp() {
    http.HandleFunc("/", sayHello)

    err := http.ListenAndServe("127.0.0.1:9999", nil)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    fmt.Printf("Listening: http://127.0.0.1:9000")
}

func main() {
    // StartHttp();
}

loader.c

#include <windows.h>
#include <stdio.h>

// https://forum.pellesc.de/index.php?topic=4725.0
#ifdef __GNUC__
HANDLE k __attribute__((section(".shared"), shared)) = NULL;
#endif
#ifdef _MSC_VER
#pragma data_seg(".shared")
HANDLE k = NULL;
#pragma data_seg()
#pragma comment(linker, "/section:.shared,RWS")
#endif

typedef void (*StartHttp)();

HMODULE hHttp = NULL;

BOOL APIENTRY DllMain(HMODULE hModule,
                      DWORD ul_reason_for_call,
                      LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {
        hHttp = LoadLibrary("http.dll");
        StartHttp startWorker = (StartHttp)GetProcAddress(hHttp, "StartHttp");
        k = startWorker;  
        // do not execute the function here!
        break;
    }

    case DLL_PROCESS_DETACH:
    {
        FreeLibrary(hHttp);
        break;
    }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    default:
        break;
    }
    return TRUE;
}

HANDLE GetHttpStarter()
{
    return k;
}

client.c

#include <windows.h>
#include <stdio.h>
#include <memory.h>

typedef void (*StartHttp)();
typedef HMODULE (*GetHttpStarter)();

int main()
{
    HMODULE hLoader = NULL;

    hLoader = LoadLibrary("loader.dll");
    GetHttpStarter getHttpStarter = (GetHttpStarter)GetProcAddress(hLoader, "GetHttpStarter");
    StartHttp startHttp = (StartHttp)getHttpStarter();
    startHttp();

    printf("^^\n");
    FreeLibrary(hLoader);
    return 0;
}

> go build -buildmode=c-shared -o http.dll .\http.go
> gcc .\loader.c -shared -o loader.dll
> gcc .\client.c -o client.exe
> .\client.exe

The most important thing I've modified is that only load the http.dll(go) in loader.dll(c)'s DllMain() without execute http.dll(go)'s functions, and store those functions address in a shared memory. so I can get the function address of http.dll(go) by calling the export funtion GetHttpStarter() of loader.dll(c). Looks complex, but it do work :)


I also have tested the dll injetion, as the result, I can host a http service on notepad.exe now :D

Upvotes: 0

raoyc
raoyc

Reputation: 191

Test fine, maybe you called Microsoft-Windows-Only api. Try to change MessageBox() (it using win32 user32.dll) to Sum() to test c-dll which called go-dll.

Here is go file to gen go-dll // gosum.go

package main

/*
#include <stdlib.h>
*/
import "C"

//export Sum
func Sum(a int32, b int32) int32 {
    return a + b
}

func main() {
}

Go build c-shared Windows dll:

go build -ldflags "-s -w" -buildmode=c-shared -o gosum.dll

you can write test file in c/cpp:

#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include "gosum.h"

int main() {
    printf("Sum(1,2)=%d\n", Sum(1,2));
    return 1;
}

Compiled it in MinGW-w64

g++ -c test_gosum.cpp -o test_gosum.o -g -std=c++11
g++ test_gosum.o gosum.dll -o test_gosum.exe -g -std=c++11
.\test_gosum.exe

enter image description here

Then to build loader.dll in Visual Studio which called gosum.dll

// copy go-dll CGO gen gosum.h file to project, may be you need remove some error code

// gosum.h file:

/* Code generated by cmd/cgo; DO NOT EDIT. */

/* package gosum */


#line 1 "cgo-builtin-export-prolog"

#include <stddef.h> /* for ptrdiff_t below */

#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H

#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char* p; ptrdiff_t n; } _GoString_;
#endif

#endif

/* Start of preamble from import "C" comments.  */


#line 3 "gosum.go"

#include <stdlib.h>

#line 1 "cgo-generated-wrapper"


/* End of preamble from import "C" comments.  */


/* Start of boilerplate cgo prologue.  */
#line 1 "cgo-gcc-export-header-prolog"

#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H

typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef float GoFloat32;
typedef double GoFloat64;

/*
  static assertion to make sure the file is being used on architecture
  at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*) == 64 / 8 ? 1 : -1];

#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
typedef void* GoMap;
typedef void* GoChan;
typedef struct { void* t; void* v; } GoInterface;
typedef struct { void* data; GoInt len; GoInt cap; } GoSlice;

#endif

/* End of boilerplate cgo prologue.  */

#ifdef __cplusplus
extern "C" {
#endif
    extern GoInt32 Sum(GoInt32 a, GoInt32 b);
#ifdef __cplusplus
}
#endif

// pch.h file(gen by vs, need add something):

#define PCH_H
#include "framework.h"
#endif

// ----- added
#ifdef IMPORT_DLL
#else
#define IMPORT_DLL extern "C" _declspec(dllimport)
#endif

IMPORT_DLL int get_sum_by_another_dll(int a, int b);

// ----- added

// dllmain.cpp file(gen by vs):

#include "pch.h"

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

// loader.cpp file:

#include "pch.h"
#include <stdlib.h>
#include <windows.h>
#include "gosum.h"  // also need add `gosum.dll` to VS project file
using namespace std;

typedef int (CALLBACK* GOSUMFUNC)(GoInt32 a, GoInt32 b);

int __stdcall get_sum_by_another_dll(int a, int b)
{
    HMODULE h = LoadLibrary(TEXT("gosum.dll"));
    if (NULL == h || INVALID_HANDLE_VALUE == h) {
        return 0;
    }
    GOSUMFUNC go_dll_sum = (GOSUMFUNC)GetProcAddress(h, "Sum");
    if (go_dll_sum) {
        return go_dll_sum(a, b);
    }
    return 1;
}

then compiled them in VS, you will get loader.dll.

there gosum.dll is go-dll, loader.dll is c-dll compiled by VisualStudio which called gosum.dll.

enter image description here

you can try call them in golang or clang.

// demo.go test loader.dll and gosum.dll with golang, also need to copy gosum.dll and loader.dll in same dir.

package main

import (
    "fmt"
    "log"
    "syscall"
)

func main() {
    h, e := syscall.LoadLibrary("loader.dll")   //Make sure this DLL follows Golang machine bit architecture (64-bit in my case)
    if e != nil {
        log.Fatal(e)
    }
    defer syscall.FreeLibrary(h)
    proc, e := syscall.GetProcAddress(h, "get_sum_by_another_dll") //One of the functions in the DLL
    if e != nil {
        log.Fatal(e)
    }
    var a int32 = 2
    var b int32 = 5
    ret, _, err := syscall.Syscall(proc, 2, uintptr(a), uintptr(b), 0)  //Pay attention to the positioning of the parameter
    fmt.Println(ret)
    fmt.Println(err)
}

go run it you will get result: get_sum_by_another_dll(2,5) = 7

enter image description here

// callloader.cpp test loader.dll and gosum.dll with c/cpp, also need to copy gosum.dll and loader.dll in same dir.

#include <iostream>
#include <Windows.h>

int main()
{
    HINSTANCE hDllInst;
    hDllInst = LoadLibrary(L"loader.dll");
    typedef int(*SUMFUNC)(int a, int b);
    SUMFUNC dll_sum_fun = (SUMFUNC)GetProcAddress(hDllInst, "get_sum_by_another_dll"); 
    std::cout << dll_sum_fun(1, 2);
}

then compiled it in VS, run ./callloader.exe, you will get result: get_sum_by_another_dll(1,2) = 3

enter image description here

Upvotes: 2

Related Questions