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 ?!
根据我的研究 - 它必须只是完整路径名(不是文件名)或以冒号开头 :(流的新名称)
更好地使用 NtSetInformationFile 和 FileRenameInformation
将 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);
}
我正在看这个 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)
其中
用于编译 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 ?!
根据我的研究 - 它必须只是完整路径名(不是文件名)或以冒号开头 :(流的新名称)
更好地使用 NtSetInformationFile 和 FileRenameInformation
将 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);
}