区块链出题指南 solidity篇

ctf 区块链 出题指南 solidity篇

前言:

区块链方向已然成为ctf一大主流的竞赛内容,故写此文章来分享如何出eth相关的题目,主要面向solidity,move语言暂时不在本文章讨论范围内,后续看心情编写。本文只介绍如何在公链和私链部署智能合约题目。

在测试链上部署智能合约:

首先谈一下测试链的问题,以及为什么逐渐被淘汰了,首先的问题就是测试链不防上车,一个选手解出来后通过eth的浏览器可以轻松的看见该选手的exp内容,可以快速实现上车的目的

接下来浅谈一下如何在测试链上部署题目

关于测试币如何领取可以参考之前的文章:Goerli与Sepolia,mumbai测试币领取-魔法少女雪殇 (snowywar.top)

关于测试链上合约编写特点

由于测试链上环境共享,每个选手在做题时独立来部署合约不现实(现在可以实现了,后续介绍),所以通用办法是写一个mapping来记录每个选手所交互的地址是否满足条件,如果满足条件则触发emit事件(emit可以由后台脚本进行监听)来决定是否进行发送相关的flag内容等。

案例演示:

编写智能合约部署在测试网:

pragma solidity ^0.8.9;

contract Testcontract {
   mapping(address=>uint) _flag;

   event sendflag(string email,string tokens);

   function test() public {
       _flag[msg.sender] = 1;
  }
   function payforflag(string memory _base64Email,string memory _token) public {
       require(_flag[msg.sender] == 1);
       emit sendflag(_base64Email , _token);
  }
}
图片[1]-区块链出题指南 solidity篇-魔法少女雪殇

然后基本上所有的eth浏览器都给了可以查询的api接口,比如我用的mumbai测试网的api网址

Mumbai PolygonScan – PolygonScan

接下来就是参考文档写轮询脚本即可

花了五分钟写的,大概逻辑就是反复请求给的api就行了,然后把event进行解密,之后发送邮箱,具体思路这样

from web3 import Web3, HTTPProvider
import requests
import smtplib
from email.mime.text import MIMEText
from email.header import Header
import base64
import time


sender = "xxx@qq.com"
mail_host = "smtp.qq.com"
smtpport = 465
password = "xxxx"


def send_mail(mail, token):
   try:
       smtp=smtplib.SMTP_SSL(mail_host, smtpport)
       smtp.login(sender, password)
       message = MIMEText("flag is a flag{123}", "plain", "utf-8")
       message["From"] = Header("Sender", "utf-8")
       message["To"] = Header("Receiver", "utf-8")
       subject = "here is your flag"
       message["Subject"] = Header(subject, "utf-8")
       smtp.sendmail(sender, mail, message.as_string())
   except smtplib.SMTPException as e:
       print(e)
   finally:
       smtp.quit()

topics = []

def event():
   url = "https://api-testnet.polygonscan.com/api?module=logs&action=getLogs&topic1&address=xxxxx"
   response = requests.get(url)
   data = response.json()
   print(data)
   if data['status'] == '1':
       for i in data['result']:
           if i["topics"][0] in topics:
               continue
           else:
               #发送邮件
               logs = i['data']
               logs = logs[2:]
               logs = [logs[i:i+64] for i in range(0, len(logs), 64)]
               logs[3] = Web3.toText(Web3.toBytes(hexstr=logs[3]))
               logs[5] = Web3.toText(Web3.toBytes(hexstr=logs[5]))
               mail = logs[3]
               token = logs[5]
               mail = base64.b64decode(mail).decode()
               send_mail(mail, token)
               topics.append(i["topics"][0])
               
while(True):
   event()
   time.sleep(20)

具体内容log要做具体解析,看区块链浏览器的event就好。

图片[2]-区块链出题指南 solidity篇-魔法少女雪殇

效果演示:

图片[3]-区块链出题指南 solidity篇-魔法少女雪殇

测试链结合自动部署出题方案

该方案部署起来也比较方便,不需要单独运行水龙头等内容,可以运行在测试网上

使用工具:

eth-challenge-base/example at main · chainflag/eth-challenge-base (github.com)

后续也会使用这套工具来进行,还是非常感谢这套系统的作者的。

项目克隆下来后,进入example路径,编写.env文件

图片[4]-区块链出题指南 solidity篇-魔法少女雪殇

其余几个不用管,常规题目基本上用不傲,只需要注意第一个即可

前往app.infura.io网站注册,获取api

图片[5]-区块链出题指南 solidity篇-魔法少女雪殇

选择想要部署的网络,复制即可。

将你编写好的合约文件放入contract文件夹中,来编写一个测试合约

pragma solidity 0.8.7;

contract Checkin {
   string greeting;

   constructor(string memory _greeting) public {
       greeting = _greeting;
  }

   function greet() public view returns (string memory) {
       return greeting;
  }

   function setGreeting(string memory _greeting) public {
       greeting = _greeting;
  }

   function isSolved() public view returns (bool) {
       string memory key = "HelloCTF";
       return keccak256(abi.encodePacked(key)) == keccak256(abi.encodePacked(greeting));
  }
}

然后编写challenge.yml

图片[6]-区块链出题指南 solidity篇-魔法少女雪殇

其实注释也写得比较完整,比较需要注意的是contract和args,contract上要写的内容是合约的名称,比如上述合约是Checkin,那么这个是checkin,包括后续去编写工厂合约还是ERC20等相关内容,只需要写上主要的主合约名称即可。

