skip to Main Content

Why are strings in C++ so freakin’ complicated?!?!

OK, let’s back up a bit and I’ll explain what’s going on.

I am in the process of moving all of the scripts we use for accounts and such off of UMRA (which is going the way of the Dodo) to PowerShell. I had to find a way to capture password changes so they can be synced out to other systems, and was delighted when I found out that I could write my own password filter DLL to do the trick.

Now, I’ve played around with a number of programming languages, and I’m sure C++ was in there somewhere, so even though it’s been years since I’ve worked with C++ I figured that, with a little help from Google, I should be able to write something that triggers a PowerShell scrip that can take care of the rest.

When I looked up the best way to trigger a PowerShell script, I found the system() command. Looks simple enough. I see that it takes a char string, and the DLL is getting a PUNICODE_STRING, but it shouldn’t be a problem to convert one string to another… right?

So I decided to start with a very simple DLL that just passes everything it gets to a simple PowerShell script as command line arguments (which I tested with CMD to make sure that would work). Then, once I get that working, I plan to add some actual password filtering and maybe encrypting the password (in case the system() command is logged somewhere) before passing it to PS.

That was 2 weeks ago, and I can’t even figure out how to translate PUNICODE_STRING to the char type that system() expects. And I’ve tried so many things, I can’t remember them all. Yesterday I spent some time re-trying some of the ones I could remember, so I could document them here.

I’m working in Visual Studio 2022, on Windows Server 2016.

In the first code example, I have shown the entire code, but in subsequent examples I have commented out DllMain, InitializeChangeNotify, and PasswordFilter to save room in this post.

I would also like to point out that I haven’t tried compiling anything yet, I’m trying to get the code to a point where the debugger is happy first. So I have included the debugger errors as comments on the line that triggered them.

OK, my first attempt was to pack all of the text and variables into the system() command… which didn’t work, so I started trying to make a char variable that I could pass to system().

#include "pch.h"
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <SubAuth.h>
#include <format>
#include <iostream>
#include <cstring>
#include <winternl.h>
#include <string.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;
}

BOOLEAN __stdcall InitializeChangeNotify(void)
{
    return TRUE;
}

NTSTATUS __stdcall PasswordChangeNotify(PUNICODE_STRING UserName, ULONG RelativeId, PUNICODE_STRING NewPassword)
{

    char PSCommand[] = "start powershell.exe SD60PWS.ps1 -Section 'ChangeNotify' -UserName ";
    strcat(PSCommand, UserName); //Error - argument of type "PUNICODE_STRING" is incompatible with parameter of type "const char *"
    //strcat(PSCommand, " -RelativeId ");
    //strcat(PSCommand, RelativeId);
    //strcat(PSCommand, " -NewPassword ");
    //strcat(PSCommand, NewPassword);
    system(PSCommand);
    return 0;
}

BOOLEAN __stdcall PasswordFilter(PUNICODE_STRING AccountName, PUNICODE_STRING FullName, PUNICODE_STRING Password, BOOLEAN SetOperation)
{
    return 1;
}

So I started Googling and found a reference to using RtlUnicodeStringToAnsiString() to do the conversion. Of course they didn’t say how so I had to do a bunch more searching and eventually came up with this…

#include "pch.h"
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <SubAuth.h>
#include <format>
#include <iostream>
#include <cstring>
#include <winternl.h>
#include <string.h>

//DllMain and InitializeChangeNotify

NTSTATUS __stdcall PasswordChangeNotify(PUNICODE_STRING UserName, ULONG RelativeId, PUNICODE_STRING NewPassword)
{
    ANSI_STRING ConvUserName;
    RtlUnicodeStringToAnsiString(&ConvUserName, UserName, TRUE);
    //Note: the example I found had an & in front of both variables, but the debugger didn't like that
    // So I took them both away, and it didn't like that either. But the debugger was OK with this.

    char PSCommand[] = "start powershell.exe SD60PWS.ps1 -Section 'ChangeNotify' -UserName ";
    strcat(PSCommand, ConvUserName); //Error - no suitable conversion function from "ANSI_STRING" to "const char *"
    //strcat(PSCommand, " -RelativeId ");
    //strcat(PSCommand, RelativeId);
    //strcat(PSCommand, " -NewPassword ");
    //strcat(PSCommand, NewPassword);
    system(PSCommand);
    return 0;
}

