Rugpull Contracts: Masquerading Code in Etherscan

DeFi Security Summit 2023 · 15 Jul 2023 · 19:47

Yoav Weiss and Oren Fine show how scammers make verified code on Etherscan misrepresent what a contract actually does. Techniques include burying a missing access control check inside thousands of unused lines, fake proxies that make Etherscan display the wrong implementation, and hidden fallback functions deep in an inheritance tree that delegate calls elsewhere. They also present a real mainnet vulnerability that auditors missed because of such tricks, and the talk questions how far verified source code can be trusted.

Speakers

Topics

From DeFi Security Summit 2023, hosted by DeFi Security Summit.

Transcript

hi everyone so uh so today we're going to talk about a masquerading code in ethoscan or a hiding code hiding code and not showing what's really going to run and as everyone here probably knows uh when starting to interact with a new contract it's a common practice to look at to look at the verified code in ethoscan to understand what it's going to do and assess whether it's uh whether it can be trusted and of course scammers know this too so the question is can they can they pull this rug from under us and it turns out that they they can and they often do so what we're going to show here is uh a

couple of techniques a couple of techniques that are used to bait and switch to show to show you legitimate code that actually runs something else and then we're going to show an actual an actual vulnerability that was exploited on mainnet and it was missed by Auditors due to such techniques the most obvious case is um the most obvious way to do it is to hide a bug in plain sight and it's made possible by the fact that you can verify any you can when you're verifying a contract on ethoscan you can upload any number of solidity files that you import even if these files have nothing to do with your

uh with your byte code so you can upload huge code most of it is not actually used but it can overwhelm the auditor and and then and then some vulnerability can be well hidden inside it's also in many cases there would be multiple implementations of some functions in different files and it can be quite difficult for an auditor or even for an experienced auditor to find out which one is actually going to be used in each situation so we demonstrated it here by deploying this decks which is uh more than 7000 lines of code most of this code of

course is unused it was just uploaded as part of a verification and inside it there's a there is one small bug well hidden let's look at it so this contact is ownable and it has a transfer ownership function there are multiple implementations that inherit from a merit from owner from honorable and we see transfer ownership so the first two versions are fine the third implementation is almost fine but it's missing them it's missing the only owner modifier which means that which means that basically anyone can take over this contract another form another form of attack is

using a fake proxy so etherscan has this a nice feature where it recognizes standard proxies like eapa in 1967 it recognizes transparent proxies and it shows you the implementation and it's common to rely on that now it's great for ux it's not so great for security because they have no way to detect to detect fake proxies so proxies that seem to point to a legitimate implementation but actually in some situations or maybe always use a different implementation so it's going to show us the wrong implementation and here we see such a proxy it looks like a standard proxy but you can see that it

you can see that it has the standard implementation slot but when you call the get implementation internally it's actually going to bring the implementation from a different slot and let's see what it looks like on on an etherscan so we deployed it and it is recognized as it's recognized as a standard proxy and we see the implementation address so it's recognized as the it's recognized as a proxy we see the implementation address and Below you can see the code in this implementation address there's a function called check and it returns one but then if you look at the if you look above it you see that

we call check and we actually got zero that's because the implementation that's being shown to us is not the implementation that's actually being used a more interesting version of this is what I call the non-proxy proxy it's um it's a contact that seems immutable and it seems immutable and you don't think of it as a proxy it can be some erc20 token or something and it inherits from a multiple contracts and each of these contracts can inherit from a couple more so you can have a very complex inheritance tray and somewhere deep inside this tree you can have a fallback function you can have a hidden fallback function that

delegates to the delegates to some other address so effectively the entire contract becomes a proxy for unimplemented functions so anything that is outside the ABI will will try to delegate to that contract and at the time of deployment it can be well hidden because you because you can make it counterfactual so when you deploy it and when the code gets audited and evaluated by users there is nothing in the delegated address which means that if you try to call something outside the ABI it's going to revert because that's what solidity does when you try to make a call to a something with no code so it will divert but then later you can

