The CTF (Capture the Flag) has ended, but the security vulnerabilities and knowledge in many of these topics continue to attract our interest in studying and learning them. However, this also comes with difficulties and challenges.
Numen Cyber Labs is paying attention to all aspects of security information related to blockchain and trying to share some solutions with you as well. This time, we will analyze MerkleDrop. If there are better methods and techniques, we welcome discussion with us.
MerkleDrop CTF Introduction
Merkledrop is a popular airdrop token technology. Its rough logic is that crypto projects send free tokens to the community to encourage adoption. They are based on the use of Merkle trees. Because of the nature of these data structures (i.e., each non-leaf node is a binary tree of hashes of its children), they are particularly effective in space usage and validation.
The leaves store information about user addresses and the number of tokens they are eligible to receive. Leaves are then grouped in pairs and hashed. Their hashes are grouped in pairs and hashed again, and so on until there is a root hash. Such a tree can be made public to anyone without risk of modifying its data, and consistency is guaranteed by hashing.
t’s also easy to prove that you’re eligible for airdrops. For example, you need to provide:
- A leaf with your address and amount.
- A path from leaf to root hash. Specifically, you need to prove the other hash values in each pair on each layer of the tree.
CTF Question Analysis
Analysis: The main function that can be called in this contract is the claim function, which collects tokens under the contract. “isClaimed” is a query function to query the pick status of leaf nodes.
Analysis: We can see that in the constructor, the merkleDistributor contract was created and 75,000 tokens were transferred into it.
Question: In the condition that not all leaf nodes are in the claimed status, claim all the tokens under the contract.
To solve this problem, we can only call the claim function in the merkleDistributor contract to claim the tokens under the contract, and the leaf nodes must not claim them. At first, when I saw these two conditions, I thought it was very contradictory.
This question provides the tree.json file, which contains the verification information of 64 leaf nodes, including the extraction address, index, amount and verification hash, which can be called normally.
However, by directly using this information, the tokens under the contract can be extracted, but all leaf nodes have been extracted, and this problem cannot be solved.
By continuing to analyze the question, we can see that the amount in the claim function is uint96, and When getting :is, is, is, this produces 64 bytes, and 64 is a good number. If we look again:
node: index uint256 account address amount uint96 MerkleProof
It concatenates two hashes (each with a hash value of 32 bytes) and hashes them. So the input here is also 64 bytes long.
If one of the leaves is also hashed, then we package all the leaf information, as shown below:
Each of these sequences is hashed, i.e. the 32 bytes on the left are one hash and the 32 bytes on the right areanother hash. However, there are a lot of 0 in front that don’t look like real hashes. The 32 bytes on the right look more like a hash value. Note that each of them has multiple 00s in about the same position — this is due to the number of fills. Let’s look at the tree.json if there’s one piece of evidence with a similar padding?
We see a similar message in the 37th leaf, the number of uint96 in hexadecimal 00000f40f0c122ae08d2207b is72033437049132565012603.
At this point, we can organize the extraction parameters and call the claim function to collect them. We will make the first proof in the proof list for leaf 37 itself a leaf. We can pass the proof with multiple 00s as the second half of the input to the claim function. 0xd48451c19959e2d9bd4e620fbe88aa5f6f7ea72a will be an account, 0x00000f40f0c122ae08d2207b will be a cap.
However, we need the index, which cannot be an arbitrary number because it is hashed with addresses and amounts, while the resulting hash value is hashed along with other proofs. The proof is concatenated, and the index is also connected to the address and amount. And this index is the hash of leaf 37, the leaves are hashed, and then their hashes are grouped in pairs and hashed again. Thus, the hash value of leaf 37 is grouped with the first proof in its proof array.
A total of 75,000*1e18 tokens were transferred into the contract, 72033437049132565012603 had just been withdrawn, and there were 2966562950867434987397 remaining, and the hexadecimal was 0xa0d154c64a300ddf85. So we looked for no leaf nodes in tree.json. The answer is yes, with an index of 8 leaf nodes.
Then, we continue to call the claim function, passing the parameter information in to extract the remaining tokens.
In conclusion, by understanding the logic and structure of Merkle trees and analyzing the provided information, we were able to find a solution to claim all the tokens under the contract without claiming all the leaf nodes. It’s important to pay attention to all the details and information provided in the question and continue to analyze them to find the solution. If you have any better methods or skills, we welcome you to contact us and have a discussion.
Numen Cyber Labs is committed to facilitating the safe development of Web 3.0. We are dedicated to the security of the blockchain ecosystem, as well as operating systems & browser/mobile security. We regularly disseminate analyses on topics such as these, please stay tuned or visit our blog here for more!
This blog was originally published on our Medium Account.