Paradigm CTF 2021 比赛环境

搭建 Paradigm CTF 2021 比赛环境,从入门到放弃~

## 简介 [Paradigm CTF 2021](https://ctf.paradigm.xyz/) 是今年二月份举办的一场夺旗比赛,共 17 题,为时两天。[这里](https://ctf.paradigm.xyz/scoreboard) 是大佬们各显神通的最终排名 此次比赛群英荟萃:主要出题人为 [samczsun](https://samczsun.com/),作为知名白帽于去年加入 Paradigm 任职研究合伙人,暂列 [以太坊赏金榜](https://bounty.ethereum.org/) 第二名;另外,排行榜中也有几个熟悉的 ID,比如解出 15 题的冠军队伍 dilicious,其成员 [smarx](https://smarx.com/) 是 [Capture the Ether](https://capturetheether.com/challenges/) 的出题人;而在 Capture the Ether 中并列第一的队伍 WHOAMI,此次比赛名列第四,解出 11 题 ## 环境 ### 声明 实际上可以手工部署合约,所以本地环境不是必须,只是无法体验参赛的感觉,少了部分压力 我在网上搜了一圈,没找到比赛环境相关的信息.. 因此只能参考赛后放出的环境代码,以下都是推测,且有多处存疑.. 欢迎大佬告知,提前谢过了~ ### 环境推测 [eth_challenge_base/eth_sandbox/server.py](https://github.com/paradigm-operations/paradigm-ctf-2021/blob/fc408571b83c3eea46c04bd703ed940485bc5b0f/eth_challenge_base/eth_sandbox/server.py#L84) [eth_challenge_base/eth_sandbox/launcher.py](https://github.com/paradigm-operations/paradigm-ctf-2021/blob/fc408571b83c3eea46c04bd703ed940485bc5b0f/eth_challenge_base/eth_sandbox/launcher.py) Paradigm CTF 2021 使用 `Docker` 创建隔离的容器,作为各个队伍的的比赛环境,不至互相影响 为了公平,容器内使用 `ganache-cli` 克隆主链得到私链,比赛在私链上进行,因此队伍间无法看到他人的解题过程 所有题目都是对应智能合约 `Setup.sol` 的实例,其构造函数执行出题逻辑,公有函数 `function solve() public;` 测试题目是否成功解答 一道题目可以有多个 `setup contract` 实例,每次请求出题都会得到重新克隆的私链,多条私链将会共存,因此队伍内成员间可以同时解题,不会影响 每次请求出题,除了返回 `setup contract` 之外,还会返回 `uuid`,请求通关时需要输入,表示通关哪个实例 `ganache-cli` 监听在随机端口上,不与外界互通,因此无法将其作为 `Web3 Provider` 节点提交解题交易 容器启动时会创建代理服务,默认监听在 8545 端口上,与外界互通,参赛者必须将解题交易提交至代理服务,由其转发给私链 代理服务遵循 [Ethereum JSON-RPC Specification](https://playground.open-rpc.org/?schemaUrl=https://raw.githubusercontent.com/ethereum/eth1.0-apis/assembled-spec/openrpc.json&uiSchema%5BappBar%5D%5Bui:splitView%5D=false&uiSchema%5BappBar%5D%5Bui:input%5D=false&uiSchema%5BappBar%5D%5Bui:examplesDropdown%5D=false),只是请求时 `header` 中必须包括 `X-Auth-Key` 字段,作为鉴权;它是个随机数,每个容器唯一,其他队伍无法猜出 #### X-Auth-Key 我来回看了环境代码,还是没确定参赛队伍怎样得到自己的 `X-Auth-Key` 一种很显然的推论是登陆自己容器查询 `/tmp/auth`,但结合后面请求出题时,要求输入的 工作量证明来看,感觉容器无法登陆,否则工作量证明失去意义.. 另一种方式,可能是组织者私下分发 ### 赛制推测 #### 是否实时排名 比赛在私链进行,所以比赛过程中,应该没有实时排名,也就无法根据题目通关人数判断难易程度,权衡解题策略 退一步说,即使组织者安排了实时排名,参与者也可以在解答后,暂时不提交通关交易,隐藏自己的解题分布 除非比赛积分计算时,包括消耗时间 #### 积分 每道题有不同的积分,首位通关者应该有额外积分 除了首位通关者,后续答出此题的参赛者,计算积分时是否根据解题次序变化,个人存疑 ## 搭建 了解环境原理后,搭建其实挺简单的,基本参考 [官方文档](https://github.com/paradigm-operations/paradigm-ctf-2021/blob/master/README.md) 即可 ### 准备 ```js $ pip3 install solc-select $ solc-select install 0.4.16 0.4.24 0.5.12 0.6.12 0.7.0 0.7.6 0.8.0 $ pip3 install ecdsa sha3 ``` ### 构建 ```zsh $ ./build ``` ## 参与 这里以送分题 `HELLO` 为例 ### 启动 ```zsh $ export RPC_URL="https://mainnet.infura.io/v3/7044f2db16e84c559b57b67b565be7ae" $ ./run hello 31337 31338 [+] running eth challenge [+] running /startup/00-create-xinetd-service [+] running /startup/98-start-gunicorn [+] running /startup/99-start-xinetd ==> /var/log/ctf/gunicorn.access.log <== ==> /var/log/ctf/gunicorn.error.log <== [2021-08-29 15:14:24 +0000] [14] [INFO] Starting gunicorn 20.0.4 [2021-08-29 15:14:24 +0000] [14] [INFO] Listening at: http://0.0.0.0:31338 (14) [2021-08-29 15:14:24 +0000] [14] [INFO] Using worker: sync [2021-08-29 15:14:24 +0000] [17] [INFO] Booting worker with pid: 17 ``` 启动前需要配置环境变量 `RPC_URL`,作为私链的克隆来源 注意这里必须是主链的 `endpoint`,因为题目用到不少主链上的合约地址,如 `WETH9`,`UniswapV2Pair` 等 ### 请求出题 ```zsh $ nc localhost 31337 1 - launch new instance 2 - get flag action? 1 hashcash -mb24 bwrsartb = ? 1:24:210830:bwrsartb::lPMmFUGK:66b1dc your private blockchain has been deployed it will automatically terminate in 30 minutes here\'s some useful information uuid: 8dc44d42-1b55-49b2-aef9-163a8c5d57cb rpc endpoint: http://127.0.0.1:31338/8dc44d42-1b55-49b2-aef9-163a8c5d57cb private key: 0xee958753a7e115323be41435c0fafac5f5f83024107cb06c4f76f40d318776f0 setup contract: 0xD535677b44ffe3e32C64F26bcB191186c182A33f ``` 这里需要输入 `bwrsartb` 的计算结果作为工作量证明,容器以之提高请求出题的成本,避免泛滥的出题 `Hashcash` 计算如下 ```zsh $ python3 eth_challenge_base/eth_sandbox/hashcash.py -mb24 bwrsartb 1:24:210830:bwrsartb::lPMmFUGK:66b1dc ``` 服务器验证通过后,返回如下信息 `uuid`: 实例标识,请求通关时要求输入 `rpc endpoint`: 代理服务 `private key`: 解题账户,balance 为 5000 ETH `setup contract`: 题目实例 #### 一个小坑 返回信息中还包括一个提示 `it will automatically terminate in 30 minutes` 这是个容易漏过的小坑,后文描述 #### 题外 关于 `Hashcash` ,可以参考 [比特币与密码朋克的延续](https://ethfans.org/posts/bitcoin-and-the-rise-of-the-cypherpunks) ### 请求通关 ```zsh $ nc localhost 31337 1 - launch new instance 2 - get flag action? 2 uuid? 8dc44d42-1b55-49b2-aef9-163a8c5d57cb PCTF{placeholder} ``` 完成题目后,就可以请求通关了,这里需要输入前面的 `uuid` 验证通关会返回 `PCTF{placeholder}`,其中 `placeholder` 就是一面旗子 啦 `placeholder` 记录在题目目录下的 `info.yaml`,正式比赛时应该是容器启动时的环境变量 ## 报错 现在可以开心的玩耍了 ...直到时不时遇到一些报错... 组织者特意在 30 分钟后关闭私链,就是为了尽可能绕过第一个错误 `Returned error: project ID does not have access to archive state`,参考 [project ID does not have access to archive state](https://github.com/compound-developers/compound-supply-examples/issues/2) ```js (node:49933) UnhandledPromiseRejectionWarning: Error: Returned error: VM Exception while processing transaction: Error: Returned error: project ID does not have access to archive state ``` ```zsh ValueError: {'message': 'Cannot get state root with uncommitted checkpoints', 'code': -32000, 'data': {'stack': 'Error: Cannot get state root with uncommitted checkpoints\n at e.getStateRoot (/usr/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:55:643335)\n at ...)', 'name': 'Error'}} ``` ```zsh Error: The fork provider errored when checking the nonce for account 0x065c2701b5a84ba82be91ded7eed2528788492aa: Returned error: header not found ``` ```zsh ValueError: {'message': 'Returned error: header not found', 'code': -32000, 'data': {'stack': 'Error: Returned error: header not found\n at Object.ErrorResponse (/usr/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:55:2110625)\n at ...)', 'name': 'Error'}} ``` ```zsh socket.timeout: timed out ``` ```zsh ValueError: {'message': 'sender account not recognized', 'code': -32000, 'data': {'stack': 'n: sender account not recognized\n at S.queueTransaction (/usr/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:55:211190)\n at ...)', 'name': 'n'}} ``` ## 本地 最终放弃了容器环境,转为本机直接搭了私链,真的香~ ```zsh ./node_modules/ganache-cli/cli.js --fork https://mainnet.infura.io/v3/7044f2db16e84c559b57b67b565be7ae --accounts 1 --defaultBalanceEther 5000 Ganache CLI v6.12.2 (ganache-core: 2.13.2) Available Accounts ================== (0) 0x127a8b71E2807C23D4a5E801925d0157a5d12de8 (5000 ETH) Private Keys ================== (0) 0x93944d12f660133cf6f0c6d5125c6cb7493e0ae11fc485773688fed89c278af0 HD Wallet ================== Mnemonic: bench turkey giraffe bid cup grape expect miss rigid believe rookie inmate Base HD Path: m/44'/60'/0'/0/{account_index} Gas Price ================== 20000000000 Gas Limit ================== 6721975 Call Gas Limit ================== 9007199254740991 Forked Chain ================== Location: https://mainnet.infura.io/v3/7044f2db16e84c559b57b67b565be7ae Block: 13138345 Network ID: 1 Time: Wed Sep 01 2021 15:10:27 GMT+0800 (China Standard Time) Max Cache Size: 1073741824 bytes ```

