EthScope: A Transaction-centric Security Analytics Framework to Detect Malicious Smart Contracts on Ethereum

05/17/2020 ∙ by Lei Wu, et al. ∙ City University of Hong Kong Florida State University Zhejiang University 0

As one of the representative blockchain platforms, Ethereum has attracted lots of attacks. Due to the potential financial loss, there is a pressing need to detect malicious smart contracts and understand their behaviors. Though there exist multiple systems for smart contract analysis, they cannot efficiently analyze a large number of transactions and re-execute smart contracts to introspect malicious behaviors. In this paper, we urge for a transaction-centric security analytics framework for Ethereum, which provides an efficient way to quickly locate suspicious ones from a large number of transactions and extensible way to detect malicious smart contracts with analyst-provided scripts. We present the system design in the paper, which solves three technical challenges, i.e., incomplete states, scalability and extensibility. We have implemented a prototype system named EthScope to solve these challenges. In particular, the first component Data Aggregator collects and recovers critical blockchain states. The second component Replay Engine is able to replay arbitrary and a large number of transactions. The third component Instrumentation Framework exposes interfaces for an analyst to dynamically instrument smart contracts and introspect the execution of suspicious transactions. The comprehensive evaluation with six types of attacks demonstrated the effectiveness of our system. The performance evaluation shows that our system can perform a large-scale analysis on suspicious transactions (more than 8 million ones) and has a speed up of around 2,300x compared with the JSTracer provided by Go-Ethereum. To engage the community, we will release our system and a dataset of detected attacks on https://github.com/zjuicsr/ethscope.

READ FULL TEXT VIEW PDF
POST COMMENT

Comments

There are no comments yet.

Authors

page 11

page 22

This week in AI

Get the week's most popular data science and artificial intelligence research sent straight to your inbox every Saturday.

1. Introduction

With an explosive growth of the blockchain technique, Ethereum (13) has become one of representative platforms. One reason is possibly due to its inborn support of smart contracts. Developers use smart contracts to build DApps (decentralized applications), ranging from gaming, lottery, decentralized finance (DeFi), and cryptocurrency, e.g., ERC20 tokens (11).

At the same time, attacks targeting Ethereum are increasing. By exploiting vulnerabilities of smart contracts, attackers could make huge profits in a short time. For instance, in April 2016, attackers exploited the reentrancy vulnerability in the DAO smart contract and stole around 3.6 million Ether (Siegel, 2016). Attackers used the similar vulnerability to attack the decentralized exchange Uniswap (50) (July 2019) and DeFi (Decentralized Finance) application Lend.Me (49) (April, 2020). Besides, lots of other types of attacks have been observed in the wild (30; 34; 32).

Due to the huge financial loss, there is a pressing need to detect malicious smart contracts and understand their behaviors, which is crucial for effective solutions to mitigate the threat.

Existing approaches   Existing approaches are mainly analyzing smart contracts to detect vulnerable ones. However, interactions between multiple smart contracts during real attacks make them ineffective to detect malicious ones. Specifically, a real attack usually requires the involvement of more than one smart contract. They interact with each other using internal transactions, which are similar to remote procedure calls in traditional computer programs. To detect malicious smart contracts, a system should have the capability to analyze their interactions (transactions) and re-execute (replay) smart contracts to observe malicious behaviors.

Specifically, existing systems roughly fall into two categories. The first category includes systems that mainly focus on the analysis of smart contract code, instead of transactions. They use static and dynamic methods (Nikolić et al., 2018; Chen et al., 2018b; Brent et al., 2018; Torres et al., 2018; Luu et al., 2016), the fuzzing testing (Jiang et al., 2018; He et al., 2019; Wüstholz and Christakis, 2019; Nguyen et al., 2020) and the formal verification (So et al., 2020; Permenev et al., 2020; Tsankov et al., 2018; Kalra et al., 2018) to analyze smart contracts and asses the security of them. Such approaches only provide a static view of smart contract code, i.e., whether they are vulnerable or not. They cannot provide a dynamic view of contract interactions (or transactions). The second category includes systems (Rodler et al., 2019; Grossman et al., 2017; Ferreira Torres et al., 2019; Chen et al., 2020) that hook into the Ethereum Virtual Machine (EVM) at runtime to detect malicious behaviors. However, they either need to re-execute all historical transactions (a time-consuming process) or leverage the JavaScript-based transaction tracing (JSTracer) (26) of the popular Ethereum client Geth (20) to monitor smart contract execution. This tracing mechanism is inefficient due to its frequent context switches between the EVM and the tracer (see the comparison result in Table 6). As a result, they suffer from the scalability issue when performing a large-scale analysis. For instance, we need to replay more than millions transactions to detect the bad randomness attack (Section 5.3).

Transaction-centric approach   We urge for a transaction-centric security analytics framework for Ethereum, which provides an efficient way to quickly locate suspicious ones from a large number of transactions and an extensible way to detect malicious smart contracts with analyst-provided scripts.

Specifically, instead of solely analyzing smart contract code, an analyst first locates suspicious transactions from multiple perspectives. For instance, transactions that create a loop between smart contracts, and transactions interact with vulnerable contracts reported by existing systems (Nikolić et al., 2018; Torres et al., 2018; Luu et al., 2016; Tsankov et al., 2018; Kalra et al., 2018) are suspicious. After that, suspicious transactions are re-executed (or replayed) using a replay engine (a customized Ethereum Virtual Machine) to monitor the execution of smart contracts. Analyst-provided scripts will be invoked at certain places (instrumentation points in this paper) to spot malicious behaviors and detect malicious contracts.

Note that, our approach does not intend to replace existing systems (Nikolić et al., 2018; Chen et al., 2018b; Brent et al., 2018; Torres et al., 2018; Luu et al., 2016; Jiang et al., 2018; So et al., 2020; Permenev et al., 2020; Tsankov et al., 2018; Kalra et al., 2018), but complements them to provide capabilities to detect malicious smart contracts. In fact, the output of existing systems could be a hint to locate suspicious transactions. For instance, transactions interact with vulnerable contracts reported by them are suspicious.

Though the basic idea is straightforward, building such a framework faces several technical challenges.

  1. [leftmargin=*]

  2. Incomplete states. In order to quickly locate suspicious transactions, the framework should collect rich chain states and provide an efficient interface to look up them. Note that, some critical intermediate states (Section 2.3) are not stored in Ethereum nodes by default.

  3. Scalability. The framework should have the capability to efficiently replay a large number of and arbitrary transactions. Replaying a transaction is not an easy task, since the execution depends on blockchain historical states. Existing approaches that re-execute all or partial transactions (Schmideg, 2019; Grossman et al., 2017; Rodler et al., 2019; Ferreira Torres et al., 2019; Chen et al., 2020) to recover historical states are not scalable.

  4. Extensibility. The framework should be extensible to detect different types of attacks by providing different scripts, without the need to change the framework itself. Moreover, the performance overhead should be optimized, e.g., reducing the number of context switches between analysis scripts and the framework.

