Created
May 17, 2015 19:37
-
-
Save kusano/6ef8ea4993170d49bf7e to your computer and use it in GitHub Desktop.
This file contains hidden or 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
#define WIN32_LEAN_AND_MEAN | |
#include <Windows.h> | |
#include <shellapi.h> | |
#include <tchar.h> | |
#include <locale.h> | |
#include <cstdio> | |
#include <vector> | |
#include <cassert> | |
#pragma comment(lib, "shell32.lib") | |
using namespace std; | |
const ULONGLONG invalid = 0xffffffffffffffffULL; | |
struct DATARUN | |
{ | |
ULONGLONG offset; | |
ULONGLONG length; | |
DATARUN(ULONGLONG offset, ULONGLONG length) | |
: offset(offset), length(length) {} | |
}; | |
void seek(HANDLE h, ULONGLONG position) | |
{ | |
LARGE_INTEGER pos; | |
pos.QuadPart = (LONGLONG)position; | |
LARGE_INTEGER result; | |
if (!SetFilePointerEx(h, pos, &result, SEEK_SET) || | |
pos.QuadPart != result.QuadPart) | |
throw "Failed to seek"; | |
} | |
vector<DATARUN> parseRunList(BYTE *runList, ULONGLONG totalCluster) | |
{ | |
vector<DATARUN> result; | |
ULONGLONG offset = 0; | |
while (true) | |
{ | |
BYTE offsetLen = *runList++; | |
if (offsetLen == 0x00) | |
break; | |
int ll = offsetLen&0xf; | |
int ol = offsetLen>>4; | |
if (ol == 0) | |
throw _T("Sparse file is not supported"); | |
_tprintf(_T("%x\n"), offsetLen); | |
if (ll >= 8 || ol >= 8) | |
throw _T("Invalid data run"); | |
ULONGLONG len = 0; | |
for (int i=0; i<ll; i++) | |
len |= *runList++ << (i*8); | |
LONGLONG ofs = 0; | |
for (int i=0; i<ol; i++) | |
ofs |= *runList++ << (i*8); | |
if (ofs >= (1LL<<((ol*8)-1))) | |
ofs -= 1LL<<(ol*8); | |
offset += ofs; | |
if (offset >= totalCluster) | |
throw _T("Invalid data run"); | |
result.push_back(DATARUN(offset, len)); | |
} | |
return result; | |
} | |
bool readMFTCluster(HANDLE h, ULONGLONG cluster, vector<DATARUN> &runList, | |
DWORD clusterSize, BYTE *buf, DWORD offset, DWORD size) | |
{ | |
assert(offset+size <= clusterSize); | |
static ULONGLONG cacheCluster = invalid; | |
static vector<BYTE> cache; | |
if (cache.size() != clusterSize) | |
{ | |
cache.resize(clusterSize); | |
cacheCluster = invalid; | |
} | |
if (cacheCluster != cluster) | |
{ | |
ULONGLONG vcn = 0; | |
bool ok = false; | |
for (DATARUN &run: runList) | |
{ | |
if (cluster < vcn + run.length) | |
{ | |
seek(h, (cluster - vcn + run.offset) * clusterSize); | |
DWORD read; | |
ok = ReadFile(h, &cache[0], clusterSize, &read, NULL) && | |
read == clusterSize; | |
break; | |
} | |
vcn += run.length; | |
} | |
if (!ok) | |
return false; | |
} | |
memcpy(buf, &cache[offset], size); | |
return true; | |
} | |
bool readRecord(HANDLE h, ULONGLONG recordIndex, vector<DATARUN> &MFTRunList, | |
DWORD recordSize, DWORD clusterSize, BYTE *buf) | |
{ | |
if (recordSize >= clusterSize) | |
{ | |
assert(recordSize%clusterSize == 0); | |
ULONGLONG recordCluster = recordIndex * (recordSize/clusterSize); | |
for (DWORD i=0; i<recordSize/clusterSize; i++) | |
{ | |
if (!readMFTCluster(h, recordCluster+i, MFTRunList, clusterSize, | |
buf + i*clusterSize, 0, clusterSize)) | |
return false; | |
} | |
} | |
else | |
{ | |
assert(clusterSize%recordSize == 0); | |
if (!readMFTCluster(h, recordIndex / (clusterSize/recordSize), MFTRunList, | |
clusterSize, buf, recordIndex * recordSize % clusterSize, | |
recordSize)) | |
return false; | |
} | |
return true; | |
} | |
int main() | |
{ | |
LPWSTR *argv = NULL; | |
HANDLE h = INVALID_HANDLE_VALUE; | |
HANDLE output = INVALID_HANDLE_VALUE; | |
try | |
{ | |
_tsetlocale(LC_ALL, _T("")); | |
// args | |
int argc; | |
argv = CommandLineToArgvW(GetCommandLineW(), &argc); | |
if (argc!=2 && argc!=4) | |
{ | |
_tprintf(_T("Usage:\n")); | |
_tprintf(_T(" ntfsdump DriveLetter\n")); | |
_tprintf(_T(" ntfsdump DriveLetter RecordIndex OutputFileName\n")); | |
return 0; | |
} | |
TCHAR drive[] = _T("\\\\?\\_:"); | |
drive[4] = argv[1][0]; | |
// Open volume | |
h = CreateFile( | |
drive, | |
GENERIC_READ, | |
FILE_SHARE_READ | FILE_SHARE_WRITE, | |
NULL, | |
OPEN_EXISTING, | |
0, | |
NULL); | |
if (h == INVALID_HANDLE_VALUE) | |
throw _T("Failed to open drive"); | |
// Boot sector | |
BYTE boot[0x200]; | |
DWORD read; | |
if (!ReadFile(h, boot, sizeof boot, &read, NULL) || | |
read != sizeof boot) | |
throw _T("Failed to read boot sector"); | |
LPCSTR oemID = LPCSTR(boot+0x3); | |
printf("OEM ID: \"%s\"\n", oemID); | |
if (memcmp(oemID, "NTFS ", 8) != 0) | |
throw _T("Volume is not NTFS"); | |
WORD bytePerSector = *LPWORD(boot+0x0b); | |
BYTE sectorPerCluster = *LPBYTE(boot+0x0d); | |
ULONGLONG totalSector = *PULONGLONG(boot+0x28); | |
ULONGLONG MFTCluster = *PULONGLONG(boot+0x30); | |
BYTE clusterPerRecord = *LPBYTE(boot+0x40); | |
DWORD clusterSize = sectorPerCluster * bytePerSector; | |
DWORD recordSize; | |
if (clusterPerRecord < 0x80) | |
recordSize = clusterPerRecord * clusterSize; | |
else | |
recordSize = 1 << -(clusterPerRecord-0x100); | |
ULONGLONG totalCluster = totalSector/sectorPerCluster; | |
_tprintf(_T("Byte/Sector: %u\n"), bytePerSector); | |
_tprintf(_T("Sector/Cluster: %u\n"), sectorPerCluster); | |
_tprintf(_T("Total Sector: %llu\n"), totalSector); | |
_tprintf(_T("Cluster of MFT: %llu\n"), MFTCluster); | |
_tprintf(_T("Cluster/Record: %u\n"), clusterPerRecord); | |
_tprintf(_T("Cluster Size (B): %u\n"), clusterSize); | |
_tprintf(_T("Record Size (B): %u\n"), recordSize); | |
// Read MFT size and data run | |
seek(h, MFTCluster*clusterSize); | |
vector<BYTE> MFTRecord(recordSize); | |
if (!ReadFile(h, &MFTRecord[0], recordSize, &read, NULL) || | |
read != recordSize) | |
throw _T("Failed to read MFT record"); | |
DWORD attribute = *LPWORD(&MFTRecord[0x14]); | |
BYTE *data = NULL; | |
while (data == NULL) | |
{ | |
// $ATTRIBUTE_LIST for MFT is not supported | |
if (attribute >= recordSize) | |
throw _T("Failed to find $Data attribute of MFT"); | |
DWORD id = *LPDWORD(&MFTRecord[attribute]); | |
if (id == 0xffffffff) | |
throw _T("Failed to find $Data attribute of MFT"); | |
if (id == 0x80) | |
data = &MFTRecord[attribute]; | |
DWORD length = *LPDWORD(&MFTRecord[attribute+0x4]); | |
attribute += length; | |
} | |
// MFT data must be non-residend | |
assert(*LPBYTE(data + 0x8) != 0); | |
ULONGLONG MFTSize = *PULONGLONG(data + 0x30); | |
ULONGLONG RecordNumber = MFTSize / recordSize; | |
_tprintf(_T("MFT size (B): %llu\n"), MFTSize); | |
_tprintf(_T("Record number: %llu\n"), RecordNumber); | |
BYTE *runList = data + *LPWORD(data + 0x20); | |
vector<DATARUN> MFTRunList = parseRunList(runList, totalCluster); | |
_tprintf(_T("MFT run list:\n")); | |
for (DATARUN &run: MFTRunList) | |
_tprintf(_T(" %16llx %16llx\n"), run.offset, run.length); | |
// Read File List | |
if (argc == 2) | |
{ | |
_tprintf(_T("File List:\n")); | |
vector<BYTE> record(recordSize); | |
for (ULONGLONG recordIndex=0; recordIndex<RecordNumber; recordIndex++) | |
{ | |
_tprintf(_T("%12lld"), recordIndex); | |
try | |
{ | |
if (!readRecord(h, recordIndex, MFTRunList, recordSize, clusterSize, &record[0])) | |
throw _T("Failed to read record"); | |
if (memcmp(&record[0], "FILE", 4) != 0) | |
{ | |
_tprintf(_T(" -\n")); | |
continue; | |
} | |
ULONGLONG refference = *PULONGLONG(&record[0x20]) & 0xffffffffffffULL; | |
if (refference != 0) | |
{ | |
_tprintf(_T(" extension for %llu\n"), refference); | |
continue; | |
} | |
WORD flag = *LPWORD(&record[0x16]); | |
switch (flag) | |
{ | |
case 0x0000: _tprintf(_T(" x ")); break; | |
case 0x0001: _tprintf(_T(" ")); break; | |
case 0x0002: _tprintf(_T(" x dir")); break; | |
case 0x0003: _tprintf(_T(" dir")); break; | |
default: _tprintf(_T(" ?????")); | |
} | |
DWORD attribute = *LPWORD(&record[0x14]); | |
while (true) | |
{ | |
// $File_Name outside the record is not supported | |
if (attribute >= recordSize) | |
throw _T("Failed to find $File_Name attribute"); | |
DWORD id = *LPDWORD(&record[attribute]); | |
if (id == 0xffffffff) | |
throw _T("Failed to find $File_Name attribute"); | |
if (id == 0x30) | |
{ | |
LPBYTE name = &record[attribute]; | |
if (*LPBYTE(name+0x8) != 0) | |
throw _T("Non-resident $File_Name is not supported"); | |
LPBYTE content = name + *LPWORD(name+0x14); | |
BYTE nameLength = *LPBYTE(content+0x40); | |
BYTE nameSpace = *LPBYTE(content+0x41); | |
LPWSTR fileName = LPWSTR(content+0x42); | |
if (nameSpace != 0x02) // 0x02 = DOS Name | |
{ | |
_tprintf(_T(" %.*s\n"), nameLength, fileName); | |
break; | |
} | |
} | |
DWORD length = *LPDWORD(&record[attribute+0x4]); | |
attribute += length; | |
} | |
} | |
catch (LPCWSTR error) | |
{ | |
_tprintf(_T(" %s\n"), error); | |
} | |
} | |
} | |
else if (argc == 4) | |
{ | |
ULONGLONG recordIndex = _tstoi64(argv[2]); | |
LPWSTR outputFile = argv[3]; | |
_tprintf(_T("Record index: %llu\n"), recordIndex); | |
_tprintf(_T("Output file name: %s\n"), outputFile); | |
vector<BYTE> record(recordSize); | |
if (!readRecord(h, recordIndex, MFTRunList, recordSize, clusterSize, &record[0])) | |
throw _T("Failed to read record"); | |
// Find $Data and $Attribute_List | |
DWORD attribute = *LPWORD(&record[0x14]); | |
LPBYTE data = NULL; | |
LPBYTE attributeList = NULL; | |
while (true) | |
{ | |
if (attribute >= recordSize) | |
break; | |
DWORD id = *LPDWORD(&record[attribute]); | |
if (id == 0xffffffff) | |
break; | |
switch (id) | |
{ | |
case 0x20: | |
if (attributeList == NULL) | |
attributeList = &record[attribute]; | |
break; | |
case 0x80: | |
// Some file contains two $Data attribute | |
// The second attribute is corrupted | |
if (data == NULL) | |
data = &record[attribute]; | |
break; | |
} | |
DWORD length = *LPDWORD(&record[attribute+0x4]); | |
attribute += length; | |
} | |
if (data != NULL) | |
{ | |
_tprintf(_T("$Data in the record\n")); | |
output = CreateFile(outputFile, GENERIC_WRITE, 0, | |
NULL, CREATE_ALWAYS, 0, NULL); | |
if (output == INVALID_HANDLE_VALUE) | |
throw _T("Failed to open output file"); | |
if (data[0x30] == 0) | |
{ | |
_tprintf(_T("Resident\n")); | |
DWORD size = *LPDWORD(data+0x10); | |
WORD offset = *LPWORD(data+0x14); | |
BYTE *content = data + offset; | |
_tprintf(_T("File size: %u\n"), size); | |
DWORD written; | |
if (!WriteFile(output, content, size, &written, NULL) || | |
written != size) | |
throw _T("Failed to write output file"); | |
} | |
else | |
{ | |
_tprintf(_T("Non-resident\n")); | |
ULONGLONG size = *PULONGLONG(data+0x30); | |
_tprintf(_T("File size: %llu\n"), size); | |
vector<DATARUN> runList = parseRunList( | |
data + *LPWORD(data+0x20), totalCluster); | |
ULONGLONG writeSize = 0; | |
_tprintf(_T("Run list:\n")); | |
vector<BYTE> cluster(clusterSize); | |
for (DATARUN &run: runList) | |
{ | |
_tprintf(_T(" %16llx %16llx\n"), run.offset, run.length); | |
seek(h, run.offset*clusterSize); | |
for (ULONGLONG i=0; i<run.length; i++) | |
{ | |
if (writeSize + run.length > size) | |
throw _T("File size is strange"); | |
if (!ReadFile(h, &cluster[0], clusterSize, &read, NULL) || | |
read != clusterSize) | |
throw _T("Failed to read cluster"); | |
DWORD s = DWORD(min(size - writeSize, clusterSize)); | |
DWORD written; | |
if (!WriteFile(output, &cluster[0], s, &written, NULL) || | |
written != s) | |
throw _T("Failed to write output file"); | |
writeSize += s; | |
} | |
} | |
if (writeSize != size) | |
{ | |
_tprintf(_T("Expected size: %llu\n"), size); | |
_tprintf(_T("Actual size: %llu\n"), writeSize); | |
throw _T("File size is strange"); | |
} | |
} | |
} | |
else | |
{ | |
_tprintf(_T("$Data in another record\n")); | |
if (attributeList == NULL) | |
throw _T("$Data nor $Attribute_List is not found"); | |
if (attributeList[0x8] != 0) | |
throw _T("Non-resident $Attribute_List is not supported"); | |
DWORD length = *LPDWORD(attributeList+0x10); | |
WORD offset = *LPWORD(attributeList+0x14); | |
output = CreateFile(outputFile, GENERIC_WRITE, FILE_SHARE_READ, | |
NULL, CREATE_ALWAYS, 0, NULL); | |
if (output == INVALID_HANDLE_VALUE) | |
throw _T("Failed to open output file"); | |
ULONGLONG fileSize = invalid; | |
ULONGLONG writeSize = 0; | |
vector<BYTE> cluster(clusterSize); | |
LPBYTE content = attributeList + offset; | |
DWORD p = 0; | |
while (p < length) | |
{ | |
DWORD id = *LPDWORD(content+p); | |
WORD len = *LPWORD(content+p+0x4); | |
ULONGLONG recordNum = *PULONGLONG(content+p+0x10) & 0xffffffffffffLL; | |
if (id == 0x80) | |
{ | |
_tprintf(_T("MFT extension: %lld\n"), recordNum); | |
vector<BYTE> extRecord(recordSize); | |
if (!readRecord(h, recordNum, MFTRunList, recordSize, clusterSize, &extRecord[0])) | |
throw _T("Failed to read record"); | |
if (memcmp(&extRecord[0], "FILE", 4) != 0) | |
throw _T("Extenion record is invalid"); | |
BYTE *data = &extRecord[*LPWORD(&extRecord[0x14])]; | |
if (*LPDWORD(data) != 0x80) | |
throw _T("Could not find $Data in the extension record"); | |
if (fileSize == invalid) | |
{ | |
fileSize = *PULONGLONG(data+0x30); | |
_tprintf(_T("File size: %llu\n"), fileSize); | |
} | |
vector<DATARUN> runList = parseRunList( | |
data + *LPWORD(data+0x20), totalCluster); | |
_tprintf(_T("Run list:\n")); | |
for (DATARUN &run: runList) | |
{ | |
//_tprintf(_T(" %16llx %16llx\n"), run.offset, run.length); | |
_tprintf(_T("%llx %16llx %16llx\n"), writeSize/clusterSize, run.offset, run.length); | |
seek(h, run.offset*clusterSize); | |
for (ULONGLONG i=0; i<run.length; i++) | |
{ | |
if (writeSize + run.length > fileSize) | |
throw _T("File size is strange"); | |
if (!ReadFile(h, &cluster[0], clusterSize, &read, NULL) || | |
read != clusterSize) | |
throw _T("Failed to read cluster"); | |
DWORD s = (DWORD)min(fileSize - writeSize, clusterSize); | |
DWORD written; | |
if (!WriteFile(output, &cluster[0], s, &written, NULL) || | |
written != s) | |
throw _T("Failed to write output file"); | |
writeSize += s; | |
} | |
} | |
} | |
p += len; | |
} | |
if (writeSize != fileSize) | |
{ | |
_tprintf(_T("Expected size: %llu\n"), fileSize); | |
_tprintf(_T("Actual size: %llu\n"), writeSize); | |
throw _T("File size is strange"); | |
} | |
} | |
_tprintf(_T("Success")); | |
} | |
} | |
catch (LPCWSTR error) | |
{ | |
_tprintf(_T("Error: %s\n"), error); | |
} | |
if (argv != NULL) | |
GlobalFree(argv); | |
if (h != INVALID_HANDLE_VALUE) | |
CloseHandle(h); | |
if (output != INVALID_HANDLE_VALUE) | |
CloseHandle(output); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment