0

In my C# application I have a variable lpData of type IntPtr (received from a call to unmanaged code), and it points to a string.

I have to replace this string with another value.

I tried:

int RegQueryValueExW_Hooked(
        IntPtr hKey,
        string lpValueName,
        int lpReserved,
        ref Microsoft.Win32.RegistryValueKind lpType,
        IntPtr lpData,
        ref int lpcbData)
{ 
    lpData = Marshal.StringToHGlobalUni("new string"); 
    ...
}

but this doesn't seem to replace the actual string.

Can someone point me in the right direction on how to do this?

Thanks

L-Four
  • 13,345
  • 9
  • 65
  • 109
  • Replace what actual string? Are you trying to change a value in the registry? Strings are immutable, so you can only replace a string with a new reference. However, for that particular API call you can use a `StringBuilder` instead - perhaps that will work for what you want to do? – Matthew Watson Feb 12 '14 at 12:12
  • In my case StringBuilder crashes (see http://stackoverflow.com/questions/21722555/easyhook-32-bit-application), so I tried to use IntPtr instead, which doesn't crash; but then I don't know how to replace the string. – L-Four Feb 12 '14 at 12:18
  • I see your other post here: http://stackoverflow.com/questions/21722555/easyhook-32-bit-application .... the problem is that it is the "caller" of that API that allocates the buffer (and it's size) and supplies the pointer. As it isn't a pointer to pointer/ reference parameter, you can't change the pointer back to the caller. – Colin Smith Feb 12 '14 at 12:19

1 Answers1

3

Of course it doesn't replace the string - you're getting the pointer to the string where your caller has the value. You're only replacing the value of your "local variable" (the parameter), this doesn't change anything on the caller's side.

If you want to modify the value on the original pointer (and make sure you actually do want that, this is where "weird errors" lurk - it's very easy to overwrite surrounding variables, forget about the null terminator, etc.), you can use Marshal.Copy, for example:

var bytes = Encoding.Unicode.GetBytes("your string\0");

Marshal.Copy(bytes, 0, lpData, bytes.Length);

Again - this is a very dangerous behaviour and you shouldn't be doing this. You're violating several contracts implied by parameter passing etc.

Now that I've answered your question, let me talk about how wrong you are about actually needing to do this (and this is very much related to your other post about using the StringBuilder).

You are trying to modify a value passed to you as a parameter. However, the string was allocated by the caller. You don't even know how long it is! If you start copying data to that pointer, you're going to overwrite the data of eg. completely different variables, that just randomly happened to be allocated just after the string. This is considered "very bad".

Instead, what you want to do, is follow the proper process that RegQueryValueEx has (http://msdn.microsoft.com/en-us/library/windows/desktop/ms724911(v=vs.85).aspx). That means you first have to check the lpcbData value. If it is large enough to hold all the bytes you want to write, you just write the data to the lpData and set the lpcbData value to the proper length. If not, you still set lpcbData, but return ERROR_MORE_DATA. The caller should then call RegQueryValueEx again, with a larger buffer.

The sample code would be something like this:

string yourString = "Your string";

int RegQueryValueExW_Hooked(
        IntPtr hKey,
        string lpValueName,
        int lpReserved,
        ref Microsoft.Win32.RegistryValueKind lpType,
        IntPtr lpData,
        ref int lpcbData)
{ 
    var byteCount = Encoding.Unicode.GetByteCount(yourString);

    if (byteCount > lpcbData)
    {
        lpcbData = byteCount;

        return ERROR_MORE_DATA;
    }

    if (lpData == IntPtr.Zero)
    {
        return ERROR_SUCCESS;
    }

    lpcbData = byteCount;

    var bytes = Encoding.Unicode.GetBytes(yourString);   
    Marshal.Copy(bytes, 0, lpData, bytes.Length);

    return ERROR_SUCCESS;
}

Do note that this is just what I've written after quickly glancing through the documentation - you should investigate further, and make sure you're handling all the possible cases. .NET doesn't protect you in this case, you can cause major issues!

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • 1
    @L-Three You can't print out a byte array as if it were a string. You have to use [`Encoding.GetString()`](http://msdn.microsoft.com/en-us/library/744y86tc%28v=vs.110%29.aspx) to convert it. – Matthew Watson Feb 12 '14 at 12:34
  • Thank you!!!!! Now I understand better how it should be done. Your implementation seems to work, but one strange thing remains: if the caller gets the registry value (that was overridden), it gets in the the format of a byte array. If I do Encoding.Default.GetString(registryvalue), then I get 'y o u r s t r i n g'. Do you know how to make it a string instead of a byte array? – L-Four Feb 12 '14 at 12:58
  • @L-Three I've encoded the string in unicode, so to get the correct string, you have to decode in unicode as well, eg. `Encoding.Unicode.GetString(registryvalue)`. In any case, you should report the proper encoding using the `lpType` parameter, and on the caller side, read it as such. – Luaan Feb 12 '14 at 13:08
  • Yes, but the problem is that I cannot change the original application. It reads the registry value and expects a string; not a byte array :( – L-Four Feb 12 '14 at 13:10
  • @L-Three It certainly doesn't expect a string. There is no such thing as a string in memory - it's just a bunch of bytes that someone decided to give a special meaning to. `RegQueryValueExW` should output a unicode string, that's what the `W` in the name means (wide). If you want an ANSI string (that's the `Encoding.Default` in most cases), you have to call (and hook on) `RegQueryValueExA`. Then you'd encode the string using `Encoding.Default` instead of `Encoding.Unicode` and you're done :) – Luaan Feb 12 '14 at 13:14
  • Well, I changed it to RegQueryValueExA, but that hook is never called. It's the RegQueryValueExW that is being called... – L-Four Feb 12 '14 at 13:55
  • @L-Three Well, it seems that the caller is expecting an undocumented behaviour in that case (it might be that it simply isn't unicode aware). If you're positive this is safe, simply use the ANSI encoding in your `RegQueryValueExW`. Also, don't forget to null terminate the string, if needed. – Luaan Feb 12 '14 at 13:59
  • I tried that, it's still returning a byte array when I do Registry.LocalMachine.OpenSubKey in the client, and it contains only a bunch of question marks... – L-Four Feb 12 '14 at 14:25
  • @L-Three What returns a byte array? You're adding some of your own code that's causing issues, most likely. And yes, if you do Registry.LocalMachine.OpenSubKey, it gives a bunch of question marks *because it expects the string to be in unicode*. – Luaan Feb 12 '14 at 14:34
  • Well, if I create a registry key of type String, and I read it with Registry.LocalMachine.OpenSubKey, you get back a string. If I do the same, but I enable the hook to override this registry string, it returns a byte array. – L-Four Feb 12 '14 at 14:37
  • @L-Three Oh, you have to set `lpType` to the correct type, eg. `RegistryValueKind.String` :) My bad, I seem to have removed the information about that from my answer, sorry :D – Luaan Feb 12 '14 at 14:38
  • I did that, yes, but still a byte array :) – L-Four Feb 12 '14 at 14:47
  • Sorry! I had to set it in the beginning of RegQueryValueExW_Hooked operation. Now I get back a string! Thanks for the effort of helping me!!! – L-Four Feb 12 '14 at 14:48