0

I am writing a method to add data to registry key using RegSetValueEx() after checking the registry the value has not been written to the key. All 3 functions I use to load the hive, key and add value are returning ERROR_SUCCESS which has got me stumped. I have read this and this, however both of these questions suggest I should be receiving an error if I were coming across these problems.

I feel I am missing something to do with wide strings, ANSI and Unicode, although I cannot understand what. I could be way off base here though. I will also add that Visual Studio is running as Admin.

This is the code I have written so far:

#include <iostream>
#include <Windows.h>

int addValuetoRegistryKey(std::string executable) {

    LPCWSTR registryKey = L"Software\\Classes\\mscfile\\shell\\open\\command\\";
    HKEY hiveHandle;
    HKEY keyHandle;

    if (RegOpenCurrentUser(KEY_SET_VALUE, &hiveHandle) != ERROR_SUCCESS) {

        wchar_t buf[256];
        FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            buf, (sizeof(buf) / sizeof(wchar_t)), NULL);

        /* Display error */
        std::wcout << "Opening hive failed: " << buf << std::endl;

        return -1;

    }
    else {

        std::wcout << "HKCU Hive opened successfully" << std::endl;

    }

    if (RegOpenKeyEx(hiveHandle, registryKey, 0, KEY_SET_VALUE, &keyHandle) != ERROR_SUCCESS) {

        wchar_t buf[256];
        FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            buf, (sizeof(buf) / sizeof(wchar_t)), NULL);

        /* Display error */
        std::wcout << "Opening key failed: " << buf << std::endl;

        return -1;

    }
    else {

        std::wcout << registryKey << " opened successfully" << std::endl;

    }

    if (RegSetValueEx(keyHandle, NULL, 0, REG_SZ, (LPCBYTE)executable.c_str(), executable.size() + 1) != ERROR_SUCCESS) {

        wchar_t buf[256];
        FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            buf, (sizeof(buf) / sizeof(wchar_t)), NULL);

        /* Display error */
        std::wcout << "Writing value to  key failed: " << buf << std::endl;

        return -1;
    }
    else {

        std::wcout << executable.c_str() << " written to key" << std::endl;

    }

    RegCloseKey(keyHandle);
    RegCloseKey(hiveHandle);

    return 0;
}

int main() {

    addValuetoRegistryKey("C:\\Windows\\System32\\cmd.exe");

}

Full disclosure: I am a computer security graduate, and some users may notice that I am trying to implement a method for UAC bypass. This is simply for educational purposes, instead of implementing metasploit modules, I would like to write the code and understand the workflow myself.

This is the data returned to the console: enter image description here

