Challenge #2: IcedID Configuration Extraction
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 functionHdQZgnE
,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
)
once the PE is dumped successfully, we can move on with config extraction.
hunting the config
stage-02: the dumped PE
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
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),
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
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()