Перейти к содержанию

Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity


CryptoDeepTech

1 437 просмотров

 

Following the article: “Solidity Forcibly Send Ether Vulnerability to a Smart Contract continuation of the list of general EcoSystem security from attacks”. In this article, we will continue this topic related to vulnerabilities and traps. In the process of cryptanalysis of various cryptocurrencies, we are increasingly getting loopholes and backdoors. Honeypots work by luring attackers with a balance stored in the smart contract, and what appears to be a vulnerability in the code. Typically, to access the funds, the attacker would have to send their own funds, but unbeknownst to them, there is some kind of recovery mechanism allowing the smart contract owner to recover their own funds along with the funds of the attacker.

Let’s look at a couple different real world examples:

pragma solidity ^0.4.18;

contract MultiplicatorX3
{
    address public Owner = msg.sender;
   
    function() public payable{}
   
    function withdraw()
    payable
    public
    {
        require(msg.sender == Owner);
        Owner.transfer(this.balance);
    }
    
    function Command(address adr,bytes data)
    payable
    public
    {
        require(msg.sender == Owner);
        adr.call.value(msg.value)(data);
    }
    
    function multiplicate(address adr)
    public
    payable
    {
        if(msg.value>=this.balance)
        {        
            adr.transfer(this.balance+msg.value);
        }
    }
}

In this contract, it seems that by sending more than the contract balance to multiplicate(), you can set your address as the contract owner, then proceed to drain the contract of funds. However, although it seems that this.balance is updated after the function is executed, it is actually updated before the function is called, meaning that multiplicate() is never executed, yet the attackers funds are locked in the contract.

pragma solidity ^0.4.19;

contract Gift_1_ETH
{
    bool passHasBeenSet = false;
    
    function()payable{}
    
    function GetHash(bytes pass) constant returns (bytes32) {return sha3(pass);}
    
    bytes32 public hashPass;
    
    function SetPass(bytes32 hash)
    public
    payable
    {
        if(!passHasBeenSet&&(msg.value >= 1 ether))
        {
            hashPass = hash;
        }
    }
    
    function GetGift(bytes pass)
    external
    payable
    {
        if(hashPass == sha3(pass))
        {
            msg.sender.transfer(this.balance);
        }
    }
    
    function PassHasBeenSet(bytes32 hash)
    public
    {
        if(hash==hashPass)
        {
           passHasBeenSet=true;
        }
    }
}

This contract is especially sneaky. So long as passHasBeenSet is still set to false, anyone could GetHash()SetPass(), and GetGift(). The sneaky part of this contract, is that the last sentence is entirely true, but the problem is that passHasBeenSet is already set to true, even though it’s not in the etherscan transaction log.

You see, when smart contracts make transactions to each other they don’t appear in the transaction log, this is because they perform what’s known as a message call and not a transaction. So what happened here, must have been some external contract setting the pass before anyone else could.

A safer method the attacker should have used would have been to check the contract storage with a security analysis tool.

Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity
Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity

Hardly a week passes without large scale hacks in the crypto world. It’s not just centralised exchanges that are targets of attackers. Successful hacks such as the DAOParity1 and Parity2 have shown that vulnerabilities in smart contracts can lead to losing digital assets worth millions of dollars. Attackers are driven by making profits and with the incredible value appreciation in 2017 in the crypto world, individuals and organisations who hold or manage digital assets are often vulnerable to attacks. Especially smart contracts have become a prime target for attackers for the following reasons:

  • Finality of transactions: This is a special property of blockchain systems and it means that once a transaction (or state change) took place it can’t be taken back or at least not with grave consequences which in case of the DAO hack led to a hard fork. For an attacker targeting smart contracts, finality is a great property since a successful attack can not easily be undone. In traditional banking systems this is quite different, an attack even though initially successful could be stopped and any transactions could be rolled back if noticed early enough.
  • Monetising successful attacks is straight forward: Once the funds of a smart contract can be withdrawn to an attacker’s account, transferring the funds to an exchange and cashing out in Fiat while concealing ones identity is something that the attackers can get away with if they are careful enough.
  • Availability of contract source code / byte code: Ethereum is a public blockchain and so at least the byte code of a smart contract is available to anyone. Blockchain explorers such Etherscan allow also to attach source code to a smart contract and so giving access to high level Solidity code to potential attackers.

Since we have established now why attackers find smart contracts attractive targets, let’s further look into the circumstances that could decide if a smart contracts gets attacked:

  1. Balance: The greater the balance of a smart contract the more attackers will try to attack it and the more time they are willing to spend to find a vulnerability. This is an easier economic equation than for none smart contract targets since the balance that can be potentially stolen is public and attackers have certainty on how profitable a successful attack could be.
  2. Difficulty/Time: This is the unknown variable in the equation. Yet the approach to look for potential targets can be automated by using smart contract vulnerability scanners. Availability of source code addtionally decreases analyis time while also lowering the bar for potential attackers to hack smart contracts since byte code is harder to read and therefore it takes more skill and time to analyse.

