> 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/malops.io/operation-silent-veil.md).

# Operation Silent Veil

Hi all,

this writeup documents my analysis of the multistage malware sample being part of [Malops](https://malops.io/chain-challenges) Labs. These exercises enable participants to analyze complex challenges that involve a sequential chain of events leading up to a realistic attack.

Starting with an Excel document and obfuscated VBA, we need to decrypt the final payload from a dropped image to reveal a .NET stager that communicates through Filen cloud storage.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FgneuA6DEiNGLhtBmYK3p%2Fimage.png?alt=media&amp;token=074b2a09-4fc6-4cd3-ac86-44d565533e3b" alt=""><figcaption><p><a href="https://x.com/MalGamy12/status/2103823019269406805">https://x.com/MalGamy12/status/2103823019269406805</a></p></figcaption></figure>

{% hint style="info" %}
To be more efficient during malware analysis and to train focusing on the most relevant aspects (e.g. delivery of a decrypted C2 configuration to build a detection on instead of reversing the encryption algorithm in depth), I tried to myself into some kind of 'incident' mode were possible.

Here and there I used AI as a copilot, for educational purposes (especially in fields I had no experience previously) or for automation. The corresponding sections are highlighted.
{% endhint %}

***

## Stage 1 - The Hidden Document - sample.xls

The whole infection chain starts with an Excel file, provided by the author of this lab. Triage returns the following, most interesting information:

{% tabs %}
{% tab title="SHA256" %}
c99d00dede7a704ce86ef5fa5f8ec7e2388750327ec04f9691e11a32267ba031
{% endtab %}

{% tab title="File size" %}
748032 bytes (730.5 KiB)
{% endtab %}

{% tab title="Embedded files" %}

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FNzCwU17qDAwArXWiaj2I%2Fimage.png?alt=media&amp;token=bc87612c-d402-4965-955c-f8fcf6db28ad" alt=""><figcaption><p>Inside Office.Workbook8</p></figcaption></figure>
{% endtab %}

{% tab title="Strings" %}

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2Fr9BejKyubFuGE4mCU3Et%2Fimage.png?alt=media&amp;token=8b7e2a02-d986-4dca-9c22-1e2c42b70704" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FuJNcglXlqfOi1o0BB32o%2Fimage.png?alt=media&amp;token=d7269552-3abd-46b1-92aa-0e5d57a9fa4b" alt=""><figcaption></figcaption></figure>

ingest\[.]filen\[.]io

Various API names like: \
GetCommandLineA\
RegCreateKeyExW\
RegSetValueExW\
LoadLibraryExW\
GetProcAddress\
CreateProcessA\
gethostbyname\
FindNextFileW\
IsUserAnAdmin
{% endtab %}

{% tab title="Document metadata" %}

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FP2k3fFpBatj3h5yZhioS%2Fimage.png?alt=media&amp;token=c02fbbe9-3fb2-43dd-ad19-bd2718a46c8e" alt=""><figcaption></figcaption></figure>
{% endtab %}
{% endtabs %}

The file header shows the bytes <kbd><mark style="color:$primary;">D0CF11E0A1B11AE1<mark style="color:$primary;"></kbd>, the magic bytes for the <kbd>OLE2 file format</kbd> (Question 1).&#x20;

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2F6JKxqYPUP0MOhplqyJb4%2Fimage.png?alt=media&amp;token=200ff52b-59ff-458b-8dd9-9aaa64724196" alt=""><figcaption><p>file header</p></figcaption></figure>

When the Excel file is opened, the <kbd>Workbook\_Open</kbd> (Question 2) VBA procedure runs the macros embedded inside.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FfyMX6EVB5kPyP2gu2n7J%2Fimage.png?alt=media&amp;token=9ce6d3e6-9fc4-4650-8d00-3655e137f2e6" alt=""><figcaption></figcaption></figure>

{% hint style="warning" icon="comment-question" %}
AI usage - GPT-6 Astra & Malcat MCP server - Summary of VB code\
Prompt:\
*I found all the VBA macros, but it is obfuscated and contains more than 1500 lines. Please analyze all the modules for me and give me a brief summary of what the VBA is doing in a clean table. Entry point is the smf function within the Workbook\_Open procedure*
{% endhint %}

{% hint style="success" icon="message-check" %}

| Module              | Main routines                           | What it does                                                                                                                                                                                              |
| ------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **ThisWorkbook**    | `Workbook_Open`, `Workbook_BeforeClose` | Starts the installation sequence, hides the first sheet, and controls execution and saving when the workbook closes.                                                                                      |
| **Module1**         | `ini`, `gin`, `chkin`, `clr`            | Loads embedded configuration, resolves environment paths, performs a DNS lookup of `ingest[.]filen[.]io`, and coordinates closing actions.                                                                |
| **Module2**         | `CrFld`, `ini2`, `t1`, `uu`             | Creates folders, assigns configuration values, launches a hidden process, and masks visible worksheet cells with `#` characters.                                                                          |
| **Module3**         | `sge`, `ttt`, `asd`                     | Checks for an existing installation, copies the extracted DLL into place, and coordinates killing/restarting Explorer.                                                                                    |
| **Module4**         | `COmkn`, `reflux`, `SearchEB`           | Writes an HKCU `CLSID\…\InProcServer32` entry pointing to the DLL—an attempted [COM hijack](https://learn.microsoft.com/en-us/windows/win32/com/inprocserver32). Includes extraction and command helpers. |
| **Module5**         | `u`, `SearchBytes`, `WF`, `tfs`         | Extracts embedded configuration, DLL, and PNG bytes. Restores visible worksheet content from the hidden sheet and records status.                                                                         |
| **Module6**         | `SPe`, `hc`, `ges`, `u1`                | Copies the XLS to `%TEMP%`, locates embedded data, masks cells, and launches the Explorer termination command.                                                                                            |
| **Module7**         | **`smf`**, `chkos`, `ost`               | Main controller. Inventories OS/Excel versions and program folders; stores XOR-obfuscated results in hidden cell `Z101`. Provides fallback DLL execution.                                                 |
| **Module8**         | `t0`, `hpnol`, `getGrs`, `wtof`         | Reads/writes binary files and builds the `taskkill` and [regsvr32](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/regsvr32) commands.                                   |
| **Module9**         | `ini0`                                  | Initializes `Scripting.FileSystemObject`, `%TEMP%`, and `system32` settings.                                                                                                                              |
| **Module10**        | Constants only                          | Defines the embedded-data marker `A056FA73` and a shared variable.                                                                                                                                        |
| **Module11**        | API declarations                        | Provides Windows registry, process, file, and DNS APIs for VBA7.                                                                                                                                          |
| **Module12**        | Legacy API declarations                 | Older-VBA equivalents, with apparent compatibility defects.                                                                                                                                               |
| **Sheet1 / Sheet2** | No procedures                           | Hold worksheet data and diagnostics; contain no executable VBA routines.                                                                                                                                  |
| {% endhint %}       |                                         |                                                                                                                                                                                                           |

Module10 contains the data marker `A056FA73` (Question 3) of the config blob. In the screenshot below we see each element of the configuration (Question 11 - Question 15, Question 17 - Question 18 - A more detailed analysis regarding Persistence via COM Hijacking, file dropping and a fallback mechanism is visible below).

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FZRy26Vlpbk1hS7bCwmec%2Fimage.png?alt=media&amp;token=a16b65b5-b2c0-4a89-9765-3d37c10fb115" alt=""><figcaption><p>embedded config</p></figcaption></figure>

The routine <kbd><mark style="color:$primary;">hpnol()<mark style="color:$primary;"></kbd> in Module8 contains the logic to read bytes, starting from a given offset of a file and loading them into the buffer <kbd><mark style="color:$primary;">altunb<mark style="color:$primary;"></kbd>.&#x20;

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FUo1HpPZlim5JX1Zqi7v7%2Fimage.png?alt=media&amp;token=8224c9fd-bfa0-4e76-a6cf-3db9500b1c46" alt=""><figcaption></figcaption></figure>

The object created via the <kbd><mark style="color:$primary;">CreateObject()<mark style="color:$primary;"></kbd>  (Question 4) function is an <kbd>ADODB.Stream</kbd> object, defined in the beginning of Module4 via the variable <kbd><mark style="color:$primary;">fsagh<mark style="color:$primary;"></kbd>.&#x20;

<kbd><mark style="color:$primary;">hpnol()<mark style="color:$primary;"></kbd> is only called once, namely within Module1.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FWs83vDForTNQzh6JKtUi%2Fimage.png?alt=media&amp;token=39f4d78f-77e9-4bc4-8bdc-daf3f4392218" alt=""><figcaption></figcaption></figure>

The filepath used in this function is constructed before and will be <kbd><mark style="color:$primary;">%TEMP%\\\~FILENAME.tmp<mark style="color:$primary;"></kbd>.

Modul1 is responsible for finding and loading the embedded configuration. After the code reads the desired bytes into a buffer, the malware searches for them via a self-defined <kbd><mark style="color:$primary;">SearchBytes()<mark style="color:$primary;"></kbd> function.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FOGv4sT3LknfUooMzFcos%2Fimage.png?alt=media&amp;token=387cacbb-26a6-4563-a2b3-4232fc93584b" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FwOCtGj1apltXWXIGfcW0%2Fimage.png?alt=media&amp;token=62db0356-8dab-4b1e-aa4c-1bd20416e72a" alt=""><figcaption></figcaption></figure>

The third parameter defines the size, in case of Question 5 hardcoded in the variable <kbd>fsz0</kbd>. A simple search for this variable reveals its definition within Module7.

Within <kbd><mark style="color:$primary;">SearchBytes()<mark style="color:$primary;"></kbd> the byte <kbd><mark style="color:$primary;">0xEB<mark style="color:$primary;"></kbd> is searched via a dedicated routine and its position adjusted by <kbd>8228 = 0x2024 bytes</kbd> (Question 6).

Afterwards, the malware search for its configuration and splits the fields, saving them in the array <kbd><mark style="color:$primary;">v<mark style="color:$primary;"></kbd>.

In total, this array contains 14 elements (Question 7) and is used gradually as code execution proceeds.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FnsxZJKLw3uvGx5PERQSy%2Fimage.png?alt=media&amp;token=0b1e8201-bd90-4370-a3fa-62b528e32135" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FXKnMGmZpzEmasbFoF9Mp%2Fimage.png?alt=media&amp;token=fb1a48fc-00c7-4371-8d99-502fe5658de8" alt=""><figcaption></figcaption></figure>

From triage it is already clear, that the Excel file contains an embedded PE resp. DLL file. The magic bytes header is either visible in the Excel file or in the Office.Workbook8 file (Question 8).

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FfIn2lS871NUOOAOw1tH8%2Fimage.png?alt=media&amp;token=1a9f0781-d546-4e17-aac8-b50765ed5128" alt=""><figcaption><p>Question 8</p></figcaption></figure>

Similar to the <kbd><mark style="color:$primary;">0xEB<mark style="color:$primary;"></kbd> marker from the previous question, the <kbd><mark style="color:$primary;">0xC600<mark style="color:$primary;"></kbd> marker within the file is accessed from within the <kbd><mark style="color:$primary;">SearchBytes()<mark style="color:$primary;"></kbd> routine. In the screenshots below we can see the size of the block, but also its location within the Excel file (Question 9).

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FpjeTgcn1kjdIRsx62bfB%2Fimage.png?alt=media&amp;token=21d7acbb-f96e-42f6-95b2-7a7b85fbb6f1" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FU3pz1xVhRs3hnz6xfUq3%2Fimage.png?alt=media&amp;token=c8345e78-d6b8-4151-baec-5ce58c2fc90d" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FaUJDPdfR4mfJtRvtNdh4%2Fimage.png?alt=media&amp;token=137bcce7-75a7-46c3-99ff-cce15a64c3be" alt=""><figcaption></figcaption></figure>

The <kbd><mark style="color:$primary;">SearchBytes()<mark style="color:$primary;"></kbd> routine is able to search for bytes regarding different file types. The second parameter <kbd><mark style="color:$primary;">obj<mark style="color:$primary;"></kbd> determines for which file header to search for.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2F8x6HZtvYBf9yNlZjiPNw%2Fimage.png?alt=media&amp;token=0350d8e6-006c-4713-a791-0b8c0c423e15" alt=""><figcaption></figcaption></figure>

For example, if <kbd><mark style="color:$primary;">obj = 1<mark style="color:$primary;"></kbd>, the routine searches for the magic bytes of a PE file header. In case of <kbd><mark style="color:$primary;">obj = 2<mark style="color:$primary;"></kbd> the routine searches for the magic bytes of a PNG file header.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FcJRNDcjZBKfP52YKnEVg%2Fimage.png?alt=media&amp;token=350e9126-191e-4d8a-8c32-bf0addfae039" alt=""><figcaption></figcaption></figure>

Transforming the decimal values to hex values will return the bytes <kbd><mark style="color:$primary;">89 50 4E 47 0D<mark style="color:$primary;"></kbd>, the [magic bytes for a PNG file](https://en.wikipedia.org/wiki/List_of_file_signatures). Tracing back the calls to <kbd><mark style="color:$primary;">SearchBytes()<mark style="color:$primary;"></kbd> show only one occurrence with <kbd><mark style="color:$primary;">obj = 2<mark style="color:$primary;"></kbd> namely in the routine <kbd><mark style="color:$primary;">WF()<mark style="color:$primary;"></kbd> that is also called only one time, with the size of the image as 3rd parameter (Question 10).

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FeZwUe4atOjgnEwXqwJNv%2Fimage.png?alt=media&amp;token=87762ab4-48c1-4045-985b-c9261060c821" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2F1PsSh5qUl9R6zIHQgdki%2Fimage.png?alt=media&amp;token=83208016-21e0-4c5c-a445-cd54be6eaf3d" alt=""><figcaption></figcaption></figure>

### Dropping next stage payloads

From the configuration information embedded in the Excel file we get a hint, which file names and paths the malware uses to drop the payloads for the next stage. Additionally, before extracting bytes, the malware used the function <kbd><mark style="color:$primary;">SearchBytes()<mark style="color:$primary;"></kbd> multiple times to locate them within the Excel document.

Routine <kbd><mark style="color:$primary;">WF()<mark style="color:$primary;"></kbd> is not only responsible for locating and extracting the bytes from the Excel file, but also responsible for writing the extracted bytes to disk. To achieve this goal, it calls the routine <kbd><mark style="color:$primary;">wtof()<mark style="color:$primary;"></kbd> two times. In combination with tracing back calls to<kbd><mark style="color:$primary;">kernel32!CreateFileW<mark style="color:$primary;"></kbd> we get an idea what is happening here (relevant for Question 16). This Windows API is defined in Module11 and called within the routine <kbd><mark style="color:$primary;">wtof()<mark style="color:$primary;"></kbd>.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2Fa2GlcEBGC5pqiqTkDHVh%2Fimage.png?alt=media&amp;token=765d31ff-c815-478a-9f28-569d5feb14d1" alt=""><figcaption></figcaption></figure>

Tracing back calls to <kbd><mark style="color:$primary;">wtof()<mark style="color:$primary;"></kbd> leads again to the routine <kbd><mark style="color:$primary;">WF()<mark style="color:$primary;"></kbd> with the relevant call visible in the first yellow rectangle.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2F2Ny0WYYRF2UQxqkNvLY8%2Fimage.png?alt=media&amp;token=324274eb-994e-46dd-98a0-9ac1e186d43a" alt=""><figcaption></figcaption></figure>

The variable <kbd><mark style="color:$primary;">tmpd<mark style="color:$primary;"></kbd> used in the concatenation as first parameter is filled before with the sixth element (via <kbd><mark style="color:$primary;">v(5)<mark style="color:$primary;"></kbd>) of the configuration found earlier (see picture before). <kbd><mark style="color:$primary;">tf<mark style="color:$primary;"></kbd> is the resolved environment variable of the <kbd><mark style="color:$primary;">TEMP<mark style="color:$primary;"></kbd> directory. This results into the file creation of <kbd><mark style="color:$primary;">%TEMP%\DEFAULT-20241101-1112.log<mark style="color:$primary;"></kbd><mark style="color:$primary;">,</mark> containing the bytes of the DLL file loaded later during COM Hijacking.

The second call to <kbd><mark style="color:$primary;">wtof()<mark style="color:$primary;"></kbd> writes the bytes of the PNG file into <kbd><mark style="color:$primary;">Microsoft\Network\Cache\icon.png<mark style="color:$primary;"></kbd>.

However, at this point the file <kbd><mark style="color:$primary;">%appdata%\Microsoft\Internet Explorer\UserData\Low\DB\uimanager.dll<mark style="color:$primary;"></kbd> does not exist yet, but only the LOG file in the Temp directory. Additionally, the parts of the full file path of this LOG file are still stored in the variables <kbd><mark style="color:$primary;">tf<mark style="color:$primary;"></kbd> and <kbd><mark style="color:$primary;">tmpd<mark style="color:$primary;"></kbd>.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2Frjveac6Caj1S68139GuW%2Fimage.png?alt=media&amp;token=e59198b3-c902-4985-8b18-553a7f8c715d" alt=""><figcaption></figcaption></figure>

Within Module3, various strings are concatenated and then, via <kbd><mark style="color:$primary;">CopyFile<mark style="color:$primary;"></kbd>, the content dropped previously is copied into <kbd><mark style="color:$primary;">%appdata%\Microsoft\Internet Explorer\UserData\Low\DB\uimanager.dll<mark style="color:$primary;"></kbd>.

### Persistence - COM Hijacking

During routing <kbd><mark style="color:$primary;">COmkn<mark style="color:$primary;"></kbd> the malware accesses and modifies the user hive of the Registry of the infected machine via a combination of API calls like <kbd><mark style="color:$primary;">advapi32!RegOpenCurrentUser<mark style="color:$primary;"></kbd>, <kbd><mark style="color:$primary;">advapi32!RegCreateKeyExW<mark style="color:$primary;"></kbd> and <kbd><mark style="color:$primary;">advapi32!RegSetValueExW<mark style="color:$primary;"></kbd>.

{% hint style="info" %}
To establish Persistence via COM Hijacking, attackers target the user hive (HKEY\_CURRENT\_USER resp. HKEY\_USER) and modify the targeted COM interface within this hive. By default, the OS searches for the needed COM interface in this hive first, before falling back to the SYSTEM hive (HKEY\_LOCAL\_MACHINE).
{% endhint %}

In the screenshot below we can see the mentioned API calls in action and relevant content of the configuration filled into variables before.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FzndZBFaAKFJQWWFXvMb9%2Fimage.png?alt=media&amp;token=661d3f06-fcfd-400b-8f08-c88e21b99d99" alt=""><figcaption></figcaption></figure>

After the COM Hijack has been established, the malware forces <kbd><mark style="color:$primary;">explorer.exe<mark style="color:$primary;"></kbd> to exit and to restart. This ensures, that this process performs a lookup for the required COM interface, effectively loading the malicious DLL into its own process, namely <kbd><mark style="color:$primary;">explorer.exe<mark style="color:$primary;"></kbd>.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2F7z3X9DRtiOWEIyCftrxm%2Fimage.png?alt=media&amp;token=7047ba54-3817-4859-9937-2f96d47b5a0f" alt=""><figcaption><p>original COM interface targeted by the malware. IDStore.dll loaded within explorer.exe</p></figcaption></figure>

Question 19 and Question 20 can be answered together, exploiting the COM Hijack established previously.

The malware kills the process <kbd><mark style="color:$primary;">explorer.exe<mark style="color:$primary;"></kbd> first via <kbd><mark style="color:$primary;">taskkill.exe<mark style="color:$primary;"></kbd>. It then starts <kbd><mark style="color:$primary;">explorer.exe<mark style="color:$primary;"></kbd> again, effectively forcing it to load the malicious DLL dropped earlier via COM Hijacking.&#x20;

{% hint style="warning" icon="comment-question" %}
AI usage - GPT-6 Astra & extracted .vbs script - Review of my API call tracking\
Prompt:\
*The VBS establishes COM Hijacking before. I think it kills explorer.exe first via taskkill.exe, then restarts it to abuse the COM Hijack. Is this correct?*
{% endhint %}

{% hint style="success" icon="message-check" %}
**Yes—that is the intended execution chain.**

1. During `Workbook_Open`, `smf → COmkn` creates the per-user COM registration pointing to the dropped `uimanager.dll`. A successful registry write sets `flr = 1`.
2. When the workbook **closes**, `clr → asd → reflux → u1` launches:

   ```
   taskkill.exe /f /IM explorer.exe
   ```
3. After waiting up to 17 seconds, `asd → t1` launches:

   ```
   %WINDIR%\explorer.exe
   ```

{% endhint %}

During execution, the malware concatenates multiple parts of strings, being used for two calls to <kbd><mark style="color:$primary;">kernel32!CreateProcessA<mark style="color:$primary;"></kbd>. Chattie did a good job to tackle the obfuscation, rebuild the strings and summarizing the execution flow, making the whole purpose of the routines way clearer.

### Fallback mechanism

The malware uses a fallback mechanism to execute the dropped DLL via <kbd><mark style="color:$primary;">regsvr32.exe<mark style="color:$primary;"></kbd>, if establishing persistence via COM Hijack wasn't successful. The routines are visible in the pictures below\...

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FgRqlBzuyNCiyYVtTQjSF%2Fimage.png?alt=media&amp;token=6bbec4a9-5b3b-42a1-a919-131abcd6beba" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2Fh1g3fLMCkTvRuGGV4fPr%2Fimage.png?alt=media&amp;token=dce45321-c46e-431d-b253-7233fa097a03" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FMN9jObfsTK4Cl0iVfbWC%2Fimage.png?alt=media&amp;token=0f0bdcf4-8a12-4940-9c1e-8ce690e560dd" alt=""><figcaption></figcaption></figure>

... and execute only if the variable <kbd><mark style="color:$primary;">flr<mark style="color:$primary;"></kbd> equals 0. This will be the case, when the malware fails to establish COM Hijacking (within routine <kbd><mark style="color:$primary;">COmkn<mark style="color:$primary;"></kbd>).

### Check Name resolution (DNS)

Question 21 and Question 22 can be answered in combination:

Within the first routine executed, the routine <kbd><mark style="color:$primary;">chkin()<mark style="color:$primary;"></kbd> is called.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2F2zEMHTp6QNAi9rGxrQ5i%2Fimage.png?alt=media&amp;token=8ce6c5a3-06d0-4eee-a3b7-845bd207c4d3" alt=""><figcaption></figcaption></figure>

This routine is responsible for resolving the hostname <kbd><mark style="color:$primary;">ingest\[.]filen\[.]io<mark style="color:$primary;"></kbd> via <kbd><mark style="color:$primary;">gethostbyname<mark style="color:$primary;"></kbd>. If the check fails, the variable <kbd><mark style="color:$primary;">chk<mark style="color:$primary;"></kbd> will be modified and later written into a specific cell of the Excel file.

### Visibility

The visibility (Question 23) of the sheet is changed during the <kbd><mark style="color:$primary;">Workbook\_Open()<mark style="color:$primary;"></kbd> procedure. There the visibility is set to <kbd><mark style="color:$primary;">2<mark style="color:$primary;"></kbd> ensuring the object is set to <kbd><mark style="color:$primary;">VeryHidden<mark style="color:$primary;"></kbd><mark style="color:$primary;">,</mark> [preventing users from making it visible again via the UI](https://learn.microsoft.com/en-us/office/vba/api/excel.xlsheetvisibility).

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FtXkOg1V4sIJUrTGE8Yh4%2Fimage.png?alt=media&amp;token=2dc31f49-d7dc-4f0e-a6e0-da53f046a557" alt=""><figcaption></figcaption></figure>

When the workbook is closed, the <kbd><mark style="color:$primary;">Workbook\_BeforeClose()<mark style="color:$primary;"></kbd> routine executes, calling <kbd><mark style="color:$primary;">clr<mark style="color:$primary;"></kbd>, <kbd><mark style="color:$primary;">uu<mark style="color:$primary;"></kbd> and then <kbd><mark style="color:$primary;">hc<mark style="color:$primary;"></kbd> for predefined cells of sheet 2 (Question 24)<mark style="color:$primary;">.</mark>

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FlFrGYxLEw5lsfzC4Lyex%2Fimage.png?alt=media&amp;token=f92dd940-5411-4116-91da-573380f01276" alt=""><figcaption></figcaption></figure>

Within <kbd><mark style="color:$primary;">hc<mark style="color:$primary;"></kbd> the value for the predefined cells is set to preset constants (multiple hash symbols).&#x20;

### OS architecture

The script performs checks regarding the OS architecture multiple times. For the 64bit architecture, the script uses the constant <kbd><mark style="color:$primary;">Win64<mark style="color:$primary;"></kbd> (Question 25).

### Payload extraction

{% hint style="warning" icon="comment-question" %}
AI usage - GPT-6 Astra & sample.xls - Extraction of DLL and PNG file\
Prompt:\
*Started the Malcat MCP server with the sample.xls file. We already know, that it contains a PE32+ DLL with the size of 183296 bytes. Extract it for me and save it into my workspace. Follow the procedure from the the vbs script and give me a short summary, of what the malware author did to thwart simple extraction via Malcat. Do the same for the embedded PNG file with the size of 215393 bytes.*
{% endhint %}

{% hint style="success" icon="message-check" %}
Saved **uimanager.dll** in your workspace.

* **Size:** 183,296 bytes
* **Format:** PE32+ x64 DLL
* **SHA-256:** `125abe1a497067a61011eb6072b1b3d91d3f0b658ed20a99734f0341d9cbdd6a`

I reproduced `SearchEB → SPe → SearchBytes` statically. The DLL occupies **184,920 physical bytes** in the XLS, interrupted by **22 four-byte Excel record headers** and **three 512-byte OLE allocation-table sectors**. Removing those 1,624 bytes reconstructs the DLL.

This storage layout defeats simple contiguous carving: exporting directly from the `MZ` signature includes intervening container metadata and corrupts the recovered file. The VBA rebuilds the payload by skipping those structures.

**Neither the VBS nor the DLL was executed.** The original XLS is unchanged.
{% endhint %}

{% hint style="success" icon="message-check" %}
Saved **icon.png** in your workspace.

* **Size:** 215,393 bytes
* **Dimensions:** 516 × 354 pixels
* **Validation:** PNG signature, all chunk CRCs, and final `IEND` verified
* **SHA-256:** `6c7ddb53dd300d39f2e5b3d4d599be56991e807c633b5a5564b8b7d3fc7d491b`

Following the VBA’s reconstruction logic, I removed **26 four-byte Excel record headers** and **three 512-byte OLE allocation-table sectors**. These 1,640 intervening bytes prevent straightforward contiguous extraction.

**No VBA or payload was executed; the original XLS is unchanged.**
{% endhint %}

***

## Stage 2 - Beneath the Surface - uimanager.dll

### Triage

When analyzing this stage, we need to keep in mind, that the DLL is loaded into either <kbd><mark style="color:$primary;">explorer.exe<mark style="color:$primary;"></kbd> or into <kbd><mark style="color:$primary;">regsvr32.exe<mark style="color:$primary;"></kbd>, depending on whether COM hijacking has been established.\
Question 1 and Question 2 can be answered without looking too deeply into disassembled code.

{% tabs %}
{% tab title="SHA256" %}
125abe1a497067a61011eb6072b1b3d91d3f0b658ed20a99734f0341d9cbdd6a
{% endtab %}

{% tab title="Capa" %}

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FbLRDXGzexyeB13ojHK37%2Fimage.png?alt=media&amp;token=e364ecc4-aa82-417b-9e9e-ad3f973e8530" alt=""><figcaption></figcaption></figure>
{% endtab %}

{% tab title="Sections" %}

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FbYdWL89bB3Rxtwk7EBnL%2Fimage.png?alt=media&amp;token=b8e826d9-0f88-4965-84cb-c4b81910fc79" alt=""><figcaption></figcaption></figure>
{% endtab %}

{% tab title="Anomalies" %}

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2Fz51UeXn0hcqrIvQkjywS%2Fimage.png?alt=media&amp;token=9ecd0ff5-18a2-4257-8c1b-176ce19aeaec" alt=""><figcaption></figcaption></figure>
{% endtab %}

{% tab title="Interesting strings" %}
─────────────────────────\
FLOSS TIGHT STRINGS (7)\
─────────────────────────

%appdata%\Microsoft\Network\Cache\icon.png\
838645f4bab7458fb130c7120a500982\
resolvefree\
idstore.dll\
explorer.exe
{% endtab %}
{% endtabs %}

{% hint style="warning" icon="comment-question" %}
AI usage - GPT-5.6 Luna - Research (answer for Question 13)\
Prompt:\
*838645f4bab7458fb130c7120a500982*\
*What do you find about this?*
{% endhint %}

{% hint style="success" icon="message-check" %}

That string is **not just a random identifier**. I found an exact match in a July 2026 malware-analysis report from 360 Security / Threat Intelligence.

#### What it is

`838645f4bab7458fb130c7120a500982` is described as a **hard-coded key seed** used by malware attributed by 360 to **APT-C-20 / APT28 (Fancy Bear)**.

According to the analysis:

* The malware uses this seed with **PBKDF2** to derive an **AES-256 key**.
* It then reads a seemingly normal PNG file, `EdgeLogo.png`.
* Data hidden in the image using **LSB steganography** contains encrypted malware.
* The derived key is used to decrypt that hidden payload.
* The resulting shellcode is executed **in memory**, avoiding a conventional executable being written to disk.
* The later-stage malware communicates through the legitimate **Filen.io** cloud-storage infrastructure as a C2 mechanism.

The same report gives related hashes, including:

* `77014b3e77529079f041b5b9e73a013b` — `readme.docm`
* `b077401fe3d9642345d4c3deafa60aa5` — `dnxstore.dll`
* `d416eca59188474efa3408827578588b` — `Publish.exe`&#x20;
  {% endhint %}

### String decryption

API Hashing (described in the next chapter) occurs at different places within this DLL and is performed as a preparation for subsequent calls to resolved APIs. Before this procedure, the malware decrypts content, embedded as a cipher in the <kbd><mark style="color:$primary;">.rdata<mark style="color:$primary;"></kbd> section (Question 8, Question 9 and Question 11).

{% hint style="warning" icon="comment-question" %}
AI usage - Sidekick (Binary Ninja) - Decryption (similar prompts for other decryption)\
Prompt:\
*Perform the decryption for me in the for-loop, starting at 0x180002374 to 0x180002399.*\
*Perform the decryption for me in the for-loop, starting at 0x180001462 to 0x180001485.*
{% endhint %}

{% hint style="success" icon="message-check" %}

Decode 12 two-byte characters

0x180002374–0x180002399

Each iteration:

1. mov cl, r8b; and cl, 3; shl cl, 4 computes (index % 4) × 16.
2. mov rdx, r14; shr rdx, cl selects a 16-bit portion of the key.
3. xor dx, word \[r15+r8\*2+0x28c50] XORs that key word with an encrypted word at 0x180028c50 + index\*2.
4. mov word \[rsp+r8\*2+0x20], dx writes the decoded word into a stack buffer.
5. The loop increments r8 and repeats until all 12 words are decoded.

The repeating XOR words are:

```
ED03  D192  4A32  D1B5
```

For example, the first encrypted word is 0xed6a:

```
0xed6a XOR 0xed03 = 0x0069 = 'i'
```

Result: idstore.dll, encoded as UTF-16LE, including its terminating NUL. Later, at 0x18000253d, the function passes this decoded buffer to LoadLibraryExW.

I named the encrypted source and decoded stack buffer and annotated the loop in the database.
{% endhint %}

{% hint style="success" icon="message-check" %}
The loop at 0x180001462–0x180001485 decrypts this ASCII string:

```
838645f4bab7458fb130c7120a500982
```

That is 32 characters plus a terminating NUL, totaling 0x21 bytes.

| Property            | Value                      |
| ------------------- | -------------------------- |
| Encrypted source    | 0x180028bb8                |
| XOR key             | 0x9e3779b97f4a7c15         |
| Repeating key bytes | 15 7C 4A 7F B9 79 37 9E    |
| Destination         | Stack buffer at rsp + 0x30 |

Unlike the earlier UTF-16 loop, this one processes one byte per iteration:

* 0x180001468–0x18000146e selects a key byte by shifting the key by (index & 7) × 8.
* 0x180001471 XORs that byte with the encrypted source byte.
* 0x180001479 stores the plaintext byte.
* 0x18000147e increments the index by 1, since the setup sets rsi = 1.

For example, the first byte is 0x2d XOR 0x15 = 0x38, the ASCII character 8.

I named the source encrypted\_hex\_string\_838645f4 at 0x180028bb8 and annotated the decoding loop. The plaintext looks like a hexadecimal value; its format alone does not establish whether it is a hash, key, or identifier.
{% endhint %}

The first string decrypted is <kbd><mark style="color:$primary;">idstore.dll<mark style="color:$primary;"></kbd>. This is the DLL of the hijacked COM interface to force <kbd><mark style="color:$primary;">explorer.exe<mark style="color:$primary;"></kbd> to load the malicious <kbd><mark style="color:$primary;">uimanager.dll<mark style="color:$primary;"></kbd> (see [above](#persistence-com-hijacking)). This DLL will be loaded into the process, to ensure correct functionality of <kbd><mark style="color:$primary;">explorer.exe<mark style="color:$primary;"></kbd> (Question 10).

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FCeiYjuZA2xyVqUmFgmOG%2Fimage.png?alt=media&amp;token=c81c0b98-8dfa-4f21-b618-88df01efd322" alt=""><figcaption></figcaption></figure>

Question 12 asks for the decrypted string that will be later used to create a mutex (<kbd><mark style="color:$primary;">resolvefree<mark style="color:$primary;"></kbd>).

Additional strings being decrypted during execution are:

<kbd><mark style="color:$primary;">%appdata%\Microsoft\Network\Cache\icon.png<mark style="color:$primary;"></kbd>\
\ <kbd><mark style="color:$primary;">838645f4bab7458fb130c7120a500982<mark style="color:$primary;"></kbd>

### API Hashing

From triaging we understand, that the DLL uses API hashing to resolve various APIs dynamically without exposing their plaintext imports. With the output of <kbd><mark style="color:$success;">capa<mark style="color:$success;"></kbd> and using the <kbd><mark style="color:$success;">HashDB<mark style="color:$success;"></kbd> plugin we are able to identify [<kbd>FNV-1a</kbd>](https://mojoauth.com/security-guides/fnv-1a-in-c#how-to-implement-fnv-1a-in-c) as hash algorithm used (and Question 4 - Question 7).

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FKn7vv6NrsDAjW03CP6VV%2Fimage.png?alt=media&amp;token=500dbb50-e7f0-4052-add7-78490f54ec40" alt=""><figcaption><p>resolved hashed in Malcat</p></figcaption></figure>

API Hashing occurs at different places within this DLL and is performed as a preparation for subsequent calls to resolved APIs (see below).

### Time check

The function located <kbd><mark style="color:$primary;">0x180001c94<mark style="color:$primary;"></kbd> resolves the following APIs: <kbd><mark style="color:$primary;">kernel32!Sleep<mark style="color:$primary;"></kbd>, <kbd><mark style="color:$primary;">kernel32!QueryPerformanceCounter<mark style="color:$primary;"></kbd> and <kbd><mark style="color:$primary;">kernel32!QueryPerformanceFrequency<mark style="color:$primary;"></kbd>.&#x20;

This function is executed before additional actions happen. If it returns false, the malware skips additional activities.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FLu3RZyljkE2jvhrCxVV9%2Fimage.png?alt=media&amp;token=bc04e433-1109-481f-a8ad-d072d01da8c4" alt=""><figcaption></figcaption></figure>

Basically, the code in the picture above, retrieves a timestamp before executing a <kbd><mark style="color:$primary;">Sleep<mark style="color:$primary;"></kbd> for 5000ms resp. 5s. If <kbd><mark style="color:$primary;">Sleep<mark style="color:$primary;"></kbd> has been executed for longer than 4.9s, the malicious code continues.

### Thread Creation

At address <kbd><mark style="color:$primary;">0x1800022bd<mark style="color:$primary;"></kbd> the malware calls <kbd><mark style="color:$primary;">kernel32!CreateThread<mark style="color:$primary;"></kbd> after the pointer to this API has been resolved dynamically. This thread runs immediately after its creation and points via the <kbd><mark style="color:$primary;">lpStartAddress<mark style="color:$primary;"></kbd> parameter to the address <kbd><mark style="color:$primary;">0x18000208c<mark style="color:$primary;"></kbd>.

### Steganography

Within the executed thread, the malware resolves additional APIs and decrypts the path of the dropped picture (see above). Afterwards the malware attempts to locate and decrypt the embedded payload.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FyB7kzT8llFsqIWq1XP9C%2Fimage.png?alt=media&amp;token=1792206f-ccd5-41eb-aff8-625d371eade4" alt=""><figcaption></figcaption></figure>

#### Steganography basics

[Steganography](https://en.wikipedia.org/wiki/Steganography) is a technique used to hide information within another messages like in pictures or videos. These information is not evident to an unsuspecting person's examination.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FXc9ZjHaMb0zlciYzmAmb%2Fimage.png?alt=media&amp;token=35442f47-c886-4c0a-80fe-8bb34f92fda4" alt=""><figcaption><p>icon.png</p></figcaption></figure>

The technique applied here is LSB (least significant bit) Steganography. In a 24-bit RGB pixel, each color occupies one byte:

```
R = 182 = 0xB6 = 10110110
G = 105 = 0x69 = 01101001
B = 212 = 0xD4 = 11010100
```

Changing the LSB of the red channel would result in

```
10110110 → 10110111 - 182 -> 183
```

usually visually unseen for the human eye.

Extracting the LSB from the above pixel will return the bits <kbd>010</kbd>. With enough pixels, from a picture for example, LSB steganography is able to reconstruct hidden bytes:

```
Pixel 0 → 010
Pixel 1 → 000
Pixel 2 → 010
...
```

In case of the picture in this challenge, metadata reveals the following information:

| Value                                                                  | Data                                                                                 |
| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| <p>File Name<br>File Size<br>Dimensions<br>Bit Depth<br>Color Type</p> | <p>icon.png<br>210.3 KB<br>516 × 354 px<br>8 bits<br>RGBA (Truecolor with Alpha)</p> |

The Alpha channel adds transparency, effectively combining four bytes into one pixel.

#### Capacity

With the LSB steganography described above, it is only possible to hide a specific amount of information resp. bytes.

Since this exercise uses an image with four channels to hide information, we can perform the following calculation to determine the maximum number of bytes the attacker can store:

```
516 * 354 = 182,664 pixels
182,664 pixels * 4 bytes = 730656 bytes (Answer for Question 14 and Question 15)
730656 bytes / 8 = 91332 bytes (if only one LSB is used - Question 17; if four LSB are used it will be 365328 bytes - Question 18)
91332 bytes / 1024 = 89.2 kB
```

#### Analysis

Before the hidden information can be processed further, the malware must extract the needed bits from the image first.

In the calculation above, we see how many bytes are needed to apply the four channels on a 516x354 pixels picture. The same calculation (multiplication and division by 4) can be seen in the code, after the malware extracted the pixels from the image.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FlMaz5bHmM8Mp9mvS3YQb%2Fimage.png?alt=media&amp;token=46b3177a-844d-4609-a6f8-2e95f0c2dc52" alt=""><figcaption></figcaption></figure>

The function in the picture below is responsible for extracting the bits from each byte of each color channel.

{% hint style="warning" icon="comment-question" %}
AI usage - GPT-6 Astra with Binary Ninja MCP server & uimanager.dll - explaination of a function\
Prompt:\
*what about sub\_180004840? What is the purpose of the parameters? Rename them accordingly*
{% endhint %}

{% hint style="success" icon="message-check" %}
Renamed **`sub_180004840` → `extract_lsb_stego_bytes`**, renamed all five parameters, and saved the Binary Ninja database.

It reconstructs hidden data from the **least significant bits of decoded image bytes**, forming the extraction stage before decryption.

| Original | New name        | Purpose                                                                             |
| -------- | --------------- | ----------------------------------------------------------------------------------- |
| `arg1`   | `image_ctx`     | Pointer to an image context whose first field points to the decoded pixel buffer.   |
| `arg2`   | `output`        | Destination buffer for the extracted bytes.                                         |
| `arg3`   | `output_size`   | Number of **output bytes** to reconstruct.                                          |
| `arg4`   | `lsb_mode`      | Selects how many low bits to extract from each source byte; see below.              |
| `arg5`   | `source_offset` | Starting byte offset within the **decoded pixel buffer**, not the PNG file on disk. |

The mode is an encoded selector:

| `lsb_mode`      | Bits extracted per source byte | Source bytes per output byte |
| --------------- | ------------------------------ | ---------------------------- |
| `0`             | 1                              | 8                            |
| `1`             | 2                              | 4                            |
| Any other value | 4                              | 2                            |

The first source byte supplies the lowest bits of the reconstructed output byte.

In `decryptStego`, its four calls extract:

1. **16-byte salt** at source offset `0`, mode `0`.
2. **16-byte IV** at source offset `128`, mode `0`.
3. **64-byte encrypted header** at source offset `256`, mode `0`.
4. **Encrypted payload**, using the length, mode, and offset obtained from the decrypted header.
   {% endhint %}

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FwK5Qj3jvRA6xgW5RuTTR%2Fimage.png?alt=media&amp;token=df9b6537-8bbf-401b-8390-9a535c423166" alt=""><figcaption></figcaption></figure>

In mode 0, only one LSB (instruction <kbd>and cl, 0x1</kbd>) will be used to extract hidden information. That means, eight bytes are needed to extract one byte of hidden information (Question 16).

### Cryptography

The function to extract the LSB bytes from the dropped picture is called four times in total. Each call is used to only extract a small portion of the bytes for a different purpose.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FB68L9gsNAVIHWkQrWjY3%2Fimage.png?alt=media&amp;token=a84dcad1-8eeb-4e27-a9ce-5e1fd7938dd5" alt=""><figcaption></figcaption></figure>

The first call starts at <kbd>offset = 0</kbd>, extracts 16 bytes and saves them into a specific variable, that is later used as <kbd><mark style="color:$primary;">salt<mark style="color:$primary;"></kbd> in the <kbd>PBKDF2-HMAC-SHA256</kbd> function (Question 19 and Question 25). Within this function we can see calls to another function that perfoms hashing via <kbd>SHA256</kbd>, identifyable via various constants. In total, <kbd><mark style="color:$primary;">20000 iterations<mark style="color:$primary;"></kbd> are executed (Question 26).

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FVOxeZDCfYMrtdvzLW7Dz%2Fimage.png?alt=media&amp;token=7ec9b32e-9164-4364-a674-a5052b3d843d" alt=""><figcaption></figcaption></figure>

The <kbd><mark style="color:$primary;">IV<mark style="color:$primary;"></kbd> for the <kbd><mark style="color:$primary;">AES-256-CBC<mark style="color:$primary;"></kbd> decryption performed later is located at offset <kbd><mark style="color:$primary;">128<mark style="color:$primary;"></kbd> and calculated via a shift left by 3 operation before extracting the LSB (Question 20, Question 21 and Question 27). Similar calculation is performed to get the offset for the encrypted header, located at offset <kbd><mark style="color:$primary;">256<mark style="color:$primary;"></kbd> (Question 22).

Successful decryption of the encrypted header is verified via a check of the first four bytes, that show the word <kbd><mark style="color:$primary;">HIDE<mark style="color:$primary;"></kbd> ASCII encoded (Question 23).

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FrghXMRieBJWnAtJw2tU2%2Fimage.png?alt=media&amp;token=dd14ac33-b20b-4a93-a8b0-d75d973af077" alt=""><figcaption></figcaption></figure>

{% hint style="warning" icon="comment-question" %}
AI usage - GPT-6 Astra with Binary Ninja MCP server & uimanager.dll - creation of decryption script (Python)\
Prompt:\
*Analyze the function at 0x180002ea0 responsible for extracting LSB multiple times, getting salt, IV and encrypted header. It performs PBKDF-HMAC-SHA256 and AES-256-CBC decryption of an encrypted header and afterwards the final payload.*

*Create a python script for me that performs the decryption, based on the picture I pass as a command line argument. The script must perform the header check and produce the following output:*

* *decrypted payload*
* *decrypted header with HIDE as first four bytes*
* *print seed, salt, IV and AES key*
  {% endhint %}

{% hint style="success" icon="message-check" %}
{% code overflow="wrap" expandable="true" %}

```python
#!/usr/bin/env python3
"""Extract and decrypt the HIDE payload handled by uimanager.dll!0x180002ea0.

Install: python -m pip install Pillow pycryptodome
Usage:   python decrypt_stego.py picture.png [--out-dir output] [--seed TEXT]

Static data processing only: the extracted payload is written as bytes, never run.
The default seed is the ASCII string decoded by the caller at 0x1800013f0.
It is SHA-256 hashed as text bytes, NOT decoded from hexadecimal first.
"""

from __future__ import annotations

import argparse
from dataclasses import dataclass
import hashlib
from pathlib import Path
import struct
import sys

try:
    from Crypto.Cipher import AES
    from PIL import Image
except ImportError as exc:
    raise SystemExit(
        "Missing dependency. Install with: python -m pip install Pillow pycryptodome"
    ) from exc


DEFAULT_SEED = b"838645f4bab7458fb130c7120a500982"
ITERATIONS = 20_000
HEADER_SIZE = 64


@dataclass(frozen=True)
class DecryptionResult:
    header: bytes
    payload: bytes
    seed_sha256: bytes
    salt: bytes
    iv: bytes
    aes_key: bytes
    lsb_mode: int
    data_offset: int
    ciphertext_size: int
    padding_size: int


def extract_lsb(carrier: bytes, count: int, mode: int, offset: int) -> bytes:
    """Reproduce 0x180004840; offsets count decoded RGBA bytes, not pixels.

    Mode 0 takes one low bit per carrier byte, mode 1 takes two, and all
    other mode values take four. Earlier carrier bytes supply lower bits.
    """
    bits = 1 if mode == 0 else 2 if mode == 1 else 4
    stride = 8 // bits
    if count < 0 or offset < 0 or offset + count * stride > len(carrier):
        raise ValueError("LSB extraction extends outside the decoded image buffer")
    mask = (1 << bits) - 1
    output = bytearray(count)
    for i in range(count):
        start = offset + i * stride
        value = 0
        for j in range(stride):
            value |= (carrier[start + j] & mask) << (j * bits)
        output[i] = value
    return bytes(output)


def decrypt_carrier(carrier: bytes, seed: bytes = DEFAULT_SEED) -> DecryptionResult:
    """Decrypt an already-decoded, top-to-bottom RGBA image byte buffer."""
    # sha256_cstring stops at the first NUL, excluding that terminator.
    seed_sha256 = hashlib.sha256(seed.split(b"\x00", 1)[0]).digest()
    salt = extract_lsb(carrier, 16, 0, 0)
    iv = extract_lsb(carrier, 16, 0, 16 * 8)
    encrypted_header = extract_lsb(carrier, HEADER_SIZE, 0, 32 * 8)
    aes_key = hashlib.pbkdf2_hmac("sha256", seed_sha256, salt, ITERATIONS, dklen=32)

    # Keep this SAME cipher object for the body. After the header, its chaining
    # value is encrypted_header[-16:], as in the original mutable AES context.
    cipher = AES.new(aes_key, AES.MODE_CBC, iv=iv)
    header = cipher.decrypt(encrypted_header)
    if header[:4] != b"HIDE":
        raise ValueError("Decrypted header magic is not HIDE; check the image and seed")
    if struct.unpack_from("<H", header, 4)[0] != 1:
        raise ValueError("Unsupported HIDE header version (expected 1)")
    if any(header[52:64]):
        raise ValueError("Invalid HIDE header: the final 12 reserved bytes must be zero")

    mode = header[6]
    data_offset, ciphertext_size = struct.unpack_from("<II", header, 8)
    if ciphertext_size == 0:
        raise ValueError("Invalid HIDE header: encrypted payload length is zero")
    if ciphertext_size % AES.block_size:
        raise ValueError("Encrypted payload length must be a multiple of 16 bytes")
    ciphertext = extract_lsb(carrier, ciphertext_size, mode, data_offset)
    padded_payload = cipher.decrypt(ciphertext)

    # Match 0x180002ea0's actual trailer handling. It only checks the last
    # byte against the buffer length; it does not verify strict PKCS#7.
    padding_size = padded_payload[-1]
    if padding_size > len(padded_payload):
        raise ValueError("Payload padding count exceeds the decrypted payload length")
    payload = padded_payload[:len(padded_payload) - padding_size]
    return DecryptionResult(
        header, payload, seed_sha256, salt, iv, aes_key,
        mode, data_offset, ciphertext_size, padding_size,
    )


def load_rgba(image_path: Path) -> bytes:
    """Match the sample's four-channel image decode for PNGs up to 8 bits/channel."""
    with image_path.open("rb") as stream:
        png_header = stream.read(29)
    if png_header[:8] != b"\x89PNG\r\n\x1a\n":
        raise ValueError("Expected a PNG image")
    if len(png_header) < 29 or png_header[12:16] != b"IHDR":
        raise ValueError("PNG has no complete IHDR header")
    if png_header[24] == 16:
        # Pillow conversion of 16-bit RGB(A) and grayscale is not uniformly
        # equivalent to stb_image's high-byte conversion. Do not guess.
        raise ValueError("16-bit-per-channel PNGs are not supported by this extractor")
    with Image.open(image_path) as picture:
        picture.seek(0)
        return picture.convert("RGBA").tobytes()


def save_outputs(result: DecryptionResult, out_dir: Path, force: bool = False) -> tuple[Path, Path]:
    """Write the exact 64-byte header and the payload after trailer removal."""
    header_path = out_dir / "header.bin"
    payload_path = out_dir / "payload.bin"
    if not force and (header_path.exists() or payload_path.exists()):
        raise FileExistsError("Output files already exist; select another --out-dir or use --force")
    out_dir.mkdir(parents=True, exist_ok=True)
    for target, data in ((header_path, result.header), (payload_path, result.payload)):
        with target.open("wb" if force else "xb") as stream:
            stream.write(data)
    return header_path, payload_path


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument("image", type=Path, help="PNG containing the hidden encrypted data")
    parser.add_argument("--out-dir", type=Path, help="Output directory (default: ./<image_stem>_decrypted)")
    parser.add_argument("--seed", default=DEFAULT_SEED.decode("ascii"),
                        help="Seed text, encoded as UTF-8; defaults to the seed from this DLL")
    parser.add_argument("--force", action="store_true", help="Replace existing header.bin and payload.bin")
    args = parser.parse_args(argv)
    try:
        seed = args.seed.encode("utf-8")
        result = decrypt_carrier(load_rgba(args.image), seed)
        out_dir = args.out_dir or Path.cwd() / (args.image.stem + "_decrypted")
        # Do not allow an output path to replace the input image, even with --force.
        if args.image.resolve() in {(out_dir / name).resolve() for name in ("header.bin", "payload.bin")}:
            raise ValueError("An output path would overwrite the input image")
        header_path, payload_path = save_outputs(result, out_dir, args.force)
    except (OSError, ValueError, Image.DecompressionBombError) as exc:
        print(f"Error: {exc}", file=sys.stderr)
        return 1

    print(f"Seed (text):   {args.seed}")
    print(f"Seed SHA-256:  {result.seed_sha256.hex()}")
    print(f"Salt:          {result.salt.hex()}")
    print(f"IV:            {result.iv.hex()}")
    print(f"AES key:       {result.aes_key.hex()}")
    print("Header check:  HIDE / version 1 / reserved bytes OK")
    print(f"Header:        {header_path.resolve()} ({len(result.header)} bytes)")
    print(f"Payload:       {payload_path.resolve()} ({len(result.payload)} bytes)")
    print(f"Payload SHA256: {hashlib.sha256(result.payload).hexdigest()}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

```

{% endcode %}
{% endhint %}

Within the decrypted header the <kbd><mark style="color:$primary;">data\_offset<mark style="color:$primary;"></kbd> asked in Question 24 is visible at <kbd><mark style="color:$primary;">offset + 8<mark style="color:$primary;"></kbd>.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FACUGk3F7H7Q8k9RO9SUZ%2Fimage.png?alt=media&amp;token=3a2e6b54-c1bd-4037-8dd9-fe6e65304152" alt=""><figcaption></figcaption></figure>

For the <kbd><mark style="color:$primary;">AES-256-CBC<mark style="color:$primary;"></kbd> decryption performed, <kbd><mark style="color:$primary;">PKCS#7<mark style="color:$primary;"></kbd> padding is used (Question 28).

{% hint style="info" %}
[PKCS#7](https://node-security.com/posts/cryptography-pkcs-7-padding/) padding is used in block ciphers, to ensure the last block equals the defined block size. If, for example, the block size is defined as 16 bytes and the last block is less then 16 bytes, the difference will be padded to match the size of 16 bytes. Each byte added has the value of the padding length, calculated as difference between block size and the size of the last, unpadded block. If seven bytes are missing, the byte 0x07 will be padded.
{% endhint %}

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FDad5GRdDCRUtjCxOW9K5%2Fimage.png?alt=media&amp;token=969a60e7-aed3-4e1d-8a8f-96550309b677" alt=""><figcaption></figcaption></figure>

After decryption, the code in the picture above extracts the last byte of the decrypted payload and calculates the difference to get the size of the unpadded payload. After allocating space in the heap via <kbd><mark style="color:$primary;">HeapAlloc<mark style="color:$primary;"></kbd>, the code continues to copy the unpadded payload into the allocated memory region.

After removing the padding, the decrypted payload has a size of <kbd><mark style="color:$primary;">86331 bytes<mark style="color:$primary;"></kbd> (Question 29).

### Shellcode execution

After decrypting the payload and creating a mutex (not visible in the picture below), the malware allocates memory space for the shellcode and writes the decrypted payload into it.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2Fe573Yv0ZxC3E30iluJQB%2Fimage.png?alt=media&amp;token=e0f2eb0f-a6aa-480a-a283-5f2905b11540" alt=""><figcaption></figcaption></figure>

It resolves the API <kbd><mark style="color:$primary;">ntdll!NtAllocateVirtualMemory<mark style="color:$primary;"></kbd> via API Hashing and calls this function with the parameters visible below.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FwNbAq46heh9BvaI7hCTt%2Fimage.png?alt=media&amp;token=2c0c88fc-4fd6-4a15-9f6e-ecee8f983384" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" expandable="true" %}

```c
__kernel_entry NTSYSCALLAPI NTSTATUS NtAllocateVirtualMemory(
  [in]      HANDLE    ProcessHandle,
  [in, out] PVOID     *BaseAddress,
  [in]      ULONG_PTR ZeroBits,
  [in, out] PSIZE_T   RegionSize,
  [in]      ULONG     AllocationType, // 0x3000 (Question 30)
  [in]      ULONG     Protect // 0x40 - PAGE_EXECUTE_READWRITE (Question 31)
);
```

{% endcode %}

In the end, after the memory region has been allocated and filled in the own process space (still <kbd>explorer.exe</kbd>), the shellcode gets executed with a <kbd><mark style="color:$primary;">Sleep<mark style="color:$primary;"></kbd> interval of <kbd><mark style="color:$primary;">1200000<mark style="color:$primary;"></kbd> (Question 32).

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FiNRDP5EyqN72sFh0UnAv%2Fimage.png?alt=media&amp;token=51ed112c-5782-459f-98b7-7b0971b33d35" alt=""><figcaption></figcaption></figure>

Examining the written payload, reveals an embedded <kbd>.NET PE file</kbd>. The shellcode bootstraps the <kbd><mark style="color:$primary;">.NET CLR<mark style="color:$primary;"></kbd> runtime (Question 33).

***

## Stage 3 - The Final Payload - appresolve.exe

### Triage

{% tabs %}
{% tab title="SHA256" %}
f8da726832c341128c9f589b5334a788ffd0619f300fe4cb3cebdb4157f92a2e
{% endtab %}

{% tab title="Interesting Strings" %}
(potentially) many Base64 encoded strings. They do not decode into something meaningful during Triage
{% endtab %}

{% tab title="Capa" %}

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FmueL2VAjwvPsNTPALFdz%2Fimage.png?alt=media&amp;token=b4ef4ce9-2fe8-4902-a9f1-8b02186aeda3" alt=""><figcaption></figcaption></figure>
{% endtab %}

{% tab title="Anomalies" %}

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2Fi7s2OZPl0OiPwJTLpVXL%2Fimage.png?alt=media&amp;token=b9ca445d-9958-4fa7-82b5-d782482fdfdb" alt=""><figcaption></figcaption></figure>
{% endtab %}

{% tab title="DiE" %}

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FgjjK8WIPlLvZwugBMuRR%2Fimage.png?alt=media&amp;token=e50e1c16-ff13-440d-9372-fd981136312b" alt=""><figcaption></figcaption></figure>

Question 1: 4.7.2
{% endtab %}
{% endtabs %}

### Static analysis

The <kbd><mark style="color:$primary;">Main<mark style="color:$primary;"></kbd> routine is embedded within the <kbd><mark style="color:$primary;">VDT9SWRKDV<mark style="color:$primary;"></kbd> namespace (Question 2). Immediately the malware starts to decrypt a Base64 encoded string we gathered during Triage.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FOHgiahbIihIiFI96yeXJ%2Fimage.png?alt=media&amp;token=41d5806d-62e7-416a-89aa-c9d8b2811e47" alt=""><figcaption></figcaption></figure>

Overall, the .NET executable is heavily obfuscated and not easily readable. Neither <kbd><mark style="color:$success;">de4dotex<mark style="color:$success;"></kbd>, nor <kbd><mark style="color:$success;">DetectItEasy<mark style="color:$success;"></kbd> were able to detect a commonly used obfuscator.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FSl1zo6d82d8PpBWu6NrG%2Fcallgraph_EstablishSessionAndLoadStage.svg?alt=media&amp;token=cc052348-2a15-48af-ab62-ec04164f62ff" alt="" width="188"><figcaption><p>Callgraph (depth = 6)</p></figcaption></figure>

#### String decryption

The decryption routine (left side, <kbd><mark style="color:$success;">dnSpy<mark style="color:$success;"></kbd>) decodes the Base64 encoded string and then XORs it with <kbd><mark style="color:$primary;">BQGLBRTQON<mark style="color:$primary;"></kbd> (Question 3). The result is visible on the right side in <kbd><mark style="color:$success;">Malcat<mark style="color:$success;"></kbd>.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FfxerxeDE1QDsRuTxo94h%2Fimage.png?alt=media&amp;token=4b742170-2870-48bc-bd8a-099a9045bb08" alt=""><figcaption></figcaption></figure>

{% hint style="warning" icon="comment-question" %}
AI usage - GPT-6 Astra with Malcat MCP server & appresolve.exe - string decryption\
Prompt:\
*I opened the final payload in Malcat, accessibly via MCP. It contains several Base64 strings. They are decoded and then XORed with the ASCII string 'BQGLBRTQON'.*\
*Perform the decryption for me for each Base64 encoded string you find. Create a Python script, embed it in Malcat and add a comment with each plaintext string next to the Base64 encoded value*
{% endhint %}

{% hint style="success" icon="message-check" %}
{% code overflow="wrap" expandable="true" %}

```tsv
Base64	plaintext
ESg0OCc/Bj4gOg==	"SystemRoot"
Jyk3IA==	"expl"
DD4zKQ==	"Note"
OWE6YDljKQ==	"{0},{1}"
SA==	"\n"
emgheiQzZmJ3eg==	"89f6fa2384"
KjoSJCsDBhcbPycIMH4WABgGABhxPnQpdTkjHik/JgF+KwwZGmQcKXI7IHE=	"hkUhiQRFTqeYw2TRLWOV3o3e7kwOfqdP9gNKN5Sg0jg="
bDMuIg==	".bin"
YmE=	" 0"
YmA=	" 1"
YmM=	" 2"
bw==	"-"
egUGCxUNFgUuIRgHMik7HBkUFgESBnciDQo9JD85KDo9BG85GT4FL28LBTomGx8gBi0OPSh+EwFkFCwmKhIXNg==	"8TAGW_BTaoZVueyNMEYOPW0nOXiupwjkzH-kMoJa-ZBvdIKqIcLlo2QS0EchhCPz"
cGRyeCNgbTAreiEzc3sgYW00dy8gMnN6IGpkaSl5IDMmKnRmYmh5e3BhcSlxNzVheCh3aXApJzE1Zyt8ejIieQ==	"2554a29ad4cb47b39e8abc46b808f7bbaf646965206e3ea07f587eeca6d28ce5"
JjAkfXszZmRiL3ZgdGF2ZmRgYiwgN3JhdzBsYix2emd3eiAx	"dac19a25-a413-4401-bbf5-5b83c88606bc"
ERkGfndk	"SHA256"
KiUzPDFoe34=	"https://"
KiUzPDFoe34rIDF/ICMtNTg0YDwnIiggNDdrPy4jJ2w=	"https://dns.google/resolve?name="
ZCU+PCdvFQ==	"&type=A"
IyE3ICsxNSUmISx+IyIxfz4iICA=	"application/dns-json"
NiMyKQ==	"true"
cw==	"1"
LDAqKQ==	"name"
IyE3ICsxNSUmISx+KC82NyB8PDowNCYh	"application/octet-stream"
JTAzKTUzLX8pJy40KWIrPQ==	"gateway.filen.io"
JTAzKTUzLX8pJy40KWIsNyA=	"gateway.filen.net"
JTAzKTUzLX8pJy40KWFzfDo0Ow==	"gateway.filen-1.net"
JTAzKTUzLX8pJy40KWFwfDo0Ow==	"gateway.filen-2.net"
JTAzKTUzLX8pJy40KWFxfDo0Ow==	"gateway.filen-3.net"
JTAzKTUzLX8pJy40KWF2fDo0Ow==	"gateway.filen-4.net"
JTAzKTUzLX8pJy40KWF3fDo0Ow==	"gateway.filen-5.net"
JzYiPzZ8MjgjKyx/LiM=	"egest.filen.io"
JzYiPzZ8MjgjKyx/KSk2	"egest.filen.net"
JzYiPzZ8MjgjKyx8dmIsNyA=	"egest.filen-1.net"
JzYiPzZ8MjgjKyx8dWIsNyA=	"egest.filen-2.net"
JzYiPzZ8MjgjKyx8dGIsNyA=	"egest.filen-3.net"
JzYiPzZ8MjgjKyx8c2IsNyA=	"egest.filen-4.net"
JzYiPzZ8MjgjKyx8cmIsNyA=	"egest.filen-5.net"
Kz8gKTEmejcmIic/aSUt	"ingest.filen.io"
Kz8gKTEmejcmIic/aSInJg==	"ingest.filen.net"
Kz8gKTEmejcmIic/an1sPDEl	"ingest.filen-1.net"
Kz8gKTEmejcmIic/an5sPDEl	"ingest.filen-2.net"
Kz8gKTEmejcmIic/an9sPDEl	"ingest.filen-3.net"
Kz8gKTEmejcmIic/anhsPDEl	"ingest.filen-4.net"
Kz8gKTEmejcmIic/anlsPDEl	"ingest.filen-5.net"
IyE3ICsxNSUmISx+LT8tPA==	"application/json"
IzMkKCc0MzkmJCk9KiItIiUjPDo3JzA0OyhkYH19dmRxe3pr	"abcdefghijklmnopqrstuvwxyz0123456789"
bDY3Kw==	".gpg"
bCEgPA==	".pgp"
bDAgKQ==	".age"
bDQpLw==	".enc"
bDUmOA==	".dat"
bDMrIyA=	".blob"
bDImLyo3	".cache"
bCEmLyk=	".pack"
bDgjNA==	".idx"
bCUqPA==	".tmp"
bCYmIA==	".wal"
AxMECAcUExkGBAkdCgINAgUDHBoXBxAUGwg1MywqJzcgJCs4Pz0iIC0hNj4xJiEnODY7K3d9cGFgZHl5emg=	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
cmF1	"002"
bSd0YyY7Jn4sPCcwMyk=	"/v3/dir/create"
bSd0YyY7Jn4sISwlIiI2	"/v3/dir/content"
bSd0YyY7Jn4qNisiMz8=	"/v3/dir/exists"
bQ==	"/"
bSd0YyY7Jn4rKy40Mylt	"/v3/dir/delete/"
bSd0YyQ7ODRgKic9IjgnfQ==	"/v3/file/delete/"
ADQmPicg	"Bearer"
bSd0YzciOD4uKm01KCIn	"/v3/upload/done"
bSd0YzciOD4uKn0=	"/v3/upload?"
NyQuKH8=	"uuid="
ZDgpKCcqaQ==	"&index="
ZCEmPic8IGw=	"&parent="
ZCQ3IC0zMBoqN38=	"&uploadKey="
ZDkmPypv	"&hash="

```

{% endcode %}
{% endhint %}

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2Ffsp7doMg9vwBykyI6Ci3%2Fimage.png?alt=media&amp;token=cc31ae28-8eea-49d8-a7ab-5f6739556578" alt=""><figcaption></figcaption></figure>

With the decrypted strings it is possible to answer some questions and get a basic idea of what this stage is doing.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FtaUvjp0J6FTdHDexF71o%2Fimage.png?alt=media&amp;token=fba39a50-e305-4491-8777-adca90d2402c" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FutK8JuJzyejR73lPATup%2Fimage.png?alt=media&amp;token=861fd19b-11ec-43e9-87df-288dcb457fcd" alt=""><figcaption><p>encrypted config in dnSpy</p></figcaption></figure>

Question 4: gateway.filen.io\
Question 5: ingest.filen.io\
Question 6: egest.filen.io

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2F3wT6X2RXD11tRsMUNUWG%2Fimage.png?alt=media&amp;token=8c2e5bb0-ce64-4d54-932d-b4f4113b11aa" alt=""><figcaption></figcaption></figure>

Question 7: /v3/dir/exists\
Question 8: /v3/dir/content\
Question 9: /v3/upload/done\
Question 10: /v3/file/delete/

Question 11: hxxps\[://]dns\[.]google/resolve?name=\&type=A

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FQaZxnBRqn6cYHjGCMf37%2Fimage.png?alt=media&amp;token=3e39d631-da72-4003-9867-fcf7092427cd" alt=""><figcaption><p>method for DNS over HTTPS. Concatenation of decrypted strings</p></figcaption></figure>

#### Advanced Cryptography

If the current process starts with <kbd><mark style="color:$primary;">expl<mark style="color:$primary;"></kbd> (Question 25) the .NET executable starts its additional operation. I renamed the method into <kbd>start</kbd>. If this check fails, the malware sleeps for <kbd><mark style="color:$primary;">62914560 ms<mark style="color:$primary;"></kbd> (approx 17.5h - Question 26).

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FUh0TSvxx8R57Eib4WQ3H%2Fimage.png?alt=media&amp;token=8fb88033-17a7-4013-88ad-e76157405c9c" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FT5LBX5r6FPwjLL83aS9l%2Fimage.png?alt=media&amp;token=ab54d66d-6b40-456a-92eb-6979b5e0037b" alt=""><figcaption><p>sleep interval visible here for Question 19</p></figcaption></figure>

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FgjlcWI6McSyk0x47l8l7%2Fimage.png?alt=media&amp;token=5081a0c0-7ed6-42c6-8bdd-889ca1d797a9" alt=""><figcaption></figcaption></figure>

At the beginning of <kbd>start</kbd> a new <kbd>RSACryptoServiceProvider</kbd> with a key size of <kbd><mark style="color:$primary;">2048 bit<mark style="color:$primary;"></kbd> will be created (Question 14).

During the registration phase, the malware uses the value <kbd><mark style="color:$primary;">0<mark style="color:$primary;"></kbd> (Question 12 - line 380) and a GUID, based on the <kbd><mark style="color:$primary;">MD5<mark style="color:$primary;"></kbd> hash value (Question 20) created previously, using the user domain name and user name of the current logged in user of the infected machine.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2F3NUIXzVQWczopP6h71hI%2Fimage.png?alt=media&amp;token=e3b9dfe1-5d84-473b-9918-bdd871eb98e2" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FoDneYHr3dRUNJr1OQ7UV%2Fimage.png?alt=media&amp;token=8e7fa2a4-ad00-4be8-9a72-a42807baabe5" alt=""><figcaption><p>Question 21 and Question 22 can be answered with this picture</p></figcaption></figure>

With a search of the encrypted string for the value asked in Question 13 (<kbd><mark style="color:$primary;">1 - YmA=<mark style="color:$primary;"></kbd>) we get to the following location:

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FiRceH80vwPCoVfx9u9la%2Fimage.png?alt=media&amp;token=513aeeb7-b3af-47a5-b427-c7f89dc33b3c" alt=""><figcaption></figcaption></figure>

In line 398 in the picture the code performs decryption, using the <kbd><mark style="color:$primary;">OAEP<mark style="color:$primary;"></kbd> padding parameter (Question 15).

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FkFC4WIYzx7faj3YecmI5%2Fimage.png?alt=media&amp;token=ed6f9826-e3c4-4b92-a973-905f63c8072e" alt=""><figcaption></figcaption></figure>

Before encryption of the JSON message takes place, the malware creates an <kbd><mark style="color:$primary;">AES-256-CBC<mark style="color:$primary;"></kbd> object used for encryption (Quesiton 16). In the pictures below we can see how this object is being created, before it is actually used.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FvBE5czAIjquPq6955cmj%2Fimage.png?alt=media&amp;token=98c7ca69-6431-4263-834e-938bf27c5fed" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FZAT0jyCdNxTzsUqrqwFk%2Fimage.png?alt=media&amp;token=57b517ff-5f08-446e-805f-776bc96db6d4" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2F3wtmrb6F87sjIVOniOUD%2Fimage.png?alt=media&amp;token=34f1c921-899e-4987-9e11-ef5f8ea93315" alt=""><figcaption></figcaption></figure>

The malware uses <kbd><mark style="color:$primary;">HMAC-SHA256<mark style="color:$primary;"></kbd> (Question 17) as message integrity mechanism for its C2 communication. Via the function <kbd><mark style="color:$primary;">bcrypt!BCryptOpenAlgorithmProvider<mark style="color:$primary;"></kbd> it loads and initializes the crypto provider. In this case, it uses SHA256 (2nd parameter; decrypted string) and the value 8 (fourth parameter; **BCRYPT\_ALG\_HANDLE\_HMAC\_FLAG**) to use SHA256 for HMAC (see [Microsoft documentation](https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptopenalgorithmprovider)).

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2F6vxIwCdsBTGSbjJF66mX%2Fimage.png?alt=media&amp;token=e18eb704-ae6d-4ed0-b832-f4345a9b5bc0" alt=""><figcaption></figcaption></figure>

The cryptographic operations performed by this .NET executable are exported by <kbd><mark style="color:$primary;">bcrypt.dll<mark style="color:$primary;"></kbd> which is visible in the import table (Question 18) but also within the class <kbd><mark style="color:cyan;">JHAAFAND43<mark style="color:cyan;"></kbd>.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2Fk8Ab9sDesJjNQ1BnGtDa%2Fimage.png?alt=media&amp;token=de25a8b2-cafa-4095-8a4c-94f6d181af1d" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2Fb7UiPxbZddscIydsahFk%2Fimage.png?alt=media&amp;token=271b6a3e-eb0a-4cd0-ab0f-a77f327f867b" alt=""><figcaption></figcaption></figure>

In the pictures below we can see, how various classes and methods are used to create a new <kbd><mark style="color:$primary;">AES-256-GCM<mark style="color:$primary;"></kbd> (Question 23) algorithm provider with a corresponding property. The malware uses a decimal encoding to eliminate plaintext information about the setup of the crypto provider.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FF6Kx2F25C9WrPWMv8gGp%2Fimage.png?alt=media&amp;token=2ecf069a-3c06-4c0a-a00d-9ea42b0b3736" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FITdIOval5aqZc1un0ZBY%2Fimage.png?alt=media&amp;token=dda84895-63ef-4aee-8e97-b2fb0d8bb88c" alt=""><figcaption></figcaption></figure>

Tracing back the execution chain via the <kbd>Analyze</kbd> function of <kbd><mark style="color:$success;">dnSpy<mark style="color:$success;"></kbd> allows us to see where and how the encryption with <kbd><mark style="color:$primary;">AES-256-GCM<mark style="color:$primary;"></kbd> is used.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FOtO9YaAXy0C8fnyY9uzh%2Fimage.png?alt=media&amp;token=036c0c22-bc02-4bf3-86d0-c738d3e5efb1" alt=""><figcaption></figcaption></figure>

Before encryption takes place, an encryption key needs to be derived. To achieve this goal, the <kbd><mark style="color:cyan;">Rfc2898DeriveBytes<mark style="color:cyan;"></kbd> class is used. These constructors are marked as obsolete by Microsoft, but are still used in this malware. In this case, the constructor takes four arguments, as defined [here](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rfc2898derivebytes.-ctor?view=net-10.0#system-security-cryptography-rfc2898derivebytes-ctor\(system-string-system-byte\(\)-system-int32-system-security-cryptography-hashalgorithmname\)). \
Basically, <kbd><mark style="color:$primary;">PBKDF2-HMAC-SHA512<mark style="color:$primary;"></kbd> (Question 23) is used to derive a 32 byte AES encryption key, with identical password and salt bytes and with only one iteration.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FG8xdC2B4ChTe7AOlPpK9%2Fimage.png?alt=media&amp;token=f0411a4a-b4b7-4303-a6e9-c0d0ad012efe" alt=""><figcaption></figcaption></figure>

#### Loading Assembly

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FYPbWuy4gSf3ckLdELHQw%2Fimage.png?alt=media&amp;token=5fae2fb7-7aac-45e7-8b02-251216d31ef4" alt=""><figcaption></figcaption></figure>

In the end of the whole routine, the malware uses the .NET API <kbd><mark style="color:$primary;">Assembly.Load<mark style="color:$primary;"></kbd> to reflectively load the final payload into memory (Question 27).

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FambAJ1HWLcaqOfYnNR2q%2Fimage.png?alt=media&amp;token=8156c68c-c2c2-48e8-805e-d9d94fc3a394" alt=""><figcaption></figcaption></figure>

After the final stage has been loaded into memory, the malware needs to find the desired entry method. Therefor, it uses <kbd><mark style="color:$primary;">GetCustomAttribute.DisplayNameAttribute<mark style="color:$primary;"></kbd> (Question 28).&#x20;

The desired name is encrypted (see above; Question 29) and passed as a parameter into the function responsible for finding the method that is desired to be executed.

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FmV976ZM8dNLFMfUbwGZu%2Fimage.png?alt=media&amp;token=d4ae640f-d0be-4c71-89e8-bbce56bed75f" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2FFNethf8CeGJBbp2iMhlr%2Fimage.png?alt=media&amp;token=3692d0b8-8779-41c1-8cbf-ea85db8e6640" alt=""><figcaption></figcaption></figure>

#### Threat actor

Based on the analysis performed during this lab, we know that this malware belongs to [APT28](https://attack.mitre.org/groups/G0007/). Researching the according MITRE page, gives us the following answers for Question 30:

<kbd><mark style="color:$primary;">GRU Unit 26165, CVE-2022-38028<mark style="color:$primary;"></kbd>

### Deobfuscation with AI

At a certain point, manual deobfuscation became exhausting, so I used AI to at least automatically rename the functions.

{% hint style="warning" icon="comment-question" %}
AI usage - GPT-6 Astra with Malcat MCP server & appresolve.exe - deobfuscation\
Prompt:\
*Analyze the file opened in Malcat, accessible via the MCP server.*\
*Rename all functions, methods and classes accordingly but do not provide any additional informationen about this binary. I want to go through it by myself*
{% endhint %}

{% hint style="success" icon="message-check" %}
Done. Renamed the functions, methods, and classes in Malcat, preserved your comments.
{% endhint %}

<figure><img src="https://1703752413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfsfLFJyQD5J9YvbCICsP%2Fuploads%2Fhllw6mbMnbnkdlORNG3v%2Fimage.png?alt=media&amp;token=f3cc88da-2e76-4049-908e-496c441cfcdc" alt=""><figcaption></figcaption></figure>

Additionally, after I answered all questions, I asked AI to analyze the whole .NET executable for me to get a better understanding about the purpose of this stage but also <mark style="color:$danger;">**how**</mark> the malware achieves its goal. Just for learning purposes.

{% hint style="warning" icon="comment-question" %}
AI usage - GPT-6 Astra with Malcat MCP server & appresolve.exe - purpose of the file\
Prompt:\
*Opened the file in Malcat again.*\
*Analyze it for me and explain the overall purpose of this malware and how it accomplishes its goals.*

*NEVER EXECUTE THE FILE*
{% endhint %}

{% hint style="success" icon="message-check" %}
The file open in Malcat is an **obfuscated .NET stager**. Its purpose is to establish encrypted communications through **Filen cloud storage**, download another .NET assembly, and execute it inside the current process.

I analyzed it statically. **I did not execute the file or contact its remote infrastructure.**

| Phase                   | What it does                                                                                                                                         | Purpose                                                                                                                     |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| Check execution context | Requires the process name to start with `expl` and its executable path to start with `%SystemRoot%`. Otherwise, sleeps approximately **17.5 hours**. | Consistent with expecting an Explorer-like host. This assembly does not itself perform the earlier COM hijack or injection. |
| Identify the client     | Derives a ten-character identifier from the domain, username, and an embedded configuration value.                                                   | Associates messages with a particular client.                                                                               |
| Prepare communication   | Uses embedded Filen authorization and encryption settings. Creates a client-specific receive folder, replacing an existing one.                      | Establishes a cloud-file mailbox for replies.                                                                               |
| Negotiate encryption    | Generates an **RSA-2048** key pair, sends its public key, and unwraps the returned session key using **RSA-OAEP**.                                   | Establishes a new secret for subsequent communications.                                                                     |
| Exchange challenges     | Sends a random four-byte challenge, verifies its echo, and returns the peer’s challenge.                                                             | Checks continuity of the negotiated exchange.                                                                               |
| Retrieve another stage  | Verifies the final reply’s HMAC, decrypts it, and calls **`Assembly.Load(byte[])`**.                                                                 | Loads downloaded managed code directly from memory.                                                                         |
| Transfer control        | Finds a public method marked **`DisplayName("Note")`** and invokes it with communication settings, client identifier, and the session AES object.    | Lets the downloaded stage continue using the established session.                                                           |
| {% endhint %}           |                                                                                                                                                      |                                                                                                                             |

***

## Detection possibilities

### Host based indicators

<table><thead><tr><th width="115">Type</th><th width="752">Value</th><th width="92">Stage</th></tr></thead><tbody><tr><td>SHA256</td><td>c99d00dede7a704ce86ef5fa5f8ec7e2388750327ec04f9691e11a32267ba031 - sample.exe</td><td>1</td></tr><tr><td>File path</td><td>%APPDATA%\Microsoft\Network\Cache\icon.png</td><td>1</td></tr><tr><td>File path</td><td>%APPDATA%\Microsoft\Internet Explorer\UserData\Low\DB\uimanager.dll </td><td>1</td></tr><tr><td>Registry</td><td>HKEY_CURRENT_USER\SOFTWARE\Classes\CLSID{30d49246-d217-465f-b00b-ac9ddd652eb7}\InProcServer32\(Default) - %APPDATA%\Microsoft\Internet Explorer\UserData\Low\DB\uimanager.dll</td><td>1</td></tr><tr><td>Behavior</td><td>taskkill.exe killing explorer.exe</td><td>1</td></tr><tr><td>Behavior</td><td>excel.exe spawns taskkill.exe and explorer.exe</td><td>1</td></tr><tr><td>Behavior</td><td>uimanager.dll as loaded module in explorer.exe</td><td>1</td></tr><tr><td>Behavior</td><td>uimanager.dll as loaded module in regsvr32.exe</td><td>1</td></tr><tr><td>SHA256</td><td>6c7ddb53dd300d39f2e5b3d4d599be56991e807c633b5a5564b8b7d3fc7d491b - %APPDATA%\Microsoft\Network\Cache\icon.png</td><td>2</td></tr><tr><td>SHA256</td><td>125abe1a497067a61011eb6072b1b3d91d3f0b658ed20a99734f0341d9cbdd6a - %APPDATA%\Microsoft\Internet Explorer\UserData\Low\DB\uimanager.dll </td><td>2</td></tr><tr><td>Mutex</td><td>resolvefree</td><td>2</td></tr></tbody></table>

### Network based indicators

<table><thead><tr><th width="170">Type</th><th width="625">Value</th><th>Stage</th></tr></thead><tbody><tr><td>DNS</td><td>ingest[.]filen[.]io</td><td>1</td></tr><tr><td>Behavior</td><td>explorer.exe communcating with filen domains</td><td>3</td></tr><tr><td>Traffic</td><td>dns[.]google</td><td>3</td></tr><tr><td>Traffic</td><td>gateway[.]filen[.]io, ingest[.]filen[.]io, egest[.]filen[.]io</td><td>3</td></tr><tr><td>API endpoints</td><td>/v3/dir/content, /v3/upload, /v3/upload/done, and /v3/file/delete/permanent</td><td>3</td></tr></tbody></table>
