Chinese APT31 Droppers Targeting Government Organizations Analysis

The Chinese nation-state group APT31 also known as ZIRCONIUM, JUDGMENT PANDA, and BRONZE VINEWOOD carried out offensive cyber operations against targets in Russia, Belarus, and others between January and July of 2021. This attack included malware in the form of droppers that lead to the deployment of backdoors. The droppers rely on DLL-sideloading to load the malicious second-stage payload. APT31 is a Chinese-backed nation-state APT group that provides the Chinese government and state-owned enterprises with information to aid in political, economic, and military advantages. The group has a history of targeting government-related organizations.

Key Findings

  • The first stage dropper includes two embedded Windows PE files that are written to disk on execution
  • The two dropped files work together to execute the second stage payload (file2 that is dropped) via DLL-sideloading
  • The legitimate file (file1) loads and calls the export function _initterm_e from the malicious DLL library (file2)
  • The second stage payload services the purpose of using the Windows WinInet library to download and execute a third stage payload from an embedded C2 server
  • The dropped payload that get’s loaded through DLL sideloading is responsible for performing the following actions:
    • Maintain persistence on the target system via the Windows Registry
    • Download and write a payload to disk from an embedded C2 Server


First Stage Dropper Payload

The dropper analyzed in this post includes two embedded files within its.rdata section, these two embedded files are dropped to disk using standard Windows API functions such as CreateFileA and WriteFile. Of the two embedded files, one is a legitimate instance of ssvagent.exe which is an update agent for Java, the other is a malicious second-stage payload that mimics the legitimate MSVCR100.dll library that ssvagent.exe would normally load.

Looking at the dropper payload in DIE indicates that the PE is not packed, the PE file is a Microsoft Visual C/C++ compiled binary, compiled for 32-bit, and was compiled on February 18th, 2021. Based on the original timeline, this payload was compiled and used during the later stages of the offensive operation. The PE file imports four libraries including wtsapi32.dll, kernel32.dll, user32.dll, and shell32.dll. From the function imports there are a few semi-suspicious functions such as WTSGetActiveConsoleSessionId, WTSQueryUserToken, CreateProcessA, GetCurrentProcessId, and ShellExecuteW.


On execution, the dropper first checks for the existence of the second stage payload on disk, it checks for the legitimate application ssvvagent.exe within C:\ProgramData\Apacha\ssvagent.exe using FindFirstFileA. If the file does not exist it also checks for the directory that the file would be dropped to at C:\ProgramData\Apacha, if either of these doesn’t exist, it will create them. If the dropped files don’t exist on disk, the dropper uses CreateFileA and WriteFile to locate and write out the embedded files to their respective locations on disk.


If the files do exist on disk the dropper will execute the second stage payload by calling CreateProcessA with the location of the legitimate (dropped) ssvagent.exe file. When this new process is executed it creates a new thread with CreateThread that results in a fake Windows message box popping up stating that there was some kind of installation error. If the new process fails to be created, it calls the TerminateCurrentProcess function which gets the current process and then calls TerminateProcess.


After all is successful, the current process is terminated.


When WriteFile is called twice, the following “files” are dropped to disk from within the droppers .rdata section. There are two embedded executables (file1 = the legitimate application, file2 = loaded by file1 through DLL-sideloading)


Searching HXD allows you to locate and then dump out the embedded files without needing to execute or debug the dropper.


Located at offset 10FCE is the malicious DLL file that gets dropped to disk


Located at offset 13DCE is the legitimate application that is responsible for loading the malicious second stage payload through DLL-sideloading.


Debugging the dropped and setting a breakpoint on WriteFile allows you to capture the embedded PE files written to disk.


Inspecting the application ssvagent.exe that was dropped to disk reveals that within its function imports, it requests the _initterm_a function from within msvcr100.dll. In this case, msvcr100.dll was replaced with a malicious second-stage payload. But this gives us the first clue on how to locate the main malicious section of code within msvcr.dll that was dropped along with ssvagent.exe from the dropper payload.