Taking the two factors above in consideration, one could assume that every smart contract published to the main net with a sufficient balance is analysed automatically by scanners or/and manually by humans for vulnerabilities and is likely going to be exploited if it is in fact vulnerable. The economic incentives and the availability of smart contracts on the public chain have given rise to a very active group attackers, trying to steal from vulnerable smart contracts. Among this larger group of attackers, a few seem to have specialised to hack the hackers by creating seemingly vulnerable smart contracts. In many ways these contracts have resemblance to honeypot systems. They are created to lure attackers with the following properties:

  • Balance: Honeypots are created with an initial balance that often seem to be in the range of 0.5–1.0 ETH.
  • Vulnerability: A weakness in the code that seemingly allows an attacker to withdraw all the funds.
  • Recovery Mechanism: Allows owner to reclaim the funds including the funds of the attacker.

Let’s analyse three different types of smart contract honeypots that I have come across over the last couple of weeks.

honeypot1: Multiplicator.sol

The contract’s source code was published on Etherscan with a seemingly vulnerable function. Try to spot the trap.

Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity

GITHUB

This is a really a short contract and the multiplicate() function is the only function that does allow a call from anyone else than the owner of the contract. At first glance it looks like by transferring more than the current balance of the contract it is possible to withdraw the full balance. Both statements in line 29 and 31 try to reinforce the idea that this.balance is somehow credited after the function is finished. This is a trap since the this.balance is updated before the multiplicate() function is called and so if(msg.value>=this.balance) is never true unless this.balance is initially zero.

It seems that someone has actually tried to call multiplicate() with 1.1 Ether. Shortly after the owner has withdrawn the full balance.

Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity

honeypot2: Gift_1_ETH.sol

Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity

GITHUB

The contract has a promising name, if you want to figure out the trap yourself have a look at the code here. Also check out the transaction log … why did 0xc4126a64c546677146FfB3f3D5A6F6d5A2F94DF1 lose 1 ETH?

Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity

It seems that 0xc4126a64c546677146FfB3f3D5A6F6d5A2F94DF1 did everything right. First SetPass() was called to overwrite hashPass and then GetGift() to withdraw the Ether. Also the attacker made sure PassHasBeenSet() has not been called. So what went wrong?

One important piece of information in order to understand honeypot2 is to clarify what internal transactions are. They actually do not exist according to the specifications in the Ethereum Yellow Paper (see Appendix A for terminologies). Transactions can only be sent by External Actors to other External Actors or non-empty associated EVM Code accounts or what is commonly referred to as smart contracts. If smart contracts exchange value between each other then they perform a Message Call not a Transaction. The terminology used by EtherScan and other blockchain explorers can be misleading.

Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity

It’s interesting how one takes information as a given truth if the data comes from a familiar source. In this case EtherScan does not show the full picture of what happened. The assumption is that the transaction (or message call) should show up in internal transactions tab but it seems that calls from other contracts that have msg.value set to zero are not listed currently. Etherchain on the other hand shows the transaction (or message call) that called PassHasBeenSet() with the correct hash and so denying any future password reset. The attacker (in this case more of a victim) could have also been more careful and actually read the contract storage with Mythril for instance. It would have been apparent that passHasBeenSet is already set to true.

Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity
Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity
Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity
Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity

honeypot3: TestToken

I have taken the trick from the honeypot contract WhaleGiveaway1 (see analysis) and combined it with one of my own ideas. The contract is available here on my Github. Something is missing here …

Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity

This contract relies on a very simple yet effective technique. It uses a lot of whitespaces to push some of the code to the right and out of the immediate visibility of the editor if horizontal scrolling is enabled (WhaleGiveaway1). When you try this locally in Remix and you purely rely on the scrolling technique like in WhaleGiveaway1 then the trick actually does not work. It would be effective if an attacker copies the code and is actually able to exploit the issue locally but then fails on the main net. This can be done using block numbers. Based on what network is used the block numbers vary significantly from the main net.

Ganache: starts from 0

Testrpc: starts from 1150000

Ropsten: a few weeks ago around 2596174

Main net: a few weeks ago around 5040270

Therefore the first if statement is only true on the main net and transfers all ETH to the owner. On the other networks the “invisible” code is not executed.

if (block.number > 5040270 ) {if (_owner == msg.sender ){_owner.transfer(this.balance);} else {throw;}}

EtherScan also had the horizontal scrolling enabled, but they deactivated it a few a few weeks ago.

TL;DR

