I hope that most malware hunters are aware of an old way to hide registry values using null bytes in their names.
Are there any other ways to hide something in the registry?
For the purpose of this post, I won’t exploit bugs in the Registry Editor (like this one, which is related to very long key names).
Currently, “defensive tools” utilize two ways to read the registry in a running system:
- calling standard or native API functions;
- running an offline registry parser against exported hive files (these files can be obtained using API functions like RegSaveKeyExA(), or by creating and mounting a shadow copy, or by reading the files directly from a volume using an NTFS parser <1>).
<1> In most cases, registry files are locked and can’t be read using API functions, this is why registry files are read from an NTFS volume directly.
In order to hide registry data from an offline parser working on a running machine, we can exploit the lack of transaction log files support in many registry parsing libraries by forcing an operating system not to write data to primary files, leaving all modified data in transaction log files only. In this situation, “defensive tools” that read hive files directly from a volume and that don’t support transaction log files are defeated.
To do so, we need a malicious driver to set the CmpFailPrimarySave kernel variable to 3 (this is a debugging variable used to simulate failed writes to primary files of registry hives, which was left in retail builds of Windows for some reason; available in Windows Vista and later versions). After this, all changes to the registry won’t reach primary files until a reboot: if a malicious program creates an autorun entry in a registry hive using a standard API call, this change won’t hit a primary file of that hive (but this change can be observed using standard and native API functions, because the in-memory representation of registry data isn’t affected).
Such changes, however, will appear in primary files after a reboot (because a kernel is replaying transaction log files when loading a hive), so a malicious program has to remove the autorun entry from the registry as soon as the operating system is restarted and repeat the trick to maintain hidden persistence.
Of course, “defensive tools” that use standard or native API functions to view the registry can’t be fooled with this trick.
Another way to defeat offline registry parsers doing the job on a running machine is to exploit possible differences between the implementations of the registry in a Windows kernel and a third-party parser.
For example, some third-party offline registry parsers handle names of keys and values with extended ASCII characters incorrectly. In the registry, key names and value names are stored using the following encodings: UTF-16LE and Latin-1 (the latter is used when a name can be “compressed” from UTF-16LE characters to single-byte characters). Some libraries, however, use a different set: UTF-16LE and Windows-1252.
It’s possible to create two registry keys or two registry values that have different names when the correct set of encodings is used, but that share the same name if Windows-1252 is used instead of Latin-1. Such duplicate names can cause troubles when traversing the registry tree.
In the example below, I use a character with this code: 159 (0x9F). This character has no meaning in Latin-1, but it means “Ÿ” in Windows-1252. The following subkeys were created under the root key:
- “key_\x9F” (“\x9F” means a single byte with the specified hexadecimal value).
The first name from the list above is stored in UTF-16LE, while the second one is stored in Latin-1. If the second name is interpreted as stored in Windows-1252, the decoded string is “key_Ÿ” (the same as the first name in that list). If a “defensive tool” attempts to open the “\key_Ÿ” path, it will open one of these keys; another key will be unreachable.
The following Python script is used to demonstrate the issue (the script uses the python-registry library, version: 1.2.0):
#!/usr/bin/env python2 from Registry import Registry reg = Registry.Registry('StrangeNames') names_level1 =  for sk in reg.root().subkeys(): names_level1.append(sk.name()) print 'The list of first-level subkeys contains these names:' print names_level1 print 'Here are the second-level subkeys:' for name_level1 in names_level1: sk = reg.open(name_level1) for ssk in sk.subkeys(): print(ssk.name())
The following output is produced by the script:
The list of first-level subkeys contains these names: [u'key_\u0178', u'key_\u0178'] Here are the second-level subkeys: mydata1 mydata1
Compare that to the tree shown by the Registry Editor:
As you can see, the script didn’t find the “mydata2” subkey.
So, offline registry parsers may contain bugs that allow bad guys to hide registry keys and values. But…
Is it possible to hide registry data from standard and native API functions?
Basically, everything written to the registry with “regular” API calls can be read with “regular” API calls. Something can be hidden when additional restrictions are introduced.
The null byte issue, mentioned in the beginning of this post, is introduced when native API functions are called from standard API functions. Since native API functions allow a null byte to be a part of a string and standard API functions treat a null byte as a terminating character (it marks the end of a string), it could be possible to create a value that can be referenced (by its name) using native API functions, but that can’t be referenced (by its name) using standard API functions.
“Defensive tools” may introduce their own restrictions. For example, GRR treats the registry as a file system, where keys are directories and values are files. In this interpretation, a subkey of a key can’t share the same name as a value of the same key. This is wrong!
Therefore, if you use GRR to hunt for specific registry keys, you may notice that an existing key is invisible when its parent key has a value with the same name as the key in question.
Finally, there is a way to hide registry keys and values from all “regular” API calls (both standard and native).
The registry subsystem of Windows allows a usermode program to replace a primary file of a hive with a new one. The RegReplaceKeyA() function is used to do that; this function accepts four arguments:
- hKey – a handle to an open registry key;
- lpSubKey – a registry path to a mount point of a hive (under a registry key referenced by the hKey argument);
- lpNewFile – a file system path to a new primary file (which is going to replace an existing primary file);
- lpOldFile – a file system path to a file used to store a backup copy of hive data.
Under the hood, the following actions are performed by the system:
- an original primary file of a hive (which was specified using the hKey and lpSubKey arguments) is renamed to lpOldFile;
- a new primary file (lpNewFile) is given the original name of an original primary file (for example, “C:\Windows\System32\config\SOFTWARE“).
After the function returns, the operating system continues to run with an old file (lpOldFile) used as a primary file of a replaced hive (no data from a new primary file is used). After a reboot, a new primary file will be loaded and used instead of an old one. The effects of the RegReplaceKeyA() function can be seen after a reboot, but not before.
The trick is that a malicious program can export current data of a registry hive using the RegSaveKeyExA() function, load an exported file and write some new registry data to it (for example, create a malicious autorun entry), unload the file, and then replace that registry hive with the patched file.
The modified registry data won’t be visible to “regular” API calls, because the operating system is running with an old primary file used as a backing file for a hive in question. During the boot, modified registry data becomes visible to the operating system (a new primary file is loaded). After a boot, a malicious program can remove registry keys and values it created before, then it can repeat the trick to store such registry keys and values again (and they will become invisible until the next boot).
The problem with this method is that registry data created or modified after the RegReplaceKeyA() function has returned is missing from a new primary file. There is no way to edit a new primary file before the operating system is restarted (unless you are in the kernel mode). But there are some tricks that can be used to bring up-to-date registry data to a new primary file, which won’t be disclosed here (I don’t want to help bad guys; for the same reason, there is no proof-of-concept code posted).
This is it!
Update (2021-07-02): minor edits.