The Boring Details

The first step when evaluating a password manager is to see if the master password is exposed in memory in the clear. This can be done by using a simple hex editor that has the ability to interact with a process address space. HxD hex editor is one, for example (and it’s free!). Using HxD we can point it at the address space of 1Password 4.

Using HxD to open 1Password 4’s memory address space.

And we land at an interface that lists the first readable region of 1Password 4’s memory space.

Example HxD memory view.

Nothing too exciting as of yet. We can also search the entire address space of a process. For example, what if we fill in the master password in 1Password 4’s unlock dialog but don’t click the ‘unlock’ button:

Locked 1 Password 4 vault, with the master password filled in.

Surely, the password is in memory somewhere?

Using HxD to search memory for a partial string of our master password (“Z3Superpass#”), yields no results.

No dice! (Both in ASCII and Unicode)

1Password must be encrypting or performing another form of obfuscation on the entry as we type it in. Is this good enough, should we leave it alone?

Diving Deeper

To find out why our master password is not in memory while it is clearly filled into the unlock dialog, we must locate the code that interacts with it. There are multiple ways of doing this. One could identify the message loop that captures keyboard and mouse activity by locating ‘GetMessage’, ‘PeekMessage’, ‘GetWindowText’ or other Window’s APIs that typically handle user input to locate the buffer our keystrokes are being captured into and following it until we reach an encryption/obfuscation routine. This can get cumbersome and be an error prone process, especially with thick frameworks that have weird memory management that will require you to follow the buffer through numerous of copies and transformations.

Instead, we’ll use an in-house tool (‘Thread Imager’) created for reverse engineering ‘weird’ proprietary protocols at the application layer to identify where 1Password 4 interacts with our master password. The following image is of this tool ‘automagically’ identifying code areas in 1Password 4 that interact with the obfuscated password (In short, instructions that interact with data of interest, in this case, ‘Z3superpass#’, our master password, are flagged by this tool for further analysis). The output looks something like this:

Using Thread Imager to locate 1Password 4 code that interacts with the un-obfuscated master password.

Since the master password exists in memory in an obfuscated format, we anticipate, that the first result returned by our tool would be where the master password buffer is decrypted/de-obfuscated.

An excerpt of the first result identifies that our master password first appears when code was transitioning from 0x7707A75D -> 0x701CFA10.

Entry detail of Thread Imager which highlights code transfer from 0x7707A75D to 0x701CFA10 with the master password present in a buffer pointed to by in EAX, ECX.

Examining the above code location, 0x7707A75D, in a debugger (x64dbg), we confirm our theory in that we first identify ‘Z3superpass#’ at the end of a decoding function, ‘RtlRunDecodeUnicodeString’, inside ntdll.dll.

Thread Imager catching the un-obfuscated master password at the end of RtlRunDecodeUnicodeString located at 0x7707A75D.

After some brief analysis we notice that ‘RtlRunEncodeUnicodeString’ and ‘RtlRunDecodeUnicodeString’ are used to obfuscate the master password memory region to conceal it from trivial memory forensics, explaining why we couldn't locate it in HxD earlier.

If we examine the encoded buffer at the end of the ‘RtlRunEncodeUnicodeString’ function we can see the encoded master password string looks like this:

Encoded master password.

After ‘RtlRunDecodeUnicodeString’, decoded, it looks like:

Decoded master password.

Interestingly, this memory region persists at the same location in an instance of 1Password 4, at 0x00DFA790, and we can watch it as we type in the master password into the 1Password 4 unlock dialog:

Watching the buffer where the master password is stored.

The Flaw

‘RtlRunEncodeUnicodeString’ and ‘RtlRunDecodeUnicodeString’ are simple functions that mask a string using a single byte as the XOR value. This isn't too bad, and apparently this is how all Windows’ native edit controls that are passed the ‘ES_PASSWORD’ flag to make it a masked password control, work.

However, upon unlocking 1Password 4 we notice that the encoded master password is not cleared from memory:

Even worse, it is still in memory after we lock 1Password 4. So now we have a locked password vault, but with the encoded master password left residing in memory.

And to make matters worse, as we interact with the master password entry dialog, the same memory region is reused along with the same XOR byte value — giving us easy access to the encoded buffer to craft an exploit.

The Challenge

To craft a 1Password 4 version-agnostic exploit we needed to get a clearer picture of whats going on to identify how our master password was handled by 1Password 4’s workflows. We charted the output (Figure Below) by using details obtained from our tool.