从0开始MoveCtf week1task

MoveCtf

说在前头:作者是move菜鸟,有问题望大家指正

任务目标

参加平台:https://platform.cyclens.tech/activity/1 根据week1.move的代码提示,调用get_flag函数 调用后提交交易哈希 (Transaction Hash)和合约返回的 FLAG (Contract Returned FLAG)即可完成任务 我是写了一个solve_week1的合约来调用get_flag

环境配置

Sui Cli官方文档https://docs.sui.io/references/cli

  • brew install sui使用homebrew包管理器安装 Sui(MacOS)
  • sui client new-address ed25519使用 ED25519 签名方案创建一个新地址
  • sui client faucet获取gas(交易手续费),命令行输入后需自行去网站手动领取,sui client gas查询是否领取到gas

分析week1

public entry fun get_flag(
        seed: u64,
        score: u64,
        rand: &Random,
        github_id: String,
        hash_input: vector<u8>,
        magic_number: u64,
        challenge: &mut Challenge,
        guess: vector<u8>,
        ctx: &mut TxContext
)

可以看到我们需要ctx除外的8个参数才能调用get_flag

  • 命令行输入sui client publish把week1部署到链上
  • 根据tx去Sui浏览器https://suivision.xyz/ ,找到一个Owner为Shared的,名字叫challenge的ObjectId,后续会用到,也可以在终端找到 截屏2025-06-19 18.51.32.png
  • 在init中let secret = b"Letsmovectf_week1"; 所以后续的&challenge.secret都是“Letsmovectf_week1”这串字符
  1. seed:
    let secret_bytes = *string::as_bytes(&challenge.secret);
    let secret_len = vector::length(&secret_bytes);
    assert!(seed == secret_len * 2, EINVALID_SEED);
    • string::as_bytes(&challenge.secret): 这个函数会把 challenge.secret 字符串转换成一个字节数组L e t~ e k 1
    • vector::length(&secret_bytes)获取数组元素的个数=17 可知 seed == secret_let 2 = 17 2 = 34
    • 最后一句检查传入的 score 参数是否等于计算出的 expected_score assert!()是Move语言中的一个断言语句,如果条件为假,程序就会立即中止执行(回滚交易),并返回指定的错误代码。如果为真,程序正常运行(后续出现不再赘述)
  2. score:
let secret_hash = sha3_256(*string::as_bytes(&challenge.secret));
let expected_score = (
    ((*vector::borrow(&secret_hash, 0) as u64) << 24) |
    ((*vector::borrow(&secret_hash, 1) as u64) << 16) |
    ((*vector::borrow(&secret_hash, 2) as u64) << 8) |
    (*vector::borrow(&secret_hash, 3) as u64));
assert!(score == expected_score, EINVALID_SCORE);
  • 第一句 将secret进行 SHA3-256 哈希运算,并将结果存储在 secret_hash 变量中
  • 中间部分 vector::borrow(&secret_hash, 0): 会从 secret_hash 字节数组中获取索引为 0 的字节 << 24:将字节进行左移位操作,左移24位 |: 这是一个按位或操作。左移后的四个字节值通过按位或操作组合在一起。因为这些字节被移动到了不同的位置,所以按位或操作会将它们有效地拼接成一个 u64 整数。
secret_hash[0] = 0xAB               //举例 
secret_hash[1] = 0xCD 
secret_hash[2] = 0xEF 
secret_hash[3] = 0x12 
expected_score = ((0xAB << 24) |    // 0xAB000000
                  (0xCD << 16) |    // 0x00CD0000
                  (0xEF << 8)  |    // 0x0000EF00
                  (0x12))           // 0x00000012
expected_score = 0xABCDEF12
  1. rand:0x8
  2. github_id:启动环境后可得
  3. hash_input
