Skip to content

Instantly share code, notes, and snippets.

@kusano
Created May 17, 2015 19:37
Show Gist options
  • Save kusano/6ef8ea4993170d49bf7e to your computer and use it in GitHub Desktop.
Save kusano/6ef8ea4993170d49bf7e to your computer and use it in GitHub Desktop.
#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