//PasswordFilter

In my poking around on the interweb, I came across this excellent blog ‘The Complete Guide to C++ Strings, Part I and Part II‘ by Michael Dunn. Very informative… but also melted my brain.
One thing I found in it that I thought I should try was ATL conversion macros.

#include "pch.h"
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <SubAuth.h>
#include <format>
#include <iostream>
#include <cstring>
#include <winternl.h>
#include <string.h>
#include <atlconv.h>

//DllMain and InitializeChangeNotify

NTSTATUS __stdcall PasswordChangeNotify(PUNICODE_STRING UserName, ULONG RelativeId, PUNICODE_STRING NewPassword)
{
    USES_CONVERSION;
    using std::string;

    string ConvUserName;
    ConvUserName = W2CA(UserName); //Error - a value of type "PUNICODE_STRING" cannot be assigned to an entity of type "LPCWSTR"

    char PSCommand[] = "start powershell.exe SD60PWS.ps1 -Section 'ChangeNotify' -UserName ";
    strcat(PSCommand, ConvUserName); //Error - no suitable conversion function from "std::string" to "const char *" exists
    //strcat(PSCommand, " -RelativeId ");
    //strcat(PSCommand, RelativeId);
    //strcat(PSCommand, " -NewPassword ");
    //strcat(PSCommand, NewPassword);
    system(PSCommand);
    return 0;
}

//PasswordFilter

After that, I went back to Microsoft’s post How to: Convert Between Various String Types. I though I’d picked up enough to be able to try their suggestion. I think I copied (and personalized) it all correctly, maybe I missed something?

#include "pch.h"
#include "atlbase.h"
#include "atlstr.h"
#include "comutil.h"
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <SubAuth.h>
#include <format>
#include <iostream>
#include <cstring>
#include <winternl.h>
#include <string.h>
#include <atlconv.h>

using namespace std;
using namespace System; //Error - name must be a namespace name

//DllMain and InitializeChangeNotify

NTSTATUS __stdcall PasswordChangeNotify(PUNICODE_STRING UserName, ULONG RelativeId, PUNICODE_STRING NewPassword)
{
    
    size_t origsize = wcslen(UserName) + 1; //Error - argument of type "PUNICODE_STRING" is incompatible with parameter of type "const wchar_t *"
    size_t convertedChars = 0;
    char strConcat[] = " (char *)";
    size_t strConcatsize = (strlen(strConcat) + 1) * 2;
    const size_t newsize = origsize * 2;
    char* ConvUserName = new char[newsize + strConcatsize];
    wcstombs_s(&convertedChars, ConvUserName, newsize, UserName, _TRUNCATE); //Error - no instance of overloaded function "wcstombs_s" matches the argument list
    _mbscat_s((unsigned char*)ConvUserName, newsize + strConcatsize, (unsigned char*)strConcat);

    char PSCommand[] = "start powershell.exe SD60PWS.ps1 -Section 'ChangeNotify' -UserName ";
    strcat(PSCommand, ConvUserName);
    //strcat(PSCommand, " -RelativeId ");
    //strcat(PSCommand, RelativeId);
    //strcat(PSCommand, " -NewPassword ");
    //strcat(PSCommand, NewPassword);
    system(PSCommand);
    return 0;
}

//PasswordFilter

Lastly, while searching Stack Overflow specifically (before asking this question) I came across this post that wasn’t exactly what I was looking for, but they were passing the PUNICODE_STRING to a function that accepted it as a char* variable. So I figured I’d give that a try.

#include "pch.h"
#include "atlbase.h"
#include "atlstr.h"
#include "comutil.h"
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <SubAuth.h>
#include <format>
#include <iostream>
#include <cstring>
#include <winternl.h>
#include <string.h>
#include <atlconv.h>

using namespace std;

//DllMain and InitializeChangeNotify

NTSTATUS __stdcall PasswordChangeNotify(PUNICODE_STRING UserName, ULONG RelativeId, PUNICODE_STRING NewPassword)
{
    WriteToPowerShell("ChangeNotify", UserName, NewPassword); //Error - argument of type "PUNICODE_STRING" is incompatible with parameter of type "const char *"
    
    return 0;
}