We have designed a framework with three components to solve these challenges. Specifically, the first component, i.e., Data Aggregator, collects and recovers critical blockchain states, including internal transactions, self-destructed smart contracts, account balance of each block, and etc. These states are usually missed or incomplete in publicly available services (16; E. M. Allen Day (2018)) and research prototypes (Chen et al., 2019, 2018a). It also provides an efficient interface to quickly locate suspicious transactions.

The second component, i.e., Replay Engine, is able to efficiently replay arbitrary and a large number of transactions. First, our system takes snapshots of the historical states for each transaction. Thus it can replay arbitrary transactions without executing unnecessary ones to recover historical states. Note that, the recovery of historical states is a time-consuming process, which even makes the analysis impossible when the number of transactions is huge. Second, it parallelizes the replay process with multithreading, which speeds up this process.

The third component, i.e., Instrumentation Framework, exposes interfaces for an analyst to dynamically instrument smart contracts and introspect the execution of suspicious transactions. An analyst can develop analysis scripts (using the JavaScript language) to analyze suspicious transactions and detect malicious ones. Our framework reduces the performance overhead by a fine-grained design of instrumentation points and minimizes context switches between the EVM and the analysis script. Compared with the JSTracer (26) supported in Geth that will be triggered for each opcode, our framework is more flexible and efficient (Table 6). To ease the development of analysis scripts, our framework is equipped with a dynamic taint analysis engine that could be directly invoked with well-defined APIs.

We have implemented a prototype system named EthScope. In particular, Data Aggregator is implemented with around lines of changes to the Geth client. It uses the distributed search and analytics engine Elasticsearch (38) to store recovered blockchain states and provide an interface to query transactions. The Replay Engine and Instrumentation Framework are implemented with lines of changes to EVM.

To evaluate the effectiveness of our system, we use it to detect six types of attacks and report the result of two representative ones in the paper. Specifically, it successfully detected reentrancy attacks that have happened in the wild. Compared with other state-of-the-art tools, it can detect more vulnerable contracts that were previously unknown, while at the same time, has low false positive rate. We also present the result of detecting the bad randomness attack and understanding the attacker’s behaviors. The performance evaluation shows that our system is more efficient (around speed up) than existing ones when replaying transactions.

In summary, this paper makes the following main contributions:

  • [leftmargin=*]

  • We proposed a transaction-centric approach to detect malicious smart contracts on Ethereum. It first locates suspicious transactions and then re-executes them to confirm the malicious behaviors.

  • We implemented a prototype and illustrated methods to address three technical challenges, i.e., how to recover blockchain states and efficiently look up suspicious ones, how to replay arbitrary transactions, and how to provide extensible interfaces to instrument and introspect smart contract execution.

  • We evaluated our system to detect six types of attacks occurred in Ethereum and presented the result of two representative attacks. The performance evaluation shows that our system is much more efficient ( speed up) when replaying transactions.

To engage the community, we will released our system and a dataset of detected attacks on https://github.com/zjuicsr/ethscope.

2. Background

2.1. Ethereum Accounts

Each account in Ethereum has an address and associated balance in Ether. There exist two types of accounts, i.e., externally owned account (EOA) and smart contract account, respectively. EOAs are controlled by private keys, while smart contract accounts are controlled by their contract code (14). Note that, both accounts can have Ether and other tokens, thus are associated with balances 111A smart contract account can have balances may contradict one’s intuition..

The address of a new smart contract is calculated from the number of transactions being sent (nonce) and the address of its creator, which is the account that creates the smart contract. Due to this, the newly created contract address is predictable by its creator. We will illustrate an attack that exploits this property in Section 5.3.

Figure 1. Normal and internal transactions. N: normal transactions; I: internal transactions.

2.2. Transactions

A transaction is a type of message call that serves three purposes, i.e., transferring Ether, deploying a smart contract, and invoking functions of a smart contract. Transactions on the Ethereum are normally initiated from EOAs, hence the name normal transactions.

Besides, there exists another type of transactions that are initiated from a smart contract. They are called internal transactions, which are used to invoke functions inside another smart contract, or transfer Ether to other accounts. For instance, the opcode CALL can be used to invoke a function of another smart contract, thus creating an internal transaction.

Note that, an internal transaction is always initiated from a normal transaction, since the smart contract that creates an internal transaction should be executed in the first place (from an EOA using a normal transaction.) Moreover, a normal transaction could create a large number of internal transactions, if the invoked smart contract does so (invoking functions of other smart contracts.)

Figure 1 shows an overview of normal and internal transactions.

2.3. Ethereum States

Ethereum nodes are devices participating in validating transactions. After synchronizing from the network as a full node, we can retrieve states about the blockchain, e.g., the number of blocks and transactions, from the node. We call the full blockchain data available on the disk of a full Ethereum node as chain states.

Besides, there exists another type of blockchain states called intermediate states. They are generated during the execution and validation of transactions, including internal transactions and historical states of accounts (including EOAs and smart contract accounts.) Historical states include the time-serial data about the change of balance, nonce, storage and code of each account in each block. They are crucial when replaying transactions. For instance, when re-executing an internal transaction T in the block B that invokes a function F in the smart contract S, we need to know the historical states of S when T was packed and executed in block B on the chain.

Normally, a full Ethereum node only contains chain states. When synchronizing from the network, users can specify an option, e.g., --gcmode=archive in Geth, to retain intermediate states with the cost of storage space. After that, users can use debug.trace_transaction API to re-run a transaction (or re-execute the smart contract) in the exact same manner as it was executed on the network. However, this method is not efficient. We will discuss the way used in our system to improve the performance of the replay process in Section 4.2.

2.4. Smart Contracts

Ethereum virtual machine   A smart contract is a program that runs on an underlying Ethereum virtual machine (EVM for short) to transit the global state of the Ethereum network. A smart contract is usually programmed using a high level language, e.g., solidity, and then is compiled into low-level machine instructions (called opcodes), which will be fetched, decoded and executed by EVM.

EVM is a stack-based virtual machine. It has a virtual stack with elements. All computations are performed on the stack. It means the operands, the result of intermediate operations are stored on the stack. For instance, when executing the ADD opcode to add two operands, EVM will pop two values from the stack, add them together and then push the result on the stack.

Besides the stack, there are four other types of data locations in EVM, memory, storage, input field, and ret field. The memory, input data and ret field are used to store temporary data such as function arguments, local variables, and return values. They are volatile, which means their values will be lost when the execution of a smart contract is finished. In contrast, the storage is a (per-account) persistent key-value store. It is used to maintain state variables of a smart contract in each block. For instance, a gaming smart contract could leverage the storage to maintain the balance of each player in each block.

Function invocation   As discussed in Section 2.2, internal transactions are used to invoke smart contract functions. This is achieved through executing a message call (15) launched by six opcodes, including CALL, CALLCODE, DELEGATECALL, STATICCALL, CREATE and CREATE2.

In a smart contract, there is a signature (four bytes) to denote the destination function that will be invoked. The signature is defined as the first four bytes of the hash value (SHA3) of the canonical representation of the function, including the function name and the parenthesized list of parameter types. Since this is a one-way function, it is hard to retrieve the function name from the signature. However, there is an online service (51) that we can lookup the function name given a signature.

