summaryrefslogtreecommitdiff
path: root/src/scrounge.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/scrounge.c')
-rw-r--r--src/scrounge.c654
1 files changed, 654 insertions, 0 deletions
diff --git a/src/scrounge.c b/src/scrounge.c
new file mode 100644
index 0000000..8841aa9
--- /dev/null
+++ b/src/scrounge.c
@@ -0,0 +1,654 @@
+//
+// AUTHOR
+// N. Nielsen
+//
+// VERSION
+// 0.7
+//
+// LICENSE
+// This software is in the public domain.
+//
+// The software is provided "as is", without warranty of any kind,
+// express or implied, including but not limited to the warranties
+// of merchantability, fitness for a particular purpose, and
+// noninfringement. In no event shall the author(s) be liable for any
+// claim, damages, or other liability, whether in an action of
+// contract, tort, or otherwise, arising from, out of, or in connection
+// with the software or the use or other dealings in the software.
+//
+// SUPPORT
+// Send bug reports to: <nielsen@memberwebs.com>
+//
+
+// Scrounge.cpp
+//
+
+#include "stdafx.h"
+#include "ntfs.h"
+#include "ntfsx.h"
+#include "usuals.h"
+#include "drive.h"
+#include "locks.h"
+#include "scrounge.h"
+
+#define RET_ERROR(l) { ::SetLastError(l); bRet = TRUE; goto clean_up; }
+#define PASS_ERROR() {bRet = TRUE; goto clean_up; }
+#define RET_FATAL(l) { ::SetLastError(l); bRet = FALSE; goto clean_up; }
+#define PASS_FATAL() {bRet = FALSE; goto clean_up; }
+
+
+// ----------------------------------------------------------------------
+// Process a potential MFT Record. For directories create the directory
+// and for files write out the file.
+//
+// Current Directory is the output directory
+// hIn is an open drive handle
+// pInfo is partition info about hIn (needs to be a ref counted pointer)
+
+BOOL ProcessMFTRecord(PartitionInfo* pInfo, uint64 mftRecord, HANDLE hIn)
+{
+ // Declare data that needs cleaning up first
+ BOOL bRet = TRUE; // Return value
+ HANDLE hFile = NULL; // Output file handle
+ NTFS_Attribute* pAttribName = NULL; // Filename Attribute
+ NTFS_Attribute* pAttribData = NULL; // Data Attribute
+ NTFS_DataRun* pDataRun = NULL; // Data runs for nonresident data
+
+
+ // Tracks whether or not we output a file
+ bool bFile = false;
+
+ {
+
+ // Read the MFT record
+ NTFS_Record record(pInfo);
+ if(!record.Read(mftRecord, hIn))
+ PASS_ERROR();
+
+ NTFS_RecordHeader* pRecord = record.GetHeader();
+
+
+ // Check if this record is in use
+ if(!(pRecord->flags & kNTFS_RecFlagUse))
+ RET_ERROR(ERROR_SUCCESS);
+
+
+ // Info that we use later
+ WCHAR fileName[MAX_PATH + 1];
+ FILETIME ftCreated;
+ FILETIME ftModified;
+ FILETIME ftAccessed;
+ DWORD fileAttrib = 0;
+ uint64 mftParent = 0;
+ byte* pResidentData = NULL;
+
+
+ // Now get the name and info...
+ pAttribName = record.FindAttribute(kNTFS_FILENAME, hIn);
+ if(!pAttribName) RET_ERROR(ERROR_SUCCESS);
+
+
+ byte nameSpace = kNTFS_NameSpacePOSIX;
+ memset(fileName, 0, sizeof(fileName));
+
+ do
+ {
+ // TODO ASSUMPTION: File name is always resident
+ ASSERT(!pAttribName->GetHeader()->bNonResident);
+
+
+ // Get out all the info we need
+ NTFS_AttrFileName* pFileName = (NTFS_AttrFileName*)pAttribName->GetResidentData();
+
+ // There can be multiple filenames with different namespaces
+ // so choose the best one
+ if(NTFS_IsBetterNameSpace(nameSpace, pFileName->nameSpace))
+ {
+ // Dates
+ NTFS_MakeFileTime(pFileName->timeCreated, ftCreated);
+ NTFS_MakeFileTime(pFileName->timeModified, ftModified);
+ NTFS_MakeFileTime(pFileName->timeRead, ftAccessed);
+
+ // File Name
+ wcsncpy(fileName, (wchar_t*)(((byte*)pFileName) + sizeof(NTFS_AttrFileName)), pFileName->cFileName);
+ fileName[pFileName->cFileName] = 0;
+
+ // Attributes
+ if(pFileName->flags & kNTFS_FileReadOnly)
+ fileAttrib |= FILE_ATTRIBUTE_READONLY;
+ if(pFileName->flags & kNTFS_FileHidden)
+ fileAttrib |= FILE_ATTRIBUTE_HIDDEN;
+ if(pFileName->flags & kNTFS_FileArchive)
+ fileAttrib |= FILE_ATTRIBUTE_ARCHIVE;
+ if(pFileName->flags & kNTFS_FileSystem)
+ fileAttrib |= FILE_ATTRIBUTE_SYSTEM;
+
+ // Parent Directory
+ mftParent = NTFS_RefToSector(*pInfo, pFileName->refParent);
+
+ // Namespace
+ nameSpace = pFileName->nameSpace;
+ }
+ }
+ while(pAttribName->NextAttribute(kNTFS_FILENAME));
+
+
+ // Check if we got a file name
+ if(fileName[0] == 0)
+ RET_ERROR(ERROR_NTFS_INVALID);
+
+
+
+ // Check if it's the root
+ // If so then bumm out cuz we don't want to have anything to do with it
+ if(mftRecord == mftParent || // Root is it's own parent
+ !wcscmp(fileName, L".") || // Or is called '.'
+ !wcscmp(fileName, kNTFS_MFTName)) // Or it's the MFT
+ RET_ERROR(ERROR_SUCCESS);
+
+
+
+ // Create Parent folders
+ if(!ProcessMFTRecord(pInfo, mftParent, hIn))
+ PASS_FATAL();
+
+
+
+ // If it's a folder then create it
+ if(pRecord->flags & kNTFS_RecFlagDir)
+ {
+ // Try to change to dir
+ if(!SetCurrentDirectoryW(fileName))
+ {
+ // Otherwise create dir
+ if(CreateDirectoryW(fileName, NULL))
+ {
+ // And set attributes
+ SetFileAttributesW(fileName, fileAttrib);
+ SetCurrentDirectoryW(fileName);
+ }
+ }
+
+ wprintf(L"\\%s", fileName);
+ }
+
+
+ // Otherwise write the file data
+ else
+ {
+ // Write to the File
+ hFile = CreateFileW(fileName, GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, NULL);
+
+ uint16 nRename = 0;
+ wchar_t fileName2[MAX_PATH + 1];
+ wcscpy(fileName2, fileName);
+
+ // For duplicate files we add .x to the file name where x is a number
+ while(hFile == INVALID_HANDLE_VALUE && ::GetLastError() == ERROR_FILE_EXISTS
+ && nRename < 0x1000000)
+ {
+ wcscpy(fileName, fileName2);
+ wcscat(fileName, L".");
+ uint16 len = wcslen(fileName);
+
+ // Make sure we don't have a buffer overflow
+ if(len > MAX_PATH - 5)
+ break;
+
+ _itow(nRename, fileName + len, 10);
+ nRename++;
+
+ hFile = CreateFileW(fileName, GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, NULL);
+ }
+
+ wprintf(L"\\%s", fileName);
+ bFile = true;
+
+ // Check if successful after all that
+ if(hFile == INVALID_HANDLE_VALUE)
+ PASS_FATAL();
+
+
+ DWORD dwDone = 0;
+ uint64 fileSize = 0;
+
+
+ // Get the File's data
+ pAttribData = record.FindAttribute(kNTFS_DATA, hIn);
+ if(!pAttribData) RET_ERROR(ERROR_NTFS_INVALID);
+
+
+ // For Resident data just write it out
+ if(!pAttribData->GetHeader()->bNonResident)
+ {
+ if(!WriteFile(hFile, pAttribData->GetResidentData(), pAttribData->GetResidentSize(), &dwDone, NULL))
+ PASS_FATAL();
+ }
+
+ // For Nonresident data a bit more involved
+ else
+ {
+ pDataRun = pAttribData->GetDataRun();
+ ASSERT(pDataRun != NULL);
+
+ NTFS_AttribNonResident* pNonRes = (NTFS_AttribNonResident*)pAttribData->GetHeader();
+ fileSize = pNonRes->cbAttribData;
+
+ // Allocate a cluster for reading and writing
+ NTFS_Cluster clus;
+ if(!clus.New(pInfo))
+ RET_FATAL(ERROR_NOT_ENOUGH_MEMORY);
+
+
+ // Now loop through the data run
+ if(pDataRun->First())
+ {
+ do
+ {
+ // If it's a sparse cluster then just write zeros
+ if(pDataRun->m_bSparse)
+ {
+ memset(clus.m_pCluster, 0, clus.m_cbCluster);
+
+ for(uint32 i = 0; i < pDataRun->m_numClusters && fileSize; i++)
+ {
+ DWORD dwToWrite = clus.m_cbCluster;
+ if(!HIGHDWORD(fileSize) && dwToWrite > (DWORD)fileSize)
+ dwToWrite = (DWORD)fileSize;
+
+ if(!WriteFile(hFile, clus.m_pCluster, dwToWrite, &dwDone, NULL))
+ PASS_FATAL();
+
+ fileSize -= dwToWrite;
+ }
+ }
+
+ // Not sparse
+ else
+ {
+ // Add a lock on those clusters so we don't have to scrounge'm later
+ AddLocationLock(pInfo, CLUSTER_TO_SECTOR(*pInfo, pDataRun->m_firstCluster),
+ CLUSTER_TO_SECTOR(*pInfo, pDataRun->m_firstCluster + pDataRun->m_numClusters));
+
+ // Read and write clusters out
+ for(uint32 i = 0; i < pDataRun->m_numClusters && fileSize; i++)
+ {
+ DWORD dwToWrite = min(clus.m_cbCluster, (DWORD)fileSize);
+ uint64 sector = CLUSTER_TO_SECTOR(*pInfo, (pDataRun->m_firstCluster + i));
+
+ if(!clus.Read(pInfo, sector, hIn))
+ PASS_ERROR();
+
+ if(!WriteFile(hFile, clus.m_pCluster, dwToWrite, &dwDone, NULL))
+ PASS_FATAL();
+
+ fileSize -= dwToWrite;
+ }
+ }
+ }
+ while(pDataRun->Next());
+ }
+ }
+
+ // TODO: More intelligence needed here
+ if(fileSize != 0)
+ printf(" (Entire file not written)");
+
+ SetFileTime(hFile, &ftCreated, &ftAccessed, &ftModified);
+
+ CloseHandle(hFile);
+ hFile = NULL;
+
+ SetFileAttributesW(fileName, fileAttrib);
+ }
+ }
+
+ bRet = TRUE;
+ ::SetLastError(ERROR_SUCCESS);
+
+clean_up:
+ if(hFile && hFile != INVALID_HANDLE_VALUE)
+ CloseHandle(hFile);
+ if(pAttribName)
+ delete pAttribName;
+ if(pAttribData)
+ delete pAttribData;
+ if(pDataRun)
+ delete pDataRun;
+
+ if(bFile)
+ {
+ if(::GetLastError() != ERROR_SUCCESS)
+ {
+ printf(" (");
+ PrintLastError();
+ fputc(')', stdout);
+ }
+ }
+
+
+ return bRet;
+}
+
+
+// ----------------------------------------------------------------------
+// Helper function to print out errors
+
+void PrintLastError()
+{
+ DWORD dwErr = ::GetLastError();
+ switch(dwErr)
+ {
+ case ERROR_NTFS_INVALID:
+ printf("Invalid NTFS data structure");
+ break;
+
+ case ERROR_NTFS_NOTIMPLEMENT:
+ printf("NTFS feature not implemented");
+ break;
+
+ default:
+ {
+ LPVOID lpMsgBuf;
+
+ DWORD dwRet = ::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_MAX_WIDTH_MASK,
+ NULL,
+ dwErr,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
+ (LPTSTR) &lpMsgBuf,
+ 0,
+ NULL);
+
+ if(dwRet && lpMsgBuf)
+ {
+ // Remove return
+ ((LPTSTR)lpMsgBuf)[dwRet - 2] = 0;
+
+ wprintf((LPTSTR)lpMsgBuf);
+
+ // Free the buffer.
+ ::LocalFree(lpMsgBuf);
+ }
+ }
+ };
+}
+
+
+// ----------------------------------------------------------------------
+// Scrounge the partition for MFT Records and hand off to
+// ProcessMFTRecord for processing
+
+BOOL ScroungeMFTRecords(PartitionInfo* pInfo, HANDLE hIn)
+{
+ byte buffSec[kSectorSize];
+ DWORD dwDummy = 0;
+
+ uint64 numRecords = 0;
+
+ // Save current directory away
+ TCHAR curDir[MAX_PATH + 1];
+ GetCurrentDirectory(MAX_PATH, curDir);
+
+ // Loop through sectors
+ for(uint64 sec = pInfo->firstSector; sec < pInfo->lastSector; sec++)
+ {
+ // See if the current sector has already been read
+ if(CheckLocationLock(pInfo, sec))
+ {
+ // TODO: check this
+ sec--;
+ continue;
+ }
+
+ // Read the mftRecord
+ uint64 offRecord = SECTOR_TO_BYTES(sec);
+ LONG lHigh = HIGHDWORD(offRecord);
+ if(SetFilePointer(hIn, LOWDWORD(offRecord), &lHigh, FILE_BEGIN) == -1
+ && GetLastError() != NO_ERROR)
+ return FALSE;
+
+ if(!ReadFile(hIn, buffSec, kSectorSize, &dwDummy, NULL))
+ return FALSE;
+
+ // Check beginning of sector for the magic signature
+ if(!memcmp(&kNTFS_RecMagic, &buffSec, sizeof(kNTFS_RecMagic)))
+ {
+ // Move to right output directory
+ SetCurrentDirectory(curDir);
+
+ // Then process it
+ BOOL bRet = ProcessMFTRecord(pInfo, sec, hIn);
+
+ fputc('\n', stdout);
+
+ if(!bRet)
+ return FALSE;
+
+ }
+ }
+
+ return TRUE;
+}
+
+
+// ----------------------------------------------------------------------
+// Output strings
+
+const char kPrintHeader[] = "Scrounge (NTFS) Version 0.7\n\n";
+
+const char kPrintData[] = "\
+ Start Sector End Sector Cluster Size MFT Offset \n\
+==================================================================\n\
+";
+
+const char kPrintDrive[] = "\nDrive: %u\n";
+const char kPrintDriveInfo[] = " %-15u %-15u ";
+const char kPrintNTFSInfo[] = "%-15u %-15u";
+
+const char kPrintHelp[] = "\
+Recovers an NTFS partition with a corrupted MFT. \n\
+ \n\
+Usage: scrounge drive start end cluster mft [outdir] \n\
+ \n\
+ drive: Physical drive number. \n\
+ start: First sector of partition. \n\
+ end: Last sector of partition. \n\
+ cluster: Cluster size for the partition (in sectors). \n\
+ mft: Offset from beginning of partition to MFT (in sectors). \n\
+ outdir: Output directory (optional). \n\
+ \n\
+";
+
+
+// ----------------------------------------------------------------------
+// Info functions
+
+int PrintNTFSInfo(HANDLE hDrive, uint64 tblSector)
+{
+ byte sector[kSectorSize];
+
+ uint64 pos = SECTOR_TO_BYTES(tblSector);
+ LONG lHigh = HIGHDWORD(pos);
+ if(SetFilePointer(hDrive, LOWDWORD(pos), &lHigh, FILE_BEGIN) == -1
+ && GetLastError() != NO_ERROR)
+ return 1;
+
+ DWORD dwRead = 0;
+ if(!ReadFile(hDrive, sector, kSectorSize, &dwRead, NULL))
+ return 1;
+
+ NTFS_BootSector* pBoot = (NTFS_BootSector*)sector;
+ if(!memcmp(pBoot->sysId, kNTFS_SysId, sizeof(pBoot->sysId)))
+ printf(kPrintNTFSInfo, pBoot->secPerClus, pBoot->offMFT * pBoot->secPerClus);
+
+ wprintf(L"\n");
+ return 0;
+}
+
+
+int PrintPartitionInfo(HANDLE hDrive, uint64 tblSector)
+{
+ ASSERT(sizeof(Drive_MBR) == kSectorSize);
+ Drive_MBR mbr;
+
+ uint64 pos = SECTOR_TO_BYTES(tblSector);
+ LONG lHigh = HIGHDWORD(pos);
+ if(SetFilePointer(hDrive, LOWDWORD(pos), &lHigh, FILE_BEGIN) == -1
+ && GetLastError() != NO_ERROR)
+ return 1;
+
+ DWORD dwRead = 0;
+ if(!ReadFile(hDrive, &mbr, sizeof(Drive_MBR), &dwRead, NULL))
+ return 1;
+
+ if(mbr.sig == kMBR_Sig)
+ {
+ for(int i = 0; i < 4; i++)
+ {
+ if(mbr.partitions[i].system == kPartition_Extended ||
+ mbr.partitions[i].system == kPartition_ExtendedLBA)
+ {
+ PrintPartitionInfo(hDrive, tblSector + mbr.partitions[i].startsec);
+ }
+ else if(!mbr.partitions[i].system == kPartition_Invalid)
+ {
+ printf(kPrintDriveInfo, (uint32)tblSector + mbr.partitions[i].startsec, (uint32)tblSector + mbr.partitions[i].endsec);
+ PrintNTFSInfo(hDrive, tblSector + (uint64)mbr.partitions[i].startsec);
+ }
+ }
+ }
+
+ return 0;
+}
+
+const WCHAR kDriveName[] = L"\\\\.\\PhysicalDrive%d";
+
+int PrintData()
+{
+ printf(kPrintHeader);
+ printf(kPrintData);
+
+ WCHAR driveName[MAX_PATH];
+
+ // LIMIT: 256 Drives
+ for(int i = 0; i < 0x100; i++)
+ {
+ wsprintf(driveName, kDriveName, i);
+
+ HANDLE hDrive = CreateFile(driveName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
+ if(hDrive != INVALID_HANDLE_VALUE)
+ {
+ printf(kPrintDrive, i);
+
+ PrintPartitionInfo(hDrive, 0);
+
+ CloseHandle(hDrive);
+ }
+ }
+
+ return 2;
+}
+
+
+// ----------------------------------------------------------------------
+// Main Program
+
+int main(int argc, char* argv[])
+{
+ int curArg = 1;
+
+ if(argc < 2)
+ return PrintData();
+
+
+ // Check for flags
+ if(*(argv[curArg]) == '-' || *(argv[curArg]) == '/' )
+ {
+ char* arg = argv[curArg];
+ arg++;
+
+ while(*arg != '\0')
+ {
+ switch(tolower(*arg))
+ {
+
+ // Help
+ case 'h':
+ printf(kPrintHeader);
+ printf(kPrintHelp);
+ return 2;
+
+ default:
+ printf("scrounge: invalid option '%c'\n", *arg);
+ return PrintData();
+ }
+
+ arg++;
+ }
+
+ curArg++;
+ }
+
+ PartitionInfo* pInfo = CreatePartitionInfo();
+ if(!pInfo)
+ {
+ printf("scrounge: Out of Memory.\n");
+ return 1;
+ }
+
+ if(curArg + 5 > argc)
+ {
+ printf("scrounge: invalid option(s).\n");
+ return 2;
+ }
+
+ // Next param should be the drive
+ byte driveNum = atoi(argv[curArg++]);
+
+ // Followed by the partition info
+ pInfo->firstSector = atoi(argv[curArg++]);
+ pInfo->lastSector = atoi(argv[curArg++]);
+ pInfo->clusterSize = atoi(argv[curArg++]);
+ pInfo->offMFT = atoi(argv[curArg++]);
+
+ if(pInfo->firstSector == 0 ||
+ pInfo->lastSector == 0 ||
+ pInfo->clusterSize == 0 ||
+ pInfo->offMFT == 0)
+ {
+ printf("scrounge: invalid option(s).\n");
+ return 2;
+ }
+
+// pInfo->clusterSize = 8;
+// pInfo->firstSector = 20482938/*128*/;
+// pInfo->lastSector = 80019765/*15358077*/;
+// pInfo->offMFT = 32;
+
+ if(curArg <= argc)
+ SetCurrentDirectoryA(argv[curArg++]);
+
+ WCHAR driveName[MAX_PATH];
+
+ wsprintf(driveName, kDriveName, driveNum);
+
+ HANDLE hDrive = CreateFile(driveName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
+ if(hDrive == INVALID_HANDLE_VALUE)
+ {
+ printf("scrounge: Can't open drive %d.\n", driveNum);
+ return 2;
+ }
+
+ if(!ScroungeMFTRecords(pInfo, hDrive))
+ {
+ printf("scrounge: ");
+ PrintLastError();
+ fputc('\n', stdout);
+ return 2;
+ }
+
+ FreePartitionInfo(pInfo);
+
+ return 0;
+} \ No newline at end of file