Second Stage Payload

The dropped second-stage payload from the original dropper is responsible for downloading the final stage backdoor from an embedded C2 server. When the backdoor payload is downloaded it is again loaded using DLL sideloading using the same running process.

When setting up malicious DLLs for replacing a legitimate DLL during DLL sideloading you want to set up the proper export functions so the main application that loads it can execute your malicious code. Typically you will see a malicious DLL that includes all of the same export names that the legitimate version would have, but instead of containing the legitimate code in those functions, it replacing the code with calls to ExitProcess or similar. In this case, the exported function that gets executed first calls what ends up being the malicious payload, and then there is a call to ExitProcess.


Through the first function call made (observed above) the main section of the malicious second-stage payload is executed. For seemingly unknown reasons the payload first decides to enumerate all of the running processes using CreateToolhelp32Snapshot with TH32CS_SNAPPROCES as the first flags parameter. The payload doesn’t seem to store or use the information returned from enumerating the running processes on the system.

Next, the payload checks to see if ssvagent.dll exists on disk within the same directory that this second stage payload was dropped to. The file ssvagent.dll is the main payload that this stager is responsible for downloading from the C2 server. If the file is found on disk the payload continues to create a new mutex with CreateMutex called “ssvagent”. After this it checks the last error code against the code for ERROR_ALREADY_EXISTS to see if it succeeded, if it doesn’t then the process exists with ExistProcess Then the process will sleep for 60000 milliseconds before maintaining persistence on the system.


Before downloading and executing the final stage backdoor, this stager attempts to maintain persistence on the victim’s system via the Registry Run keys. It performs this through the usage of the Windows API functions RegOpenKeyExA, RegGetValueA, and RegSetValueExA to check to see if the persistence value is already set, if not it then sets the value of ssvagent to execute the C:\\ProgramData\\Apacha\\ssvagent.exe same parent process that was originally executed to load this stage of the dropper.


Within the DownloadFromC2Server function, setting a breakpoint on the first call to InternetCrackUrlA reveals the first parameter pszUrl which is equal to the C2 server that the payload will attempt to download a file from. When reaching out to the C2 server, the URL is encoded within the binary via XOR.


Prior to making an HTTP request to the C2 URL, it’s first decoded on execution via XOR, the XOR key is 0x9, the data reference DAT_10003009 can be followed via the encoded version of the URL. For decoding, the encoded version of the URL is looped through a basic algorithm.


For outbound C2 requests the payload makes use of standard WinInet API functions such as HttpOpenRequestA and HttpSendRequestA, the DownloadFromC2Server function issues an HTTP GET request to the XOR decoded URL, within the HTTP response is another payload that is processed via the call to InternetReadFile on line 123, the file returned in the HTTP response is written to disk via the call to CreateFileA and WriteFile.


On-disk the downloaded payload is eventually loaded into the running process by calling CreateProcessW, on disk ssvagent.exe will load ssvagent.dll at runtime again through DLL side-loading.


Following the reference to the URL parameter in the InternetCrackUrlA function call shows it’s the same location that the XOR decoding routine decodes.


Using the XOR key, we can extract the hex version of the XOR encoded bytes from within the binary and then run it through CyberChef to manually decode the C2 URL as well.


After downloading the final stage backdoor from the decoded C2 URL, the malware executes it through DLL sideloading by using the same parent process in the same way this stager was executed. It downloads the third stage backdoor and writes it to disk under the name ssvagent.dll which is another DLL file that ssvagent.exe loads when executed. After it’s downloaded from the remote URL and written to disk, the call to CreateProcessW executes it.


Other First Stage Dropper Variations

Throughout this offensive campaign, different countries were targeted, and different countries received slightly different variants in droppers. Another example of a dropper that APT31 used made use of embedded resources that when the dropper was executed would drop to disk and then execute the second-stage payload. Below is one example of an embedded resource-based dropper.