简介

Paradigm CTF 2021 是今年二月份举办的一场夺旗比赛,共 17 题,为时两天。这里 是大佬们各显神通的最终排名

此次比赛群英荟萃:主要出题人为 samczsun,作为知名白帽于去年加入 Paradigm 任职研究合伙人,暂列 以太坊赏金榜 第二名;另外,排行榜中也有几个熟悉的 ID,比如解出 15 题的冠军队伍 dilicious,其成员 smarx 是 Capture the Ether 的出题人;而在 Capture the Ether 中并列第一的队伍 WHOAMI,此次比赛名列第四,解出 11 题

环境

声明

实际上可以手工部署合约,所以本地环境不是必须,只是无法体验参赛的感觉,少了部分压力

我在网上搜了一圈,没找到比赛环境相关的信息..

因此只能参考赛后放出的环境代码,以下都是推测,且有多处存疑..

欢迎大佬告知,提前谢过了~

环境推测

eth_challenge_base/eth_sandbox/server.py

eth_challenge_base/eth_sandbox/launcher.py

Paradigm CTF 2021 使用 Docker 创建隔离的容器,作为各个队伍的的比赛环境,不至互相影响

为了公平,容器内使用 ganache-cli 克隆主链得到私链,比赛在私链上进行,因此队伍间无法看到他人的解题过程

