


























Affected Platforms: Microsoft Windows
Impacted Users: Windows Users
Impact: Fully Control the Victim’s Computer
Severity Level: High
FortiGuard Labs recently captured several malicious samples that were sending malformed DNS queries. After conducting an in-depth analysis, I determined that these samples are TrickBot variants that use DNS tunneling to communicate with their command-and-control (C2) servers.
TrickBot is a modular malware family that FortiGuard Labs has repeatedly captured over the past decade. Its modular architecture enables it to extend its capabilities by downloading and executing additional modules on compromised devices. Previously observed TrickBot variants primarily relied on HTTP to communicate with its C2 servers.
In this analysis, I use one of these samples to demonstrate the obfuscation techniques the TrickBot variant employs to protect itself from analysis. I examine the persistence mechanisms it establishes on compromised devices, the process of generating malformed DNS queries for C2 communication, and the method used to transport command-and-control data through DNS packets.
Figure 1 shows the captured TrickBot sample files, with one of the samples analyzed using a PE inspection tool. The sample is identified as a 64-bit executable and is not protected by a packer.
Figure 1: Captured samples and analysis in a PE tool.
Although these samples have different file hashes, they exhibit identical PE characteristics, startup code, and overall behaviors. This suggests they originate from the same TrickBot campaign.
To protect itself from static analysis, TrickBot employs several obfuscation techniques.
Constant strings in this TrickBot variant are encrypted and then decrypted at runtime. Most constant numbers are dynamically calculated at runtime. Figure 2 shows how the malware defines and decrypts the constant string “Kernel32.dll”. The decryption method includes executing SUB or XOR instructions.
Figure 2: Decrypting the constant strings “Kernel32.dll”.
All Windows APIs are resolved at runtime using hash-based lookups. I have named the function responsible for this operation Get_API_By_Hash(). Figure 3 shows that the malware passes the hash C8AC8026 to the function at 0x14004990, which returns the API address of LoadLibraryA() in the RAX register. TrickBot then calls this API to load the “Kernel32.dll” file, whose name was a previously decrypted constant string.
Figure 3: Dynamically retrieving the LoadLibraryA() API.
A partial list of the APIs used in this TrickBot variant, along with their hash codes, can be found in the Appendix.
This TrickBot maintains persistence on the victim’s computer by leveraging the Windows Task Scheduler.
The malware variant accepts several command-line parameters, such as “-u”, “-s”, “--log,” and “-i”.
When the TrickBot executable is launched with no parameters or without the “-u” flag, it executes the persistence maintenance workflow to create a new task that runs every several minutes.
The task’s name is formed using a randomly selected folder name from the %AppData% folder, the decrypted constant string “ autoupdate #” and a random numeric value. As shown in Figure 4, it calls a function to generate a task name, such as “Wireshark autoupdate #72784”. This time, TrickBot disguises the scheduled task as an automatic Wireshark update.
Figure 4: Calling a function to generate a random task name.
To create the scheduled task, the malware instantiates an ITaskService COM object that implements the Task Scheduler automation interface. The API CoCreateInstance() is called with the ProgID “Schedule.Service.1” to create the COM object.
Subsequently, it uses several interfaces of the COM object to configure the task properties, including the task name, task action, and task triggers.
Figure 5 illustrates a successfully installed task with the following configuration:
Figure 5: Task created in the Task Scheduler.
TrickBot stores the task name and the full path of its executable file in two NTFS Alternate Data Streams (ADS). The stream named “$TASK” contains the Base64-encoded task name, while the stream named “$FILE” holds the Base64-encoded full path of the TrickBot executable file.
Upon execution, the malware first reads data from the two streams, then executes the same code flow to recreate the task using the retrieved information. This prevents TrickBot from creating multiple duplicate scheduled tasks under different task names.
Figure 6 displays the output of three commands used to examine and decode the Base64-encoded data stored in the “$TASK” and “$FILE” streams.
Figure 6: ADS streams and extracted data.
DNS tunneling is a technique used to bypass network security controls by hiding non-DNS traffic inside standard Domain Name System (DNS) queries and responses. Figure 7 illustrates the general DNS tunneling model.
Figure 7: DNS tunneling model.
Once the TrickBot executable is launched with the “-u” parameter, it communicates with its C2 server over DNS Tunneling. Figure 8 shows that TrickBot sends malformed DNS queries over a public DNS server at 8.8.8.8.
Figure 8: DNS traffic between the TrickBot variant and a public DNS server.
The original C2 server was shut down when I analyzed the malware. I developed a Python script to simulate the C2 server’s response, allowing the analysis to continue. The data presented in this analysis is derived from the simulated C2 server.
TrickBot wraps its command-and-control request packets in encrypted DNS query packets. This section details how TrickBot transfers the data across three types of packets.
The malware encrypts and formats control command data before embedding it in DNS traffic. The process involves:
8970D9E4F4BE8F88FD3394A2C57A1763E2B9BA96D8D7DAD1D6CBE6DDD7CA96F.DFCEAF2EDF6E994FC8 1F5FF81F3EDE6EE8F8B808B898997FF8EFF8BFF8D818B.FF8AFBFA8F8A8D8CF88D8DFC8DF88FFD8F8D8E 8E8A88F8FC968996EED0D7DDD.6CECA998199C18F8D968889898896899789978997.westurn.in
TrickBot calls the Windows API function getaddrinfo() to send malformed DNS queries and receive responses.
The plaintext data of a packet is shown in Figure 9. All DNS query packets generated by this TrickBot variant follow this packet structure.
Figure 9: Plaintext data of a 0x30 packet.
The plaintext packet, shown in Figure 9, consists of the following fields:
Note: If the 0x30 type packet size exceeds 96 bytes (0x60), TrickBot splits the packet into multiple smaller packets (max 96 bytes each). This field appears only in 0x30 packets.
Figure 10 shows logs from my Python DNS server illustrating how the three 0x30-fragment packets are received and reconstructed.
Figure 10: Display showing how 0x30 fragment packets are reconstructed.
According to the data content carried by the three types of packets, they serve distinct roles:
Normal DNS response packets typically contain the IP addresses associated with the queried domain. This section explains how TrickBot hides the malicious data within the IP addresses of these responses.
The 0x32 response packets illustrate how the C2 server transports data through the DNS tunnel. These packets are responsible for delivering control command response data to the TrickBot, corresponding to the requests previously sent in 0x30 packets.
TrickBot reserves the high 6 bits of the first octet of each IPv4 address for an index value (IP index) that starts at 1, leaving the low 2 bits unused. Consequently, the IP index is obtained by right-shifting the first octet by 2 bits (equivalent to dividing it by 4), as shown in Figure 11.
Figure 11: Display of a 0x32-type response packet.
The first two IPv4 addresses are also reserved as detailed in Figure 11:
The maximum 6-bit index value is 0x3F (63). Since the first two IP addresses are reserved, the remaining 61 addresses can carry payload data. Each address can carry 3 bytes of payload. Therefore, the maximum payload size that can be transferred in a single 0x32 response packet is (63-2)*3 = 183 bytes.
While DNS protocols allow multiple IPv4 addresses in a response packet, some middle DNS resolvers may randomly reorder them before returning the response, causing the encoded payload to be out of order. To address this issue, TrickBot implements a sorting function that reorders the received IPs in their original order based on the IP index (the high 6 bits of the first octet).
To reassemble the original payload data, TrickBot needs to read the 3 payload bytes from each IP address (starting with the third IP address) and store them in a previously allocated memory buffer.
Figure 12 examines how the sorted IP addresses are reassembled into the original payload data.
Figure 12: Data reassembled from sorted IP addresses.
In my test environment, it took 40 seconds to transfer a 1.2 MB file via TrickBot DNS tunneling to the TrickBot variant. The transfer speed of DNS tunneling is about 30.7 KB/s.
I will next elaborate on the structure and functionality of TrickBot’s control commands.
The data structure for the commands used in the DNS tunnel is the same as that used in TrickBot’s HTTP protocol-based version. The following sections examine the control commands in chronological order.
The initial request command sent to the C2 server follows this format:
/anchor_dns/DESKTOP-E8LF8JT_W629200.F7F2F482F3BC6345A44E4A6D647731AE/0/Windows 8 x64/1001/0.0.0.0/4E2D56B35DDF7C44B2E42B7B33AADD57EFC3411D262559337CE94D86BA6D76 F5/EjR6pa0ELV59E8K2mR8PUbI80DlEaxyb/
Structure breakdown:
Command “0” is the first command sent to the C2 server, serving to register the victim on the C2 server.
Based on the behavior of the HTTP version, I crafted a simulated response command (replying to the request command “0”) in my DNS server:
"/1/anchor_dns/DESKTOP-E8LF8JT_W629200.F7F2F482F3BC6345A44E4A6D647731AE/EjR6pa0ELV59E8K2mR8PUbI80DlEaxyb"
TrickBot parses the response packet by directly locating the fourth “/”. It then compares the subsequent 32-byte verification data against the one sent in the request command packet.
Figure 13: TrickBot verifies the 32-byte verification data.
As shown in Figure 13, TrickBot calls a memcmp-like function to compare the verification code. The lower portion of the figure displays the received response packet. The “/1/” at the beginning can be replaced with any other valid command number for this response packet because TrickBot ignores the entire data field preceding the verification data.
TrickBot next sends the command “1” to the C2 server to request tasks to execute on the victim’s device. An example of a Command “1” request packet is shown below:
/anchor_dns/DESKTOP-E8LF8JT_W629200.F7F2F482F3BC6345A44E4A6D647731AE/1/ZQqRydar6QBCEYo4IMWUodsgQ7AzJhHc/
As with previous commands, the final 32-byte (0x20) string serves as a verification code. The C2 server must echo this value back in the command response packet for TrickBot to authenticate.
A corresponding response packet for the request command “1” appears as follows:
/{response command number}/anchor_dns/DESKTOP-E8LF8JT_W629200.F7F2F482F3BC6345A44E4A6D647731AE/ ZQqRydar6QBCEYo4IMWUodsgQ7AzJhHc/96322307/bWFsaWNpb3VzX2V4ZV9tb2R1bGUgcGFyYW1ldGVy
The 32-byte-long string after the fourth “/” is the verification code, just as it is for the other commands explained earlier. The following “96322307” is a unique command string ID that identifies the command string in this packet. The final part (“bWFsaWNp…”) is the command string encoded in Base64.
Decoding the Base64 string reveals the command structure: “malicious_exe_module parameter”. The command is split by the space into two components:
This TrickBot variant supports 12 response control commands, listed in the table below.
| Response Command Number | Description |
| “0” | Executes a Windows command-line command. |
| “1”, “2” | Downloads an EXE module to be executed on the victim’s device. |
| “3”, “4” | Downloads a module and executes it using rundll32.exe, invoking the module’s “Control_RunDLL” export function. |
| “5”, “6” | Downloads a module and injects it into a randomly selected process using process hollowing technique. |
| “7”, “8” | Downloads a module and injects it in a randomly selected process using process doppelgänging. |
| “9” | Executes Windows commands via anonymous pipes connected to cmd.exe. |
| “10” | Executes PowerShell codes through anonymous pipes connected to cmd.exe. |
| “11” | Executes raw machine code (ASM code) directly within TrickBot’s memory. |
TrickBot sends a request command “5” to the C2 server to download an executable module. This action is typically triggered by the command handler functions for response commands “1”, “3”, “4”, “5”, and “7”.
Below is an example of a request command “5” packet:
/anchor_dns/DESKTOP-E8LF8JT_W629200.F7F2F482F3BC6345A44E4A6D647731AE/5/test_dll/
The response packet contains the module data, which is delivered separately across multiple 0x32-type packets.
Figure 14: Downloading a module by requesting the command “5”.
Figure 14 shows the handler function for the response command “3”, which sends a request command “5” packet to download the DLL module shown in memory. TrickBot saves the DLL module to a randomly named temporary file.
The malware then launches rundll32.exe to execute the downloaded DLL module, invoking the CreateProcessW() API, as shown in Figure 15. The malicious DLL module (tcp469A.dll) then executes within the rundll32.exe process, starting at its “Control_RunDLL” export function.
Figure 15: Execute a downloaded DLL module on the victim’s device.
This analysis examined a TrickBot variant that uses DNS tunneling for command-and-control (C2) communication instead of the HTTP protocol used by earlier variants. DNS tunneling is achieved by embedding specially encoded data in DNS queries and responses.
The malware establishes persistence via Windows Task Scheduler, stores its configuration in NTFS Alternate Data Streams, and uses encrypted strings and runtime API resolution to hinder analysis.
Its modular architecture enables in-memory module execution and supports functions such as command execution, process injection, and PowerShell execution. Despite adopting DNS tunneling, this TrickBot variant retains the modular design and capabilities that make the malware family a persistent threat.
Fortinet customers are already protected from this campaign with FortiGuard’s AntiSPAM, Web Filtering, IPS, and AntiVirus services as follows:
The C2 server domain associated with this malicious campaign is rated as Malicious Websites by the FortiGuard Web Filtering service.
The domain is also covered by FortiGuard’s Botnet Domain DB service.
FortiGate and FortiNDR detect the TrickBot DNS Tunneling with the IPS signature:
Trick.Botnet
FortiGuard Antivirus service detects these TrickBot sample files with the following AV signature:
W64/TrickBot.WC!tr
Fortinet FortiGate, FortiMail, FortiClient, and FortiEDR support the FortiGuard AntiVirus service, and the FortiGuard AntiVirus engine is part of each solution. As a result, customers using these products already have up-to-date protections.
To stay informed of new and emerging threats, you can sign up to receive future alerts.
We also suggest that our readers complete the free NSE training: NSE 1 – Information Security Awareness, which includes information on Internet threats and is designed to help end users identify and protect themselves against phishing attacks.
The FortiPhish Phishing Simulation Service, together with Fortinet’s Security Awareness and Training Service, uses real-world phishing scenarios to train and test employees against common social engineering tactics. By improving users’ ability to recognize and respond to suspicious content—especially during high-risk periods of distraction or urgency—these services help reduce the risk of successful phishing and malware attacks.
If you believe this or any other cybersecurity threat has impacted your organization, please contact our Global FortiGuard Incident Response Team.
| Technique ID | Technique Name |
| T1071.004 | Application Layer Protocol: DNS |
| T1572 | Protocol Tunneling |
| T1053.005 | Scheduled Task/Job |
| T1564.003 | Hide Artifacts: NTFS File Attributes |
| T1055.002 | Process Injection: Process Hollowing |
| T1055.005 | Process Injection: Process doppelgänging |
| T1218.011 | Signed Binary Proxy Execution: Rundll32 |
| T1059.001 | Command and Scripting Interpreter: PowerShell |
| T1105 | Ingress Tool Transfer |
| T1059 | Command and Scripting Interpreter |
westurn.in
[TrickBot Samples]
DF527A5C2FBDE43816CD02F4CD49EEE4BB82FB4A3C7045021360888C7D504C98
6C677EB2B3FFD288083C59A13D7BB712D4754AF61A5563873F76C440962346F4
105F652E6B8F31C371F2385877E43B6772AFF5D3168D5D4635F8A1FCBB321421
33C331EDEDBF8EE9829895424423CE3FD17E359D2E784FCBCE396AACFF458CF5
3B19A82E1354AC14A3DA7C840CBDD0CE50DB38432D78E767B36F08E45024C23D
BF80245BA792992FBFE24ABAC33F8FD66F24CDEB5F0F21CFDF45A29D107C8D3B
| Hash Code | API Name |
| 1297812C | GetLastError |
| 1FC0EAEE | GetProcAddress |
| 223296ED | GetShortPathNameA |
| 279DEAC1 | FindNextFileW |
| 2CA1B5E6 | lstrcat |
| 2D40B8E6 | lstrlenA |
| 32432452 | FindFirstFileW |
| 3287EC73 | RtlInitUnicodeString |
| 3D9972F5 | Sleep |
| 475587A1 | GetFileAttributesW |
| 4B935B8E | FreeLibrary |
| 052AC19C | IsWow64Process |
| 58FE7ABE | GetTempPathA |
| 5AA7E70B | MultiByteToWideChar |
| 5CB5EF72 | FreeSid |
| 5E7EE0D0 | SetEvent |
| 69260152 | GetTickCount |
| 723EB0D5 | CloseHandle |
| 774393E8 | GetModuleFileNameA |
| 774393FE | GetModuleFileNameW |
| 7B4842C1 | FindClose |
| 81F0F0DF | DeleteFileA |
| 8D5A50DC | CreateEventA |
| 08F8F114 | CreateFileA |
| 8FB8B5BD | WSACleanup |
| 9ABFB8A6 | VirtualAllocEx |
| 9C480E24 | GetVersionExA |
| 9E6FA842 | TerminateProcess |
| A48D6762 | GetModuleHandleA |
| B8538A52 | InitializeSecurityDescriptor |
| BEA0BF35 | WriteProcessMemory |
| C09D5D66 | TerminateThread |
| C54374F3 | WaitForSingleObject |
| C8AC8026 | LoadLibraryA |
| CCD03C3A | SetSecurityDescriptorDacl |
| 0D89AD05 | GetCurrentProcess |
| 0E91A280 | GetProcessId |
| EF48E03A | SetFilePointer |
| 0FA4F502 | GetTempFileNameA |
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。