The WP-VCD malware for WordPress has existed for many years. It mainly spreads by injecting itself into legitimate plugins and themes after which it will spread itself on sites that offer downloads to (nulled) WordPress plugins and themes.
We noticed that during the corona-virus pandemic, the WP-VCD malware has also started injecting itself into plugins that can show statistics related to the coronavirus.
These plugins might be particularly popular at the moment which gives the maker of the WP-VCD malware an advantage.
We analyzed some samples that were downloaded from
www[dot]downloadfreethemes[dot]co. The samples taken from different themes and plugins offered on this site were all infected with this malware.
Similar sites that do the same kind of practices are:
During the analysis of multiple samples, we noticed that all themes contained a file called
class.theme-modules.php and all plugins contained a file called
class.plugin-modules.php. Both files contained the exact same code.
In plugins, the
class.plugin-modules.php file would be loaded in the main file of the plugin on the first line by injecting the following (reformatted):
<?php if (file_exists(dirname(__FILE__) . '/class.plugin-modules.php')) include_once(dirname(__FILE__) . '/class.plugin-modules.php'); ?>
In themes, the
class.theme-modules.php file would be loaded in the
functions.php file by injecting the following (reformatted):
<?php if (file_exists(dirname(__FILE__) . '/class.theme-modules.php')) include_once(dirname(__FILE__) . '/class.theme-modules.php'); ?>
Note that the domain name which the malware uses to communicate can change often, in this scenario it was set to
www[dot]arilns[dot]com. At the time of publication, it did not have a web server attached to it.
Refer to the overview of functions, constants, and variables at the bottom of the page to see what some of the variables mentioned in the execution flow do and contain.
The initial malware is only executed when it meets a few conditions: the user is an administrator and any of the following conditions:
The user is on the
themes.php pages or the
action parameter in the URL is set to
activate or the
plugin parameter is present in the URL.
If the statement in 1. is true, it will iterate through all folders in
/wp-content/themes/ and inject
functions.php if the file exists.
functions.php file does not exist in the root folder of the theme, it will go one level deeper and iterate through all sub-folders and attempt to find
functions.php yet again.
If it managed to inject the backdoor code into any file, it will send a ping request to
www[dot]arilns[dot]com/o.php with 2 query parameters:
host parameter which contains
password parameter which contains
Once the ping has been sent, whether it was successful or not, it will retrieve the path to the root directory of the WordPress site.
Then it will execute the
WP_URL_CD function which creates the
/wp-includes/wp-vcd.php backdoor containing
$GLOBALS['WP_CD_CODE'] and also injects it into
Finally, it will get the code of itself (and store that in
$file) and execute 2 calls to
preg_replace after which the only code left in the file is
<?php error_reporting(0);?> making it seem as if nothing happened.
preg_replace('!//install_code.*//install_code_end!s', '', $file) will remove most initial injection code from itself. (The
preg_replace('!<\?php\s*\?>!s', '', $file); will remove all
<?php ?> tags of which there is only white-space in-between.
$install_code is injected into all
functions.php files that are present in the
/wp-includes/themes/ directories. The flow of this code is described below.
password parameters are present in the request
($_REQUEST) and the password equals
md5($_SERVER['HTTP_HOST'] . AUTH_SALT), it can execute 2 actions:
Find the following in the code of the current file:
$tmpcontent = @file_get_contents("http://(.*)/code.php and replace the domain name with the new domain name that is present in the
newdomain request parameter.
Find the following in the code of the current file:
//$start_wp_theme_tmp([\s\S]*)//$end_wp_theme_tmp and inject PHP code supplied by the
newcode request parameter in-between the lines
If no actions were executed in step 1, it will check if the current script is not
wp-cron.php and not
If the current script does not equal these 2 values, it will send a request to any of the following domains, depending on if they respond properly to the request or not:
If it cannot send a request to any of these URL’s, it will retrieve content from
If the returning data from the request to
code.php on these domains contains the string held in the
$wp_auth_key variable, it will inject the PHP code into the
If it cannot be injected into this file, it will try to inject it into the root directory of the current theme. If that also fails, it will try to inject it into
wp-tmp.php in the current directory.
This file can contain pretty much anything defined by the malware creator. We have seen things from backdoors to advertisements that are injected into all pages of the site.
Since they can inject any kind of PHP code, there is pretty much no restriction as to what they are able to do on the site.
One sample that we have shows that it:
MAX_LEVEL: Defines how deep it should iterate through folders.
MAX_ITERATION: Defines how many files it can read from a folder.
P: The value of
$GLOBALS['WP_CD_CODE']: Contains a base64 encoded string that when decoded, is almost similar to the code of the malware file itself except that it doesn’t contain the function that spreads itself in the
$GLOBALS['stopkey']: Array of folders’ names. When the malware is scanning for folders/files to inject into, it will stop once it hits one of the folder names in this array.
$GLOBALS['DIR_ARRAY']: All folders that are found to be in the
$GLOBALS['stopkey'] array will be added to this array.
$search: Array used in the
SearchFile function to find the location of wp-config.php.
$install_code: Backdoor code that will be injected into multiple files.
$install_hash: String containing an MD5 hash of
AUTH_SALT is taken from the wp-config.php file.
$install_code: Replaces the string
$wp_auth_key: A MD5 string, most likely used so certain requests can only be executed by the malware creator.
getDirList($path): Get all folders in
-> If the file /wp-includes/post.php exists, it will create a file called /wp-includes/wp-vcd.php containing the base64 decoded value of
-> If /wp-includes/post.php does not contain the string ‘wp-vcd’, it will inject the inclusion of /wp-includes/wp-vcd.php into the first line of /wp-includes/post.php:
<?php if (file_exists(dirname(__FILE__) . 'wp-vcd.php')) include_once(dirname(__FILE__) . 'wp-vcd.php'); ?>
SearchFile($search, $path): Given the
$path, keep searching the entire site until the file in
$search is found.
file_get_contents_tcurl($url): Send a request to a URL and return its contents.
-> Creates a new temporary file in PHP’s temporary directory in which it will inject
$phpCode. Once the code has been injected, it will load the file and immediately delete it after which it will return all defined variables returned by the PHP function
-> If it cannot create a temporary file in PHP’s temporary directory, it will attempt to create the file in the current directory instead.
To protect your site from the WP-VCD malware you should not download plugins and themes from sites that offer nulled versions of the software.
Not only is it often against the terms and conditions of the plugin, but they are also loaded with malware and other suspicious backdoors, that can cause a lot of damage to your site and your reputation.
Only download software from the official wordpress.org site, through the WordPress backend and legitimate sites that offer premium plugins.