# IcedID Config Extraction

Hi all,\
this report provides a technical description of unpacking and extraction of the C2 config from an <kbd>IcedID loader DLL</kbd>, part of Zero2Automate course's biweekly challenges.

This loader unpacks a next stage DLL into its own memory space. The next stage is then responsible for contacting the encrypted C2 domain to retrieve a GZIP file, which contains additional content for execution of additional stages.

The analyzed sample has the SHA256 hash

> 0581f0bf260a11a5662d58b99a82ec756c9365613833bce8f102ec1235a7d4f7

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

{% 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 %}

***

## Packed sample

### Triage

{% tabs %}
{% tab title="SHA 256" %}
0581f0bf260a11a5662d58b99a82ec756c9365613833bce8f102ec1235a7d4f7
{% endtab %}

{% tab title="Interesting Strings" %}
One huge hex string

<figure><img src="/files/PSf05HHRxP6I7oSTl0oT" alt=""><figcaption></figcaption></figure>
{% endtab %}

{% tab title="Imports" %}
very few libraries and APIs imported statically
{% endtab %}

{% tab title="Exports" %}

<figure><img src="/files/E9MSTCrPLou4r2qyU8wL" alt=""><figcaption></figcaption></figure>
{% endtab %}

{% tab title="Sections" %}

<figure><img src="/files/91vv8c4nmCp7evKIfh5S" alt=""><figcaption></figcaption></figure>
{% endtab %}

{% tab title="Conclusion" %}

* 64 bit DLL
* very few imports ⇒ additional APIs resolved dynamically?
* some exports
* huge <kbd>.data</kbd> section
* huge hex string
  {% endtab %}
  {% endtabs %}

### Advanced Static Analysis

Opening up the DLL with a Disassembler and jumping to the exported function <kbd><mark style="color:$primary;">DllRegisterServer<mark style="color:$primary;"></kbd> immediately shows us a complex and deep function with a lot of jump instructions and register modifications via <kbd>add</kbd> instructions.

<figure><img src="/files/ngM0mHlWFZ7MN2JCLyJr" alt=""><figcaption><p>graph view of DllRegisterServer</p></figcaption></figure>

Clicking a little bit around, I noticed access to the <kbd><mark style="color:$warning;">Process Environment Block (PEB)<mark style="color:$warning;"></kbd> and the <kbd><mark style="color:$warning;">\_PEB\_LDR\_DATA<mark style="color:$warning;"></kbd> member within function <kbd><mark style="color:$primary;">0x180005ca1<mark style="color:$primary;"></kbd>.

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

<figure><img src="/files/9DzUet6bT9Eft9wg1PoD" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/q3yNAfDOoRsSeONLKFje" alt=""><figcaption><p>_PEB_LDR_DATA structure</p></figcaption></figure>

The access to the <kbd><mark style="color:$warning;">PEB<mark style="color:$warning;"></kbd> and the <kbd><mark style="color:$warning;">\_PEB\_LDR\_DATA<mark style="color:$warning;"></kbd> may indicate, that the malware wants to walk through the list of loaded modules, in order to locate desired APIs and extend the malware's functionalities. This allows the malware to enumerate loaded modules and to avoid the usage of APIs like <kbd><mark style="color:$success;">GetProcAddress<mark style="color:$success;"></kbd> or <kbd><mark style="color:$success;">GetModuleHandle<mark style="color:$success;"></kbd>.&#x20;

In combination with <kbd>API-Hashing</kbd>, this procedure allows a malware in general to resolve and execute various APIs in a more stealthy approach because it identifies needed functions by comparing pre-calculated hashes instead of storing API names in plaintext.

The resolved function pointers for desired APIs will be stored on the stack, allowing the malware to call them indirectly.

Instead of reversing this technique, I decided to switch over to Dynamic Analysis with the idea in mind, to place breakpoints on various indirect calls to see which APIs are getting called.

### Unpacking with Dynamic Analysis

Since our overall goal is to unpack the malware, I decided to put breakpoints on APIs like <kbd><mark style="color:$success;">VirtualAlloc<mark style="color:$success;"></kbd>, <kbd><mark style="color:$success;">VirtualAllocEx<mark style="color:$success;"></kbd> , <kbd><mark style="color:$success;">VirtualProtect<mark style="color:$success;"></kbd> and <kbd><mark style="color:$success;">NtWriteProcessMemory<mark style="color:$success;"></kbd> that the malware may use to write some content into newly allocated memory. Additionally, to learn a little bit more about the malware's behavior and the resolved APIs, I put additional breakpoints on all indirect calls within <kbd><mark style="color:$primary;">DllRegisterServer<mark style="color:$primary;"></kbd>.

