Learning Man's Blog

一道题回顾智能合约 Coverage

字数统计: 798阅读时长: 3 min
2020/07/13

题目

pragma solidity ^ 0.4 .24;
contract Hash {

    bytes32 question = "";
    struct Game {
        bytes32 slogan;
        address owner;
        uint96 times;
        address player;
        bool ready;

    }
    Game game;


    modifier onlyOwner() {
        require(msg.sender == game.owner, "Illegal user!");
        _;
    }

    modifier onlyPlayer() {
        require(msg.sender == game.player, "Illegal user!");
        _;
    }

    constructor(string hash) public {
        question = keccak(hash);
        game.slogan = "Welcome to imagin's Hash World!";
        game.owner = msg.sender;

    }

    function getFlag() view public returns(bool) {
        return (game.times == 43856731668828204536206669571);
    }


    function play() public {
        require(game.player == address(0), "Have been played!");
        game.player = address(msg.sender);

    }

    function guessHash(string answer) public onlyPlayer payable returns(string) {
        game.player = msg.sender;
        require(msg.value > 0.1 ether, "Get yourself rich first.");
        require(game.ready);
        game.ready = false;
        require(isMan(), "You should be real man.");
        if (keccak(answer) == question) {
            game.times++;
            return "Congratulations, you're right~";
        } else {
            game.times--;
            return "Oops, you lost your chance (-1s).";
        }

    }

    function changeSlogen(bytes32 slogan) public {
        Game a;
        a.slogan = slogan;
        game = a;
    }

    function nextHash(string hash) public onlyOwner {
        question = keccak256(hash);
        game.ready = true;
    }

    function keccak(string str) internal pure returns(bytes32) {
        return keccak256(str);
    }


    function getSlogan() view public returns(bytes32) {
        return game.slogan;
    }

    function isMan() internal view returns(bool) {
        uint size;
        assembly { size: = extcodesize(caller) }
        return (size == 0);
    }

    // 方便调试,原题目以下函数不存在

    function getquestion() view public returns(bytes32) {
        return question;
    }

    function getTime() view public returns(uint96) {
        return game.times;
    }

    function getOwner() view public returns(address) {
        return game.owner;
    }

}

分析

注意到,题目提供了

  • 3 个公开传参方法分别为changeSlogenguessHashnextHash
  • 2 个公开无参方法分别为playgetFlag

首先我们看 getFlag 方法,最终目标是让它返回 true,而对应的目标 times 为
43856731668828204536206669571(10) == 8db5702bac41153c09c73703(16)

这个 times(次数) 是通过 guessHash 进行调整的,但是根本不可能在有限时间内通过循环操作来达到,尤其是这个函数还有onlyPlayer payable修饰,另外内部还有 isMan 的合约调用检测,只能手工来操作实现

我们抛开题目,先来看下guessHash的代码逻辑,可以看到通过一堆检测后,其主要内容就是实现对answer和question的等值判断,之后对 times 进行 +- 操作

function guessHash(string answer) public onlyPlayer payable returns(string) {
    game.player = msg.sender;
    require(msg.value > 0.1 ether, "Get yourself rich first.");
    require(game.ready);
    game.ready = false;
    require(isMan(), "You should be real man.");
    if (keccak(answer) == question) {
        game.times++;
        return "Congratulations, you're right~";
    } else {
        game.times--;
        return "Oops, you lost your chance (-1s).";
    }

}

粗略想想,应该有两种解题方向

  1. 爆破 times
  2. 控制 times 然后通过操作 guessHash 进行调整

上面提到无法通过合约爆破形式使 times 暴增,而且题目实际上同时几个人在做,如果中间有人参一脚修改了 times同时攻击者有没有发觉,则会导致前功尽弃,难以控制

那我们只能看第二种方法能否实现

变量覆盖

原理

  1. Understanding Ethereum Smart Contract Storage
  2. 详解Solidity合约数据存储布局
  3. Solidity中存储方式错误使用所导致的变量覆盖
  4. 智能合约审计系列————3、变量覆盖&不一致性检查

分析

我们向 changeSlogen 传入0x0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff来观察下结构体值的变化,可以看到所有值全部被修改了

尤其注意此时 times 为 80596284442678810400085(10) == 11112222333344445555(16)

调用前 调用后
-w668 -w667

根据占位符,我们可以快速确定如何填充数据,通过修改 times 然后进行 +- 操作就可以完成解题了

解题

  1. 执行 changeSlogen 传入 0x8db5702bac41153c09c737040000000000000000000000000000000000000000,修改 times 为 43856731668828204536206669572(10)
  2. 执行 play 成为当前玩家
  3. 执行 guessHash 传入 任意值 导致 times - 1
  4. 执行 getFlag

题目地址:0x299DfDB000C6c0131D4cEe84348e6B5Fb656Fff8

-w682

CATALOG
  1. 1. 题目
  2. 2. 分析
  3. 3. 变量覆盖
    1. 3.1. 原理
    2. 3.2. 分析
    3. 3.3. 解题