DEV Community

晓道
晓道

Posted on

2 2

尝试爆破NFT奖励时间限制 (二)

前文

尝试爆破NFT奖励时间限制 (一) | 登链社区 | 深入浅出区块链技术 (learnblockchain.cn)

昨天写了,网页爆破的尝试,没有成功,今天讲讲,为什么先是网页爆破,如前面所见,智能合约调用参数太多搞不清除每一个参数是干什么的,通过调用他自己的api,可以减少犯错的几率,也不需要理解别人的代码,就可以干,如果顺利,就捡便宜了,当然没捡到,所以有了我们第二篇。

网页再分析

网页没有成功,当然得理解为什么没有成功,所以我们的理解他的流程,他的业务。

网页失败的地方
根据日志查找出错请求的发送位置。

16441283511.png

查看调用代码

public async claimDrop() {
    try {
      this.claimLoading = true;

      const feeInfo = await this.getFeeInfo(Operation.Claim);
      if (!feeInfo || !feeInfo.platformFee) {
        console.log('get fee error');
      }
      const mintSignContent = `${this.campaign?.name}

${this.campaign?.description}`;

      const signature1 = '';

      const variables = {
        input: {
          signature: signature1,
          campaignID: this.campaign?.id,
          address: this.account,
          mintCount: 1,
          chain: this.isGasless ? this.campaign.chain : this.currentChainName,
        },
      };
      const { data } = await this.$apollo.mutate({
        mutation: PREPARE_PARTICIPATE,
        variables,
      });

      if (data && data.prepareParticipate.allow && this.isMinter) {
        const signature2 = data.prepareParticipate.signature;
        const dummyId = data.prepareParticipate.mintFuncInfo.verifyIDs[0];
        const powahs = data.prepareParticipate.mintFuncInfo.powahs[0];
        const starNFT = data.prepareParticipate.mintFuncInfo.nftCoreAddress;
        const spaceStationAddress = this.spaceStationAddress;
        const cap = data.prepareParticipate.mintFuncInfo.cap;
        if (this.isGasless) {
          const isAsyncClaim = data.prepareParticipate.metaTxResp.reqQueueing;
          console.log(data.prepareParticipate.metaTxResp);
          // alert(data.prepareParticipate.metaTxResp.reqQueueing);
          if (isAsyncClaim) {
            await this.claimEvmGaslessCampaign(data.prepareParticipate);
            return;
          }
Enter fullscreen mode Exit fullscreen mode

根据代码内容,在进行claimDrop的时候是先请求服务器,得到signature 信息才能进行下面的步骤。
目前的情况,从服务器获取,已经没办法了,有没有其他办法呢。

合约分析

只能开始了解合约内容了,了解signature 是如何生成的,以及在合约中是怎么使用的。
从一个合约调用开始分析
etherscan.io
找到合约代码,看etherscan的信息,合约已经验证,但是没有源代码,所以,只能想其他办法。
这就用到了,上次写蜜罐分析,网友推荐的Contract list - Ethereum Contract Library by Dedaub (contract-library.com)
找到反汇编信息:

16441289401.png
找到上面etherscan上的调用函数,
MethodID: 0x2e4dbe8f,搜索contract-library上的反汇编代码得到如下函数:

function 0x2e4dbe8f(uint256 varg0, address varg1, uint256 varg2, uint256 varg3, uint256 varg4) public payable {  find similar
    require(msg.data.length - 4 >= 160);
    require(varg4 <= 0x100000000);
    require(4 + varg4 + 32 <= 4 + (msg.data.length - 4));
    require(!(((?).length > 0x100000000) | (36 + varg4 + (?).length > 4 + (msg.data.length - 4))));
    require(!_paused, 'Contract paused');
    require(varg2 > 15000, 'SpaceStation migrated');
    require(!(0xff & map_5[varg2]), 'Already minted');
    v0 = 0x18b2(keccak256(0xab24fc7f8acd203d6001ca43a3e2f9954f0e9c8939ff9c48ba3cb56b750c6486, varg0, varg1, varg2, varg3, msg.sender));
    v1 = new bytes[]((?).length);
    CALLDATACOPY(v1.data, 36 + varg4, (?).length);
    MEM[v1.data + (?).length] = 0;
    v2 = 0x1d02(v1, v0);
    require(address(v2) == stor_0_1_20, 'Invalid signature');
    map_5[varg2] = 0x1 | ~0xff & map_5[varg2];
    require(1 > 0, 'Must mint more than 0');
    if (map_4[varg0][2]) {
        v3 = _SafeMul(1, map_4[varg0][2]);
        require(msg.value >= v3, 'Insufficient Payment');
        v4 = v5 = MEM[64] + 32;
        MEM[64] = v5;
        v6 = v7 = 0;
        while (v6 >= 32) {
            MEM[v4] = MEM[v4];
            v6 = v6 + ~31;
            v4 += 32;
            v4 += 32;
        }
        MEM[v4] = MEM[v4] & ~(256 ** (32 - v6) - 1) | MEM[v4] & 256 ** (32 - v6) - 1;
        v8, v9 = _fallback.call(MEM[(MEM[64]) len (v7 + v5 - MEM[64])], MEM[(MEM[64]) len 0]).value(msg.value).gas(msg.gas);
        if (RETURNDATASIZE() != 0) {
            v10 = new bytes[](RETURNDATASIZE());
            RETURNDATACOPY(v10.data, 0, RETURNDATASIZE());
        }
        require(v8, 'Transfer platformFee failed');
    }
    if (map_4[varg0][1]) {
        v11 = _SafeMul(1, map_4[varg0][1]);
        require((address(map_4[varg0])).code.size);
        v12, v13 = address(map_4[varg0]).transferFrom(msg.sender, _fallback, v11).gas(msg.gas);
        require(v12); // checks call status, propagates error data on error
        require(RETURNDATASIZE() >= 32);
        require(v13, 'Transfer erc20Fee failed');
    }
    require(varg1.code.size);
    v14, v15 = varg1.mint(msg.sender).gas(msg.gas);
    require(v14); // checks call status, propagates error data on error
    require(RETURNDATASIZE() >= 32);
    emit 0x7b817396dff06715a9274aba8056efc47492ff13d976d2c7cfbcd1d3508580a4(varg0, varg2, v15, msg.sender);
}
Enter fullscreen mode Exit fullscreen mode

现在想办法把反汇编的函数,还原成solidity代码的函数,上面的代码是伪代码,是不能编译的,所以必须翻译成solidity的代码。
具体我就不教学了,翻译过后的代码如下:

function claim(uint256 _cid, IStarNFT _starNFT, uint256 _dummyId, uint256 _powah, bytes calldata _signature) external payable onlyNoPaused {
        require(!hasMinted[_dummyId], "Already minted");
        require(_verify(_hash(_cid, _starNFT, _dummyId, _powah, msg.sender), _signature), "Invalid signature");
        hasMinted[_dummyId] = true;
        _payFees(_cid);
        uint256 nftID = _starNFT.mint(msg.sender, _powah);
        emit EventClaim(_cid, _dummyId, nftID, msg.sender);
    }
Enter fullscreen mode Exit fullscreen mode
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
    }
function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
        return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);
    }
     function _hash(uint256 _cid, IStarNFT _starNFT, uint256 _dummyId, uint256 _powah, address _account) public view returns (bytes32) {
        return _hashTypedDataV4(keccak256(abi.encode(
                keccak256("NFT(uint256 cid,address starNFT,uint256 dummyId,uint256 powah,address account)"),
                _cid, _starNFT, _dummyId, _powah, _account
            )));
Enter fullscreen mode Exit fullscreen mode
function _verify(bytes32 hash, bytes calldata signature) public view returns (bool) {
        return ECDSA.recover(hash, signature) == galaxy_signer; //0x1d02 函数
    }
    //stor_0_1_20 就是 galaxy_signer
    function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
        require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "ECDSA: invalid signature 's' value");
        require(v == 27 || v == 28, "ECDSA: invalid signature 'v' value");
        address signer = ecrecover(hash, v, r, s);
        require(signer != address(0), "ECDSA: invalid signature");

        return signer;
    }
