Hardhat plugin for building solidity capture the flag (CTF) challenges.

Downloads in past


220.0.64 months ago4 months agoMinified + gzip package size for hardhat-ctf in KB


Hardhat plugin for building solidity capture the flag (CTF) challenges.


This is a framework to implement a solidity CTF challenge.
Check the example folder for a sample challenge built using this plugin.
This plugin can be helpful when you:
  1. Don't want the flag to be available in the contract source code / bytecode.
  2. Want people to solve it in a competitive CTF environment where they get points for flags.
  3. Don't want people to be able to cheat by querying the RPC node for other people's transactions.
  4. Want to deploy the RPC server in a scalable stateless way in order to mitigate porential DDOS attacks.
  5. Want to provide a clean execution environment each time trying to solve the challenge (can be important when dealing with nonces or deploying contracts to specific addresses).

It achieves it by creating a temporary Hardhat Network for each websocket connection, initializing the challenge contracts, and allows the challenge author to hook into the RPC responses in order to inject the flag without it being on-chain.


npm install hardhat-ctf

Import the plugin in your hardhat.config.js:

Or if you are using TypeScript, in your hardhat.config.ts:
import "hardhat-ctf";


This plugin provides the ctf-node task, which allows you run a websocket hardhat network that creates a temporary hardhat network for each websocket connection, and can hook into the RPC responses to inject the flag.
This plugin provides the ctf-try task, which allows you to try and solve the challenge. Once you solved it locally, you can run the ctf-try task with the --submit flag to send the solution to the remote CTF node in order to obtain the real flag.


This plugin extends the HardhatUserConfig object with ctfResponseHook and ctfRemoteNode fields.
The ctfResponseHook field is only relevant for the npx hardhat ctf-node task, while the ctfRemoteNode is only relevant for the npx hardhat ctf-try --submit task.
This is an example of how to set it:
import { ethers } from "ethers";
import { EthereumProvider, JsonRpcRequest } from "hardhat/types";
import { JsonRpcResponse, SuccessfulJsonRpcResponse } from "hardhat/internal/util/jsonrpc";

const config: HardhatUserConfig = {
  solidity: "0.8.4",
  ctfRemoteNode: "ws://ctf.example.com:8545",
  ctfResponseHook: async (provider: EthereumProvider, rpcReq: JsonRpcRequest, rpcResp: JsonRpcResponse) => {
    if (
      rpcReq.method === "eth_call"
      && rpcReq?.params?.[0]?.to == "0x5fbdb2315678afecb367f032d93f642f64180aa3"
      && (rpcReq?.params?.[0]?.data || '').startsWith('0x95fdc999')
      && (rpcResp as SuccessfulJsonRpcResponse).result == ethers.utils.defaultAbiCoder.encode(["string"], ["CTF{mock_flag}"])
    ) {
      (rpcResp as SuccessfulJsonRpcResponse).result = ethers.utils.defaultAbiCoder.encode(["string"], [process.env.CTF_FLAG ?? "Use the CTF_FLAG environment variable when running 'npx hardhat ctf-node'"]);
    return rpcResp


Check out the example folder for an example project using this plugin to create a CTF challenge.
On the server running the CTF node which validates solutions and gives the flag to succesful contestants, run:
npx hardhat ctf-node

Then, edit the hardhat.config.ts file to point the config's ctfRemoteNode field to your server running ctf-node, and give the folder to anyone who wants to try solving the challenge.