Given the key for some registry value (e.g. HKEY_LOCAL_MACHINE\blah\blah\blah\foo) how can I:
Safely determine that such a key exists.
Programmatically (i.e. with code) get its value.
I have absolutely no intention of writing anything back to the registry (for the duration of my career if I can help it). So we can skip the lecture about every molecule in my body exploding at the speed of light if I write to the registry incorrectly.
Prefer answers in C++, but mostly just need to know what the special Windows API incantation to get at the value is.
Here is some pseudo-code to retrieve the following:
If a registry key exists
What the default value is for that registry key
What a string value is
What a DWORD value is
Example code:
Include the library dependency: Advapi32.lib
HKEY hKey;
LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Perl", 0, KEY_READ, &hKey);
bool bExistsAndSuccess (lRes == ERROR_SUCCESS);
bool bDoesNotExistsSpecifically (lRes == ERROR_FILE_NOT_FOUND);
std::wstring strValueOfBinDir;
std::wstring strKeyDefaultValue;
GetStringRegKey(hKey, L"BinDir", strValueOfBinDir, L"bad");
GetStringRegKey(hKey, L"", strKeyDefaultValue, L"bad");
LONG GetDWORDRegKey(HKEY hKey, const std::wstring &strValueName, DWORD &nValue, DWORD nDefaultValue)
{
nValue = nDefaultValue;
DWORD dwBufferSize(sizeof(DWORD));
DWORD nResult(0);
LONG nError = ::RegQueryValueExW(hKey,
strValueName.c_str(),
0,
NULL,
reinterpret_cast<LPBYTE>(&nResult),
&dwBufferSize);
if (ERROR_SUCCESS == nError)
{
nValue = nResult;
}
return nError;
}
LONG GetBoolRegKey(HKEY hKey, const std::wstring &strValueName, bool &bValue, bool bDefaultValue)
{
DWORD nDefValue((bDefaultValue) ? 1 : 0);
DWORD nResult(nDefValue);
LONG nError = GetDWORDRegKey(hKey, strValueName.c_str(), nResult, nDefValue);
if (ERROR_SUCCESS == nError)
{
bValue = (nResult != 0) ? true : false;
}
return nError;
}
LONG GetStringRegKey(HKEY hKey, const std::wstring &strValueName, std::wstring &strValue, const std::wstring &strDefaultValue)
{
strValue = strDefaultValue;
WCHAR szBuffer[512];
DWORD dwBufferSize = sizeof(szBuffer);
ULONG nError;
nError = RegQueryValueExW(hKey, strValueName.c_str(), 0, NULL, (LPBYTE)szBuffer, &dwBufferSize);
if (ERROR_SUCCESS == nError)
{
strValue = szBuffer;
}
return nError;
}
const CString REG_SW_GROUP_I_WANT = _T("SOFTWARE\\My Corporation\\My Package\\Group I want");
const CString REG_KEY_I_WANT= _T("Key Name");
CRegKey regKey;
DWORD dwValue = 0;
if(ERROR_SUCCESS != regKey.Open(HKEY_LOCAL_MACHINE, REG_SW_GROUP_I_WANT))
{
m_pobLogger->LogError(_T("CRegKey::Open failed in Method"));
regKey.Close();
goto Function_Exit;
}
if( ERROR_SUCCESS != regKey.QueryValue( dwValue, REG_KEY_I_WANT))
{
m_pobLogger->LogError(_T("CRegKey::QueryValue Failed in Method"));
regKey.Close();
goto Function_Exit;
}
// dwValue has the stuff now - use for further processing
Since Windows >=Vista/Server 2008, RegGetValue is available, which is a safer function than RegQueryValueEx. No need for RegOpenKeyEx, RegCloseKey or NUL termination checks of string values (REG_SZ, REG_MULTI_SZ, REG_EXPAND_SZ).
#include <iostream>
#include <string>
#include <exception>
#include <windows.h>
/*! \brief Returns a value from HKLM as string.
\exception std::runtime_error Replace with your error handling.
*/
std::wstring GetStringValueFromHKLM(const std::wstring& regSubKey, const std::wstring& regValue)
{
size_t bufferSize = 0xFFF; // If too small, will be resized down below.
std::wstring valueBuf; // Contiguous buffer since C++11.
valueBuf.resize(bufferSize);
auto cbData = static_cast<DWORD>(bufferSize * sizeof(wchar_t));
auto rc = RegGetValueW(
HKEY_LOCAL_MACHINE,
regSubKey.c_str(),
regValue.c_str(),
RRF_RT_REG_SZ,
nullptr,
static_cast<void*>(valueBuf.data()),
&cbData
);
while (rc == ERROR_MORE_DATA)
{
// Get a buffer that is big enough.
cbData /= sizeof(wchar_t);
if (cbData > static_cast<DWORD>(bufferSize))
{
bufferSize = static_cast<size_t>(cbData);
}
else
{
bufferSize *= 2;
cbData = static_cast<DWORD>(bufferSize * sizeof(wchar_t));
}
valueBuf.resize(bufferSize);
rc = RegGetValueW(
HKEY_LOCAL_MACHINE,
regSubKey.c_str(),
regValue.c_str(),
RRF_RT_REG_SZ,
nullptr,
static_cast<void*>(valueBuf.data()),
&cbData
);
}
if (rc == ERROR_SUCCESS)
{
cbData /= sizeof(wchar_t);
valueBuf.resize(static_cast<size_t>(cbData - 1)); // remove end null character
return valueBuf;
}
else
{
throw std::runtime_error("Windows system error code: " + std::to_string(rc));
}
}
int main()
{
std::wstring regSubKey;
#ifdef _WIN64 // Manually switching between 32bit/64bit for the example. Use dwFlags instead.
regSubKey = L"SOFTWARE\\WOW6432Node\\Company Name\\Application Name\\";
#else
regSubKey = L"SOFTWARE\\Company Name\\Application Name\\";
#endif
std::wstring regValue(L"MyValue");
std::wstring valueFromRegistry;
try
{
valueFromRegistry = GetStringValueFromHKLM(regSubKey, regValue);
}
catch (std::exception& e)
{
std::cerr << e.what();
}
std::wcout << valueFromRegistry;
}
Its parameter dwFlags supports flags for type restriction, filling the value buffer with zeros on failure (RRF_ZEROONFAILURE) and 32/64bit registry access (RRF_SUBKEY_WOW6464KEY, RRF_SUBKEY_WOW6432KEY) for 64bit programs.
The pair RegOpenKey and RegQueryKeyEx will do the trick.
If you use MFC CRegKey class is even more easier solution.
RegQueryValueEx
This gives the value if it exists, and returns an error code ERROR_FILE_NOT_FOUND if the key doesn't exist.
(I can't tell if my link is working or not, but if you just google for "RegQueryValueEx" the first hit is the msdn documentation.)
Typically the register key and value are constants in the program. If so, here is an example how to read a DWORD registry value Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled:
#include <windows.h>
DWORD val;
DWORD dataSize = sizeof(val);
if (ERROR_SUCCESS == RegGetValueA(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\FileSystem", "LongPathsEnabled", RRF_RT_DWORD, nullptr /*type not required*/, &val, &dataSize)) {
printf("Value is %i\n", val);
// no CloseKey needed because it is a predefined registry key
}
else {
printf("Error reading.\n");
}
To adapt for other value types, see https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-reggetvaluea for complete spec.
This console app will list all the values and their data from a registry key for most of the potential registry values. There's some weird ones not often used. If you need to support all of them, expand from this example while referencing this Registry Value Type documentation.
Let this be the registry key content you can import from a .reg file format:
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\added\subkey]
"String_Value"="hello, world!"
"Binary_Value"=hex:01,01,01,01
"Dword value"=dword:00001224
"QWord val"=hex(b):24,22,12,00,00,00,00,00
"multi-line val"=hex(7):4c,00,69,00,6e,00,65,00,20,00,30,00,00,00,4c,00,69,00,\
6e,00,65,00,20,00,31,00,00,00,4c,00,69,00,6e,00,65,00,20,00,32,00,00,00,00,\
00
"expanded_val"=hex(2):25,00,55,00,53,00,45,00,52,00,50,00,52,00,4f,00,46,00,49,\
00,4c,00,45,00,25,00,5c,00,6e,00,65,00,77,00,5f,00,73,00,74,00,75,00,66,00,\
66,00,00,00
The console app itself:
#include <Windows.h>
#include <iostream>
#include <string>
#include <locale>
#include <vector>
#include <iomanip>
int wmain()
{
const auto hKey = HKEY_CURRENT_USER;
constexpr auto lpSubKey = TEXT("added\\subkey");
auto openedKey = HKEY();
auto status = RegOpenKeyEx(hKey, lpSubKey, 0, KEY_READ, &openedKey);
if (status == ERROR_SUCCESS) {
auto valueCount = static_cast<DWORD>(0);
auto maxNameLength = static_cast<DWORD>(0);
auto maxValueLength = static_cast<DWORD>(0);
status = RegQueryInfoKey(openedKey, NULL, NULL, NULL, NULL, NULL, NULL,
&valueCount, &maxNameLength, &maxValueLength, NULL, NULL);
if (status == ERROR_SUCCESS) {
DWORD type = 0;
DWORD index = 0;
std::vector<wchar_t> valueName = std::vector<wchar_t>(maxNameLength + 1);
std::vector<BYTE> dataBuffer = std::vector<BYTE>(maxValueLength);
for (DWORD index = 0; index < valueCount; index++) {
DWORD charCountValueName = static_cast<DWORD>(valueName.size());
DWORD charBytesData = static_cast<DWORD>(dataBuffer.size());
status = RegEnumValue(openedKey, index, valueName.data(), &charCountValueName,
NULL, &type, dataBuffer.data(), &charBytesData);
if (type == REG_SZ) {
const auto reg_string = reinterpret_cast<wchar_t*>(dataBuffer.data());
std::wcout << L"Type: REG_SZ" << std::endl;
std::wcout << L"\tName: " << valueName.data() << std::endl;
std::wcout << L"\tData : " << reg_string << std::endl;
}
else if (type == REG_EXPAND_SZ) {
const auto casted = reinterpret_cast<wchar_t*>(dataBuffer.data());
TCHAR buffer[32000];
ExpandEnvironmentStrings(casted, buffer, 32000);
std::wcout << L"Type: REG_EXPAND_SZ" << std::endl;
std::wcout << L"\tName: " << valueName.data() << std::endl;
std::wcout << L"\tData: " << buffer << std::endl;
}
else if (type == REG_MULTI_SZ) {
std::vector<std::wstring> lines;
const auto str = reinterpret_cast<wchar_t*>(dataBuffer.data());
auto line = str;
lines.emplace_back(line);
for (auto i = 0; i < charBytesData / sizeof(wchar_t) - 1; i++) {
const auto c = str[i];
if (c == 0) {
line = str + i + 1;
const auto new_line = reinterpret_cast<wchar_t*>(line);
if (wcsnlen_s(new_line, 1024) > 0)
lines.emplace_back(new_line);
}
}
std::wcout << L"Type: REG_MULTI_SZ" << std::endl;
std::wcout << L"\tName: " << valueName.data() << std::endl;
std::wcout << L"\tData: " << std::endl;
for (size_t i = 0; i < lines.size(); i++) {
std::wcout << L"\t\tLine[" << i + 1 << L"]: " << lines[i] << std::endl;
}
}
if (type == REG_DWORD) {
const auto dword_value = reinterpret_cast<unsigned long*>(dataBuffer.data());
std::wcout << L"Type: REG_DWORD" << std::endl;
std::wcout << L"\tName: " << valueName.data() << std::endl;
std::wcout << L"\tData : " << std::to_wstring(*dword_value) << std::endl;
}
else if (type == REG_QWORD) {
const auto qword_value = reinterpret_cast<unsigned long long*>(dataBuffer.data());
std::wcout << L"Type: REG_DWORD" << std::endl;
std::wcout << L"\tName: " << valueName.data() << std::endl;
std::wcout << L"\tData : " << std::to_wstring(*qword_value) << std::endl;
}
else if (type == REG_BINARY) {
std::vector<uint16_t> bins;
for (auto i = 0; i < charBytesData; i++) {
bins.push_back(static_cast<uint16_t>(dataBuffer[i]));
}
std::wcout << L"Type: REG_BINARY" << std::endl;
std::wcout << L"\tName: " << valueName.data() << std::endl;
std::wcout << L"\tData:";
for (size_t i = 0; i < bins.size(); i++) {
std::wcout << L" " << std::uppercase << std::hex << \
std::setw(2) << std::setfill(L'0') << std::to_wstring(bins[i]);
}
std::wcout << std::endl;
}
}
}
}
RegCloseKey(openedKey);
return 0;
}
Expected console output:
Type: REG_SZ
Name: String_Value
Data : hello, world!
Type: REG_BINARY
Name: Binary_Value
Data: 01 01 01 01
Type: REG_DWORD
Name: Dword value
Data : 4644
Type: REG_DWORD
Name: QWord val
Data : 1188388
Type: REG_MULTI_SZ
Name: multi-line val
Data:
Line[1]: Line 0
Line[2]: Line 1
Line[3]: Line 2
Type: REG_EXPAND_SZ
Name: expanded_val
Data: C:\Users\user name\new_stuff
#include <windows.h>
#include <map>
#include <string>
#include <stdio.h>
#include <string.h>
#include <tr1/stdint.h>
using namespace std;
void printerr(DWORD dwerror) {
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dwerror,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &lpMsgBuf,
0,
NULL
);
// Process any inserts in lpMsgBuf.
// ...
// Display the string.
if (isOut) {
fprintf(fout, "%s\n", lpMsgBuf);
} else {
printf("%s\n", lpMsgBuf);
}
// Free the buffer.
LocalFree(lpMsgBuf);
}
bool regreadSZ(string& hkey, string& subkey, string& value, string& returnvalue, string& regValueType) {
char s[128000];
map<string,HKEY> keys;
keys["HKEY_CLASSES_ROOT"]=HKEY_CLASSES_ROOT;
keys["HKEY_CURRENT_CONFIG"]=HKEY_CURRENT_CONFIG; //DID NOT SURVIVE?
keys["HKEY_CURRENT_USER"]=HKEY_CURRENT_USER;
keys["HKEY_LOCAL_MACHINE"]=HKEY_LOCAL_MACHINE;
keys["HKEY_USERS"]=HKEY_USERS;
HKEY mykey;
map<string,DWORD> valuetypes;
valuetypes["REG_SZ"]=REG_SZ;
valuetypes["REG_EXPAND_SZ"]=REG_EXPAND_SZ;
valuetypes["REG_MULTI_SZ"]=REG_MULTI_SZ; //probably can't use this.
LONG retval=RegOpenKeyEx(
keys[hkey], // handle to open key
subkey.c_str(), // subkey name
0, // reserved
KEY_READ, // security access mask
&mykey // handle to open key
);
if (ERROR_SUCCESS != retval) {printerr(retval); return false;}
DWORD slen=128000;
DWORD valuetype = valuetypes[regValueType];
retval=RegQueryValueEx(
mykey, // handle to key
value.c_str(), // value name
NULL, // reserved
(LPDWORD) &valuetype, // type buffer
(LPBYTE)s, // data buffer
(LPDWORD) &slen // size of data buffer
);
switch(retval) {
case ERROR_SUCCESS:
//if (isOut) {
// fprintf(fout,"RegQueryValueEx():ERROR_SUCCESS:succeeded.\n");
//} else {
// printf("RegQueryValueEx():ERROR_SUCCESS:succeeded.\n");
//}
break;
case ERROR_MORE_DATA:
//what do I do now? data buffer is too small.
if (isOut) {
fprintf(fout,"RegQueryValueEx():ERROR_MORE_DATA: need bigger buffer.\n");
} else {
printf("RegQueryValueEx():ERROR_MORE_DATA: need bigger buffer.\n");
}
return false;
case ERROR_FILE_NOT_FOUND:
if (isOut) {
fprintf(fout,"RegQueryValueEx():ERROR_FILE_NOT_FOUND: registry value does not exist.\n");
} else {
printf("RegQueryValueEx():ERROR_FILE_NOT_FOUND: registry value does not exist.\n");
}
return false;
default:
if (isOut) {
fprintf(fout,"RegQueryValueEx():unknown error type 0x%lx.\n", retval);
} else {
printf("RegQueryValueEx():unknown error type 0x%lx.\n", retval);
}
return false;
}
retval=RegCloseKey(mykey);
if (ERROR_SUCCESS != retval) {printerr(retval); return false;}
returnvalue = s;
return true;
}
Related
This is a repost, as my last one was too much, so I'm going to make it simple.
On the second call to BCryptDecrypt(), it fails returning 0xc000000d. I am aware the code is bad and has many points of failure, but in my test none of those points failed.
I compared this to the C# version of the code that works, debuggers for both show they have all the same values for buffers / returned values just fails at the second call of BCryptDecrypt().
My goal is to decrypt an Encrypted / Ciphered Text Block using a Key from that Block. Uses AES-GCM I want to use ONLY Bcrypt. It is possible as I have a C# version, I don't want to import extra libs, like OpenSSL.
If you want to try, here is the Base64 Encoded Key, you need to decode from base64, skip 5 bytes for "DPAPI", then call to CryptUnprotectData().
The value to be Decrypted / Decoded is (Base64 + AES-GCM):
RFBBUEkBAAAA0Iyd3wEV0RGMegDAT8KX6wEAAADeBU2aOO6lQ73DYvrL8hhAAAAAAAIAAAAAABBmAAAAAQAAIAAAALXsSijRcSAV3S8PSvKZXUddV2eE+nv5xtn8JaePfpjrAAAAAA6AAAAAAgAAIAAAAJ96r2xQdEkkXuEaGEcVG24QJSKXG9s14w/yS8gdO/CxMAAAABs64+DTxkrQnkH1e3d0w/tOPIRrB1OBPrw4uxX4Q0AfYv6pyMZKXchhn1qol8bMvUAAAADisCS/m6UBBzMxVLBgpCvQsGIa6hYT/J8NGZuFOqydlstlxLBL2K8tCX550IoHtwuEA8EGJTxMsW6wh/0H3VRn
(this is my password/key for my CTF / Project, I allow it to be decrypted / decoded)
Code to decrypt:
std::vector<uint8_t> AesGcm::DecryptWithKey(
const std::vector<uint8_t>& ciphered_data,
const std::vector<uint8_t>& key)
{
//Create the IV
std::vector<uint8_t> iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // IV 12 bytes
std::copy(ciphered_data.begin() + 3, ciphered_data.begin() + 15, iv.begin());
//Create Buffer witth Ciphered Data only
std::vector<uint8_t> buffer(ciphered_data.size() - 15);
std::copy(ciphered_data.begin() + 15, ciphered_data.end(), buffer.begin());
//Create TAG
std::vector<uint8_t> tag(16);
std::vector<uint8_t> data(buffer.size() - tag.size());
//Last 16 bytes for tag
std::copy(buffer.end() - 16, buffer.end(), tag.begin());
//encrypted password
std::copy(buffer.begin(), buffer.end() - tag.size(), data.begin());
std::vector<uint8_t> aad(0);//Just an Empty AAD param
return Decrypt(key, iv, aad, data, tag);
}
std::vector<uint8_t> AesGcm::Decrypt(
const std::vector<uint8_t>& key,
std::vector<uint8_t>& iv,
std::vector<uint8_t>& aad,
std::vector<uint8_t>& cipher_text,
std::vector<uint8_t>& authTag)
{
std::vector<uint8_t> p_text(0);
BCRYPT_ALG_HANDLE hAlg;
if (!OpenAlgorithmProvider(hAlg, MS_PRIMITIVE_PROVIDER, BCRYPT_CHAIN_MODE_GCM))
return p_text;
auto key_sz_bts = AesGcm::GetProperty(hAlg, BCRYPT_OBJECT_LENGTH);
if (key_sz_bts.empty() || key_sz_bts.size() != 4) {
std::cout << "Inavlid PropertySize::" << key_sz_bts.size() << std::endl;
if (hAlg) BCryptCloseAlgorithmProvider(hAlg, 0x0);
return p_text;
}
auto blob_vec = AesGcm::CreateBlob(key);
auto key_sz = *reinterpret_cast<int*>(key_sz_bts.data());
if (blob_vec.empty()) {
std::cout << "Invalid Blob Size::" << blob_vec.size() << std::endl;
if (hAlg) BCryptCloseAlgorithmProvider(hAlg, 0x0);
return p_text;
}
BCRYPT_KEY_HANDLE hBck;
std::vector<uint8_t> kdata_buffer(key_sz);
std::vector<uint8_t> mac(authTag.size());
DWORD dwStatus =
BCryptImportKey(hAlg, NULL, BCRYPT_KEY_DATA_BLOB, &hBck, kdata_buffer.data(), kdata_buffer.size(), blob_vec.data(), blob_vec.size(), 0x0);
if (FAILED(dwStatus)) {
std::cout << "BCryptImportKey(FAILED)::[0x" << std::hex << dwStatus << "]" << std::endl;
if (hBck) BCryptDestroyKey(hBck);
if (hAlg) BCryptCloseAlgorithmProvider(hAlg, 0x0);
return p_text;
}
//Create the AUTH Struct
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo;
authInfo.dwInfoVersion = 0x00000001;
authInfo.pbNonce = iv.data();
authInfo.cbNonce = iv.size();
authInfo.pbAuthData = aad.data(); //aad.data();
authInfo.cbAuthData = aad.size();//aad.size();
authInfo.pbTag = authTag.data();
authInfo.cbTag = authTag.size();
authInfo.pbMacContext = mac.data();
authInfo.cbMacContext = mac.size();//authTag.size();
authInfo.cbAAD = 0;
authInfo.cbData = 0;
//authInfo.cbSize = sizeof(BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO);
authInfo.cbSize = sizeof(authInfo);
auto authtg_tg_bts = AesGcm::GetProperty(hAlg, BCRYPT_AUTH_TAG_LENGTH);
//Get Auth Tag Size
auto atag_size = *reinterpret_cast<int*>(&authtg_tg_bts[4]);
std::vector<uint8_t> iv_data(atag_size);
DWORD ptext_size = 0;
//Decrypt Data now
dwStatus = BCryptDecrypt(
hBck, cipher_text.data(), cipher_text.size(), &authInfo, iv_data.data(), iv_data.size(), nullptr, 0, &ptext_size, 0x0);
if (FAILED(dwStatus) || ptext_size <= 0) {
std::cout << "BCryptDecrypt2(FAILED)::[0x" << std::hex << dwStatus << "]::SIZE::" << ptext_size << std::endl;
goto end;
}
p_text.resize(ptext_size);
//Fails at this Second Call
dwStatus = BCryptDecrypt(
hBck,
cipher_text.data(),
cipher_text.size(),
&authInfo,
iv_data.data(),
iv_data.size(), p_text.data(), p_text.size(), &ptext_size, 0x0);
if (FAILED(dwStatus)) {
std::cout << "BCryptDecrypt2(FAILED)::[0x" << std::hex << dwStatus << "]" << std::endl;
goto end;
}
end:
if (hBck) BCryptDestroyKey(hBck);
if (hAlg) BCryptCloseAlgorithmProvider(hAlg, 0x0);
return p_text;
}
"Unprotect" decoded Base64 Key:
//encryptedData is the Decoded Base64 Data skipped 5 Bytes
DATA_BLOB DataIn;
DataIn.cbData = encryptedData.size();
DataIn.pbData = (BYTE*)encryptedData.data();//const_cast<uint8_t*>(encryptedData.data());
auto num = 1u;
if (dwFlags == 1) {
num |= 4u;
}
DATA_BLOB DataOut;
if (!CryptUnprotectData(&DataIn, NULL, NULL, NULL, NULL, num, &DataOut))
throw std::exception("CryptUnprotectData failed.");
std::vector<uint8_t> result(DataOut.pbData, DataOut.pbData + DataOut.cbData);
LocalFree(DataOut.pbData);
Rest of the code (incase I am doing something wrong somewhere else)
std::vector<uint8_t> AesGcm::CreateBlob(const std::vector<uint8_t>& key) {
//Create KeyBlob
std::vector<uint8_t> key_bloc_vec(4 * 3 + key.size());
//Write The MAGIC
DWORD kbm = BCRYPT_KEY_DATA_BLOB_MAGIC;
//uint8_t* bytes = reinterpret_cast<uint8_t*>(&kbm);
std::memcpy(key_bloc_vec.data(), reinterpret_cast<uint8_t*>(&kbm), sizeof(int));
//Write 0x1
DWORD rnd = 0x1;
//uint8_t* bytes = reinterpret_cast<uint8_t*>(&rnd);
std::memcpy(key_bloc_vec.data() + 4, reinterpret_cast<uint8_t*>(&rnd), sizeof(int));
//Write Size
DWORD ksz = key.size();
std::memcpy(key_bloc_vec.data() + 8, reinterpret_cast<uint8_t*>(&ksz), sizeof(int));
//Write KEY
std::memcpy(key_bloc_vec.data() + 12, key.data(), key.size());
return key_bloc_vec;
}
bool AesGcm::OpenAlgorithmProvider(BCRYPT_ALG_HANDLE& hBC, const TCHAR* provider, const TCHAR* chaining_mode) {
DWORD dwStatus = BCryptOpenAlgorithmProvider(&hBC, BCRYPT_AES_ALGORITHM, provider, 0x0);
if (FAILED(dwStatus)) {
std::cout << "BCryptOpenAlgorithmProvider(FAILED)::[0x" << std::hex << dwStatus << "]" << std::endl;
return false;
}
//std::wstring cc_mode(BCRYPT_CHAIN_MODE_GCM);
//std::vector<uint8_t> bmode_bys(cc_mode.size() * sizeof(wchar_t) + 2);
//std::memcpy(bmode_bys.data(), cc_mode.data(), bmode_bys.size());
//dwStatus = BCryptSetProperty(hBC, BCRYPT_CHAINING_MODE, bmode_bys.data(), bmode_bys.size() - 2, 0x0);
//if (FAILED(dwStatus)) {
// std::cout << "BCryptSetProperty(FAILED)::[0x" << std::hex << dwStatus << "]" << std::endl;
// return false;
//}
dwStatus = BCryptSetProperty(hBC, BCRYPT_CHAINING_MODE, (PBYTE)BCRYPT_CHAIN_MODE_GCM, sizeof(BCRYPT_CHAIN_MODE_GCM), 0x0);
if (FAILED(dwStatus)) {
std::cout << "BCryptSetProperty(FAILED)::[0x" << std::hex << dwStatus << "]" << std::endl;
return false;
}
return true;
}
std::vector<uint8_t> AesGcm::GetProperty(BCRYPT_ALG_HANDLE& hCH, const TCHAR* prop_name) {
DWORD sz_1 = 0;//BCRYPT_AUTH_TAG_LENGTH / BCRYPT_OBJECT_LENGTH
DWORD dwStatus = BCryptGetProperty(hCH, prop_name, NULL, 0, &sz_1, 0x0);
if (FAILED(dwStatus) || sz_1 <= 0)
throw std::exception("BCryptGetProperty failed");
std::vector<uint8_t> prop_bys(sz_1);
dwStatus = BCryptGetProperty(hCH, prop_name, (PBYTE)prop_bys.data(), prop_bys.size(), &sz_1, 0x0);
if (FAILED(dwStatus))
throw std::exception("BCryptGetProperty failed2");
return prop_bys;
}
Changing from std::vector to HeapAlloc
Changing around the BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO a bit
Changing the params for BCryptSetProperty
Using well written version in C++ from GitHub Pages
Trying 4+ fifferent ways of the code, including using Chat Bot GPT
So I will leave this up for the future of people, I started to look into padding given this post BCryptEncrypt returns STATUS_INVALID_PARAMETER on AES-GCM
I created a vector with X amount of Bytes , Copied the BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO ovver using memcpy based off the static X size
I see no padding least that will cut off / malformed the data, but I do see a value that keeps changing each debug at the end of the struct that seems to keep changing each run. I cast it back over to the trust seeing if its malformed or anything it seems fine. I do notice a field in the debugger though that I did not touch called "dwFlags" , it has a random value it sets to each run.
Set:
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.dwFlags = 0;
Problem Fixed :D
It was also mentioned in comments about "dwFlags" but I am assuming he meant the function signature not the structure if not then I assumed wrong. Either way problem fixed !
Get list of windows services and print name, status, path to exe
the above statement needs to be solved, I am aware we enum services to find.....since I am beginner ,I don't understand how to retrieve all the path to exe.
this is what I knew
#include <Windows.h>
#include <iostream>
int main()
{
SC_HANDLE sHandle;
LPQUERY_SERVICE_CONFIG lpServiceConfig = NULL;
DWORD cbBufSize = 100;
LPDWORD bytesNeeded = NULL;
sHandle = OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
sHandle = OpenService(sHandle, "YOU SERVICE NAME",SERVICE_ALL_ACCESS);
QueryServiceConfig(sHandle,lpServiceConfig,cbBufSize,bytesNeeded);
std::cout << lpServiceConfig->lpBinaryPathName << std::endl;
}
You need to use EnumServiceStatus() to query which services are installed. You can then open each service to query its EXE path.
Also, your code is leaking resources and memory.
Try something more like this:
#include <Windows.h>
#include <iostream>
#include <vector>
int main()
{
DWORD bytesNeeded = 0;
DWORD numServices = 0;
DWORD resumeHandle = 0;
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
if (!hSCManager) ...
EnumServicesStatus(hSCManager, SERVICE_WIN32, SERVICE_STATE_ALL, NULL, 0, &bytesNeeded, &numServices, &resumeHandle);
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) ...
std::vector<BYTE> enumBuffer(bytesNeeded);
LPENUM_SERVICE_STATUS pEnum = reinterpret_cast<LPENUM_SERVICE_STATUS>(enumBuffer.data());
if (!EnumServicesStatus(hSCManager, SERVICE_WIN32, SERVICE_STATE_ALL, pEnum, bytesNeeded, &bytesNeeded, &numServices, &resumeHandle)) ...
for(DWORD idx = 0; idx < numServices; ++idx)
{
std::cout << pEnum[idx].lpServiceName << " (" << pEnum[idx].lpDisplayName << ")" << std::endl;
// use/display pEnum[idx].ServiceStatus as needed ...
SC_HANDLE hService = OpenService(hSCManager, pEnum[idx].lpServiceName, SERVICE_QUERY_CONFIG);
if (!hService) ...
QueryServiceConfig(hSerivce, NULL, 0, &bytesNeeded);
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) ...
std::vector<BYTE> configBuffer(bytesNeeded);
LPQUERY_SERVICE_CONFIG lpServiceConfig = reinterpret_cast<LPQUERY_SERVICE_CONFIG>(configBuffer.data());
if (!QueryServiceConfig(hSerivce, lpServiceConfig, bytesNeeded, &bytesNeeded)) ...
std::cout << lpServiceConfig->lpBinaryPathName << std::endl;
CloseServiceHandle(hService);
}
CloseServiceHandle(hSCManager);
}
Context:
I need to scan for, gather information and copy some media files from specific directories.
I have been having quite some trouble with some files not being detected, etc
Problem:
Warning to reader: As seen on the screenshot and noted in a comment,
the title and premise of this question are possible moot, as the error
was more likely to be dec 32 (== 0x20) == ERROR_SHARING_VIOLATION,
which has an "easy" explantion in the answer to this question.
In the code below, I use a c-style cast to convert my QString into a LPCWSTR for the CopyFileExW which I found in this SO post. I have tried many different conversions, but non of them seems to work correctly - which for now is besides the point.
The problem this 'conversion' technique gives is the error ERROR_NOT_SUPPORTED
ERROR_NOT_SUPPORTED
50 (0x32)
The request is not supported.
Frankly, this makes absolutely no sense to me in this context. I am copying from NTFS -> NTFS (same hard drive for testing), with destination file length < 200 characters (see image, 192 to be exact).
The core code: (see bottom for full details)
// QString src (src file location), dst (destination file location)
LPCWSTR localC_src = (LPCWSTR) src.utf16();
LPCWSTR localC_dst = (LPCWSTR) dst.utf16();
LPCWSTR dirC = (LPCWSTR) dir.utf16();
auto rc = CopyFileExW(localC_src, localC_dst, &BackupManager::copyProgress, this, &bStopBackup, 0);
if (rc == 0) {
DWORD lastError = GetLastError(); // Error = 0x32
bool dirExist = DirExists(dirC); // true
bool fileExists = FileExists(localC_src); // true
printWarning(TAG, QString("File Copy Error: %1").arg(getLastErrorMsg()));
#ifdef QT_DEBUG
if (FileExists(localC_src)) {
qDebug() << "#FailedCopy: Windows file exists but copy failed" << src; // this gets hit using the implemented c-style cast
}
else {
if (QFile::exists(src)) {
qDebug() << "#FailedCopy: Windows is really being full of shit! " << src; // this always gets triggered when using QString::toStdWString.c_str()
}
else {
qDebug() << "#FailedCopy: Windows file copy failed outright" << src;
}
}
// ...
} else {
// success
}
What does this error mean in the FileCopyExW context?
(also, if anyone has the source for windows.h implementation to allow me to trace the error further, please post it as a comment)
Image of debugger, etc
Full Code Implemenation:
static QString toString(HRESULT hr)
{
_com_error err{hr};
const TCHAR* lastError = err.ErrorMessage();
return QStringLiteral("Error 0x%1: %2").arg((quint32)hr, 8, 16, QLatin1Char('0'))
.arg(lastError);
}
static QString getLastErrorMsg()
{
DWORD lastError = GetLastError();
QString s = toString(HRESULT_FROM_WIN32(lastError));
return s;
}
BOOL FileExists(LPCWSTR szPath)
{
DWORD dwAttrib = GetFileAttributes(szPath);
return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
}
// not used
static const wchar_t* toLPCWSTR(QString s)
{
std::wstring dstWString = s.toStdWString();
const wchar_t* localC_src = dstWString.c_str();
return localC_src;
}
static bool DirExists(LPCWSTR szPath)
{
DWORD ftyp = GetFileAttributes(szPath);
if (ftyp == INVALID_FILE_ATTRIBUTES)
return false; //something is wrong with your path!
if (ftyp & FILE_ATTRIBUTE_DIRECTORY)
return true; // this is a directory!
return false; // this is not a directory!
}
BackupResult BackupManager::copyFile(QString m_src, QString m_dst)
{
QFileInfo fi(m_src);
QString dir = fi.dir().path();
// const wchar_t* dirC = toLPCWSTR(dir);
QString src = QString(m_src).replace("/", "\\");
QString dst = QString(m_src).replace("/", "\\");
// const wchar_t* localC_src = toLPCWSTR(src);
// const wchar_t* localC_dst = toLPCWSTR(dst);
LPCWSTR localC_src = (LPCWSTR) src.utf16();
LPCWSTR localC_dst = (LPCWSTR) dst.utf16();
LPCWSTR dirC = (LPCWSTR) dir.utf16();
auto rc = CopyFileExW(localC_src, localC_dst, &BackupManager::copyProgress, this, &bStopBackup, 0);
if (rc == 0) {
DWORD lastError = GetLastError(); // Error = 0x32
bool dirExist = DirExists(dirC); // true
bool fileExists = FileExists(localC_src); // true
printWarning(TAG, QString("File Copy Error: %1").arg(getLastErrorMsg()));
#ifdef QT_DEBUG
if (FileExists(localC_src)) {
qDebug() << "#FailedCopy: Windows file exists but copy failed" << src; // this gets hit using the implemented c-style cast
}
else {
if (QFile::exists(src)) {
qDebug() << "#FailedCopy: Windows is really being full of shit! " << src; // this always gets triggered when using QString::toStdWString.c_str()
}
else {
qDebug() << "#FailedCopy: Windows file copy failed outright" << src;
}
}
#endif
// copy failed
return BackupResult::IOError;
}
// copy success
return BackupResult::Success;
}
I suggest you check whether the copied file handle is opened in another process.
I created a simple sample, the code is as follows:
#include <iostream>
#include <Windows.h>
using namespace std;
int main(int argc, const char* argv[])
{
CopyFileEx(L"D:\\test\\test.txt", L"D:\\test\\test2.txt", NULL, NULL, 0, 0);
int e = GetLastError();
cout << "error code is :" << e << endl;
return 0;
}
This sample of course successfully copied the file.But if I add the code to open the handle of this file, it will return error code 32.
#include <iostream>
#include <Windows.h>
using namespace std;
int main(int argc, const char* argv[])
{
CreateFileW(L"D:\\test\\test.txt", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,NULL);
CopyFileEx(L"D:\\test\\test.txt", L"D:\\test\\test2.txt", NULL, NULL, 0, 0);
int e = GetLastError();
cout << "error code is :" << e << endl;
return 0;
}
Outout:
So I think you did not close it properly after opening the handle in other locations. If you need to copy files while the handle is open, you can modify the dwShareMode parameter to FILE_SHARE_READ. In this way, the file copy operation can be performed when the handle is opened.
Here is the sample:
#include <iostream>
#include <Windows.h>
using namespace std;
int main(int argc, const char* argv[])
{
CreateFileW(L"D:\\test\\test.txt", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,NULL);
CopyFileEx(L"D:\\test\\test.txt", L"D:\\test\\test2.txt", NULL, NULL, 0, 0);
int e = GetLastError();
cout << "error code is :" << e << endl;
return 0;
}
Output:
More reference:CreateFileW and CopyFileExA
My problem is that, I can't retrieve a Registry value correctly in a hex format, converting it to a string so it appears this way in my ListBox (image 1), I know how to do it correctly in C#, but I'm new to C++.
String ks;
DWORD reg2()
{
char value[255];
DWORD BufferSize = BUFFER;
RegGetValue(HKEY_CURRENT_USER, namebuf, "415846243", RRF_RT_ANY, NULL, (PVOID)&value, &BufferSize);
std::wstringstream box_messages;
box_messages << value;
ks = box_messages.str().c_str();
}
void __fastcall TAAP_Test::Button1Click(TObject *Sender)
{
ListBox1->Items->Add(ks);
}
This image shows the result on my program:
This image shows what registry value I'm trying to retrieve:
The Registry value you are reading is in a raw binary format (REG_BINARY). What you see in the Registry Editor is not the raw data itself, but a human-readable hex representation of the data. RegGetValueA() will not return that hex representation to you, it will return the raw binary data instead.
You are reading the raw data fine (minus the lack of error checking), but you are trying to write it as-is to your std::wstringstream, which is why you get the weird result you see in your ListBox. You need to instead loop through the individual bytes of the data, encoding each byte to a hex representation that is then put in to your std::wstringstream, eg:
#include <iostream>
#include <iomanip>
void reg2()
{
char value[255];
DWORD BufferSize = sizeof(value);
if (RegGetValueA(HKEY_CURRENT_USER, namebuf, "415846243", RRF_RT_ANY, NULL, value, &BufferSize) == ERROR_SUCCESS)
{
std::wstringstream box_messages;
for(DWORD i = 0; i < BufferSize; ++i) {
box_messages << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(value[i]) << L" ";
}
ks = box_messages.str().c_str();
}
else
ks = _D("error");
}
Alternatively, the RTL has an IntToHex() function available:
#include <System.SysUtils.hpp>
void reg2()
{
char value[255];
DWORD BufferSize = sizeof(value);
if (RegGetValueA(HKEY_CURRENT_USER, namebuf, "415846243", RRF_RT_ANY, NULL, value, &BufferSize) == ERROR_SUCCESS)
{
std::wstringstream box_messages;
for(DWORD i = 0; i < BufferSize; ++i) {
box_messages << IntToHex(static_cast<int>(value[i]), 2).c_str() << _D(" ");
}
ks = box_messages.str().c_str();
/* or simpler:
ks = _D("");
for(DWORD i = 0; i < BufferSize; ++i) {
ks += (IntToHex(static_cast<int>(value[i]), 2) + _D(" "));
}
*/
}
else
ks = _D("error");
}
Alternatively, UnicodeString has a cat_sprintf() method available:
void reg2()
{
char value[255];
DWORD BufferSize = sizeof(value);
if (RegGetValueA(HKEY_CURRENT_USER, namebuf, "415846243", RRF_RT_ANY, NULL, value, &BufferSize) == ERROR_SUCCESS)
{
ks = _D("");
for(DWORD i = 0; i < BufferSize; ++i) {
ks.cat_sprintf(_D("%02X "), static_cast<int>(value[i]));
}
}
else
ks = _D("error");
}
I am trying to read the physical memory values in Hardware\ResourceMap\System Resources\Physical Memory using the following code:
#include <iostream>
#include <conio.h>
#include <windows.h>
#include <string>
#include <stdlib.h>
using namespace std;
int main()
{
HKEY hKey = NULL;
LPCTSTR pszSubKey = L"Hardware\\ResourceMap\\System Resources\\Physical Memory";
LPCTSTR pszValueName = L".Translated";
if (! RegOpenKey(HKEY_LOCAL_MACHINE, pszSubKey, &hKey) == ERROR_SUCCESS)
{
cout << "RegOpenKey failed" << endl;
return 0;
}
DWORD dwType = 0;
LPBYTE lpData = NULL;
DWORD dwLength = 0;
if (! RegQueryValueEx(hKey, pszValueName, 0, &dwType, NULL, &dwLength) == ERROR_SUCCESS)
{
cout << "RegOpenKey failed" << endl;
return 0;
}
lpData = new BYTE[dwLength];
RegQueryValueEx(hKey, pszValueName, 0, &dwType, lpData, &dwLength);
RegCloseKey(hKey);
DWORD dwResourceCount = *(DWORD*)(lpData + 16);
auto pmi = lpData + 24;
for (int dwIndex = 0; dwIndex < dwResourceCount; dwIndex++)
{
auto start = *(uint64_t*)(pmi + 0);
cout << "-> 0x" << hex << start;
auto length = *(uint64_t*)(pmi + 8);
cout << "\t + 0x" << hex << length;
auto endaddr = start + length;
cout << "\t0x" << hex << endaddr << endl;
pmi += 20;
}
delete[]lpData;
}
A sample output:
-> 0x1000 + 0x57000 0x58000
-> 0x59000 + 0x46000 0x9f000
-> 0x100000 + 0xc855f000 0xc865f000
-> 0xc8666000 + 0xbf3000 0xc9259000
-> 0xc9759000 + 0x13779000 0xdced2000
-> 0xdd0d8000 + 0x3c000 0xdd114000
-> 0xddfff000 + 0x1000 0xde000000
-> 0x100000000 + 0x41f0000 0x1041f0000
The problem is that the last length value is incorrect.
Instead of 0x41f0000, the Registry editor shows 0x41f000000 to be the correct value:
I have been researching this issue for the past few days, but cannot figure out why I get a false value here.
Can anyone with more experience using the Win32 API help me?
if value type is REG_RESOURCE_LIST value data is CM_RESOURCE_LIST structure. need use it instead of *(DWORD*)(lpData + 16);, lpData + 24. anyway your code is incorrect in case Count != 1. what you try print is CM_PARTIAL_RESOURCE_DESCRIPTOR structures. but you not check the Type member of CM_PARTIAL_RESOURCE_DESCRIPTOR. but it cab be different. can be CmResourceTypeMemory but also can be CmResourceTypeMemoryLarge - you not take this in account. in case CmResourceTypeMemoryLarge need check Flags for
CM_RESOURCE_MEMORY_LARGE_40
CM_RESOURCE_MEMORY_LARGE_48
CM_RESOURCE_MEMORY_LARGE_64
and
you say:
Instead of 0x41f0000 the regeditor shows 0x41f000000
but 0x41f000000 is shifted on 8 bit 0x41f0000. based on this obvious that you really have here CmResourceTypeMemoryLarge with CM_RESOURCE_MEMORY_40 flag.
in this case need use Length40 member:
The high 32 bits of the 40-bit length, in bytes, of the range of
allocated memory addresses. The lowest 8 bits are treated as zero.
so code for dump CM_RESOURCE_LIST must be next:
BOOL Memory(PCM_RESOURCE_LIST pcrl, ULONG size)
{
if (size < FIELD_OFFSET(CM_RESOURCE_LIST, List))
{
return FALSE;
}
size -= FIELD_OFFSET(CM_RESOURCE_LIST, List);
if (ULONG Count = pcrl->Count)
{
PCM_FULL_RESOURCE_DESCRIPTOR List = pcrl->List;
do
{
if (size < FIELD_OFFSET(CM_FULL_RESOURCE_DESCRIPTOR, PartialResourceList.PartialDescriptors))
{
return FALSE;
}
size -= FIELD_OFFSET(CM_FULL_RESOURCE_DESCRIPTOR, PartialResourceList.PartialDescriptors);
DbgPrint("InterfaceType=%x BusNumber=%u\n", List->InterfaceType, List->BusNumber);
if (ULONG n = List->PartialResourceList.Count)
{
PCM_PARTIAL_RESOURCE_DESCRIPTOR PartialDescriptors = List->PartialResourceList.PartialDescriptors;
do
{
if (size < sizeof(CM_PARTIAL_RESOURCE_DESCRIPTOR))
{
return FALSE;
}
size -= sizeof(CM_PARTIAL_RESOURCE_DESCRIPTOR);
ULONG64 Length = PartialDescriptors->u.Memory.Length;
switch (PartialDescriptors->Type)
{
case CmResourceTypeMemoryLarge:
switch (PartialDescriptors->Flags & (CM_RESOURCE_MEMORY_LARGE_40|
CM_RESOURCE_MEMORY_LARGE_48|CM_RESOURCE_MEMORY_LARGE_64))
{
case CM_RESOURCE_MEMORY_LARGE_40:
Length <<= 8;
break;
case CM_RESOURCE_MEMORY_LARGE_48:
Length <<= 16;
break;
case CM_RESOURCE_MEMORY_LARGE_64:
Length <<= 32;
break;
default:
DbgPrint("unknown mamory type\n");
continue;
}
case CmResourceTypeMemory:
DbgPrint("%016I64x %I64x\n",
PartialDescriptors->u.Memory.Start.QuadPart, Length);
break;
}
} while (PartialDescriptors++, --n);
}
} while (List++, --Count);
}
return size == 0;
}
also when we get it data - need not forget close key handle even on error (you not do this when RegQueryValueEx fail) and use RegOpenKeyExW instead RegOpenKey for ability specify the desired access rights to the key. the use 2 sequential calls to RegQueryValueEx (with 0 buffer and allocated once buffer) also not the best. because in theory buffer size can changed (some change value) between this 2 calls and you can fail got data on second call RegQueryValueExtoo. also we can already on first call allocate reasonable memory space, and only if it will be not enough - reallocate on next call. so better call this in loop until we got ERROR_MORE_DATA and first time call with already not empty buffer:
ULONG Memory()
{
HKEY hKey;
ULONG dwError = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"Hardware\\ResourceMap\\System Resources\\Physical Memory",
0, KEY_READ, &hKey);
if (dwError == NOERROR)
{
ULONG cb = 0x100;
do
{
dwError = ERROR_NO_SYSTEM_RESOURCES;
union {
PVOID buf;
PBYTE pb;
PCM_RESOURCE_LIST pcrl;
};
if (buf = LocalAlloc(0, cb))
{
ULONG dwType;
if ((dwError = RegQueryValueExW(hKey, L".Translated",
0, &dwType, pb, &cb)) == NOERROR)
{
if (dwType == REG_RESOURCE_LIST)
{
if (!Memory(pcrl, cb))
{
DbgPrint("error parsing resource list\n");
}
}
else
{
dwError = ERROR_INVALID_DATATYPE;
}
}
LocalFree(buf);
}
} while (dwError == ERROR_MORE_DATA);
RegCloseKey(hKey);
}
return dwError;
}