-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: raw bytecode analysing script (#768)
* feat: Raw bytecode analyser Signed-off-by: Mariusz Jasuwienas <[email protected]> * feat: Raw bytecode analyser Signed-off-by: Mariusz Jasuwienas <[email protected]> * feat: Adding information about the possibility to use in args not only contract id but contract evm address as well. Fixing issue with the latest commit. Exporting some of the strings to the constant in order to ensure clarity. Signed-off-by: Mariusz Jasuwienas <[email protected]> * feat: Adding information about the possibility to use in args not only contract id but contract evm address as well. Fixing issue with the latest commit. Exporting some of the strings to the constant in order to ensure clarity. Signed-off-by: Mariusz Jasuwienas <[email protected]> * feat: making sure that the description covers all possible usecases Signed-off-by: Mariusz Jasuwienas <[email protected]> --------- Signed-off-by: Mariusz Jasuwienas <[email protected]>
- Loading branch information
1 parent
84e1b99
commit ea72b2b
Showing
3 changed files
with
154 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
bytecode_cache.sqlite |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
# Hedera Smart Contract Bytecode Analyser | ||
|
||
This Python script analyses the bytecode of Smart Contracts on the Hedera network to detect specific operand usages. | ||
This following example determines the likelihood that a Smart Contract creates a token using an SECP256k1 key. | ||
|
||
## Requirements | ||
|
||
- Python 3.6 or higher | ||
- `requests` library | ||
- `requests-cache` library: A transparent, persistent cache for the `requests` library to improve performance by caching HTTP responses. | ||
- `evmdasm` library: A tool for disassembling EVM bytecode to human-readable assembly instructions. | ||
|
||
To install the required Python libraries, run: | ||
|
||
```bash | ||
pip install requests requests-cache evmdasm | ||
``` | ||
|
||
## Usage | ||
|
||
The script can be run from the command line with the following arguments: | ||
|
||
- `contract_id`: The ID or EVM address of the smart contract whose bytecode you want to analyze. | ||
- `--mainnet`: Optional flag to use the Mainnet Mirror Node URL. | ||
- `--previewnet`: Optional flag to use the Previewnet Mirror Node URL. | ||
|
||
If no network flag is specified, the script defaults to using the Testnet Mirror Node URL. | ||
|
||
### Basic Command | ||
|
||
```bash | ||
python detect_token_creation_with_secp_key.py <contract_id> | ||
``` | ||
|
||
### Examples | ||
|
||
1. **Analyze a Contract on Mainnet** | ||
|
||
```bash | ||
python detect_token_creation_with_secp_key.py 0.0.123456 --mainnet | ||
``` | ||
|
||
2. **Analyze a Contract on Testnet** | ||
|
||
```bash | ||
python detect_token_creation_with_secp_key.py 0.0.123456 | ||
``` | ||
|
||
3. **Analyze a Contract on Previewnet** | ||
|
||
```bash | ||
python detect_token_creation_with_secp_key.py 0.0.123456 --previewnet | ||
``` | ||
|
||
## Features | ||
|
||
The script performs the following checks on the smart contract bytecode: | ||
|
||
- **External Calls Detection**: Identifies if there are any calls to external addresses. | ||
- **Specific Address Usage**: Checks for the usage of Hedera addresses 0x167. [Out of context analysis.](#warning-section) | ||
- **Function Selector Usage**: Detects if any of the token creating functions selectors are used. [Out of context analysis.](#warning-section) | ||
|
||
| Detected function | Selector | | ||
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------| | ||
| createFungibleToken((string,string,address,string,bool,int64,bool,(uint256,(bool,address,bytes,bytes,address))[],(int64,address,int64)),int64,int32) | 0x0fb65bf3 | | ||
| createFungibleTokenWithCustomFees((string,string,address,string,bool,int64,bool,(uint256,(bool,address,bytes,bytes,address))[],(int64,address,int64)),int64,int32,(int64,address,bool,bool,address)[],(int64,int64,int64,int64,bool,address)[]) | 0x2af0c59a | | ||
| createNonFungibleToken((string,string,address,string,bool,int64,bool,(uint256,(bool,address,bytes,bytes,address))[],(int64,address,int64))) | 0xea83f293 | | ||
| createNonFungibleTokenWithCustomFees((string,string,address,string,bool,int64,bool,(uint256,(bool,address,bytes,bytes,address))[],(int64,address,int64)),(int64,address,bool,bool,address)[],(int64,int64,int64,address,bool,address)[]) | 0xabb54eb5 | | ||
|
||
- **Parameter Usage**: Identifies if the parameter value equal to the enum KeyValueType.SECP256K1 was used. [Out of context analysis.](#warning-section) | ||
|
||
> ⚠️ **Warning!** <div id="warning-section">Only the operand of the PUSHn instruction is considered. The context and actual usage of this value are not taken into account.</div> | ||
|
||
### Limitations | ||
Even when all four of these conditions are met, it does not conclusively mean that in the Smart Contract | ||
there is a call to address 0x167 using one the methods listed above with a struct containing the enum `KeyValueType.SECP256K1`. | ||
For example: the enum value `KeyValueType.SECP256K1` may be used for other purposes elsewhere in the code or the | ||
function selector may be present but not necessarily used in a CALL operation. | ||
These limitations highlight the potential for false positives and the need for more robust analysis methods to accurately interpret the bytecode and its intended behavior. | ||
|
||
### How detection works: | ||
1. The Smart Contract bytecode is downloaded from the mirrornode. | ||
2. The bytecode is disassembled into opcodes. | ||
3. The operands of the PUSH opcode instructions are analysed to check for the occurrences of the searched values. | ||
|
||
## Output | ||
|
||
Upon successful execution, the script will output the detection results directly to the console. It will indicate whether | ||
there is a chance that the Smart Contract will call one of the HTS Token Creating functions using SECP256K1 key. | ||
|
||
If the contract ID provided does not exist or has no bytecode, the script will inform the user accordingly. |
62 changes: 62 additions & 0 deletions
62
tools/custom/raw-bytecode-analyser/detect_token_creation_with_secp_key.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
from evmdasm import EvmBytecode | ||
import argparse | ||
import requests | ||
import requests_cache | ||
|
||
KEY_VALUE_TYPE_SECP256K1 = '03' | ||
HEDERA_ADDRESS = '0167' | ||
|
||
def fetch_contract_bytecode(mirror_node_url, contract_id): | ||
url = f'{mirror_node_url}/api/v1/contracts/{contract_id}' | ||
response = requests.get(url) | ||
|
||
if response.status_code == 200: | ||
bytecode = response.json().get('runtime_bytecode') | ||
if bytecode: | ||
address_detected = False | ||
method_detected = False | ||
param_detected = False | ||
call_detected = False | ||
|
||
evm_bytecode = EvmBytecode(bytecode) | ||
disassembled_code = evm_bytecode.disassemble() | ||
for instruction in disassembled_code: | ||
if instruction.name == 'CALL': | ||
call_detected = True | ||
if instruction.name[:4] != 'PUSH': | ||
continue | ||
if instruction.operand == HEDERA_ADDRESS: | ||
address_detected = True | ||
if instruction.operand in ['0fb65bf3', '2af0c59a', 'ea83f293', 'abb54eb5']: | ||
method_detected = True | ||
if instruction.operand == KEY_VALUE_TYPE_SECP256K1 and instruction.name == 'PUSH1': | ||
param_detected = True | ||
if address_detected and call_detected: | ||
print('Usage of Hedera address 0x167 detected. Calls to this address may have been possibly made.') | ||
if method_detected: | ||
print('Usage of Hedera method creating token selector detected. Calls using this method may have been possibly made.') | ||
if param_detected and address_detected and method_detected: | ||
print('Possibile usage of Hedera parameter KeyValueType.SECP256K1 selector detected. Calls with this param may have been possibly made.') | ||
else: | ||
print('No bytecode found for the specified contract ID/contract EVM address.') | ||
else: | ||
print(f'Failed to fetch bytecode. Status code: {response.status_code}') | ||
print('Response:', response.text) | ||
|
||
|
||
def main(): | ||
requests_cache.install_cache('bytecode_cache') | ||
parser = argparse.ArgumentParser(description='Fetch bytecode of a smart contract from Hedera network') | ||
parser.add_argument('contract_id', help='ID or EVM address of the smart contract') | ||
parser.add_argument('--mainnet', action='store_true', help='Use the Testnet Mirror Node URL') | ||
parser.add_argument('--previewnet', action='store_true', help='Use the Previewnet Mirror Node URL') | ||
args = parser.parse_args() | ||
mirror_node_url = 'https://testnet.mirrornode.hedera.com' | ||
if args.previewnet: | ||
mirror_node_url = 'https://previewnet.mirrornode.hedera.com' | ||
elif args.mainnet: | ||
mirror_node_url = 'https://mainnet.mirrornode.hedera.com' | ||
fetch_contract_bytecode(mirror_node_url, args.contract_id) | ||
|
||
if __name__ == "__main__": | ||
main() |