deploy the contract at any time you can anytime deploy it and add new functionality to the contract so what we're demonstrating here is an elc20 token and it has it it inherits from some contract and if you look at some contract you see that it has this small fallback function that delegates to a hard-coded address and there was nothing in this address at the time of deployment but later we deploy something so if you look at the ABI on the left you see that there is no mint function because it's not a part of the API but if you decompile the if you decompile the new code you see that it has two new functions one of them being the mint function which allows us to

Mint any number of tokens and that's what we did here so we demonstrated that we start from an address with no tokens and we call it manually because it can be done from the UI because it's not part of the API but you call Mint manually and now we have a lot of tokens which we can use to drain the to drain the liquidity pulling unit swap for that token another interesting case is select all collisions so uh the way solidity encodes functions is by hashing them and using the front and using the first four bytes hashing the the function signature and using the first four bytes and four bytes is not a lot so it's easy to find

a collision now when you have a collision between a proxy and an implementation the proxy will take precedence so it allows you to hide any function and I mean cover it with a different function and it doesn't have to be something that looks very suspicious it actually can look very harmless so we demonstrate it here by using uh but we have this simple Vault implementation it's evolved it has a it has a mapping of Vault managers and functions for adding and removing managers which can only be called by the current managers so this contract is supposedly safe and here's a proxy to it and the proxy seems like a standard proxy except this smaller po function at the bottom called image URL so image URL

is a pure function that returns true or it could return some string or whatever now an auditor might wonder uh why does this proxy have this pure function but most people wouldn't care about it too much because it's just in a pure function it doesn't touch the state it only returns to also how bad can it be well it turns out that it can be quite bad because it shows the first four bytes of the hash with default managers the map a mapping getter which means that now you're going to return to for any uh for anything you check in this mapping so here we have this we have a number Vault it's a volt that has a number that anyone can read but only managers should be able to change it and it's using this

proxy vault and when we um and if we check it we see that any address is going to return to because of the image URL function so we were able to change the number from an address that is not supposed to be a manager so anyone can actually can actually act as a manager on this contact and another form of another form of such attacks is to have a hundred percent legit code not mess with the code in any way but mess with the storage and a legitimate contract with the legitimate contact with bad storage with compromised storage is still a compromised storage

so well so you can compromise the storage during initialization or if there is a delegate call somewhere in the code you can also do it later and when looking at the storage it's really hard to know what's it's really hard to know what this storage means because of the way mapping is uh is built in solidity so you see that there is something in some random slot you don't know what it is until it's actually used and I'm demonstrating it here using um using nosy safe so we haven't so we deployed a safe and it's using the uh this safe is it's a grocery safe proxy Factory so it uses the official Factory and we make a call to the official

Factory and we use the official single tone the official implementation so all the code is uh is diagnosis safe code there is nothing efficient about the code itself and if we look at their UI we can see that it has one signer yeah so it has one signer and this signer is um so this signer is supposed to be the only one that can transact in Ethernet also we can also see that get own and get owners return only this address but below you can see a different address and there are actually 20 20 more signers that are not visible but they return to when you call his owner that's because of the that's because the get owner uses the linked

list but is owner actually uses a mapping so these users can actually transact and here we send a transaction from such uh from such address and the transaction is successful if we look at ifscan you see that execution was successful even though it is signed by a different uh it's signed by a by a different address that should not be able to transact and now Owen is going to present an actual real life case okay so I'll dive right into it I've a bit more than six minutes and I'll make it uh okay A bit of orientation uh chain swap was a cross train bridge was active in 2021 up to late 2021 and was hacked

twice in about a week and and we're talking today about the second hack first hack was 800k stolen and the second hack was 4.4 million dollars hacked and basically after the hack there were a few explainers that were published some talked about sloppy authentication checks some talks about some talked about uh something wrong in the increasing of the uh quota but basically they either got it wrong or didn't touch the the spot so let's find out why and we start with looking at the attacker's transaction and we see that the attacker's transaction in etherscan is basically interacting with chain swaps proxy which directs them to a verified contract now it doesn't show here but

