Writeup - Bank and Bank-Revenge (ImaginaryCTF 2024)
ImaginaryCTF 2024 - Bank and Bank-Revenge Writeup
Description
1 | Can you actually steal the bank's money? |
Writeup
Just as a quick note, this writeup will cover both the Bank challenge and the Bank-Revenge challenge, they are identical except for a single line that differs, namely uint48 flag_cost = 50;
in bank.sol
and uint48 flag_cost = 281474976710655;
in bank2.sol
. The method of solving them is also identical, so I will only cover solving Bank-Revenge. In addition, I didn’t actually solve this challenge during the competition, but was very close.
The provided contract file is provided below:
1 | pragma solidity ^0.7.0; |
Interacting with the provided netcat session allowed you to create a personal instance of this challenge. Since I cover how to setup interacting with contracts using Python’s web3
library in the EightFiveFourFive
writeup, I won’t cover it in-depth here.
The goal here seems to be fairly simple - you need to have 281474976710655
in your account while loan == 0
. The functions in the contract allow you to deposit money from your wallet, withdraw money into your wallet, get a loan of however much money you want, and see if the challenge is solved. Note that the getMoney()
function is not relevant to us, as the comment states.
How to Not Exploit the Contract
Since I’m a web3 noob, I’m going to list some ideas I had for how to exploit the contract that didn’t/wouldn’t work and why.
- Deposit 281474976710655 eth from your wallet and redeem for the flag - you don’t start out with that much money so you can’t actually deposit that much.
- Spoof your deposited amount - since the amount added to your account balance in the
deposit
actually comes from theamount
argument and notmsg.value
, it would be possible to only send (for example) 1 eth but have amount set to281474976710655
so your balance goes up way more than it should. However, the function checks thatamount == msg.value
so that’s not valid. - Reentrancy - all functions (
deposit
,withdraw
, andloan
) follow theCheck-Effect-Interaction
pattern to protect against this attack, plus each function ensures the transaction comes from your wallet and not a malicious contract acting on your behalf. - Withdraw a negative amount - the sole argument to
withdraw
isuint48 amount
, which means it must be unsigned so a negative number isn’t accepted.
How to Exploit the Contract
One thing I noticed fairly early on is that the version of solidity it uses is 0.7.0
, which is an older version. As I do with all CTF problems, I looked to see if there were vulnerabilities with the older version. It turns out that Solidity versions before 0.8.0
are vulnerable to integer overflows and underflows by default and there wasn’t a library to check for them. If you wanted to protect yourself against it, you had to implement those checks yourself. Solidity 0.8.0
introduced the Safe Math library that does arithmetic with built-in overflow/underflow checks for you to use. This told me it was likely an integer underflow/overflow problem.
I quickly realized that the only function that was realistically vulnerable to this was loan
, as you could get as much money as you wanted from loan
. If you borrowed 2**48 - 1
eth once, then 1
eth a second time, your loan amount was set to 0. What tripped me up during the actual competition was I thought it should have incremented amount_you_have
as its way of “giving me” the money, and since that line wasn’t in there the function was useless.
What I understood later was that the msg.sender.call{value:amount}("");
function sends the eth to your wallet and doesn’t just increase your amount on the contract. This means you just need to deposit the money you got from the loan and then you can redeem the flag.
To summarize, this is the exploit chain:
- Borrow
2**48 - 1
eth from the contract (maxuint48
value) - Borrow
1
eth from the contract, overflowing theloan
variable and setting it to 0. - Deposit
2**48 - 1
eth from your wallet into your account balance (amount_you_have
) - Call
isChallSolved()
and profit
Exploit Code
Here’s my solve.py
script:
1 | from web3 import Web3 |
Flag - ictf{r0bb1ng_7h3_b4nk_8f4a3d2b}