Smart contract creation and destruction   A smart contract could be created using two opcodes, i.e., CREATE and CREATE2. Both opcodes behave similar, except the way to calculate the address of the newly created smart contract (10).

A smart contract could be self-destructed through the opcode SELFDESTRUCT. This opcode destroys the smart contract itself, and transfers all the Ether inside the contract to the address specified in the parameters of this opcode (the target address). However, if the account with the target address does not exist, this opcode will create a new account with this address. This means that the SELFDESTRUCT opcode could implicitly create a new account.

3. System Design

In the following, we will illustrate technical challenges and then present the overall design of EthScope.

3.1. Technical Challenges

The basic idea of transaction-centric analysis is to first locate suspicious transactions and then re-execute the transactions to detect malicious smart contracts. Though the idea is straightforward, implementing such a system faces several technical challenges.

Chain
States
Internal
Transactions
Historical
States
Flexible
Interface
Ethereum full node
Ethereum archive node(12)
Etherscan
Ethereum in BigQuery(21)
Our system
Table 1. States that could be retrieved. : not supported; ✓: fully supported; : partially supported.

Incomplete chain states and intermediate states.   Our system needs to provide an interface to look up suspicious transactions. Though there exist many methods that could be leveraged to explore normal transactions, few of them could be used for look up internal ones. For instance, users could send RPC requests to the Ethereum node to retrieve information about a normal transaction. However, internal transactions are not available on Ethereum nodes (both full and archive nodes.) Besides, historical states are crucial to replay a transaction. That’s because they provide the execution context at the time when the transaction was executed on the chain.

Moreover, our system should provide an efficient and flexible interface to query a large number of transactions and historical states. Unfortunately, no existing methods could satisfy our requirement. For instance, the most popular service that allows users to explore and search transactions is Etherscan (16). But the network latency and the limit on the number of transactions could be queried (Etherscan only shows the latest normal transactions for each account) make it ineffective. Table 1 shows a comparison of states that could be retrieved through each method.

Capability to replay arbitrary and a larger number of transactions.   After locating suspicious transactions, our system re-executes them to detect malicious smart contracts. There exist two different types of methods to replay transactions, both of which suffer from the scalability issue.

The first one is to import the whole blockchain data with a customized EVM, which will execute all transactions (normal and internal ones) from the genesis block (the first block on the chain). Representative tools include ECFChecker (Grossman et al., 2017), Sereum (Rodler et al., 2019), AEGIS (Ferreira Torres et al., 2019) and SODA (Chen et al., 2020). This method cannot selectively replay interested transactions. Thus, many unrelated ones have been executed, consuming lots of time. For example, according to the official statistics (19), a full block importing costs with i3.2xlarge AWS EC2 instances.

The second way is to use the debug.trace_transaction API exposed by Geth, which allows users to replay a transaction with the Ethereum archive node. Though this method is more efficient than the previous one, it still suffers from the scalability issue. That’s because the granularity of historical states maintained by the Ethereum archive node is a block rather than a transaction. In order to replay a transaction, all the (unnecessary) transactions before this one inside the same block will be executed. Our system solves this challenge with a transaction-level granularity of historical states.

Extensibility with customized analysis scripts to detect different attacks.   Our system should be extensible to detect multiple attacks with analyst-provided scripts without changing the underlying framework. Existing systems, e.g., ECFChecker, Sereum, AEGIS and SODA, modify the EVM to perform detection when importing the blockchain data. Due to the tight coupling between these systems and the importing process of blockchain data, they cannot be applied to selected transactions to detect attacks.

Geth has an mechanism called JSTracer (26) to introspect the execution of a smart contract. It allows users to specify a JavaScript file that will be invoked for every opcode executed. However, frequent switches between the EVM and the JavaScript file make it impractical to analyze a large number of transactions. Our system solves this challenge with two optimizations. First, it has well-defined instrumentation points to minimize the number of context switches. The analysis script will be invoked on-demand (instead of each opcode) when defined instrumentation points are hit. Second, our framework is equipped with a dynamic taint analysis engine inside the EVM. Analysts do not need to implement their own taint engine using JavaScript files, which further reduces the number of context switches.

Figure 2. The overall design of EthScope.

3.2. Overall Design

We solve the challenges with three components, i.e., Data Aggregator, Replay Engine, and Instrumentation Framework. Figure 2 shows the overall system design.

Specifically, Data Aggregator imports the whole blockchain data and collects chain states and intermediate states. In particular, chain states could be directly retrieved by sending RPC requests to the Ethereum node, while intermediate states are collected by modifying the EVM. Collected states are stored in a cluster database equipped with a flexible query interface for further analysis. Note that, the process to import the blockchain data is a one-time effort. All the saves states could be queried and used without the need to import the chain data again.

The second component, i.e, Replay Engine, is used to replay arbitrary transactions. In particular, an analyst first locates suspicious transaction and feeds them the engine. The Replay Engine retrieves transactions that need to be analyzed and then obtains the historical states for each transaction. After that, it re-executes the transaction. This process is paralleled according to the number of transactions and number of CPU cores.

The third component, i.e., Instrumentation Framework, provides a mechanism to customize the analysis. Specifically, an analyst can write his or her own analysis scripts by defining callback functions for instrumentation points. For instance, a specific callback function could be defined and will be invoked if and only if the CALL opcode is executed. By doing so, our system avoids unnecessary context switches between EVM and the analysis script. During this process, EVM states, including related stack and memory values are provided to the script. Moreover, to facilitate the development of analysis scripts, a dynamic taint engine is provided by our system with well-defined APIs.

4. Implementation Details

We have implemented a prototype system named EthScope. In particular, Data Aggregator is implemented with around lines changes to the Geth client. Our system uses the distributed search and analytics engine Elasticsearch (38) to store recovered blockchain states and provide an interface to query transactions. The Replay Engine and Instrumentation Framework are implemented with lines changes to the EVM. In the following sections, we will elaborate the implementation details of each component.

4.1. Data Aggregator

In order to collect chain and intermediate states, we modified the Ethereum client Geth. The collected data is stored into a database, which provides a query interface for an analyst to locate suspicious transactions.

1    func (s *stateObject) setState(key, value common.Hash) {
2        s.dirtyStorage[key] = value
3        // capture data
4        s.db.JournalCapture.SetCSStorage(s.address, key, value)
5    }
Figure 3. The code snippet to capture intermediate states when executing the SSTORE opcode.

States collection   The process of chain states collection is straightforward. Our system changes the EVM to collect the data before the execution of each block (block information) and after the execution of each transaction (normal transactions).

Collecting intermediate states requires our system to hook into the process of executing smart contracts. For instance, when the opcode SSTORE is executed, the method setState in EVM is triggered. We change this method and add the code to collect the captured states. Figure 3 is a the code snippet in state_object.go. We added a class called JournalCapture to capture intermediate states. Note that, the states are not immediately stored into the underlying database. Instead, we create a buffer and write the states into the database when the buffer is full.