RandomHash
  • 669
  • 6
  • 20
  • 1
    Is this a case of [Registry Virtualization](https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-virtualization)? – IInspectable Feb 01 '21 at 19:13
  • 1
    `LPCWSTR registryKey = L"Soft...` This indicates you are compiling for Unicode. `(LPCBYTE)executable.c_str()` But this is sending an ANSI string to the wide version `RegSetValueExW`. – dxiv Feb 01 '21 at 19:13
  • @dxiv OK. I can look into that, a little bit of a gap in my knowlege. Should it not fail if that were the case though? – RandomHash Feb 01 '21 at 19:17
  • @IInspectable, I am reading that document now, although I feel that as an Administrator, the write should be successful. It works from a command prompt using reg add. Still, I will read further into this, Thanks. – RandomHash Feb 01 '21 at 19:19
  • @RandomHash The function will trust that what you are sending is a wide string, since it has no independent means to verify that. So the call won't fail, but it won't write what you expected, either. – dxiv Feb 01 '21 at 19:22
  • @dxiv Got it. Could you recommend a good resource explaining the differences between the ANSI and Unicode versions of these methods, and how to correctly supply data to these? I am looking myself also. I understand that ANSI and Unicode are different encoding methods, but in the context of C++ and the WinAPI, I must admit my knowledge fails me. – RandomHash Feb 01 '21 at 19:24
  • Strangely, when using the RegSetKeyValueA function, as suggested in the Microsoft Docs, the value is written to the registry. Although the value actually becomes C:\Windows\SysWow64\cmd.exe. It still has the required affect, although I will have to test this on an x86 system to see if it works there also. I still feel as if I am missing something with regard to ANSI and Unicode differences though. – RandomHash Feb 01 '21 at 19:34
  • 1
    @RandomHash You can find some starting points at [1](https://learn.microsoft.com/en-us/windows/win32/learnwin32/working-with-strings), [2](https://stackoverflow.com/questions/4592261/windows-api-ansi-and-wide-character-strings-is-it-utf8-or-ascii-utf-16-or-u). – dxiv Feb 01 '21 at 19:45

1 Answers1

1

Thanks for dxiv for pointing me toward the right resources.

Firstly, after reading the Microsoft information, I decided to change all strings to wide strings, as ANSI no longer needs to be supported in new applications, or when NOT taking user input.

std::string executable to LPCWSTR executable,

std::wcout << executable.c_str() << " written to key" << std::endl; to std::wcout << executable << " written to key" << std::endl;

addValuetoRegistryKey("C:\\Windows\\System32\\cmd.exe"); to addValuetoRegistryKey(L"C:\\Windows\\System32\\cmd.exe");

Next I chose to use the specific wide character implementation of RegSetValueEx, RegSetValueExW.

Using (wcslen(executable) + 1) * sizeof(wchar_t) to calculate the length of executable for the final parameter.

The final code is below:

#include <iostream>
#include <Windows.h>

int addValuetoRegistryKey(LPCWSTR executable) {

    LPCWSTR registryKey = L"Software\\Classes\\mscfile\\shell\\open\\command\\";
    HKEY hiveHandle;
    HKEY keyHandle;

    if (RegOpenCurrentUser(KEY_SET_VALUE, &hiveHandle) != ERROR_SUCCESS) {

        wchar_t buf[256];
        FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            buf, (sizeof(buf) / sizeof(wchar_t)), NULL);

        /* Display error */
        std::wcout << "Opening hive failed: " << buf << std::endl; 

        return -1;

    }
    else {

        std::wcout << "HKCU Hive opened successfully" << std::endl;

    }

    if (RegOpenKeyEx(hiveHandle, registryKey, 0, KEY_SET_VALUE, &keyHandle) != ERROR_SUCCESS) {

        wchar_t buf[256];
        FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            buf, (sizeof(buf) / sizeof(wchar_t)), NULL);

        /* Display error */
        std::wcout << "Opening key failed: " << buf << std::endl;

        return -1;

    }
    else {

        std::wcout << registryKey << " opened successfully" << std::endl;

    }


    if (RegSetValueExW(keyHandle, NULL, 0, REG_SZ, (LPCBYTE) executable, (wcslen(executable) + 1) * sizeof(wchar_t)) != ERROR_SUCCESS) {

        wchar_t buf[256];
        FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            buf, (sizeof(buf) / sizeof(wchar_t)), NULL);

        /* Display error */
        std::wcout << "Writing value to  key failed: " << buf << std::endl;

        return -1;
    }
    else {

        std::wcout << executable << " written to key" << std::endl;

    }

    RegCloseKey(keyHandle);
    RegCloseKey(hiveHandle);

    return 0;


}

int main() {

    addValuetoRegistryKey("C:\\Windows\\System32\\cmd.exe");

}

The value is now written to the registry correctly. Upon reading more of the Microsoft docs, the function RegSetKeyValueW was suggested. I have implemented this method, however, I wanted to work out how to do this correctly, and post here.

For anyone who stumbles upon this question, and would like an example of how to implement the RegSetKeyValueW function, you can find the code below:

if (RegSetKeyValueW(keyHandle, NULL, 0, REG_SZ, executable, (wcslen(executable) + 1) * sizeof(wchar_t)) != ERROR_SUCCESS) {

        wchar_t buf[256];
        FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            buf, (sizeof(buf) / sizeof(wchar_t)), NULL);

        /* Display error */
        std::wcout << "Writing value to  key failed: " << buf << std::endl;

        return -1;
    }
    else {

        std::wcout << executable << " written to key" << std::endl;

    }
RandomHash
  • 669
  • 6
  • 20