{
	"id": "c16e7566-c7fa-4a4f-ae71-540bfb0e35f8",
	"created_at": "2026-04-06T03:37:59.071008Z",
	"updated_at": "2026-04-10T03:21:10.989375Z",
	"deleted_at": null,
	"sha1_hash": "b1d28999e3ff2d0edcbabbdc1730fa08ba17b15a",
	"title": "Evolving Tactics of SLOW#TEMPEST: A Deep Dive Into Advanced Malware Techniques",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1444799,
	"plain_text": "Evolving Tactics of SLOW#TEMPEST: A Deep Dive Into\r\nAdvanced Malware Techniques\r\nBy Mark Lim\r\nPublished: 2025-07-11 · Archived: 2026-04-06 02:51:09 UTC\r\nExecutive Summary\r\nIn late 2024, we discovered a malware variant related to the SLOW#TEMPEST campaign. In this research article,\r\nwe explore the obfuscation techniques employed by the malware authors. We deep dive into these malware\r\nsamples and highlight methods and code that can be used to detect and defeat the obfuscation techniques.\r\nUnderstanding these evolving tactics is essential for security practitioners to develop robust detection rules and\r\nstrengthen defenses against increasingly sophisticated threats.\r\nWe focus on the following techniques used by the threat actors for the SLOW#TEMPEST campaign:\r\nControl flow graph (CFG) obfuscation using dynamic jumps\r\nObfuscated function calls\r\nPalo Alto Networks customers are better protected from the threats discussed in this article through the following\r\nproducts and services:\r\nAdvanced WildFire\r\nCortex XDR and XSIAM\r\nIf you think you might have been compromised or have an urgent matter, contact the Unit 42 Incident Response\r\nteam.\r\nRelated Unit 42 Topics Anti-analysis, DLL sideloading\r\nBackground\r\nIn this article, we analyze a more recent variant of the malware sample (SHA256 hash:\r\na05882750f7caac48a5b5ddf4a1392aa704e6e584699fe915c6766306dae72cc) from the SLOW#TEMPEST\r\ncampaign. The attackers distribute the malware as an ISO file, which is a common technique used to bundle\r\nmultiple files to potentially evade initial detection. This ISO file contains 11 files; two are malicious, and the\r\nremainder are benign.\r\nThe first malicious file is zlibwapi.dll, which we’ll refer to as loader DLL, decrypts and executes the embedded\r\npayload. The payload is not integrated into the loader DLL in a typical manner. Instead, it is appended to the end\r\nof another DLL named ipc_core.dll.\r\nhttps://unit42.paloaltonetworks.com/slow-tempest-malware-obfuscation/\r\nPage 1 of 15\n\nThe loader DLL is executed via DLL side-loading by a legitimate signed binary named DingTalk.exe. DLL side-loading is a technique where attackers use a legitimate program to load a malicious DLL file, causing the\r\nlegitimate program to execute the attacker's code. Separating the payload from the loader DLL complicates\r\ndetection, as the malicious code will only execute if both the loader and payload binaries are present.\r\nIn the following sections, we dive deeper into the anti-analysis techniques used by the malware authors to\r\nobfuscate the code in the loader DLL.\r\nControl Flow Graph (CFG) Obfuscation Using Dynamic Jumps\r\nCFG obfuscation alters the execution order of program instructions, making static and dynamic analysis more\r\ndifficult. This makes it harder to understand the program’s logic, identify malicious functionality and create\r\neffective signature-based detection.\r\nFor static analysis, traditional tools relying on linear sequences or predictable control flow become ineffective.\r\nDynamic analysis also becomes more challenging because misleading execution paths obscure actual malicious\r\noperations. CFG obfuscation breaks the mapping between the original source or compiled code and runtime\r\nexecution, making it significantly more difficult to create reliable detection rules.\r\nTo demonstrate this technique, we analyze the application of CFG obfuscation, specifically using dynamic jumps\r\nto the loader DLL's main function. Figure 1 illustrates the CFGs of this function, both with and without\r\nobfuscation. Once we remove the obfuscation, the continuous code flow becomes apparent, marked by two\r\ncolored lines:\r\nGreen for True branches\r\nRed for False branches\r\nhttps://unit42.paloaltonetworks.com/slow-tempest-malware-obfuscation/\r\nPage 2 of 15\n\nFigure 1. CFGs of the main function for the loader DLL with and without obfuscation.\r\nhttps://unit42.paloaltonetworks.com/slow-tempest-malware-obfuscation/\r\nPage 3 of 15\n\nThis function is extensive, comprising over 17,000 lines of assembly instructions. Given the size of the main\r\nfunction for the loader DLL, we turned to the Hex-Rays decompiler to speed up analysis. However, the Hex-Rays\r\ndecompiler was only able to generate 10 lines of pseudocode for the same main function, as shown in Figure 2.\r\nFigure 2. Pseudocode generated by Hex-Rays of the main function for the loader DLL.\r\nThe reason for the incomplete Hex-Rays decompiler output is because the sample used dynamic jump\r\ninstructions. Dynamic jumps are where target addresses in code are computed at runtime.\r\nUnlike direct jumps to fixed addresses, dynamic jumps make it impossible for the decompiler to determine the\r\nexecution flow without actually running the program. This lack of a clear, predetermined path severely hinders the\r\ndecompiler's ability to reconstruct the original high-level code, often leading to incomplete or inaccurate\r\ndecompilation results.\r\nFigure 3 shows one of the dynamic jumps using the JMP RAX instruction near the entry point of the main\r\nfunction of the loader DLL. The JMP instruction will cause the execution flow to be diverted to a different target\r\naddress.\r\nThe target address depends on factors like memory contents, register values and the results of conditional checks\r\nperformed during execution. In this case, it is computed at runtime by a sequence of preceding instructions and\r\nstored in the CPU register RAX.\r\nhttps://unit42.paloaltonetworks.com/slow-tempest-malware-obfuscation/\r\nPage 4 of 15\n\nFigure 3. One dynamic jump in the main function for the loader DLL.\r\nWe countered CFG obfuscation employing dynamic jumps by first identifying all instances of these jumps using\r\nthe IDAPython script shown in Figure 4.\r\nFigure 4. Code to locate dynamic jumps.\r\nUsing the above script, we identified 10 dynamic jumps in the main function of the loader DLL.\r\nThe dynamic jump target is determined by a preceding sequence of nine instructions, termed a “dispatcher,”\r\nbefore each JMP RAX instruction. These 10 dispatchers, found within the loader DLL's main function, share a\r\nsimilar structure. However, each dispatcher uses a distinct set of instructions to compute the jump's destination\r\naddress, effectively hiding the program's control flow.\r\nImagine the program is a complex dance routine. Normally, the dancers move predictably from one step to the\r\nnext. However, in this case, the program has hidden \"jump points.\" Before each jump, there's a mini-routine, like a\r\nsecret handshake, that decides exactly where the next jump will land. These secret handshakes are all a bit\r\nhttps://unit42.paloaltonetworks.com/slow-tempest-malware-obfuscation/\r\nPage 5 of 15\n\ndifferent, making it very hard to predict the dance's true path, almost like the dancers are improvising where they'll\r\ngo next, even though it's all pre-programmed.\r\nEach dispatcher implements a two-way branching mechanism. The code path taken depends on the state of the\r\nZero Flag (ZF) or the carry flag (CF) when the dispatcher is entered. These flags, which are set by previous\r\ninstructions to indicate the result of an operation (e.g., zero or overflow), determine which branch is taken. Each\r\ndispatcher has a pair of conditional move (CMOVNZ) or set (SETNL) instructions and an indirect jump (JMP\r\nRAX). This creates a dynamic control flow that depends on runtime conditions and memory contents, making\r\nstatic analysis difficult.\r\nZF and CF are CPU status flags that reflect arithmetic and logical operation outcomes. These flags act as internal\r\nswitches, enabling dynamic program execution based on prior computation results. Each conditional move or set\r\ninstruction has two possible target addresses: one for a true condition and the other for a false condition. For\r\nexample, the conditional move if not zero (CMOVNZ) instruction will only move data if the ZF is 0, indicating\r\nthat the previous operation did not result in 0. If the ZF is 1 (meaning the previous result was 0), the CMOVNZ\r\ninstruction will not move the data, and execution will continue to a different target address.\r\nFigure 5 shows one of the dispatchers. We annotated the instructions to explain how the destination addresses are\r\ncomputed.\r\nFigure 5. CPU instructions of one dispatcher.\r\nUsing Unicorn — a multi-platform, multi-architecture CPU emulator framework — automates the identification\r\nof destination jump addresses. We achieve this by executing the nine instructions preceding each JMP RAX in a\r\ncontrolled manner, rather than running the entire binary. This allows us to determine the jump addresses for each\r\ndispatcher.\r\nTo extract the bytecodes of these instructions, we use the code shown in Figure 6.\r\nFigure 6. Code to extract the bytecodes of the dispatchers.\r\nNext, we emulate each dispatcher. Since each dispatcher uses a two-way branching mechanism with two target\r\naddresses, the emulation process must be repeated twice for each dispatcher to determine both destination\r\nhttps://unit42.paloaltonetworks.com/slow-tempest-malware-obfuscation/\r\nPage 6 of 15\n\naddresses. Figure 7 shows the code used to emulate the dispatchers.\r\nFigure 7. Code to emulate the dispatcher to determine the destination addresses.\r\nAfter computing the two destination addresses, we replaced the dispatcher instructions with direct jump\r\ninstructions to those addresses, effectively removing the CFG obfuscation. This allowed us to see the original code\r\nflow easily in IDA Pro. Figure 8 shows the code to patch the instructions in the IDA database.\r\nhttps://unit42.paloaltonetworks.com/slow-tempest-malware-obfuscation/\r\nPage 7 of 15\n\nFigure 8. Code to patch the dispatchers with de-obfuscated jump instructions.\r\nFinally, we forced IDA Pro to re-analyze the entire function that was patched using the code shown in Figure 9.\r\nThis was to trigger IDA Pro to update its CFG based on the de-obfuscated instructions.\r\nFigure 9. Code to force IDA Pro to re-analyze the patched functions.\r\nThe complete script for resolving with CFG obfuscation using dynamic jumps is available at\r\nemu_jmp_rax_idapython.py.\r\nAfter executing this script, the Hex-Rays decompiler successfully decompiled the main function within the loader\r\nDLL. Figure 10 shows part of the decompilation output.\r\nhttps://unit42.paloaltonetworks.com/slow-tempest-malware-obfuscation/\r\nPage 8 of 15\n\nFigure 10. Decompiled output of the main function in the loader DLL.\r\nHowever, we observed that further obfuscation remained in the code. Specifically, most functions were called\r\ndynamically, and we did not observe any direct Windows API calls. This made it challenging to immediately\r\ndiscern the code's purpose, as the actual functionality was obscured by indirect function resolution.\r\nObfuscated Function Calls\r\nObfuscated function calls use indirect calls, where the function's address is calculated dynamically at runtime and\r\nthen called through a pointer, instead of directly invoking the function by its name. Attackers use this technique to\r\nhinder static analysis, as the actual target function is not immediately apparent in the code. This makes it more\r\ndifficult to understand the program's behavior and identify malicious actions.\r\nAnalysis of the main function's assembly code reveals the presence of multiple obfuscated function calls. The Call\r\nRAX instruction is a key indicator, as it signifies that the function address is being dynamically determined at\r\nruntime rather than being directly specified in the code. Similar to dynamic jumps, the target addresses of these\r\nfunction calls were calculated at runtime. We were not able to determine the target addresses without executing the\r\nbinary. Figure 11 shows some of the obfuscated function calls.\r\nhttps://unit42.paloaltonetworks.com/slow-tempest-malware-obfuscation/\r\nPage 9 of 15\n\nFigure 11. Instructions of multiple obfuscated function calls.\r\nTo determine the target address of obfuscated function calls, we applied a similar approach to the one we used for\r\ndynamic jumps. This is because both techniques involve calculating target addresses at runtime.\r\nOur script successfully calculated the destination addresses of these obfuscated function calls. However, we\r\nobserved that IDA Pro failed to identify the arguments of standard Windows APIs even though the destination\r\naddresses were correctly resolved, as Figure 12 shows. This is because there was missing function signature\r\ninformation linking the addresses of the Windows APIs with the obfuscated function calls.\r\nhttps://unit42.paloaltonetworks.com/slow-tempest-malware-obfuscation/\r\nPage 10 of 15\n\nFigure 12. Destination addresses of the obfuscated function calls resolved.\r\nTo enable IDA Pro to correctly identify the function arguments, rename local variables and perform proper\r\nanalysis, we needed to explicitly set the “callee” address for each obfuscated function call using the code shown in\r\nFigure 13. This provides IDA Pro with the necessary information to recognize the function as a known Windows\r\nAPI.\r\nhttps://unit42.paloaltonetworks.com/slow-tempest-malware-obfuscation/\r\nPage 11 of 15\n\nFigure 13. Code to set the “callee” address to each obfuscated function call.\r\nAfter adding the code shown in Figure 13 to set the callee address, IDA Pro will automatically label function\r\narguments and rename local variables for each obfuscated function call. This significantly improved our ability to\r\nread and analyze the code, allowing us to understand the function's purpose more easily. Figure 14 shows some of\r\nthe function arguments that IDA Pro labeled.\r\nhttps://unit42.paloaltonetworks.com/slow-tempest-malware-obfuscation/\r\nPage 12 of 15\n\nFigure 14. Function arguments and local variables renamed for the obfuscated function calls.\r\nThe complete script for resolving obfuscated function calls is at emu_call_rax_idapython.py.\r\nAfter executing this script, we successfully de-obfuscated both the control flow and the function calls within the\r\nloader DLL. With the code now significantly more readable and the Windows API calls properly identified, we\r\ncould proceed with analyzing its core functionality. In the final section, we examine the main purpose of the\r\nloader DLL.\r\nLoader DLL Analysis\r\nAfter removing the obfuscation using the scripts emu_call_rax_idapython.py and emu_call_rax_idapython.py, we\r\neasily located the main functionality of the loader DLL.\r\nFirst, we observed an anti-sandbox check that uses the Windows API GlobalMemoryStatusEx to determine the\r\ntotal physical memory available on the system. The loader DLL will only unpack its payload and execute it in\r\nhttps://unit42.paloaltonetworks.com/slow-tempest-malware-obfuscation/\r\nPage 13 of 15\n\nmemory if the target machine has at least 6 GB of RAM. Figure 15 shows the pseudocode of the core components\r\nof the loader DLL.\r\nFigure 15. Pseudocode of the core components of the loader DLL.\r\nConclusion\r\nThe SLOW#TEMPEST campaign's evolution highlights malware obfuscation techniques, specifically dynamic\r\njumps and obfuscated function calls. This illustrates the importance for security practitioners to adopt advanced\r\ndynamic analysis techniques (e.g., emulation) alongside static analysis to effectively dissect and understand\r\nmodern malware.\r\nThe success of the SLOW#TEMPEST campaign using these techniques demonstrates the potential impact of\r\nadvanced obfuscation on organizations, making detection and mitigation significantly more challenging.\r\nUnderstanding how threat actors leverage these methods is crucial for developing robust detection rules and\r\nstrengthening defenses against increasingly complex threats.\r\nPalo Alto Networks customers are better protected from the threats discussed above through the following\r\nproducts:\r\nAdvanced WildFire can detect the malware samples discussed in this article.\r\nCortex XDR and XSIAM are designed to prevent the execution of known malicious malware, and also\r\nprevent the execution of unknown malware using Behavioral Threat Protection and machine learning based\r\non the Local Analysis module. The Cortex Shellcode AI module can help detect and prevent shellcode\r\nattacks.\r\nIf you think you may have been compromised or have an urgent matter, get in touch with the Unit 42 Incident\r\nResponse team or call:\r\nNorth America: Toll Free: +1 (866) 486-4842 (866.4.UNIT42)\r\nUK: +44.20.3743.3660\r\nhttps://unit42.paloaltonetworks.com/slow-tempest-malware-obfuscation/\r\nPage 14 of 15\n\nEurope and Middle East: +31.20.299.3130\r\nAsia: +65.6983.8730\r\nJapan: +81.50.1790.0200\r\nAustralia: +61.2.4062.7950\r\nIndia: 00080005045107\r\nPalo Alto Networks has shared these findings with our fellow Cyber Threat Alliance (CTA) members. CTA\r\nmembers use this intelligence to rapidly deploy protections to their customers and to systematically disrupt\r\nmalicious cyber actors. Learn more about the Cyber Threat Alliance.\r\nIndicators of Compromise\r\nSHA256 hash: a05882750f7caac48a5b5ddf4a1392aa704e6e584699fe915c6766306dae72cc\r\nFile size: 7.42 MB\r\nFile description: ISO file distributed in the SLOW#TEMPEST campaign\r\nSHA256 hash: 3d3837eb69c3b072fdfc915468cbc8a83bb0db7babd5f7863bdf81213045023c\r\nFile size: 1.64 MB\r\nFile description: DLL used to load and execute the payload\r\nSHA256 hash: 3583cc881cb077f97422b9729075c9465f0f8f94647b746ee7fa049c4970a978\r\nFile size: 1.64 MB\r\nFile description: DLL with encrypted payload in the overlay segment\r\nSource: https://unit42.paloaltonetworks.com/slow-tempest-malware-obfuscation/\r\nhttps://unit42.paloaltonetworks.com/slow-tempest-malware-obfuscation/\r\nPage 15 of 15",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"ETDA"
	],
	"references": [
		"https://unit42.paloaltonetworks.com/slow-tempest-malware-obfuscation/"
	],
	"report_names": [
		"slow-tempest-malware-obfuscation"
	],
	"threat_actors": [],
	"ts_created_at": 1775446679,
	"ts_updated_at": 1775791270,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/b1d28999e3ff2d0edcbabbdc1730fa08ba17b15a.pdf",
		"text": "https://archive.orkl.eu/b1d28999e3ff2d0edcbabbdc1730fa08ba17b15a.txt",
		"img": "https://archive.orkl.eu/b1d28999e3ff2d0edcbabbdc1730fa08ba17b15a.jpg"
	}
}