Last active
August 21, 2024 16:16
-
-
Save EvanMcBroom/5590775e4f22e6d6aa648e889606faee to your computer and use it in GitHub Desktop.
An example bypass of FMAPI's MiniNT check using a registry transaction
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright (C) 2023 Evan McBroom | |
cmake_minimum_required(VERSION 3.21.0) | |
project(transacted-fmapi | |
LANGUAGES CXX | |
) | |
set(CMAKE_CXX_STANDARD 17) | |
set(CMAKE_CXX_STANDARD_REQUIRED YES) | |
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") | |
add_executable(${PROJECT_NAME}) | |
target_sources(${PROJECT_NAME} PRIVATE fmapi.h main.cpp) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (C) 2023 Evan McBroom | |
#pragma once | |
#include <Windows.h> | |
// The definitions in this source file were created using the documentation provided here: | |
// https://learn.microsoft.com/en-us/previous-versions/windows/desktop/fmapi/ | |
#define FILE_RESTORE_MAJOR_VERSION_1 0x0001 // Used to define the major version number of the file restore functions. | |
#define FILE_RESTORE_MINOR_VERSION_1 0x0001 // Used to define the minor version number of the file restore functions. | |
#define FILE_RESTORE_VERSION_1 ((FILE_RESTORE_MAJOR_VERSION_1 << 16) | FILE_RESTORE_MINOR_VERSION_1) // Used to define the complete version number of the file restore functions. | |
#define FILE_RESTORE_MAJOR_VERSION_2 0x0002 // Used to define the major version number of the file restore functions. | |
#define FILE_RESTORE_MINOR_VERSION_2 0x0000 // Used to define the minor version number of the file restore functions. | |
#define FILE_RESTORE_VERSION_2 ((FILE_RESTORE_MAJOR_VERSION_2 << 16) | FILE_RESTORE_MINOR_VERSION_2) // Used to define the complete version number of the file restore functions. | |
#define FILE_RESTORE_VERSION_CURRENT FILE_RESTORE_VERSION_2 // Used to define the current version number for file restore functions. | |
#define VOLUME_INFO_ENCRYPTED 0x0001 // Used to define the encrypted status of a volume. | |
#define VOLUME_INFO_LOCKED 0x0002 // Used to define the locked status of a volume. | |
typedef PVOID PFILE_RESTORE_CONTEXT; | |
typedef enum { | |
FileSystemUnknown, | |
FileSystemFAT12, | |
FileSystemFAT16, | |
FileSystemFat32, | |
FileSystemNTFS | |
} BOOT_SECTOR_FILE_SYSTEM_TYPE; | |
typedef enum { | |
FileRestoreProgressInfo = 100, | |
FileRestoreFinished = 101 | |
} FILE_RESTORE_PACKET_TYPE, * PFILE_RESTORE_PACKET_TYPE; | |
typedef enum { | |
ContextFlagVolume = 0x00000001, | |
ContextFlagDisk = 0x00000002, | |
FlagScanRemovedFiles = 0x00000004, | |
FlagScanRegularFiles = 0x00000008, | |
FlagScanIncludeRemovedDirectories = 0x00000010 | |
} RESTORE_CONTEXT_FLAGS; | |
typedef struct _BOOT_SECTOR_INFO { | |
LONGLONG TotalSectors; | |
BOOT_SECTOR_FILE_SYSTEM_TYPE FileSystem; | |
ULONG BytePerSector; | |
ULONG SectorPerCluster; | |
BOOL IsEncrypted; | |
} BOOT_SECTOR_INFO, * PBOOT_SECTOR_INFO; | |
typedef struct _FILE_RESTORE_FINISHED_INFORMATION { | |
BOOL Success; | |
ULONG FinalResult; | |
PVOID ClbkArg; | |
} FILE_RESTORE_FINISHED_INFORMATION, * PFILE_RESTORE_FINISHED_INFORMATION; | |
typedef struct _FILE_RESTORE_PROGRESS_INFORMATION { | |
LONGLONG TotalFileSize; | |
LONGLONG TotalBytesCompleted; | |
LONGLONG StreamSize; | |
LONGLONG StreamBytesCompleted; | |
PVOID ClbkArg; | |
} FILE_RESTORE_PROGRESS_INFORMATION, * PFILE_RESTORE_PROGRESS_INFORMATION; | |
typedef struct _RESTORABLE_FILE_INFO { | |
ULONG Size; | |
DWORD Version; | |
ULONGLONG FileSize; | |
FILETIME CreationTime; | |
FILETIME LastAccessTime; | |
FILETIME LastWriteTime; | |
DWORD Attributes; | |
BOOL IsRemoved; | |
LONGLONG ClustersUsedByFile; | |
LONGLONG ClustersCurrentlyInUse; | |
ULONG RestoreDataOffset; | |
WCHAR FileName[1]; | |
} RESTORABLE_FILE_INFO, * PRESTORABLE_FILE_INFO; | |
BOOL WINAPI CloseFileRestoreContext( | |
_In_ PFILE_RESTORE_CONTEXT Context | |
); | |
BOOL WINAPI CreateFileRestoreContext( | |
_In_ PCWSTR Volume, | |
_In_ RESTORE_CONTEXT_FLAGS Flags, | |
_In_opt_ LONGLONG StartSector, | |
_In_ LONGLONG BootSector, | |
_In_ DWORD Version, | |
_Out_ PFILE_RESTORE_CONTEXT Context | |
); | |
BOOL WINAPI DetectBootSector( | |
_In_ CONST UCHAR* BootSector, | |
_Out_ PBOOT_SECTOR_INFO BootSectorParams | |
); | |
BOOL WINAPI DetectEncryptedVolume( | |
_In_ PFILE_RESTORE_CONTEXT Context, | |
_Out_ PDWORD VolumeEncryptionInfo | |
); | |
BOOL WINAPI DetectEncryptedVolumeEx( | |
_In_ PFILE_RESTORE_CONTEXT Context, | |
_Out_ PDWORD VolumeEncryptionInfo, | |
_Out_ PULONGLONG VolumeSize | |
); | |
typedef BOOLEAN(*FILE_RESTORE_CALLBACK)( | |
_In_ FILE_RESTORE_PACKET_TYPE PacketType, | |
_In_ ULONG PacketLength, | |
_In_ PVOID PacketData | |
); | |
BOOL WINAPI RestoreFile( | |
_In_ PFILE_RESTORE_CONTEXT Context, | |
_In_ PRESTORABLE_FILE_INFO RestorableFile, | |
_In_ PCWSTR DstFile, | |
_In_opt_ FILE_RESTORE_CALLBACK Callback, | |
_In_opt_ PVOID ClbkArg | |
); | |
BOOL WINAPI ScanRestorableFiles( | |
_In_ PFILE_RESTORE_CONTEXT Context, | |
_In_ PCWSTR Path, | |
_In_ ULONG FileInfoSize, | |
_Out_ PRESTORABLE_FILE_INFO FileInfo, | |
_Out_ PULONG FileInfoUsed | |
); | |
BOOL WINAPI SupplyDecryptionInfo( | |
_In_ PFILE_RESTORE_CONTEXT Context, | |
_In_opt_ PCWSTR RecoveryKeyFilePath, | |
_In_opt_ PVOID RecoveryPassword, | |
_In_opt_ PVOID KeyPackage, | |
_In_opt_ ULONG KeyPackageSize | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (C) 2023 Evan McBroom | |
#include "fmapi.h" | |
#include <imagehlp.h> | |
#include <ktmw32.h> | |
#include <iostream> | |
#include <vector> | |
#include <Odbcinst.h> | |
#pragma comment(lib, "KtmW32.lib") | |
namespace { | |
template<typename Function> | |
inline Function* LazyLoad(const std::wstring& libraryName, const std::string& procName) { | |
auto library{ LoadLibraryW(libraryName.data()) }; | |
return (library) ? reinterpret_cast<Function*>(GetProcAddress(library, procName.data())) : nullptr; | |
} | |
struct Pe { | |
byte* base; | |
inline Pe(byte* base = nullptr) { | |
this->base = (base) ? base : reinterpret_cast<byte*>(GetModuleHandleW(nullptr)); | |
} | |
template<typename Type> | |
inline Type* DataDirectory(size_t index) { | |
auto dataDirectory{ OptionalHeader()->DataDirectory[index] }; | |
return (dataDirectory.Size) ? reinterpret_cast<Type*>(this->base + dataDirectory.VirtualAddress) : nullptr; | |
} | |
inline auto DosHeader() { | |
return reinterpret_cast<IMAGE_DOS_HEADER*>(base); | |
} | |
inline auto ImportAddressTable() { | |
return DataDirectory<IMAGE_THUNK_DATA>(IMAGE_DIRECTORY_ENTRY_IAT); | |
} | |
inline auto NtHeaders() { | |
return reinterpret_cast<IMAGE_NT_HEADERS*>(this->base + DosHeader()->e_lfanew); | |
} | |
inline auto OptionalHeader() { | |
return &NtHeaders()->OptionalHeader; | |
} | |
}; | |
template<typename Type> | |
bool WriteAddress(Type* address, Type value) { | |
DWORD oldProtection; | |
if (VirtualProtect(address, sizeof(Type), PAGE_READWRITE, &oldProtection)) { | |
*address = value; | |
DWORD discard; | |
return VirtualProtect(address, sizeof(Type), oldProtection, &discard); | |
} | |
} | |
} | |
#define IMPORT(LIBRARY, PROC) \ | |
auto Lazy##PROC { \ | |
LazyLoad<decltype(PROC)>(L#LIBRARY, #PROC) \ | |
} | |
#define MINI_NT_KEY L"System\\CurrentControlSet\\Control\\MiniNT" | |
__declspec(thread) HANDLE transaction{ INVALID_HANDLE_VALUE }; | |
LSTATUS RegOpenKeyExWHook(HKEY hKey, LPCWSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult) { | |
if (hKey == HKEY_LOCAL_MACHINE && !wmemcmp(lpSubKey, MINI_NT_KEY, lstrlenW(MINI_NT_KEY))) { | |
// First check if the key exists | |
if (RegOpenKeyExW(hKey, lpSubKey, ulOptions, samDesired, phkResult) == ERROR_SUCCESS) { | |
return ERROR_SUCCESS; | |
} | |
// If it does not, check if the transaction already exists | |
if (transaction != INVALID_HANDLE_VALUE) { | |
return RegOpenKeyTransactedW(hKey, lpSubKey, ulOptions, samDesired, phkResult, transaction, nullptr); | |
} | |
// If it does not, then create the transaction and the key within it | |
transaction = CreateTransaction(nullptr, 0, 0, 0, 0, 0, nullptr); | |
if (transaction != INVALID_HANDLE_VALUE) { | |
DWORD disposition; | |
return RegCreateKeyTransactedW(hKey, lpSubKey, 0, nullptr, ulOptions, samDesired, nullptr, phkResult, &disposition, transaction, nullptr); | |
} | |
// Return a standard error that may be returned by the real RegOpenKeyExW | |
return ERROR_NO_SYSTEM_RESOURCES; | |
}; | |
// Call the normal API for anything other than MINI_NT_KEY | |
return RegOpenKeyExW(hKey, lpSubKey, ulOptions, samDesired, phkResult); | |
} | |
class FmapiHook { | |
public: | |
FmapiHook() { | |
fmapi = LoadLibraryW(L"fmapi.dll"); | |
if (fmapi) { | |
Pe pe{ reinterpret_cast<byte*>(fmapi) }; | |
auto iat{ pe.ImportAddressTable() }; | |
size_t index{ 0 }; | |
while (iat[index].u1.Function != (size_t)RegOpenKeyExW) { | |
index++; | |
} | |
hookAddress = &iat[index].u1.Function; | |
if (WriteAddress<size_t>(hookAddress, (size_t)RegOpenKeyExWHook)) { | |
applied = true; | |
} | |
} | |
} | |
~FmapiHook() { | |
WriteAddress<size_t>(hookAddress, (size_t)RegOpenKeyExW); | |
if (transaction != INVALID_HANDLE_VALUE) { | |
RollbackTransaction(transaction); | |
CloseHandle(transaction); | |
} | |
} | |
auto Applied() { | |
return applied; | |
} | |
private: | |
HANDLE fmapi; | |
size_t* hookAddress; | |
DWORD oldProtection; | |
bool applied{ false }; | |
}; | |
void ScanDeletedFiles() { | |
IMPORT(fmapi.dll, CreateFileRestoreContext); | |
auto flags{ static_cast<RESTORE_CONTEXT_FLAGS>(ContextFlagVolume | FlagScanRemovedFiles | FlagScanIncludeRemovedDirectories) }; | |
PFILE_RESTORE_CONTEXT context; | |
if (LazyCreateFileRestoreContext(L"\\\\.\\C:", flags, 0, 0, FILE_RESTORE_VERSION_2, &context)) { | |
IMPORT(fmapi.dll, ScanRestorableFiles); | |
bool moreFiles{ true }; | |
std::cout << "Found files:" << std::endl; | |
ULONG fileInfoSize = sizeof(RESTORABLE_FILE_INFO) + MAX_PATH; | |
while (moreFiles) { | |
std::vector<byte> bytes(fileInfoSize, 0); | |
auto fileInfo{ reinterpret_cast<PRESTORABLE_FILE_INFO>(bytes.data()) }; | |
if (LazyScanRestorableFiles(context, L"\\", fileInfoSize, fileInfo, &fileInfoSize)) { | |
if (fileInfoSize > bytes.size()) { | |
continue; | |
} | |
fileInfoSize = 0; | |
if (fileInfo->IsRemoved) { | |
std::wcout << L" " << fileInfo->FileName << std::endl; | |
} | |
} | |
else { | |
auto error{ GetLastError() }; | |
if (error == ERROR_NO_MORE_FILES) { | |
moreFiles = false; | |
} | |
if (error == ERROR_INSUFFICIENT_BUFFER) { | |
continue; | |
} | |
else { | |
std::cout << "Unknown error: " << error << std::endl; | |
} | |
} | |
} | |
IMPORT(fmapi.dll, CloseFileRestoreContext); | |
LazyCloseFileRestoreContext(context); | |
} | |
} | |
int main() { | |
FmapiHook hook; | |
if (hook.Applied()) { | |
ScanDeletedFiles(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment