{
	"id": "8aa20d01-931b-4e1b-b426-3415439a7d56",
	"created_at": "2026-04-06T00:19:51.091479Z",
	"updated_at": "2026-04-10T03:22:50.346455Z",
	"deleted_at": null,
	"sha1_hash": "f9dce315981e793f0cc5a4d00674304a2d8a20e4",
	"title": "Hunting IcedID and unpacking automation with Qiling",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1736675,
	"plain_text": "Hunting IcedID and unpacking automation with Qiling\r\nBy Quentin Fois, Pavankumar Chaudhari\r\nPublished: 2021-07-26 · Archived: 2026-04-05 23:39:44 UTC\r\nIn our previous blog post “Detecting IcedID” we provided a global overview of the IcedID threat, its multiple\r\nstages and capabilities.\r\nThis new blog post is focused on how to be proactive and hunt for IcedID DLL components to extract network\r\nIOCs. It will involve a combination of Yara rules, the Qiling framework, and Python scripting.\r\nBeing a highly active threat, IcedID updates its packing technique regularly. This article focuses on what has been\r\nobserved during the April – May 2021 timeframe. While the Yara introduced in this blog post may not be up to\r\ndate for the latest samples at the time of publication, the overall hunting pipeline stays valid and can easily be\r\ntuned to tackle the latest threats.\r\nThis article is articulated in 4 parts:\r\nBuilding a Yara rule to find packed DLL samples\r\nOverview of the collected samples\r\nAutomatic unpacking with Qiling\r\nIOC extraction\r\nHunting for packed samples with Yara\r\nFigure 1 IcedID infection chain\r\nFigure 1 above shows a diagram of the typical IcedID infection chain. The two DLLs mentioned above, 1st stage\r\nand 2nd stage loaders, are packed and obfuscated using the same packer. Thanks to VMware technology, detection\r\nhttps://blogs.vmware.com/security/2021/07/hunting-icedid-and-unpacking-automation-with-qiling.html\r\nPage 1 of 11\n\nrules can be written on any layer of a packed sample. Meaning that we can write a detection rule based on the\r\nunpacked version of the DLL making our detection resilient to packer alteration.\r\nHowever, for hunting purposes, being capable of detecting the packed layer of a sample is essential. This allows\r\npractitioners to find samples in an environment where only a static approach is available.\r\nWriting a Yara rule is the standard way to detect samples in such an environment, and our research makes no\r\nexception. Designing a Yara rule is always a trade-off. One needs to find the fine balance between:\r\nHaving a Yara so specific that it will only trigger on a couple of samples.\r\nHaving a Yara so generic that it will trigger on unrelated samples, maybe even benign ones.\r\nFor this research, we focused on finding patterns in the code generated by the packer.\r\nBelow (Figure 2) is a screenshot of the disassembled code from a packed 1st stage DLL.\r\nFigure 2 Screenshot of disassembled packed code\r\nSquared in red is a condition pattern that can be found in many places over the packed code, and as such may be a\r\ngood candidate for a Yara rule. Let’s have a closer look at the conditions.\r\nFigure 3 Decompiled condition\r\nAbstracting the variable used in the operation by a generic byte called x, we get:\r\nFigure 4 Generic condition\r\nhttps://blogs.vmware.com/security/2021/07/hunting-icedid-and-unpacking-automation-with-qiling.html\r\nPage 2 of 11\n\nSimplifying the equation by stating Y = (x * (x-1)) gives us:\r\nFigure 5 Simplified condition\r\nOne interpretation of this condition is checking if Y is odd or even.\r\nThe trick here is that Y = (x * (x-1)) always returns an even number because:\r\neither x or (x-1) is even.\r\nMultiplying any number with an even number gives an even number.\r\nThus Y \u0026 1 always return 0. Summing up:\r\nFigure 6 condition truth table\r\nThese conditions are what is called an opaque predicate. Opaque predicates are fake conditions that always\r\nresolve to the same outcome whatever value is used. Their only purpose is to artificially complexify the execution\r\nflow.\r\nFrom our experience, these opaque predicate constructions seemed specific to this packer and would make a good\r\ncandidate for a Yara rule.\r\nSince Yara rules operate on the byte level, the next step is to find a proper byte pattern generalizing the\r\ninstructions making this condition.\r\nLooking at the assembly level, this is what the instructions look like:\r\nFigure 7 Highlighted in green are the common bytes between the instruction opcodes\r\nBy abstracting the part of the opcodes corresponding to a register, it can be converted into a seemingly generic\r\nYara rule:\r\nhttps://blogs.vmware.com/security/2021/07/hunting-icedid-and-unpacking-automation-with-qiling.html\r\nPage 3 of 11\n\nFigure 8 IcedID packer opaque predicate Yara rule\r\nRunning this rule on different datasets returned great results and provided us with many packed samples and very\r\nlimited false positives. A curated and enriched IoC CSV, as well as the Yara rule is available on our Github\r\nrepository.\r\nPacked samples observations\r\nThis section highlights a few observations made on the bulk of samples collected via the Yara rule described\r\nabove. Hashes can be found in the annexes section and on Github.\r\nMost packed DLLs contain a valid timestamp both in the file header and in the debug directory. Timestamp\r\ndoes not appear to be edited and could be used for timeline building purpose.\r\nFigure 9 Timestamps found in the File Header and Debug directory\r\nTwo subsystems were observed:\r\nhttps://blogs.vmware.com/security/2021/07/hunting-icedid-and-unpacking-automation-with-qiling.html\r\nPage 4 of 11\n\nIMAGE_SUBSYSTEM_WINDOWS_GUI: Commonly used by GUI application.\r\nIMAGE_SUBSYSTEM_NATIVE: This subsystem is used by drivers. However, in this case it is\r\njust here to confuse analysis systems as the DLL is invoked using rundll32 as a regular user space\r\nDLL.\r\nFigure 10 Packed DLL exposed different subsystems\r\nAll samples analyzed have an exported function named either “PluginInit” or “update”. Usually\r\n“PluginInit” is used for 1st stage DLL, and “update” for 2nd stage DLL. Sometimes both are exported at the\r\nsame time, and they both end up unpacking the sample the same way.\r\nhttps://blogs.vmware.com/security/2021/07/hunting-icedid-and-unpacking-automation-with-qiling.html\r\nPage 5 of 11\n\nFigure 11 Export function names highlighted from 3 samples\r\nSome DLLs were identical except for 4 bytes in the overlay section at the very end of the binary. This data\r\ndid not appear to be used at all during the unpacking routine and thus suggest that is only a trick to change\r\nthe file hash. Not sure if samples are distributed as such or if another external actor just does so to mix up\r\nleads.\r\nhttps://blogs.vmware.com/security/2021/07/hunting-icedid-and-unpacking-automation-with-qiling.html\r\nPage 6 of 11\n\nFigure 12 Two samples only differing by 4 bytes in the overlay part of the binary\r\nOne sample was signed with a valid certificate chain.\r\nFigure 13 Signed sample\r\nOverall, all these DLL samples are a mix of 1st stage and 2nd stage DLLs. While we already gathered interesting\r\ninformation just by looking at the available metadata, 1st stage DLLs contain something even more valuable: the\r\ndomain name contacted to download the 2nd stage DLL.\r\nHowever, to extract it one would first have to unpack the DLL and then decrypt the configuration blob.\r\nAutomatic unpacking with Qiling\r\nAs shown in the Yara creation section, the outer packed layer of the 1st stage DLL is full of opaque predicates and\r\nother obfuscation artifacts. While one could reverse the packer code and come up with a 100% static approach to\r\nunpack the inner layer of the DLL, this would take a non-negligeable amount of time and it would not be resilient\r\nto small packer change.\r\nPrevious manual experimentation showed that we were able to extract a clean version of the unpacked sample by\r\nusing a debugger and placing a breakpoint on CreateThread. When the executed DLL triggers the breakpoint, we\r\ncould then use PeSieve tool to extract the unpacked PE from memory.\r\nHowever, this required too much manual interaction and did not scale well, so we found a middle ground by using\r\nQiling.\r\nQiling is a lightweight emulator framework, easy to instrument, with a ready-to-use PE loader and Python\r\nbindings. It has already been introduced through multiple talks and blogpost.\r\nRunning a packed sample through Qiling is as easy as a few lines of python:\r\nhttps://blogs.vmware.com/security/2021/07/hunting-icedid-and-unpacking-automation-with-qiling.html\r\nPage 7 of 11\n\nFigure 14 Executing IcedID 1st stage DLL via Qiling\r\nQiling output is rather verbose by default. One can see multiple calls to interesting windows API functions that\r\ncan be used to have a better understanding of the execution flow and how the sample unpacks itself.\r\nThe output can be split into 4 parts:\r\n1). Loader initialization\r\nFigure 15 Qiling sample initialization logs\r\nThis is completely handled by Qiling framework. It loads the binary in a way Windows loader would do, creating\r\nPEB, TEB, then loading the IAT.\r\n2). Unpacking routine\r\nFigure 16 Unpacking routine logs\r\nHere Qiling is instructed to start executing code from the address 0x180007FDF which was identified earlier as\r\nthe address of the exported function ”PluginInit”. One can see the packer layer starting its unpacking job by\r\nallocating multiple memory region that will ultimately contain the unpacked binary.\r\n3). Reflective loading\r\nhttps://blogs.vmware.com/security/2021/07/hunting-icedid-and-unpacking-automation-with-qiling.html\r\nPage 8 of 11\n\nFigure 17 Unpacked sample IAT parsing logs\r\nAfter completing the unpacking logic, the sample starts loading the unpacked DLL in memory. We can observe the\r\nIAT parsing in the logs, including the winhttp.dll portion that will handle the C2 communication.\r\n4). Unpacked sample execution\r\nFigure 18 Unpacked sample execution logs\r\nFinally, the unpacked DLL starts executing and one of the first APIs that is executed is CreateThread followed by\r\na Sleep loop.\r\nIt should be noted that the value of the lpStartAddress parameter is listed as 0x5000078bc (Figure 14) which is\r\ninside the range returned by the last VirtualAlloc called in the 2nd step (Figure 12).\r\nKnowing that, we can make the following assumptions:\r\nThe embedded PE is being unpacked in one of the memory regions allocated with VirtualAlloc\r\nThe sample is fully unpacked now that CreateThread is being called\r\nFrom there, one can make use of the hooking functionalities exposed by Qiling to design the following unpacking\r\nprocedure:\r\n1. Hook VirtualAlloc\r\n2. Hook CreateThread\r\n3. Save each allocated memory address in an array\r\n4. When we reach CreateThread, go through the allocated memory blob, and check if they contain a PE\r\nheader. If yes, dump the content to disk\r\nhttps://blogs.vmware.com/security/2021/07/hunting-icedid-and-unpacking-automation-with-qiling.html\r\nPage 9 of 11\n\nRunning this properly unpacks the binary.\r\nIcedID Stages and IOC extraction\r\nAs mentioned above, IcedID is a multi-stage threat. The same packer is used to pack both 1st stage and 2nd stage\r\nDLLs. However, only the 1st stage reaches out to a CnC subsequently containing a network domain IOC. For this\r\nreason, figuring out heuristics enabling one to identify what stage is being unpacked is important (see table)\r\nLayer Heuristic Description\r\nPacked DLL\r\nExport\r\nfunction\r\n1\r\nst\r\n stage DLL tend to have only the `PluginInit` function exported while\r\n2\r\nnd\r\n stage DLL usually have `update` function exported.\r\nUnpacked\r\nDLL\r\nImported\r\nfunctions The 1st\r\n stage DLL import functions from the winhttp.dll DLL\r\nUnpacked\r\nDLL\r\nFile size The 1st\r\n stage DLL is about twice the size of the 2nd stage DLL\r\nTable 1 DLL identification heuristics\r\nOut of these, we found the presence of the “winhttp” DLL import to be the easiest heuristic to implement.\r\nThe 1st stage DLL contains a small configuration blob, storing an ID and a domain name from which 2nd stage\r\nwill be downloaded. The “encryption” algorithm is straightforward: two data blobs of lengths 0x20 are xored\r\nbetween them.\r\nFigure 19 Configuration decryption routine\r\nAs far as we’ve observed, this xored data blob is always stored at the start of the .data section, making it trivial to\r\nlocate and write a few lines python script to extract and decode the domain name.\r\nEverything is stitched together in the Python script provided on our Github repository:\r\nSample unpacking\r\nStage identification\r\nNetwork IoC extraction\r\nConclusion\r\nThrough this article, we went through the different essential stages of sample hunting, from Yara designing to\r\nunpacking automation and IoC extraction. While the Yara provided may not generate hits anymore at the time of\r\nhttps://blogs.vmware.com/security/2021/07/hunting-icedid-and-unpacking-automation-with-qiling.html\r\nPage 10 of 11\n\nreading due to the fast pace of IcedID packer update, the whole thought process stays relevant.\r\nIOCs:\r\nAnnex 1: Figures SHA256\r\nFigure 2:\r\nF75EFD25362D8EED3C2B190E3BA178B2CBBCF7C7DA0B991F4FE1D85072E8DCC6\r\nFigure 5:\r\nD2511FDA63F3F4A1ED6A85765B0FBCDC4A965C933D77A2C1EB49D025B0E2609D\r\nFigure 6:\r\nA15AB7254721CD06C225E4FBBEC127DE2AC987D6AA508C7D4803ACA9430BF94B\r\n3FB98E45DFD23FCEF1D156330471B3E4D4F142FC3C2AB5D560C5D3E77D7C01EA\r\nFigure 7:\r\n3FB98E45DFD23FCEF1D156330471B3E4D4F142FC3C2AB5D560C5D3E77D7C01EA\r\nBE27D0ABD5EC6AC7CCA28716776B2A4F429B25A12D5AC57942801B97CEC45081\r\n647E7210AF220356632DF3EF8421242DBEDBD2676EABB30846E984DDBA040619\r\nFigure 8:\r\n2D96A3F885A90F08E75FFB0877033B3A3803B128DEF666E55227D8DB3E2867C3\r\n24B4D4DA790B032E6C0017132586D587F49031DAA708A52CB2B68341594557C2\r\nFigure 9:\r\n908381E81C59EC12908BA7929D38FAF542297E550DB628E330F3652CD6C99F91\r\nAnnex 2: Hunted IOCs\r\nAvailable on https://github.com/Lastline-Inc/iocs-tools\r\nSource: https://blogs.vmware.com/security/2021/07/hunting-icedid-and-unpacking-automation-with-qiling.html\r\nhttps://blogs.vmware.com/security/2021/07/hunting-icedid-and-unpacking-automation-with-qiling.html\r\nPage 11 of 11",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://blogs.vmware.com/security/2021/07/hunting-icedid-and-unpacking-automation-with-qiling.html"
	],
	"report_names": [
		"hunting-icedid-and-unpacking-automation-with-qiling.html"
	],
	"threat_actors": [
		{
			"id": "b740943a-da51-4133-855b-df29822531ea",
			"created_at": "2022-10-25T15:50:23.604126Z",
			"updated_at": "2026-04-10T02:00:05.259593Z",
			"deleted_at": null,
			"main_name": "Equation",
			"aliases": [
				"Equation"
			],
			"source_name": "MITRE:Equation",
			"tools": null,
			"source_id": "MITRE",
			"reports": null
		}
	],
	"ts_created_at": 1775434791,
	"ts_updated_at": 1775791370,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/f9dce315981e793f0cc5a4d00674304a2d8a20e4.pdf",
		"text": "https://archive.orkl.eu/f9dce315981e793f0cc5a4d00674304a2d8a20e4.txt",
		"img": "https://archive.orkl.eu/f9dce315981e793f0cc5a4d00674304a2d8a20e4.jpg"
	}
}