Skip to content

Latest commit

 

History

History

tbl_status.php

tbl_status.php - another ring.php web shell

Downloaded via an apikey.php variant, obscured almost identically to ring.php, blah blah

Origin

It looks like the attacker(s) tried to download a plain PHP source code file to an instance of apikey.php file gateway. All that apikey.php requires is a HTTP file transfer, with a name of "filename".

Fortunately, my WordPress includes an apikey.php file gateway emulation. This code actually got downloaded to a variant of apikey.php file gateway that someone at 45.132.192.22 thought they had downloaded 2019-10-05 via a plugin upload, which my honey pot also emulates.

This download has an item of interest. The attacker(s) sent t_file_wp.php extra HTTP parameters.

Parameter Name Parameter Value
folder
home 1
wp_admin 0
wp_content 1
wp_includes 0

If I read t_file_wp.php code correctly, the "home" parameter and it's value of "1" would cause t_file_wp.php to put a copy of the downloaded code in Apache's DocumentRoot directory. The "wp_content" parameter and a value of "1" causes it to put a copy of the downloaded code in WordPress' wp-content/ directory. In both cases, the file would have the name tbl_status.php

IP Address 213.59.146.28

213.59.146.28 reverse-lookups as ip-213.59.146.28.zelenaya.net

213.59.146.28 seems like it belongs to zelenay.net,

inetnum:        213.59.144.0 - 213.59.147.255
netname:        GSS-NET
descr:          OOO GSS Network
country:        RU
admin-c:        LSV84-RIPE
tech-c:         LSV84-RIPE
created:        2015-08-28T14:59:27Z
last-modified:  2019-04-11T13:08:58Z

route:          213.59.144.0/22
descr:          GSS-NET route
origin:         AS48176
created:        2015-08-28T15:08:27Z
last-modified:  2015-08-28T15:08:27Z

Domain Name: ZELENAYA.NET
Registry Domain ID: 1514637937_DOMAIN_NET-VRSN
Updated Date: 2019-07-18T21:17:27Z
Creation Date: 2008-08-18T09:10:14Z
Name Server: NS.OOONET.RU
Name Server: NS2.OOONET.RU

zelenay.net is a Russian "Internet and digital TV for home and business" provider, In 18 Russian cities:

  • Belgorod
  • Mud
  • Moscow
  • Stavropol
  • Ufa
  • Beloretsk
  • Efremov
  • Nalchik
  • Mikhailovsk
  • Elista
  • Vladivostok
  • Kochubeyevskoe
  • Nevinnomyssk
  • Tambov
  • Dace
  • Lipetsk
  • Neftekamsk
  • Tomsk

That covers a lot of ground.

Deobfuscation

The downloaded malware appears at first glance to constitute a piece of WordPress code. It claims:

* Toolbar API: Top-level Toolbar functionality
*
* @package WordPress
* @subpackage Toolbar
* @since 3.1.0

There are other comments in the code that make it look like WordPress code.

The code closely resembles WordPress code. It's all snake_case, space characters appear after every left parentheses, and before every right parentheses. A function _wp_admin_bar_init() appears, which WordPress official web pages include. It may very well constitute some real WordPress code. It's never invoked - it's window dressing.

The malware carries a Base64-encoded string. When the malware gets invoked with an HTTP POST parameter named "f_pp", the malware invokes function pre_admin_bar with the value of "f_pp", and the Base64-encoded string.

function pre_admin_bar uses an MD5 hash of the "f_pp" string to do a variant autokey decoding on the base64-encoded string (after base64 decoding it).

$b = $ciphertext[$i];
$o = ord($b);             // encrypted byte
$k = ord($key[$i]);       // key byte
$c = chr(($o - $k)%256);  // cleartext byte
$key .= $c;
$encoded .= $c;

The string $key starts out with a length of at least 34 bytes. Every deciphered byte gets appended to the key string, so the algorithm never runs out of key bytes.