Smart contract honeypot authors form a very interesting sub culture among a larger group of hackers trying to profit from vulnerable smart contracts. In general I would like to give anyone the following advice:

  • Be careful where you send your ETH, it could be a trap.
  • Be nice and don’t steal from people.

I have created a Github repo for honeypot smart contracts here. Should you have any honey pot contracts yourself that you want to share please feel free to push them to the repo or share them in the comments.

Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity

Honeypot programs are one of the best tools that security researchers have ever made to study the new or unknown hacking techniques used by attackers. Therefore, using honeypots in smart contract could be a very good idea to study those attacks. So what is honeypot in smart contract?

Honeypots in the Blockchain industry is an intentionally vulnerable smart contract that was made to push attackers to exploit its vulnerability. The idea is to convince attackers or even simple users to send a small portion of cryptocurrency to the contract to exploit it, then lock those ethers in the contract. 

In this blog post, you are going to see some examples of those honeypots with a detailed technical explanation of how they work. So if you are interested to learn more about this subject just keep reading and leave a comment at the end.

What is honeypot in smart contract?

A honeypot is a smart contract that purports to leak cash to an arbitrary user due to a clear vulnerability in its code in exchange for extra payments from that user. The monies donated by the user to the vulnerable contract get then locked in the contract and only the honeypot designer or attacker will be able to recover them.

The concept of a honeypot is well known in the field of network security and was used for years by security research. The main objective of using them was to identify new or unknown exploits or techniques already used in the wild. In addition, Honeypots were used to identify zero-day vulnerabilities and report them to vendors. This technique was basically designed to trap black hat hackers and learn from them.

However, with the rise of Blockchain technology and the smart contract concept.

Blockchain is the new trending technology in the market, many companies start to implement it to solve multiple problems. Usually, this technology manages the different types of user information related to their money. Therefore, to secure this technology you should first understand how it works. Blockchain technology can be seen as a 6 layer system that works together. Therefore, what are the six layers of blockchain technology?

The Blockchain technology is built upon 6 main layers that are:

  1. The TCP/IP network
  2. Peer-to-Peer protocols
  3. Consensus algorithms
  4. Cryptography algorithms
  5. Execution (Data blocs, Transactions, …)
  6. Applications (Dapps, smart contracts …)
Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity

Black hat hackers started to use this concept to trap users both with good or bad intentions.


The idea is simple, the honeypot designer creates a smart contract and puts a clear vulnerability in it. Then hid a malicious code in its smart contract or between its transactions to block the right execution of the withdraw function. Then he deploys the contract and waits for other users to get into the trap.

Best 10 solidity smart contract audit tools that both developers and auditors use during their audit?

  1. Slither
  2. Securify
  3. SmartCheck
  4. Oyente
  5. Mythril
  6. ContractFuzzer
  7. Remix IDE static analysis plug-in
  8. Manticore
  9. sFuzz
  10. MadMax

 

Honestly, the honeypots concept in blockchain is just exploiting the greedy of users that cannot see the whole picture of the smart contract and does not dig deeper to understand the smart contract mechanism and code.

What actually makes this concept even more dangerous in the context of blockchain is that implementing a honeypot is not really difficult and does not require advanced skills. In fact, any user can implement a honeypot in the blockchain, all it needs is the actual fees to deploy such a contract in the blockchain.

In fact, in the blockchain, the word “attacker” could be given to both the one who deploys the smart contract honeypot and the one trying to exploit it (depending on his intention). Therefore, in the following sections of this blog post, we will use the word “deployer” to the one who implements the honeypot and “user” to the one trying to exploit that smart contract.

What are the types of smart contract honeypots?

Honeypots in smart contract can be divided into 3 main categories depending on the used techniques:

  • EVM based smart contract honeypots
  • Solidity compiler-based smart contract honeypots
  • Etherscan based smart contract honeypots

The main idea of honeypot in the network context is to supervise an intentionally vulnerable component to see how it can be exploited by hackers. However, in smart contract the main idea is to hide a behavior from users and trick them to send ether to gain more due to the vulnerability exploitation.

six things you should do to prevent using components with known vulnerabilities:

  • Use components from official repositories
  • Remove unused components
  • Only accept components with active support
  • Put a vulnerability management system for you components
  • Put in place a components firewall
  • Remove or replace components with a stopped support

Therefore, what actually defines each smart contract honeypot category is the used technique to hide that information from users.

The first category of smart contract honeypot is based on the way the EVM instruction is executed. It is true that the EVM follow an exact set of rules, however, some instruction requires a very good experience with the way EVM works to be able to detect the honeypot otherwise the user could easily be fooled.

The second category of smart contract honeypot is related to the solidity compiler. In other words, the smart contract honeypot builder should have a good experience with smart contract development and a deep understanding of how Solidity compiler would work. For example, the way inherence is managed by each version of the solidity compiler, or when overwriting variables or parameters would happen.