所有题目都是对应智能合约 Setup.sol 的实例,其构造函数执行出题逻辑,公有函数 function solve() public; 测试题目是否成功解答

一道题目可以有多个 setup contract 实例,每次请求出题都会得到重新克隆的私链,多条私链将会共存,因此队伍内成员间可以同时解题,不会影响

每次请求出题,除了返回 setup contract 之外,还会返回 uuid,请求通关时需要输入,表示通关哪个实例

ganache-cli 监听在随机端口上,不与外界互通,因此无法将其作为 Web3 Provider 节点提交解题交易

容器启动时会创建代理服务,默认监听在 8545 端口上,与外界互通,参赛者必须将解题交易提交至代理服务,由其转发给私链

代理服务遵循 Ethereum JSON-RPC Specification,只是请求时 header 中必须包括 X-Auth-Key 字段,作为鉴权;它是个随机数,每个容器唯一,其他队伍无法猜出

X-Auth-Key

我来回看了环境代码,还是没确定参赛队伍怎样得到自己的 X-Auth-Key

一种很显然的推论是登陆自己容器查询 /tmp/auth,但结合后面请求出题时,要求输入的 工作量证明来看,感觉容器无法登陆,否则工作量证明失去意义..

另一种方式,可能是组织者私下分发

赛制推测