One issue is how to ensure the completeness and correctness of collected states. In our system, we solve this issue by comparing the collected states with ground truths. Specifically, for chain states, we can easily compare them with the data stored inside the Ethereum node. However, internal transactions are not available in the Ethereum node. To this end, we compare our data with the data provided by online services, i.e., Etherscan. Note that, this process is continuously performed for newly collected states.

Data organization and query interface   Our system leverages the Elasticsearch (38) to store the collected states. Table 8 in Appendix shows the detailed data schema.

Specifically, the Block index 222The index in Elasticsearch is similar to the database in a relational database. stores the information about a block and transactions inside the block. To quickly retrieve necessary historical states when replying a transaction, we group them into a nested field called Transaction. This transaction-level granularity of historical states makes the replay of arbitrary transactions efficient, since we do not need to execute unnecessary transactions insides the same block to recover the states. Besides Block, we have two extra indices State and Code to store the states about accounts and the information of the process to create and destruct accounts.

Thanks to the Elasticsearch, an analyst could leverage the Query DSL based on JSON to define queries (41) to locate suspicious transactions.

4.2. Replay Engine

In order to monitor the behaviors of smart contracts and capture the interactions between them, we build an engine that is capable of replaying arbitrary transactions on blockchain. Our engine is based on Geth, with modifications to prune unnecessary components and add support to retrieve states from Data Aggregator. Moreover, it provides interfaces to communicate with Instrumentation Framework, which we will discuss in Section 4.3.

Group transactions   The input to Replay Engine is a list of hash values for the suspicious transactions. In order to speed up the process of obtaining related data from Data Aggregator, our system divides transactions into different groups, with a threshold that each group contains no more than transactions. This threshold is related to the size of the system memory. During this process, transactions belong to the same block are inside the same group. After that, Replay Engine replays each transaction, which consists of two steps, i.e., preparing historical states, and executing the transaction.

Retrieve historical states   In order to replay a transaction, we need to retrieve historical states from Data Aggregator. First, we need to get the block and transaction information such as Difficulty and GasLimit from the Block index. Second, we need to retrieve the code of smart contracts that are related to this transaction in the nested field GetCodeList inside the field Transactions. That’s because a normal transactions could initialize internal transactions with multiple smart contracts. We need to retrieve the code for all the contracts. Third, we obtain all account states, e.g., nonce, balance and storage values of accounts that the transaction will read from the the nested field ReadCommittedState. Note that, the retrieved data constructs a snapshot of blockchain states that the transaction needs to be re-executed.

However, for some transactions, our system needs extra states. One case is when a transaction is to create a new smart contract. In this case, we need to retrieve the deploying code of the new smart contract from the index Code. Table 8 in Appendix shows the detailed information for the mentioned fields and indices.

Execute the transaction   After retrieving historical states, Replay Engine executes the transaction. During this process, callback functions defined in the analysis script will be invoked.

In order to speed up the process of executing transactions, our system further divides transactions into different groups according to the number of CPU cores, and executes transactions inside different groups in parallel. This optimization can achieve a speed up (Table 7).

4.3. Instrumentation Framework

Instrumentation Framework aims to provide extensible APIs for an analyst to develop scripts to detect attacks. Besides, Instrumentation Framework provides a dynamic taint engine to facilitate the analysis.

Overview   The framework is hooked into the Replay Engine and provides JavaScript interfaces to interact with the Replay Engine. Our system uses the Duktape JavaScript engine bindings for Go (9) to execute JavaScript functions inside the EVM (developed using the Go language.) Specifically, it defines instrumentation points, where the replay will be suspended and user-defined callback functions (in JavaScript) will be invoked. At the same time, it provides the interfaces for analysis scripts to access the current execution context, e.g., stack values, memory values. When the callback function ends, the Replay Engine continues the execution of the smart contract from the program counter after the instrumentation point.

Note that, user-defined callback functions can only observe the execution context. They cannot make any changes to the stack and memory values. Otherwise, the execution after the instrumentation point could be broken, since important states of the EVM may have been corrupted. However, our framework does support the explicit change of a control flow through an API named cfg.hijack() (Table  3). This is useful when we want to perform force-execution (Peng et al., 2014), i.e., deliberately executing a program path.

Instrumentation Points Type Description
{op}
after{Op}
O
before and after the opcode {op}
is executed
transactionStart
transactionEnd
T
before and after an external
transaction is executed
contractStart
contractEnd
C
before and after a new contract
is executed
Table 2. Three types of instrumentation points supported in our system. O: opcode-orientated; T: transaction-orientated; C: context-orientated.

Instrumentation points   Our system supports three types of instrumentation points, i.e., opcode-, transaction- and contract-oriented ones. Table 2 shows an overview of these instrumentation points.

First, the opcode-oriented instrumentation point links with two callback functions for each opcode, {op} and after{Op}. They are launched before and after executing the opcode {op}.

Second, transaction-oriented callbacks, transactionStart and transactionEnd, are launched before and after a transaction is executed. These two instrumentation points are usually used for the initialization and processing result in an analysis script. Note that, this type of instrumentation points only works for normal transactions, which are initialized from EOAs. For internal transactions that are initialized from smart contracts, they are covered in the contract-oriented instrumentation point.

Third, the contract-oriented callback functions, contractStart and contractEnd, deal with function calls crossing smart contracts (internal transactions.) These two functions are invoked at the start and at the end of the execution of a smart contract function.

Figure 4. The sequence of invoking callback functions at different types of instrumentation points. The code of the smart contract is for illustration only. O: opcode-oriented; T: transaction-oriented; C: contract-oriented.

Figure 4 shows the sequence of invoking callback functions at different instrumentation points. When an EOA issues a normal transaction, transactionStart will be invoked first, and then contractStart is executed. That’s because the normal transaction initializes the execution of smart contract A. Then the callback functions for each opcode are launched, until the CALL opcode. This opcode invokes the function inside the smart contract B and creates an internal transaction. Since the smart contract B is executed, contractStart will be invoked again. After that, callback functions for different opcodes will be invoked accordingly.

Note that, the context is switched from the EVM to the Duktape JavaScript engine to execute the analysis script, only when the callback function is defined and the instrument point is hit at runtime. This minimizes the number of context switches between EVM and Duktape. Compared with the JSTracer inside the EVM, our implementation is more efficient.

Execution contexts   When a callback function is invoked, it can observe the execution context through APIs provided by our system. However, there exist multiple ways that an internal transaction could be triggered, which makes the execution context more complicated.

Specifically, there are six different opcodes that will trigger an internal transaction to execute a new smart contract function, including CALL, STATICCALL, CALLCODE, DELEGATECALL, CREATE and CREATE2. When the EVM triggers an internal transaction, it will construct a new execution context with the callee address. It also sets two critical variables, i.e., self and CodeAddr to indicate context contract and code contract 333We follow the names used by Geth., respectively. The CodeAddr is always set to the address of the callee contract. However, when the internal transaction is triggered through executing DELEGATECALL and CALLCODE, EVM will set the self to the address of the caller contract. Otherwise EVM will set the self to the callee address.

