前文
尝试爆破NFT奖励时间限制 (一) | 登链社区 | 深入浅出区块链技术 (learnblockchain.cn)
昨天写了,网页爆破的尝试,没有成功,今天讲讲,为什么先是网页爆破,如前面所见,智能合约调用参数太多搞不清除每一个参数是干什么的,通过调用他自己的api,可以减少犯错的几率,也不需要理解别人的代码,就可以干,如果顺利,就捡便宜了,当然没捡到,所以有了我们第二篇。
网页再分析
网页没有成功,当然得理解为什么没有成功,所以我们的理解他的流程,他的业务。
网页失败的地方
根据日志查找出错请求的发送位置。
查看调用代码
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
找到合约代码,看etherscan的信息,合约已经验证,但是没有源代码,所以,只能想其他办法。
这就用到了,上次写蜜罐分析,网友推荐的Contract list - Ethereum Contract Library by Dedaub (contract-library.com)
找到反汇编信息:
找到上面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算法的公钥和私钥
第二步:执行私钥签名, 使用私钥签名,生成私钥签名
第三步:执行公钥签名,生成公钥签名
第四步:使用公钥验证私钥签名
备注:所谓的公钥与私钥匙成对出现。 遵从的原则就是“私钥签名、公钥验证”。
所以我们现在最重要的问题是,有验证数据,有公钥,但是没有私钥,根据理论,我们是没办法生成私钥签名的。要能打破这个我就牛逼了!!!
所以就这个问题就到此为止吧,当然你有更好的方法,可以在评论区评论出来,大家再努力一下。
那个朋友原以为是个合约调用问题,调用合约其实是一个很简单的事,经过分析,却是一个没有数字签名,调用合约通不过验证的问题。
区块链应用安全性还是比其他应用要高级不少,其他应用,代码一破解,基本就算搞定,这个是没有私钥,技术再高,也不可能突破理论。
文章挺简单,其中过程,还是很复杂,我只写了我弄的过程中,正确的部分,错误的尝试就没写了,欢迎大家交流,文章内容也只用于技术探讨,不要用于黑客活动。
Top comments (0)