args决定了部署合约时传输的内容,以数组模式表示,此处对应constructor,如果传输数量和 constructor所需内容不符就会报错,这里要对应上,如果不需要就注释

value值决定了该合约在创建后有多少个eth,对应的在创建时需要转给相应的eth,单位是eth

其他的就没什么好说了,后面都有注释。

(注:合约要有isSolved函数,如果没有就去修改solved_event)

编写好后就可以docker-compose up -d进行部署测试,编译需要一段时间,等几分钟再nc查看,如果报错可能是网络问题,自行解决。

nc即可,如有需要也可以修改docker-compose.yml来修改端口

效果演示

图片[7]-区块链出题指南 solidity篇-魔法少女雪殇
图片[8]-区块链出题指南 solidity篇-魔法少女雪殇

还是比较方便的,也不需要去维护geth,如果没有特殊要求我个人还是比较推荐该方案的。

当然所有数据在测试网均可查,转账也是用metamask进行转账操作。

私链水龙头+自动部署题目出题方案

这个方案应该是如今ctf环境用得比较多的,当然部署起来还是有点小坑,这里也都编写出来分享一下。

整体的项目还是参考chainflag/eth-challenge-base: xinetd docker for building ethereum contract challenges (github.com)

这个项目,具体修改内容如下:

主目录的entrypoint.sh可能存在编码问题,我建议跑一次uinx2doc。image-20230118134506774

然后其余不用动了,进入geth文件目录,主要修改内容是.env.example

图片[9]-区块链出题指南 solidity篇-魔法少女雪殇

这些内容其实在 readme里面有详解,那么也还是说一下。chain_id其实无所谓,就是指定一个id,任意即可,第二个是geth所指定的一个用户地址,这个需要用来当水龙头的分发地址和miner的地址,必须要有的,第三行是对应上面地址的一个私钥,可以从metamask导出,也可以自主生成,总之要和地址对应起来

第四行也任意,配置好后保存重命名为.env文件

修改项目自带的njs路径下的eth-jsonrpc-access.js

项目自带的该文件无法让用户自行部署合约,这很不好,不符合我使用remix进行解题的幻想,重写为如下内容

function access(r) {
 var whitelist = [
     "eth_blockNumber",
     "eth_call",
     "eth_chainId",
     "eth_estimateGas",
     "eth_gasPrice",
     "eth_getBalance",
     "eth_getCode",
     "eth_getStorageAt",
     "eth_getTransactionByHash",
     "eth_getTransactionCount",
     "eth_getTransactionReceipt",
     "eth_sendRawTransaction",
     "net_version",
     "rpc_modules",
     "web3_clientVersion"
];

 try {
     var payload = JSON.parse(r.requestBody);
     if (payload.jsonrpc !== "2.0") {
         r.return(401, "jsonrpc version not supported\n");
         return;
    }
     // if (!whitelist.includes(payload.method)) {
     //     r.return(401, "jsonrpc method is not allowed\n");
     //     return;
     // }
     if (Object.keys(payload).filter(key => key.toLowerCase() === 'method').length > 1) {
         r.return(401, "jsonrpc method is not allowed\n");
         return;
    }
} catch (error) {
     r.return(415, "Cannot parse payload into JSON\n");
     return;
}

 r.internalRedirect('@jsonrpc');
}

export default { access }

(不排除其他问题,但却是使用默认的无法部署合约。欢迎补充)

修改后然后编写docker-compose.yml

把上个方案的那些内容拿过来,然后把uri改为geth:8545

图片[10]-区块链出题指南 solidity篇-魔法少女雪殇

文件也复制在同一目录下

在docker-compose.yml中追加,当然也可以部署多个,直接后面继续修改端口和新建几个合约文件夹即可,这样题目和水龙头就都在一个网络里了

图片[11]-区块链出题指南 solidity篇-魔法少女雪殇

docker-compose up -d进行部署

图片[12]-区块链出题指南 solidity篇-魔法少女雪殇

可以访问8080查看水龙头

图片[13]-区块链出题指南 solidity篇-魔法少女雪殇

可以在metamask进行连接rpc网络。

图片[14]-区块链出题指南 solidity篇-魔法少女雪殇

剩下的与上一方案基本没差别,只是把整体网络搬到了私链,极大程度加大了上车的难度。整体响应速度也比在公链上快,也不会出现搞不到水龙头的情况。

补充:该项目nc后的第四个选项只会显示主要编译的合约,如果出现了多合约的题目,个人建议再challenges.yml的show_source:False这段取消注释,然后以附件的形式提供题目源码等内容。当然有能力的也可以去修改app.py源码,可以自行发挥。

图片[15]-区块链出题指南 solidity篇-魔法少女雪殇

总结

以上是常见的三种eth出题的一个方案,也算是在自己近期出题的时候踩的坑和相关内容进行一个记录,有些地方可能存在不全或者不正确的问题,也希望大家能够指出,大家伙也给chainflag这个项目去点点star,真的非常牛逼。

参考文章

https://app.infura.io/

智能合约攻击面及ctf出题指南 – DoubleMice – 我以晦朔春秋为聘,不知你愿否共我度完蜉蝣小年。

chainflag/eth-challenge-base: xinetd docker for building ethereum contract challenges (github.com)

CTF区块链题目环境的防抄袭方案 | iczc

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 共1条
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情
    • 头像rantrism0