Shortly after execution, the malware called the APIs <kbd><mark style="color:$success;">NtQuerySystemTime<mark style="color:$success;"></kbd> and <kbd><mark style="color:$success;">RtlTimeToTimeFields<mark style="color:$success;"></kbd>. With the combination of both APIs, the malware gets the actual system time and converts it into a <kbd><mark style="color:$warning;">TIME\_FIELDS<mark style="color:$warning;"></kbd> structure. During my execution, the structure was filled with the values visible in the picture below.

<figure><img src="/files/QgF91ySC6H8nvSXskSJz" alt=""><figcaption><p>filled TIME_FIELDS structure</p></figcaption></figure>

The malware performs a comparison with the <kbd>Year</kbd> member of above structure and terminates execution, if the <kbd>Year</kbd> doesn't equal 2022 (0x7E6). This time-based execution check acts as a kind of defense evasion technique or as a restriction, preventing the malware from execution after 2022 when a specific campaign ended ([Time Based Checks](https://attack.mitre.org/techniques/T1497/003/) and [Execution Guardrails](https://attack.mitre.org/techniques/T1480/)).&#x20;

<figure><img src="/files/SUqIz3i3KtAFlN2UnNp1" alt=""><figcaption><p>comparison with the Year member</p></figcaption></figure>

During unpacking the malware, I noticed the huge hex string of the <kbd>.data</kbd> section identified during static analysis is copied into allocated heap memory and transformed into meaningful bytes, located in another allocated heap memory. These shellcode bytes (right picture below) assemble into readable instruction and are responsible for unpacking the next stage we are interested in.

{% columns %}
{% column %}

<figure><img src="/files/V2i8XhEJIp52Cscoj9tF" alt=""><figcaption></figcaption></figure>
{% endcolumn %}

{% column %}

<figure><img src="/files/RoMWLNB17LxmKqGsvPB5" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

The final call to <kbd><mark style="color:$success;">VirtualAlloc<mark style="color:$success;"></kbd> allocates memory for a valid PE file, our unpacked sample. Dumping and rebasing it, gives us a clean and readable PE file.

<figure><img src="/files/6ktzKPVtzgKG1MJHrsBD" alt=""><figcaption><p>call to VirtualAlloc with 0x180000000 as starting address</p></figcaption></figure>

{% columns %}
{% column %}

<figure><img src="/files/MUNf4BJ1AuQrK1IZ40Ag" alt=""><figcaption><p>rebased PE file</p></figcaption></figure>
{% endcolumn %}

{% column %}

<figure><img src="/files/yhaLoxsSP1tNyiFXdVrr" alt=""><figcaption><p>readable imports</p></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

## Unpacked sample

### Triage

{% tabs %}
{% tab title="SHA 256" %}
610220afa05689f5de25b21857b6cb01215c9b4f13366d72edae837c012522f8
{% endtab %}

{% tab title="Imphash" %}
B8B6216613B83B9374DA0AC1163E6C23
{% endtab %}

{% tab title="Interesting Strings" %}
; \_\_io=\
; \_ga=\
%016IX\
; \_gid=\
; \_u=\
%s%u\
POST\
Cookie: \_s=\
Cookie: \_\_gads=\
; \_gat=\
ilekvoyn\[.]com
{% endtab %}

{% tab title="Imports" %}
![](/files/5L30RrKnCHCh16TJFxr5)
{% endtab %}

{% tab title="Exports" %}

<figure><img src="/files/LE5c5q5C9Xji6LJ60wq9" alt=""><figcaption></figcaption></figure>
{% endtab %}

{% tab title="Sections" %}

<figure><img src="/files/wSkW4XlRwca1fQU0S6uv" alt=""><figcaption></figcaption></figure>
{% endtab %}

{% tab title="Capa" %}

<figure><img src="/files/sWnvIvsENZuqq2LqNMdP" alt=""><figcaption></figcaption></figure>
{% endtab %}

{% tab title="Conclusion" %}

* 64 bit DLL
* HTTP APIs
* domain visible in decoded strings
* possibly allocation of memory
* possibly writing files to disk
  {% endtab %}
  {% endtabs %}

### Config extraction

As the objective of the challenge is to extract the configuration, I will prioritize this. I will then describe my findings regarding the malware's overall capabilities, as far it is possible for me.

Beginning at the entry point within the <kbd>.c</kbd> section, we notice a new thread is created, pointing to a specific function that will be immediately executed.

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

Diving deeper into this function, we notice an interesting function responsible for accessing and decrypting bytes, located in the <kbd>.d</kbd> section.

<figure><img src="/files/qSjNdXjSHHsIA2On8J80" alt=""><figcaption><p>decryption routine</p></figcaption></figure>

Basically, the first 32 bytes act as the key and as kind of marker, to access one encrypted byte during each iteration within the loop. The result is saved into a memory location, passed as the only parameter to this function. The offset of <kbd>0x40</kbd> is important for later calculation when the malware accesses the decrypted config, visible in the picture below.

