DNNE icon indicating copy to clipboard operation
DNNE copied to clipboard

Provide example for strings

Open david-bouyssie opened this issue 3 years ago • 11 comments

It would be nice to have an example for strings as function parameters or returned value.

Thanks, amazing project!

david-bouyssie avatar Apr 12 '21 16:04 david-bouyssie

@david-bouyssie Like this one?:

       [UnmanagedCallersOnlyAttribute(EntryPoint = "GetData")] //functions name for C interface
        public static int GetData([DNNE.C99Type("const char**")] System.IntPtr StringText, [DNNE.C99Type("int*")] System.IntPtr intNumber, [DNNE.C99Type("double*")] System.IntPtr doubleNumber)
        {
            var returnValue = CSharpLibrary.GetData(out string strText, out int iNumber, out double dNumber);
            Marshal.WriteIntPtr(StringText, Marshal.StringToHGlobalAnsi(strText));
            Marshal.WriteInt32(intNumber, iNumber);
            double[] val = new double[1];
            val[0] = dNumber;
            Marshal.Copy(val, 0, doubleNumber, 1);
            return returnValue;
        }

        [UnmanagedCallersOnlyAttribute(EntryPoint = "SetData")] //functions name for C interface
        public static int SetData([DNNE.C99Type("const char*")] System.IntPtr StringText, int intNumber, double doubleNumber)
        {
            var returnValue = CSharpLibrary.SetData(Marshal.PtrToStringAnsi(StringText), intNumber, doubleNumber);
            return returnValue;
        }

AlexGer123 avatar Apr 13 '21 15:04 AlexGer123

@AlexGer123 That is a solid example - thanks! It would be great to see the project that is a part of. Any chance you can share it?

AaronRobinsonMSFT avatar Apr 13 '21 16:04 AaronRobinsonMSFT

@AaronRobinsonMSFT I have built some simply examples to provide how it works. I can share the examples tomorrow. :)

AlexGer123 avatar Apr 13 '21 16:04 AlexGer123

Great, thank you @AlexGer123

david-bouyssie avatar Apr 14 '21 07:04 david-bouyssie

Small examples Interoperability C <-> C#

DNNE_TO_ANSI_C.zip

And this one for StringList from ANSI-C To C#: https://stackoverflow.com/questions/66932663/marshalling-string-array-from-ansi-c-to-string-in-c-sharp/66933877#66933877

SDK 5.0.202 x86 is needed and the build also must be build as x86. Some settings in "InteroperabilityTest.csproj" you should check...

Maybe that is enough. :)

AlexGer123 avatar Apr 14 '21 15:04 AlexGer123

@AlexGer123 thanks!

Two questions:

  • what is the purpose of char sResultText[OPTO_GLOBAL_MAX_PROCESSING_STRING_DATA_SIZE] = {""};?
  • what is preventing from targeting x64?

david-bouyssie avatar Apr 14 '21 15:04 david-bouyssie

what is the purpose of char sResultText[OPTO_GLOBAL_MAX_PROCESSING_STRING_DATA_SIZE] = {""};?

To copy the result like "strcpy(sResultText, strTextOut);" size for "OPTO_GLOBAL_MAX_PROCESSING_STRING_DATA_SIZE" freely selectable.

what is preventing from targeting x64?

For the build you must select. image

AlexGer123 avatar Apr 14 '21 15:04 AlexGer123

I can contribute the opposite example -- calling a function in C/C++/D etc that expect a const char* or equivalent 😃

Repo Here: https://github.com/GavinRay97/ReaperDNNE

The scenario for me is that the managed C# .dll is being loaded by an external, closed-source C++ program, which calls extension/addon .dll entrypoint functions with the following struct:

struct reaper_plugin_info_t
{
	int caller_version;
	void* hwnd_main;
	int (*Register)(const char* name, void* infostruct);
	void* (*GetFunc)(const char* name);
};

Where reaper_plugin_info_t->GetFunc() takes a function name, looks up the function's C++ address, and returns a function pointer. Here's a little C stub loader which simulates the 3rd party program loading my .dll:

https://github.com/GavinRay97/ReaperDNNE/blob/master/include/example_loader.c

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

#define EXPORTED_ENTRYPOINT_FN_NAME "ReaperPluginEntry"
#define DLL_NAME "../bin/Debug/net6.0/win-x64/ReaperDNNENE.dll"

typedef struct {
  int caller_version;
  HWND hwnd_main;
  int (*Register)(const char *name, void *infostruct);
  void *(*GetFunc)(const char *name);
} reaper_plugin_info_t;

void ShowConsoleMsg(const char *msg) {
  printf("[ShowConsoleMsg]: %s \n", msg);
  return;
}

void *GetFunc(const char *name) {
  printf("GetFunc called with function name: %s \n", name);
  if (strcmp(name, "ShowConsoleMsg") == 0)
    return &ShowConsoleMsg;
  return NULL;
}

