Skip to content

Instantly share code, notes, and snippets.

@EvanMcBroom
Last active August 21, 2024 16:16
Show Gist options
  • Save EvanMcBroom/5590775e4f22e6d6aa648e889606faee to your computer and use it in GitHub Desktop.
Save EvanMcBroom/5590775e4f22e6d6aa648e889606faee to your computer and use it in GitHub Desktop.
An example bypass of FMAPI's MiniNT check using a registry transaction
# 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)
// 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
);
// 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