1

TL;DR
I'm trying to pass the path to a file (e.g. DLL), which contains unicode (UTF-16) characters, to a C++ function, which only supports the "A" variant, so it takes only ANSI characters.

More precisely it's DetourCreateProcessWithDllEx resp. DetourCreateProcessWithDlls from the Microsoft Detours library.

Their lpDllName resp. *rlpDlls parameters are of the type LPCSTR, so no wide characters can be used. According to this issue, this seems to be because the "import table does not have a wide character version". The issue has been locked in the meantime, so maybe someone here has an idea on how I could still pass the path with unicode characters to the function.

When looking at the withdll sample code, you probably don't even need to load the Detours library, as the point of failure is already at the LoadLibraryExA call, which checks if the DLL has the correct exports.

Yes, using the unicode variant LoadLibraryExW would work for this, but the point of failure is then just delayed until the DetourCreateProcessWithDllEx call, so I guess Detours itself isn't necessary to test this. If the call to LoadLibraryExA fails, then the Detours functions would probably fail as well.

So I'm looking for ways to make this work. I'm afraid there isn't any, but maybe somebody knows one. It does actually already work with "simple" unicode directories like "E:\test\Chäröcter Teßt" or "E:\test\Bjørn". But it fails for e.g. "E:\test\Bölükbaşı". With this it throws a "126" error for LoadLibraryExA ("The specified module could not be found") or "3" for the Detours call ("The system cannot find the path specified").

Here's some stripped down code taken from the withdll sample as a test case:

(As explained, for an even more simple test case you could probably remove the Detours part and only check with the LoadLibraryExA call.)

#include <windows.h>
#include <detours.h>
#include <strsafe.h>

int CDECL main(int argc, char** argv)
{
    LPCSTR rpszDllRaw = "detoursSimpleDll64.dll"; // Exists in the same directory, along with its 32bit counterpart
    LPCSTR szExe = "HelloWorld.exe";              // Exists in the same directory

    LPCSTR rpszDllOut;
    CHAR szDllPath[1024];
    PCHAR pszFilePart = NULL;

    if (!GetFullPathNameA(rpszDllRaw, ARRAYSIZE(szDllPath), szDllPath, &pszFilePart)) {
        printf("Error - GetFullPathNameA: %s is not a valid path name\n", rpszDllRaw);
        return 9002;
    }

    DWORD c = (DWORD)strlen(szDllPath) + 1;
    PCHAR psz = new CHAR[c];
    StringCchCopyA(psz, c, szDllPath);
    rpszDllOut = psz;

    HMODULE hDll = LoadLibraryExA(rpszDllOut, NULL, DONT_RESOLVE_DLL_REFERENCES);

    if (hDll == NULL) {
        printf("Error - LoadLibraryExA: %s failed to load (error %ld).\n", rpszDllOut, GetLastError());
        return 9003;
    }
    else {
        printf("LoadLibraryExA succeeded for %s\n", rpszDllOut);
    }


    // Here begins the Detours code
    STARTUPINFOA si;
    PROCESS_INFORMATION pi;
    CHAR szCommand[2048];
    CHAR szFullExe[1024] = "\0";
    PCHAR pszFileExe = NULL;

    DWORD dwFlags = CREATE_DEFAULT_ERROR_MODE | CREATE_SUSPENDED;
    ZeroMemory(&si, sizeof(si));
    ZeroMemory(&pi, sizeof(pi));
    si.cb = sizeof(si);

    szCommand[0] = L'\0';

    SetLastError(0);
    SearchPathA(NULL, szExe, ".exe", ARRAYSIZE(szFullExe), szFullExe, &pszFileExe);


    if (!DetourCreateProcessWithDllExA(szFullExe[0] ? szFullExe : NULL, szCommand,
        NULL, NULL, TRUE, dwFlags, NULL, NULL,
        &si, &pi, rpszDllOut, NULL)) {
        DWORD dwError = GetLastError();
        printf("DetourCreateProcessWithDllEx failed: %ld\n", dwError);
        ExitProcess(9009);
    }

    printf("DetourCreateProcessWithDllEx succeeded for %s and %s\n", szFullExe, rpszDllOut);


    ResumeThread(pi.hThread);

    WaitForSingleObject(pi.hProcess, INFINITE);

    DWORD dwResult = 0;
    if (!GetExitCodeProcess(pi.hProcess, &dwResult)) {
        printf("GetExitCodeProcess failed: %ld\n", GetLastError());
        return 9010;
    }
    dllPathFinal = NULL;
    delete dllPathFinal;


    return dwResult;
}
sp00n
  • 1,026
  • 1
  • 8
  • 12
  • If what you write is accurate, the Detours [issues](https://github.com/microsoft/Detours/issues) section is where this question should land. Have you considered that avenue? – IInspectable May 21 '23 at 22:33
  • 1
    Windows has a UTF-8 code page these days, you could try that. – Paul Sanders May 21 '23 at 22:35
  • @IInspectable I've linked to an issue of the Detours page, in where it's explained that's it's due to the "import table does not have a wide character version", so I guess there is no way that the Detours library itself could work around that. @Paul Sanders I've tried to set the locale from the default "C" to ".UTF8" (`setlocale(LC_ALL, ".UTF8")`), but it didn't change the outcome. Or was there something else you had in mind? – sp00n May 21 '23 at 22:51
  • 1
    Totally off the top of my head, could you perhaps work around this by using short 8.3 (a.k.a. DOS) file names? – 500 - Internal Server Error May 21 '23 at 22:55
  • 1
    Maybe this? https://stackoverflow.com/a/67020394/5743288 Looks a bit tenuous though. – Paul Sanders May 21 '23 at 22:55
  • @500-InternalServerError Good thinking, Batman – Paul Sanders May 21 '23 at 22:56
  • @500 - Internal Server Error I thought about 8.3 filenames, but on my test drive (an NVME) the call to get the short name actually just returned the long name instead. The creation of the short file depends on the `NtfsDisable8dot3NameCreation` registry setting, which if set to 2 (the default) is a "per volume setting", which apparently means that it's not guaranteed to be enabled. – sp00n May 21 '23 at 23:02
  • @PaulSanders Ah, I stumbled across the manifest at some point, will have to try that. I did find this reddit link though, where it appears that with this setting the code then just internally calls the "W" variant of the function. It's worth a shot though. https://www.reddit.com/r/cpp/comments/g7tkd7/comment/fore4z3/ – sp00n May 21 '23 at 23:08
  • I missed the issue you linked to, apologies. Now that issue is full of WTF. It goes from *"the imports are encoding-invariant"* to *"it is thus encoding-invariant how the referenced module is named"*. I am at a loss how that issue could have possibly been closed. Might want to try all over again, making the explicit point that the referenced module is a file system object, that *is* subject to character encodings. – IInspectable May 21 '23 at 23:09
  • @IInspectable For reference, I've now opened a new issue: https://github.com/microsoft/Detours/issues/283 – sp00n May 21 '23 at 23:22
  • The answer from phuclv should be useful: https://stackoverflow.com/a/63454192/4641116 – Eljay May 21 '23 at 23:26
  • 1
    @PaulSanders Embedding the manifest file instead of putting `setlocale(LC_ALL, ".UTF8")` in the code seems to have worked on an initial test. I will have to do more tests with the actual code, but the stripped down code above worked! – sp00n May 22 '23 at 00:48
  • Or not. It only worked as long as the created thread stayed in its suspended state, which Detours sets when injecting a DLL. As soon as the thread is resumed (for which I've now added the code in the OP), I'm receiving a "System Error" if the path has unicode. "The code execution cannot proceed because was not found. Reinstalling the program may fix this problem." So more testing required. :c – sp00n May 22 '23 at 19:50
  • I think this must be because `LoadLibraryExA` is running in the context of the target process (which was my fear all along). – Paul Sanders May 22 '23 at 20:58
  • The interesting thing is that everything seems to work ok. `DetourCreateProcessWithDllExA` returns `TRUE` and without throwing an error, but once `ResumeThread` is called, the error message box is shown. I've just made another test and compiled the target executable "HelloWorld" also with UTF-8 support using a manifest, and it started to work again. So it seems for this solution both executables need the UTF-8 flag to be set, which of course is out of my control. :/ Maaaybe I could use mt.exe to add a manifest to those already existing files, but that's another step that could go wrong. – sp00n May 22 '23 at 21:21
  • Why not just rename the DLL you want to inject? – Paul Sanders May 23 '23 at 12:02
  • @PaulSanders The name of the DLL is not the problem, it's the directory the DLL may be placed in. Some background, I've written a Powershell script that uses Prime95 or y-cruncher to test single core stability for overclocking/undervolting (CoreCycler), and y-cruncher does not generate a log file, and only writes to the terminal with WriteConsole, which cannot be piped to a file either. The injected DLL then would be used to redirect the WriteConsole calls to WriteFile instead, so that I can grab the output from y-cruncher. I may have to just throw an error if the DLL cannot be injected now. – sp00n May 23 '23 at 21:40
  • Maybe copy the DLL to a different folder? – Paul Sanders May 23 '23 at 22:32
  • @PaulSanders I thought about that, but the y-cruncher binaries will be in the same directory (resp. under the same parent directory which can have the unicode characters), so I would need to copy the whole y-cruncher folder to e.g. the /temp directory as well, as it also called by the Detours function. That being said, the addition of the UTF-8 manifest already seemed to enable more characters than before, although not all of them. – sp00n May 23 '23 at 22:58

0 Answers0