The third category of smart contract honeypot is based on hiding things from the users. Most users that try to exploit a program look for the easier way to do so (quick wins). Therefore, they may not take the time to analyze all parts of the vulnerable smart contract. This user behavior leads to locking his money in the smart contract. In this blog post, we are going to discuss 4 techniques used by deployers to hide an internal behavior from the users and therefore fool the user.


 

In my opinion the second category of honeypots is the most difficult to detect as it require a deep knowledge of the solidity compiler.

EVM based smart contract honeypots

The EVM-based smart contract honeypots have only one subtype called balance disorder. I think the best way to understand how this type of smart contract honeypots works, is by example. So take a look at the following example:

Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity

This example is taken from the following contract: https://etherscan.io/address/0x8b3e6e910dfd6b406f9f15962b3656e799f60d2b#code

A quick look at this function from a user, he can easily understand that if he sends while calling this function more than what the contract balance actually has, then everything in the contract plus what he sends will be sent back to him. Which is obviously a good deal.

However, what a user could miss in this quick analysis of the smart contract is that the contract balance will be incremented as soon as the function of the call is performed by the user. This means that the msg.value will always be lower than the contract balance no matter what you do. Therefore, the condition will never be true and the contract will be locked in this contract.

Another example of the balance disorder type of honeypot could be found here:

https://etherscan.io/address/0xf2cf114be39a48aa2321ed39c1f132da0c51e453

By visiting this link you can see that there is no source code out there. So there are two ways to analyze this contract. The first one and the most difficult is to get the bytecode of this smart contract and then try to understand and reverse engineer it. Or the second way is to try to decompile it using different tools available to get an intermediate and easy-to-understand source code.

I personally used the second technique to accelerate the analysis and simply used the Etherscan default decompile. In the smart contract you want to decompile you can click here:

Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity

And wait for a moment about 30 seconds to get the source code.

By taking a look at the source code, and especially at the “multiplicate” function you can now easily see the same logic as the previously explained example.

Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity

The condition in line 24 will never be verified and the money will be stuck in the contract.

Solidity compiler-based smart contract honeypots

As I said, this category of smart contract honeypots is based on some deep knowledge about how the Solidity compiler works. In the following subsection, I will give you 4 techniques that are used to build this kind of smart contract honeypots. However, other unknown techniques might be used in the wild, and I will do my best to update this blog post whenever I found a new one. Please comment below and tell me if you know a technique that was not noted in this blog post.

Inheritance Disorder technique

One of the most confusing systems in solidity language or even in other programming languages is inheritance. A lot of hidden aspects in this concept could be used by deployer to fool the users and work contrary to what is expected.

In solidity language, a smart contract can implement the inheritance concept by using the word “is” followed by the different smart contract that this one wants to inherit their source code. Then only one smart contract is created and the source code from the other contracts is copied into it.

To better understand how such a mechanism could be exploited to create honeypots please take a look at the following examples:

Example1:

Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity

You can find this contract here: https://etherscan.io/address/0xd3bd3c8fb11429b0deee5301e72b66fba29782c0#code

If you take a look at this contract source code, you can easily notice that it has an obvious vulnerability related to access control. The function setup allows a user to change the owner of this contract without checking if he is the actual owner. Therefore, the user would be able to execute the withdraw function to get the money.

However, this analysis assumes that the isOwner() function inherited from the Ownable contract is going to check the local variable Owner.

Unfortunately, this is not what will actually happen. The inheritance creates a different variable for each contract even if they have the same name. The variable Ownable.Owner is totally different than the ICO.Owner. Therefore, when the user will call the setup() function, this one will change the value of ICO.Owner and not Ownable.Owner. This means that the result of the isOwner() will remain the same.

Example2

Another example of this same type of solidity compiler-based honeypot can be found here. The same logic applies to this smart contract. The Owner variable will not change by calling the setup() function.

Skip Empty String Literal

Another tricky behavior in solidity compiler that may not be very easy to discover is the skip empty string literal. The skip empty string literal problem happens in solidity when a function is called with an empty string as a parameter. This is a known bug in solidity compilers before 0.4.13 here is a reference for it.

The encoder skips the empty string literal “” when used as a parameter in a function call. As a result, the encoding of all subsequent arguments is moved left by 32 bytes, causing the function call data to be malformed.

This kind of honeypot could be easily detected, by just looking at the solidity compiler version and then scrolling down the source code to see if there is any use of the empty string in a function call. However, a knowledge of this bug is required to detect the problem in the smart contract.

Here is a simple example of this honeypot:

Check the following smart contract: https://etherscan.io/address/0x2b990227344300aded3a072b3bfb9878b209da0a#code

The source code is a little bit long so I will put just the most important functions:

Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity

In the divest() function line 83, the external function call to loggedTransfer() with the empty string will result in shifting the parameters by 32 bytes which leads to replacing the target address from msg.sender to the owner address. Therefore, the user will send the money to the owner of the contract and not his own address. This simply means that the user will never be able to retrieve the money he sent to this smart contract.

 

This behavior happens only in case of calling the function externally with the this.function().

Type Deduction Overflow

The Solidity compiler offers a nice feature that helps developers declare a variable without knowing exactly what type it would be. This could be made by creating a variable with the keyword “var” and the compiler will deduce what type is better for that result. However, this technique may cause a problem called type deduction overflow.

This problem could be used in a smart contract honeypot to cause a revert and then lock the money on the contract. To better illustrate this problem please take a look at the following source code:

Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity

You can check the whole code here:

https://etherscan.io/address/0x48493465a6a2d8db8616a3c7288a9f81d54a8835#code

In this contract the Double() function allow a user to double his money by first sending at least more than one ether and then looping to create the value of the ethers that will be sent to the user. This seems to be a nice and easy smart contract to exploit.

However, this contract loop will never reach even half of the value sent by the user. The reason behind this is the way the variable “i” is declared. The “var” keyword, will create a variable with a type of uint8 due to the 0 value affected to it in the beginning. The code should loop till it gets to msg.value which is a uint256 and the value would be more than 1 with 18 digits. However, the size of the “i” variable can only reach 255 then once incremented will get back to 0. Therefore, the loop will end and all that the user will receive is 255 wei.

Uninitialized Struct

The uninitialized structure is a common problem in solidity and could be seen both as a vulnerability and as a way to trick users. In this blog post, I am going to discuss the tricky part of this problem. However, if you want me to discuss how this could be a vulnerability, please comment below and I will be happy to make a blog post about it.

An uninitialized structure problem happens when a structure variable is not initialized at the moment of its creation. When a structure variable is not initialized in the same line as its creation with the keyword “new”, the solidity compiler point that variable to the first slot of the smart contract. This simply means the variable will be pointing to the first variable of the smart contract. Once the developer starts affecting values to the structure variable, the first element value of the structure will overwrite the first variable value.

This concept is used by smart contract honeypots deployer to trick users to send money to exploit an obvious vulnerability in it.

Here is an example of such a honeypot:

https://etherscan.io/address/0x29ed301f073f62acc13a2d3df64db4a3185f1433#code

Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity

This contract asks the user to guess a number while betting with some of his money. The secret value that a user is going to guess is stored in the first slot of the smart contract. For a quick analysis of this contract, the user would assume that the contract is vulnerable as even private variables could be seen in the Blockchain.

However, once the user will call the play() function and send money to it, the function will create a structure “game” in line 51 without correctly initializing it. This means that this structure variable will point to the first slot (variable secretNumber). In addition, the game.player will be the variable that will overwrite the secretNumber variable. Therefore, the user “would not” will not be able to correctly guess the number.

Actually, in this example, the honeypot could be bypassed to retrieve the money. If you take a look at the value affected to the game.player variable that overwrite the secretNumber. You will see that it is simply the sender’s address. Therefore, the value the user should send, is simply his address converted to decimals.


Most techniques used in this category of smart contract honeypots can easily be detected by users if they first try to compile and test their exploit in a local environment or a test chain. However, most of the time the vulnerabilities in those smart contracts are so easy to spot and exploit. Therefore, with a small portion of greediness and self-confidence, the users do not even think twice and directly execute their exploit on the mainnet.

Etherscan based smart contract honeypots

All the smart contracts that we have seen until now, exploit a solidity language gap of knowledge in the user. However, in this section of this blog post, the deployer exploits some features related to etherscan platform to hide some important information that may trick users.

Hidden State Update

The Etherscan platform helps developers and any Ethereum Blockchain user to debug his smart contract or track his transactions. Therefore, the platform display user’s transaction and internal messages that are performed by smart contracts. However, one of the features of Etherscan is that it does not show internal messages with an empty value.

Therefore, smart contract honeypot deployer exploit this feature to trick users and change the smart contract behavior. Here is an example to better understand this concept:

Check the following smart contract: https://etherscan.io/address/0x8bbf2d91e3c601df2c71c4ee98e87351922f8aa7#code

Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity

This contract might be used as a honeypot, as the user could be fooled by the initial value of the variable passHasBeenSet. By checking the Etherscan data he would not be able to see any transaction that has changed the value of passHasBeenSet. Therefore, he would assume that the value didn’t change and attempt to exploit the contract.

To do that, the user would try to exploit the contract by sending more than one ether to the contract using the GetGift() after setting the hashPass using SetPass() function.

However, the passHasBeenSet variable might be already changed by another contract and that would not be seen in the etherscan platform.

Straw Man Contract

