← Back to list

Unmasking Amadey: When Static Analysis Fails, Dynamic Extraction Prevails

Reverse engineering malware is rarely a linear journey. As analysts, we are often taught to follow structured workflows, but the malware…

Svetoslav · 2026-05-18 15:51 · 0 claps · 7.2 min read
#botnet #amadey #threat-intelligence #threat-hunting #cybersecurity
Open on Medium ↗
Wiki topics: 🔒 · Cybersecurity

Unmasking Amadey: When Static Analysis Fails, Dynamic Extraction Prevails

Reverse engineering malware is rarely a linear journey. As analysts, we are often taught to follow structured workflows, but the malware itself rarely complies with our textbook expectations. Recently, I completed a deep-dive analysis of a native C++ Amadey botnet sample. What began as a standard static disassembly session quickly evolved into a high-friction battle against compiler optimisation, automated tool failures, and custom cryptography.

This is the story of that hunt, the hurdles encountered, and the tactical shifts required to extract actionable threat intelligence from a hardened binary.

The Initial Roadmap and The Compiler Wall

Every effective malware investigation begins with basic static properties. I initiated the triage phase by running the sample through Capa to establish a high-level capabilities matrix. Capa provided a solid baseline, signalling that the binary possessed capabilities for system modification, dynamic process creation, and network communication via specific Windows internet APIs.

Armed with this roadmap, I imported the executable into Ghidra for deep static analysis. However, compiling modern C++ applications introduces significant noise. Ghidra dumped me directly into a labyrinth of Microsoft Visual C++ (MSVC) runtime boilerplate initialisation functions. Navigating this compiler noise required patience; I had to systematically filter out standard setup routines to locate the true user execution entry point, which I labelled real_main.

Once inside real_main, the malware’s defence-evasion strategy immediately became clear. I observed the program calling GetModuleFileNameA to capture its current execution path. The malware compares this path against its intended installation destination. If it discovers it is running from an unapproved location, such as a desktop, it triggers an installation routine to clone itself into a hidden directory, executes the clone, and immediately terminates the original process.

Figure 1: Ghidra decompiler exposing the GetModuleFileNameA path validation loop within the malware’s primary entry function

Figure 1: Ghidra decompiler exposing the GetModuleFileNameA path validation loop within the malware’s primary entry function

The Automated Tool Mirage

Recognising that the sample relied heavily on obfuscated strings to conceal its infrastructure, I turned to FLOSS (FireEye Labs Obfuscated String Solver) to automate deobfuscation. FLOSS is typically a dependable asset for emulating code and dumping decrypted strings from memory.

Unfortunately, the automation hit a brick wall. FLOSS completed its analysis but returned exactly 0 decoded strings. The malware author had successfully thwarted automated emulation, likely through tight runtime loops or memory allocation techniques that disrupted the FLOSS emulation engine. The tool, however, yielded one highly specific piece of triage data: a single “tight string” containing the 10-digit integer 3198791665. In the architectural framework of this botnet, this number represents the hardcoded Campaign ID used by the operator to identify the bot.

To determine what FLOSS had missed, I manually audited the binary’s text objects by opening Ghidra’s Defined Strings window and applying a filter for network protocols (http).

Figure 2: Ghidra Defined Strings analysis revealing that network endpoints are absent, replaced entirely by static WinINet API function imports

Figure 2: Ghidra Defined Strings analysis revealing that network endpoints are absent, replaced entirely by static WinINet API function imports

The output was telling. Rather than finding hardcoded Command and Control (C2) URLs like http://attacker-domain.com, the filter returned raw Windows API strings such as HttpOpenRequestA and HttpSendRequestA. This confirmed two critical points: the binary was absolutely engineered to communicate over the network, but the actual target domains were thoroughly encrypted.

Tracing the Configuration Loop

To uncover where the configuration strings were being processed, I utilised Ghidra’s Symbol Tree to locate the actual import pointer for HttpOpenRequestA. Right-clicking the function allowed me to track its Cross-References (XREFs) backwards into the functional code. This path tracking led me straight to a master execution module labelled FUN_1400160c0.