basically we find in we we managed to found an identical contract to this verified contract which was audited so it's not only verified but it's even audited and now we go to tenderly to see if if the transaction behaves the same way and go step by step but we see that eventually the hacker calls the receive function in the proxy in James proxy and but eventually it ends up in another contract right we see here a different address and the address when we go to etherscan we see that this is an unverified contract and closed source so we see that something behaves differently from what etherscan shows us and basically what what happens there is

that the hacker calls the proxy chainsaw proxy it calls another proxy which called which is called the factory contract he calls another contract which is called the factor implementation contract together with this uh string that we see here by the way this is the name of the verified contract so it's the same name but eventually it gets another address which is the address of the unverified contract and eventually the call is delegated to that contract so basically already here we see that there is incoherency between what etherscan shows us and what actually happens on chain so let's dig dig in a little deeper to understand the root cause so when we look at the verified code we see that receive function does a few

authentication checks then called decrease auth quota which naturally checks your quota and out quota off is a mapping so naturally it defaults to zero and when you look at the code you see that only authenticated players can increase the quota so so far so good but again the transaction behaves differently and we saw it because you see a few different strings that do not appear in the in the source code so we looked for these strings and found them in another contract also used by Chain swap and their odd squadov is no longer a mapping but now it's a function and we'll you could you look at the function you also see that see off quota Auto

quota ratio and period which are those strings and all kinds of computations Divisions multiplications and so on but instead of of looking at at the code we just went Twitter scan and tried to query how much quota do we have how much the quota does the attacker have and so on and basically uh you see here every address that you check has 10 in the power of 22 which is basically unlimited quota so everybody gets unlimited quota and this was the situation for for a week ever since the unverified contract was deployed to the moment that it was hacked so to wrap this up uh basically what was supposed to happen was that the hacker

calls the proxy contract which so which calls token mapped verified contract seems very legitimate but in practice this whole mess of calling the proxy which calls another proxy which calls the factory which Returns the address and eventually delegates to the closed Source contract so that's about it uh just to conclude so we presented a few techniques uh that confuses either Auditors or ether scan users about the code in ether scan and what really happens in in on chain and we've also seen a real-life vulnerability that was exploited and ended up in 4.4 million dollars uh of loss and and eventually it's a cat and mouse game right there there will always be new techniques and new ways that uh

hackers will be able to trick Auditors and and users so you must keep up that's it [Applause] thanks it's great great great and interesting stuff I think a lot of questions guys please hands up there I can see hello thank you for the talk can you please iterate a bit more on how the grenosi safe example worked with multiple signers

uh yes so if you look at the code the the safe code uh the way it saves and it has a data structure that it uses for signers and for modules that is uh it seems to be like a linked list where it's actually stored in uh the addresses are are saved in a mapping but the map there is a link list pointing to this mapping so get signers get signers is going to get you whatever is linked in the linked list but of course when they actually check for a signer they don't need to go to Traverse a list they can do it in order of one they just check if something is in the mapping so you can have things that are in the mapping but not uh but not in the linked list and

even and by looking at the storage it's impossible to know what it means because the way solidity and the way solidity handles mapping is by uh doing a catch-up hashing the base slot of the mapping and the and the key so if you look at a certain slot but it was written directly in assembly and it was not a Northwest mapping you are unable to tell which mapping it is a part of what it actually means so you can describe a meaning to a meaningfully slot it's just some value that appears in some random slot so during initialization I created 20 such 20 such slots I could also do it later by making some delegate caller from the safe

and then you can have it you can add signers or modules and and it's impossible to know what they are until someone actually abuses it but this is a uh this will become irrelevant soon specifically for safe because they are working on removing all delegate call which is great all right thank you and hello and question I had two quick questions um one we will explain that when there's some non-studdled proxy then eterscan will go and ask like what this is the implementation and query that why doesn't it just query The View function of this contract like I guess

then then even if there's some non-student proxy you could get the actual result and the second question when you gave the Collision example it doesn't the unchanged source code would be different if then the code that was verified with the pure image URL function okay so for the first question uh it was uh what was it again I I'm sorry sorry for interrupting we have a strict schedule for today so Applause is for this various of the light side I think for this death Summit we are better protected from this dark side people who

find new methods to hack us one more time applouses [Applause]