This technique is built upon showing a source code for a contract that is not actually the one used by the contract. For example, the deployer could build a contract that requires another library and that that library address is initialized during the deployment of the contract or by calling a specific function.

At this stage, there is nothing that holds the deployer from using another contract address that is totally different than the one that the source code is displayed in Etherscan.

Unfortunately, this really a tricky honeypot and a really difficult technique to discover from a user. I mean the user should verify the addresses of the deployed contract and the different transactions and data passed to the contract to be able to find this issue. Moreover, even if the user tries to test this smart contract in a different contract, he will use the smart contract code displayed by the attacker and he will see a normal behavior. Which makes it even more difficult to find the issue.

Here is an example of such a honeypot, try to take a look at it and see what makes this smart contract a honeypot:

https://etherscan.io/address/0xdc5c87ba250b65a83042333f1101940b74312a65#code

Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity

Etherscan is an Ethereum blockchain explorer that, besides other features, allows developers to submit the code of the smart contracts they deploy. The main benefit of this feature is that it allows users to check what contracts do by reading their source code. Etherscan makes sure that the code matches the smart contract as deployed.

The list of verified contracts is long. As of this writing, Etherscan offers the source code for 26055 contracts, which can be browsed here.

On a lazy Sunday afternoon I decided to casually browse it to see what kind of contracts people were running and get a sense of what people use the blockchain for, and how well written and secure these contracts are. Most contracts I found implemented tokens, crowdsales, multi-signature wallets, ponzis, and.. honeypots!

Honeypot contracts are the most interesting findings to me. Such contracts hold ether, and pretend to do so insecurely. In short, they are scam contracts that try to fool you into thinking you can steal the ether they hold, while in fact all you can do is lose ether.

A common pattern they follow is, in order to retrieve the ether they hold, you must send them some ether of your own first. Of course, if you try that, you’re in for a nasty surprise: the smart contract eats up your ether, and you find out that the smart contract does not do what you thought it did.

In this post I will analyze a couple honeypot contracts I came across, and explain what they seem to do, but really do.

The not-really-insecure non-lottery

The first contract I will go through implements a lottery that, apparently, is horribly insecure and easy to steal from with a guaranteed win. I have come across several of these. The last instance I found is deployed at address 0x8685631276cfcf17a973d92f6dc11645e5158c0c, and its source code can be read here. I am copying the code below for convenience. Can you spot the bait? Can you tell why, if you try to exploit it, you will actually lose ether?

pragma solidity ^0.4.23;// CryptoRoulette
//
// Guess the number secretly stored in the blockchain and win the whole contract balance!
// A new number is randomly chosen after each try.
//
// To play, call the play() method with the guessed number (1-16).  Bet price: 0.2 ethercontract CryptoRoulette {
    uint256 private secretNumber;
    uint256 public lastPlayed;
    uint256 public betPrice = 0.001 ether;
    address public ownerAddr;    struct Game {
        address player;
        uint256 number;
    }
    Game[] public gamesPlayed;    constructor() public {
        ownerAddr = msg.sender;
        shuffle();
    }    function shuffle() internal {
        // randomly set secretNumber with a value between 1 and 10
        secretNumber = 6;
    }    function play(uint256 number) payable public {
        require(msg.value >= betPrice && number <= 10);
        Game game;
        game.player = msg.sender;
        game.number = number;
        gamesPlayed.push(game);        if (number == secretNumber) {
            // win!
            msg.sender.transfer(this.balance);
        }        //shuffle();
        lastPlayed = now;
    }    function kill() public {
        if (msg.sender == ownerAddr && now > lastPlayed + 6 hours) {
            suicide(msg.sender);
        }
    }    function() public payable { }
}

It’s easy to tell that the shuffle() method sets secretNumber to 6. Hence, if you call play(6)and send it 0.001 ether, you will always win your ether plus whatever the balance of the contract is, namely 0.015 ether. Easy money, right? Wrong.

What’s the trick? Look closely at how play() is implemented. It declares a variable Game game, but does not initialize it. It will therefore default to a pointer to slot zero of the contract’s storage space. Then, it stores your address in its first member, storage slot 0, and the submitted number in the second one, that maps to storage slot 1. So, in practice, this will end up overwriting the contract’s secretNumber with the attacker account’s address, and lastPlayed with the number submitted.

Then, it will compare secretNumber, which is now your account’s address, with the number you submitted. Since you can only submit numbers smaller than 10, you can only win if your account’s address is within the range 0x0 to 0x0a. (Don’t bother trying to bruteforce-search for one account in that small range! Simply unfeasible.)

So, the comparison will fail, and the contract will keep your ether. Of course, the attacker can at any time call kill() to retrieve the ether.

The not-really-insecure non-riddle