<figure><img src="/files/rExtcNxkWhvyjNbSG3Y3" alt=""><figcaption><p>offset calculation</p></figcaption></figure>

The decrypted config contains a campaign ID and the C2 domain discovered during triage.

#### Python

{% code expandable="true" %}

```python
import pefile

def findSection(): 
    pe = pefile.PE("unpacked_icedID_rebased.bin") 
    for section in pe.sections: 
        if b'.d' == section.Name.rstrip(b'\x00'): 
            text_data = pe.__data__[section.PointerToRawData:section.PointerToRawData + 0x80] 
            return text_data

def extractKey(data):
    key = data[:0x20]
    return key

def decryption(key, encrypted):
    plain = bytearray()
    for i in range(0, 0x20):
        decrypted_byte = encrypted[i] ^ key[i]
        plain.append(decrypted_byte)
        i = i+1
    return plain

data = findSection()
key = extractKey(data)
encrypted = data[0x40:]
plaintext = decryption(key, encrypted)
print("Plaintext Bytes: ", plaintext)
campaignID = int.from_bytes(plaintext[0:4], 'little')
domain = plaintext.split(b'\x00')[0][4:].decode()
print("Campaign ID: ", campaignID)
print("C2 domain: ", domain)
```

{% endcode %}

#### Binary Refinery

{% code expandable="true" %}

```bash
emit.exe "unpacked_icedID_rebased.bin" | vsect.exe .d | put key cut:0x00:0x20 [| snip 0x20:0x60 | xor key | put decrypted | snip 0x00:0x04 | base -R -e 10 | put CampaignID | swap decrypted | xtp | put domain | peek -W32 ]
```

{% endcode %}

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

### Analysis

#### Gathering System Information

After the C2 config has been decrypted, the malware gathers various system information and prepares exfiltration of gathered information by filling various parameters.

<figure><img src="/files/tpVlbu6FLhIdAFBK83lV" alt=""><figcaption><p>gathering system information</p></figcaption></figure>

At <kbd><mark style="color:$primary;">0x1800028BB<mark style="color:$primary;"></kbd> for example, the malware fills in the first 4 bytes (interpreted as integer) of the decrypted C2 config into the first parameter. Additionally, information like running processes, adapter information, CPU manufacturer and computer and account information are collected and filled into predefined parameters. Notable here is that some information are being encoded before transmitting them.

<figure><img src="/files/K45ELf7afOkAWKan7yDx" alt=""><figcaption><p>gathered system info filled in predefined parameters</p></figcaption></figure>

#### Exfiltration

The filled parameters visible above are sent via HTTP GET request to the attacker. Being part of the header, this approach allows the attacker to extract the values and decode them.

<figure><img src="/files/ZFhlTCVMrmdXWLLVje4m" alt=""><figcaption><p>WinHttpConnect targeting C2 domain on port 80</p></figcaption></figure>

<figure><img src="/files/aQmuXZK02lmePRaTXCXF" alt=""><figcaption><p>Exfiltration via GET request</p></figcaption></figure>

#### C2 control

The response to the request performed to exfiltrate system information, is used to extract information regarding further execution.

<figure><img src="/files/aGySN05NG7LkmPjgpCQk" alt=""><figcaption><p>query of received header</p></figcaption></figure>

The malware is interested in the content length and the status code (see [Query Info Flags](https://gitlab.winehq.org/iamahuman/wine/-/blob/05687bf80d40080e800a247a5a91fb833de4d9e8/include/winhttp.h)). Both values are saved into buffers for later usage to determine further execution of the malware.

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

<figure><img src="/files/7AxnQ6zTP3NGYIXaLVSQ" alt=""><figcaption><p>check status code</p></figcaption></figure>

<figure><img src="/files/zSgg1nHFT1bgiZfBucCB" alt=""><figcaption><p>checks content length (0x102 = 258 in my case)</p></figcaption></figure>

Notable here is, that the malware checks for specific bytes in the received response to continue its execution. The bytes visible here, <kbd>0x1F8B</kbd>, are the [magic bytes of a GZIP header](https://en.wikipedia.org/wiki/List_of_file_signatures).

<figure><img src="/files/6yNkpN4hWoeVbw2Z4ffp" alt=""><figcaption><p>check for specific bytes</p></figcaption></figure>

If all checks are correct, the malware enters a function (at <kbd><mark style="color:$primary;">0x180001760<mark style="color:$primary;"></kbd>) to decompress the GZIP.

If the check fails, the malware sleeps and tries to contact the C2 server again.

#### Drop file and execution

The malware uses the response to write some of the content into a new file created under <kbd>c:\ProgramData\\</kbd> or <kbd>AppData</kbd> after calling [<kbd><mark style="color:$success;">SHGetFolderPathA<mark style="color:$success;"></kbd>](https://tarma.com/support/im9/using/symbols/functions/csidls.htm).

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

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

Within function <kbd><mark style="color:$primary;">0x180002B5C<mark style="color:$primary;"></kbd> the malware allocates a new memory region, writes content into this allocated region and loads several DLLs and APIs dynamically. <kbd><mark style="color:$success;">VirtualProtect<mark style="color:$success;"></kbd> is called within a loop multiple times before executing a function stored in <kbd>RAX</kbd> indirectly.

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

Due to the isolated analysis environment and the possibility that the C2 domain is offline, further execution cannot be observed. However, I asked the REMnux MCP Server about my hypothesis and received some interesting answers.

***

## REMnux MCP Server

Recently I read about the new [MCP Server on REMnux](https://zeltser.com/ai-malware-analysis-remnux) and decided to give it a try after my approaches to unpack, extract the config and analyze the malware.

The prompts and answers are listed below in the order I executed them.

{% hint style="info" %}
I used the MCP server from REMnux out of the box without any additional configuration or external AI agents. The goal was not to replace the manual analysis, but to see how AI can support my learning process and compare its results with my own findings. This experiment helped me to better understand where AI can be useful as a supporting tool during malware analysis.
{% endhint %}

{% stepper %}
{% step %}
Hi, \
I placed an unpacked malicious file with the name 'unpacked\_icedID\_rebased.bin' into /home/remnux/samples/. Through Triage and some static analysis I already know, that the .d section contains an encrypted C2 config that is accessed by the function located at 0x180002428. Can you help me to decrypt the config?

<figure><img src="/files/IjulmrmU7OIz3yjCponv" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}
Can you create a Python script to decrypt the stored config?

{% code expandable="true" %}

```python
#!/usr/bin/env python3
"""
IcedID Config Decryptor
Decrypts C2 configuration from the .d section of unpacked IcedID samples.
"""

import struct
import sys

def decrypt_icedid_config(binary_path):
    """Decrypt IcedID config from .d section."""
    with open(binary_path, 'rb') as f:
        data = f.read()
    
    if len(data) < 0x8080:
        print(f"Error: File too small, expected at least 0x8080 bytes")
        return None
    
    encrypted = data[0x8000:0x8020]
    key = data[0x8040:0x8060]
    
    decrypted = bytes(a ^ b for a, b in zip(encrypted, key))
    
    return decrypted

def parse_config(decrypted):
    """Parse the decrypted config structure."""
    config = {}
    
    config['header'] = decrypted[0:4].hex()
    
    null_pos = decrypted[4:].find(b'\x00')
    if null_pos != -1:
        config['domain'] = decrypted[4:4+null_pos].decode('ascii')
        remaining = decrypted[4+null_pos+1:]
    else:
        config['domain'] = None
        remaining = decrypted[4:]
    
    config['remaining'] = remaining.hex() if remaining else ''
    
    return config

def main():
    if len(sys.argv) < 2:
        print(f"Usage: {sys.argv[0]} <binary_file>")
        sys.exit(1)
    
    binary_path = sys.argv[1]
    
    print(f"[*] Reading: {binary_path}")
    
    try:
        decrypted = decrypt_icedid_config(binary_path)
    except Exception as e:
        print(f"Error decrypting: {e}")
        sys.exit(1)
    
    print(f"[*] Decrypted bytes: {decrypted.hex()}")
    print()
    
    config = parse_config(decrypted)
    
    print("=" * 50)
    print("IcedID Config")
    print("=" * 50)
    print(f"Header (Campaign ID): {config['header']}")
    print(f"C2 Domain:             {config['domain']}")
    print(f"Additional Data:     {config['remaining']}")
    print("=" * 50)

if __name__ == "__main__":
    main()
```

{% endcode %}
{% endstep %}

{% step %}
Okay thanks. It works perfectly.\
Can you analyze the malware roughly with static methods to determine key capabilities of the provided sample?

<figure><img src="/files/nI1FONHM3pPXS5RMmuNn" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}
Can you determine which files are written and what the target directories are? Where do the files come from and will they be executed?

<figure><img src="/files/srPDaqHu1jBUXJrsK3hZ" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}
In function 0x180001E64 I see multiple DLLs loaded via calls to LoadLibrary and multiple APIs are resolved via calls to GetProcAddress. Can you determine which DLLs will be loaded? Could it be, that the decompressed GZIP file downloaded contains some DLLs that were written to disk previously? Feel free to analyze whatever you need.

<figure><img src="/files/5hDHELnihBZDe7aygnTy" alt=""><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}


---

# Agent Instructions: Querying This Documentation

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

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

```
GET https://txc.gitbook.io/documentation/writeups/icedid-config-extraction.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.
