It’s a BEE! It’s a… no, it’s ShadowPad.

asuna amawaka
Published in
8 min readNov 19, 2021


ShadowPad, a malware name that is familiar to many, became widely known since 2017 through its participation in a series of supply-chain attacks in CCleaner, NetSarang and ASUS. There has been a lot of good work describing how clusters of activities by different threat actors (APT41, Fishmonger, Tonto Team, RedFoxTrot, BackdoorDiplomacy are all the “big names” we know) are linked by ShadowPad. I thought to join in the fun of reverse engineering one of the variant of ShadowPad that I found from VirusTotal. So let’s begin!

Side-note: Kaspersky calls this variant “ShadowShredder” [1]. This variant has also been documented by PTSecurity [2] in Jan 2021 and was also recently in TeamT5’s presentation in VB2021localhost conference [3].

The malware trinity (DLL load-order attack) analyzed in this post:

Legitimate EXE, bdreinit.exe (Bitdefender’s Crash Handler) — SHA256: 386EB7AA33C76CE671D6685F79512597F1FAB28EA46C8EC7D89E58340081E2BD

Malicious DLL, log.dll — SHA256: 8D1A5381492FE175C3C8263B6B81FD99AACE9E2506881903D502336A55352FEF

Encrypted Payload, log.dll.dat — SHA256: 0371FC2A7CC73665971335FC23F38DF2C82558961AD9FC2E984648C9415D8C4E

I found these files separately, so they may not be originally intended as a package. I managed to piece them together based on their filenames, and they work fine as a set. These files happened to have debugging strings included, which makes the analysis slightly more pleasing to follow.

Let’s start with observations from dynamic analysis.

Here’s what the log looked like in the debugger upon execution without breakpoints:

logged by x32dbg

The same debugging information is also written into a file C:\ProgramData\bee.log.

more of bee.log

It seems that I’ve picked up a test sample. The C2 domain configured within is a subdomain of wikimedia[.]vip, which has been associated with Funnydll and other ShadowPad samples.

I’m curious about the author’s choice of name — What does “BEE” stand for?

I extracted all the interesting filename strings, for reference if anyone is interested at guessing the full suite of capabilities within ShadowPad:

source filenames

And here’s the list of debugging messages found in memory:

debugging messages

The processes created are as such:

logged with APIMonitor

“Test.exe” is a copy of bdreinit.exe and is part of the persistency mechanism of the malware — The EXE and DLL are started via Services, and the encrypted payload is loaded from Registry. I supposed if service installation fails, then the malware shall persist through Run regkey.

The EXE and DLL are copied to C:\ProgramData\DRM\Test (specified by configuration), while the DAT payload file is deleted after the first execution. For subsequent executions, the payload is read from Registry.

The payload is re-encrypted by the malware before it is being written into the Registry for persistency. For some reasons, the initialization value used in the re-encryption algorithm is the compilation timestamp of the malicious DLL file, while the initialization value used in the original encryption algorithm is the first 4 bytes within the dat file, hence the encrypted data seen in the registry differs from the log.dll.dat file.

encrypted payload in registry
encrypted payload file

Here is the python script I used to decrypt the payload file log.dll.dat:

And here is the script for decrypting the payload from registry:

The malware has keystroke logging capabilities, and the keystroke log file is encrypted and saved to a random-looking filepath in %PROGRAMDATA% like this:


Decryption of the keystroke log file is done in the same manner as the configuration data (I’ll talk about how to get this in awhile):

Anti-reverse engineering obfuscation

Now, here comes details on how I handled the reverse engineering. This is slightly harder than usual, because of the obfuscation technique used in the malware. The original malware binary is “shredded” into pieces, with 1 instruction per piece, and put back together with a “jumper” function as glue. Oh, and there are some junk “cmp” followed by “jb” instructions just to make your eyes hurt.

Let’s see what it looks like in IDA.

Up till this point at address 0x100011D6, everything is normal.

disassembly at beginning of malware logic

Then came this jmp:

Followed by another call:

If we follow the instructions starting from 0x10004465, you will see something like this:

within “jumper” function

It looks terribly complicated, but all it does is to read the next dword after the call and add it to original intended return address.

illustrate jumper (1)

And these “bits and pieces” of instructions occur from here onwards, throughout the whole malware. The “real” instruction that is part of the malware’s logic is the single instruction before the call to jumper. Trying to recover these instructions makes me feel like I am picking up the pieces from the shredding machine and gluing them back.

illustrate jumper (2)
illustrate jumper (3)
illustrate jumper (4)
illustrate jumper (5)
illustrate jumper (6)

After doing away with the jumper calls, here’s a snippet of recovered “shreds” of instructions:

I greyed out the junk “cmp” and “jb” instructions. Up till this point, the process of recovery is very manual, with abit of help from this IDAPython script I wrote:

The python script is not perfect — at some point it will fail to work as intended, and I will have to apply the script again at the position where it failed. Furthermore, this manner of “advancing” through the disassembly in IDA didn’t feel very efficient. As it turns out, using the “trace” feature in the debugger produced the expected results with ease.

trace in x32dbg
traced log

After some cleaning up, here’s the code logic that decrypts the payload from registry, for a taste of what we can see after doing away with the jumper calls and junk cmp-jbs.

traced instructions for decrypting payload from registry

Malware Configuration

In order to recover the configuration data, it helps to know what it looks like from older variants of ShadowPad (without the shredding obfuscation) — so that we can recognize it in memory. I used APIMonitor to look out for memory copies, because I knew that was what ShadowPad will do with its configuration.

APIMonitor log of memcpy

One visual characteristic of the configuration data is that it will start and end with many zeroes, and it is not very long (the part that looks like encrypted data is approx. ~400 bytes long). In this particular sample, the memory size expected for the configuration is 2198 bytes long. This can perhaps be a helping value to look out for the memcpy call dealing with the configuration.

Decryption of the configuration data uses the exact same algorithm as what is used to decrypt the keystroke log file. Earlier I shared the standalone python script used to decrypt the keystroke log file. Here’s the IDAPython version to handle the configuration data in IDA:

And here’s the decrypted configuration:

ShadowPad configuration decrypted

Hmmm~ it looks like someone was testing his BEE some 3 months ago, based on that timestamp in the configuration.

Well then, that is all I have. Come chat with me on Twitter if you have any idea how I can automate this analysis; I seem to have done most stuff in the painful way :|

Network IOC:


Host IOCs:

log.dll — SHA256: 8D1A5381492FE175C3C8263B6B81FD99AACE9E2506881903D502336A55352FEF

log.dll.dat — SHA256: 0371FC2A7CC73665971335FC23F38DF2C82558961AD9FC2E984648C9415D8C4E