Figure 5. Contexts of the SLOAD and SSTORE opcodes are different when internal transactions are initialized through the CALL and DELEGATECALL opcodes.

Accordingly, opcodes SLOAD and SSTORE interact with the storage of the contract that self points to. Opcodes CODECOPY and CODESIZE get information about the code of the contract that CodeAddr points to. Figure 5 illustrates this process.

APIs to retrieve execution context
op.getN() stack.length() memory.slice(start, end) contract.getSelfAddress() getBlockNumber() getPc()
op.toNumber() stack.peek(n) memory.getUint(offset) contract.getCodeAddress() getTxnIndex() getGas()
op.toString() contract.getValue() getTxnHash() getDepth()
contract.getInput() getReturnData()
Other APIs
cfg.hijack(isJump) params.get(key)
APIs to assign, clear and check taint tags
labelStack(n,tag) labelMemory(offset,size,tag) labelInput(o,s,t) labelReturnData(o,s,t) labelStorage(addr,slot,tag)
clearStack(n) clearMemory(offset,size) clearInput(o,s) clearReturnData(o,s) clearStorage(addr,slot)
peekStack(n) peekMemory(offset) peekInput(o) peekReturnData(o) peekStorage(addr,slot)
peekMemorySlice(offset,size) peekInputSlice(o,s) peekReturnDataSlice(o,s)
Table 3. APIs provided by our instrumentation framework.

APIs to retrieve the execution context   Our system provides multiple APIs to get the information of current execution context. Table 3 shows an overview of these APIs. We elaborate some of them in the following.

  • [leftmargin=*]

  • Normal transactions. Attributes of normal transactions are obtained by invoking getBlockNumber, getTxnIndex and getTxnHash. These attributes are used to distinguish different normal transactions.

  • Internal transactions. Two APIs contract.getSelfAddress and contract.getCodeAddress are used to retrieve the context contract and code contract. The API contract.getValue returns the amount of Ether that is transferred into the code contract. Every time an internal transaction starts, the EVM stack depth will increase by one. On the contrary, every time an internal transaction ends, it will decrease by one. The API getDepth is provided to get current EVM stack depth. By using this information, we can detect the occurrence of a recursive function call.

  • Parameters and return values. The API contract.getInput returns the input data (parameters) when invoking a function, while getReturnData obtains return values of a function call. Our system do not provide APIs to access storage variables, since only SLOAD and SSTORE can operate on storage variables. Analysts can easily observe their values by defining callback functions of these two opcodes.

  • The program counter and remaining gas. APIs getPc and getGas return the current program counter and remaining gas.

1{
2    sload: function(log){
3        contextContract = toHex(log.contract.getSelfAddress())
4        key = log.stack.peek(0).toString(16)
5        tag = contract+"_"+key
6        log.taint.labelStack(0, tag)
7    },
8
9    jumpi: function(log) {
10        tags = log.taint.peekStack(1)
11        for (tag in tags) {
12            contextContract = tag.substring(0, tag.indexOf("_"))
13            key = tag.substring(tag.indexOf("_"))
14            console.log("Storage", key, "in contract", contextContract, "influenced the control flow.")
15        }
16    }
17}
Figure 6. An example of how to use the dynamic taint engine to assign and check taint tags.

Dynamic taint engine   Dynamic taint analysis has been widely used for security applications. Our framework implements a dynamic taint engine that facilitates the development of analysis scripts. In our implementation, the granularity of taint tags for stack, storage is 32 bytes. That’s because the size of each entry in stack and storage is 32 bytes. However, our system assign each taint tag for each byte in the memory.

Our taint analysis engine supports the taint tag prorogation crossing different smart contracts. When the EVM triggers an internal transaction, it will pass input values from the caller’s memory to the callee’s input field. When the invocation returns, the return values is put into the caller’s ret field. We propagate the taint tags in opcodes CALLDATALOAD, CALLDATACOPY, RETURNDATACOPY that operate on stack, memory, ret and input field.

Table 3 shows APIs to assign, clear and check taint tags. APIs label* and clear* allow analysts to assign and clear tags. APIs peek* allow analysts to check taint tags. Figure 6 shows an example of how to use these APIs. Specifically, two callback functions sload and jumpi are invoked before executing opcodes SLOAD and JUMPI, respectively. Inside the callback function sload, it assigns the taint tag to the value on the top of the stack (index 0) using log.taint.labelStack(0, tag). Then the taint engine will propagate the tag, even crossing different contracts. When the callback function jumpi is executed, the log.taint.peekStack(1) checks whether the second value on the stack (index 1) has the taint tag. Note that, the JUMPI instruction checks the second value on the stack and changes the program counter if the value is true. Thus, by checking the taint tag, analysts can understand which storage variables can influence the control flow.

5. Evaluation

In the following, we will first evaluate the effectiveness of our system by detecting multiple types of attacks, and then report the system’s performance.

# of Blocks # of Transactions # of Accounts
NOR EOA:
INT: CON:
Attacks
# of Suspicious
Transactions
# of Malicious
Transactions
# of
Victims
# of
Attackers
Short address NOR: [width=2.30cm, height=0.65cm] EOA:
attack INT: CON:
ERC20 Token NOR: [width=2.30cm, height=0.65cm] EOA:
AirDrop INT: CON:
DoS Attack I: NOR: [width=1.80cm, height=0.55cm] EOA:
EXTCODESIZE INT: CON:
DoS Attack II: NOR: [width=1.80cm, height=0.55cm] EOA:
SUICIDE INT: CON:
Integer NOR: EOA:
overflow INT: CON:
Reentrancy NOR: EOA:
INT: CON:
Bad NOR: EOA:
Randomness INT: CON:
Table 4. The overall results of attacks detected by our system. The evaluation is performed with the Data Aggregator that has blocks. The table shows the number of suspicious transactions after querying the Data Aggregator and malicious transactions after verifying by the Replay Engine. We also show the number of victim accounts that have been exploited and attackers’ accounts. (NOR: normal transaction. INT: internal transactions. EOA: externally owned account. CON:contract account.)

5.1. Overall Results

We have leveraged our system to detect six types attacks, including the short address attack (44), abuse the AirDrop mechanism to obtain bonus, DoS attacks using the EXTCODESIZE and SUICIDE opcodes (Chen et al., 2017), integer overflow (30; 34; 35; 33; 31), the reentrancy attack and bad randomness attack.

Table 4 shows the overall results of attacks detected by our system. We report the number of suspicious transactions and malicious ones after querying to the Data Aggregator and verifying by the Replay Engine. We also show the number of victim accounts and attacker’s accounts in the table. Due to the page limit, we will report the detection result of two representative attacks, i.e., reentrancy and bad randomness, in this paper.

5.2. Reentrancy Attack

Understand the attack   The reentrancy attack is well-known on Ethereum. For instance, attackers leveraged the reentrancy vulnerability to launch the attack towards the DAO smart contract and stole million Ether (Siegel, 2016).