Inspecting the variation 3F5EA95A5076B473CF8218170E820784 in PeStudio reveals that it includes two embedded DLL based resources. When malware accesses resources via the Windows API they are commonly referenced by name, here the resource IDs are 101 and 102. Both resources are detected as 32-bit executable files.


This dropper variation includes the typical API calls such as FindResourceW, SizeofResource, and LoadResource to access and then eventually write out the embedded resource files to disk with CreateFileA and WriteFile

There are two function calls within the dropper, one to writes out the legitimate application (Symantec.exe) and another that writes a second DLL file that is not malicious, but is believed to be something required, and without loading it, the legitimate executable might not properly function.

Below is an image of the function responsible for writing the legitimate application to C:\ProgramData\Symentec\Symantec.exe, note that the directory it writes out the application to seems to be misspelled (using “a” instead of “a”), it’s safe to assume this is done to prevent overwriting a legitimate installation of the real Symantec.exe utility.


Below is an image of the function that handles dropping the second embedded DLL payload to the same directory as the legitimate application that was previously dropped. Here we can see that the payload is named jli.dll which would replace whatever legitimate DLL Symantec.exe would normally load. According to an article from, Jli.dll is a DLL developed by Oracle for providing functionality related to the JAVA platform. This DLL doesn’t include anything of interest, from the functions it does export, it just includes calls to ExitProcess.


And just like the other dropper variants, first, the directory that the embedded files are dropped to are checked for their existence, if they don’t exist, the directory is created with a call to CreateDirectoryA (on line 24). After performing directory checks, the embedded resources are written to disk (line 29, 30), and then the legitimate application is executed, which leads to the malicious DLL payload being executed.

On lines 31 and 32, the malicious second-stage DLL payload is written to disk, unlike the embedded resources, which is writing data (DLL PE file) from the droppers .data section.


Debugging the dropper and setting a breakpoint on the ShellExecuteW function call allows us to retrieve all of the embedded DLL files prior to them being used. As seen in the C:\ProgramData\Symentec\ directory, the dropped MSVCR100.dll is the payload to be loaded by the legitimate Symantec.exe application.


C2 domain DNS resolution and response


Rules And Indicators

MD5 HashDescriptionCompilation TimestampFilename
626270D5BF16EB2C4DDA2D9F6E0C4EF9APT31 initial dropper responsible for dropping and loading embedded executables via DLL- sideloading2021-02-19 02:38:24 
98EEC12BB0342A0AB6DBC6CEA436D4ADLegitimate file used for DLL-sideloading a second stage dropper2017-03-15 08:47:50ssvagent.exe
8CEFAA146178F5C3A297A7895CD3D1FCAPT31 second stage dropper responsible for downloading and loading the final backdoor2021-02-18 06:46:37MSVCR100.dll

Rule 1

import "pe"

rule apt31_dropper_first_stage
		date = "8/15/2021"
		hash = "626270D5BF16EB2C4DDA2D9F6E0C4EF9"
		description = "APT31 intial dropper responsible for droppering and loading embedded executables via DLL sideloading"
		$str1 = "C:\\ProgramData\\Apacha" fullword ascii
		$str2 = "C:\\ProgramData\\Apacha\\ssvagent.exe" fullword ascii
		$str3 =	"C:\\ProgramData\\Apacha\\MSVCR100.dll" fullword ascii
		all of them and ( uint16(0) == 0x5a4d and filesize < 300KB )
		and uint16(0x10F80) == 0x5a4d and uint16(0x13D80) == 0x5a4d
		and pe.imports("kernel32.dll","CreateProcessA")
		and pe.imports("kernel32.dll","WTSGetActiveConsoleSessionId")


  • T1059: Command and Scripting Interpreter
  • T1106: Native API
  • T1574.001: DLL Search Order Hijacking
  • T1546.011: Application Shimming
  • T1497: Virtualization/Sandbox Evasion
  • T1055: Process Injection
  • T1027: Obfuscated Files or Information
  • T1083: File and Directory Discovery
  • T1082: System Information Discovery
  • T1095: Non-Application Layer Protocol