When I decompiled this module, I immediately noticed a distinct, repeating pattern of function calls occurring at the very top of the execution flow.

Figure 3: Decompiled configuration-loading sequences within function FUN_1400160c0 processing hardcoded data blocks.

Figure 3: Decompiled configuration-loading sequences within function FUN_1400160c0 processing hardcoded data blocks.

Unmasking a Custom Cypher

I isolated the processing function (FUN_1400074c0) to determine how the data was being decrypted. Many automated engines categorise this type of obfuscation as standard RC4 encryption. However, as I reversed the decompression logic, I looked for the hallmark characteristics of an RC4 Key Scheduling Algorithm (KSA), specifically, an array initialised sequentially from 0 to 255. It wasn’t there.

Instead, the decompiler exposed a custom, math-heavy substitution algorithm that processed the characters through modular arithmetic.

Figure 4: Detailed decompiled view of the custom substitution routine showing the modular arithmetic subtraction loop used for decryption.

Figure 4: Detailed decompiled view of the custom substitution routine showing the modular arithmetic subtraction loop used for decryption.

Hitting the Wall with Static Analysis

To construct a standalone script to decrypt the configuration manually, I needed to extract that underlying alphabet string from memory. I double-clicked the alphabet variable reference, DAT_14007c858, expecting to find a standard alphanumeric sequence in the static data viewer.

Figure 5: Ghidra Listing view confirming an empty static memory block where the alphabet is dynamically written at runtime.

Figure 5: Ghidra Listing view confirming an empty static memory block where the alphabet is dynamically written at runtime.

The storage block was completely empty, containing only raw padding zeros (00 00 00). The XREFs revealed that the function FUN_14005f180 held a Write (W) permission to this address. The malware author was dynamically generating the cypher alphabet in RAM when the process executed. At this point, static analysis had reached its technical limit. To progress further, I had to transition to dynamic extraction.

The Dynamic Extraction

I transitioned to a secure Windows 10 environment and attached the 64-bit version of x64dbg to the binary. My objective was clear: bypass the dropper behaviour, allow the program to dynamically generate its alphabet and decrypt its strings, and trap it right as it attempted to communicate.

The initial detonation demonstrated the effectiveness of the malware’s evasion tricks. When executed directly from the desktop, the dropper generated its persistent clone in the user’s temporary folder, initiated the clone process, and immediately terminated the debugger session.

To overcome this, I let the persistent instance run in the background. Monitoring Windows Task Manager revealed the true payload running silently under a randomised string filename: Ujkjwrro.exe, located within the hidden path: C:\Users\Analyst\AppData\Local\Temp\08e25196df.

Figure 6: Task Manager and File Explorer revealing the Amadey payload executing from its hidden persistence directory under a randomised filename.

Figure 6: Task Manager and File Explorer revealing the Amadey payload executing from its hidden persistence directory under a randomised filename.

Rather than trying to fight the dropper’s self-termination loop, I used an Attach-and-Trap technique. I allowed the persistent process to run natively, opened x64dbg as an administrator, and attached directly to the active Ujkjwrro.exe process. Once attached, the debugger forcefully froze the process in memory.

I set a hardware breakpoint on the InternetConnectA function inside WININET.dll and resume execution. Because malware operates on a periodic beaconing timer, I simply had to wait for the next callback interval. Within moments, the process hit the breakpoint and snapped to a halt.

Figure 7: The dynamic extraction phase in x64dbg, capturing the live malware call to the network connection subsystem.

Figure 7: The dynamic extraction phase in x64dbg, capturing the live malware call to the network connection subsystem.

By checking the CPU registers at the moment of the function call, the entire structure collapsed. In x64 call conventions, the second argument to a function is stored in the RDX register. Looking at the registers panel, the dynamic alphabet had completed its work, and the fully decrypted target C2 IP address was sitting in plain text: 196.251.107.130.

Figure 8: CPU Register state showing the RDX and R12 registers holding the completely deobfuscated infrastructure indicators.

Figure 8: CPU Register state showing the RDX and R12 registers holding the completely deobfuscated infrastructure indicators.