This is another fun one. It had me scratching my head for a while. However, there is a huge giveaway that the contract is up to something nasty right away. But let’s not get ahead of ourselves.

Here is its code. Can you spot the supposed vulnerability? And, can you tell why an exploit won’t work? And what is the giveaway I was talking about?

contract G_GAME
{
    function Play(string _response)
    external
    payable
    {
        require(msg.sender == tx.origin);
        if(responseHash == keccak256(_response) && msg.value>1 ether)
        {
            msg.sender.transfer(this.balance);
        }
    }
    
    string public question;
    address questionSender;
    bytes32 responseHash;
 
    function StartGame(string _question,string _response)
    public
    payable
    {
        if(responseHash==0x0)
        {
            responseHash = keccak256(_response);
            question = _question;
            questionSender = msg.sender;
        }
    }
    
    function StopGame()
    public
    payable
    {
       require(msg.sender==questionSender);
       msg.sender.transfer(this.balance);
    }
    
    function NewQuestion(string _question, bytes32 _responseHash)
    public
    payable
    {
        require(msg.sender==questionSender);
        question = _question;
        responseHash = _responseHash;
    }
    
    function() public payable{}
}

The code supposedly implements a riddle. It sets up a question, and, if you can tell what the answer is, it will presumably send you its balance, currently a little more than 1 ether. Of course, to produce an answer, you must send an ether first, which you will get back if you are correct. The code seems fine, but there is a dirty trick: notice how NewQuestion allows questionSender to submit a hash that does not match _question. So, as long as this function isn’t used, we should be alright.

Can we tell what the question and answer are? If you read the transaction history of the contract on etherscan, it appears that the 2nd transaction sets up the question. It’s even more obvious if you click the “Convert to UT8” button on etherscan. This reveals the question “I am very easy to get into,but it is hard to get out of me. What am I?”, and the answer “TroublE”.

Since this transaction is called, according to etherscan, after the creation of the contract, responseHash is going to be zero, and will become keccak265("TroublE"). Then, there is a third transaction that loads up one ether in the contract. So, apparently, we could call Play("TroublE") and send one ether to get two ether back. Too good to be true? Probably. Let’s make sure.

We can make sure we will the contract’s ether by inspecting the state of the smart contract. Its variables are not public, but still all it takes is just a few extra strokes to retrieve their values by querying the blockchain. questionSender and responseHash are the 2nd and 3rd variables, so they will occupy slots 1 and 2 on the storage space of the smart contract. Let’s retrieve their values.

web3.eth.getStorageAt(‘0x3caf97b4d97276d75185aaf1dcf3a2a8755afe27’, 1, console.log);

The result is `0x0..0765951ab946f3a6f0379680a6b05fb807d52ba09`. That spells trouble (pun intended) for an attacker, since the transaction setting up the question came from an account starting with0x21d2. Something’s up.

web3.eth.getStorageAt(‘0x3caf97b4d97276d75185aaf1dcf3a2a8755afe27’, 2, console.log);

The result is `0xc3fa7df9bf24…`. Is this the hash of “TroublE”?

web3.sha3('TroublE');

That call returns 0x92a930d5..., so it turns out that, if we were to call Play("TroublE") and send 1 ether, we’d actually lose it. But how is it possible that the hashes do not match?

Notice how StartGame does nothing if responseHash is already set. Clearly, that second transaction did not alter the state of the contract, so it must have already been set before this transaction. But how is it possible that responseHash was already initialized, if that was the first transaction after the creation of the contract?

After some serious head scratching, I found a recent interesting post on honeypot contracts that explains that Etherscan does not show transactions between contracts when msg.value is zero. Other blockchain explorers such as Etherchain do show them. Surely enough, etherchain reveals a couple additional transactions in the contract’s history, where a contract at 0x765951.. modifies responseHash via a zero-value transactions.

So let’s check these transactions; perhaps the ether can still be stolen? To track what happened, we need to decode these calls. We can get the contract’s ABI from Etherscan, and the internal transaction data from the “parity traces” of Etherchain (firstsecond). That’s all we need to decode the transactions into human readable format.

