近年来,各个大型CTF(Capture The Flag,中文一般译作夺旗赛,在网络安全领域中指的是网络安全技术人员之间进行技术竞技的一种比赛形式)比赛中都有了区块链攻防的身影,而且出现的题目绝大多数都是区块链智能合约攻防。此系列文章我们主要以智能合约攻防为中心,来剖析智能合约攻防的要点,前两篇我们分享了合约反编译,反汇编的基础内容。后续的文章中,我们会继续分享CTF比赛中智能合约常见题型(重入,整数溢出,空投,随机数可控等)及解题思路,相信会给读者带来不一样的收获。
上篇文章中我们分享了CTF比赛中常考的重入漏洞题型,本篇继续来分享CTF比赛中的整数溢出题型,也是比较常见的一类题型,当然多数CTF智能合约题目并不仅仅考察单个漏洞的攻防,可能涉及多个漏洞的组合。
本篇我们以2018年WCTF上BelluminarBank题目为例,给大家分享智能合约整数溢出的题型。解出这道题不仅需要整数溢出攻击,也需用到变量覆盖,权限设置等多个攻击技巧。
题目地址:
由于WCTF智能合约比赛没有在以太坊测试网(ropsten)进行,没有在线的攻防场景,合约具体题目介绍及合约源码已在GitHub给出:https://github.com/beched/ctf/tree/master/2018/wctf-belluminar
团队需要对字节码进行反向工程,并使用以下攻击:
整数溢出绕过存款期限限制;
存储溢出以覆盖银行所有者;
存储访问权限以泄露私有属性;
不一定需要意外的以太攻击,如果使用withdraw()和invest()调用,则可以适当平衡。可能是由于导致错误解决方案的巨大错误所致:withdraw()函数不会更改balances数组。但是仍然需要事先利用整数溢出。
Belluminar Bank非常小而特别。其工作方式如下:
目标是破解这家银行并清空其余额。如果成功,该机器人将向您发送交易数据中的标志。
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);