{
	"id": "22738915-7f1a-464e-a06a-2bf2b1fcb940",
	"created_at": "2026-04-06T00:06:16.452455Z",
	"updated_at": "2026-04-10T03:22:10.202237Z",
	"deleted_at": null,
	"sha1_hash": "a0d84ac4a27f3d24d452465888b3fe10be4fac0f",
	"title": "Analyzing .NET Core Single File Samples (DUCKTAIL Case Study)",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 472908,
	"plain_text": "Analyzing .NET Core Single File Samples (DUCKTAIL Case Study)\r\nPublished: 2022-08-07 · Archived: 2026-04-05 22:42:00 UTC\r\nThis post is dedicated to my colleague Matt Graeber (@mattifestation) who showed me how to do the manual calculations\r\nand carving of PEs using CFF Explorer and a hex editor, making me think “there has to be a tool for this”. There are loads of\r\nways to deploy .NET Framework applications, and I’ve mostly been familiar with just the traditional compile-and-run\r\nmethod. As .NET malware has evolved, adversaries have looked to use different deployment reasons for the same reason as\r\nlegitimate developers. In some cases it’s easier, allows bundling files in different ways, or other reasons. One malware\r\nfamily named DUCKTAIL has recently embraced deployment in as a “.NET Single File” deployment feature. This feature is\r\ninteresting in the sense that it allows a developer to deploy a .NET application to systems without requiring the systems to\r\nhave a pre-deployed .NET Framework runtime installed. This dependency-free deployment is achieved by appending\r\nmultiple binaries together into a single file, resulting in a large executable with multiple executables inside. In this post we’ll\r\ntake a look at a DUCKTAIL sample and how the “single file” deployment choice affects malware analysis. The sample\r\nwe’re working with is here in VT:\r\nhttps://www.virustotal.com/gui/file/740fd780b2b45c08d1abb45cddc6d1017c9fcc6bcce54fd8415d87a80d328ff6.\r\nTriaging the file\r\nAs always, we can start out triaging the file using a combination of diec and pehash . First, let’s figure out the file type:\r\n1\r\n2\r\n3\r\n4\r\nremnux@remnux:~/cases/ducktail$ diec ducktail.exe\r\nPE32\r\n Compiler: Microsoft Visual C/C++(-)[-]\r\n Linker: Microsoft Linker(14.29**)[GUI32,signed]\r\nRight off the bat, we can see our output is a little different than what we’d expect for a standard .NET Framework app. In\r\nthis case we see diec says the file was compiled using Visual C/C++ instead of a .NET language like C# or VB. Usually\r\nfor .NET apps, the output would look similar to this:\r\n1\r\n2\r\n3\r\n4\r\nPE32\r\n Library: .NET(v4.0.30319)[-]\r\n Compiler: VB.NET(-)[-]\r\n Linker: Microsoft Linker(8.0)[GUI32]\r\nWe know for sure we’re dealing with a Windows Portable Executable (PE) file, so let’s take a look at the import and rich\r\nheader hashes. If you’re looking to get the imphash for a sample, you can easily do so with pehash . For the rich header\r\nhash, I like using the tool I made here that leverages the Python pefile library.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\nremnux@remnux:~/cases/ducktail$ pehash ducktail.exe\r\nfile\r\n filepath: ducktail.exe\r\n md5: 72840061e0f1b2f4bc373b5561970303\r\n sha1: c773a6285a54183e792f23e499646f61d9b2f88f\r\n sha256: 740fd780b2b45c08d1abb45cddc6d1017c9fcc6bcce54fd8415d87a80d328ff6\r\n ssdeep: 1572864:ha0XsmjPyZmtmuwKl7E2LmZBhbCV6ZE5GSQiOjEBkqYnIgJM0cAZv7SGdAcA689p:jjPyZxuwz+6y\r\n imphash: 34dc34e244a6f4378a06076ff16fc082\r\nremnux@remnux:~/cases/ducktail$ ./rhh-md5.py ducktail.exe\r\ne0f1735adef0e9f084efeaee57b351d2\r\nhttps://forensicitguy.github.io/analyzing-net-core-single-file-ducktail/\r\nPage 1 of 5\n\nThe imphash and rich header hash values are different that what I expect of traditional .NET malware. Usually, .NET\r\nexecutables have an import hash of f34d5f2d4577ed6d9ceec516c1f5a744 and .NET DLLs have an import hash of\r\ndae02f32a21e03ce65412f6e56942daa . In addition, .NET executables and DLLs usually don’t have rich header hashes. So\r\nthis sample triages more like a native, unmanaged code binary than a .NET one.\r\nDigging deeper with exiftool and pedump\r\nWe can get our first hints of the app being compiled as a .NET Core single file executable using exiftool and pedump .\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\n25\r\n26\r\n27\r\n28\r\n29\r\n30\r\n31\r\n32\r\n33\r\n34\r\n35\r\n36\r\n37\r\n38\r\n39\r\n40\r\n41\r\n42\r\n43\r\nremnux@remnux:~/cases/ducktail$ exiftool ducktail.exe\r\nExifTool Version Number : 12.42\r\nFile Name : ducktail.exe\r\nDirectory : .\r\nFile Size : 56 MB\r\nFile Modification Date/Time : 2022:08:07 21:14:35-04:00\r\nFile Access Date/Time : 2022:08:07 21:15:11-04:00\r\nFile Inode Change Date/Time : 2022:08:07 21:15:04-04:00\r\nFile Permissions : -rw-rw-r--\r\nFile Type : Win32 EXE\r\nFile Type Extension : exe\r\nMIME Type : application/octet-stream\r\nMachine Type : Intel 386 or later, and compatibles\r\nTime Stamp : 2022:04:13 21:36:43-04:00\r\nImage File Characteristics : Executable, 32-bit\r\nPE Type : PE32\r\nLinker Version : 14.29\r\nCode Size : 233984\r\nInitialized Data Size : 331776\r\nUninitialized Data Size : 0\r\nEntry Point : 0x2f8f0\r\nOS Version : 6.0\r\nImage Version : 0.0\r\nSubsystem Version : 6.0\r\nSubsystem : Windows GUI\r\nFile Version Number : 1.0.0.0\r\nProduct Version Number : 1.0.0.0\r\nFile Flags Mask : 0x003f\r\nFile Flags : (none)\r\nFile OS : Win32\r\nObject File Type : Executable application\r\nFile Subtype : 0\r\nLanguage Code : Neutral\r\nCharacter Set : Unicode\r\nCompany Name : DataExtractor\r\nFile Description : DataExtractor\r\nFile Version : 1.0.0.0\r\nInternal Name : DataExtractor.dll\r\nLegal Copyright :\r\nOriginal File Name : DataExtractor.dll\r\nProduct Name : DataExtractor\r\nProduct Version : 1.0.0\r\nAssembly Version : 1.0.0.0\r\nThe file size is 56 MB. It’s not the beefiest binary ever, but it’s still pretty heavy and that can indicate multiple binaries in a\r\nsingle file. From here we can look at binary properties with pedump to get some more data. In the interest of brevity I’ve\r\ncut down the pedump output to just the exports since it contains the relevant bits.\r\n1\r\n2\r\n3\r\n4\r\nremnux@remnux:~/cases/ducktail$ pedump --exports ducktail.exe\r\n=== EXPORTS ===\r\nhttps://forensicitguy.github.io/analyzing-net-core-single-file-ducktail/\r\nPage 2 of 5\n\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\n25\r\n26\r\n27\r\n28\r\n29\r\n30\r\n31\r\n32\r\n# module \"singlefilehost.exe\"\r\n# flags=0x0 ts=\"2106-02-07 06:28:15\" version=0.0 ord_base=1\r\n# nFuncs=23 nNames=23\r\n ORD ENTRY_VA NAME\r\n 1 21580 corehost_initialize\r\n 2 20ad0 corehost_load\r\n 3 20f20 corehost_main\r\n 4 21090 corehost_main_with_output_buffer\r\n 5 21960 corehost_resolve_component_dependencies\r\n 6 eba0 corehost_set_error_writer\r\n 7 218e0 corehost_unload\r\n 8 f260 hostfxr_close\r\n 9 e8d0 hostfxr_get_available_sdks\r\n a eaa0 hostfxr_get_native_search_directories\r\n b ef80 hostfxr_get_runtime_delegate\r\n c f190 hostfxr_get_runtime_properties\r\n d efd0 hostfxr_get_runtime_property_value\r\n e ed40 hostfxr_initialize_for_dotnet_command_line\r\n f ee70 hostfxr_initialize_for_runtime_config\r\n 10 e520 hostfxr_main\r\n 11 e3e0 hostfxr_main_bundle_startupinfo\r\n 12 e490 hostfxr_main_startupinfo\r\n 13 e5c0 hostfxr_resolve_sdk\r\n 14 e720 hostfxr_resolve_sdk2\r\n 15 ef10 hostfxr_run_app\r\n 16 eba0 hostfxr_set_error_writer\r\n 17 f130 hostfxr_set_runtime_property_value\r\n.NET core “single file” apps are multiple binaries appended to one another, right? Well, the first binary in the append chain\r\nhas the responsibility of being a “.NET loader” that loads subsequent .NET resources (appended after the loader) into\r\nmemory at runtime. Once the resources get loaded, the actual .NET app gets run. The export details seen here in pedump\r\nare from the .NET loader overhead itself, which results in some good predictability. The .NET core “single file” apps should\r\nusually have exports like corehost_initialize , corehost_load , and others.\r\nGetting the actual app/malware\r\nWe’ve got our bearings a bit and we know from documentation that a “single file” app is just a bunch of binaries appended\r\nto each other. So, logically, we should be able to walk the file and extract all the PEs from it. We can do this using\r\npecheck.py .\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\nremnux@remnux:~/cases/ducktail$ pecheck -l P ducktail.exe\r\n1: 0x00000000 EXE 32-bit 0x0350a17f 72840061e0f1b2f4bc373b5561970303 0x0350a17f (EOF) b'' b'singlefilehost.exe'\r\n2: 0x0008a71c DLL 64-bit 0x0013e6cb d35f8c57d217a41dfc5e68bf25e5ecb1 0x0350a17f (EOF) b'empty' b'clrcompression.dll'\r\n3: 0x0013e6cc DLL 64-bit 0x0024687b e127d23181160e02391e628192b1d08a 0x0350a17f (EOF) b'clrjit.dll' b'clrjit.dll'\r\n4: 0x0024687c DLL 64-bit 0x00652623 99004b84b758edc90f90671221152667 0x0350a17f (EOF) b'CoreCLR.dll' b'coreclr.dll'\r\n5: 0x00652624 DLL 64-bit 0x007443c3 ea613da6eeb3f2968faa2d65dabadab1 0x0350a17f (EOF) b'mscordaccore.dll' b'mscordac.dll'\r\n6: 0x007443c4 DLL 32-bit 0x008655c3 e02613d1a6211eb1bfc8d15431acbd68 0x0350a17f (EOF) b'' b'e_sqlite3.dll'\r\n...\r\n24: 0x0087fbd0 DLL 32-bit 0x010aed77 d3cfe3422fb4d5a93c1cf9807debd230 0x0350a17f (EOF) b'' b'System.Private.CoreLib.dll'\r\n25: 0x010aed80 DLL 32-bit 0x0111d57f 4ef7d9040e94a8c3a9ede74a8f66a73f 0x0350a17f (EOF) b'' b'Dapper.dll'\r\n26: 0x0111d580 DLL 32-bit 0x0117b77f a660b3d199853c0b014812f39e46eaa8 0x0350a17f (EOF) b'' b'HtmlAgilityPack.dll'\r\n27: 0x0117b780 DLL 32-bit 0x011ce97f 2904b6192503177cf287f6ae23ed65d5 0x0350a17f (EOF) b'' b'Microsoft.Data.Sqlite.dll'\r\n28: 0x011ce980 DLL 32-bit 0x0135e57f 47d413a62176af3f801b9f6a1146e1a7 0x0350a17f (EOF) b'' b'Newtonsoft.Json.dll'\r\n29: 0x0135e580 DLL 32-bit 0x019a2f7f 6697ec4f0f13bed443f3b070cc4192df 0x0350a17f (EOF) b'' b'BouncyCastle.Crypto.dll'\r\n30: 0x019a2f80 DLL 32-bit 0x019a4b7f 9b59e64ef76c1a543983b8dcb1ce8d75 0x0350a17f (EOF) b'' b'SQLitePCLRaw.batteries_v2.dll'\r\n31: 0x019a4b80 DLL 32-bit 0x019a637f 8b477db107c8ac8c219d90d94d93aaa4 0x0350a17f (EOF) b'' b'SQLitePCLRaw.nativelibrary.dll\r\n32: 0x019a6380 DLL 32-bit 0x019ba57f 5bacb4c47e3ba56dd53cf88781bb4e05 0x0350a17f (EOF) b'' b'SQLitePCLRaw.core.dll'\r\n33: 0x019ba580 DLL 32-bit 0x019d077f 7a9ca8439b58afd87f4faec21968c087 0x0350a17f (EOF) b'' b'SQLitePCLRaw.provider.dynamic_\r\n34: 0x019d0780 DLL 32-bit 0x019d837f 5b015246ff6883063438c8ecf4af101e 0x0350a17f (EOF) b'' b'System.Security.Cryptography.P\r\n35: 0x019d8380 DLL 32-bit 0x01a3417f ed5bdc648cba3d82edd0b14bed18b931 0x0350a17f (EOF) b'' b'Telegram.Bot.dll'\r\n36: 0x01a34180 DLL 32-bit 0x01aa217f 6a62b196160d1a477effa8e07ae48533 0x0350a17f (EOF) b'' b'DataExtractor.dll'\r\nhttps://forensicitguy.github.io/analyzing-net-core-single-file-ducktail/\r\nPage 3 of 5\n\n24\r\n25\r\n26\r\n27\r\n28\r\n29\r\n30\r\n37: 0x01aa2180 DLL 32-bit 0x01b7bb7f 0b360b2e48ad740b2045c96c228d8dfa 0x0350a17f (EOF) b'' b'Microsoft.CSharp.dll'\r\n...\r\n92: 0x03459580 DLL 32-bit 0x034c917f 6d306c25b62c2422a8411315307f5bf5 0x0350a17f (EOF) b'' b'System.Text.RegularExpressions\r\n93: 0x034c9180 DLL 32-bit 0x034e0b7f 21fef48538579c3d2533532c4b143e75 0x0350a17f (EOF) b'' b'System.Threading.Channels.dll'\r\n94: 0x034e0b80 DLL 32-bit 0x034f037f e58c38c4e4bfc5151c0f1ff350bfe6b7 0x0350a17f (EOF) b'' b'System.Threading.dll'\r\nUsing pecheck with arguments to list the available PEs in the file reveals a whopping 94 different PE files within the\r\nsingle original ducktail.exe sample. Thankfully, there are only a couple of PEs here that are interesting to us:\r\n35/Telegram.Bot.dll and 36/DataExtractor.dll. We can extract those with pecheck as well!\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\nremnux@remnux:~/cases/ducktail$ pecheck -l 35 -g s -D ducktail.exe \u003e Telegram.Bot.dll\r\nremnux@remnux:~/cases/ducktail$ diec Telegram.Bot.dll\r\nPE32\r\n Library: .NET(v4.0.30319)[-]\r\nremnux@remnux:~/cases/ducktail$ pecheck -l 36 -g s -D ducktail.exe \u003e DataExtractor.dll\r\nremnux@remnux:~/cases/ducktail$ diec DataExtractor.dll\r\nPE32\r\n Library: .NET(v4.0.30319)[-]\r\nExcellent, we’ve successfully extracted the actual DUCKTAIL .NET code from its “single file” container!\r\nDecompilation and further steps\r\nDecompiling is a breeze with this sample thanks to ilspycmd . Lately I’ve been using it with command line arguments to\r\nexport code as a .NET project so I can get extra details in there like the icon used by the malware.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\nremnux@remnux:~/cases/ducktail$ mkdir ducktail-src\r\nremnux@remnux:~/cases/ducktail$ ilspycmd -p -o ./ducktail-src/ DataExtractor.dll\r\nremnux@remnux:~/cases/ducktail$ mkdir telegrambot-src\r\nremnux@remnux:~/cases/ducktail$ ilspycmd -p -o ./telegrambot-src/ Telegram.Bot.dll\r\nremnux@remnux:~/cases/ducktail$ tree -a ducktail-src/\r\nducktail-src/\r\n├── app.ico\r\n├── cnData\\Core\\Models\\Json\r\n│ └── DataJsonModel.cs\r\n├── CokiWin\\Core\\Models\\Json\\BusinessJsonModel\r\n│ ├── Adaccount_Permissions.cs\r\n│ ├── BusinessJsonModel.cs\r\n│ ├── Clients.cs\r\n│ ├── Cursors1.cs\r\n│ ├── Cursors.cs\r\n│ ├── Datum1.cs\r\n│ ├── Datum.cs\r\n│ ├── Paging1.cs\r\n│ └── Paging.cs\r\n├── DataExtractor\r\n│ └── Program.cs\r\n...\r\nFrom here, if you want to get into deeper analysis you can start with the Program.cs file and simply follow the flow of\r\ncode to other files as relevant!\r\nhttps://forensicitguy.github.io/analyzing-net-core-single-file-ducktail/\r\nPage 4 of 5\n\nSource: https://forensicitguy.github.io/analyzing-net-core-single-file-ducktail/\r\nhttps://forensicitguy.github.io/analyzing-net-core-single-file-ducktail/\r\nPage 5 of 5",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://forensicitguy.github.io/analyzing-net-core-single-file-ducktail/"
	],
	"report_names": [
		"analyzing-net-core-single-file-ducktail"
	],
	"threat_actors": [],
	"ts_created_at": 1775433976,
	"ts_updated_at": 1775791330,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/a0d84ac4a27f3d24d452465888b3fe10be4fac0f.pdf",
		"text": "https://archive.orkl.eu/a0d84ac4a27f3d24d452465888b3fe10be4fac0f.txt",
		"img": "https://archive.orkl.eu/a0d84ac4a27f3d24d452465888b3fe10be4fac0f.jpg"
	}
}