🚩
Practical CTF
BlogContact
  • 🚩Home - Practical CTF
  • 🌐Web
    • Enumeration
      • Finding Hosts & Domains
      • Masscan
      • Nmap
      • OSINT
    • Client-Side
      • Cross-Site Scripting (XSS)
        • HTML Injection
        • Content-Security-Policy (CSP)
      • CSS Injection
      • Cross-Site Request Forgery (CSRF)
      • XS-Leaks
      • Window Popup Tricks
      • Header / CRLF Injection
      • WebSockets
      • Caching
    • Server-Side
      • SQL Injection
      • NoSQL Injection
      • GraphQL
      • XML External Entities (XXE)
      • HTTP Request Smuggling
      • Local File Disclosure
      • Arbitrary File Write
      • Reverse Proxies
    • Frameworks
      • Flask
      • Ruby on Rails
      • NodeJS
      • Bun
      • WordPress
      • Angular
    • Chrome Remote DevTools
    • ImageMagick
  • 🔣Cryptography
    • Encodings
    • Ciphers
    • Custom Ciphers
      • Z3 Solver
    • XOR
    • Asymmetric Encryption
      • RSA
      • Diffie-Hellman
      • PGP / GPG
    • AES
    • Hashing
      • Cracking Hashes
      • Cracking Signatures
    • Pseudo-Random Number Generators (PRNG)
    • Timing Attacks
    • Blockchain
      • Smart Contracts
      • Bitcoin addresses
  • 🔎Forensics
    • Wireshark
    • File Formats
    • Archives
    • Memory Dumps (Volatility)
    • VBA Macros
    • Grep
    • Git
    • File Recovery
  • ⚙️Reverse Engineering
    • Ghidra
    • Angr Solver
    • Reversing C# - .NET / Unity
    • PowerShell
  • 📟Binary Exploitation
    • ir0nstone's Binary Exploitation Notes
    • Reverse Engineering for Pwn
    • PwnTools
    • ret2win
    • ret2libc
    • Shellcode
    • Stack Canaries
    • Return-Oriented Programming (ROP)
      • SigReturn-Oriented Programming (SROP)
      • ret2dlresolve
    • Sandboxes (chroot, seccomp & namespaces)
    • Race Conditions
  • 📲Mobile
    • Setup
    • Reversing APKs
    • Patching APKs
    • HTTP(S) Proxy for Android
    • Android Backup
    • Compiling C for Android
    • iOS
  • 🌎Languages
    • PHP
    • Python
    • JavaScript
      • Prototype Pollution
      • postMessage Exploitation
    • Java
    • C#
    • Assembly
    • Markdown
    • LaTeX
    • JSON
    • YAML
    • CodeQL
    • NASL (Nessus Plugins)
    • Regular Expressions (RegEx)
  • 🤖Networking
    • Modbus - TCP/502
    • Redis/Valkey - TCP/6379
  • 🐧Linux
    • Shells
    • Bash
    • Linux Privilege Escalation
      • Enumeration
      • Networking
      • Command Triggers
      • Command Exploitation
      • Outdated Versions
      • Network File Sharing (NFS)
      • Docker
      • Filesystem Permissions
    • Analyzing Processes
  • 🪟Windows
    • The Hacker Recipes - AD
    • Scanning/Spraying
    • Exploitation
    • Local Enumeration
    • Local Privilege Escalation
    • Windows Authentication
      • Kerberos
      • NTLM
    • Lateral Movement
    • Active Directory Privilege Escalation
    • Persistence
    • Antivirus Evasion
    • Metasploit
    • Alternate Data Streams (ADS)
  • ☁️Cloud
    • Kubernetes
    • Microsoft Azure
  • ❔Other
    • Business Logic Errors
    • Password Managers
    • ANSI Escape Codes
    • WSL Tips
Powered by GitBook
On this page
  • Compiling .sol to .abi
  • Simple Interaction
  • Manual Transactions
  • The fallback() method
  • The receive() method
  • Requirements
  1. Cryptography
  2. Blockchain

Smart Contracts

A few small bits about attacking Smart Contracts in Web3

Compiling .sol to .abi

A contract's source code is found in .sol files, but code often requires a general structure instead, which is the Application Binary Interface (ABI) format, simply encoded in JSON. You can compile Solidity code into .abi files that you can use in your attacking script to interact with the contract in a known way.

You can compile a single file to ABI into the current directory using the following command:

$ npx solc --abi <NAME>.sol -o <DIRECTORY>
# # For example
$ npx solc --abi Setup.sol -o .

