> For the complete documentation index, see [llms.txt](https://txc.gitbook.io/documentation/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://txc.gitbook.io/documentation/writeups/operation-dreamjob.md).

# Operation Dreamjob

Hi all,\
this report provides a technical description of finding and analyzing malcious code included within a trojanized Keepass executable.

This challenge is part of Zero2Automate course's biweekly challenges. The description mentions, that the provided sample is attributed to the group [Lazarus](https://attack.mitre.org/groups/G0032/) and part of the Operation Dreamjob campaign.

The trojanized Keepass file is used to decrypt and reflectively load a next stage PE file, provided via CLI arguments by the threat actor.

The analyzed sample has the SHA256 hash

> abe3f16915dd905f899bcf66fb3bc05ffa270a7d01c91a1a3114771128298c07

and can be found on [Malware Bazaar](https://bazaar.abuse.ch/sample/abe3f16915dd905f899bcf66fb3bc05ffa270a7d01c91a1a3114771128298c07/).

{% hint style="info" %}
This analysis was conducted in a controlled environment using static and dynamic techniques to safely observe the malware’s behavior and dissect its components. To ensure safety during the analysis, a simulated internet connection was used instead of a real one, so nothing I did could connect back to the real world.
{% endhint %}

***

## Identifying suspicious modifications

### Triage

From the challenge description we already know, we are dealing with a trojanized version of the legitimate tool Keepass. My goal was to find obvious deviations from the original file and suspicious additions during triaging.

{% columns %}
{% column %}

<figure><img src="/files/CDTYcasTWNAxOeRiLNbv" alt=""><figcaption><p>trojanized Keepass</p></figcaption></figure>
{% endcolumn %}

{% column %}

<figure><img src="/files/XONswqfvbHRirfPJ4I94" alt=""><figcaption><p>original Keepass</p></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

Examining the <kbd>VS\_VERSION\_INFO</kbd> structure shows the same information like version 1.36 of the clean Keepass version. However, the trojanized file is not signed.

The difference regarding the architectures is also notable, because the original 1.36 version of Keepass is a 32bit executable while the trojanized one is a 64bit executable.

A comparison of the imported libraries and APIs reveals no significant additions that could serve as a starting point for a more in-depth analysis.

### BinDiffing

My next idea to find a starting point was BinDiffing both files. Unfortunately it showed several thousand unmatched functions between the original and trojanized file. Too many to dig through.

### Capa(bilities)

Since we are dealing with a password manager, some capabilities also seen in malicious keyloggers show up. This includes capabilities like window hooking, reading clipboard data, cryptography or access to files on disk.

The capabilities regarding HTTP communication are outstanding, but a communication to a malicious C2 server wasn't observed. Keepass also includes an update mechanism, so these functionalities make sense here.

### Malcat Kesakode

[Kesakode](https://doc.malcat.fr/analysis/kesakode.html) is hash lookup service included into Malcat, allowing the analyst to query a public server offering a database of known libraries, malware and clean files to match functions, strings and constants of a file being analyzed. After the query finished, functions, strings and constants are labeled according to the lookup result.\
I fed the results of Kesakode into the MCP server included in Malcat to identify similarities in the function call graph and call relationships, rather than manually comparing the call graphs of each function previously labeled as malicious. This procedure helped me focus on a single function, acting as caller for four functions labeled as malicious previously.

<figure><img src="/files/uRnXLcPijnnNSeWYEHIE" alt=""><figcaption><p>function call graph for the function located at 0x14025e550</p></figcaption></figure>

***

## Orchestrating function

The function located at <kbd>0x14025e550</kbd> acts as a reflective PE loader and calls additional functions to mimic Windows loader capabilities. The MCP server included in Malcat was able to triage the labeled functions and to generate a nice table for a brief overview:

<table><thead><tr><th width="143">Function</th><th width="109">Category</th><th>Role in loader</th></tr></thead><tbody><tr><td><code>sub_14025ddf0</code></td><td><strong>MALWARE</strong></td><td><strong>Import resolver</strong> — walks the PE's import directory, calls <code>LoadLibraryA</code> + <code>GetProcAddress</code> via function pointers stored in the loader context to bind all imports</td></tr><tr><td><code>sub_14025dce0</code></td><td><strong>MALWARE</strong></td><td><strong>Cleanup / teardown</strong> — calls <code>DLL_PROCESS_DETACH</code>, iterates loaded libraries and calls <code>FreeLibrary</code>, releases the <code>VirtualAlloc</code>'d image via <code>VirtualFree</code>, and <code>HeapFree</code>s the context structure</td></tr><tr><td><code>sub_14025e0b0</code></td><td><strong>MALWARE</strong></td><td><strong>Base relocation patcher</strong> — reads <code>IMAGE_DIRECTORY_ENTRY_BASERELOC</code> (<code>offset 0xB0</code>), applies the delta between the preferred image base (<code>[rsi+0x30]</code>) and the actual allocation, patching every absolute pointer via the <code>.reloc</code> table</td></tr><tr><td><code>sub_14025df90</code></td><td><strong>MALWARE</strong></td><td><strong>TLS callback executor</strong> — reads <code>IMAGE_DIRECTORY_ENTRY_TLS</code> (<code>offset 0xD0</code>), walks the TLS callback array and invokes each callback with <code>(imageBase, DLL_PROCESS_ATTACH, NULL)</code> — exactly what the Windows loader does for DLLs with TLS</td></tr></tbody></table>

In the next chapter I will briefly step through these functions to show what they are doing, to verify the analysis of the AI agent and for learning purposes of course (see chapter [Reimplementation of Windows loader capabilities](#reimplementation-of-windows-loader-capabilities)).

The function located at <kbd>0x14025e550</kbd> takes three arguments in total and starts with parsing the PE header from the first argument to determine if decryption was successful and whether the decrypted file targets the correct archtitecture.

<figure><img src="/files/mY3B85A80bAMJJGWRfko" alt=""><figcaption><p>PE header checks</p></figcaption></figure>

The decrypted PE file will be mapped into freshly allocated memory via a loop, via the function visible in the picture below. This function will be called during execution of the orchestration function described here.

<figure><img src="/files/phSyjHpZTG6pg9BThkS7" alt=""><figcaption><p>section mapper</p></figcaption></figure>

Later, the orchestrating function is also responsible for filling a custom structure with a size of 0x68 bytes with different members that is later used during different functions (see below).

<figure><img src="/files/icOFwvazK77qYZ2p6Fo3" alt=""><figcaption><p>heap space allocated and structure filled</p></figcaption></figure>

<figure><img src="/files/o9BJoGDieWIOkEqgGSu0" alt=""><figcaption><p>custom structure</p></figcaption></figure>

If everything went well without errors, especially the reimplementation of Windows loader capabilitites (see functions described in the next [chapter](#reimplementation-of-windows-loader-capabilities)), the malware walks the PE header of the mapped PE file until it reaches the <kbd>AddressOfEntryPoint</kbd> member and executes it. Notably here is the 3rd parameter, acting as an argument for the execution of the next stage and being passed by the threat actor via CLI before ([see below](#origin-of-mapped-pe-file)).

<figure><img src="/files/eVN6W1dwN4hH718pcplV" alt=""><figcaption><p>execution of mapped PE file</p></figcaption></figure>

### Origin of PE file to be mapped

To understand the whole infection chain better, it is necessary to track down where the PE file to be mapped comes from.

<figure><img src="/files/HHtUhva4YvXUD2B1fSt1" alt=""><figcaption></figcaption></figure>

The orchestrating function described above is called with three parameters, the first of them being the decrypted PE file to be mapped. In the picture above we can see a combination of calls to APIs like <kbd><mark style="color:$warning;">CreateFileA<mark style="color:$warning;"></kbd>, <kbd><mark style="color:$warning;">GetFileSize<mark style="color:$warning;"></kbd>, <kbd><mark style="color:$warning;">LocalAlloc<mark style="color:$warning;"></kbd> and <kbd><mark style="color:$warning;">ReadFile<mark style="color:$warning;"></kbd> to read a specific, encrypted file into memory. Later, the AES algorithm in CBC mode decrypts the content.

Malcat did a great job identifying the well-known Rijndael S-Boxes and transformation boxes used during AES decryption.

<figure><img src="/files/IOK4KX1ZPmsA8YFjdcoa" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/G4aW6rPYzVzXNUlJqGuQ" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/5kMxVMUVWWp5WA30l5Bj" alt=""><figcaption></figcaption></figure>

Interesting for us as analysts is to understand where the encrypted file and the password for decrypting it comes from.

If we track down the first parameter of the call to the API <kbd><mark style="color:$warning;">CreateFileA<mark style="color:$warning;"></kbd> we are able to notice the presence of two global variables, both being written to in the <kbd>setargv()</kbd> runtime function.

<figure><img src="/files/qbYTrqVTdYkAbIMqIQZf" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/5rTeGjeaSlv9bNSRAjzt" alt=""><figcaption></figcaption></figure>

This function parses the command line and writes a pointer to an array of pointers to each argument string (<kbd>argv</kbd>) and the number of user-supplied arguments (excluding the program name - <kbd>argc</kbd>) into both global variables.&#x20;

```
argv[0] = [0x140390048+0x00]  → "KeePass.exe"  (program name)
argv[1] = [0x140390048+0x08]  → first user arg  ← ENCRYPTED FILE PATH
argv[2] = [0x140390048+0x10]  → second user arg ← AES KEY / PASSWORD
argv[3] = [0x140390048+0x18]  → third user arg
argv[4] = [0x140390048+0x20]  → fourth user arg
... and so on
```

Via the offset of <kbd>0x08</kbd>, the malware copies the file path of the encrypted file into a local variable, used for the call to <kbd><mark style="color:$warning;">CreateFileA<mark style="color:$warning;"></kbd>. Same counts for the offset of <kbd>0x10</kbd> and the password for the AES decryption.

{% columns %}
{% column %}

<figure><img src="/files/HBhJMS8CVWNcwiUcKo98" alt=""><figcaption><p>filename</p></figcaption></figure>
{% endcolumn %}

{% column %}

<figure><img src="/files/fmpIG7DqkN8rCmLdMthD" alt=""><figcaption><p>AES password</p></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

The behaviour is similar for the third argument, located at offset <kbd>0x18</kbd> and acting as the 3rd parameter passed to the orchestrating function. As descibed above, this parameter is later used as argument for executing the mapped PE file.

<figure><img src="/files/Dh3OPH533x2BZ46cztOF" alt=""><figcaption></figcaption></figure>

***

## Reimplementation of Windows loader capabilities

### sub\_14025ddf0 - Import resolver

In this function the malware uses the APIs <kbd><mark style="color:$warning;">LoadLibraryA<mark style="color:$warning;"></kbd> and <kbd><mark style="color:$warning;">GetProcAddress<mark style="color:$warning;"></kbd> from the struct filled before to dynamically resolve needed APIs for the reflectively loaded PE file.

<figure><img src="/files/3USUD9lo8c4xqm6atJOn" alt=""><figcaption><p>loop to resolve imports dynamically</p></figcaption></figure>

To get the name of the DLL to load, the import resolver walks the PE header, until it reaches the <kbd>IMAGE\_DATA\_DIRECTORY</kbd> structure. From here, it jumps to an array of [<kbd>IMAGE\_IMPORT\_DESCRIPTOR</kbd>](https://learn.microsoft.com/en-us/archive/msdn-magazine/2002/march/inside-windows-an-in-depth-look-into-the-win32-portable-executable-file-format-part-2) structures, containing the name of the DLL to be loaded at offset <kbd>0x0C</kbd>. This loop continues, until every required DLL has been loaded. Therefore, the function adds the value <kbd>0x14</kbd>(size of a IMAGE\_IMPORT\_DESCRIPTOR structure) to iterate through the array until every needed DLL has been loaded.

<figure><img src="/files/iqL3Pap6P8txSAO2j4lf" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/g4bOYYWeSQYCSSY5WgDz" alt=""><figcaption><p>accessing next array member and testing the pointer</p></figcaption></figure>

### sub\_14025df90 - TLS callback execution

This function is responsible for executing TLS callbacks, if any are included in the PE file loaded reflectively.

The TLS resolver only takes one argument, namely the custom structure created before.

```c
typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER OptionalHeader;
} IMAGE_NT_HEADERS;
```

Starting from the <kbd>Signature</kbd> member of the <kbd>IMAGE\_NT\_HEADERS</kbd> structure, the code jumps to the <kbd>TLSDirectory</kbd> member of the [<kbd>IMAGE\_DATA\_DIRECTORY</kbd>](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_data_directory) structure, by adding the offset of <kbd>0xD0</kbd>. If a TLS directory exists, the malware continues to grab the Virtual addresses of each TLS callback and executes them one after another.

<figure><img src="/files/Pwiky1TjXM5FijpjphYV" alt=""><figcaption><p>execution of TLS callbacks</p></figcaption></figure>

### sub\_14025e0b0 - Base relocation

This functions ensures, that the decrypted PE file is mapped into the desired <kbd>ImageBase</kbd> address, found in the <kbd>OptionalHeader</kbd>. The function only executes, if the substraction in the picture below does not equal 0, meaning the malware currently deals with two different <kbd>ImageBase</kbd> addresses.&#x20;

<figure><img src="/files/4tzYYEtn8qMmCnu8Suxu" alt=""><figcaption><p>ImageBase comparison</p></figcaption></figure>

Way before, virtual memory is allocated via <kbd><mark style="color:$warning;">VirtualAlloc<mark style="color:$warning;"></kbd>. In the first attempt, the function tries to allocate memory at the <kbd>ImageBase</kbd> as desired address. If this is unsuccessful, the <kbd><mark style="color:purple;">lpAddress<mark style="color:purple;"></kbd> parameter is zeroed out, meaning the OS determines where to allocate the memory. <kbd><mark style="color:$success;">RSI+0x30<mark style="color:$success;"></kbd> grabs the <kbd>ImageBase</kbd> of the decrypted PE file.

<figure><img src="/files/AAa8GdjB6Lp8UvV3io9e" alt=""><figcaption><p>allocation of memory</p></figcaption></figure>

At start of the relocation function, the <kbd>BaseRelocationTable</kbd> is accessed to determine the address of the <kbd>.reloc</kbd> section, if any exists.

<figure><img src="/files/HQg2LqIcK1Y7O8yJlIQD" alt=""><figcaption></figcaption></figure>

The logic shows some characteristics of relocation going on, for instance the comparsions with the values <kbd>0x03</kbd> and <kbd>0x0A</kbd>, in this case [relocation types](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#base-relocation-types) <kbd>IMAGE\_REL\_BASED\_HIGHLOW</kbd> and <kbd>IMAGE\_REL\_BASED\_DIR64</kbd>.

<figure><img src="/files/44XlFa9OueSTM4cQYA3e" alt=""><figcaption><p>relocation logic</p></figcaption></figure>

### sub\_14025dce0 - Cleanup

After the PE file has been loaded and executed successfully, the malware frees up the allocated memory region and the process heap.

<figure><img src="/files/mDGJ8X7QtOB5AryWRCEj" alt=""><figcaption></figcaption></figure>

### sub14025e190 - protection change

To change the protection of each section, the malware calls the Windows API <kbd><mark style="color:$warning;">VirtualProtect<mark style="color:$warning;"></kbd>. To set the correct protection, the malware checks the <kbd>Characteristics</kbd> member of the [<kbd>IMAGE\_SECTION\_HEADER</kbd>](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_section_header) structure and determines, if the section is discardable (25th bit tested). If not, the function continues to enumerate the required protection (via the bits set at position 29, 30 and 31) and uses a lookup table to gather the correct protection constant.

<figure><img src="/files/dnO14qvqNICwxNJIubQS" alt=""><figcaption><p>enumeration and set of needed permissions</p></figcaption></figure>

<figure><img src="/files/ethWbiyar9Som7dpLp7Y" alt=""><figcaption></figcaption></figure>

***

## Summary

This was an interesting real world sample to analyze, especially finding the small additions included by the malware developer. Additionally, the analysis of this sample was a great learning opportunity to leverage AI resp. the MCP server included in Malcat, to speed up analysis, get better understanding of the whole execution flow and explaination how command line arguments are handled 😉.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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://txc.gitbook.io/documentation/writeups/operation-dreamjob.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.