This algorithm is known to be vulnerable to a known cleartext attack: If you can determine or guess the first few cleartext bytes, you can discover the bytes that get appended to the key string. Unfortunately, the last step of this algorithm is to run PHP's gzinflate on the cleartext. gzinflate is a great choice. gzdeflate format only has a few 3-bit (!!!) markers in it, and they're not in very predictable positions. There's very little in a gzdeflated block of bytes to use as known cleartext.

Similarity to ring.php

This obfusction is nearly identical to that of ring.php. ring.php has:

function pre_term_name( $wp_kses_data, $wp_nonce ) {
	$kses_str = str_replace( array ('%', '*'), array ('/', '='), $wp_kses_data );
	$filter = base64_decode( $kses_str );
	$md5 = strrev( $wp_nonce );
	$sub = substr( md5( $md5 ), 0, strlen( $wp_nonce ) );
	$wp_nonce = md5( $wp_nonce ). $sub;
	$preparefunc = 'gzinflate';
	$i = 0; do {
		$ord = ord( $filter[$i] ) - ord( $wp_nonce[$i] );
	 	$filter[$i] = chr( $ord % 256 );
	 	$wp_nonce .= $filter[$i]; $i++;
	} while ($i < strlen( $filter ));
	return @$preparefunc( $filter );
} 

This malware, tbl_status.php has:

function pre_admin_bar ( $wp_kses_data, $wp_nonce ) {

	$kses_str = str_replace( array ('%', '*'), array ('/', '='), $wp_kses_data );
	$filter = 'base'.'6'.'4'.'_decode';
	$filter = $filter( $kses_str );
	$md5 = strrev( $wp_nonce );
	$sub = substr( md5( $md5 ), 0, strlen( $wp_nonce ) );
	$wp_nonce = md5( $wp_nonce ). $sub;
	$prepare_func = 'g'.'z'.'inflate';
	$i = 0; do {
		$ord = ord( $filter[$i] ) - ord( $wp_nonce[$i] );
	 	$filter[$i] = chr( $ord % 256 );
	 	$wp_nonce .= $filter[$i]; $i++;
	} while ($i < strlen( $filter ));
	return @$prepare_func( $filter );
	
} 

They're virtually identical, differing only in obscuring invocations of base64_decode and gzinflate.

The ring.php file has a more concealed base64-encoded string, using what appears to the human eye as an inline image to mask the purpose of the base64-encoded string.

f_pp value, autokey key F5d4JH6m1

I had to wait a few days, but ultimately my WordPress honey pot caught a set of accesses that included a value of "f_pp" that worked Values of "f_pp" that my honey pot caught:

  • asdf
  • t4c3PFr5
  • F5d4JH6m1

With the autokey key value of "F5drJH6m1", I got the original code. It's just another ring.php web shell. There's a different comment on line 2, perhaps to make the gzdeflated string different.

Analysis

I went to all this work just to uncover a virtually unmodified version of the ring.php web shell. The only real question I've got is: Why change the ring.php obfuscation? I mean, constructing "gzinflate" and "base64_decode" from substrings makes sense. Signture-based IDS could conceivably flag PHP files with either of those strings in them. But why make the presence of the base64-encoded ciphertext in the tbl_status.php file more obvious? The attacker(s) went to the trouble of re-working the fake WordPress code in tbl_status.php (versus ring.php), but then just hang the base64-encoded string out in front of everyone.

Subsequent Accesses

The IP address 176.9.23.3 invoked a variety of URLs ending in "tbl_status.php" on 2019-09-27, and 2019-10-13. Oddly, all the invocations were HTTP GET requests, and the URL ended in "z=F5d4JH6m1". The tbl_status.php malware only runs the web shell on POST requests, with a parameter named "f_pp". The parameter "z" has the correct name, but the wrong value. It's hard to believe these invocations aren't related, except that they just can't work with tbl_status.php due to mechanics of HTTP and how PHP handles those mechanics.

Around the web

https://www.unphp.net/decode/223e1910ed9dcd610c50bdfea19900fc/ contains 'F5d4JH6m1'

This file looks like an even more obfuscated version of tbl_status.php. Looks roughly like someone ran tbl_status.php serially through two different obfuscators.