为什么 C++ 中的字符串如此复杂?!?!
好吧,让我们先回顾一下,我会解释一下发生了什么。
我正在将我们用于帐户等的所有脚本从 UMRA(这将像 Dodo 那样)转移到 PowerShell。我必须找到一种方法来捕获密码更改,以便将它们同步到其他系统,当我发现我可以编写自己的密码过滤器 DLL 来实现这一目的时,我很高兴。
现在,我已经尝试过多种编程语言,而且我确信 C++ 就在其中,所以尽管我使用 C++ 已经有年了,但我想,在来自Google,我应该能够编写一些东西来触发 PowerShell 脚本来处理剩下的事情。
当我查找触发 PowerShell 脚本的最佳方法时,我找到了 system() 命令。看起来很简单。我看到它需要一个 char 字符串,并且 DLL 正在获取 PUNICODE_STRING,但将一个字符串转换为另一个字符串应该不成问题......对吗?
所以我决定从一个非常简单的 DLL 开始,它只是将它得到的所有内容作为命令行参数传递给一个简单的 PowerShell 脚本(我使用 CMD 进行了测试以确保它可以工作)。然后,一旦我开始工作,我计划添加一些实际的密码过滤,并可能在将密码传递给 PS 之前对密码进行加密(以防 system() 命令记录在某处)。
那是两周前的事了,我什至不知道如何将 PUNICODE_STRING 转换为 system() 期望的 char 类型。我尝试过很多事情,但我无法全部记住。昨天我花了一些时间重新尝试了一些我记得的,所以我可以在这里记录它们。
我正在 Windows Server 2016 上的 Visual Studio 2022 中工作。
在第一个代码示例中,我展示了整个代码,但在后续示例中,我注释掉了 DllMain、InitializeChangeNotify 和 PasswordFilter 以节省本文空间。
我还想指出,我还没有尝试编译任何东西,我正在尝试让代码达到调试器首先满意的程度。因此,我已将调试器错误作为注释包含在触发它们的行上。
好吧,我的第一次尝试是将所有文本和变量打包到 system() 命令中...这不起作用,所以我开始尝试创建一个可以传递给 system() 的 char 变量。
#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;
}
所以我开始谷歌搜索并找到了使用 RtlUnicodeStringToAnsiString() 进行转换的参考。当然他们没有说如何所以我不得不做更多的搜索并最终想出这个......
#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
在互联网上闲逛时,我发现了 Michael Dunn 撰写的优秀博客“C++ 字符串完整指南,第一部分和第二部分”。内容非常丰富……但也融化了我的大脑。 我在其中发现我认为应该尝试的一件事是 ATL 转换宏。
#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
之后,我回到了微软的帖子如何:在各种字符串类型之间进行转换。我想我已经学到了足够多的东西,可以尝试他们的建议。我认为我完全正确地复制(并个性化)了它,也许我错过了一些东西?
#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
最后,在专门搜索 Stack Overflow 时(在问这个问题之前),我遇到了这篇文章,这并不是我正在寻找的完全,但他们将 PUNICODE_STRING 传递给一个将其作为 char* 接受的函数多变的。所以我想我应该尝试一下。
#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);
}
最后一点。我知道我可能有很多我不需要的 #includes 之类的东西。那是因为每次我尝试某些东西时,我都会添加该测试所需的任何内容,但之后我并没有费心删除它们。我打算稍后将它们一一注释掉,并丢弃不会导致错误的那些,并取消注释那些会导致错误的。
我希望有人能提供帮助。我希望所有这些失败不仅仅是因为我不知道我需要 #include <this> 或使用命名空间 that。
或者,如果有其他方法来触发接受 PUNICODE_STRING 的 PowerShell 脚本,那也会很有帮助。
谢谢你。
我什至不知道如何将 PUNICODE_STRING 转换为 system() 期望的 char 类型。
您可以使用 Win32
wchar_t*
函数将 char*
字符串转换为 WideCharToMultiByte()
字符串。或者,您可以将 wchar_t*
字符串分配给 std::wstring
,然后使用(已弃用的)std::wstring_convert
类,或等效的第 3 方库,例如 ICU。
或者,如果有其他方法来触发接受 PUNICODE_STRING 的 PowerShell 脚本,那也会很有帮助。
根本不用使用
system()
,您可以简单地使用 Win32 CreateProcessW()
(这是 system()
内部调用的)。 system()
仅运行 cmd.exe /C <command>
,您可以使用 CreateProcessW()
字符串通过 wchar_t*
来完成此操作。