Am 27. März 2022 wurde das Staking-DeFi-Projekt Revest Finance auf Ethereum aufgrund des ERC-1155-Callback-Mechanismus angegriffen, was zum Diebstahl von Token im Wert von rund 2 Millionen US-Dollar (nämlich BLOCKS, ECO, LYXe und RENA) führte. Wir haben den Angriff zunächst analysiert und unsere Analyse noch am selben Abend (UTC+8) auf Twitter veröffentlicht.
Tatsächlich hatten wir zum Zeitpunkt der Twitter-Veröffentlichung noch Zweifel an einer Funktion im Revest TokenVault-Vertrag. Wir haben den Vertrag untersucht, um seine Funktionalität zu verstehen. Später stellten wir fest, dass es sich um eine weitere kritische Zero-Day-Schwachstelle handelt, die auf wesentlich einfachere Weise ausgenutzt werden kann und zu den gleichen enormen Verlusten führen kann (wie beim aufgetretenen Angriff).
Wir haben uns umgehend an das Revest Finance-Team gewandt, das schnell reagierte und eine Umgehungslösung für die Schwachstelle vorschlug. Nachdem wir bestätigt hatten, dass die Schwachstelle nicht ausgelöst werden kann, entschieden wir uns, diesen Blog zu veröffentlichen.
Der folgende Teil dieses Blogs besteht aus drei Teilen: dem Mechanismus von Revest Finance, dem ursprünglichen Re-entrancy-Angriff und der neuen Zero-Day-Schwachstelle.
Was ist das Revest Finance FNFT?
Der Financial Non-Fungible Token (FNFT) von Revest Finance ermöglicht die vertrauenslose Übertragung zukünftiger Rechte an gesperrte Vermögenswerte. Der Einstiegsvertrag (Revest-Vertrag) bietet drei verschiedene Schnittstellen, um FNFTs durch Sperren zugrunde liegender Vermögenswerte zu prägen:
mintTimeLock: Der zugrunde liegende Vermögenswert wird nach einer bestimmten Zeit freigeschaltet.mintValueLock: Der zugrunde liegende Vermögenswert wird freigeschaltet, wenn sein Wert einen vorgeschriebenen Wert über- oder unterschreitet.mintAddressLock: Der zugrunde liegende Vermögenswert wird von einem vorgeschriebenen Konto freigeschaltet.
Der Revest-Vertrag verbindet die anderen drei Verträge zum Sperren und Entsperren zugrunde liegender Vermögenswerte.
-
FNFTHandler: Erbt vom ERC-1155-Token. Er erstellt für jede Sperre einen neuen FNFT mit der inkrementellen
fnftId. Die Sperre schreibt die Gesamtzahl der neu erstellten FNFTs bei der Erstellung vor. Die FNFTs können nicht auf andere Weise geprägt werden, können aber zum Freischalten zugrunde liegender Vermögenswerte verbrannt werden. -
LockManager: Erfasst die Entsperrungsbedingungen für jede Sperre bei der Erstellung und entscheidet, ob die Sperre beim Entsperren freigegeben werden kann.
-
TokenVault: Empfängt und sendet die zugrunde liegenden Vermögenswerte und erfasst die Metadaten für jeden FNFT, wie z. B. den Wert eines bestimmten FNFT.
Wir nehmen mintAddressLock als Beispiel, um den Prozess der Prägung von FNFTs zu veranschaulichen.


Die beiden obigen Abbildungen beschreiben, wie ein FNFT erstellt, geprägt und verbrannt wird.
Konkret sperrt Benutzer A 100 WETH in Revest Finance, wodurch der entsprechende FNFT mit fnftId als 1 erstellt wird. Schließlich prägt er 100 1-FNFTs an angegebene Empfänger mit angegebenen Anteilen.
Beachten Sie, dass nach dem Freischalten des zugrunde liegenden Vermögenswerts jeder 1-FNFT zum Erhalt eines (1e18) WETH verbrannt werden kann. Wie in Abbildung 2 gezeigt, zieht Benutzer B 25 ( 1e18) WETH ab, indem er 25 1-FNFTs verbrennt.
Darüber hinaus bietet der Revest-Vertrag eine weitere Schnittstelle namens depositAdditionalToFNFT, die zwei Schwachstellen birgt, die im Folgenden besprochen werden.
Wir verwenden zunächst die folgenden beiden Abbildungen, um die normale Verwendung dieser Funktion zu beschreiben.


Die Funktion depositAdditionalToFNFT sperrt weitere zugrunde liegende Vermögenswerte in eine bestehende Sperre (angegeben durch fnftId). Sinnvollerweise (Abbildung 3) erfordert sie, dass die angegebene Menge der Gesamtversorgung des angegebenen FNFT entspricht und verteilt dann die zusätzlichen Vermögenswerte gleichmäßig auf jeden angegebenen FNFT.
Andernfalls (Abbildung 4) erstellt sie eine neue Sperre mit der neuesten fnftId, verbrennt die angegebenen Mengen alter FNFTs und prägt die angegebene Menge neuer FNFTs und zeichnet dann den depositAmount der neuen Sperre als Summe des depositAmount der alten Sperre und des angegebenen Betrags auf, wie im folgenden Code gezeigt.
// Jetzt transferieren wir zum Token-Tresor
if(fnft.asset != address(0)){
IERC20(fnft.asset).safeTransferFrom(_msgSender(), vault, quantity * amount);
}
ITokenVault(vault).handleMultipleDeposits(fnftId, newFNFTId, fnft.depositAmount + amount);
emit FNFTAddionalDeposited(_msgSender(), newFNFTId, quantity, amount);
Da der im TokenVault-Vertrag gespeicherte depositAmount den Betrag des zugrunde liegenden Vermögenswerts angibt, den ein bestimmter FNFT abheben kann, überträgt diese Operation den Wert der angegebenen Mengen des alten FNFT von der alten Sperre zur neuen Sperre.
(Eine angegebene Menge, die größer als die Gesamtversorgung ist, führt zum Zurücksetzen der Transaktion)
Was ist die Re-entrancy-Schwachstelle?
In diesem Teil werden wir erläutern, wie der Re-entrancy-Angriff funktioniert, und die Ursache sowie die Korrekturmethode erörtern.



Die obigen drei Abbildungen beschreiben im Wesentlichen den gesamten Prozess des Re-entrancy-Angriffs. Insbesondere sperrt der Angreifer zunächst null RENA-Token, um 2 1-FNFTs zu prägen, die keinen Wert haben. Zweitens sperrt der Angreifer erneut null RENA-Token, prägt aber 360.000 2-FNFTs, die ebenfalls (noch) keinen Wert haben. Im letzten Schritt greift der Angreifer den Revest-Vertrag der Funktion depositAdditionalToFNFT über den Callback-Mechanismus des FNFTHandler wieder auf, der vom ERC-1155-Token-Standard geerbt wird, was den depositAmount der Sperre mit fnftId als 2 überschreibt, bevor fnftId aktualisiert wird. Infolgedessen erhält der Angreifer 360.001 2-FNFTs mit einem depositAmount von 1e18, was bedeutet, dass er 360.001 * 1e18 RENA aus dem TokenVault-Vertrag abziehen kann. Außerdem betragen die einzigen Kosten 1e18 RENA.
Korrekturmethode
Die Codes von Revest Finance entsprechen vollständig dem klassischen Re-entrancy-Muster: Verwende fnftId -> externer Aufruf mit Callback-Mechanismus -> Aktualisiere fnftId. Daher ist der einfachste Weg, die Probleme zu beheben, dieses Muster zu durchbrechen. Der korrigierte Code ist unten gezeigt:
function mint(
address account,
uint id,
uint amount,
bytes memory data
) external override onlyRevestController {
require(amount > 0, "Invalid amount");
require(supply[id] == 0, "Repeated mint for the same FNFT");
supply[id] += amount;
fnftsCreated += 1;
_mint(account, id, amount, data);
}
Erstens verschiebt er die Aktualisierungsoperation vor den externen Aufruf (_mint), was den Angriff vermeiden kann. Zweitens, da das System keine null FNFTs prägen und dieselbe FNFT wiederholt prägen lässt, fügt er zwei Prüfungen hinzu, um sicherzustellen, dass das System wie erwartet funktioniert, was die Sicherheit des Systems verbessern kann.
Die neue Zero-day-Schwachstelle
Bei der Analyse des Codes von Revest Finance verwirrte uns immer die Funktion handleMultipleDeposits im TokenVault-Vertrag, deren Code wie folgt gezeigt wird:
function handleMultipleDeposits(
uint fnftId,
uint newFNFTId,
uint amount
) external override onlyRevestController {
require(amount >= fnfts[fnftId].depositAmount, 'E003');
IRevest.FNFTConfig storage config = fnfts[fnftId];
config.depositAmount = amount;
mapFNFTToToken(fnftId, config);
if(newFNFTId != 0) {
mapFNFTToToken(newFNFTId, config);
}
}
Beim Aufruf der Funktion depositAdditionalToFNFT ändert die Funktion handleMultipleDeposits den depositAmount der alten Sperre oder erfasst ihn für die neue. Wenn newFNFTId null ist, erfasst sie den depositAmount der neuen Sperre nicht, da es sich um eine Operation zum Hinzufügen zusätzlicher Vermögenswerte zu einer bestehenden Sperre handelt.
Laut gesundem Menschenverstand erfasst sie, wenn newFNFTId nicht null ist, nur den depositAmount der neuen Sperre, ändert aber nicht den depositAmount der alten. Der Code besagt jedoch, dass er nicht nur den depositAmount der neuen Sperre erfasst, sondern auch den depositAmount der alten ändert.
Wir halten dies für eine schwerwiegende Zero-Day-Logik-Schwachstelle und haben daraufhin eine PoC zur Verifizierung geschrieben. Die folgenden drei Abbildungen beschreiben, wie die PoC funktioniert.



Konkret sperrt der Angreifer zunächst null RENA, um 360.000 1-FNFTs zu prägen. Danach ruft der Angreifer direkt die Funktion depositAdditionalToFNFT auf, um eine neue Sperre zu erstellen. Aufgrund des Logikfehlers ändert der TokenVault-Vertrag den depositAmount der alten Sperre fälschlicherweise von null auf 1e18. Infolgedessen erhält der Angreifer 359.999 1-FNFTs im Wert von 359.999 RENA. Offensichtlich ist die PoC weitaus einfacher als der tatsächliche Re-entrancy-Angriff.
Die Umgehungslösung zur Behebung der Schwachstelle
Dies ist ein Logikfehler, und wir empfehlen die Verwendung des folgenden Codes zur Behebung:
function handleMultipleDeposits(
uint fnftId,
uint newFNFTId,
uint amount
) external override onlyRevestController {
require(amount >= fnfts[fnftId].depositAmount, 'E003');
IRevest.FNFTConfig memory config = fnfts[fnftId];
config.depositAmount = amount;
if(newFNFTId != 0) {
mapFNFTToToken(newFNFTId, config);
} else {
mapFNFTToToken(fnftId, config);
}
}
Da die beiden anfälligen Verträge: TokenVault und FNFTHandler viele kritische Zustände speichern, kann das Projekt den TokenVault-Vertrag und den FNFTHandler-Vertrag nicht ohne Migration von Zuständen neu bereitstellen. Um weitere Angriffe auf diese Schwachstelle zu vermeiden, hat das Projekt eine Lite-Version des Revest-Vertrags neu bereitgestellt, die komplexere Funktionen deaktiviert, um die Angriffsflächen für potenzielle Angreifer zu reduzieren. Nach der Überprüfung der Umgehungslösung sind wir der Meinung, dass der Lite- Revest-Vertrag die in diesem Blog genannten möglichen Angriffe abmildern kann.
Fazit
Die Absicherung eines DeFi-Projekts ist keine leichte Aufgabe. Neben der Code-Prüfung sind wir der Meinung, dass die Community einen proaktiven Ansatz verfolgen sollte, um den Projektstatus zu überwachen und den Angriff zu blockieren, bevor er überhaupt stattfindet.
Über BlockSec
BlockSec ist ein wegweisendes Blockchain-Sicherheitsunternehmen, das 2021 von einer Gruppe weltweit angesehener Sicherheitsexperten gegründet wurde. Das Unternehmen engagiert sich für die Verbesserung der Sicherheit und Benutzerfreundlichkeit für die aufstrebende Web3-Welt, um deren breite Akzeptanz zu fördern. Zu diesem Zweck bietet BlockSec Audit-Dienste für Smart Contracts und EVM-Ketten-Sicherheit, die Phalcon-Plattform für die sichere Entwicklung und proaktive Bedrohungsabwehr, die MetaSleuth-Plattform für die Verfolgung und Untersuchung von Geldern sowie die MetaDock-Erweiterung für Web3-Entwickler zum effizienten Surfen in der Krypto-Welt.
Bis heute hat das Unternehmen über 300 angesehene Kunden wie MetaMask, Uniswap Foundation, Compound, Forta und PancakeSwap betreut und in zwei Finanzierungsrunden von führenden Investoren wie Matrix Partners, Vitalbridge Capital und Fenbushi Capital zweistellige Millionenbeträge erhalten.
Offizielle Website: https://blocksec.com/
Offizielles Twitter-Konto: https://twitter.com/BlockSecTeam