The root cause of the attack is developers do not have a full understanding of the fallback function (17) used in the Solidity language. In particular, a fallback function does not have any arguments. It can be reached externally and does not return anything. This function will be implicitly invoked in multiple cases, e.g., when a contract receives Ether without any other data. As a result, the fallback function of a malicious smart contract could create a reentrant call to vulnerable smart contracts. As a result, Ether will be transferred to attacker-controlled smart contracts before the remaining balance is updated.

To detect the reentrancy attack, we need to first understand it and then locate suspicious transactions. To this end, we leverage a known malicious transaction that exploited the vulnerability and constructed a dynamic call graph to illustrate the attack. In particular, our Data Aggregator has the full information of internal transactions. We traverse all the internal transactions triggered by a known malicious normal transaction between smart contracts to construct the call graph.

Figure 7. The dynamic call graph of an normal transaction that exploits the reentrancy vulnerability of the DAO smart contract. We use four lines to describe an internal transaction: 1. Serial number and the opcode to trigger an internal transaction; 2. Transferred Ether, null means no Ether transferred; 3. Invoked function (we search the name from the 4byte function signature database (51)), null means that the input data is empty; 4. EVM stack depth. (EOA: rectangle; smart contract: ellipse. Attacker: gray; Victim: white.)

Figure 7 shows the dynamic call graph. In the graph, nodes represent accounts, while edge are transactions. The serial numbers of transaction are in the chronological order. The 0th transaction is a normal transaction, and others are internal transactions triggered by the normal transaction. For best illustration, we only use the first 20 internal transactions to draw the graph. The actual number of internal transactions triggered is .

By looking up this graph, we can find two distinct features of transactions that launches the attack. First, there should exist a loop in the graph. This is reasonable since the call to the fallback function that further invokes the vulnerable contracts will create a loop in the call graph. For instance, internal transactions , and create a loop that starts from and ends at the malicious contract (0xc0ee9db). Second, there should exist a special smart contract called reentry point, which is the smart contract that will be invoked again before its previous invocation completes. For instance, the DAO contract (0xbb9bc2) is a reentry point, since the EVM stack depths of internal transactions (index to ) are all bigger than internal transaction . That means before an invocation to the DAO contract (0xbb9bc2) (internal transaction ) returns, another invocation (internal transaction ) to the same contract happens.

Locate suspicious transactions   After understanding the attack, we use the following two rules to lookup suspicious transactions that may launch the reentrancy attack. In particular, we label a normal transaction as a suspicious one, when

  1. [leftmargin=*]

  2. Internal transactions triggered by this normal transaction create a loop that contains at least one reentry point. This detects the existences of reentrant function calls.

  3. There is at least one internal transaction that involves with the Ether or ERC20 token transfer. This rule is to remove transactions that do not cause any change to the Ether or REC20 tokens. They are not real attacks since no financial benefits are achieved during this process.

Detect attacks   We further replay suspicious transactions to detect attacks. During this process, an analysis script is invoked (Figure 14 in Appendix). Inspired by the four reentrancy patterns summarized in the Sereum paper (Rodler et al., 2019), our system first constructs a set of variables that could influence jump targets of the JUMPI opcode or values of transferred Ether. Thanks to the dynamic taint engine of our system, we can check whether a variable could influence the control flow by checking the taint tag of the second top value on the stack (taint.peekStack(1)). For each variable v in this set, we define the callback function for the SSTORE opcode to monitor whether the variable has been updated after the reentrant point. If so, we will label the normal transaction as malicious.

1  function doWithdraw(address from, address to, uint256 amount) internal {
2  // only use in emergencies!
3  // you can only get a little at a time.
4  // we will hodl the rest for you.
5
6  require(amount <= MAX_WITHDRAWAL);
7  require(balances[from] >= amount);
8  require(withdrawalCount[from] < 3);
9
10  balances[from] = balances[from].sub(amount);
11  // reentry point
12  to.call.value(amount)();
13  withdrawalCount[from] = withdrawalCount[from].add(1);
14  }
Figure 8. The code snippet of HODLWallet.

Detection result   As shown in Table 4, our system located suspicious (normal) transactions. After replaying them, our system detected malicious normal transactions and malicious internal transactions, initialed from EOAs and malicious smart contracts. Attackers are targeting victims, which are shown in Table 10 in Appendix.

We manually analyzed each detected attack. During the analysis, we only considers transactions that have caused financial loss as true positives (real attacks). Our analysis shows that there are false positives (marked with * in Table 10). We show the detailed analysis of one false positive in the following.

Our system reported one attack targeting HODLWallet. Figure 8 shows the code snippet of the doWithdraw function. Specifically, the variable withdrawalCount[from] in line influences the control flow. Also this variable is updated after the reentry point in line . Thus, our system detects this as a real reentrancy attack. However, the transaction does not cause any financial loss since the balance balances[from] has been updated in line 10 (before the reentry point.) This is a false positive, though technically it is still a reentrancy attack that targets withdrawalCount[from] instead of balances[from].

Comparison with state-of-the-art tools   There are multiple systems that aim to detect the reentrancy vulnerability. Though the main purpose of our system is to detect attacks, we can still compare our result of victim contracts with that reported by other tools. In this paper, we compare our result with two state-of-the-art tools, Sereum (Rodler et al., 2019) and SODA (Chen et al., 2020). Since the result of SODA has covered vulnerable contracts reported by Sereum, we will directly compare our system with SODA. For a fair comparison, if not specified, we only use the result before million blocks, which is same with SODA.

In particular, SODA reported vulnerable contracts, with false positives and true positives. Among false positives reported by SODA, only (HODLWallet) was also reported by our system. This means our system has lower false positives than SODA. For the true positives reported by SODA, we detected of them. Our further analysis shows that the remaining one (address is 0x59ABb8006B30D7357869760d21B4965475198d9D

) is actually not vulnerable, and should not be reported. This means the SODA system mis-classified this smart contract as a true positive.