是否实时排名

比赛在私链进行,所以比赛过程中,应该没有实时排名,也就无法根据题目通关人数判断难易程度,权衡解题策略

退一步说,即使组织者安排了实时排名,参与者也可以在解答后,暂时不提交通关交易,隐藏自己的解题分布

除非比赛积分计算时,包括消耗时间

积分

每道题有不同的积分,首位通关者应该有额外积分

除了首位通关者,后续答出此题的参赛者,计算积分时是否根据解题次序变化,个人存疑

搭建

了解环境原理后,搭建其实挺简单的,基本参考 官方文档 即可

准备

$ pip3 install solc-select
$ solc-select install 0.4.16 0.4.24 0.5.12 0.6.12 0.7.0 0.7.6 0.8.0
$ pip3 install ecdsa sha3

构建

$ ./build

参与

这里以送分题 HELLO 为例

启动

$ export RPC_URL="https://mainnet.infura.io/v3/7044f2db16e84c559b57b67b565be7ae"
$ ./run hello 31337 31338
[+] running eth challenge
[+] running /startup/00-create-xinetd-service
[+] running /startup/98-start-gunicorn
[+] running /startup/99-start-xinetd
==> /var/log/ctf/gunicorn.access.log &lt;==

==> /var/log/ctf/gunicorn.error.log &lt;==
[2021-08-29 15:14:24 +0000] [14] [INFO] Starting gunicorn 20.0.4
[2021-08-29 15:14:24 +0000] [14] [INFO] Listening at: http://0.0.0.0:31338 (14)
[2021-08-29 15:14:24 +0000] [14] [INFO] Using worker: sync
[2021-08-29 15:14:24 +0000] [17] [INFO] Booting worker with pid: 17

启动前需要配置环境变量 RPC_URL,作为私链的克隆来源

注意这里必须是主链的 endpoint,因为题目用到不少主链上的合约地址,如 WETH9UniswapV2Pair

请求出题

$ nc localhost 31337
1 - launch new instance
2 - get flag
action? 1
hashcash -mb24 bwrsartb = ? 1:24:210830:bwrsartb::lPMmFUGK:66b1dc

your private blockchain has been deployed
it will automatically terminate in 30 minutes
here\'s some useful information
uuid:           8dc44d42-1b55-49b2-aef9-163a8c5d57cb
rpc endpoint:   http://127.0.0.1:31338/8dc44d42-1b55-49b2-aef9-163a8c5d57cb
private key:    0xee958753a7e115323be41435c0fafac5f5f83024107cb06c4f76f40d318776f0
setup contract: 0xD535677b44ffe3e32C64F26bcB191186c182A33f

