TaxOff: um, you've got a backdoor... By Positive Technologies Published: 2024-11-28 · Archived: 2026-04-05 16:48:27 UTC Table of contents: Table of contents: TaxOff: um, you've got a backdoor... Author:  Vladislav Lunin, Senior Specialist of the Positive Technologies Expert Security Center Sophisticated Threat Research Group Takeaways: 1. A new group was discovered targetting Russian government structures: TaxOff. 2. TaxOff phished using legal and financial emails. 3. TaxOff used the Trinper backdoor in its attacks. 4. Trinper is a multithreaded backdoor written in C++ with flexible configuration using the template method as a design pattern, STL containers, and a buffer cache to improve performance. It has numerous malicious capabilities. Introduction In Q3 2024, the Positive Technologies Expert Security Center (PT ESC) TI Department discovered a series of attacks on Russian government agencies. We were unable to establish any connection with known groups using the same techniques. The main goal was espionage and gaining a foothold to follow through on further attacks. We dubbed the group TaxOff because of their legal and finance-related phishing emails leading to a backdoor written in at least C++17, which we named Trinper after the artifact used to communicate with C2. Initial infection vector TaxOff uses phishing emails. We found several of them, including one with a link to Yandex Disk with malicious content for 1C and another with a fake installer for special software used by government employees to submit annual income and expense reports. This software is updated every year and targeted by attackers who distribute malware pretending to be updates. Materials.img One email had a link to Yandex Disk with the file Materials.img containing the following: DCIM.lnk — a shortcut used to start the Trinper backdoor drive.google.com — the Trinper backdoor Encrypteddata — merged encrypted RAR archives with trimmed headers  История поисков.html — a phishing form like the image below https://global.ptsecurity.com/en/research/pt-esc-threat-intelligence/taxoff-um-you-ve-got-a-backdoor/ Page 1 of 20 Figure 1. Phishing form Spravki BK The other vector contained the Spravki BK software used by government employees in Russia to submit income and expense reports. This software has also been targeted by group to spread the Konni backdoor as the renamed WEXTRACT.EXE.MUI file usually responsible for extracting compressed CAB files. In our case, it contains two executable files instead: bk.exe (Figure 2, Spravki BK) and DotNet35.exe, the Trinper backdoor. Figure 2. Information about the bk.exe file Similar to CAB files, the RCData resource section contains attributes of the execution sequence of the files it stores. The first attribute, RUNPROGRAM, contains instructions to execute a specific program or command at the start and launches bk.exe. https://global.ptsecurity.com/en/research/pt-esc-threat-intelligence/taxoff-um-you-ve-got-a-backdoor/ Page 2 of 20 Figure 3. RUNPROGRAM attribute contents The second attribute, POSTRUNPROGRAM, contains instructions to launch the executable file after RUNPROGRAM has been executed. So after bk.exe is run, DotNet35.exe is launched. Figure 4. POSTRUNPROGRAM attribute contents Trinper backdoor To improve general understanding of how the backdoor works, the sections below include an explanation of its architecture, STL, design pattern, custom serialization, and buffer cache as a preface to its functional description. Architecture Like any other multithreaded application, Trinper is built on a parallel programming paradigm, specifically thread parallelism. Tasks are broken down into sequential steps as shown on the diagram below, each of which can be performed in parallel with others. https://global.ptsecurity.com/en/research/pt-esc-threat-intelligence/taxoff-um-you-ve-got-a-backdoor/ Page 3 of 20 Figure 5. Trinper's architecture One aspect of thread parallelism is data transfer between threads using global variables that can be divided into the following groups: 1. Group 1 include a container for storing instances of the class communicating with C2 (CommHTTP_instance). 2. Group 2 include a container for storing information about code injections (map_TaskInject). 3. Group 3 include containers for storing running commands (vector_RunningTasks, deque_shared_RunningTasks, map_RunningTasks). 4. Group 4 include containers for storing the operation of background commands (map_shared_ptr_BgJobs, deque_BgJobKeylogger, unordered_map_BgJobKeylogger). Use of STL The Standard Template Library provides a set of common generic algorithms, containers, means of accessing their contents, and various functions in C++ used by the backdoor. The main sign of STL runtime is the error messages for various containers. Figure 6. Error strings related to STL container runtime std::string and std::wstring Strings are objects represented as a sequence of characters. Symbols can either have ASCII or wide encoding, which makes these containers clearly distinguishable. In std::string, the maximum length of the predefined buffer can't exceed 15 bytes, otherwise a heap buffer will be allocated. As for std::wstring, the length of the predefined buffer can't exceed 7 bytes. This runtime is for comparing the length of the stored string and possible subsequent allocation of a heap buffer, which allows one of the containers used to be determined precisely. Figure 7. Recognizing the std::string and std::wstring runtime std::vector Vectors are containers for array-like sequences that can vary in size. One way to recognize the runtime of a vector is to compare successive memory addresses and then assign a new value to one of them. If the pointer to the first vector element is equal to the pointer to the last element (for example, when adding a new element), then the vector will have to change its current size, allocate additional memory before adding it, and redefine the pointer to the last element. https://global.ptsecurity.com/en/research/pt-esc-threat-intelligence/taxoff-um-you-ve-got-a-backdoor/ Page 4 of 20 Figure 8. Recognizing the std::vector runtime std::list Lists are sequence containers that allow constant-time insertion and erasure of elements anywhere in the sequence, as well as iteration in both directions. One way to recognize the runtime of a doubly linked list is to compare the selected buffer with the subsequent one to see if the end has been reached when iterating the elements. Figure 9. Recognizing the std::list runtime std::map Maps are associative containers that store elements formed by a combination of a key and mapped value in a specific order. One way to recognize map runtime is that the map needs to know if there's already an element with the provided key before inserting a new key-value pair or returning a value based on the key. Figure 10. Recognizing std::map runtime std::unordered_map https://global.ptsecurity.com/en/research/pt-esc-threat-intelligence/taxoff-um-you-ve-got-a-backdoor/ Page 5 of 20 Unordered maps are associative containers that store elements formed by a combination of a key and mapped value, which allows individual elements to be quickly found by their keys. One of the ways to recognize the runtime of an unordered map is how its elements are stored in hash tables, as the hash sum will always be calculated for any element index regardless of the operation. Figure 11. Recognizing the std::unordered_map runtime std::deque Deques are dynamically sized sequence containers that can expand or contract at the front (head) or back (tail). One method of recognizing the runtime of a deque is to access elements in the blocks. Their size is always a multiple of two, so access uses bitwise operations to split the index into a block and an offset. Figure 12. Recognizing the std::deque runtime std::shared_ptr Smart pointers control pointer storage and provide limited trash collection, potentially sharing this control with other objects. One way to recognize the runtime of a smart pointer is with atomic operations. If the number of shared pointers to an object decreases to zero, then the control block is deleted. https://global.ptsecurity.com/en/research/pt-esc-threat-intelligence/taxoff-um-you-ve-got-a-backdoor/ Page 6 of 20 Figure 13. Recognizing the std::shared_ptr runtime std::filesystem std::filesystem provides means for performing operations on file systems and their components, including paths, regular files, and directories. One of the ways to recognize its runtime is the presence of functions with the _std_fs_* prefix, which indicates operations with the file system. Figure 14. Recognizing the std::filesystem runtime Including header files To avoid creating structures manually, we need to include header files. To determine their location, we run the x86/x64 Native Tools Command Prompt for VS 20XX (depending on the bit depth of the executable file) and enter the echo %INCLUDE% command. Then we copy all the paths and paste them in Options > Compiler > Include directories. We also specify -target x86_64-pc-win32/i386-pc-win32 -x c++ as arguments to include C++ header files. https://global.ptsecurity.com/en/research/pt-esc-threat-intelligence/taxoff-um-you-ve-got-a-backdoor/ Page 7 of 20 Figure 15. Including header files In most cases, the element type of containers is set at compile time, so you can't just include a vector header file (for example) and expect all the element types to be included. Instead, you need to create a separate header file where the container element type will be defined explicitly. Figure 16. Vector structure Design pattern used A design pattern in software engineering is a recurring architectural construct offering a solution to a design problem within a frequently occurring context. This is a rare find in malicious code and indicates that the programmer who wrote the backdoor is an experienced professional. The backdoor uses the template method, which is a behavioral design pattern that defines the skeleton of an algorithm and defers certain steps to subclasses. The pattern allows subclasses to redefine the algorithm's steps without changing its overall structure. https://global.ptsecurity.com/en/research/pt-esc-threat-intelligence/taxoff-um-you-ve-got-a-backdoor/ Page 8 of 20 Figure 17. The template method This backdoor pattern is used to create command subclasses that are inherited from the base class and redefine its methods and fields. Figure 18. Template method in use Custom serialization used In addition to encryption, the backdoor uses custom serialization to store the configuration and increase flexibility, as it allows fields to have multiple values in the same token. https://global.ptsecurity.com/en/research/pt-esc-threat-intelligence/taxoff-um-you-ve-got-a-backdoor/ Page 9 of 20 Figure 19. Serialized configuration For example, if a container token is negative, then it has another container in its value that may also only be part of a sequential nesting of containers or unequivocally determine the value of the token (for example, store a string). https://global.ptsecurity.com/en/research/pt-esc-threat-intelligence/taxoff-um-you-ve-got-a-backdoor/ Page 10 of 20 Figure 20. Deserialization of the configuration Buffer cache use A buffer cache is a data structure designed for the temporary storage of data to speed up access to it. The Trinper backdoor uses caching to reduce access time to frequently used data, minimize latency, and improve overall program performance. Figure 21. Use of buffer cache Initialization and execution of main class instances At the start, the backdoor deserializes the configuration and gets the name it should have. If it's different, execution is stopped, but if the names match, the backdoor continues initialization and calls a function to obtain information about the victim's computer and collect it with the following type of VictimInfo structure: struct struct_VictimInfo { DWORD magic; struct_VictimData VictimData; }; struct struct_VictimData { GUID guid; BYTE pbSecret[16]; BYTE UserNameW[64]; BYTE hostname[32]; BYTE disks[32]; BYTE h_addrs[20]; DWORD KeyboardLayout; BYTE dwOemId; BYTE val_64; BYTE dwMajorVersion; BYTE dwMinorVersion; BYTE Authority; BYTE FileNameW[64]; BYTE AdaptersAddresses[6]; }; The fields of the VictimInfo structure have the following purposes: Member Purpose magic Magic number 0xB0B1B201 VictimData Member Purpose guid Generated GUID pbSecret Session key for AES-128-CBC UserNameW Username hostname Host name disks Disk names h_addrs Host address list https://global.ptsecurity.com/en/research/pt-esc-threat-intelligence/taxoff-um-you-ve-got-a-backdoor/ Page 11 of 20 Member Purpose Member Purpose KeyboardLayout System language used dwOemId Information about the architecture val_64 Constant value 64 dwMajorVersion Major version of the system dwMinorVersion Minor version of the system Authority Level of integrity FileNameW File path AdaptersAddresses Addresses of network adapters After filling in the VictimInfo structure, the backdoor creates and runs these class instances for execution in different threads: CommHTTP — the class for the thread for communication with C2 servers BgJobFileCapture — the class for the thread that monitors the file system BgJobKeylogger — the class for the thread where keystrokes will be intercepted In its thread, the CommHTTP class parses the deserialized configuration it will use to communicate with C2, generates a session key for AES-128-CBC (with the initialization vector equal to zero), imports the public RSA key, and enters a communication loop with C2 servers where: Commands are received Results about command operations are received and sent Figure 22. CommHTTP class instance execution cycle An instance of the BgJobFileCapture class monitors the file system in its thread, loops through all connected disks, and searches recursively for .doc, .xls, .ppt, .rtf, and .pdf files stored on disks. It also stores execution results in a map with a key (file name) and value (the structure containing information about the file, including its contents). https://global.ptsecurity.com/en/research/pt-esc-threat-intelligence/taxoff-um-you-ve-got-a-backdoor/ Page 12 of 20 Figure 23. Receiving information about the file system An instance of the BgJobKeylogger class intercepts keystrokes in its thread and stores them in a deque, with data from the clipboard stored in an unordered map. Figure 24. Installing the keylogger Configuration The configuration is encrypted and stored in the .data section, and decryption is carried out with a one-byte key for a regular Xor operation. Figure 25. Decrypting the configuration Here's what the decrypted and deserialized configuration structure looks like: struct struct_Config { DWORD sleep_time; https://global.ptsecurity.com/en/research/pt-esc-threat-intelligence/taxoff-um-you-ve-got-a-backdoor/ Page 13 of 20 DWORD size; std::wstring UserAgent; std::wstring wstr_x86; std::wstring wstr_x64; std::vector C2; QWORD *public_key; QWORD public_key_len; struct_Commands Commands; struct_TaskResults TaskResults; }; struct struct_Commands { std::wstring Uri; std::vector Headers; struct_CommandsResponse CommandsResponse; struct_CommandsHeaders CommandsHeaders; QWORD HelloMessage; QWORD HelloMessageLen; }; struct struct_CommandsResponse { std::string TagOpen; std::string Encoder; std::string TagClose; }; struct struct_CommandsHeaders { std::string Header; std::string TagOpen; std::string Encoder; std::string TagClose; }; struct struct_TaskResults { std::wstring Uri; std::vector Headers; struct_TaskResultsData TaskResultsData; struct_TaskResultsHeaders TaskResultsHeaders; }; struct struct_TaskResultsData { std::string TagOpen; std::string Encoder; std::string TagClose; }; struct struct_TaskResultsHeaders { std::string Header; std::string TagOpen; std::string Encoder; std::string TagClose; }; The fields of the Config structure have the following purposes: Member Purpose sleep_time Timeout for a CommHTTP class instance in the C2 communication loop size Sized of used buffer cache UserAgent User-Agent used to communicate with C2 servers wstr_x86 x86 wide string not used https://global.ptsecurity.com/en/research/pt-esc-threat-intelligence/taxoff-um-you-ve-got-a-backdoor/ Page 14 of 20 wstr_x64 X64 wide string not used C2 C2 addresses public_key Public key for encrypting information about the victim and session key public_key_len Length of the public key Commands Structure used to receive commands Member Purpose Uri Command request path Headers Custom headers CommandsResponse Member Purpose TagOpen Start of command mask Encoder Command encoding algorithm TagClose End of command mask CommandsHeaders Member Purpose Header Header storing information about the victim TagOpen Start of the header mask Encoder Encoding algorithm TagClose End of header mask HelloMessage Command request string HelloMessage Command request string length TaskResults Structure used to send command operation results Member Purpose Uri Path of command operation results Headers Custom headers TaskResultsData Member Purpose TagOpen Start of task results mask https://global.ptsecurity.com/en/research/pt-esc-threat-intelligence/taxoff-um-you-ve-got-a-backdoor/ Page 15 of 20 Member Purpose Member Purpose Encoder Algorithm for encoding task results TagClose End of task results mask TaskResultsHeaders Member Purpose Header Header storing information about the victim TagOpen Start of the header mask Encoder Encoding algorithm TagClose End of header mask Communication protocol with C2 All communication with C2 is carried out by an CommHTTP class instance using calls to WININET.DLL library network functions. Information about the victim's computer and session key is encrypted with the public RSA key, encoded using Base64, and sent to C2 in the Config.Commands.CommandsHeaders header with Config.Commands.HelloMessage in the request data. The commands received from C2 in response are enclosed between the Config.Commands.CommandsResponse.TagOpen and Config.Commands.CommandsResponse.TagClose markers and encoded using Base64. The task results are encrypted with the AES-128-CBC session key, encoded using Base64, and enclosed between the Config.TaskResults.TaskResultsData.TagOpen and Config.TaskResults.TaskResultsData.TagClose markers in the request data to C2. Figure 26. POST request to receive commands https://global.ptsecurity.com/en/research/pt-esc-threat-intelligence/taxoff-um-you-ve-got-a-backdoor/ Page 16 of 20 For example, below is a backdoor request to C2 to receive commands. The greeting message in the data is mid=76&mod=TRINP, and you can see that the User-Agent header doesn't display the content received from Config.UserAgent correctly. This is due to an error passing the header value to InternetOpenW. The problem is that InternetOpenW tries to convert the string for User-Agent from wide to ASCII encoding, but does so incorrectly because the pointer to the values from the configuration is passed incorrectly, leading to an undisplayable string generated at the output. Figure 27. Request packet to receive commands Commands As mentioned earlier, commands are invoked not by calling a specific function, but by instantiating classes and adding them to a smart pointer wrapper to be added to a deque for execution, and then retrieved from there and called in the main thread loop. The table below includes descriptions of the commands. ID Command Description 0x1213C7 Inject Code injection into process 0xF17E09 WriteFile Write to file 0xF17ED0 ReadFile Read from file 0xC033A4D Cmd Execute command using cmd.exe 0x6E17A585 GetRunningTasks Receive commands running currently 0xECEC Exec Reverse shell 0xCD Cd Change of directory 0x108 JobConf Add command in the background 0xD1E Die Backdoor shutdown 0x6177 KillTask End working command 0xC04F SetCommConfValue Configuration update Insights The TaxOff group tricks users by baiting them with time-sensitive material they're expecting at work and attack using a sophisticated multithreaded backdoor called Trinper. After establishing persistent access to compromised systems, they can effectively manage multiple tasks simultaneously and follow through on various malicious actions without significantly impacting system performance. Multithreading provides a high degree of parallelism to hide the backdoor while retaining the ability to collect and exfiltrate data, install additional modules, and maintain communications with C2. This combination of convincing bait and a sophisticated multithreaded backdoor makes TaxOff's attacks particularly dangerous and difficult to detect and prevent, highlighting the need for continuous user awareness of cyberthreats and the implementation of multi-layered security measures for protection. IoC File indicators FILE MD5 SHA-1 SHA-256 Материалы.img fdeb5b2771785dc412227904127e1cae 6e7bf3ef4e53efea9a7b0446f498545e8dc517dc dd3a609b7beb35fb2527 История поисков.html e4da6bd811eb3b5adc4ec29fa859c08c e810613df0dbb5d8634e7e5321f5b14c62ccfcf6 00f433c593204eaa1facb https://global.ptsecurity.com/en/research/pt-esc-threat-intelligence/taxoff-um-you-ve-got-a-backdoor/ Page 17 of 20 FILE MD5 SHA-1 SHA-256 BK_new2.2.EXE 7815db832ef5124935d9b53445a72f49 d45c3392011070e7e827dd3f8d6797725384b1b3 f699c309f0d2547a85f66 DCIM.lnk 468f4b71eac65391d3d59466e21ec379 9a083844696dd8ccce9a6f11d3a9f1227ea639ba 93b07ba651fb6dbebaaa Trinper drive.google.com 463d8f6e597fc7c2acdb3f5a3bae37b6 8dfecf3417b8f2ab96a3591c93223d6802690fe3 2a0c6a66774cc535f51e PhotoScreenSaver.scr 19354fc1fb24d2eb08de0d46d464b16b 62e27a7e392a48d6cf14040c6fe59dabb8df44a7 6d4fac9e4c36face9e0d0 SearchApps.exe 62739a86a227ad89fa6c57f5c2335220 f5815561dfc63ad12f96a3e86e0f40cd39622373 7e82b3f1be69d34684a4 DotNet35.exe f590d65dce86589b0e0d507cfeef9f68 c3012a66acaea8801446ee61f8213a663eb7a76a e93c1a0696b59a58e244 Network indicators 185.158.248.91 193.37.215.111 server.1cscan.net usfna.global.ssl.fastly.net usfnb.global.ssl.fastly.net usfnc.global.ssl.fastly.net cfn.global.ssl.fastly.net fna.global.ssl.fastly.net fnb.global.ssl.fastly.net consult-asset-feed.global.ssl.fastly.net consult-vendor-free.global.ssl.fastly.net consult-zero-ads.global.ssl.fastly.net File signatures rule PTESC_apt_win_ZZ_TaxOff__Backdoor__Trinper{ strings: $s1 = "Task" $s2 = "TaskCd" $s3 = "TaskCmd" $s4 = "TaskExec" $s5 = "TaskGetRunningTasks" $s6 = "TaskInject" $s7 = "TaskDie" $s8 = "TaskJobConf" $s9 = "TaskKillTask" $s10 = "TaskReadFile" $code1 = {E8 ?? ?? ?? ?? 44 38 60 ?? 75 ?? 66 39 58 ?? 72 ?? 48 8B 40 ?? 8B 08 EB ??} $code2 = {48 89 4C 24 ?? 48 8B 44 24 ?? 0F B6 40 ?? 85 C0 75 ?? 48 8B 44 24 ?? 0F B7 40 ?? 83 F8 ?? 7D ?? 33 C0 EB condition: ((uint16(0) == 0x5a4d) and (all of($s*)) and (any of($code*))) } MITRE TTPs Initial Access T1566.002 Phishing: Spearphishing Link TaxOff used phishing emails with links to malicious files Execution T1204.002 User Execution: Malicious File TaxOff used bait files to run the Trinper backdoor https://global.ptsecurity.com/en/research/pt-esc-threat-intelligence/taxoff-um-you-ve-got-a-backdoor/ Page 18 of 20 Initial Access Defense Evasion T1055.012 Process Injection: Process Hollowing TaxOff used the Trinper backdoor to inject code into processes Credential Access T1187 Forced Authentication TaxOff used a false authorization form T1056.001 Input Capture: Keylogging TaxOff used the Trinper backdoor to intercept keystrokes Discovery T1083 File and Directory Discovery TaxOff used the Trinper backdoor to collect file system information Collection T1115 Clipboard Data TaxOff used the Trinper backdoor to access the clipboard T1056.001 Input Capture: Keylogging TaxOff used the Trinper backdoor to intercept keystrokes Command And Control T1071 Application Layer Protocol TaxOff used http (https) to connect the Trinper backdoor to C2 T1132.001 Data Encoding: Standard Encoding TaxOff used the Trinper backdoor to encode received information using Base64 T1573.001 Encrypted Channel: Symmetric Cryptography TaxOff used the Trinper backdoor to encrypt sent information using AES-256 T1573.002 Encrypted Channel: Asymmetric Cryptography: TaxOff used the Trinper backdoor to encrypt sent information using RSA T1090.004 Proxy: Domain Fronting TaxOff used domain fronting to communicate with the Trinper backdoor Exfiltration T1020 Automated Exfiltration TaxOff used the Trinper backdoor to automatically exfiltrate results from executing commands T1041 Exfiltration Over C2 Channel TaxOff used the Trinper backdoor to exfiltrate data to C2 Positive Technologies product verdicts PT Sandbox apt_win_ZZ_TaxOff__Backdoor__Trinper MaxPatrol SIEM Suspicious_Connection RunAs_System_or_External_tools Run_Executable_File_without_Meta Suspicious_Directory_For_Process PT NAD BACKDOOR [PTsecurity] Trinper (APT TaxOff) sid: 10012123 https://global.ptsecurity.com/en/research/pt-esc-threat-intelligence/taxoff-um-you-ve-got-a-backdoor/ Page 19 of 20 SUSPICIOUS [PTsecurity] Suspicious HTTP header Trinper (APT TaxOff) sid: 10012124, 10012125 Share link Source: https://global.ptsecurity.com/en/research/pt-esc-threat-intelligence/taxoff-um-you-ve-got-a-backdoor/ https://global.ptsecurity.com/en/research/pt-esc-threat-intelligence/taxoff-um-you-ve-got-a-backdoor/ Page 20 of 20