Blog

Category:Developers
How to Fuzz a Smart Contract Using ZIION VM
profile
Ziion
04.11.2023

After the release of ZIION 23.1, it’s important to learn how one can leverage ZIION VM to perform blockchain development and security auditing. Today, in this blog, we will learn how to perform fuzzing on smart contracts using ZIION in order to find vulnerabilities.

But first: what is Fuzzing?

Fuzzing or Fuzz Testing is an automated testing technique (once the fuzzing harness has been created), used to discover potential vulnerabilities or issues in a program by feeding it with vast amounts of random, semi-random, or invalid data. 

It helps to identify potential vulnerabilities and weaknesses that may not be apparent through other testing methods. By subjecting the blockchain protocol or smart contract to many input scenarios, fuzzing can exhibit how it handles malformed inputs or unexpected data, which can help to discover potential attack vectors or issues related to scalability and performance.

A significant advantage of fuzzing and why it's critical to smart contracts is that, by design, it proves a vulnerability exists and is not a false positive when found. Furthermore, once it’s running, it's fully automated and can run for days or weeks, easily sifting through millions of input variation possibilities.

Tools used for Fuzzing

To fuzz EVM-based smart contracts, we can use tools like:

  • Echidna

  • HEVM

To fuzz Rust-based smart contracts, we can use tools like:

  • honggfuzz

  • afl++

  • cargo-fuzz (libfuzzer)

Most fuzzers are only fully usable on amd64 architecture. We usually create the fuzzing harness with an IDE like VSCodium but one can do it in any text editor.

All the above-mentioned tools are pre-installed and pre-configured in ZIION (amd64). If you have not downloaded and installed ZIION 23.1 (amd64), then please download ZIION and use it to perform fuzzing.

How to Fuzz a smart contract using ZIION VM

The smart contracts could be written in Solidity and Rust and require different tools to perform fuzzing on them. Let’s discuss fuzzing an EVM-based smart contract and a Rust-based smart contract separately.

Fuzzing an EVM-based smart contract using ZIION VM:

To perform fuzzing on a smart contract, you can use specialized tools designed for Ethereum smart contract analysis, which are already pre-installed and configured in ZIION VM.

Here, we will use Echidna EVM Fuzzer to fuzz the smart contract.

Step 1: Let’s take a sample smart contract named, MyToken.sol and ensure it is saved and is available in ZIION VM.

pragma solidity ^0.8.0;

contract MyToken {

                 // Track token balances in a mapping

    mapping(address => uint256) public balances;


    // Declare the total supply of tokens

    uint256 public totalSupply;


    // Event emitted when tokens are transferred

    event Transfer(address indexed from, address indexed to, uint256 _value);


    // Initialize the contract and assign the total supply of tokens

    constructor(uint256 _initialSupply) {

        totalSupply = _initialSupply;

        balances[msg.sender] = _initialSupply; // Assign the entire supply to the contract creator

    }


    // Function to check the token balance of an address

    function balanceOf(address _owner) public view returns (uint256) {

        return balances[_owner];

    }


    // Function to transfer tokens from one address to another

    function transfer(address to, uint256 value) public returns (bool success) {

        // Check if the sender has enough tokens

        require(balances[msg.sender] >= _value);


        // Update the token balances

        balances[msg.sender] -= _value;

        balances[_to] += _value;


        // Emit the Transfer event

        emit Transfer(msg.sender, to, value);


        return true;

    }

}


Now, let us start fuzzing the above-written smart contract using Echidna.

Step 2: Write Properties for Your Smart Contract

Next, you need to write properties for your smart contract that Echidna will try to falsify. A property is a function that returns a boolean value, representing an assertion about the contract's behavior. In Solidity, you can define properties in your smart contract using the following syntax:

function echidna_property_name() public view returns (bool) {

  // Your property logic here

}


For example, for the "MyToken" contract, you might write a property that checks if the total supply of tokens remains constant after transfers:

function echidna_totalSupplyInvariant() public view returns (bool) {

return total_Supply == initialSupply;
}

Step 3: Prepare Your Smart Contract for Testing in ZIION VM

Before testing your smart contract with Echidna, make sure it's written in Solidity with a version compatible with Echidna (e.g., ^0.6.0 or ^0.8.0). You also need to compile your contract using the solc compiler.

Step 4: Run Echidna in ZIION VM

To run Echidna on your smart contract, use the echidna command, providing the path to your smart contract file:

echidna /path/to/your/contract.sol

Echidna will start fuzz-testing your contract, attempting to falsify the properties you defined. By default, Echidna will run for a very long time, so you might want to stop the process after some time (e.g. 30 minutes or a few hours) if it doesn't find any issues.

Step 5: Analyze the Results

If Echidna finds a counterexample that falsifies one of your properties, it will report it along with a sequence of transactions that led to the issue. You can use this information to debug and fix your smart contract.

If Echidna doesn't find any issues, it doesn't mean your contract is bug-free, but it's a good indication that your contract might be robust against some common issues.


Fuzzing a Rust-based smart contract in ZIION VM:

Step 1: Initialize cargo-fuzz in ZIION:

Navigate to the directory of your Rust smart contract project and initialize cargo-fuzz by running:

cargo fuzz init

This command will create a ‘fuzz’ directory inside your project, containing a ‘Cargo.toml’ file and a ‘fuzz_targets’ folder.

Step 2: Define your fuzz target:

Create a new Rust file inside the ‘fuzz_targets’ directory, e.g., ‘fuzz_smart_contract.rs’. In this file, define a fuzz target function that takes a ‘&[u8]’ input and processes it using your smart contract functions. The cargo-fuzz tool will generate random input data and call this function with it.

For example, considering a simple counter smart contract [simple_counter.rs]:

#![no_main]

use libfuzzer_sys::fuzz_target;

use simple_counter::SimpleCounter;


fuzz_target!(|data: &[u8]| {

    if data.len() < 5 {

        return;

    }


    // Use the first byte of the data as an operation selector

    let operation = data[0] % 3;


    // Use the next 4 bytes as input to the constructor

    let init_value = u32::from_le_bytes([data[1], data[2], data[3], data[4]]);


    let mut contract = SimpleCounter::new(init_value);


    match operation {

        0 => contract.increment(),

        1 => contract.decrement(),

        _ => (),

    }


    // No need to assert the state, as we are checking for panics and other errors

});


Make sure to replace simple_counter::SimpleCounter with the appropriate path to your smart contract module and struct.

Step 3: Build and run the fuzzer in ZIION VM:

Run the fuzzer by executing the following command:

cargo fuzz run fuzz_smart_contract

Replace fuzz_smart_contract with the name of the fuzz target you created in step 4.

Step 4: Run cargo-fuzz in ZIION VM

To run the fuzzer, use the following command:

cargo fuzz run fuzz_target_name

Replace fuzz_target_name with the name of your fuzz target file without the .rs extension.

cargo-fuzz will now start generating random inputs and running your fuzz target. It will continue running until you stop it manually or it encounters an issue, like a panic or a bug.

Step 5: Analyze the results

If the fuzzer finds a bug, it will report the input that caused the issue. Use this information to debug and fix your Rust-based smart contract. If the fuzzer does not find any issues, it does not guarantee your contract is bug-free, but it provides some level of confidence in your contract's correctness.

Some Tips and Tricks to fuzz smart contracts smartly

  1. Understand the Contract: Before starting fuzzing, it is essential to have a good understanding of the smart contract being audited. This includes understanding the contract's functionality, the data it handles, and the expected behavior of the contract under different conditions. It will help identify which functions are great fuzzing targets.


  2. Well-defined Test Cases: Create a set of test cases that cover different scenarios and input variations. The test cases should include both valid and invalid input data, such as edge cases and unexpected inputs. Test cases that include the largest amount of different functions you want to abuse are great candidates for fuzzing.


  3. Triaging and debugging results: Analyze fuzzing results to identify potential vulnerabilities and security issues. Prioritize the identified issues based on their severity and impact, and provide detailed recommendations for remediation. The triaging and debugging phase must not be underestimated.

  1. Setup a VPS: Once your fuzzing harness is well set up and tested to work, don’t hesitate to use a VPS or reliable server to let the fuzzing run for days. Just don’t forget to triage and debug the results!


Remember that while fuzzing can help identify issues, it does not guarantee that a smart contract is secure. Always combine fuzz testing with other testing techniques, code review, and formal verification, when possible, to ensure the security of your smart contracts.

Fuzzing requires you to run various tools, and installing those tools and configuring them individually would be time-consuming. To save precious time, you can install ZIION, which contains all the necessary tools used for Fuzzing. Download
ZIION 23.1 now!

Get Started Today