let mut bcs_input = bcs::to_bytes(&challenge.secret);
vector::append(&mut bcs_input, *string::as_bytes(&github_id));
let expected_hash = sha3_256(bcs_input);
assert!(hash_input == expected_hash, EINVALID_HASH);
  • 第一句 将 challenge.secret通过 BCS 格式序列化成字节,并将这些字节存储在一个名为 bcs_input 的可变字节数组中。 BSC是"Binary Canonical Serialization"(二进制规范序列化)的缩写
  • 第二句 将 github_id 字符串转换成的字节数组,追加到 bcs_input 字节数组的末尾
  • 第三句 对bsc_input进行sha3_256哈希计算,将结果储存在中expected_hash
    1. magic_number
let expected_magic = challenge.current_score % 1000 + seed;
assert!(magic_number == expected_magic, EINVALID_MAGIC);
  • seed进行一些运算没什么好说的
    1. challenge:前面提到的ObjectId
    2. guess:
let mut guess_data = guess
vector::append(&mut guess_data, *string::as_bytes(&challenge.secret));
let random = sha3_256(guess_data);
let prefix_length = 2;
assert!(compare_hash_prefix(&random, &challenge.round_hash, prefix_length), EINVALID_GUESS_HASH);

fun compare_hash_prefix(hash1: &vector<u8>, hash2: &vector<u8>, n: u64): bool {
        if (vector::length(hash1) < n || vector::length(hash2) < n) {
            return false
        };
        let mut i = 0;
        while (i < n) {
            if (*vector::borrow(hash1, i) != *vector::borrow(hash2, i)) {
                return false
            };
            i = i + 1;
        };
        true
    }

该段代码是将guesschallenge.secret进行sha3_256加密运算,并两个字符串加密后的哈希值与prefix_length = 2传入compare_hash_prefix,将两个哈希值的前缀‘prefix_length’(2)位字符进行比较,若两位都相同,程序继续运行

编写solve_week1

  1. sui move new solve_week1创建一个新项目
  2. 在solve_week1中的move.toml中导入week1的合约
    [dependencies]
    week1 = { local = "../week1" }

    才能在solve_week1里调用week1的module

完整代码

可以与上一部分对应着看,大部分代码都可以直接cv week1.move其实是我懒得写

module solve_week1::solve;

use std::bcs;
use std::hash::sha3_256;
use std::string::{Self, String};
use std::vector;
use sui::random::Random;
use sui::tx_context::TxContext;
use week1::challenge::{Self, Challenge};

public entry fun solve_get_flag(
    challenge: &mut Challenge, 
    rand: &Random, 
    ctx: &mut TxContext
) {
    //seed
    let seed = 34; // length of "Letsmovectf_week1" is 17, so 17 * 2 = 34

    //score
    let secret = b"Letsmovectf_week1";
    let secret_hash = sha3_256(secret);
    let score = (
        ((*vector::borrow(&secret_hash, 0) as u64) << 24) |
        ((*vector::borrow(&secret_hash, 1) as u64) << 16) |
        ((*vector::borrow(&secret_hash, 2) as u64) << 8) |
        (*vector::borrow(&secret_hash, 3) as u64),
    );

    //github_id
    let github_id = string::utf8(b"12cecd31-3002-4557-a8a1-da79ab1e4e5f"); 

    //hash_input
    let mut bcs_input = bcs::to_bytes(&string::utf8(secret));
    vector::append(&mut bcs_input, *string::bytes(&github_id));
    let hash_input = sha3_256(bcs_input);

    //magic_number
    let magic_number = score % 1000 + seed;

    //guess
    let guess = b"83419";

    challenge::get_flag(
        seed,
        score,
        rand,
        github_id,
        hash_input,
        magic_number,
        challenge,
        guess,
        ctx,
    );
}

部署运行

sui client publish将solve_week1部署上链 新合约只需要两个args就可以调用

  • 运行
    sui client call --package <packageID> --module solve --function solve_get_flag --args "<challengeID>" "0x8"
  • 调用成功

截屏2025-06-18 20.15.13.png

  • 原创
  • 学分: 5
  • 分类: Move
  • 标签: Move 
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
0xkrypton
0xkrypton
江湖只有他的大名,没有他的介绍。