1    function sell(bytes32 hash, uint amount) public {
2      ……
3      msg.sender.transfer(total);
4      // reentry point
5      ERC20(info.token).transfer(info.owner, tradeAmount);
6      info.fill = info.fill.add(total);
7      emit Trade(hash, msg.sender, info.token, tradeAmount, info.owner, total);
8    }
Figure 9. The code snippet of DecentralizedExchanges.
Contract Address Name Birth Block
1. 0x3e64b1a66f3aa6f0765b093540481aa690c2b9b7 Anonymous
2. 0x857a8d8aa8d83562f9118405335bd4a1fb523317 I_bank
3. 0xfdb27beadad89f8282c51f5e5a4e77c1f19d7220 CB_Bank
4. 0x4b23577c0672ab1e436097b7daceadb75e5721c6 Pg_Bank
5. 0xffcf45b540e6c9f094ae656d2e34ad11cdfdb187 Uniswap
6. 0x4122073496955adb48e9a1dfaf6e456631b595a1 Anonymous
7. 0x2d5df43d54ae164a912db8de092cf707b446f693 CA_BANK
8. 0xb3e396f500df265cdfde30ec6e80dbf99bee9e96 pg_bank
9. 0x83a3a9b3068911b55a3989df0e642f487d08e424 CA_BANK
10. 0x6e3c384480e71792948c29e9fc8d7b9c9d75ae8f p_bank
11. 0x55791ea128a7b7fc871272d9147435a3abb3d1eb Anonymous
12. 0x7c6220c9537946a0861d7e86f6423af526f41375 Anonymous
13. 0xac629878277bf6a2fc46857eac4d4dd17bfa330f Anonymous
14. 0xf5cff81d51e81596519ecf61830cb084037a2218 Anonymous
15. 0xdd71e35f680bb5adc77c6d1d9ef5793598e613dc Piggy_BanK
16. 0x0eee3e3828a45f7601d5f54bf49bb01d1a9df5ea Lend.Me
17. 0x3ac969b43affc4e0684dc52dc3072b109d0e348d Bank
18. 0x8ce53575e1ce89131b370cbed602ce8cfa4f7805 Anonymous
19. 0x72f60eca0db6811274215694129661151f97982e
Decentralized-
Exchanges
20. 0xd4cd7c881f5ceece4917d856ce73f510d7d0769e
Decentralized-
Exchanges
Table 5. Vulnerable contracts detected by our system but missed in SODA. The last column shows the number of blocks when the contract was created.

There are vulnerable contracts reported by our system but are missed by SODA (Table 5). Among them, were created after million blocks. Attacks targeting smart contracts and happened after million blocks, though the contracts themselves were created before that time. For the remaining two ( and ), they are susceptible to the reentrancy vulnerability but are mis-classified by SODA as false positives. Specifically, both of these two contracts are called DecentralizedExchanges. The code snippet of the contract is shown in Figure 9. In particular, the code in line is a reentry point. The variable info.fill is the one checked before and updated after the reentry point.

5.3. Bad Randomness Attack

Understand the attack   Smart contracts need to use pseudorandom number in some cases. For instance, the popular game Fomo3D (24) has a marketing mechanism to attract players. It randomly sends bonus to game players, who have transferred Ether to the game (buy the key). This marketing mechanism is called AirDrop. In particular, it uses timestamp, difficulty, beneficiary address and gas limit of current block and caller’s (player’s) account address to determine whether the player could win the AirDrop bonus.

However, sources to determine winners are predictable. Attackers could use a smart contract to execute the same algorithm and use same random number sources to predict whether itself could win the bonus. If so, the contract will transfer a small number of Ether to play the game and become the winner of the AirDrop bonus (24).

Locate suspicious transactions   We use the following query to locate suspicious transactions. In particular, we find normal transactions that have triggered more than one internal transaction that transfers Ether. That’s because in order to launch the attack, attackers have to use a contract to transfer Ether to play the game, thus creating an internal transaction. Compared with the rules used in previous attack, it is a general one and will find a large number of transactions. This is intentional since we want to include as many suspicious transactions as possible in this step, and leverage the replay engine to confirm the attack.

Detect attacks   The key observation of this attack is that the attacking contract is running a same algorithm using same random number sources with the victim contract. The detection script is shown in Figure 15 in Appendix.

  1. [leftmargin=*]

  2. First, we find all the variables that are generated from block information, e.g., coinbase, gaslimit and etc. This is implemented using our taint analysis engine by setting the block information as taint sources.

  3. Second, for each variable v found in the previous step, we check whether it influences the control flow of the smart contract. That’s because we only care the variables that can determine the winner. If so, we log its execution context C.

  4. If there exist two same execution contexts in different internal transactions that are triggered by a same normal transaction, then the normal transaction is a malicious one that launches the attack. That’s because two smart contracts are executing a same algorithm from same random number sources to generate a variable that can influence the control flow to determine the winner.

Detection result   We replayed normal transactions with our analysis script. After that, normal transactions are labeled as malicious ones. During this process, malicious smart contracts are detected. We then group these malicious contracts based on their creators, i.e., EOAs that create these contracts. In total, we get groups that are shown in Table 11 in Appendix. We manually checked the malicious smart contracts created in each group and found that of them are true positives. In total, they have initialized normal transactions to attack victim smart contracts (after removing false positives.) Table 12 in Appendix shows the detailed information of victim contracts.

Figure 10. The money flow graph of the Fomo3D smart contract. For better illustration, we use transactions to generate this graph. The total number of transactions with the Fomo3D smart contract is much larger.

Case study: Fomo3D   To fully understand the attack to Fomo3D, we performed a detailed analysis using our system. Our first step is to construct the money flow graph. The intuition behind this is that the money will eventually flow into (successful) attackers. Figure 10 shows the money flow graph constructed using transactions retrieved from the Data Aggregator. Specifically, nodes in the graph represent accounts, and edges represent the direct and indirect transactions with the Fomo3D game. The size of each node denotes the number of Ether it receives.

Several accounts in the figure have a much larger size than others. These accounts have received much more Ether from the game than others. An initial analysis shows that three of them belong to Fomo3D (number 0, 1 and 6). We then take a further analysis on other accounts.

Figure 11. The dynamic call graph of a transaction from contract 0x94c0d029a7b64bf443e89c5006089364c0d60d61, a malicious contract that is attacking the Fomo3D game to get the Airdrop rewards.

Specifically, we analyzed transactions from the smart contract 0x94c0d029a7b64bf443e89c5006089364c0d60d61 to understand why the Ether in this smart contract accumulates. To this end, we first find an internal transaction initiated from this smart contract, which eventually receives Ether from the Fomo3D game. Then we construct the dynamic call graph (Figure 11). The call sequence of this graph shows that, the contract (0x94c0d029a) transfers Ether to 0xbe997116 (index 4), which further creates a new smart contract 0x73542fcd (index 6). This contract buys the key (index 9) with Ether and then received Ether (index 17) from the game. The received Ether is transferred to 0x94c0d029a with a SELFDESTRUCT operation (index 18). During this process, it obtains a profit of Ether. We queried the Data Aggregator for similar transactions from this smart contract. There are transaction, which earned Ether in total.

Our next analysis intends to understand why the Fomo3D game sends Ether to the malicious smart contract previously discussed. Since the transfer occurs after the invocation of buyXid (index 9), we manually analyzed its source code. The function eventually invokes the core function. This function increases the balance (plyr_[pID].win) of a player, if the return value of the call to airdrop function is true. The airdrop function calculates a random seed based on the information of the block, e.g., timestamp, difficulty, and the address of the caller smart contract (0x73542fcd, index 9). After that, it will compare the seed with another variable to determine the return value as true or false. According to the source code, this is a mechanism called AirDrop to randomly send bonus to players, if the return result of the airdrop function is true (code snippet is shown in Figure 13 in Appendix.).

Surprisingly, the contract 0x73542fcd is created by 0xbe997116, and its only transaction is to buy the key and get the bonus. We suspect that attackers have a mechanism to predict whether it can win the bonus before buying the key. Since the random seed depends on block information (which are fixed values for a block) and the address of the caller smart contract. If attackers can control an address of a smart contract, then they can predict the result of whether winning the bonus.

