本文章是参考一位大佬博客学来的。
智能合约题的环境主要包括两部分:一个是智能合约的部署,另外一个就是监听合约事件进而发送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)可能短于新区块的产生时间,致使会连续发送多个邮件过来(我猜想是这个缘由,具体后面再推断)。