const abiDecoder = require('abi-decoder');
const Web3 = require('web3');
const web3 = new Web3();const abi = [{“constant”:false,”inputs”:[{“name”:”_question”,”type”:”string”},{“name”:”_response”,”type”:”string”}],”name”:”StartGame”,”outputs”:[],”payable”:true,”stateMutability”:”payable”,”type”:”function”},{“constant”:false,”inputs”:[{“name”:”_question”,”type”:”string”},{“name”:”_responseHash”,”type”:”bytes32"}],”name”:”NewQuestion”,”outputs”:[],”payable”:true,”stateMutability”:”payable”,”type”:”function”},{“constant”:true,”inputs”:[],”name”:”question”,”outputs”:[{“name”:””,”type”:”string”}],”payable”:false,”stateMutability”:”view”,”type”:”function”},{“constant”:false,”inputs”:[{“name”:”_response”,”type”:”string”}],”name”:”Play”,”outputs”:[],”payable”:true,”stateMutability”:”payable”,”type”:”function”},{“constant”:false,”inputs”:[],”name”:”StopGame”,”outputs”:[],”payable”:true,”stateMutability”:”payable”,”type”:”function”},{“payable”:true,”stateMutability”:”payable”,”type”:”fallback”}];const data1 = '0x1f1c827f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000464920616d2076657279206561737920746f2067657420696e746f2c627574206974206973206861726420746f20676574206f7574206f66206d652e205768617420616d20493f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000754726f75626c4500000000000000000000000000000000000000000000000000';const data2 = '0x3e3ee8590000000000000000000000000000000000000000000000000000000000000040c3fa7df9bf247d144f6933776e672e599a5ed406cd0a15a9f2da09055b8f906700000000000000000000000000000000000000000000000000000000000000464920616d2076657279206561737920746f2067657420696e746f2c627574206974206973206861726420746f20676574206f7574206f66206d652e205768617420616d20493f0000000000000000000000000000000000000000000000000000';abiDecoder.addABI(abi);
console.log(abiDecoder.decodeMethod(data1));
console.log(abiDecoder.decodeMethod(data2));

Running this code, we get the following result:

{ name: ‘StartGame’,
  params: [ { name: ‘_question’,
              value: ‘I am very easy to get into,but it is hard to get out of me. What am I?’,
              type: ‘string’ },
            { name: ‘_response’,
              value: ‘TroublE’,
              type: ‘string’ }
  ]
}
{ name: ‘NewQuestion’,
  params: [ { name: ‘_question’,
              value: ‘I am very easy to get into,but it is hard to get out of me. What am I?’,
              type: ‘string’ },
            { name: ‘_responseHash’,
              value: ‘0xc3fa7df9bf247d144f6933776e672e599a5ed406cd0a15a9f2da09055b8f9067’,
              type: ‘bytes32’ }
  ]
}

We learn that the first transaction sets the answer to keccak256("TroublE"), but the second one sets the answer to a hash value for which we don’t know the original data! Again it’s quite easy to miss that the second call does not use _question to compute the hash; instead, it’s set to an arbitrary value that does not match the string provided in the previous call, although the question does match.

So, unless we can find out a value that produces the given hash, possibly via a dictionary attack or a bruteforce search, we’re out of luck. And, given how sophisticated this honeypot is, I would assume trying to bruteforce the hash is not going to work out very well for us.

Unraveling this honeypot took quite some effort. Its creator is ultimately counting on attackers trusting the etherscan data, which does not contain the full picture.

The giveaway

I said this contract contains a dead giveaway that its creator is playing tricks. This is in this line:

require(msg.sender == tx.origin);

What this line achieves is, it prevents contracts from calling Play. This is because tx.origin is always an “external account”, and never a smart contract. Why is this useful for the attacker? A way to safely attack a contract is to call them from an “attack contract” that reverts execution if it didn’t gain ether from attack:

function attack() {
    uint intialBalance = this.balance;
    attack_contract();
    require (this.balance > initialBalance);
}

This way, unless the attacker’s contract’s balance increases, the transaction fails altogether. The creator of the honeypot wants to prevent an attacker from using this trick to protect themselves.


Literature:


Conclusion

Honeypots are a moral grey area for me. Is it OK to scam those who are looking to steal from contracts? I don’t think so. But I do not feel very strongly about this. In the end, if you got scammed, it is because you were searching for smart contracts to steal from to begin with.

These scams play on the greed of people who are smart enough to figure out an apparent vulnerability in a contract, yet not knowledgeable enough to figure out what the underlying trap is.

If you want to get deeper into Smart Contract security, check this amazing wargame called Capture the Ether. It’s a fun way to hone your skills and train your eye for suspicious Solidity code.


GitHub

Telegram: https://t.me/cryptodeeptech

Video: https://youtu.be/UrkOGyuuepE

Source: https://cryptodeep.ru/solidity-vulnerable-honeypots


Феномен от Blockchain Криптовалют // Уязвимые приманки Solidity

Навигация по записям

 

0 Комментариев


Рекомендуемые комментарии

Комментариев нет

Для публикации сообщений создайте учётную запись или авторизуйтесь

Вы должны быть пользователем, чтобы оставить комментарий

Создать учетную запись

Зарегистрируйте новую учётную запись в нашем сообществе. Это очень просто!

Регистрация нового пользователя

Войти

Уже есть аккаунт? Войти в систему.

Войти
  • Последние посетители   0 пользователей онлайн

    • Ни одного зарегистрированного пользователя не просматривает данную страницу
×
×
  • Создать...