Revealing Excel Password Secrets Stored in Process Memory


During a red team exercise, it is common to maintain a Command and Control (C2) in a compromised workstation and search for files on the personal drive. Sometimes, there is the possibility of discovering a password Excel file, but when attempting to open it, a password prompt appears. At this point, one might extract the Excel password hash using office2hashcat and try to crack it with hashcat, which could be challenging depending on the password's complexity.
Recently, my teammate Jonathan and I encountered a similar situation. However, this time, the compromised user was opening the password-protected Excel file on his workstation. My teammate had a brilliant idea to examine the memory of the Excel process to see if the password was stored there, and that's how our research journey began.

Thought Process to Understand How Excel Handles Entered Password

In this section, we will guide you through the process of reverse engineering "Excel.exe" to gain a deeper understanding of how passwords for encrypted spreadsheets are handled. This understanding may enable us to retrieve the password for a password-protected Excel file within the Excel process if it is being opened.
Before begin reversing Excel, a password-protected Excel file was created using a "highly secure" password "VERYSECUREPASSWORD" as an example.
Afterwards, we began the process of reverse engineering "Excel.exe" in order to identify the point at which it begins handling entered passwords. Luckily, the PDB for "Excel.exe" was available, which allowed us to search for functions related to passwords in IDA. One function that caught our attention was called "PasswordDialog".
We then fired Windbg to set a breakpoint on this function, in order to observe whether it would be triggered when the password prompt appeared.
Searching function containing "Password"
The breakpoint was triggered as expected when I opened the password-protected Excel. From there, we were able to trace the calling stack and arrived at "HrFullSaveFlushPkgEx" function.
Call stack of the "PasswordDialog" function
After several attempts, we observed "HrFullSaveFlushPkgEx" called a function named "mso20win32client!MsoHrLoadCryptSession" appeared to be responsible for handling the password. It takes the entered password as the first argument.
Entered password was used as first argument to "MsoHrLoadCryptSession" function
Since this function was undocumented, I decided to search on GitHub for more information. This led me to the source code for Windows XP, where we found a comment about the function. Upon examining the comment, it became apparent that our password was likely used to generate the key.
The Windows XP source code provides a description of the function
After providing a valid password for the password-protected Excel file, we executed both the crypt function and its inner function, "HrCheckPwd". The return value of 0 indicated that the password validation was successful.
Inner function of MsoHrLoadCryptSession
Return 0 when password is valid
On the contrary, it comes as no surprise that providing an incorrect password will result in an error code being returned.
Return 0xe0040603 when password is incorrect
Now that we have a better understanding of how Excel validates the entered password, our next step is to determine whether the plaintext password is stored elsewhere in the process, such as in the heap, or if it will be erased.
Continuing our tracing of the flow, we eventually reached a function that takes the password as an argument.
Function that takes entered password as argument
Upon closer examination, you may notice that the function does not only take the password as an argument, but also includes two additional bytes before the actual password that appear to represent the password length, as we have observed during our testing.
Password structure
Here is an example of what the structure might look like in pseudocode:
typedef struct Password {
DWORD passwordLength;
} Password;
Further down the line, we encountered an interesting function called "FHpAllocCore":
A function appears to allocate memory
Within the "FHpAllocCore" function, there is an inner function called "AllocateEx" that appears to be relevant to memory allocation.
"AllocateEx" in "FHpAllocCore"
Further examination, we found it is a wrapper function of NTDLL RtlAllocateHeap. This function will assign a block from the heap with a size of 0x26 (38 in decimal) which is exactly size of password length (2) + password length in WCHAR (36). We then obtained a heap address to the newly allocated block as a return value. (0x1a35537a370 in this case)
Wrapper function of NTDLL RtlAllocateHeap
After finishing the "FHpAllocCore" function and advancing a few steps further, we reach another function called "BltB," which accepts three arguments (password, heap address, and password length). It is reasonable to assume that this function will do something with the entered password.
IDA view from FHpAllocCore to BltB
Register setup when calling BltB
Inside the BltB function, there is a function called "memmove". Apparently, it attempts to move values from one memory address to another.
"memmove" inside "BltB" function
By setting a breakpoint at the "memmove" function, we observed that it attempted to copy the password structure (password length + password) from the stack to the heap address, which was the heap block address allocated previously.
memmove execution to copy password structure from stack to heap
After rerunning and opening a password-protected Excel file multiple times, we confirmed that our assumption is correct. The entered password is indeed persisted in the heap.
Password stored in heap
It is worth mentioning that an incorrect password will not be stored in the heap. Furthermore, after conducting several tests, we discovered that the reason the password is stored in memory is because when you save the Excel file again, Excel will use this password in heap memory to re-encrypt the file by calling the "MsoHrCreateCryptSession" function.
Having confirmed that the entered password is indeed saved in the heap memory, our next step is to determine how to locate it programmatically. While it is possible to search every heap block for the password, this approach would be excessively time-consuming.
Fortunately, we were able to discover a signature pattern located near the address in the heap that stores the password. Because the heap address for the password is also stored within the heap itself, this finding simplifies our search process considerably.
Address storing the password is stored in the heap
Upon comparing another Excel instance with the password stored in memory, we found that both instances shared a common signature. Additionally, they had the exact same offset (0x30) to the address storing the password.
Signature to seach for the address storing the password
So we performed additional reverse engineering to gain a better understanding of this signature. Further investigation revealed that a part of the signature (0x2375d68f) was hardcoded.
Signature exists in the assembly code
As we delved deeper, we discovered that every time we open an Excel spreadsheet, Excel executes a constructor-like function called "SH". This function takes the value 0x05 as the second unsigned char argument and 0x2375D68F as the third unsigned long argument:
The signature we found is part of the SH::SH function parameters
This deassembled code shows more clearly how the hardcoded signature is constructed in this constructor function.
Deassembled code of SH::SH function
Furthermore, the signature value is passed to a function called "LogObjectLifetimeEvent." In the disassembled code for "LogObjectLifetimeEvent," the "0x05" and "0xff" represent the LifeTimeObjectType, while "0x2375d68f" represents the ulong type.
Deassembled code for "LogObjectLifetimeEvent"
Based on the information above, we can conclude that the signature is a hardcoded value, while "0x05" and "0xFF" are enum values of the LifeTimeObjectType. Additionally, "0x2375D68F" represents a static ulong value for this log object.

Summary and Proof of Concept

Based on our research, we have discovered that when a password for a password-protected spreadsheet is entered and validated in an Excel file, it will then be stored in the heap memory of the process. Additionally, we have identified a common signature near the address in memory where the password is stored, which can be used to locate the password more efficiently.
Based on the study of this, a tool called "officedump" was created. With the aim to use the signature and offset discussed to extract password from process memory.
POC of officedump
Currently we only tested on Windows 10/11 with Microsoft Office 365 installed. Please feel free to let me know your test result.
As a bonus point, "officedump" has an additional capability to dump the passwords of password-protected Word documents found in the Word process memory. It should be noted, however, that this process utilizes a different set of offsets and signatures compared to Excel. Last but not least, reverse engineering Word will not be covered in this blog and we leave it as an exercise for the reader.
Here is the link to the tool: officedump.