@inspex/poctous
v1.2.0
Published
A tool for creating a Forge project that replicates the same environment as the attacking incident during the specified transaction.
Downloads
8
Readme
POCtoUS
POCtoUS is pronounced like 'Octopus'🐙 but in reverse. It is a script that aims to help create an environment for analyzing a transaction on the blockchain by leveraging the power of an incredible tool, Foundry.
The tool will create a foundry test file of a given transaction hash. The tool will try to imitate the attacker's attack contract. It also downloads and flattens the contract source code of every related address.
For now, the code looks really messy. We want to express the ideas first, then re-organize them after consider it worth doing.
Usage
The script can be executed anywhere using poctopus
.
poctous [txHash] [-f force pulling the rpc] [-r url of the rpc] [-k API key of the block explorer] [-e (optional) endpoint of the block explorers API] [--auto-merge enable auto-merge feature] [--maximize download all un-related verified contract]
For example
poctous 0xc42fe1ce2516e125a386d198703b2422aa0190b25ef6a7b0a1d3c6f5d199ffad -r https://eth.llamarpc.com -k ABC1234567
The script will create a folder POC
at the current directory that contains basic necessities for doing the POC.
- Some contracts downloading may fail. If you experience some oddity in the POC file, you can run the script again to re-download the failed files. This might help in some cases.
Normally, the script will pull the data from the RPC once and cache the pulled data. On the next executions, it will use the data from the cache file. You can use the -f
flag to force the script to pull the data from the RPC instead of using the cache.
Run the test file
The POC files are forge test files. They can be run with the ol' classic forge test
command. You can add --match-contract
flag to specify the POC contract that you want to test specifically.
In the test file, we specified that the test would fork from Anvil
environment. We suggest running the anvil
beforehand, which would be good for the testing.
anvil -f <RPC_URL> --no-mining
Required Environment
The script requires the RPC and the API key of the chain's block explorer to operate. They can be specified using the -r
and -k
flags when executing the script.
poctous 0xc42fe1ce2516e125a386d198703b2422aa0190b25ef6a7b0a1d3c6f5d199ffad \
-r https://eth.llamarpc.com \
-k ABC1234567
The -e
flag is totally an optional. The script will check the chain from the RPC and use default block explorer from the predefined list. When you need to use the script on another chain that does not in the Predefined endpoint chain
list, you need to specify the endpoint through the -e
flag.
Predefined endpoint chain
- Ethereum Mainnet (https://api.etherscan.io/api)
- Goerli (https://api-goerli.etherscan.io/api)
- OP Mainnet (https://api-optimistic.etherscan.io/api)
- BNB Smart Chain Mainnet (https://api.bscscan.com/api)
- BNB Smart Chain Testnet (https://api-testnet.bscscan.com/api)
- Arbitrum One (https://api.arbiscan.io/api)
File structure
The script will create a POC
folder that have the forge init
setup.
Sourcecode
The script will download all verified contract the are interacted on the transaction. The downloaded sourcecode will be flattened and have the abis and interfaces extracted. They will be stored under the src_poc
folder and each transaction
folder.
Test file
The testfile (POC) is a forge test file. It will be created under the test
folder. You can run the test file like any other forge test file by using forge test
command.
POC
├── README.md
├── cache
├── foundry.toml
├── lib
├── out
├── script
├── src
├── src_poc
│ └── 0xabcdef // First 4 bytes of the tx
│ ├── (Contracts replate to the tx)
│ │ ├── (ContractName).abi.json
│ │ ├── (ContractName).interface.sol
│ │ └── (ContractName).sol
│ ├── interface.aggregate.sol // the interface that being used by the POC
│ └── lib.constant.sol // the constrant value of the addresses
└── test
└── CopiedCall(0xabcdef).t.sol // The POC file (almost) ready to be tested
Installation
Users can install POCtoUS from npm registry directly.
npm install -g @inspex/poctous
Or clone the repository then install it locally.
git clone https://github.com/InspexCo/POCtoUS.git
cd poctous
npm install -g
Example
To demonstrate the tool, we choose a transaction that have had an attack occurred, 0xc42fe1ce2516e125a386d198703b2422aa0190b25ef6a7b0a1d3c6f5d199ffad
.
In this transaction, there are callbacks. It is good example to show the quirky side of the tool that might confuse a new user.
1. Run the tool
We assume that you have already cloned and completed the npm install step. The tool can be run anywhere, and the POC
folder will be created in that directory.
In this example, we will run the tool at the root of the repository path using the following command.
src/main.js 0xc42fe1ce2516e125a386d198703b2422aa0190b25ef6a7b0a1d3c6f5d199ffad \
-r https://eth.llamarpc.com \
-k {YOUR_BLOCK_EXPLORER_API_KEY}
If there are no errors, the log should appear as follows. The tool will attempt to retrieve the source code of every address called in the transaction from the respective block explorer's API endpoint. The download process might take time, and some downloads may fail. Failed contract downloads may or may not impact the generated POC file. You can rerun the tool to attempt downloading the failed contracts again.
There are three files being generated from the tool:
test/CopiedCall(0xc42fe1ce25).t.sol
This is the POC file that can be run withforge test
.src_poc/0xc42fe1ce/lib.constant.sol
A file that contains the named addresses' address.src_poc/0xc42fe1ce/interface.aggregate.sol
A file that collects the interfaces that being used by the POC file.
2. Look at the POC file
The tool has completed its duty. It ain't much, but it's an honest work. The POC file might be runnable for less complex transactions, but in most cases, it will require cleanup. It's our responsibility to tidy up the POC file.
The C_A_0x9d9820_13d
contract imitates the attacker's exploiting contract. Each function includes comments explaining how it's called and who the caller is. Additionally, there's a comment indicating when a function is duplicated. Handling these aspects will be our next step.
The addresses are replaced by constant variables sourced from the lib.constant.sol file to improve the readability of the POC file. The names are gathered from the block explorer's verified contracts.
- If the name has
A_
as a prefix, it means that the addresses are not verified or be an EOA address. - If the name has
C_
as a prefix, it means that the addresses are contracts that being generated by the tool that meant to represent the respective addresses.
The test contract locates at the bottom of the POC file. It has only one test function testMimicContractCall()
.
- The
EOA
state is the address of the attacker. It is not necessary to prank being the attacker; you can remove thevm.deal()
andvm.startPrank()
from the test. - The
target
state is the address of the original attacking contract. The address of the state will be overwritten by our imitated contract (a contract that hasC_
as a prefix).
The setUp()
function will call the vm.createSelectFork()
function to use the environment from Anvil
. You can change this line if you want to use other ways to fork the chain to test.
The testMimicContractCall()
function will deploy the imitated contract and call it with the call data. The function signature can be changed by the tool if the called cannot be decoded.
3. Clean the POC file
The imitated contract has one problem; it contains numerous functions with identical signatures. A function is generated for each call made to the contract. For instance, in this scenario, the attacking contract has three calls to the onERC1155Received()
function, resulting in the creation of three such functions.
The first and second functions are identical, including their inputs. The third function, however, differs as it lacks a call to the deposit() function. To merge them into a single function, we need to establish a condition for branching the execution. Alternatively, using a more straightforward approach, just count them.
4. Setup Anvil (Optional)
Since the attack occurred in the past, we need to fork that block to have the environment as same as the real attacking. We use Anvil
as the default value for the forking chain. If you wish the change, you can edit the endpoint in the foundy.toml
file.
Anvil can be started by simply using anvil
command. Since we want to fork block, we have to add the -f
flag and the RPC to the command.
5. Do anything with education purpose
Now the test file is ready. You can do anything you want with the POC file. Since we are doing this for educational purposes, we will run the POC using the forge test -vvvv
command to understand how the transaction works.
The result of the testing is FAIL
. Why? Because the entire process of the attack doesn't start and end within one transaction. This is a crucial point I want to emphasize to users of the tool: the tool generates a POC from a single transaction only. Simulating the complete attack may require information spanning multiple transactions.
Feature
Auto-merge
--auto-merge
// Automatically merge the duplicated functions in imitated contracts
From the example, we have encountered a problem that there are duplicated functions in the imitated contract and the users have to manually merge them.
By adding the --auto-merge
flag, the tool will automatically merge any duplicated functions into one single function in the most naive approach.
Maximize
--maximize
// Download all the verified contract sourcecode that appear in the transaction
Normally, the script will download the source code of the contract that being balled by the generated contracts from the script. In the case that you want to use the source code of some contracts that appear in the transaction for further analyzing the POC, you can use this flag, --maximize
, to have the script download all source code of verified contract that appear in the transaction.
Limitation
- The functions that are being called multiple times will be generated multiple times too. The user needs to merge them into one function or relying on the
auto-merge
feature of the script. - It can only generate a contract for one transaction at a time. The simulation of multiple transaction attacks will likely fail.
- Some calls that we cannot decode will become low-level calls in which the parameters will not be dynamically altered.
- A call that contains a complex data type will not likely be able to be decoded.
- The RPC must support the
debug_traceTransaction
method. - The script will download ALL verified contracts of the address that appear at least once in the transaction though the POC does not need some of them.
Common errors/issues
An error after
Pulling data
process.They are common errors about the RPC pulling transaction data. The error code are commonly be
-32000
and-32600
.- Try changing the RPC that supports
debug_traceTransaction
method and the RPC must have the data of the block of the transaction, preferably an RPC from an archive node.
- Try changing the RPC that supports
Function with same name and parameter types defined twice.
It is an intended behavior of the tool (for now). It happens when the functions are called multiple time. It likely happens on a reentrancy attack.
- The user has to merge them into one function. By merging, the user has to guess how to correctly return the right return value of each call; we have a hint of a call as a comment before the functions.
- In the new version, you can use the
--auto-merge
flag to let the tool automatically merge the functions.