背景:
我正在尝试编写一个C ++应用程序,该应用程序可以扫描并尝试从WinPE环境中恢复已删除的文件,主要是作为学习练习。此应用程序利用了FMAPI库(fmapi.dll),这是一个文献很少的库,仅在WinPE环境中可用(在完整的Windows操作系统中不可用)。我一直使用MS(可用here发布)的ScanRestorableFiles示例作为起点。
现在,我已经进行了大量的挖掘工作,并且在涉及FMAPI文档时几乎什么都没发现-仅是上面提到的示例和一些基本的MSDN文档here。 MSDN页面提供API函数的定义,并在几个提供提示的功能上提供了一些额外的注释,仅此而已。所以我以为我会来这里,希望能得到一些帮助。
还请注意,就开发语言而言,C ++不是我的强项-我认为自己最多还是新手。
现在要解决的问题:
我的应用程序能够成功加载库,创建文件还原上下文并扫描可恢复的文件。但是,一旦尝试在ScanRestorableFiles()调用返回的可恢复项之一上调用RestoreFile()函数,就会收到无效句柄错误。 “还原的文件”最终是一个0字节的文件(确实在正确的位置成功创建了文件),其中没有数据。
有趣的是,即使返回了Invalid Handle错误代码,我的应用程序也会在文件上保持打开句柄,直到关闭文件还原上下文为止(我知道这一点,因为如果我尝试在还原文件后立即读取已还原的文件,我收到“文件正在被另一个进程使用”错误。)
下面发布的是我应用程序的全部代码(它只是一个源文件,不包括标题等)-由于这似乎是很少使用的API,我觉得我应该发布整个文件以添加上下文每个函数调用(也是因为我不太确定到底是什么与我遇到的问题有关)。
代码:
#include <windows.h>
#include <stdio.h>
#include <iostream>
#include <string>
#include <sstream>
#include <stdlib.h>
#define SCAN_PATH L"\\"
//
//Define the needed FMAPI structures as documented in FMAPI
//
#define FILE_RESTORE_MAJOR_VERSION_2 0x0002
#define FILE_RESTORE_MINOR_VERSION_2 0x0000
#define FILE_RESTORE_VERSION_2 ((FILE_RESTORE_MAJOR_VERSION_2 << 16) | FILE_RESTORE_MINOR_VERSION_2)
using namespace std;
//External API function declarations
//We don't have an import library or
//header for the FMAPI functions, so
//we must dynamically link to them at
//runtime.
typedef PVOID PFILE_RESTORE_CONTEXT;
typedef enum {
ContextFlagVolume = 0x00000001,
ContextFlagDisk = 0x00000002,
FlagScanRemovedFiles = 0x00000004,
FlagScanRegularFiles = 0x00000008,
FlagScanIncludeRemovedDirectories = 0x00000010
} RESTORE_CONTEXT_FLAGS;
typedef enum {
FileRestoreProgressInfo = 100,
FileRestoreFinished = 101
} FILE_RESTORE_PACKET_TYPE, *PFILE_RESTORE_PACKET_TYPE;
typedef BOOL (WINAPI *FuncCreateFileRestoreContext) (
_In_ PCWSTR Volume,
_In_ RESTORE_CONTEXT_FLAGS Flags,
_In_ LONGLONG StartSector,
_In_ LONGLONG BootSector,
_In_ DWORD Version,
_Out_ PFILE_RESTORE_CONTEXT* Context
);
typedef BOOL (WINAPI *FuncCloseFileRestoreContext) (
_In_ PFILE_RESTORE_CONTEXT Context
);
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]; // Single-element array indicates a variable-length structure
} RESTORABLE_FILE_INFO, *PRESTORABLE_FILE_INFO;
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 _FILE_RESTORE_FINISHED_INFORMATION {
BOOL Success;
ULONG FinalResult;
PVOID ClbkArg;
} FILE_RESTORE_FINISHED_INFORMATION, *PFILE_RESTORE_FINISHED_INFORMATION;
typedef BOOL (WINAPI *FuncScanRestorableFiles) (
_In_ PFILE_RESTORE_CONTEXT Context,
_In_ PCWSTR Path,
_In_ ULONG FileInfoSize,
_Out_bytecap_(FileInfoSize) PRESTORABLE_FILE_INFO FileInfo,
_Out_ PULONG FileInfoUsed
);
typedef BOOLEAN (*FILE_RESTORE_CALLBACK) (
_In_ FILE_RESTORE_PACKET_TYPE PacketType,
_In_ ULONG PacketLength,
_In_ PVOID PacketData
);
typedef BOOL (WINAPI *FuncRestoreFile) (
_In_ PFILE_RESTORE_CONTEXT Context,
_In_ PRESTORABLE_FILE_INFO RestorableFile,
_In_ PCWSTR DstFile,
_In_opt_ FILE_RESTORE_CALLBACK Callback,
_In_opt_ PVOID ClbkArg
);
HMODULE hLib;
wchar_t VOLUME[255];
BOOLEAN FuncRestoreCallback(_In_ FILE_RESTORE_PACKET_TYPE pType, _In_ ULONG pLength, _In_ PVOID pData)
{
// This is the callback that is passed to RestoreFile(), which
// returns data about the status of an attempted restoration.
if (pType == FileRestoreProgressInfo)
{
wcout << L"FILE RESTORE PROGRESS INFO:" << L"\n";
wprintf(L"Length of status data: %lu\n", pLength);
wcout << L"Location of data: " << pData << L"\n";
PFILE_RESTORE_PROGRESS_INFORMATION restoreProgressInfo = static_cast<PFILE_RESTORE_PROGRESS_INFORMATION>(pData);
wprintf(L"Total file size: %lld\n", restoreProgressInfo->TotalFileSize);
wprintf(L"Total bytes completed: %lld\n", restoreProgressInfo->TotalBytesCompleted);
wprintf(L"Stream size: %lld\n", restoreProgressInfo->StreamSize);
wprintf(L"Stream bytes completed: %lld\n", restoreProgressInfo->StreamBytesCompleted);
//wcout << L"Callback arg data: " << restoreProgressInfo->ClbkArg << L"\n";
wprintf(L"Callback arg: %p\n", restoreProgressInfo->ClbkArg);
}
else if (pType == FileRestoreFinished)
{
wcout << L"FILE RESTORE FINISHED INFO:" << L"\n";
wprintf(L"Length of status data: %lu\n", pLength);
wcout << L"Location of data: " << pData << L"\n";
// Obtain the struct
PFILE_RESTORE_FINISHED_INFORMATION restoreFinishedInfo = static_cast<PFILE_RESTORE_FINISHED_INFORMATION>(pData);
// Try to read some data from it
wprintf(L"Success data: %d\n", restoreFinishedInfo->Success);
wprintf(L"Final result data: %lu\n", restoreFinishedInfo->FinalResult);
wprintf(L"Callback arg: %p\n", restoreFinishedInfo->ClbkArg);
}
return TRUE;
}
void Scan(_In_ PFILE_RESTORE_CONTEXT context, _In_ LPCWSTR path)
{
// This is the main function that scans the files
// Dynamically link to the needed FMAPI functions
FuncScanRestorableFiles ScanRestorableFiles;
ScanRestorableFiles = reinterpret_cast<FuncScanRestorableFiles>( GetProcAddress( hLib, "ScanRestorableFiles" ) );
ULONG neededBufferSize = 0;
BOOL success = TRUE;
RESTORABLE_FILE_INFO tempFileInfo;
// Call ScanRestorableFiles the first time with a size of 0 to get the required buffer size
if ( ! ScanRestorableFiles(context, path, 0, &tempFileInfo, &neededBufferSize) )
{
wprintf(L"Failed to retrieve required buffer size, Error: #%u\n", GetLastError());
return;
}
// Create the buffer needed to hold restoration information
BYTE *buffer = new BYTE[neededBufferSize];
wcout << L"Initial buffer size is: " << neededBufferSize << L"\n";
// Loops until an error occurs or no more files found
while (success)
{
// Cast the byte buffer pointer into a structure pointer
PRESTORABLE_FILE_INFO fileInfo = reinterpret_cast<PRESTORABLE_FILE_INFO>(buffer);
#pragma warning( push )
#pragma warning( disable : 6386 ) /* warning is ignored since fileInfo grows in size by design */
success = ScanRestorableFiles(context, path, neededBufferSize, fileInfo, &neededBufferSize);
#pragma warning( pop )
wcout << L"Current buffer size is: " << neededBufferSize << L"\n";
if (success)
{
wcout << L"Call returned success! Required buffer size from latest call is " << neededBufferSize <<
L" bytes." L"\n";
if (fileInfo->IsRemoved)
{
// Found restorable file
wprintf(L"Restorable file found: %s\n", fileInfo->FileName);
// Echo size of char array containing file name
wcout << L"Restorable file name size: " <<
(sizeof(fileInfo->FileName) / sizeof(fileInfo->FileName[0])) << L"\n";
// Echo RESTORABLE_FILE_INFO structure info to console
wcout << L"Restorable file info structure memory address: " << fileInfo << L"\n";
wcout << L"Restorable file info structure size (returned via RESTORABLE_FILE_INFO): " << fileInfo->Size << L"\n";
wcout << L"Restorable file info structure size (returned via sizeof()): " << sizeof(*fileInfo) << L"\n";
// Echo restorable file (FMAPI) version to console
wcout << L"Restorable file version: " << fileInfo->Version << L"\n";
// Retrieve creation, write and access times for the file
// Define temp FILETIME, SYSTEMTIME and TIME_ZONE_INFORMATION
// structure vars
// All of these types and functions are defined in windows.h
FILETIME tmpFT;
SYSTEMTIME tmpST;
TIME_ZONE_INFORMATION tmpTZI;
// Initialize empty char arrays
wchar_t szLocalDate[255] = {0}, szLocalTime[255] = {0};
// Get local time zone info
SetTimeZoneInformation(&tmpTZI);
// Get file creation time
FileTimeToLocalFileTime(&(fileInfo->CreationTime), &tmpFT);
FileTimeToSystemTime(&tmpFT, &tmpST);
// Format to readable output and store in char arrays
GetDateFormatEx(LOCALE_NAME_SYSTEM_DEFAULT, DATE_LONGDATE, &tmpST, NULL, szLocalDate, 255, NULL);
GetTimeFormatEx(LOCALE_NAME_SYSTEM_DEFAULT, 0, &tmpST, NULL, szLocalTime, 255);
wcout << L"Restorable file created: " << szLocalDate << " " << szLocalTime << L"\n";
// Clear array for re-use
fill(begin(szLocalDate), end(szLocalDate), 0);
// Get last write time
FileTimeToLocalFileTime(&(fileInfo->LastWriteTime), &tmpFT);
FileTimeToSystemTime(&tmpFT, &tmpST);
// Format to readable output and store in char arrays
GetDateFormatEx(LOCALE_NAME_SYSTEM_DEFAULT, DATE_LONGDATE, &tmpST, NULL, szLocalDate, 255, NULL);
GetTimeFormatEx(LOCALE_NAME_SYSTEM_DEFAULT, 0, &tmpST, NULL, szLocalTime, 255);
wcout << L"Restorable file last written: " << szLocalDate << " " << szLocalTime << L"\n";
// Clear array for re-use
fill(begin(szLocalDate), end(szLocalDate), 0);
// Get last access time
FileTimeToLocalFileTime(&(fileInfo->LastAccessTime), &tmpFT);
FileTimeToSystemTime(&tmpFT, &tmpST);
// Format to readable output and store in char arrays
GetDateFormatEx(LOCALE_NAME_SYSTEM_DEFAULT, DATE_LONGDATE, &tmpST, NULL, szLocalDate, 255, NULL);
GetTimeFormatEx(LOCALE_NAME_SYSTEM_DEFAULT, 0, &tmpST, NULL, szLocalTime, 255);
wcout << L"Restorable file last accessed: " << szLocalDate << " " << szLocalTime << L"\n";
// Output the rest of the file info
wcout << L"Restorable file attributes: " << fileInfo->Attributes << L"\n";
wcout << L"Restorable file size: " << fileInfo->FileSize << L"\n";
wcout << L"Restorable file ClustersUsedByFile: " << fileInfo->ClustersUsedByFile << L"\n";
wcout << L"Restorable file ClustersCurrentlyInUse: " << fileInfo->ClustersCurrentlyInUse << L"\n";
wcout << L"Restorable file RestoreDataOffset: " << fileInfo->RestoreDataOffset << L"\n";
// Attempt to restore the file
wstring tmpStr;
getline(wcin, tmpStr);
// Convert input to uppercase
for (wstring::size_type i = 0; i < tmpStr.size(); i++)
{
towupper(tmpStr[i]);
}
wcout << L"tmpStr is: " << tmpStr << L"\n";
if (tmpStr == L"RESTORE")
{
// Attempt to restore the file
wcout << L"Attempting to restore file " << fileInfo->FileName << L"..." << L"\n";
FuncRestoreFile RestoreFile;
//RestoreFile = (FuncRestoreFile)GetProcAddress( hLib, "RestoreFile" );
RestoreFile = reinterpret_cast<FuncRestoreFile>(GetProcAddress(hLib, "RestoreFile"));
wcout << L"RestoreFile address: " << RestoreFile << L"\n";
BOOL tmpRetVal = false;
PCWSTR restoredFileName = L"X:\\testfile.txt";
wcout << L"New file name: " << restoredFileName << L"\n";
PVOID cbArg = NULL;
tmpRetVal = RestoreFile(context, fileInfo, restoredFileName, &FuncRestoreCallback, cbArg);
wcout << L"Return value: " << tmpRetVal << L" ; cbArg: " << cbArg << L"\n";
if (tmpRetVal == 0)
{
wcout << L"Error was: " << GetLastError() << L"\n";
}
}
else if (tmpStr == L"CLOSE")
{
// Abort the scanning process and close the file restore context
wcout << L"Aborting scan and closing file restore context..." << L"\n";
success = false;
}
}
}
else
{
DWORD err = GetLastError();
if (ERROR_INSUFFICIENT_BUFFER == err)
{
wcout << L"Insufficient buffer size! Current size is " << sizeof(buffer) << L" bytes; " <<
L" required size is " << neededBufferSize << L" bytes. Resizing..." << L"\n";
delete [] buffer;
buffer = new BYTE[neededBufferSize];
success = true;
}
else if (ERROR_NO_MORE_FILES == err)
{
wprintf(L"Scanning Complete.\n");
success = false;
}
else
{
wprintf(L"ScanRestorableFiles, Error #%u.\n", err);
}
}
}
delete [] buffer;
buffer = NULL;
}
//
// Program entry point
//
void __cdecl wmain(int argc, wchar_t *argv[])
{
HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
// Load the FMAPI DLL
hLib = ::LoadLibraryEx(L"fmapi.dll", NULL, NULL);
if ( !hLib )
{
wprintf(L"Could not load fmapi.dll. Error #%u.\n", GetLastError());
return;
}
// Dynamically link to the needed FMAPI functions
FuncCreateFileRestoreContext CreateFileRestoreContext;
CreateFileRestoreContext = reinterpret_cast<FuncCreateFileRestoreContext>( GetProcAddress( hLib, "CreateFileRestoreContext" ) );
FuncCloseFileRestoreContext CloseFileRestoreContext;
CloseFileRestoreContext = reinterpret_cast<FuncCloseFileRestoreContext>( GetProcAddress( hLib, "CloseFileRestoreContext" ) );
// Set the flags value for which kind of items we want to scan for
RESTORE_CONTEXT_FLAGS flags;
flags = (RESTORE_CONTEXT_FLAGS)(ContextFlagVolume | FlagScanRemovedFiles);
switch (argc)
{
case 2:
{
if (wcslen(argv[1]) < (sizeof(VOLUME) / sizeof(VOLUME[0]))) // ensure that argv[1] is not >255 chars long
{
wcscpy_s(VOLUME, argv[1]);
}
else
{
wcscpy_s(VOLUME, 255, argv[1]);
}
wcout << L"Volume set to: " << VOLUME << L"\n";
wcout << L"Defaulting to VOLUME search type; setting flags accordingly..." << L"\n";
flags = (RESTORE_CONTEXT_FLAGS)(ContextFlagVolume | FlagScanRemovedFiles);
break;
}
case 3:
{
if (wcslen(argv[1]) < (sizeof(VOLUME) / sizeof(VOLUME[0]))) // ensure that argv[1] is not >255 chars long
{
wcscpy_s(VOLUME, argv[1]);
}
else // if it is, only copy the first 255 chars
{
wcscpy_s(VOLUME, 255, argv[1]);
}
int len;
// Get length of second argument passed by the user
len = wcslen(argv[2]);
// Allocate new wchar_t array big enough to hold it
wchar_t *tmpArg = new (nothrow) wchar_t[len + 1];
// Ensure that the memory was allocated successfully
if (tmpArg == nullptr)
{
// Error allocating memory - bail out
wcout << L"Error allocating memory! Bailing out..." << L"\n";
return;
}
// Copy the argument string from argv to tmpArg
wcscpy_s(tmpArg, len + 1, argv[2]);
// Convert to uppercase
_wcsupr_s(tmpArg, len + 1);
// Check whether VOLUME or DISK search type was specified and set flags accordingly
if (wcscmp(tmpArg, L"VOLUME") == 0)
{
wcout << L"Volume set to: " << VOLUME << L"\n";
if (wcschr(VOLUME, L':') != NULL)
{
wcout << L"VOLUME search type specified; setting flags accordingly..." << L"\n";
flags = (RESTORE_CONTEXT_FLAGS)(ContextFlagVolume | FlagScanRemovedFiles);
}
else
{
wcout << L"VOLUME search type specified, but the volume identifier given doesn't appear " <<
L"to be a valid mounted volume! Please check your arguments and try again." << L"\n";
delete [] tmpArg;
return;
}
}
else if (wcscmp(tmpArg, L"DISK") == 0)
{
wcout << L"Disk set to: " << VOLUME << L"\n";
if (wcschr(VOLUME, L':') == NULL)
{
wcout << L"DISK search type specified; setting flags accordingly..." << L"\n";
flags = (RESTORE_CONTEXT_FLAGS)(ContextFlagDisk | FlagScanRemovedFiles);
}
else
{
wcout << L"DISK search type specified, but the disk identifier given doesn't appear " <<
L"to be a valid physical disk! Please check your arguments and try again." << L"\n";
delete [] tmpArg;
return;
}
}
else
{
wcout << L"UNKNOWN search type specified! Defaulting to VOLUME; setting flags accordingly..." << L"\n";
flags = (RESTORE_CONTEXT_FLAGS)(ContextFlagVolume | FlagScanRemovedFiles);
}
delete [] tmpArg;
break;
}
default:
{
// Set the default volume to scan here
wcscpy_s(VOLUME, L"\\\\.\\D:");
wcout << L"No arguments specified! Defaulting to VOLUME search of \\\\.\\D:" << L"\n";
break;
}
}
// Create the FileRestoreContext
PFILE_RESTORE_CONTEXT context = NULL;
if ( ! CreateFileRestoreContext(VOLUME, flags, 0, 0, FILE_RESTORE_VERSION_2, &context) )
{
DWORD err = GetLastError();
wprintf(L"Failed to Create FileRestoreContext, Error #%u.\n", err);
return;
}
else
{
wcout << L"Success! File restore context created! Value of context is: " << context << L"\n";
}
// Find restorable files starting at the given directory
Scan(context, SCAN_PATH);
// Close the context
if (context)
{
CloseFileRestoreContext(context);
context = NULL;
}
}
请注意,这在开始阶段仍然是一个非常初级的应用程序。它基本上开始扫描,默认情况下(如果未提供args)扫描D:\驱动器,然后尝试将已删除的文件还原到X:\ testfile.txt。对于可能不熟悉WinPE的用户,X:是WinPE环境中的系统驱动器,而D:通常是分配给在PE环境中引导到PE的计算机的OS驱动器的字母,具体取决于设置,其中C:是系统保留的分区。
对于找到的每个可恢复文件,它将暂停,并且如果用户输入文本“ RESTORE”并按ENTER键,它将尝试恢复该文件(此操作在第247行发生)。指示恢复状态的回调函数(通过FinalResult)返回错误代码6,GetLastError()也是如此。
对此非常感谢!