Writeup “In the Shadows”

This is a writeup on one of the tasks prepared for the CTFZone qualifying stage, that took place in late November. You can also read about how we organized the qualifying stage as a whole in this story.

You start with two files: decrypt_flag.py and ntfs_volume.raw.

Let’s take a look at the script.

It takes a file called “key.bin” and then tries (in a loop) a binary string of 34 bytes from every offset within this file as input data for the PBKDF2 function with a really high number of iterations. Each returned key is used as a XOR key to decrypt a hard-coded encrypted string and, if this string (in the decrypted form) yields a predefined MD5 value, use it to build and print the flag.

So, you need to locate the “key.bin” file. You can’t try every offset within the image file (“ntfs_volume.raw”) because of the really slow key derivation process (okay, you can do it, but you won’t finish before the CTF ends).

The image file contains an MBR partition table with one partition, its offset is 2048 512-byte sectors, it contains an NTFS file system. But there is no “key.bin” file:

$ fls -o 2048 -r -p ntfs_volume.raw | grep -F key.bin | wc –l

0

In NTFS, all file names are stored in the UTF-16LE encoding. Let’s search for the name in this encoding!

Search hits for a file record

After examining all of the search hits, let’s focus on file records, which start with the “FILE” signature [1]. Here is the only file record found:

A file record found

We are so close! We have a file record, but we need file data. In NTFS, data is stored within the $DATA attribute, which can be either resident or nonresident [2]. The data attribute of the file record in question starts at the offset 0x3AADD00 and points to nonresident data. (This means that data is stored outside of the file record.)

So, what’s the location of file data? To find it out, we need to decode so-called mapping pairs or data runs (two names for the same thing) [3].

The data runs for the file are (see the offset 0x3AADD40): 22 53 01 A0 4E 21 05 31 C1 11 38 30 00.

Or, regrouped:

1. 22 53 01 A0 4E

2. 21 05 31 C1

3. 11 38 30 00

The file has three fragments, the first one is 339 clusters in size and starts at cluster #20128. Well, this fragment is pretty large for the PBKDF2 implementation used. According to the file system header, the cluster size is 4096 bytes:

$ fsstat -o 2048 ntfs_volume.raw | grep ‘Cluster Size’

Cluster Size: 4096

So, let’s check some data at the following offset (in bytes): 2048 * 512 + 20128 * 4096 = 83492864. Let’s export some reasonable amount of bytes (e.g., 128 bytes) from this location to a new file called “key.bin” and run the script… And…

It fails.

Perhaps, this file comes from a previous format, not from the current file system (remember, there was no entry for a deleted file with the name in question). What was the cluster size before? Let’s search for a file system header, which has the “NTFS “ signature [4]. Hopefully, there will be a header from a previous format.

Search hits for a file system header

The first one and the last one are for the current file system. All file system headers between them seem to be for the previous format. And they record a different cluster size!

A file system header for a previous format

The following sector size is recorded at the offset 0x4554800B: 00 02 or 512. The following cluster size is record at the offset 0x0x4554800D: F7 or 247.

So, the cluster size (in bytes) is 512 * 247 = 126464. This doesn’t make sense.

According to an NTFS parsing tool [5], such a value should be treated as signed and in a special way, so the real cluster size (in sectors) is: 1 << -(-9) = 512. Or, in bytes: 512 * 512 = 262144. Sounds legit.

And file data starts at the following offset (in bytes): 2048 * 512 + 20128 * 262144 = 5277483008.

Let’s try the same thing with some bytes exported from that location and… We fail again!

What’s wrong? This is a CTF, so anything could go wrong!

The task is called “In the Shadows”. Hopefully, it has something to do with volume shadow copies.

So, we have a file. Actually, a file from a file system which previously existed on the volume. We can’t easily mount a shadow copy for that file system. But we know the exact offset where file data starts! It’s 5277483008 or, within the partition, 5277483008–2048 * 512 = 5276434432.

According to the VSS format specification [6], redirected data blocks are described in the “Block descriptor” structure, which contains a 64-bit field describing the original offset (within the volume) and a 64-bit field describing the target block offset (within the volume). Let’s search for 5276434432 as a 64-bit little-endian number.

There are two hits, only one of them is at the even offset.

A block descriptor found

The target block offset is: 00 00 9B 03 00 00 00 00.

Or just 60489728. The final offset is: 60489728 + 2048 * 512 = 61538304. Export some amount of bytes from that offset to a new file called “key.bin” and…

$ ./decrypt_flag.py ctfzone{my_c0ngr4t5_t0_u,w311_d0n3_31337}

We are done!

Maxim Suhanov (https://twitter.com/errno_fail)

Links

1. https://flatcap.org/linux-ntfs/ntfs/concepts/file_record.html

2. https://flatcap.org/linux-ntfs/ntfs/attributes/data.html

3. https://flatcap.org/linux-ntfs/ntfs/concepts/data_runs.html

4. https://flatcap.org/linux-ntfs/ntfs/files/boot.html

5. https://github.com/msuhanov/dfir_ntfs/blob/94bb46d6600153071b0c3c507ef37c42ad62110d/dfir_ntfs/BootSector.py#L58

6. https://github.com/libyal/libvshadow/blob/master/documentation/Volume%20Shadow%20Snapshot%20(VSS)%20format.asciidoc#431-block-descriptor