SANS Holiday Hack Challenge 2020: Objective 11b — Naughty/Nice List with Blockchain Investigation Part 2
Each year, the SANS and Counter Hack Challenges teams put together my favorite capture the flag (CTF) competition, the SANS Holiday Hack Challenge. The 2020 SANS Holiday Hack Challenge, featuring KringleCon 3: French Hens! was held at Santa’s newly renovated castle at the North Pole from December 10, 2020 to January 11, 2021. This is a walk-through for an objective from the event.
Objective 11b) The SHA256 of Jack’s altered block is: 58a3b9335a6ceb0234c12d35a0564c4e f0e90152d0eb2ce2082383b38028a90f. If you’re clever, you can recreate the original version of that block by changing the values of only 4 bytes. Once you’ve recreated the original block, what is the SHA256 of that block?
First, I evaluated how I would determine that I have recreated the original block. The Chain class provided a function called
verify_chain() which could be called to ensure the chain was valid. I ran this as a baseline to make sure it would work for my use case. I first read in the chain from the file, and then ran the
verify_chain() method to make sure I was starting with a valid block.
My outcome showed that, out of the box, my
blockchain.dat file was not valid.
Fortunately, I knew that index 128449 is the index of the first block in my
blockchain.dat file. I found this out earlier by printing
c2.blocks.index when exploring the blockchain. In looking at the code, I realized that it set the
genesis_block_fake_hash value as all zeros as shown below.
I updated this to be the hash of the first block, essentially telling the program that the first block in my file is the first block in the entire chain. I used the MD5 hash of the first block, not the SHA256, since MD5 is what is used by the chain currently.
Then I reran the verify command and confirmed that the chain verified successfully.
With my plan in place for how I was going to verify that my changes did not alter the chain, my next task was to locate the altered block with a SHA256 hash of
58a3b9335a6ceb0234c12d35a0564c4ef0e90152d0eb2ce2082383b38028a90f. To do this, I created a function in the Block hash to get the SHA256 hash. That code is shown below.
I then iterated through each of the blocks read in from the file shown previously and compared it’s SHA256 hash to the hash provided in the objective.
When I ran this code, I learned that the altered block was at index 129459.
With the index of the first block in the file being 128449 and the altered block being 129459, I needed to look at the 1,010th index (129459–128449 = 1010) in
c2.blocks to view the altered block. I viewed the contents of this block by printing
c2.blocks. This seemed like the correct block because the Sign has a value of 1, indicating that the block represented a Nice record, and the score for how many nice points that person would get was very high (
0xffffffff). I also learned that there were two documents attached, one binary blob and one PDF.
Next, I dumped both documents using the commands
c2.blocks.dump_doc(2). I examined the first document, the binary blob, and it didn’t seem to contain anything useful at the moment. However, looking at the PDF document revealed a suspicious document. It contained several testaments to the character of Jack Frost with the below signature at the bottom. This definitely didn’t seem to be what Shinny Upatree remembered writing.
Thanks to the hints from Tangle Coalbox, I was able to read through the presentation from Ange Albertini about hash collisions. One method in particular stood out to me. The Unicoll collision allowed changing two bytes at the same offset in two subsequent 64-byte blocks that the attacker has control over. This seemed promising. I wasn’t sure if Jack Frost had control over the original documents, but if he somehow did, he easily could have flipped the naughty/nice bit using this method.
Unicoll involved changing two characters, one each in each of the 64-byte blocks at the same offset. One of the characters would increase by one hex value and the other would decrease by one hex value. To test this, I created a copy of the
blockchain.dat file called
blockchain_fixed.dat. I opened this file in a hex editor and made sure I was showing 16 bytes across to easily evaluate the file in 64-byte blocks (which would equate to 4 16-byte lines).
I located the altered block by searching for the block’s nonce value (using text search) that I found when printing out its details (
a9447e5771c704f4). I then located the naught/nice bit in this block, which currently had a value of 1 (hex value
0x31). I was able to locate this by comparing it to the other values in the block details. In the screenshot of the block above, the naughty/nice bit seems to sit between the score (
ffffffff) and the binary blob data type indicator (
ff), so I looked for a 1 between these values and found it at offset
I flipped this bit from 1 to 0. Then, assuming this is the first line of a 64-byte block, I went down to the next 64-byte block to locate the second bit to flip. This was at offset
0x1630B5 and had a hex value of
0xD6. Since I decremented the naughty/nice bit from 1 to 0, I incremented this second bit from
0xD7 to take advantage of a possible unicoll collision. The resulting data file is shown below. Note that Unicoll involves updating the 10th character of the 64-byte blocks and here it appears that I am updating the 5th character. This is not actually true. The original block starts at offset
0x16302C. The next 64-byte block, which contains the first alteration, technically starts at
0x16306C, making the character at
0x163075 the 10th character of this block. However, to simplify the description based on the way the block is formatted in the hex editor, I have chosen to represent the start of the block with the start of the line in the hex editor.
I saved this file, and reran the following code, now reading from to
blockchain_fixed.dat, and ran the verify command. I also printed out the block to ensure that my changes were reflected.
The blockchain was able to verify successfully and the naughty/nice bit was successfully flipped! These must have been the first two bits that Jack Frost changed.
Next, I looked at the PDF file that was attached to the block. It seemed suspicious because Shinny did not seem to remember writing it and it was a pretty large file for such a small amount of displayed text. I opened the dumped PDF file in a text editor to get a feel for what was going on. Immediately at the top of the document, I saw the tag
<</Type/Catalog/_Go_Away/Santa/Pages. The Catalog key word indicates that this is the object that tells the application how to render the page.
The Catalog object references Object 2, which had one Kid in Object 23. In looking into Object 23, I saw that it sets an 8.5 x 11 piece of paper (that’s the media box portion) and adds the contents stored in object 16. That is likely where the text is contained that is displayed when the PDF document is rendered. However, as shown in the screenshot above, Object 3 seemed to mirror object 2 very closely. It has one Kid in Object 15 which also sets an 8.5 x 11 sheet of paper and adds the contents of object 4.
I began to wonder if there were actually two documents in this PDF and only one was being displayed. To test this theory, I went back to Object 1 and changed the reference from 2 to 3 to see what would load when I opened the PDF. It was a totally different document containing a long list of grievances against Jack Frost!
At the bottom of the document I found out what really happened! Shinny had an emergency at home and had to leave unexpectedly. Jack kindly offered to submit the report for him. That explains how Jack had control over the contents of the block and was able to create such an elegant hash conflict.
The final step was to apply the same method used with the naughty/nice value to the reference in the PDF object. To do this, I opened
blockchain_fixed.dat in the hex editor again and located the beginning of the PDF document in the altered block. The reference to object two exists at offset
0x163135 and I incremented the value from
0x33. Next, I went into the same offset in the next 64-byte block. I decremented the value at
0x1B to produce the below data file.
I then reran the verify command after reading in the block from the fixed file. The output showed that the chain continued to verify successfully, indicating that I had located and fixed all four bytes that Jack had changed. Finally, I printed out the value returned by
c2.blocks.sha_256_hash() to get the SHA256 hash of the updated block to enter into my badge.
Interested in learning more about the 2020 SANS Holiday Hack Challenge? Check out my other walk-throughs available here.