本文章是參考一位大佬博客學來的。
智能合約題的環境主要包括兩部分:一個是智能合約的部署,另外一個就是監聽合約事件進而發送flag的腳本。python
這裏寫的合約是指solidity合約,使用Remix IDE。
合約主要部署到以太坊測試鏈而非主鏈上(沒錢😑),幾個主要的測試鏈:Ropsten,Rinkeby,Kovan。
這裏須要一個瀏覽器錢包插件MetaMask(能夠在FireFox和Chrom上下載),註冊並申請帳戶後,選擇測試網絡(筆者選擇的是Rospten):git
新建立的帳戶是沒有以太幣的,須要到測試水管(在首頁點擊存入)申請:github
有了以太幣以後就能夠利用Remix IDE將合約代碼部署到測試網絡。
這裏先準備一個簡單的發送flag的合約:web
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。ide
先註冊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)可能短於新區塊的產生時間,致使會連續發送多個郵件過來(我猜想是這個緣由,具體後面再推斷)。