TheAbsnt

Challenge #2: IcedID Configuration Extraction

· THEABSNT

the agenda

Welcome back!.. and i hope you’re having a nice time :)

This is a quick write-up that will go through the process of unpacking the initial IcedID malware loader and extract, decrypt and display the configuration from the unpacked stage of this malware,

this is the second challenge from the course Zero2Automated as part of their bi-weekly challenges


the file triage

The sample we’re provided with is a PE64 DLL file with SHA256 hash 0581f0bf260a11a5662d58b99a82ec756c9365613833bce8f102ec1235a7d4f7.

With some initial triage, it seems packed, as

  • it has almost nothing to import,
  • and exports DLLRegisterServer with six another function HdQZgnE, IfkPmdu, cJPSzqHBMN, pcufUY, rHqnYSA, zlmkoZLQMd
  • strings output is flooded with random bytes hinting packed state and interestingly literal hex values
  • also you may notice that being a dll it has no entry point set

assumption: now that there happens to be no entry point set then the dll may be executed by it’s exported function, and we’ll do the same using the windows system utility run32dll.exe

Usage: run32dll [dll_name], [export_name or #ordinal_number]

then run within a debugger and make sure to enable Events->break on->User DLL load from Options->Preferences of x64dbg

how to unpack though

stage-01: the loader

starts off by calling sub_180005A13 a bunch of time to dynamically resolve the needed functions via PEB, then continue if current year is 2022 or terminate the process otherwise.

Within x64dbg run until VirtualAlloc followed by VirtualProtect is hit and checking the memory region reveals another MZ header ie. a executable file, dump that to disk (using OllyDumpEx in X64dbg) xdbg-snap.png once the PE is dumped successfully, we can move on with config extraction.

hunting the config

stage-02: the dumped PE

fn-dllEntryPoint-stage02

This DLL starts off by immediately making a call to CreateThread where lpStartAddress points to StartAddress ie.sub_180002AA4 and is executed, which calls another subroutine sub_180002174

fn-sub_180002174-stage02

this subroutine at first creates a number from CPU tick counts, then calls another subroutine sub_180002428 that will decrypt the .d section by XORing first 0x20 bytes against bytes 0x40 ahead resp. (see the config extraction script for more), section-d-stage02

fn-sub_180002428-stage02 once decrypted we see a config url ilekvoyn[.]com

C:\Users\theabsnt\Desktop>python extract-config.py stage-2-iced-id.dll.vir
Decrypted .b sections:
b'\xd9o\x8d\x06ilekvoyn.com\x00\xafO\xc2y\xd2N\xb6!\xfd\xbd@}u\xd4\x17'

Campaign URL: ilekvoyn.com

after decryption it moves on to gather various system related information by calling sub_180002860 fn-sub_180002860-stage02 which forms a string with various system info, that will eventually be sent back to C2 url after establishing the connection down the line.

That’s it for now, hope you got some value off of it! I’ll see you around :)


config extraction script

# filename : extract-config.py
# author : theabsnt :)
# usage:  extract-config.py [unmapped_or_dumped_stage-02_icedid]

import pefile
import sys, struct


def decrypt_config(config_data):
    decrypted_blob = b""
    for i in range(0x20):
        decrypted_blob += bytes([config_data[i] ^ config_data[i+0x40]])
    
    return decrypted_blob
    
def extract_config(decrypted_config):
    url = decrypted_config[4:].split(b"\x00")[0].decode()
    return url

def main():
    pe = pefile.PE(sys.argv[1])
    
    config_data = None
    for section in pe.sections:
        if b'.d\x00' in section.Name:
            config_data = section.get_data()
            
    if config_data != None:
        decrypted_blob = decrypt_config(config_data)
        print(f"Decrypted .b sections:\n{decrypted_blob}")
        print(f"\nCampaign URL: {extract_config(decrypted_blob)}")


if __name__ == "__main__":
    main()