Objective 11b - Naughty/Nice List with Blockchain Investigation Part 2
Objective
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?
Tip
Watch Professor Qwerty Petabyte's talk Working with the Official Naughty/Nice Blockchain for some important background on this topic.
Tip
Read the slide deck on Hash Collision Exploitation suggested by Tangle Coalbox.
Analysis
It's helpful to get comfortable using the blockchain.dat file and naughty_nice.py. Explore the functions and write some test code. One thing you can do is dump out all the documents stored in the blockchain. Some of the elf reports in these files are hilarious.
You'll find that one block (#129459) has more than one file attached. The first is a PDF, and the other is an unrecognizable binary file. Take a closer look at the block and its contents.
#!/usr/bin/env python3
import naughty_nice
import os
if __name__ == '__main__':
# Import the official public key
with open('official_public.pem', 'rb') as fh:
official_public_key = naughty_nice.RSA.importKey(fh.read())
# Read the blockchain data into c2
c2 = naughty_nice.Chain(load=True, filename='blockchain.dat')
# Verify the blockchain data
print("C2: Block chain verify: %s" % (c2.verify_chain(official_public_key,c2.blocks[0].previous_hash)))
# Dump out all of the files
for i in range(len(c2.blocks)):
for j in range(c2.blocks[i].doc_count):
orig_filename = '%s.%s' % (str(c2.blocks[i].index), naughty_nice.data_extension[c2.blocks[i].data[j - 1]['type']])
new_filename = '%s-%i.%s' % (str(c2.blocks[i].index), j, naughty_nice.data_extension[c2.blocks[i].data[j - 1]['type']])
c2.blocks[i].dump_doc(j)
os.rename(orig_filename, r'docs/' + new_filename)
The block itself shows that Jack Frost received the highest nice score possible, and the PDF contains a very complimentary report by Shinny Upatree. Shinny denies writing this, but the unbroken blockchain would seem to contradict that.
Index: 129459
Nonce: a9447e5771c704f4
PID: 0000000000012fd1
RID: 000000000000020f
Document Count: 2
Score: ffffffff (4294967295)
Sign: 1 (Nice)
Data item: 1
Data Type: ff (Binary blob)
Data Length: 0000006c
Data: b'ea465340303a6079d3df2762be68467c27f046d3a7ff4e92dfe1def7407f2a7b73e1b759b8b919451e37518d22d987296fcb0f188dd60388bf20350f2a91c29d0348614dc0bceef2bcadd4cc3f251ba8f9fbaf171a06df1e1fd8649396ab86f9d5118cc8d8204b4ffe8d8f09'
Slide 194 in the aforementioned slidedeck talks about a way to create a PDF with an MD5 hash collision. The technique relies on the fact that a PDF file is composed of multiple sections that are mapped by something like a table of contents. You can leave certain pages out of the mapping, and while the data is still in the file, it will be unused by the reader.
You can modify that map using a hex editor, restoring the original text from the text Jack Frost injected into the file. The first few lines of the PDF files read as follows:
$ strings 129459-0.pdf
%PDF-1.3
1 0 obj
<</Type/Catalog/_Go_Away/Santa/Pages 2 0 R 0
Use a hex editor to change "Pages 2 0 R" to "Pages 3 0 R" and then view the PDF again.
This time, Jack Frost's true nature is revealed. Jack used a UniColl technique (described here: https://github.com/corkami/collisions#pdf) to create the alternate PDF file without altering the MD5 hash. In fact, a ready-made script is available that claims to do this in mere seconds.
In addition to changing the PDF file, Jack also flipped the sign from naughty to nice in the block without altering its MD5 hash. This was done using the same technique, made possible because the file attachment data is stored in the block immediately after the 64-bytes containing the naughty/nice value.
Examining the block_data() function in naughty_nice.py, you can determine the data structure. The first 64-bytes are made up of the block's index number, nonce value, pid, and rid. The next 64-bytes begin with: doc_count (c), score, sign (s), file attachment type, file attachment data length, and then the data for the file itself.
This block has another file attached besides the PDF. That second file is stored beginning in the first data field. Jack Frost used that binary blob to create an MD5 hash collision for the changed naughty/nice sign.
Your job now is to restore the sign back to naughty (0) and the original text in the PDF file. The objective says you can do this with changes to only 4 bytes in the block.
Solution
Step 1: Create a program to output the block to a file.
#!/usr/bin/env python3
import naughty_nice
if __name__ == "__main__":
# Import the official public key
with open('official_public.pem', 'rb') as fh:
official_public_key = naughty_nice.RSA.importKey(fh.read())
# Read the blockchain data into c2
c2 = naughty_nice.Chain(load=True, filename='blockchain.dat')
# Verify the blockchain data
print("C2: Block chain verify: %s" % (c2.verify_chain(official_public_key, c2.blocks[0].previous_hash)))
# Show the block details
print("C2: Block data: %s" % (c2.blocks[129459-c2.blocks[0].index]))
# Dump the block to a file
c2.save_a_block(129459-c2.blocks[0].index, "block.dat")
Step 2: Run the program and check its hashes to ensure you got what you were expecting. The objective stated that the sha256 hash would begin with 58a3b933; the matching file hash confirms the program ran properly.
$ ./obj-11b.py
$ sha256sum block.dat
58a3b9335a6ceb0234c12d35a0564c4ef0e90152d0eb2ce2082383b38028a90f
$ md5sum block.dat
b10b4a6bd373b61f32f4fd3a0cdfbf84
Step 3: Open the dat file in a hex editor and modify some bytes. Earlier you changed one byte in the pdf file to restore its original content, however that altered the file’s MD5 hash. Apply the UniColl technique, updating adjacent bytes in the next 64-byte block. Since you made a +1 change in the PDF file, make -1 change in the next block to negate the change to the MD5 hash.
Repeat the technique to fix the sign value. This time you made a -1 change to the sign, so make a +1 change in the next 64-byte block.
Note
I used hexedit with the -l 16 option so that the bytes would be aligned nicely in the terminal, and each 4 lines represents a 64-byte block. The default behavior is to display as many bytes across the screen that will fit, which could be harder to read depending on your terminal width.
Step 4: Verify the hashes. You can see that the md5 hash of the original block.dat file and the modified block.mod file match, but the sha256 hash is different.
$ md5sum block.dat
b10b4a6bd373b61f32f4fd3a0cdfbf84
$ md5sum block.mod
b10b4a6bd373b61f32f4fd3a0cdfbf84
$ sha256sum block.dat
58a3b9335a6ceb0234c12d35a0564c4ef0e90152d0eb2ce2082383b38028a90f
$ sha256sum block.mod
fff054f33c2134e0230efb29dad515064ac97aa8c68d33c58c01213a0d408afb
Answer: fff054f33c2134e0230efb29dad515064ac97aa8c68d33c58c01213a0d408afb