It might complain about having the wrong compiler version installed, but this can often be circumvented by changing the version in the contract source itself. You might get a ParserError like this:

Setup.sol:1:1: ParserError: Source file requires different compiler version (current compiler is 0.7.3+commit.9bfce1f6.Emscripten.clang) - note that nightly builds are considered to be strictly less than the released version

It says the current version is 0.7.3, so simply change that first line in the source:

- pragma solidity ^0.8.18;
+ pragma solidity ^0.7.3;

Simple Interaction

Take the following contract as a simple example:

pragma solidity ^0.8.18;

contract Example {
    bool public updated;

    function call_me(uint256 number) external {
        if (number == 42) {
            updated = true;
        }
    }
}

To interact with a smart contract on a private chain, you need the following:

It starts with connecting to the RPC provider, and creating an account object from your private key:

from web3 import Web3

# Connect to the private chain using an RPC provider
web3 = Web3(Web3.HTTPProvider('http://<HOST>:<PORT>'))

# Set the account that will execute transactions
private_key = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
account = web3.eth.account.privateKeyToAccount(private_key)

From here, you likely want to interact with a contract. You can get an instance of the contract in Python by opening the .abi file (see Compiling .sol to .abi) and providing the address of the contract on the server.

# Create an instance of the contract
contract_address = '0x9876543210abcdef0123456789ABCDEF01234567'
contract_abi = open('Example.abi').read()
example = web3.eth.contract(address=contract_address, abi=contract_abi)

Now that we have an instance of the contract, we can interact with it by calling functions on it. In the example Solidity code above, we need to call the call_me() function with an argument of 42. In our script that would look like this:

tx_hash = example.functions.call_me(42).transact()
print(tx_hash)  # b'\x91\xfb\x10\x93...

If you run this script and the tx_hash prints something, it probably worked. Otherwise, you will likely receive a clear Exception on why it did not work.

Manual Transactions

These function calls abstract away a lot of details, but sometimes we as the attacker want more low-level control over the transaction being sent. Here are two examples.

The fallback() method

The contract might contain a payable method named fallback():

fallback() external payable {
    ...
}

You cannot call this function directly, because it has a special meaning. This function is called when the function you try to call does not exist. It is often used for updated contracts that need to handle the case when scripts interacting with it don't update. But to intentionally call this function we would need to try and call a wrong function name in our script.

Web3 won't actually let you do this straight away, to help you not make mistakes. But in the case where you intentionally want to do this, you can trick it into thinking the wrong method does exist.

tx_hash = example.functions.wrong().transact()
print(tx_hash)
Error before transaction
web3.exceptions.ABIFunctionNotFound: ("The function 'wrong' was not found in this contract's abi. ", 'Are you sure you provided the correct contract abi?')

To bypass this, we can just manually change the .abi file to add a function called wrong, and make it think it exists:

  {
    "inputs": [],
    "name": "wrong",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },

When we now run the script again, it will correctly pass it through to the fallback() method.

The receive() method

Your contract may also have receive() method:

receive() external payable {
    ...
}

This method is used when you aren't calling a function at all, so when there is no data involved in your transaction, specifically a transaction directly to the contract address. To trigger this function we just have to manually create a transaction and send it over, without the data: component. Here is an example:

transaction = {
    'from': account.address,
    'to': contract_address,
    'value': web3.toWei(0, 'ether'),
    'gas': 2000000,
    'gasPrice': web3.toWei('50', 'gwei'),
}

tx_hash = web3.eth.send_transaction(transaction)
print(tx_hash)

In this case, we send 0 ether with some gas price configuration. It is sent to the contract address which will trigger the receive() method.

As I hinted, you can add a data component to this transaction which calling functions will automatically do for you. Here you can manually craft any call you want to make.

Requirements

With more involved contracts, you'll likely find the modifier keyword and the require() function. These can set specific conditions for if you can call a method or not. If this condition fails, your call will not go through. For example:

contract ShootingArea {
    bool public allowed;
    bool public updated;

    modifier isAllowed() {
        require(allowed);
        _;
    }
    
    function open_gates() public {
        allowed = true;
    }

    function enter() public isAllowed {
        updated = true;
    }
}

Here, someone would first have to call open_gates() before they could call enter(). Keep in mind that this state is remembered, so if you set allowed = true it will now forever be true, and you will be allowed in the next call.

PreviousBlockchainNextBitcoin addresses

Last updated 2 years ago

Then you can use libraries like or to do the heavy lifting. The libraries are very similar in usage, but in the following examples, I will use the Python version.

🔣
web3.py
web3.js