Accordingly to Section 2.1, the address of a newly created smart contract is calculated using the address of its creator and nonce, thus the attacker could predict the address of the smart contract it is going to create, and predict whether it will win the bonus.

Figure 12. The flow of the bad randomness attack.

Figure 12 shows the attack flow. There is a controller contract, which creates a lot of proxy contracts (more than ). Then during the attack, the controller attack loops through each proxy contract. It calculates the address of a newly created smart contract if it is created by the proxy contract (but does not actually create it.) Then it uses this address and the block information to predict whether it will get the bonus by executing a same logic with the airdrop) function in the Fomo3D contract. If so, the proxy smart contract creates the attacking contract, which further buys the key to play the game and win the bonus. After that, the attacking contract self-destructs itself to transfer the earned bonus to the controller smart contract.

5.4. Performance

In this section, we report the performance of our system. All the experiments were performed on a machine with four CPUs (Intel(R) Xeon(R) Silver 4110 CPU @ 2.10GHz) and 128G memory.

Data Aggregator   The purpose of Data Aggregator is to quickly locate suspicious transactions. In our experiment, our system takes around minutes to retrieve normal transactions used for the the reentrancy () and the bad randomness attack () from normal transactions.

Tools
Retrieve
Snapshot
Execute
Script
Other
JSTracer
Total
Our system
Total
Table 6. The comparison of JSTracer and our system to replay normal transactions.

Replay Engine and Instrumentation Framework   In the following, we will compare the performance of JSTracer supported by Geth and our system.

First, we randomly pick normal transactions that triggered internal transactions. Then, we develop an analysis script that has a same functionality with the example (1) (4byte_tracer.js) provided by Geth. Finally, we use the JSTracer and our system to replay normal transactions. Note that, a normal transaction could trigger multiple internal transaction. The total number of normal and internal transactions replayed is .

Table 6 shows the result. Our system takes only around seconds to replay the transactions, while JSTracer uses more than minutes (around speed up). We further explore the reasons.

  • [leftmargin=*]

  • Granularity of historical states   In order to retrieve snapshot of the normal transactions, JSTracer replayed additional normal transactions. However, EthScope can retrieve snapshot directly by querying fine-grained states from Data Aggregator.

  • Number of context switches   JSTracer needs to switch to the JavaScript environment for each opcode. However, our Instrumentation Framework only performs context-switch when instrumentation points are hit. Therefore, EthScope performed context switches, while JSTracer performed switches.

This results shows that our system successfully solves the scalability challenges and can replay a larger number of transactions. In fact, for the normal transactions used to detect the bad randomness attack, our system takes to replay them. Without the optimization, it’s nearly impossible to replay such as large number of transaction in JSTracer.

Group Group
Single thread
Multithreading
Table 7. Performance gain of using multithreading. Group 1: normal transactions that will trigger more than internal transactions; Group 2: simple normal transactions that will trigger only one internal transaction.

Our system parallelizes the replay process according to the number of transactions and CPU cores. We report the performance improvement due to this optimization in Table 7. During the experiment, we use two groups of normal transactions. The first group contains normal transactions. For each one, it will trigger more than internal transactions. This is for the scenario of multiple interactions between contracts. The second group contain the transactions that only trigger one internal transaction. This represents a simple function invocation. The result shows that by using multithreading, our system can have a speedup of around .

6. Discussion

False negatives   The purpose of our system is to detect malicious smart contracts and understand their behaviors. Analysts use Data Aggregator to quickly locate suspicious transactions, and then use the latter two components to confirm malicious behaviors at runtime. Compared with other static analysis tools, our system may have false negatives, which means it may miss some malicious smart contracts. However, it usually has low false positives. During the evaluation, we do find that it can locate some attacks that are missed in previous research. For instance, our system successfully detected real attacks to two vulnerable smart contracts (0x72f60eca0db6811274215694129661151f97982e and another one 0xd4cd7c881f5ceece4917d856ce73f510d7d0769e) that were previously unknown. Nevertheless, we can combine other static analysis tools. For instance, our system could replay transactions of the vulnerable smart contracts reported by other systems (Nikolić et al., 2018; Chen et al., 2018b; Brent et al., 2018; Torres et al., 2018; Luu et al., 2016; Jiang et al., 2018; So et al., 2020; Permenev et al., 2020; Tsankov et al., 2018; Kalra et al., 2018) to locate real attacks.

Usability

   Though we have demonstrated the effectiveness of our system, an analyst still needs the domain knowledge to first locate suspicious transactions. One potential direction is to use new techniques, e.g., machine learning algorithms to automatically locate suspicious ones. Currently, our system provides a dynamic taint engine to facilitate the analysis. In the future, we can integrate more components, e.g., dynamic symbolic execution, into the system to ease the development of analysis scripts.

7. Related Work

Data analysis frameworks of Ethereum   The proposed graph analysis method (Chen et al., 2018a) collects Ethereum states and then performs money flow analysis, account creation analysis and contract invocation analysis. DataEther (Chen et al., 2019) first instruments an Ethereum full node to collect data and then uses ElasticSearch (38) to store the collected data. Similar with our system, they can be used to locate suspicious transactions. However, they do not have the capability to introspect the execution of smart contracts to detect attacks.

Static analysis tools of smart contracts   Recent studies have focused on vulnerability detection, including Oyente (Luu et al., 2016), Mythril (29), Osiris (Torres et al., 2018), MAIAN (Nikolić et al., 2018), ContractFuzzer (Jiang et al., 2018), ILF Fuzzer (He et al., 2019), Securify (Tsankov et al., 2018) and ZEUS (Kalra et al., 2018). These systems only provide a static view of smart contract code, i.e., whether they are vulnerable or not. They cannot provide a dynamic view of contract interactions (or transactions). This limitation motivates the development of our system. Note that, our approach does not intend to replace them, but complements them to provide capabilities to detect malicious smart contracts. In fact, their outputs could provide hints to locate suspicious transactions, e.g., transactions interact with vulnerable contracts reported by these systems.

Dynamic analysis tools of smart contracts   Dynamic analysis has been regarded as an effective complement to the static analysis for security purposes. ECFChecker (Grossman et al., 2017), Sereum (Rodler et al., 2019) and SODA (Chen et al., 2020) are representative ones. Both Sereum (Rodler et al., 2019) and ECFChecker (Grossman et al., 2017) focus on the detection of the reentrancy attack. Our system is an extensible framework that could be leveraged to detect multiple types of attacks. SODA (Chen et al., 2020) can detect inappropriate behaviors. However, it is tightly coupled with the importing process of blockchain data, and cannot be used to analyze selected transactions to detect attacks.

8. Conclusion

In this paper, we design and implement a transaction-centric security analytics framework for malicious smart contracts detection. It provides an efficient way to locate suspicious transactions and an extensible way to detect malicious contracts with analyst-provided scripts. We solved multiple technical challenges to build such a framework. The evaluation result shows the effectiveness and efficiency of our system.

References