这里需要输入 bwrsartb 的计算结果作为工作量证明,容器以之提高请求出题的成本,避免泛滥的出题

Hashcash 计算如下

$ python3 eth_challenge_base/eth_sandbox/hashcash.py -mb24 bwrsartb
1:24:210830:bwrsartb::lPMmFUGK:66b1dc

服务器验证通过后,返回如下信息

uuid: 实例标识,请求通关时要求输入

rpc endpoint: 代理服务

private key: 解题账户,balance 为 5000 ETH

setup contract: 题目实例

一个小坑

返回信息中还包括一个提示 it will automatically terminate in 30 minutes

这是个容易漏过的小坑,后文描述

题外

关于 Hashcash ,可以参考 比特币与密码朋克的延续

请求通关

$ nc localhost 31337
1 - launch new instance
2 - get flag
action? 2
uuid? 8dc44d42-1b55-49b2-aef9-163a8c5d57cb
PCTF{placeholder}

完成题目后,就可以请求通关了,这里需要输入前面的 uuid

验证通关会返回 PCTF{placeholder},其中 placeholder 就是一面旗子 啦

placeholder 记录在题目目录下的 info.yaml,正式比赛时应该是容器启动时的环境变量

报错

现在可以开心的玩耍了

...直到时不时遇到一些报错...

组织者特意在 30 分钟后关闭私链,就是为了尽可能绕过第一个错误 Returned error: project ID does not have access to archive state,参考 project ID does not have access to archive state

(node:49933) UnhandledPromiseRejectionWarning: Error: Returned error: VM Exception while processing transaction: Error: Returned error: project ID does not have access to archive state
ValueError: {'message': 'Cannot get state root with uncommitted checkpoints', 'code': -32000, 'data': {'stack': 'Error: Cannot get state root with uncommitted checkpoints\n    at e.getStateRoot (/usr/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:55:643335)\n    at ...)', 'name': 'Error'}}
Error: The fork provider errored when checking the nonce for account 0x065c2701b5a84ba82be91ded7eed2528788492aa: Returned error: header not found
ValueError: {'message': 'Returned error: header not found', 'code': -32000, 'data': {'stack': 'Error: Returned error: header not found\n    at Object.ErrorResponse (/usr/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:55:2110625)\n    at  ...)', 'name': 'Error'}}
socket.timeout: timed out
ValueError: {'message': 'sender account not recognized', 'code': -32000, 'data': {'stack': 'n: sender account not recognized\n    at S.queueTransaction (/usr/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:55:211190)\n    at ...)', 'name': 'n'}}

本地

最终放弃了容器环境,转为本机直接搭了私链,真的香~

./node_modules/ganache-cli/cli.js --fork https://mainnet.infura.io/v3/7044f2db16e84c559b57b67b565be7ae --accounts 1 --defaultBalanceEther 5000

Ganache CLI v6.12.2 (ganache-core: 2.13.2)
Available Accounts
==================
(0) 0x127a8b71E2807C23D4a5E801925d0157a5d12de8 (5000 ETH)

Private Keys
==================
(0) 0x93944d12f660133cf6f0c6d5125c6cb7493e0ae11fc485773688fed89c278af0

HD Wallet
==================
Mnemonic:      bench turkey giraffe bid cup grape expect miss rigid believe rookie inmate
Base HD Path:  m/44'/60'/0'/0/{account_index}

Gas Price
==================
20000000000

Gas Limit
==================
6721975

Call Gas Limit
==================
9007199254740991

Forked Chain
==================
Location:       https://mainnet.infura.io/v3/7044f2db16e84c559b57b67b565be7ae
Block:          13138345
Network ID:     1
Time:           Wed Sep 01 2021 15:10:27 GMT+0800 (China Standard Time)
Max Cache Size: 1073741824 bytes

本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

  • 发表于 2021-09-03 21:59
  • 阅读 ( 443 )
  • 学分 ( 14 )
  • 分类:智能合约

评论