本文章是參考一位大佬博客學來的。
智能合約題的環境主要包括兩部分:一個是智能合約的部署,另一個就是監聽合約事件進而發送flag的腳本。
智能合約部署
這里寫的合約是指solidity合約,使用Remix IDE。
合約主要部署到以太坊測試鏈而非主鏈上(沒錢😑),幾個主要的測試鏈:Ropsten,Rinkeby,Kovan。
這里需要一個瀏覽器錢包插件MetaMask(可以在FireFox和Chrom上下載),注冊並申請賬戶后,選擇測試網絡(筆者選擇的是Rospten):
新創建的賬戶是沒有以太幣的,需要到測試水管(在首頁點擊存入)申請:
有了以太幣之后就可以利用Remix IDE將合約代碼部署到測試網絡。
這里先准備一個簡單的發送flag的合約:
pragma solidity ^0.4.24; //選擇solidity編譯器版本
contract TestFlag {
event victory(string b64email,string slogan); //定義事件
function getFlag(string b64email) public {
emit victory(b64email, "666!"); //觸發此事件,發送flag到郵箱
}
}
整個編譯器界面是這樣的:
右側選擇編譯器版本,然后點擊Start to compile進行編譯,編譯成功的話右側就會顯示一個寫着合約名稱的綠色框框。
點擊右上角的Run,Envir選擇Injected Web3,賬戶就會自動變為你MetaMask錢包里的賬戶,如果之前沒有部署過這個合約就點擊下方紅框Deploy,此時會跳出支付gas的彈窗,點擊確定即可,等待幾秒合約就會部署完成,最下方就會顯示已部署的合約(及其地址);如果之前部署過相同合約,那么可以將合約地址復制到At Address並點擊藍色按鈕加載合約,效果相同。
紅框getFlag就是合約里的函數,輸入一個郵箱base64字符串(雙引號括上)並點擊紅色按鈕就可以調用此函數了,通過ropsten.etherscan.io可以查到此合約的交易和事件。
智能合約的部署就這樣了,但是現在調用函數還不能收到郵件,現在還缺少自動發送郵件的腳本,往下看。
注意下面的腳本需要用到合約地址和事件日志中的topic0。
郵件發送腳本的編寫
先注冊Infura https://infura.io 獲取遠程節點rpc:
點擊黑色按鈕創建project,然后在KEYS欄中找到ENDPOINT,Ropsten網絡的URL,就是后面腳本中加載的RPC了(注意,API key不要暴露,具體什么安全規則這里咱也不知道😂)。
這里使用python3編寫腳本,需要用到web3的包,提前下一個(不過安裝這個包有一點坑,百度一下如何下載web3.py包)。
附上python腳本:
# -*- coding:UTF=8 -*-
from web3 import Web3,HTTPProvider
import os
import time
import binascii
import base64
import smtplib
from email.mime.text import MIMEText
from email.header import Header
contract_address = "0x128..." # 你的合約地址
contract_topic0 = "0x90c...1e8a11" # 事件日志中的topic0,針對同意合約的所有事件日志的topic0都是相同的
rpc = "https://ropsten.infura.io/v3/1b8...64b0" # 你注冊的Infura中的ENDPOINT
flag = "flag{a_smart_contract_test}"
email = {
"host":"smtp.163.com",
"port":25,
"user":"sender@163.com", # 用來發送flag的郵箱
"code":"******" # 郵箱的客戶端授權碼
}
# initial
w3 = Web3(Web3.HTTPProvider(rpc))
sender = smtplib.SMTP(host=email["host"],port=email["port"])
sender.ehlo()
sender.starttls()
sender.login(email["user"],email["code"])
# email content
message = MIMEText("收下你的flag:"+flag, 'plain', 'utf-8')
message["From"] = email["user"]
message["Subject"] = Header("ctf flag","utf-8")
# 發送flag的函數
def sendflag(toEmail):
message["To"] = toEmail
sender.sendmail(email["user"],toEmail,message.as_string())
# log
os.system("echo "+time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) +": Get flag -- "+toEmail+" >> /tmp/variant_of_cat.log")
print("send success")
# 監聽合約事件的函數
def event():
# 從網絡中的事件日志中抓取符合這一合約的日志信息
flag_logs = w3.eth.getLogs({
"address":contract_address,
"topic0":contract_topic0
})
if flag_logs is not []:
for flag_log in flag_logs:
data = flag_log["data"][2:]
length = int(data[64*2:64*3].replace('00', ''),16)
data = data[64*3:][:length*2]
b64email = binascii.unhexlify(data).decode('utf-8')
try:
email = base64.b64decode(b64email).decode('utf-8')
sendflag(email)
except:
errmsg = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())+":decode or send to b64 - {} fail".format(b64email)
os.system("echo " + errmsg + ">> /tmp/variant_of_cat_error.log")
print(errmsg)
# 循環運行
while(True):
event()
time.sleep(30)
運行上述腳本就可以實現一旦調用合約的getFlag函數就能執行發送flag郵件的操作了。
不過這里還有一點小毛病,就是sleep(30)可能短於新區塊的產生時間,導致會連續發送多個郵件過來(我猜測是這個原因,具體后面再推斷)。