//PasswordFilter

void WriteToPowerShell(const char* PSSection, const char* PSUserName, const char* PSPassWord)
{
    char PSCommand[] = "start powershell.exe SD60PWS.ps1 -Section ";
    strcat(PSCommand, PSSection);
    strcat(PSCommand, " -UserName ");
    strcat(PSCommand, PSUserName);
    strcat(PSCommand, " -NewPassword ");
    strcat(PSCommand, PSPassWord);
    system(PSCommand);
}

One last note. I know I probably have a lot of #includes that I don’t need and things like that. That’s because every time I tried something I would add any that I needed for that test, but I didn’t bother removing them after. I plan to comment them out one-by-one later, and discard the ones that don’t cause an error, and uncomment the ones that do.

I hope someone out there can help. And I hope that all of these failures weren’t just because I didn’t know I needed to #include <this> or using namespace that.

Alternately, if there’s some other way to trigger the PowerShell script that accepts PUNICODE_STRING, that would be helpful too.

Thank you.

2

Answers


  1. I can’t even figure out how to translate PUNICODE_STRING to the char type that system() expects.

    UNICODE_STRING contains a wchar_t* string pointer.

    You can convert a wchar_t* string to a char* string using the Win32 WideCharToMultiByte() function. Or, you can use the (deprecated) std::wstring_convert class, or wcsrtombs(). Or, an equivalent 3rd party library, like ICU (which is now built-in to Windows 10 and later). There are MANY different ways (and MANY answered questions on StackOverflow) related to string conversions.

    Alternately, if there’s some other way to trigger the PowerShell script that accepts PUNICODE_STRING, that would be helpful too.

    Rather than using system() at all, you can instead use the Win32 CreateProcessW() function (which is what system() calls internally). system() merely runs "cmd.exe /C <command>". In this case, your <command> is using start to run PowerShell.exe. You can run PowerShell.exe directly with CreateProcessW() using a wchar_t* string, eg:

    NTSTATUS __stdcall PasswordChangeNotify(PUNICODE_STRING UserName, ULONG RelativeId, PUNICODE_STRING NewPassword)
    {
        std::wstring PSCommand = L"powershell.exe SD60PWS.ps1 -Section 'ChangeNotify' -UserName ";
        PSCommand.append(UserName->Buffer, UserName->Length / sizeof(WCHAR));
    
        //PSCommand += L" -RelativeId ";
        //PSCommand += std::to_wstring(RelativeId);
        //PSCommand += L" -NewPassword ";
        //PSCommand.append(NewPassword->Buffer, NewPassword->Length / sizeof(WCHAR));
    
        STARTUPINFOW si = {};
        si.cb = sizeof(si);
        // setup any parameters of si as needed...
    
        PROCESS_INFORMATION pi = {};
    
        if (CreateProcessW(nullptr, PSCommand.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi))
        {
            CloseHandle(pi.hThread);
            CloseHandle(pi.hProcess);
        }
    
        return 0;
    }
    
    Login or Signup to reply.
  2. at first of course you need use here not system but CreateProcessW () for start new process. about string manipulations – one simply way – use c style format functions. here _snwprintf most suitable and use %wZ format for PUNICODE_STRING. example:

    NTSTATUS NTAPI PasswordChangeNotify(PUNICODE_STRING UserName, ULONG RelativeId, PUNICODE_STRING NewPassword)
    {
        int len = 0;
        PWSTR pszCmdLine = 0;
    
        while (0 < (len = _snwprintf(pszCmdLine, len, len, 
            L"powershell.exe SD60PWS.ps1 -Section 'ChangeNotify' @%x@%wZ@%wZ",
            RelativeId, UserName, NewPassword)))
        {
            if (pszCmdLine)
            {
                STARTUPINFO si { sizeof(si) };
                PROCESS_INFORMATION pi;
                if (CreateProcessW(0, pszCmdLine, 0, 0, 0, 0, 0, 0, &si, &pi))
                {
                    CloseHandle(pi.hThread);
                    CloseHandle(pi.hProcess);
                }
                break;
            }
    
            pszCmdLine = (PWSTR)alloca(++len * sizeof(WCHAR));
        }
    
        return STATUS_SUCCESS;
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search