Enter fullscreen mode Exit fullscreen mode

得出结论

合约中只存了公钥,也就是上面的galaxy_signer,这个_signature是后台生成的,用来验证claim的前4个参数是不是伪造的,其中的算法是,ECDSA,我对算法这块了解得还比较少,根据网上的信息,别人得出这样的结论:
ECDSA 实现步骤
第一步:初始化化秘钥组,生成ECDSA算法的公钥和私钥
第二步:执行私钥签名, 使用私钥签名,生成私钥签名
第三步:执行公钥签名,生成公钥签名
第四步:使用公钥验证私钥签名
备注:所谓的公钥与私钥匙成对出现。 遵从的原则就是“私钥签名、公钥验证”。

所以我们现在最重要的问题是,有验证数据,有公钥,但是没有私钥,根据理论,我们是没办法生成私钥签名的。要能打破这个我就牛逼了!!!
所以就这个问题就到此为止吧,当然你有更好的方法,可以在评论区评论出来,大家再努力一下。
那个朋友原以为是个合约调用问题,调用合约其实是一个很简单的事,经过分析,却是一个没有数字签名,调用合约通不过验证的问题。

区块链应用安全性还是比其他应用要高级不少,其他应用,代码一破解,基本就算搞定,这个是没有私钥,技术再高,也不可能突破理论。

文章挺简单,其中过程,还是很复杂,我只写了我弄的过程中,正确的部分,错误的尝试就没写了,欢迎大家交流,文章内容也只用于技术探讨,不要用于黑客活动。

Image of Datadog

The Future of AI, LLMs, and Observability on Google Cloud

Datadog sat down with Google’s Director of AI to discuss the current and future states of AI, ML, and LLMs on Google Cloud. Discover 7 key insights for technical leaders, covering everything from upskilling teams to observability best practices

Learn More

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more