First and foremost
在CTF的智能合約題目里,一個很大的瓶頸(對於我自己🙁)就是不知道該如何調用合約,尤其是無源碼的合約逆向。學習了一些文章,大概掌握了一些方法,因此再造個輪子記憶一下。可能還不是很全,學到新的再去補充。
需要用到的工具就是Remix IDE和MetaMask插件錢包。
通過部署源碼來調用
當然,自己寫的合約放在Remix里編譯部署后就可以直接調用;如果是別人的合約,並且我們能夠拿到合約地址和源碼,依然可以使用Remix來調用。
這里以一道題目舉例:
合約代碼:
pragma solidity ^0.4.21;
contract CallMeChallenge {
bool public isComplete = false;
function callme() public {
isComplete = true;
}
}
在Ropsten測試網絡下,題目給出了合約源碼及其地址,要求調用callme()函數。
首先編譯合約源碼:
因為本題環境是在Ropsten測試網絡下,Environment應選擇“Injected Web3”,並且MetaMask是需要登錄的。
然后在“At Address”欄里填入合約地址並點擊該按鈕部署合約:
之后會出現部署好的合約及其函數:
然后點擊callme按鈕就能調用callme函數,等待交易完成后,點擊isComplete就可以看到結果為true了:
在調用合約的時候我嘗試更改一下代碼里的函數名稱,發現和原代碼不一樣的話,部署后的合約函數無法正常調用,或許只能與原代碼相同的前提下才可以調用成功。
這是最簡單的合約調用方式,但也只能滿足手動的交互。
通過Web3.js調用合約
瀏覽器里執行
瀏覽器安裝了MetaMask插件后,在瀏覽器里按F12打開js控制台,就可以直接使用web3的庫,這樣的話可以直接在瀏覽器執行js代碼調用合約。
還是通過一道題目舉例:
題目源碼:
pragma solidity ^0.4.21;
// Relevant part of the CaptureTheEther contract.
contract CaptureTheEther {
mapping (address => bytes32) public nicknameOf;
function setNickname(bytes32 nickname) public {
nicknameOf[msg.sender] = nickname;
}
}
// Challenge contract. You don't need to do anything with this; it just verifies
// that you set a nickname for yourself.
contract NicknameChallenge {
CaptureTheEther cte = CaptureTheEther(msg.sender);
address player;
// Your address gets passed in as a constructor parameter.
function NicknameChallenge(address _player) public {
player = _player;
}
// Check that the first character is not null.
function isComplete() public view returns (bool) {
return cte.nicknameOf(player)[0] != 0;
}
}
CaptureTheEther合約的作用是給賬戶添加一個昵稱,並且已經部署在了Ropsten上,地址為0x71c46Ed333C35e4E6c62D32dc7C8F00D125b4fee。我們需要調用該合約給自己賬戶添加昵稱。
因為以太坊分主網和測試網絡之分,個人實驗了一下,如何在其它頁面調用web3可能只是在主網上,如果以Ropsten網絡賬戶登錄下的Remix頁面,則調用web3時是在Ropsten網絡上操作。
把合約源碼放入Remix中編譯,可以得到合約的ABI:
這里的Web3Deploy是可以直接使用的JS代碼,其中第一行就是合約ABI,類似JSON數據:
下面就是調用合約的JavaScript代碼,在Remix頁面下按F12進入js控制台里執行,並支付交易費(你在執行這段代碼的時候,就是用的你此時的賬戶)。
var abiDefinition = [{"constant":false,"inputs":[{"name":"nickname","type":"bytes32"}],"name":"setNickname","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"nicknameOf","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"}];
var CaptureTheEther = web3.eth.contract(abiDefinition);
// load from deployed contract. replace the address with yours
var cte = CaptureTheEther.at("0x71c46Ed333C35e4E6c62D32dc7C8F00D125b4fee");
// call `setNickname` function
cte.setNickname("your nickname", console.log);
接下來用題目生成的地址部署NicknameChallenge合約,將自己賬戶地址傳入構造函數,點擊deploy,調用isComplete函數看到結果是true,就說明昵稱添加成功。
nodejs執行
npm安裝的web3庫有很多問題,為了避免環境帶來的問題(npm安裝的報錯),這里選擇安裝web3@^0.20.1,ethereumjs-tx@^1.3.7版本的庫。
npm install web3@^0.20.1
npm install ethereumjs-tx@^1.3.7
js腳本如下:
let Web3 = require("web3");
let Tx = require("ethereumjs-tx");
//合約地址
let contractAddr = "0x35...2827";
//創建web3對象
let web3 = new Web3;
//連接到ropsten
let INFURA_api = "https://ropsten.infura.io/v3/1b...4b0";
web3.setProvider(new Web3.providers.HttpProvider(INFURA_api));
//賬戶地址
let fromAddr = "0x97...e05B7";
let count = web3.eth.getTransactionCount(fromAddr);
let gasPrice = web3.eth.gasPrice;
let gasLimit = 90000;
let rawTransaction = {
"from": fromAddr,
"nonce": web3.toHex(count),
"gasPrice": web3.toHex(gasPrice),
"gasLimit": web3.toHex(gasLimit),
"to": contractAddr,
"value": "0x0",
"data": "0xa3c8e393" //函數名callme取keccak后的bytes4
};
//賬戶私鑰,不包含'0x'字符
let priv_key = new Buffer.from("bb...623ef3ed3c", "hex");
let tx = new Tx(rawTransaction);
//用私鑰簽名交易信息
tx.sign(priv_key);
let serializedTx = tx.serialize();
//發送交易
web3.eth.sendRawTransaction('0x'+serializedTx.toString('hex'),
function(err, hash) {
if (!err)
console.log(hash);
else
console.log(err);
});
運行此腳本node web3test.js
會返回交易哈希,到ropsten.etherscan.io里可以搜到此交易信息。
通過Web3.py調用合約
pip安裝Web3.pypip install web3
。
下面是一個調用合約函數的python腳本:
# -*- coding:utf-8 -*-
from web3 import Web3, HTTPProvider
true = True
false = False
config = {
"abi":[ # 合約ABI
{
"constant": false,
"inputs": [],
"name": "callme",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "isComplete",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
],
"address":"0x35***27" # 合約地址
}
INFURA_api = "https://ropsten.infura.io/***" # INFURA的ropsten API地址
web3 = Web3(HTTPProvider(INFURA_api))
contract_instance = web3.eth.contract(address=config['address'], abi=config['abi'])
my_addr = "0x97***1d" # 賬戶地址
priv_key = "0xb***c" # 賬戶私鑰
def SendTxn(txn):
signed_txn = web3.eth.account.signTransaction(txn,private_key=priv_key)
res = web3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()
txn_receipt = web3.eth.waitForTransactionReceipt(res)
print(res)
return txn_receipt
txn = contract_instance.functions.callme().buildTransaction(
{
'chainId':3,
'nonce':web3.eth.getTransactionCount(my_addr),
'gas':7600000,
'value':Web3.toWei(0,'ether'),
'gasPrice':web3.eth.gasPrice,
}
)
print(SendTxn(txn))
實例化合約需要合約的ABI和地址,以及調用合約的賬戶及私鑰。
Remix里可直接復制到合約ABI,和地址:
這里借助INFURA(使用方法可參考之前的文章)的API連接到以太坊Ropsten網絡,來監聽交易。
通過創建交易的方式來調用合約函數,再將交易發送至網絡。
運行腳本會輸出交易信息。
並且在https://ropsten.etherscan.io也可以查看到交易: