NumenCTF

合约审计比赛

wp

NUMEN CTF writeup by ChaMd5 | CN-SEC 中文网

DeFiHackLabs’s Substack | SunWeb3Sec | Substack

SimpleCall

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
pragma solidity ^0.7.0;

contract ExistingStock {

address public owner;
address private reserve;

string public name = "Existing Stock";
string public symbol = "ES";
uint256 public decimals = 18;
uint256 public totalSupply = 200000000000;
uint8 public frequency = 1;

bool public Lock = false;
bool public result;
bool public flag;

event Approval(address indexed from, address indexed to, uint number);
event Transfer(address indexed from, address indexed to, uint number);
event Deposit(address indexed to, uint number);
event Withdraw(address indexed from, uint number);
event Target(address indexed from, bool result);


mapping (address => uint) public balanceOf;
mapping (address => mapping (address => uint)) public allowance;

constructor() public {
owner = msg.sender;
balanceOf[owner] = totalSupply;
}

function approve(address to, uint number) public returns (bool) {
allowance[msg.sender][to] = number;
emit Approval(msg.sender, to, number);
return true;
}

function transfer(address _to, uint _value) public returns (bool) {
require(balanceOf[msg.sender] - _value >= 0);
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
return true;
}

function transferFrom(address from, address to, uint number) public returns (bool){

require(balanceOf[from] >= number);

if (from != msg.sender && allowance[from][msg.sender] != uint256(-1)) {
require(allowance[from][msg.sender] >= number);
allowance[from][msg.sender] -= number;
}

balanceOf[from] -= number;
balanceOf[to] += number;

emit Transfer(from, to, number);
return true;
}

function privilegedborrowing(uint256 value,address secure,address target,bytes memory data) public {
require(Lock == false && value >= 0 && value <= 1000);
balanceOf[address(this)] -= value;
balanceOf[target] += value;

address(target).call(data);

Lock = true;

require(balanceOf[target] >= value);
balanceOf[address(this)] += value;
balanceOf[target] -= value;

Lock = false;
}

function withdraw(uint number) public {
require(balanceOf[msg.sender] >= number);
balanceOf[msg.sender] -= number;
(msg.sender).transfer(number);
emit Withdraw(msg.sender, number);
}

function setflag() public {
if(balanceOf[msg.sender] > 200000 && allowance[address(this)][msg.sender] > 200000){
flag = true;
}
}

function isSolved() public view returns(bool){
return flag;
}

}

要满足的是:

image-20220828145823279

balanceOf会下溢,利用transfer函数可以满足第一个条件:

image-20230416230045087

只有approve能修改allowance,但是发起方只能是msg.sender

image-20230404105532045

privilegeborrowing存在任意地址的call调用,调用后会使得msg.sender改为target,所以可以满足第二个条件

image-20230404105113104

image-20230330182715865

img

counter

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
pragma solidity ^0.8.13;

contract Deployer {
constructor(bytes memory code) {
assembly { return (add(code, 0x20), mload(code)) }
}
}
contract SmartCounter{
address public owner;
address public target;
bool flag=false;
constructor(address owner_){
owner=owner_;
}
function create(bytes memory code) public{
require(code.length<=24);
target=address(new Deployer(code));
}

function A_delegateccall(bytes memory data) public{
(bool success,bytes memory returnData)=target.delegatecall(data);
require(owner==msg.sender);
flag=true;
}
function isSolved() public view returns(bool){
return flag;
}
}

先学一下solidity的内存布局Solidity - 内存布局 - 简书 (jianshu.com)

delegatecall调用的时候我们的地址会变成tx.origin,又由于owner在slot0,所以把tx.origin存到slot0即可满足owner=msg.sender。构造的code为:

1
2
3
4
5
ORIGIN
PUSH1 0x00
SSTORE

32600055