尝试爆破NFT奖励时间限制 （二）
Tags: nft, solidity, ecdsa
Published: 2022-02-27 08:15:17

## 前文

[尝试爆破NFT奖励时间限制 （一） | 登链社区 | 深入浅出区块链技术 (learnblockchain.cn)](https://learnblockchain.cn/article/3516)

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

## 网页再分析

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

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

![16441283511.png](https://img.learnblockchain.cn/attachments/2022/02/Yo8ZCn8S61ff6874b98e7.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;
          }
```

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

## 合约分析

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

![16441289401.png](https://img.learnblockchain.cn/attachments/2022/02/0Y1Ilbzl61ff6ab231fea.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);
}
```

现在想办法把反汇编的函数，还原成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);
    }
```

```
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
            )));
```

```
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;
    }
```

## 得出结论

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

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

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

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