Yesterday, early morning, I
was working on a friend’s Windows PC. The plan was simple: migrate the Windows
install from an HDD partition to a smaller SSD by freeing up space, running
ntfsresize
to shrink the partition down to something that fits on the SSD, and then dd
-ing
it over. I figured ntfsresize
would be pretty safe and well-tested, since it’s
used by pretty much every Linux distro installer to resize Windows partitions.
Everything was going according to plan, there was enough free space, and
ntfsresize
did its thing with no error messages. The next step after a resize
is always to run chkdsk
from Windows, but I was curious and tried ntfsfix
in
readonly mode just to see what it found. It complained loudly, which I figured
was odd, but perhaps expected. I dd
-ed the partition over to the SSD, then went
to the Windows installer environment and ran chkdsk
on it.
Hundreds of thousands of error messages later, most of the contents of the
partition were gone, including all of C:\Windows
and C:\Users
. Crap.
It’s worth noting that I don’t personally keep any valuable data on NTFS. I also keep backups. So, had this happened to me, restoring from backup would’ve been the resonable option. However, this was a PC I was working on for a friend, and there was ample storage available (on another drive) to have performed a full file-based backup beforehand, yet I did not do so, so I considered myself at fault. While most of the critical data is backed up to cloud-based services, a lot of misc bits and pieces would’ve been lost, plus a Windows reinstall would’ve required a very lengthy process of reinstalling every app again.
Now, the good thing is that I still had the original HDD partition around,
untouched after ntfsresize
did its thing, before chkdsk
. And, unlike the
usual use case of installing Linux, the tail end of the partition hadn’t been
overwritten with anything else, since the goal wasn’t to resize the source
partition but rather to copy to a smaller one on the target SSD. My intuition
told me that a bug in ntfsresize
had caused something to go horribly wrong,
but that there was a good chance that the corruption could be mostly (perhaps
even completely) fixed, provided I had a good understanding of exactly what had
happened. And since ntfsresize
’s job is to move data to empty space, anything
it had touched should still be in the original location too, except for
metadata changes that it had to make.
I spent most of the next day learning more about NTFS than I ever wanted to.
Step one, of course, is to make an image of the victim partition. Or three, just
to be sure. I actually dd
-ed the entire HDD over to a portable USB3 HDD too,
including an unrelated sibling partition, just so I had a pristine dump
including partition layout.
Image in hand, let’s look more closely at what ntfsfix
is complaining about.
$ ntfsfix -n fail.img
Mounting volume... Failed to load runlist for $MFT/$DATA.
highest_vcn = 0xbb7e, last_vcn - 1 = 0x54bbf
Failed to load $MFT: Input/output error
FAILED
Attempting to correct errors... Failed to load runlist for $MFT/$DATA.
highest_vcn = 0xbb7e, last_vcn - 1 = 0x54bbf
Failed to load $MFT: Input/output error
FAILED
Failed to startup volume: Input/output error
Checking for self-located MFT segment... OK
The startup data can be fixed, but no change was requested
Volume is corrupt. You should run chkdsk.
No change made
That looks pretty bad. $MFT
is the Master File Table, the inode table in
NTFS-speak. It seems it is unloadable or corrupted. In particular, its
‘runlist’, which is NTFS-speak for extent map, has an issue.
NTFS has a fairly uniform design where everything in the entire partition (except the backup bootsector, for some reason) is pointed to by the MFT, including the MFT itself. The boot sector (sector 0 of the partition) points at the beginning of the MFT, and then the MFT points at everything else. This also means that the MFT has to contain the list of extents of the MFT itself, which poses some chicken-and-egg issues.
Building a debug version of ntfsprogs
and running ntfsfix
provides more
info:
$ ~/software/ntfs-3g_ntfsprogs-2015.3.14/ntfsprogs/ntfsfix -n fail.img
Mounting volume... ntfs_pread(): pos 0, count 512
<.. snip ..>
ntfs_mapping_pairs_decompress_i(): Entering for attr 0x80.
More extents to follow; deltaxcn = 0xbb7e, max_cluster = 0x54bbf
Mapping pairs array successfully decompressed:
NTFS-fs DEBUG: Dumping runlist (values in hex):
VCN LCN Run length
0 786432 26096
26096 9365576 300
26396 11638698 300
26696 14869609 300
26996 21033733 300
27296 2498893 300
27596 3716714 300
27896 5929635 300
<.. snip 60 more lines like this ..>
45953 3891743 293
46246 4681687 293
46539 5107130 292
46831 10097059 292
47123 11702900 292
47415 12898945 292
47707 18907990 292
47999 LCN_RL_NOT_MAPPED 299073
347072 LCN_ENOENT 0 (runlist end)
highest_vcn=0xc0000770000007c
next_vcn=0xbb7f
ntfs_attr_lookup(): Entering for attribute type 0x80
ntfs_external_attr_find(): Entering for inode 0, attribute type 0x80.
ntfs_attr_find(): attribute type 0x80.
ntfs_attr_find(): attribute type 0x80.
Failed to load runlist for $MFT/$DATA.
highest_vcn = 0xbb7e, last_vcn - 1 = 0x54bbf
ntfs_inode_real_close(): Entering for inode 0
Failed to load $MFT: Input/output error
<.. snip ..>
The MFT runlist is stored in the first entry of the MFT, and lists all the
extents that make up the MFT. Normally, as far as I know, the MFT is in just
one big chunk, perhaps a few if it grows too large (incidentally, I knew that
this partition had a problem with a million or so files of junk in
C:\Windows\Temp
that I was hoping to get rid of after the transfer, once I was
on the SSD and things were faster; this partition is also from an install that
has been upgraded and is quite a few years old, so this might explain a larger
than average MFT). Here, though, it seems to be broken up into 80-odd tiny bits
and pieces after one big chunk at the beginning. Worse, only 47999 clusters of
the MFT are accounted for, out of 347072 expected ones. Most of the MFT is gone.
Ouch.
Let’s look at the beginning of the MFT in more detail. It’s stored at logical
cluster 786432 (that’s 0xc0000
; the “values in hex” bit is a lie, but I prefer
to work in hex), and clusters are 4K in this filesystem:
$ dd if=fail.img bs=4096 count=1 skip=$((0xc0000)) of=mft.bin
$ hexdump -vC mft.bin
MFT entries are 1024 bytes each, and the first one is $MFT
itself. Here’s what
it looks like:
00000000 46 49 4c 45 30 00 03 00 85 40 4f 0e 71 00 00 00 |FILE0....@O.q...| 00000010 01 00 01 00 38 00 01 00 00 04 00 00 00 04 00 00 |....8...........| 00000020 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00 |................| 00000030 50 f0 32 2c 00 00 00 00 10 00 00 00 60 00 00 00 |P.2,........`...| 00000040 00 00 18 00 00 00 00 00 48 00 00 00 18 00 00 00 |........H.......| 00000050 1c 7d 64 0d 1b a1 c8 01 1c 7d 64 0d 1b a1 c8 01 |.}d......}d.....| 00000060 1c 7d 64 0d 1b a1 c8 01 1c 7d 64 0d 1b a1 c8 01 |.}d......}d.....| 00000070 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000080 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 |................| 00000090 00 00 00 00 00 00 00 00 20 00 00 00 98 00 00 00 |........ .......| 000000a0 00 00 00 00 00 00 0f 00 80 00 00 00 18 00 00 00 |................| 000000b0 10 00 00 00 20 00 00 1a 00 00 00 00 00 00 00 00 |.... ...........| 000000c0 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 |................| 000000d0 30 00 00 00 20 00 00 1a 00 00 00 00 00 00 00 00 |0... ...........| 000000e0 00 00 00 00 00 00 01 00 03 00 00 00 00 00 00 00 |................| 000000f0 80 00 00 00 20 00 00 1a 00 00 00 00 00 00 00 00 |.... ...........| 00000100 00 00 00 00 00 00 01 00 01 00 00 00 00 00 00 00 |................| 00000110 b0 00 00 00 20 00 00 1a 00 00 00 00 00 00 00 00 |.... ...........| 00000120 00 00 00 00 00 00 01 00 0e 00 00 00 00 00 00 00 |................| 00000130 30 00 00 00 68 00 00 00 00 00 18 00 00 00 03 00 |0...h...........| 00000140 4a 00 00 00 18 00 01 00 05 00 00 00 00 00 05 00 |J...............| 00000150 1c 7d 64 0d 1b a1 c8 01 1c 7d 64 0d 1b a1 c8 01 |.}d......}d.....| 00000160 1c 7d 64 0d 1b a1 c8 01 1c 7d 64 0d 1b a1 c8 01 |.}d......}d.....| 00000170 00 00 bc 54 00 00 00 00 00 00 6c 52 00 00 00 00 |...T......lR....| 00000180 06 00 00 00 00 00 00 00 04 03 24 00 4d 00 46 00 |..........$.M.F.| 00000190 54 00 00 00 00 00 00 00 80 00 00 00 10 02 00 00 |T...............| 000001a0 01 00 40 00 00 00 01 00 00 00 00 00 00 00 00 00 |..@.............| 000001b0 7e bb 00 00 00 00 00 00 40 00 00 00 00 00 00 00 |.K......@.......| 000001c0 00 00 bc 54 00 00 00 00 00 00 bc 54 00 00 00 00 |...T.......T....| 000001d0 00 00 bc 54 00 00 00 00 32 f0 65 00 00 0c 42 2c |...T....2.e...C.| 000001e0 01 48 e8 82 00 32 2c 01 62 af 22 32 2c 01 bf 4c |........b."2,..L| 000001f0 31 32 2c 01 9c 0e 5e 42 2c 01 48 2e e5 fe 50 f0 |12,...^B,.H...P.| 00000200 01 1d 95 12 32 2c 01 39 c4 21 32 2c 01 a9 22 08 |....2,.9.!2,..".| 00000210 32 2b 01 66 70 13 32 2b 01 fa ed 39 32 2b 01 e3 |2+.fp.2+...92+..| 00000220 c5 14 32 2b 01 78 9f 70 42 2b 01 3f 31 f0 fe 32 |..2+.x.pB+.?1..2| 00000230 2b 01 e2 24 1e 32 2b 01 fc 39 21 32 2b 01 98 28 |+..$.2+..9!2+..(| 00000240 4f 32 2a 01 07 4b 14 32 2a 01 c1 22 0a 32 2a 01 |O2*..K.2*..".2*.| 00000250 4f d8 45 32 2a 01 7d f1 28 42 2a 01 ea 59 ff fe |O.E2*.}.(B*..Y..| 00000260 32 2a 01 8d e0 06 32 2a 01 84 80 65 32 2a 01 49 |2*....2*...e2*.I| 00000270 ce 29 42 29 01 d4 a1 62 ff 32 29 01 89 bb 0c 32 |.)B)...b.2)....2| 00000280 29 01 ed ce 04 32 29 01 62 5f 21 32 29 01 17 07 |)....2).b_!2)...| 00000290 09 32 29 01 d1 22 55 32 29 01 23 ec 04 32 29 01 |.2).."U2).#..2).| 000002a0 c4 33 05 42 29 01 f2 ba 71 ff 32 28 01 97 7c 08 |.3.B)...q.2(..|.| 000002b0 32 28 01 8a 6f 15 32 28 01 e3 99 25 32 28 01 87 |2(..o.2(...%2(..| 000002c0 5f 26 32 28 01 1b 85 0e 32 28 01 b5 49 3f 42 28 |_&2(....2(..I?B(| 000002d0 01 f9 aa 3a ff 32 27 01 c4 ee 0c 32 27 01 b9 6b |...:.2'....2'..k| 000002e0 15 32 27 01 97 6e 10 32 27 01 fc 74 16 32 27 01 |.2'..n.2'..t.2'.| 000002f0 47 24 3e 42 27 01 d0 18 8a 00 42 27 01 4c bd fb |G$>B'.....B'.L..| 00000300 fe 32 27 01 a1 30 15 32 26 01 d0 43 0c 32 26 01 |.2'..0.2&..C.2&.| 00000310 44 a7 0b 32 26 01 9d 27 0f 32 26 01 9e 83 0f 32 |D..2&..'.2&....2| 00000320 26 01 fe da 24 32 26 01 d0 7a 84 32 26 01 72 db |&...$2&..z.2&.r.| 00000330 04 32 26 01 30 a9 13 32 26 01 fb 43 17 32 25 01 |.2&.0..2&..C.2%.| 00000340 12 97 37 32 25 01 2d 29 2a 32 25 01 55 3c 4d 32 |..72%.-)*2%.U<M2| 00000350 25 01 9f 68 1b 32 25 01 36 96 29 42 25 01 f3 25 |%..h.2%.6.)B%..%| 00000360 bd fe 32 25 01 ed e3 1a 32 25 01 7c 3d 0c 32 25 |..2%....2%.|=.2%| 00000370 01 1f 56 17 32 25 01 4f b0 13 32 25 01 c9 30 cd |..V.2%.O..2%..0.| 00000380 32 25 01 b8 0d 0c 32 24 01 e3 7d 06 32 24 01 e9 |2%....2$..}.2$..| 00000390 23 4c 32 24 01 d1 80 18 32 24 01 0d 40 12 32 24 |#L2$....2$..@.2$| 000003a0 01 d5 b0 5b 00 24 01 32 b0 00 00 00 50 00 00 00 |...[.$.2....P...| 000003b0 01 00 40 00 00 00 0e 00 00 00 00 00 00 00 00 00 |..@.............| 000003c0 2b 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 |+.......@.......| 000003d0 00 c0 02 00 00 00 00 00 60 b3 02 00 00 00 00 00 |........`.......| 000003e0 60 b3 02 00 00 00 00 00 31 01 ff ff 0b 31 2b 8f |`.......1....1+.| 000003f0 8c 04 00 08 80 fa ff ff ff ff ff ff 00 00 50 f0 |..............P.|
I found this NTFS cheat sheet invaluable for figuring out the data structures.
MFT entries (files and file-like things) contain attributes. Here we see the FILE header followed by several attributes and a terminator/padding at the end:
- Standard Information (0x10)
- Attribute List (0x20)
- File Name (0x30)
- Data (0x80)
- Bitmap (0xb0)
Attributes can be resident (present inline in the MFT) or nonresident (present on raw clusters on disk, referenced by a runlist of extents). The MFT points to itself, so its data attribute is always nonresident, and therefore always contains a runlist describing the blocks occupied by the MFT.
A picture is starting to emerge here. It seems that the MFT entry for the MFT
itself is full to the brim with the runlist for the MFT. Normally, there are
mechanisms for breaking this out into multiple MFT entries/slots, for cases of
very fragmented files, but due to the chicken-and-egg issue with the MFT,
chances are that wouldn’t work here. It seems ntfsresize
couldn’t fit all the
required MFT extents into a resident attribute of the MFT entry itself… and
it just truncated the list.
Interestingly, there’s an Attribute List attribute,
which is normally used to point to other MFT entries where attributes may spill
into when they do not fit into a single entry, but in this case it just points
back into the same MFT entry (0), which seems useless and a waste of space.
Perhaps this happened as part of a half-complete attempt to append an extra
runlist to the $MFT
entry?
This should be recoverable. Assuming the MFT chunks actually exist on the drive, it should be possible to locate them, reassemble them, build a new MFT, and dump out the filesystem. Why is the MFT so fragmented, though? It sounds like this would be ntfsresize’s doing. To figure out what happened, I wanted to take a look at the backup MFT. The backup MFT (which is actually just a backup of the first 4 MFT entries) is normally stored towards the middle of the volume. The volume was ~314G and was being resized down to ~100G, so the old backup MFT should still be in the tail end of the partition, untouched.
To find things that look like an MFT, I used
bgrep. The second MFT entry is $MFTMirr
,
the backup MFT, which seems like a good string to grep for (in UTF-16, prefixed
with its length and type):
$ bgrep '080324004d00460054004d00690072007200' fail.img
fail.img: c00004f0
fail.img: 2067334f0
fail.img: 114e7d24f0
fail.img: 26496944f0
fail.img: 26f39174f0
fail.img: 27201fc4f0
fail.img: 2765aff4f0
fail.img: 2871fc04f0
fail.img: 28d28d74f0
fail.img: 2a24b4c4f0
fail.img: 2af62574f0
fail.img: 2b45a984f0
fail.img: 2b7a6b04f0
fail.img: 2bb164f4f0
fail.img: 2bf290f4f0
fail.img: 2c954c44f0
fail.img: 2ca31fb0f0
fail.img: 2ca337f0f0
fail.img: 2d04b384f0
fail.img: 2deb8bc4f0
fail.img: 2f41ddc4f0
fail.img: 2f741304f0
fail.img: 3139d004f0
fail.img: 31d42284f0
fail.img: 3effcbc4f0
fail.img: 40d5e574f0
<snipped some false positives>
That’s a lot of MFTs! Time to write a script to figure out exactly what they
are. This script
evolved during the rescue process, but essentially it dumps the entries
and some attributes of the MFT instances found, and also, for the $MFT
entry
itself, it collects the data runlist and pieces together (what is available of)
the MFT.
The output is pretty long, but looks like this:
MFT @ 0xc0000000
seq 1 hard 1 attr 0x38 flags 0001 used 1024 alloc 1024 base 0x0 next_id 0x10 recno 0
type 0x10 len 0x60 fc 0 nlen 0 noff 0x18 aflags 0x0 aid 0x0
date: 1c8a11b0d647d1c
type 0x20 len 0x98 fc 0 nlen 0 noff 0x0 aflags 0x0 aid 0xf
type 0x30 len 0x68 fc 0 nlen 0 noff 0x18 aflags 0x0 aid 0x3
u'$MFT'
type 0x80 len 0x210 fc 1 nlen 0 noff 0x40 aflags 0x0 aid 0x1
> mft_0xc0000000.bin
32f06500<..snip..>00240132
vcn=0x0 lcn=0xc0000 run=0x65f0
vcn=0x65f0 lcn=0x8ee848 run=0x12c
vcn=0x671c lcn=0xb197aa run=0x12c
vcn=0x6848 lcn=0xe2e469 run=0x12c
<..snip..>
vcn=0xb6ef lcn=0x9a11a3 run=0x124
vcn=0xb813 lcn=0xb29274 run=0x124
vcn=0xb937 lcn=0xc4d281 run=0x124
vcn=0xba5b lcn=0x1208356 run=0x124
type 0xb0 len 0x50 fc 1 nlen 0 noff 0x40 aflags 0x0 aid 0xe
3101ffff0b312b8f8c04000880faffff
vcn=0x0 lcn=0xbffff run=0x1
vcn=0x1 lcn=0x108c8e run=0x2b
seq 1 hard 1 attr 0x38 flags 0001 used 344 alloc 1024 base 0x0 next_id 0x4 recno 1
type 0x10 len 0x60 fc 0 nlen 0 noff 0x18 aflags 0x0 aid 0x0
date: 1c8a11b0d647d1c
type 0x30 len 0x70 fc 0 nlen 0 noff 0x18 aflags 0x0 aid 0x2
u'$MFTMirr'
type 0x80 len 0x48 fc 1 nlen 0 noff 0x40 aflags 0x0 aid 0x1
3101336720000000
vcn=0x0 lcn=0x206733 run=0x1
<..snip more MFT candidates..>
Some of the MFT headers were clearly from other filesystems (e.g different
creation date), but a lot of them seemed to be from this FS.
Interestingly, none of them are the backup MFT. All of the MFT headers
point $MFTMirr
at cluster number 0x206733, which is ~8GB into the partition,
so it seems this may have originally been a smaller partition that was resized
up, or the backup MFT was never in the middle of the volume, but in any case
ntfsresize
didn’t have to move it back into the volume; therefore, I was
lucky to find these lying around in the image.
I found several runlists for the MFT that seem to match:
32f06500000c43d047037f08c60300ff
vcn=0x0 lcn=0xc0000 run=0x65f0
vcn=0x65f0 lcn=0x3d2087f run=0x347d0
32f06500000c43904e037f08c60300ff
vcn=0x0 lcn=0xc0000 run=0x65f0
vcn=0x65f0 lcn=0x3d2087f run=0x34e90
32f06500000c439184037f08c60342ff1d39e1f6fe00ffff
vcn=0x0 lcn=0xc0000 run=0x65f0
vcn=0x65f0 lcn=0x3d2087f run=0x38491
vcn=0x3ea81 lcn=0x2c8e9b8 run=0x1dff
32f06500000c439184037f08c60342001e39e1f6fe313fa04406000880faffff
vcn=0x0 lcn=0xc0000 run=0x65f0
vcn=0x65f0 lcn=0x3d2087f run=0x38491
vcn=0x3ea81 lcn=0x2c8e9b8 run=0x1e00
vcn=0x40881 lcn=0x2cf2e58 run=0x3f
32f06500000c439184037f08c603<snip>80faffff
vcn=0x0 lcn=0xc0000 run=0x65f0
vcn=0x65f0 lcn=0x3d2087f run=0x38491
vcn=0x3ea81 lcn=0x2c8e9b8 run=0x1e00
vcn=0x40881 lcn=0x2cf2e58 run=0xa40
vcn=0x412c1 lcn=0x35db3d0 run=0xdeb0
vcn=0x4f171 lcn=0x3680000 run=0x2cf
32f06500000c43d0c004f7e3e70300ff
vcn=0x0 lcn=0xc0000 run=0x65f0
vcn=0x65f0 lcn=0x3f3e3f7 run=0x4c0d0
32f06500000c43d1c004f7e3e70342ff249a3e9f0000ffff
vcn=0x0 lcn=0xc0000 run=0x65f0
vcn=0x65f0 lcn=0x3f3e3f7 run=0x4c0d1
vcn=0x526c1 lcn=0x4932291 run=0x24ff
32f06500000c43d0e504ab92f90300ff
vcn=0x0 lcn=0xc0000 run=0x65f0
vcn=0x65f0 lcn=0x40592ab run=0x4e5d0
I don’t know why these MFT header copies were lying around in the rest of the partition, but they seem to paint a picture of the history of MFT size and fragmentation in this filesystem. I’ve sorted them above in what I think is chronological order. It seems the MFT grew to 6 extents at one point, before being eventually consolidated back to 2 again.
Taking the MFT dumps built from these runlists, I looked at the offsets. Of the
above candidates, only the last one had sensible MFT data after the first chunk,
so it was probably the location of the MFT before ntfsresize
ran. Its size,
0x65f0 + 0x4e5d0 = 0x54bc0, also matches the size of the MFT that ntfsfix
was
looking for.
At this point the plan is to search the HDD for MFT chunks, and use the old MFT
as a template to lay them out in the correct order (the fact that MFT entries
have their own index in the header also helps), basically building the missing
entries of the runlist that ntfsresize
truncated. To start, though, let’s
compare what we have of the MFT as ntfsresize
wrote it (the original first
extent plus 80-odd chunks) with what was in the original location:
$ cmp mft_0xc0000000.bin mft_0x31d4228000.bin
cmp: EOF on mft_0xc0000000.bin
Huh. Of course the beginning of the MFT is the same, as it’s the same extent on
disk… but it seems the 80-odd chunks that ntfsresize
relocated are also
identical to that span of data in the old extent. Could it be that ntfsresize
first relocated everything else, and then did the MFT last? This would explain
why the MFT got the short end of the stick and ended up broken up into tons
of tiny fragments, as all the larger contiguous spaces would’ve been used by
relocated files already. In that case, the old MFT location should be fully up
to date with the state of the partition.
If this is indeed the case, then recovery becomes very easy: overwrite the MFT runlist with the old one, and insert the correct final cluster number (MFT size):
000001a0 01 00 40 00 00 00 01 00 00 00 00 00 00 00 00 00 |..@.............| 000001b0 bf 4b 05 00 00 00 00 00 40 00 00 00 00 00 00 00 |.K......@.......| 000001c0 00 00 bc 54 00 00 00 00 00 00 bc 54 00 00 00 00 |...T.......T....| 000001d0 00 00 bc 54 00 00 00 00 32 f0 65 00 00 0c 43 d0 |...T....2.e...C.| 000001e0 e5 04 ab 92 f9 03 00 ff 62 af 22 32 2c 01 bf 4c |........b."2,..L| 000001f0 31 32 2c 01 9c 0e 5e 42 2c 01 48 2e e5 fe 50 f0 |12,...^B,.H...P.| 00000200 01 1d 95 12 32 2c 01 39 c4 21 32 2c 01 a9 22 08 |....2,.9.!2,..".| 00000210 32 2b 01 66 70 13 32 2b 01 fa ed 39 32 2b 01 e3 |2+.fp.2+...92+..| 00000220 c5 14 32 2b 01 78 9f 70 42 2b 01 3f 31 f0 fe 32 |..2+.x.pB+.?1..2| 00000230 2b 01 e2 24 1e 32 2b 01 fc 39 21 32 2b 01 98 28 |+..$.2+..9!2+..(| 00000240 4f 32 2a 01 07 4b 14 32 2a 01 c1 22 0a 32 2a 01 |O2*..K.2*..".2*.|
The attribute is now oversized, but the 00
terminator for the runlist
(I added ff
but I don’t think it’s required) should be all that’s needed to make it
work.
Write it over the MFT and its backup:
$ dd if=mft.bin of=fail.img bs=4096 seek=$((0xc0000)) count=1 conv=notrunc
$ dd if=mft.bin of=fail.img bs=4096 seek=$((0x206733)) count=1 conv=notrunc
Finally, put back the volume size in the boot sector, to make the filesystem cover the entire partition (since the new old MFT extent is, of course, beyond the end to which the partition was resized):
$ dd if=fail.img of=bootsect.bin bs=512 count=1
00000000 eb 52 90 4e 54 46 53 20 20 20 20 00 02 08 00 00 |.R.NTFS .....|
00000010 00 00 00 00 00 f8 00 00 3f 00 ff 00 00 50 38 01 |........?....P8.|
00000020 00 00 00 00 80 00 00 00 f8 af aa 24 00 00 00 00 |........p;......|
00000030 00 00 0c 00 00 00 00 00 33 67 20 00 00 00 00 00 |........3g .....|
$ dd if=bootsect.bin of=fail.img bs=512 count=1 conv=notrunc
The filesystem bitmap will now be incorrect (wrong size and contents), but
chkdsk
should be able to fix that.
A total of 26 bytes patched… and a whole day spent figuring out which 26 bytes to patch.
Did it work?
Amazingly enough, chkdsk gave the filesystem a clean bill of
health save for the bitmap and the uppercasing table (?). After formatting
the new SSD, using robocopy
to perform a file-level copy, manually re-creating
directory junctions that robocopy
can’t transfer, using bcdboot
and
bootsect
to put the bootloader back into a sane state, and messing with
the registry to convince windows that the SSD is its new C:
drive (all
of which are Windowsisms that I can’t claim much experience with, but which
Google will tell you all about and which are outside the scope of this post),
the entire OS is back the way it was, except now running much faster from the
SSD. Victory.