# Revealing Excel Password Secrets Stored in Process Memory

## Background

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](https://www.linkedin.com/in/jonathan-cheung-ps/) 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.

<figure><img src="/files/bEPGf0dCY815sod2YFCv" alt=""><figcaption><p>Searching function containing "Password"</p></figcaption></figure>

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.

<figure><img src="/files/GVszl7kJhnoCGdEA2d5C" alt=""><figcaption><p>Call stack of the "PasswordDialog" function</p></figcaption></figure>

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.

<figure><img src="/files/uk1poAxcfKu6HLZ3kRS9" alt=""><figcaption><p>Entered password was used as first argument to "MsoHrLoadCryptSession" function</p></figcaption></figure>

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.

<figure><img src="/files/DdgokNygIu1262zXw5TD" alt=""><figcaption><p>The Windows XP source code provides a description of the function</p></figcaption></figure>

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.

<figure><img src="/files/DfPCBUXMcBiHhxiWqlMv" alt=""><figcaption><p>Inner function of MsoHrLoadCryptSession</p></figcaption></figure>

<figure><img src="/files/o3GoESIZG1Eukg2HTy8o" alt=""><figcaption><p>Return 0 when password is valid</p></figcaption></figure>

On the contrary, it comes as no surprise that providing an incorrect password will result in an error code being returned.

<figure><img src="/files/8BXwRDgRTmb24a27oM3S" alt=""><figcaption><p>Return 0xe0040603 when password is incorrect</p></figcaption></figure>

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.

<figure><img src="/files/JX11zjRPlFrXeCJazWj4" alt=""><figcaption><p>Function that takes entered password as argument</p></figcaption></figure>

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.

<figure><img src="/files/Lp3dVHAb2o0e5wqyEjNV" alt=""><figcaption><p>Password structure</p></figcaption></figure>

Here is an example of what the structure might look like in pseudocode:

```
typedef struct Password {
    DWORD passwordLength;
    WCHAR password[MAX_PASSWORD_LENGTH];
} Password;
```

Further down the line, we encountered an interesting function called "FHpAllocCore":

<figure><img src="/files/uaZtTtzjbl2HWrmj5buq" alt=""><figcaption><p>A function appears to allocate memory</p></figcaption></figure>

Within the "FHpAllocCore" function, there is an inner function called "AllocateEx" that appears to be relevant to memory allocation.

<figure><img src="/files/Gs0z0hTLzwvmFb2MEb1M" alt=""><figcaption><p>"AllocateEx" in "FHpAllocCore"</p></figcaption></figure>

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)

<figure><img src="/files/SV5YahgQtwxqNHAqa70K" alt=""><figcaption><p>Wrapper function of NTDLL RtlAllocateHeap</p></figcaption></figure>

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.

<figure><img src="/files/vZs8UpIWYieTrL57uPtk" alt=""><figcaption><p>IDA view from FHpAllocCore to BltB</p></figcaption></figure>

<figure><img src="/files/ViOsF2VmsbkJNCcFbwSy" alt=""><figcaption><p>Register setup when calling BltB</p></figcaption></figure>

Inside the BltB function, there is a function called "memmove". Apparently, it attempts to move values from one memory address to another.

<figure><img src="/files/nxjrOEVXv7OlYJ0u4AGE" alt=""><figcaption><p>"memmove" inside "BltB" function</p></figcaption></figure>

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.

<figure><img src="/files/KcNHET6maUfW6xxh5XTh" alt=""><figcaption><p>memmove execution to copy password structure from stack to heap</p></figcaption></figure>

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.

<figure><img src="/files/2TVjLlGmgU44Tc3TIJrt" alt=""><figcaption><p>Password stored in heap</p></figcaption></figure>

{% hint style="info" %}
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.
{% endhint %}

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.

<figure><img src="/files/dkgusUYc5BAgpm6F26Fq" alt=""><figcaption><p>Address storing the password is stored in the heap</p></figcaption></figure>

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.

<figure><img src="/files/Ct95AQnTdAeLecnfkgDs" alt=""><figcaption><p>Signature to seach for the address storing the password</p></figcaption></figure>

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.

<figure><img src="/files/qo7zeHN2fCMXTEAcmk7o" alt=""><figcaption><p>Signature exists in the assembly code</p></figcaption></figure>

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:

<figure><img src="/files/S9OlfwMUxLuHPeCj9ZPv" alt=""><figcaption><p>The signature we found is part of the SH::SH function parameters</p></figcaption></figure>

This deassembled code shows more clearly how the hardcoded signature is constructed in this constructor function.

<figure><img src="/files/UnF136IrIn2h6KChUrqa" alt=""><figcaption><p>Deassembled code of SH::SH function</p></figcaption></figure>

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.

<figure><img src="/files/pkeWVtIXrGIlkF8KqLvH" alt=""><figcaption><p>Deassembled code for "LogObjectLifetimeEvent"</p></figcaption></figure>

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.

<figure><img src="/files/0cCd5OV3RiGWqrWAUBla" alt=""><figcaption><p>POC of officedump</p></figcaption></figure>

{% hint style="info" %}
Currently we only tested on Windows 10/11 with Microsoft Office 365 installed. Please feel free to let me know your test result.
{% endhint %}

{% hint style="info" %}
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.
{% endhint %}

Here is the link to the tool: [**officedump**](https://github.com/elephacking/officedump).


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://www.netero1010-securitylab.com/red-team/revealing-excel-password-secrets-stored-in-process-memory.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
