前言
近年来,各个大型CTF(Capture The Flag,中文一般译作夺旗赛,在网络安全领域中指的是网络安全技术人员之间进行技术竞技的一种比赛形式)比赛中都有了区块链攻防的身影,而且出现的题目绝大多数都是区块链智能合约攻防。此系列文章我们主要以智能合约攻防为中心,来剖析智能合约攻防的要点,前两篇我们分享了合约反编译,反汇编的基础内容。后续的文章中,我们会继续分享CTF比赛中智能合约常见题型(重入,整数溢出,空投,随机数可控等)及解题思路,相信会给读者带来不一样的收获。
上篇文章中我们分享了CTF比赛中常考的重入漏洞题型,本篇继续来分享CTF比赛中的整数溢出题型,也是比较常见的一类题型,当然多数CTF智能合约题目并不仅仅考察单个漏洞的攻防,可能涉及多个漏洞的组合。
本篇我们以2018年WCTF上BelluminarBank题目为例,给大家分享智能合约整数溢出的题型。解出这道题不仅需要整数溢出攻击,也需用到变量覆盖,权限设置等多个攻击技巧。
题目地址:
由于WCTF智能合约比赛没有在以太坊测试网(ropsten)进行,没有在线的攻防场景,合约具体题目介绍及合约源码已在GitHub给出:https://github.com/beched/ctf/tree/master/2018/wctf-belluminar
题目分析
题目提示
团队需要对字节码进行反向工程,并使用以下攻击:
整数溢出绕过存款期限限制;
存储溢出以覆盖银行所有者;
存储访问权限以泄露私有属性;
- 部署自杀合同以强制将eth发送到目标合同(以解决余额差异)
不一定需要意外的以太攻击,如果使用withdraw()和invest()调用,则可以适当平衡。可能是由于导致错误解决方案的巨大错误所致:withdraw()函数不会更改balances数组。但是仍然需要事先利用整数溢出。
合约说明
Belluminar Bank非常小而特别。其工作方式如下:
- 任何人都可以投资任何金额,并应指定存款期限(在此之前存款将被锁定);
- 存款期限必须比先前客户的存款期限至少长1年;
- 每个存款分配一个账号;
- 帐户0包含31337 wei,由银行所有者(合同创建者)锁定多年;
- 存款期限满一年(如果您不提款),银行所有者可以没收您的存款。
目标是破解这家银行并清空其余额。如果成功,该机器人将向您发送交易数据中的标志。
合约源码
pragma solidity ^0.4.23;
contract BelluminarBank {
struct Investment {
uint256 amount;
uint256 deposit_term;
address owner;
}
//全局变量
Investment[] balances;
uint256 head;
address private owner;
bytes16 private secret; //secret可读取
function BelluminarBank(bytes16 _secret, uint256 deposit_term) public {
secret = _secret;
owner = msg.sender;
if(msg.value > 0) {
balances.push(Investment(msg.value, deposit_term, msg.sender));
}
}
function bankBalance() public view returns (uint256) {
return address(this).balance;
}
//局部变量覆盖全局变量
function invest(uint256 account, uint256 deposit_term) public payable {
if (account >= head && account < balances.length) {
Investment storage investment = balances[account];
investment.amount += msg.value;
} else {
if(balances.length > 0) {
//存在整数溢出
require(deposit_term >= balances[balances.length - 1].deposit_term + 1 years);
}
//局部变量
investment.amount = msg.value;
investment.deposit_term = deposit_term;
investment.owner = msg.sender;
balances.push(investment);