/*
 * ntfs_self_delete.c
 *
 * Deletes the running executable from disk using NTFS ADS rename +
 * FILE_DISPOSITION_FLAG_POSIX_SEMANTICS.
 *
 * Requires Windows 10 RS1 (build 14393) or later for POSIX semantics.
 * Falls back to on-close deletion on earlier builds.
 *
 * Compile (MSVC):
 *   cl /W4 /O2 /DUNICODE /D_UNICODE ntfs_self_delete.c /link kernel32.lib ntdll.lib bcrypt.lib
 */

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <bcrypt.h>
#include <stdio.h>

/* FILE_DISPOSITION_INFO_EX is present in SDK 10.0.14393+ */
#ifndef FILE_DISPOSITION_FLAG_POSIX_SEMANTICS
#define FILE_DISPOSITION_FLAG_DELETE          0x00000001
#define FILE_DISPOSITION_FLAG_POSIX_SEMANTICS 0x00000002

typedef struct _FILE_DISPOSITION_INFO_EX {
    DWORD Flags;
} FILE_DISPOSITION_INFO_EX, *PFILE_DISPOSITION_INFO_EX;

#define FileDispositionInfoEx ((FILE_INFO_BY_HANDLE_CLASS)21)
#endif

/*
 * Build a rename info buffer with a random ADS name like ":a3f7c2b1".
 * Returns a heap-allocated FILE_RENAME_INFO sized for the name.
 * Caller frees.
 */
static FILE_RENAME_INFO *BuildRenameInfo(void)
{
    BYTE    rnd[4]      = {0};
    WCHAR   streamName[16];
    DWORD   nameLen;
    SIZE_T  bufLen;
    FILE_RENAME_INFO *ri;

    if (!BCRYPT_SUCCESS(BCryptGenRandom(NULL, rnd, sizeof(rnd),
                                        BCRYPT_USE_SYSTEM_PREFERRED_RNG))) {
        return NULL;
    }

    if (_snwprintf_s(streamName, ARRAYSIZE(streamName), _TRUNCATE,
                     L":%02x%02x%02x%02x",
                     rnd[0], rnd[1], rnd[2], rnd[3]) < 0) {
        return NULL;
    }

    nameLen = (DWORD)(wcslen(streamName) * sizeof(WCHAR));
    bufLen  = sizeof(FILE_RENAME_INFO) + nameLen;

    ri = (FILE_RENAME_INFO *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufLen);
    if (!ri) return NULL;

    ri->ReplaceIfExists = FALSE;
    ri->RootDirectory   = NULL;
    ri->FileNameLength  = nameLen;
    memcpy(ri->FileName, streamName, nameLen);

    return ri;
}

/*
 * Delete the running executable from disk.
 *
 * On success: returns TRUE; the file is gone from the directory.
 * On failure: returns FALSE; GetLastError() is set.
 */
static BOOL SelfDelete(void)
{
    WCHAR exePath[MAX_PATH * 2] = {0};
    HANDLE hFile                = INVALID_HANDLE_VALUE;
    FILE_RENAME_INFO *ri        = NULL;
    BOOL result                 = FALSE;

    if (!GetModuleFileNameW(NULL, exePath, ARRAYSIZE(exePath))) {
        fprintf(stderr, "GetModuleFileNameW failed: %lu\n", GetLastError());
        goto cleanup;
    }
    wprintf(L"[*] Executable path: %s\n", exePath);

    hFile = CreateFileW(
        exePath,
        DELETE | SYNCHRONIZE,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_OPEN_REPARSE_POINT,
        NULL);

    if (hFile == INVALID_HANDLE_VALUE) {
        fprintf(stderr, "CreateFileW failed: %lu\n", GetLastError());
        goto cleanup;
    }

    /* Step 1: rename :$DATA to a random ADS */
    ri = BuildRenameInfo();
    if (!ri) {
        fprintf(stderr, "BuildRenameInfo failed\n");
        goto cleanup;
    }

    wprintf(L"[*] Renaming stream to %s\n", ri->FileName);

    if (!SetFileInformationByHandle(hFile, FileRenameInfo,
                                    ri, (DWORD)(sizeof(FILE_RENAME_INFO) +
                                                ri->FileNameLength))) {
        fprintf(stderr, "FileRenameInfo failed: %lu\n", GetLastError());
        goto cleanup;
    }

    /* Step 2: mark for deletion with POSIX semantics -- unlinks from directory immediately */
    {
        FILE_DISPOSITION_INFO_EX diex;
        diex.Flags = FILE_DISPOSITION_FLAG_DELETE | FILE_DISPOSITION_FLAG_POSIX_SEMANTICS;

        if (!SetFileInformationByHandle(hFile, FileDispositionInfoEx,
                                        &diex, sizeof(diex))) {
            DWORD err = GetLastError();
            fprintf(stderr, "FileDispositionInfoEx failed: %lu\n", err);

            if (err == ERROR_INVALID_PARAMETER) {
                /* Pre-RS1 fallback: schedule deletion on last-handle-close */
                fprintf(stderr, "[!] POSIX_SEMANTICS unsupported, falling back to on-close\n");
                FILE_DISPOSITION_INFO di;
                di.DeleteFile = TRUE;
                if (!SetFileInformationByHandle(hFile, FileDispositionInfo,
                                                &di, sizeof(di))) {
                    fprintf(stderr, "FileDispositionInfo fallback failed: %lu\n",
                            GetLastError());
                    goto cleanup;
                }
                result = TRUE;
            }
            goto cleanup;
        }
    }

    printf("[+] File unlinked from directory. Process continues.\n");
    result = TRUE;

cleanup:
    if (ri)    HeapFree(GetProcessHeap(), 0, ri);
    if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile);
    return result;
}

int wmain(void)
{
    printf("[*] PID %lu -- about to delete own executable\n",
           GetCurrentProcessId());

    if (!SelfDelete()) {
        fprintf(stderr, "[-] SelfDelete failed\n");
        return 1;
    }

    printf("[+] Done. Try to find the file now.\n");
    return 0;
}