int main() {
  HMODULE hTargetDll = LoadLibraryA(DLL_NAME);
  if (!hTargetDll) {
    printf("[!] LoadLibraryA failed. Error: %lu\n", GetLastError());
    return 0;
  }
  printf("[+] Target DLL base address: 0x%p\n", hTargetDll);

  FARPROC entrypointFuncPtr =
      GetProcAddress(hTargetDll, EXPORTED_ENTRYPOINT_FN_NAME);
  void (*ReaperPluginEntry)(void *, reaper_plugin_info_t *) =
      (void (*)(void *, reaper_plugin_info_t *))entrypointFuncPtr;

  reaper_plugin_info_t rec;
  rec.caller_version = 1;
  rec.hwnd_main = NULL;
  rec.GetFunc = &GetFunc;
  rec.Register = NULL;

  ReaperPluginEntry(NULL, &rec);
  printf("[+] done\n");

  return 1;
}

Now to write a C# .dll which gets loaded by this C++ program, and use reaper_plugin_info_t->GetFunc() to ask for the pointer to ShowConsoleMsg then call it with a string, is like this:

https://github.com/GavinRay97/ReaperDNNE/blob/master/Program.cs

using System;
using System.Runtime.InteropServices;

namespace ReaperDNNE
{
  public unsafe static class MyReaperPlugin
  {

    private const string ReaperPluginInfoStructString = @"
      struct reaper_plugin_info_t
      {
        int caller_version;
        void* hwnd_main;
        int (*Register)(const char* name, void* infostruct);
        void* (*GetFunc)(const char* name);
      };
    ";

    public struct ReaperPluginInfo
    {
      public int caller_version;
      public IntPtr hwnd_main;
      public delegate* unmanaged[Cdecl]<sbyte*, void*, int> Register;
      public delegate* unmanaged[Cdecl]<sbyte*, void*> GetFunc;
    }

    [UnmanagedCallersOnly]
    [DNNE.C99DeclCode(ReaperPluginInfoStructString)]
    public static int ReaperPluginEntry(IntPtr hInstance, [DNNE.C99Type("struct reaper_plugin_info_t*")] ReaperPluginInfo* rec)
    {
      var showConsoleMsgStrPtr = Marshal.StringToHGlobalAnsi("ShowConsoleMsg");
      var ShowConsoleMsg = (delegate* unmanaged[Cdecl]<sbyte*, void>)
        rec->GetFunc((sbyte*)showConsoleMsgStrPtr);
      Marshal.FreeHGlobal(showConsoleMsgStrPtr);

      var helloMsgStrPtr = Marshal.StringToHGlobalAnsi("Hello From C#");
      ShowConsoleMsg((sbyte*)helloMsgStrPtr);
      Marshal.FreeHGlobal(helloMsgStrPtr);

      return 1;
    }
  }
}

If you run dotnet build on the C# project, with the following attributes (or similar):

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
    <EnableDynamicLoading>true</EnableDynamicLoading>
    <DnneAddGeneratedBinaryToProject>true</DnneAddGeneratedBinaryToProject>
    <DnneMSBuildLogging>high</DnneMSBuildLogging>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="DNNE" Version="1.*" />
  </ItemGroup>
</Project>

And then change #define DLL_NAME to be the path to your DNNE generated .dll (with the NE suffix), then compile the C .dll loader/symbol caller program and execute it, you should get:

image

I hope this can be helpful to someone =) This work on DNNE is awesome, coolest thing I've seen in a while:


Credits/Acknowledgements:

  • Proper conversion of the C struct (particularly the function pointers to delegates) to C# code
    • Tanner Gooding et al's https://github.com/microsoft/ClangSharp tool and his support on Discord
  • DLL Loading in C
    • https://blog.whtaguy.com/2020/04/calling-arbitrary-functions-in-exes.html
  • Using Marshal.StringToHGlobalAnsi() and freeing afterwards to properly convert managed string to sbyte*
    • User alexrp from Dotnet Foundation, on the DotNetEvolution Discord server
  • Being made aware that DNNE exists
    • Some super cool guy on a Reverse Engineering Discord server
  • This library
    • Aaron Robinson

GavinRay97 avatar Apr 14 '21 21:04 GavinRay97

I added libnethost.lib and InteroperabilityTestNE.lib to the C++ project -> Linker -> Input, however I'm getting a link error: Unresolved external symbol "int __stdcall SetData(Char cost *, int, double)". What am I missing?

hsnmck avatar Apr 19 '21 21:04 hsnmck

@hsnmck We need more information. Maybe you upload two (C++,C#) projects....

AlexGer123 avatar Apr 21 '21 04:04 AlexGer123

Hey, I was able to work based on the examples. Thanks for providing them. I am just wondering who manages the memory of the created IntPtrs. For me Marshal.WriteIntPtr(StringText, Marshal.StringToHGlobalAnsi(strText)); would require a Marshal.FreeHGlobal(StringText); at some point which we do not know when it should happen

mkrmasch avatar Jul 19 '21 12:07 mkrmasch