0

I'm working on an utility to hook various bits of the Windows API used by different applications. The aim of the project is, at the moment to make any application portable by redirecting filesystem and registry calls to custom locations using easyhook and boost (specifically the property_tree library).

I'm currently working on the registry part of the project and I have successfully created analogues to the RegCreateKey(ExA/ExW/A/W), RegOpenKey(ExA/ExW/A/W) and RegCloseKey functions and they work fine (i made a virtual handle system to create and translate hKey handles). They work by basically translating everything to strings and saving them in a boost property tree (and then writing the tree to an .info file).

I started working on the RegSetValue and RegQueryValue functions, the ones that actually handle the data and encountered a major problem. Below are the two functions. Note that these are called by easyhook with the same parameters of the original winapi call.

LSTATUS WINAPI myRegSetValueExA(HKEY hKey, LPCSTR lpValueName, DWORD Reserved, DWORD dwType, const BYTE* lpData, DWORD cbData)
{
    boost::property_tree::ptree VirtualRegistry;
    boost::property_tree::read_info("VirtualRegistry.info", VirtualRegistry);
    VirtualRegistry.put(boost::property_tree::ptree::path_type(std::string(GetPathFromHandleA(hKey) + '\\' + lpValueName), '\\'), reinterpret_cast<const char*>(lpData));
    boost::property_tree::write_info("VirtualRegistry.info", VirtualRegistry);

    return ERROR_SUCCESS;
}

This works fine for REG_SZ calls but other types of data are not saved correctly.

LSTATUS WINAPI myRegQueryValueExA(HKEY hKey, LPCSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData)
{
    boost::property_tree::ptree VirtualRegistry;
    boost::property_tree::read_info("VirtualRegistry.info", VirtualRegistry);
    try
    {
        *lpData = reinterpret_cast<const BYTE*>VirtualRegistry.get_child(boost::property_tree::ptree::path_type(std::string(GetPathFromHandleA(hKey) + "\\" + lpValueName), '\\')).data();
        return ERROR_SUCCESS;
    }
    catch (const boost::property_tree::ptree_bad_path& e1)
    {
        std::cout << "\n" << "ENTRY NOT FOUND" << "\n";
        return ERROR_FILE_NOT_FOUND;
    }
}

This does not work. The reinterpret cast on line 6 is invalid and it won't compile.

The problem is the way my functions handle different types of data. A registry call can have many different value types and the way I wrote myRegSetValue only seems to work for REG_SZ. I will also have to save the value type when writing the call to the file but that does not solve the problem.

So my question is, is there any way of saving the raw data of a call, as a string, without having to cast it as a string, so that it works for all types of data, and then reading it back from the file from a string back to raw data and passing it to the application?

I guess I could write a separate interpreter for each key type but i would really rather not because it would be very hacky and also would break applications that do not use the registry API correctly and store invalid values in the registry (like, for example, the Unity game Sunless Sea).

Thank you, I hope I explained this in enough detail.

enzeinzen
  • 11
  • 4

1 Answers1

1

It appears that the real problem is already the implementation of myRegSetValueExA. When you get lpData, you can't assume that it points to data that will remain valid. You must store not the pointer, but the pointed-to data.

That's also why cbData is necessary; you need to know how much data to store. You can't rely on strlen since the type might not be REG_SZ.

Note that your assumption about This works fine for REG_SZ is true only because VirtualRegistry.put(std::string key, const char* value) is a convenient overload which calls strlen(buf) for you.

The solution is to create explicitly std::string data(static_cast<const char*>(lpData), cbData) and use that in the put. To retrieve it, you use .get<std::string>(key) instead of get_child(key).data().

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • I don't create a std::string from it. I just reinterpret cast it to const char*. The std::string is the path argument for the .put method of the property tree. Should i put cbData in the cast somehow? – enzeinzen Nov 26 '20 at 14:56
  • Do you mean domething like this? std::string data(static_cast(lpData), static_cast(cbData)); (It doesn't work, invalid type conversion) – enzeinzen Nov 26 '20 at 15:08
  • @enzeinzen: Ah, I thought the boost property tree stores a `std::string`, so the `put` method took a string path and a string value. Will fix. – MSalters Nov 26 '20 at 15:12
  • Thank you for your answer. I understand the concept, however the static cast is invalid. (invalid type conversion). I used a reinterpret cast instead and only an empty string is saved. – enzeinzen Nov 26 '20 at 15:32
  • @enzeinzen: In `myRegQueryValueExA` you mean? You did get the `std::string` out of the property tree? But failed to write it back? Do have a peek at the semantics of the real `RegQueryValueExA`. You need to check `lpcbData`, see if it's large enough, then copy the string contents to `lpData`, and update `lpcbData`. – MSalters Nov 26 '20 at 15:36
  • no, in myRegSetValueExA, when converting lpData to a string before being passed to the .put. – enzeinzen Nov 26 '20 at 15:41
  • Well, the `reinterpret_cast` really is a "I know what I'm doing" cast, which is just hiding the real error. Did you write use `std::string data(static_cast(lpData), cbData)` exactly as I wrote? What was the error message on that? – MSalters Nov 26 '20 at 15:47
  • Yes, exactly the same. The error message is "'static_cast': cannot convert from 'const BYTE*' to 'const char*'" – enzeinzen Nov 26 '20 at 15:51
  • @enzeinzen: Oh, right, `BYTE` is unsigned. Yup, that's a `reinterpret_cast` then. – MSalters Nov 26 '20 at 15:54
  • reinterpret_cast just saves an empty string. however, boost's lexical cast seems to work – enzeinzen Nov 26 '20 at 16:10