Mingw 生成的 x86 程序只能作为管理员成功运行 - x64 和 VS(x86 和 x64)版本正常

Mingw produces x86 program which only successfully runs as Administrator- x64 and VS(x86 and x64) versions fine

我正在看这个 Github 项目:https://github.com/LloydLabs/delete-self-poc

该项目以一种有点创造性的方式使用 SetFileInformationByHandle API (https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileinformationbyhandle) 来允许从磁盘中删除锁定的文件。我正在尝试将其作为更大程序的一部分来实现,但是在针对 x86 进行编译时我遇到了 运行 问题。我在 debian 机器上使用 mingw-w64 来编译我的程序,在对 x86 进行兼容性检查时,我发现了一个非常 st运行ge 的问题。

#include "main.h"

static
HANDLE
ds_open_handle(
    PWCHAR pwPath
)
{
    return CreateFileW(pwPath, DELETE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
}

static
BOOL
ds_rename_handle(
    HANDLE hHandle
)
{
    FILE_RENAME_INFO fRename;
    RtlSecureZeroMemory(&fRename, sizeof(fRename));

    // set our FileNameLength and FileName to DS_STREAM_RENAME
    LPWSTR lpwStream = DS_STREAM_RENAME;
    fRename.FileNameLength = sizeof(lpwStream);
    RtlCopyMemory(fRename.FileName, lpwStream, sizeof(lpwStream));

    return SetFileInformationByHandle(hHandle, FileRenameInfo, &fRename, sizeof(fRename) + sizeof(lpwStream));
}

static
BOOL 
ds_deposite_handle(
    HANDLE hHandle
)
{
    // set FILE_DISPOSITION_INFO::DeleteFile to TRUE
    FILE_DISPOSITION_INFO fDelete;
    RtlSecureZeroMemory(&fDelete, sizeof(fDelete));

    fDelete.DeleteFile = TRUE;

    return SetFileInformationByHandle(hHandle, FileDispositionInfo, &fDelete, sizeof(fDelete));
}

int
main(
    int argc,
    char** argv
)
{
    WCHAR wcPath[MAX_PATH + 1];
    RtlSecureZeroMemory(wcPath, sizeof(wcPath));

    // get the path to the current running process ctx
    if (GetModuleFileNameW(NULL, wcPath, MAX_PATH) == 0)
    {
        DS_DEBUG_LOG(L"failed to get the current module handle");
        return 0;
    }

    HANDLE hCurrent = ds_open_handle(wcPath);
    if (hCurrent == INVALID_HANDLE_VALUE)
    {
        DS_DEBUG_LOG(L"failed to acquire handle to current running process");
        return 0;
    }

    // rename the associated HANDLE's file name
    DS_DEBUG_LOG(L"attempting to rename file name");
    if (!ds_rename_handle(hCurrent))
    {
        DS_DEBUG_LOG(L"failed to rename to stream");
        return 0;
    }

    DS_DEBUG_LOG(L"successfully renamed file primary :$DATA ADS to specified stream, closing initial handle");
    CloseHandle(hCurrent);

    // open another handle, trigger deletion on close
    hCurrent = ds_open_handle(wcPath);
    if (hCurrent == INVALID_HANDLE_VALUE)
    {
        DS_DEBUG_LOG(L"failed to reopen current module");
        return 0;
    }

    if (!ds_deposite_handle(hCurrent))
    {
        DS_DEBUG_LOG(L"failed to set delete deposition");
        return 0;
    }

    // trigger the deletion deposition on hCurrent
    DS_DEBUG_LOG(L"closing handle to trigger deletion deposition");
    CloseHandle(hCurrent);

    // verify we've been deleted
    if (PathFileExistsW(wcPath))
    {
        DS_DEBUG_LOG(L"failed to delete copy, file still exists");
        return 0;
    }

    DS_DEBUG_LOG(L"successfully deleted self from disk");
    return 1;
}

将在链接存储库(如上所示)中找到的基本代码编译为 x86 时,尝试 运行 程序在 ds_rename_handle 函数中的 SetFileInformationByHandle 调用失败。调用 GetLastError() returns 123:

ERROR_INVALID_NAME

123 (0x7B)

The filename, directory name, or volume label syntax is incorrect.

非常奇怪的是,当 运行 来自管理员提示时程序成功。即使是 st运行ger,为 x64 编译相同的代码在普通提示符和管理员提示符下都可以工作。

作为完整性检查,我将代码逐字复制到 VS2019 并在那里编译,生成的 x86 程序能够 运行 无需管理员权限。

在 debian 系统上对源代码所做的唯一更改是在头文件中进行的:

#pragma once

#pragma comment(lib, "Shlwapi.lib")

#include <Windows.h>
#include <shlwapi.h>
#include <stdio.h>
#include <stdlib.h>

#define DS_STREAM_RENAME L":wtfbbq"
#define DS_DEBUG_LOG(msg) wprintf(L"[LOG] - %s\n", msg)

其中 更改为 并且 DS_DEBUG_LOG 行更改为 %ls 以便打印整个日志消息。

用于编译 x86 的 GCC 命令是:

i686-w64-mingw32-gcc main.c -o delete32.exe -s -DUNICODE -Os -lshlwapi

我试过删除所有开关并编译,但仍然失败。

请注意,只有在 main() 中的最后一次调用 PathFileExistsW 中才需要 shlwapi 库。我已经注释掉了那部分,并从导入和 gcc 命令中删除了 shlwapi,但没有任何效果。

成功的 x64 gcc 命令是:

x86_64-w64-mingw32-gcc main.c -o delete32.exe -s -DUNICODE -Os -lshlwapi

在 github 存储库的问题选项卡中提到了我单独查看的代码中的一些错误。但是我非常想知道为什么 mingw 会导致该程序的 32 位版本出现问题。不幸的是,“只用 VS 编译”不是一个选项,因为我使用一个程序来生成和编译这段代码将成为我 linux 机器上的一部分的程序。

感谢您的任何见解。

RtlCopyMemory(fRename.FileName, lpwStream, sizeof(lpwStream)); 将 4 或 8 个字节写入 2 字节缓冲区!谁知道剩余字节的去向,程序行为可能未定义。

像这样删除 运行 exe 的概念可能可行,但代码表明作者对 Windows 和 C 没有完全理解。你最好从头开始重写它...

编辑:

玩过您上传的文件后,我可以自信地说,缺少应用程序清单会导致它在未提升时失败。您的 vc 文件有一个 requestedExecutionLevel 元素,该元素为其提供 Vista 操作系统上下文。如果您删除 vc exe 中的清单资源,它将停止工作。如果您将清单添加到 mingw exe,它就会开始工作。

ds_rename_handle 函数实现错误。

FILE_RENAME_INFO是可变尺寸结构。

作为结果声明

FILE_RENAME_INFO fRename;

几乎总是错误的(只有当 FileName 包含 1 或 2 个符号时才可以)

真的需要先计算FileNameLength然后再根据这个分配PFILE_RENAME_INFO 例如:

ULONG FileNameLength = (ULONG)wcslen(DS_STREAM_RENAME) * sizeof(WCHAR);

ULONG dwBufferSize = FIELD_OFFSET(FILE_RENAME_INFO, FileName) + FileNameLength;

PFILE_RENAME_INFORMATION fRename = (PFILE_RENAME_INFORMATION)alloca(dwBufferSize);

因此 ds_rename_handle 的完整代码可以是下一个:

ULONG ds_rename_handle(HANDLE hHandle, PCWSTR DS_STREAM_RENAME)
{
    ULONG FileNameLength = (ULONG)wcslen(DS_STREAM_RENAME) * sizeof(WCHAR);
    
    ULONG dwBufferSize = FIELD_OFFSET(FILE_RENAME_INFO, FileName) + FileNameLength;
    
    PFILE_RENAME_INFO fRename = (PFILE_RENAME_INFO)alloca(dwBufferSize);
    
    fRename->ReplaceIfExists = TRUE;
    fRename->RootDirectory = 0;
    fRename->FileNameLength = FileNameLength;
    memcpy(fRename->FileName, DS_STREAM_RENAME, FileNameLength);

    return SetFileInformationByHandle(hHandle, FileRenameInfo, 
        fRename, dwBufferSize) ? NOERROR : GetLastError();
}

但是 FILE_RENAME_INFO 的文档非常糟糕。不清楚 - 以什么形式 - 完整路径名、文件名或相对路径名 - 必须是 FileName ?!

根据我的研究 - 它必须只是完整路径名(不是文件名)或以冒号开头 :(流的新名称)

更好地使用 NtSetInformationFileFileRenameInformation

FILE_RENAME_INFORMATION 结构的描述与 FILE_RENAME_INFO !

进行比较

这里有详细的描述 - FileName 应该是什么形式。

所以我总是用

NTSTATUS ds_rename_handle_nt(HANDLE hHandle, PCWSTR DS_STREAM_RENAME)
{
    ULONG FileNameLength = (ULONG)wcslen(DS_STREAM_RENAME) * sizeof(WCHAR);

    ULONG dwBufferSize = FIELD_OFFSET(FILE_RENAME_INFO, FileName) + FileNameLength;

    PFILE_RENAME_INFORMATION fRename = (PFILE_RENAME_INFORMATION)alloca(dwBufferSize);

    fRename->ReplaceIfExists = TRUE;
    fRename->RootDirectory = 0;
    fRename->FileNameLength = FileNameLength;
    memcpy(fRename->FileName, DS_STREAM_RENAME, FileNameLength);

    IO_STATUS_BLOCK iosb;
    return NtSetInformationFile(
        hHandle, &iosb, fRename, dwBufferSize, FileRenameInformation);
}