Further inspection of the adjacent registers revealed the exact target path hosted on the attacker’s server: /h84jjfAr/index.php. The hunt was complete.

I set a breakpoint on InternetConnectA, the exact API that receives the C2 domain as an argument and waited for the bot to beacon. When the trap sprang, the fully decrypted C2 IP address was sitting in plain text right inside the RDX register.

This analysis underscores a vital principle of security operations: automated tools and static disassembly are essential starting points, but they are easily blinded by custom runtime modifications. Had I relied entirely on FLOSS or static code listings, the primary indicators of compromise would have remained hidden.

Understanding how to read low-level programming logic allowed me to identify the mathematical structure of a custom cypher, recognise the constraints of static analysis, and execute a surgical dynamic attachment to extract the critical indicators directly from the processor.

Technical Resources

· Detection Engineering: View the custom YARA rule here

· IoC Publication: Access the full list of IoCs on AlienVault OTX

Threat Intelligence Report: Amadey Persistent Botnet Activity

· Report ID: 2026–05–19-AMA-01

· Classification: TLP:CLEAR

· Subject: Manual Cypher Deobfuscation and Dynamic C2 Extraction of Amadey Botnet

Executive Summary

Detailed static and dynamic analysis was conducted on a native 64-bit Windows executable compiled to run the Amadey botnet payload. The sample uses an aggressive installation routine to copy itself into system temporary folders under randomised names while utilising a watchdog loop to establish persistence. The malware successfully evades automated string deobfuscation by processing its primary indicators through a custom alphanumeric substitution cypher calculated dynamically in memory. Dynamic attachment and API hooking successfully extracted live network indicators.

List of Affected Entities

Table 1: List of Affected Entities

Table 1: List of Affected Entities

Technical Analysis & Host Indicators

1. Dropper & Persistence Mechanism

Upon initial execution, the malware conducts a path validation check via GetModuleFileNameA. If the file is executed from an external directory (e.g., a Desktop environment), it deploys its persistent instance into the user’s local profile before executing the clone and terminating the dropper instance.

· Persistent Host File Path: C:\Users\Analyst\AppData\Local\Temp\08e25196df\Ujkjwrro.exe

· Process Behaviour: The persistent application runs an automated watchdog loop. If the task is killed via administrative termination, the operating system persistence keys instantly trigger a restart of the Ujkjwrro.exe process.

2. Cryptographic Architecture

The configuration data blocks are obfuscated utilising a custom Vigenère-style shift cypher. The alphabet array used as the base string for the character transformation is uninitialized within the static file sections (Base Address: 14007c858). It is dynamically generated in memory at runtime via module FUN_14005f180. Decryption is processed through a modular arithmetic loop structured as:

Decrypted Character = (Ciphertext Index — Key Index + Alphabet Length) (mod Alphabet Length)

3. Network Indicators & Infrastructure Binding

Network communications are established via WININET.dll functions. The malware uses hardcoded callback intervals to beacon operational data back to the centralised attacker node. Dynamic register tracking at the InternetConnectA API entry point successfully exposed the following indicators of compromise:

· Target Destination IP Address: 196.251.107.130 (Mapped to active external C2 host)

· HTTP Campaign Callback Path: /h84jjfAr/index.php

· Botnet Campaign Tracker ID: 3198791665

· User-Agent String Signature: Configured dynamically via HttpOpenRequestA parameters.

· SHA256: 5258a241f8c67e1666060e2c033d127dee8c2c50c7a8dfe0676264e9cb6762c1


메타데이터
post_id
0a201da0b6b8
slug
unmasking-amadey-when-static-analysis-fails-dynamic-extraction-prevails-0a201da0b6b8
url
https://medium.com/@svetli80/unmasking-amadey-when-static-analysis-fails-dynamic-extraction-prevails-0a201da0b6b8
canonical_url
https://medium.com/@svetli80/unmasking-amadey-when-static-analysis-fails-dynamic-extraction-prevails-0a201da0b6b8
author_url
https://medium.com/@svetli80
status
ok
fetched_at
2026-07-10 14:10:06