{
	"id": "c7fa3694-2a60-4a03-ba4f-cf4fa9405f85",
	"created_at": "2026-04-06T00:22:14.513745Z",
	"updated_at": "2026-04-10T03:24:23.723147Z",
	"deleted_at": null,
	"sha1_hash": "98798e5bf28a71539ea4fd3f70c4945dd013fe8a",
	"title": "Pikabot Updates | ThreatLabz",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 131845,
	"plain_text": "Pikabot Updates | ThreatLabz\r\nBy Nikolaos Pantazopoulos\r\nPublished: 2024-02-12 · Archived: 2026-04-05 13:36:50 UTC\r\nTechnical Analysis\r\nAs covered in our previous technical analysis of Pikabot, the malware consists of two components: a loader and a\r\ncore module. The core module is responsible for executing commands and injecting payloads from a command-and-control server. The malware uses a code injector to decrypt and inject the core module. It employs various\r\nanti-analysis techniques and string obfuscation. Pikabot uses similar distribution methods, campaigns, and\r\nbehaviors as Qakbot. The malware acts as a backdoor, allowing the attacker to control the infected system and\r\ndistribute other malicious payloads such as Cobalt Strike.\r\nIn the following sections, we will describe the latest Pikabot variant, including its capabilities and notable changes\r\ncompared to previous versions. The analysis was performed on Pikabot binaries with version 1.8.32.\r\nAnti-analysis techniques\r\nAs with previous versions of Pikabot, this variant employs a series of different anti-analysis techniques to make\r\nthe analysis more time-consuming. It should be noted that none of the methods below presents any significant\r\nadvanced capabilities. Furthermore, Pikabot used a series of more advanced detection features in its loader\r\ncomponent in previous versions of the malware.\r\nStrings encryption\r\nThe most notable change is the string obfuscation. In previous versions of Pikabot, each string was obfuscated by\r\ncombining the RC4 algorithm with AES-CBC. This method was highly effective in preventing analysis,\r\nparticularly when it came to automated configuration extraction. To successfully analyze Pikabot, an analyst\r\nwould need to detect not only the encrypted string but also its unique RC4 key. Additionally, they would need to\r\nextract the AES key and initialization vector, which are unique to each Pikabot payload.It should be noted that the\r\napproach the Pikabot malware developers followed is similar to the ADVobfuscator.\r\nIn the latest version of Pikabot, the majority of the strings are either constructed by retrieving each character and\r\npushing it onto the stack (Figure 1) or, in some rare cases, a few strings are still encrypted using the RC4\r\nalgorithm only.\r\nhttps://www.zscaler.com/blogs/security-research/d-evolution-pikabot\r\nPage 1 of 9\n\nFigure 1. String stack construction\r\nJunk instructions\r\nThis anti-analysis technique was also implemented in previous versions of Pikabot. Pikabot inserts junk code\r\nbetween valid instructions. The junk code is either inlined in the function or a call is made to a function, which\r\ncontains the junk code (Figure 2).\r\nhttps://www.zscaler.com/blogs/security-research/d-evolution-pikabot\r\nPage 2 of 9\n\nFigure 2. Junk code\r\nAnti-debug methods\r\nPikabot uses two methods to detect a debugging session. They are:\r\nReading the BeingDebugged flag from the PEB (Process Environment Block).\r\nCalling the Microsoft Windows API function CheckRemoteDebuggerPresent.\r\nPikabot constantly performs the debugging checks above in certain parts of its code. For example, when it\r\n(en/de)codes network data or when it makes a request to receive a network command.\r\nAnti-sandbox evasion\r\nIn addition to the anti-debugging checks above, Pikabot uses the following methods to evade security products\r\nand sandboxes:\r\nPikabot utilizes native Windows API calls.\r\nPikabot delays code execution at different stages of its code. The timer is randomly generated each time.\r\nPikabot dynamically resolves all required Windows API functions via API hashing.\r\nA Python representation of the algorithm is available below.\r\napi_name = b\"\"\r\nchecksum = 0x113B\r\nfor c in api_name:\r\n if c \u003e 0x60:\r\n c -= 0x20\r\n checksum = (c + (0x21 * checksum)) \u0026 0xffffffff\r\nprint(hex(checksum))\r\nLanguage detection\r\nIdentical to previous versions, Pikabot stops execution if the operating system's language is any of the following:\r\nRussian (Russia)\r\nUkrainian (Ukraine)\r\nThis is likely an indication that the threat actors behind Pikabot are Russian-speaking and may reside in Ukraine\r\nand/or Russia. The language check reduces the chance of law enforcement action and potential criminal\r\nprosecution in those regions.\r\nBot initialization phase\r\nUnlike previous versions, this version of Pikabot stores all settings and information in a single structure at a global\r\naddress (similar to Qakbot). The analyzed structure is shown below. For brevity, we redacted non-important items\r\nhttps://www.zscaler.com/blogs/security-research/d-evolution-pikabot\r\nPage 3 of 9\n\nof the structure (such as Windows API names).\r\nstruct bot_structure\r\n{\r\n void *host_info;\r\n WINHTTPAPI winhttp_session_handle;\r\n bool bot_error_init_flag;\r\n FARPROC LdrLoadDll;\r\n FARPROC LdrGetProcedureAddress;\r\n FARPROC RtlAllocateHeap;\r\n FARPROC RtlReAllocateHeap;\r\n FARPROC RtlFreeHeap;\r\n FARPROC RtlDecompressBuffer;\r\n FARPROC RtlGetVersion;\r\n FARPROC RtlRandomEx;\r\n ---redacted—\r\n wchar_t* bot_id;\r\n bool registered_flag;\r\n int process_pid;\r\n int process_thread_id;\r\n int* unknown_unused_1;\r\n unsigned short os_arch;\r\n unsigned short dlls_apis_loaded_flag;\r\n int unknown_unused_2;\r\n unsigned char* host_rc4_key;\r\n int number_of_swap_rounds;\r\n int beacon_time_ms;\r\n int delay_time_ms; // Used only during the initialization phase of Pikabot.\r\n int delay_seed_mul;\r\n wchar_t* bot_version;\r\n wchar_t* campaign_tag;\r\n wchar_t* unknown_registry_key_name;\r\n cncs_info* active_cnc_info;\r\n cncs_info* cncs_list;\r\n int num_of_cncs;\r\n int unknown_unused_3;\r\n int max_cnc_attempts;\r\n wchar_t* user_agent;\r\n void* uris_array;\r\n void* request_headers_array;\r\n TEB* thread_environment_block;\r\n};\r\nstruct cncs_info\r\n{\r\n wchar_t* cnc;\r\n int cnc_port;\r\nhttps://www.zscaler.com/blogs/security-research/d-evolution-pikabot\r\nPage 4 of 9\n\nint http_connection_settings; // If set to 1 then server’s certificate validation is ignored and sets the flag\r\n int connection_attempts;\r\n bool is_cnc_unavailable;\r\n cncs_info* next_cnc_ptr;\r\n};\r\nBot configuration\r\nThe latest version of Pikabot stores its entire configuration in plaintext in one address. This is a significant\r\ndrawback since in previous versions, Pikabot decrypted each required element at runtime and only when required.\r\nIn addition, many of the configuration elements (e.g. command-and-control URIs) were randomized. \r\nANALYST NOTE: Despite their randomization, all configuration elements were valid on the server-side. If a bot\r\nsent incorrect information, then it would get rejected/banned by the command-and-control server.\r\nThe configuration structure is the following:\r\nstruct configuration\r\n{\r\n int number_of_swap_rounds_number_of_bytes_to_read_from_end; // During the bot initialization process, this mem\r\n size_t len_remaining_structure; // Size of the remaining structure's data minus the last element\r\n wchar_t* bot_minor_version; // E.g. 32-beta. In some samples, this member contains both the major and minor ve\r\n size_t len_campaign_name;\r\n wchar_t* campaign_name;\r\n size_t len_unknown_registry_key_name;\r\n wchar_t* unknown_registry_key_name; // Used only in the network command 0x246F.\r\n size_t len_user_agent;\r\n wchar_t* user_agent;\r\n size_t number_of_http_headers;\r\n wchar_string request_headers[number_of_http_headers];\r\n int number_of_cnc_uris;\r\n wchar_string cnc_uris[number_of_cnc_uris];\r\n int number_of_cncs;\r\n cnc cns[number_of_cns];\r\n int beacon_time_ms;\r\n int delay_time_ms;\r\n int delay_seed_mul; // Multiplies this value with the calculated value of the operation - delay_seed_mul * 100\r\n int maximum_cnc_connection_attempts;\r\n size_t len_bot_version // major version + minor version\r\n wchar_t* major_version; // 1.8.\r\n int len_remaining_bytes_to_read; // Added to the first member and shows how many more bytes to read right afte\r\n};\r\nstruct wchar_string\r\n{\r\nhttps://www.zscaler.com/blogs/security-research/d-evolution-pikabot\r\nPage 5 of 9\n\nsize_t length;\r\n wchar_t* wstring;\r\n};\r\nstruct cnc\r\n{\r\n size_t len_cnc;\r\n wchar_t* cnc;\r\n int cnc_port;\r\n int connection_attempts;\r\n bool http_connection_settings;\r\n};\r\nOnce Pikabot parses the plaintext configuration, it erases it by setting all bytes to zero. We assess that this is an\r\nanti-dumping method to avoid automating the extraction of the configuration.\r\nLastly, Pikabot loads any remaining required Windows API functions and generates a bot identifier for the\r\ncompromised host. The algorithm is similar to previous versions and can be reproduced with the following Python\r\ncode.\r\ndef checksum(input: int) -\u003e int:\r\n return (0x10E1 * input + 0x1538) \u0026 0xffffffff\r\ndef generate_bot_id_set_1(host_info: bytes, volume_serial_number: int) -\u003e int:\r\n for current_character in host_info.lower():\r\n volume_serial_number *= 5\r\n volume_serial_number += current_character\r\n bot_id_part_1 = checksum(volume_serial_number \u0026 0xffffffff)\r\n return bot_id_part_1\r\ndef generate_bot_id_set_2(volume_serial_number: int) -\u003e int:\r\n bot_id_part_2 = checksum(volume_serial_number)\r\n bot_id_part_2 = checksum(bot_id_part_2)\r\n return bot_id_part_2\r\n \r\ndef generate_bot_id_set_3(bot_id_part_2: int) -\u003e int:\r\n out = []\r\n for i in range(8):\r\n bot_id_part_2 = checksum(bot_id_part_2)\r\n out.append(bot_id_part_2 \u0026 0xff)\r\n out = bytes(out[-4:])\r\n return int.from_bytes(out, byteorder='little')\r\n \r\nhost_info = b\"username|hostname\"\r\nvolume_serial_number = int(\"\",16)\r\nbot_id_part_1 = generate_bot_id_set_1(host_info, volume_serial_number)\r\nhttps://www.zscaler.com/blogs/security-research/d-evolution-pikabot\r\nPage 6 of 9\n\nbot_id_part_2 = generate_bot_id_set_2(volume_serial_number)\r\nbot_id_part_3 = generate_bot_id_set_3(bot_id_part_2)\r\nbot_id = f\"{bot_id_part_1:07X}{bot_id_part_2 \u0026 0xffff:09X}{bot_id_part_3}\"\r\nANALYST NOTE: In some samples, Pikabot does not read the volume serial number due to a bug in their code\r\nthat causes a failure when calling GetVolumeInformationW.\r\nNetwork communications\r\nPikabot contacts the command-and-control server to request and receive network commands. In this version, the\r\nnetwork protocol has considerably changed. Pikabot starts by registering the compromised host to its server. \r\nFirst, Pikabot collects information from the compromised host, such as:\r\nMonitor’s display settings\r\nWindows version\r\nHostname/username and operating system’s memory size\r\nBeacon and delay settings\r\nProcess information such as the process ID, parent process ID and number of threads (see the description\r\nof network command 0x985 for a comprehensive list).\r\nBot’s version and campaign name\r\nName of the domain controller\r\nThen Pikabot appends the following information to the registration packet:\r\n32-bytes network RC4 key (unique per host), which remains the same for the session. In previous versions,\r\nPikabot was using AES-CBC with a random key/IV per request.\r\nUnknown registry key name. We observed it used only in the network command with ID 0x246F.\r\nNumber of swap rounds used for encoding the data. This remains the same for the rest of the session.\r\nNext, Pikabot encrypts the data using the RC4 algorithm, encodes the encrypted output, picks a random URI from\r\nits list, and sends the data with a POST request to the command-and-control server.\r\nThe encoding involves bytes swapping for N times, where N is a randomly generated number in the range 0-25.\r\nANALYST NOTE: Despite the fact that a round number is set in the configuration (see the configuration\r\nstructure), this value is ignored and Pikabot replaces it with a random value. Moreover, Pikabot has completely\r\nremoved the JSON format in its network packets and inserts everything in a raw format.\r\nIf the bot registration is successful, Pikabot starts an infinite loop to request and execute commands. \r\nEach incoming network command (with the exception of network command with ID 0x164) has a task ID that is\r\nplaced at the start of the (decrypted) packet as a QWORD value. In Table 1 below, we list the identified network\r\ncommands along with a description of their functionality.\r\nhttps://www.zscaler.com/blogs/security-research/d-evolution-pikabot\r\nPage 7 of 9\n\nCommand\r\nID\r\nDescription\r\n0x164\r\nRequests command from command-and-control server. The packet includes the command ID,\r\nsize of bot ID, and the bot ID. The server replies with the same command ID if there is no\r\nnetwork command for the bot to execute.\r\n0x555 Reports the output of the executed network command to the command-and-control server.\r\n0x1291 Registers the bot. An unknown integer value (0x1687) is appended in the packet at offset 8.\r\n0x1FED Updates beacon time.\r\n0x1A5A Terminates/kills the bot.\r\n0x2672 Not implemented\r\n0x246F\r\nWrites a file to disk and adds registry data using the value name specified in the configuration\r\n(unknown_registry_key_name).\r\n0xACB\r\nExecutes the system command and sends back the output. Includes the error code 0x1B3 if\r\nthere is no output.\r\n0x36C\r\nInjects the code of a downloaded PE file. The target process information is specified in the\r\nnetwork packet.\r\n0x792\r\nInjects the code of a downloaded shellcode. The target process information is specified in the\r\nnetwork packet.\r\n0x359\r\nExecutes system command and sends back the output.\r\nNote: Same as 0xACB but does not send the error code.\r\n0x3A6\r\nExecutes system command and sends back the output.\r\nNote: Same as 0xACB but does not send the error code.\r\n0x240\r\nExecutes system command and sends back the output.\r\nNote: Same as 0xACB but does not send the error code.\r\n0x985\r\nCollects processes’ information. These are:\r\nExecutable's filename\r\nProcess ID\r\nBoolean flag, which indicates if it is a Pikabot process.\r\nhttps://www.zscaler.com/blogs/security-research/d-evolution-pikabot\r\nPage 8 of 9\n\nCommand\r\nID\r\nDescription\r\nBoolean flag, which indicates if Pikabot can access the process with all possible access\r\nrights.\r\nNumber of threads\r\nBase priority of threads\r\nProcess architecture\r\nParent process ID\r\n0x982 Not implemented\r\nTable 1. Pikabot Network Commands\r\nExplore more Zscaler blogs\r\nSource: https://www.zscaler.com/blogs/security-research/d-evolution-pikabot\r\nhttps://www.zscaler.com/blogs/security-research/d-evolution-pikabot\r\nPage 9 of 9",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE",
		"Malpedia"
	],
	"references": [
		"https://www.zscaler.com/blogs/security-research/d-evolution-pikabot"
	],
	"report_names": [
		"d-evolution-pikabot"
	],
	"threat_actors": [
		{
			"id": "610a7295-3139-4f34-8cec-b3da40add480",
			"created_at": "2023-01-06T13:46:38.608142Z",
			"updated_at": "2026-04-10T02:00:03.03764Z",
			"deleted_at": null,
			"main_name": "Cobalt",
			"aliases": [
				"Cobalt Group",
				"Cobalt Gang",
				"GOLD KINGSWOOD",
				"COBALT SPIDER",
				"G0080",
				"Mule Libra"
			],
			"source_name": "MISPGALAXY:Cobalt",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		}
	],
	"ts_created_at": 1775434934,
	"ts_updated_at": 1775791463,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/98798e5bf28a71539ea4fd3f70c4945dd013fe8a.pdf",
		"text": "https://archive.orkl.eu/98798e5bf28a71539ea4fd3f70c4945dd013fe8a.txt",
		"img": "https://archive.orkl.eu/98798e5bf28a71539ea4fd3f70c4945dd013fe8a.jpg"
	}
}