Security analysis framework for WebAssembly module (wasm) and Blockchain Smart Contract (BTC/ETH/EOS/NEO).
Project description
Octopus
Huge thanks to QuoScient for having sponsored this project.
Octopus is a security analysis framework for WebAssembly module and Blockchain Smart Contract.
The purpose of Octopus is to provide an easy way to analyze closed-source WebAssembly module and smart contracts bytecode to understand deeper their internal behaviours.
Features
- Explorer: Octopus JSON-RPC client implementation to communicate with blockchain platforms
- Disassembler: Octopus can translate bytecode into assembly representation
- Control Flow Analysis: Octopus can generate a Control Flow Graph (CFG)
- Call Flow Analysis: Octopus can generate a Call Flow Graph (function level)
- IR conversion (SSA): Octopus can simplify assembly into Static Single Assignment (SSA) representation
- Symbolic Execution: Octopus use symbolic execution to find new paths into a program
Platforms / Architectures
Octopus support the following types of programs/smart contracts:
- WebAssembly module (WASM)
- Bitcoin script (BTC script)
- Ethereum smart contracts (EVM bytecode & Ewasm)
- EOS smart contracts (WASM)
- NEO smart contracts (AVM bytecode)
BTC | ETH (EVM) | ETH (WASM) | EOS | NEO | WASM | ||
---|---|---|---|---|---|---|---|
Explorer | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :o: | |
Disassembler | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |
Control Flow Analysis | :heavy_multiplication_x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |
Call Flow Analysis | :heavy_multiplication_x: | :heavy_plus_sign: | :heavy_check_mark: | :heavy_check_mark: | :heavy_plus_sign: | :heavy_check_mark: | |
IR conversion (SSA) | :heavy_multiplication_x: | :heavy_check_mark: | :heavy_plus_sign: | :heavy_plus_sign: | :heavy_multiplication_x: | :heavy_check_mark: | |
Symbolic Execution | :heavy_multiplication_x: | :heavy_plus_sign: | :heavy_plus_sign: | :heavy_plus_sign: | :heavy_multiplication_x: | :heavy_plus_sign: |
- PyPI package :heavy_check_mark:
- Docker :heavy_check_mark:
:heavy_check_mark: DONE / :heavy_plus_sign: WIP / :heavy_multiplication_x: TODO / :o: N/A
Requirements
Octopus is supported on Linux (ideally Ubuntu 16.04) and requires Python >=3.5 (ideally 3.6).
Dependencies:
Quick Start
- Install system dependencies
# Install system dependencies
sudo apt-get update && sudo apt-get install python-pip graphviz xdg-utils -y
- Install Octopus:
# Download Octopus
git clone https://github.com/quoscient/octopus
cd octopus
# Install Octopus library/CLI and its dependencies
python3 setup.py install
or
# but prefer the first way to install if possible
pip3 install octopus
- Run tests
# Run tests for all platforms (disassembly, CFG, ...)
./run_tests.sh
# Run tests that require internet access (explorer tests)
./run_explorer_tests.sh
# Run tests for only one platforms
# {btc, eth, eos, neo, wasm}_run_tests.sh
cd octopus/tests/
./wasm_run_tests.sh
Docker container
A docker container providing the toolset is available at docker hub. In a terminal, run the following commands:
docker pull smartbugs/octopus
docker run -it smartbugs/octopus
cd octopus
python3 octopus_eth_evm.py -s -f examples/ETH/evm_bytecode/61EDCDf5bb737ADffE5043706e7C5bb1f1a56eEA.bytecode
Command-line tools
- WebAssembly: octopus_wasm.py
- Ethereum (EVM): octopus_eth_evm.py
In-depth Examples using APIs
WebAssembly
Disassembler
Disassembly of a Wasm module:
from octopus.arch.wasm.disassembler import WasmDisassembler
FILE = "examples/wasm/samples/helloworld.wasm"
with open(FILE, 'rb') as f:
module_bytecode = f.read()
disasm = WasmDisassembler()
# return list of functions instructions (list)
print(disasm.disassemble_module(module_bytecode))
#[[<octopus.arch.wasm.instruction.WasmInstruction at 0x7f85e4904278>,<octopus.arch.wasm.instruction.WasmInstruction at 0x7f85e4904f60>,<octopus.arch.wasm.instruction.WasmInstruction at 0x7f85e4904ef0>]]
print()
# return text of functions code
print(disasm.disassemble_module(module_bytecode, r_format='text'))
# func 0
# i32.const 0
# call 0
# end
Disassembly of wasm bytecode:
from octopus.arch.wasm.disassembler import WasmDisassembler
# bytecode in WebAssembly is the function code (i.e. function body)
bytecode = b'\x02\x7fA\x18\x10\x1cA\x00\x0f\x0b'
# create a WasmDisassembler object
disasm = WasmDisassembler(bytecode)
# disassemble bytecode into a list of WasmInstruction
# attributes r_format='list' by default
print(disasm.disassemble())
#[<octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4904eb8>, <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4904278>, <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4904390>, <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4904ef0>, <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4904f60>, <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4901048>]
print()
print(disasm.disassemble(r_format='reverse'))
#{0: <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4901048>, 1: <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4904240>, 2: <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4904f60>, 3: <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4904ef0>, 4: <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4904278>, 5: <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4904390>}
print()
print(disasm.disassemble(r_format='text'))
# block -1
# i32.const 24
# call 28
# i32.const 0
# return
# end
ModuleAnalyzer
from octopus.arch.wasm.analyzer import WasmModuleAnalyzer
FILE = "examples/wasm/samples/hello_wasm_studio.wasm"
with open(FILE, 'rb') as f:
module_bytecode = f.read()
# return list of functions instructions (list)
# attributes analysis=True by default
analyzer = WasmModuleAnalyzer(module_bytecode)
# show analyzer attributes
print(analyzer.func_prototypes)
# [('putc_js', 'i32', ''),
# ('__syscall0', 'i32', 'i32'),
# ('__syscall3', 'i32 i32 i32 i32', 'i32'),
# ('__syscall1', 'i32 i32', 'i32'),
# ('__syscall5', 'i32 i32 i32 i32 i32 i32', 'i32'),
# ('__syscall4', 'i32 i32 i32 i32 i32', 'i32'),
# ('$func6', '', ''),
# ('main', '', 'i32'),
# ('writev_c', 'i32 i32 i32', 'i32'),
# ('$func9', '', 'i32'),
# ('$func10', 'i32', 'i32'),
# ('$func11', 'i32', 'i32'),
# ('$func12', 'i32', ''),
# ('$func13', 'i32', 'i32'),
# ('$func14', 'i32 i32 i32 i32', 'i32'),
# ('$func15', 'i32 i32', 'i32'),
# ('$func16', 'i32 i32', 'i32'),
# ('$func17', 'i32', 'i32'),
# ('$func18', 'i32', 'i32'),
# ('$func19', 'i32', 'i32'),
# ('$func20', 'i32 i32 i32', 'i32'),
# ('$func21', 'i32 i32 i32', 'i32'),
# ('$func22', 'i32 i64 i32', 'i64'),
# ('$func23', 'i32 i32 i32', 'i32'),
# ('$func24', 'i32', 'i32'),
# ('$func25', 'i32 i32 i32 i32', '')]
print()
print(analyzer.contains_emscripten_syscalls())
#[('__syscall0', 'restart_syscall'),
# ('__syscall3', 'read'),
# ('__syscall1', 'exit'),
# ('__syscall5', 'open'),
# ('__syscall4', 'write')]
Control Flow Analysis
from octopus.arch.wasm.cfg import WasmCFG
# complete wasm module
file_name = "examples/wasm/samples/fib.wasm"
# read file
with open(file_name, 'rb') as f:
raw = f.read()
# create the cfg
cfg = WasmCFG(raw)
# visualize CFGGraph
# generate graph.dot and graph.pdf file
cfg.visualize()
Functions' instructions analytics
from octopus.arch.wasm.cfg import WasmCFG
# complete wasm module
file_name = "examples/wasm/samples/hello_wasm_studio.wasm"
# read file
with open(file_name, 'rb') as f:
raw = f.read()
# create the cfg
cfg = WasmCFG(raw)
# visualization
cfg.visualize_instrs_per_funcs()
Call Flow Analysis
from octopus.arch.wasm.cfg import WasmCFG
# fibonacci wasm module
file_name = "examples/wasm/samples/hello_wasm_studio.wasm"
# read file
with open(file_name, 'rb') as f:
raw = f.read()
# create the cfg
cfg = WasmCFG(raw)
# visualize Call Flow Graph
# generate call_graph.dot and call_graph.pdf file
#
# color similar to https://webassembly.studio/
# imported func == turquoise / exported func == grey
# edge label = number of different calls to the function
cfg.visualize_call_flow()
Legend:
IR conversion (SSA)
from octopus.arch.wasm.emulator import WasmSSAEmulatorEngine
# complete wasm module
file_name = "examples/wasm/samples/fib.wasm"
# read file
with open(file_name, 'rb') as f:
raw = f.read()
# run the emulator for SSA
emul = WasmSSAEmulatorEngine(raw)
emul.emulate_one_function('fib')
# or emul.emulate_functions(['fib'])
# or emul.emulate_functions() # emulate all the function
# visualization of the cfg with SSA
emul.cfg.visualize(ssa=True)
Ethereum (ETH) - EVM
Explorer
from octopus.platforms.ETH.explorer import EthereumInfuraExplorer
from octopus.platforms.ETH.explorer import INFURA_ROPSTEN
KEY_API = "bHuaQhX91nkQBac8Wtgj"
# connection to ROPSTEN network (testnet)
explorer = EthereumInfuraExplorer(KEY_API, network=INFURA_ROPSTEN)
# connection to MAINNET network (mainnet)
# explorer = EthereumInfuraExplorer(KEY_API)
# Test ROPSTEN network current block number
block_number = explorer.eth_blockNumber()
print(block_number)
# 3675552
# Retrieve code of this smart contract
addr = "0x3c6B10a5239B1a8A27398583F49771485382818F"
code = explorer.eth_getCode(addr)
print(code)
# 0x6060604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c14606e575b600080fd5b3415605857600080fd5b606c60048080359060200190919050506094565b005b3415607857600080fd5b607e609e565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a72305820e1f98c821c12eea52047d7324b034ddccc41eaa7365d369b34580ab73c71a8940029
Disassembler
from octopus.platforms.ETH.disassembler import EthereumDisassembler
# smart contract bytecode
bytecode_hex = "60606040526000357c0100000000000000000000000000000000000000000000000000000000900480635fd8c7101461004f578063c0e317fb1461005e578063f8b2cb4f1461006d5761004d565b005b61005c6004805050610099565b005b61006b600480505061013e565b005b610083600480803590602001909190505061017d565b6040518082815260200191505060405180910390f35b3373ffffffffffffffffffffffffffffffffffffffff16611111600060005060003373ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060005054604051809050600060405180830381858888f19350505050151561010657610002565b6000600060005060003373ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600050819055505b565b34600060005060003373ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828282505401925050819055505b565b6000600060005060008373ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000505490506101b6565b91905056"
disasm = EthereumDisassembler()
disasm.disassemble(bytecode_hex)
# disassemble bytecode into a list of EthereumInstruction
# attributes r_format='list' by default
print(disasm.disassemble(bytecode_hex))
#[<octopus.platforms.ETH.instruction.EthereumInstruction object at 0x7f85d4add5c0>, <octopus.platforms.ETH.instruction.EthereumInstruction object at 0x7f85d4ad8588>, <octopus.platforms.ETH.instruction.EthereumInstruction object at 0x7f85d4ad8c50>]
print()
print(disasm.disassemble(bytecode_hex, r_format='reverse'))
# {0: <octopus.platforms.ETH.instruction.EthereumInstruction object at 0x7f85d4ad8160>, ..., 229: <octopus.platforms.ETH.instruction.EthereumInstruction object at 0x7f85d4ad8630>, 230: <octopus.platforms.ETH.instruction.EthereumInstruction object at 0x7f85d4ad87b8>}
print()
print(disasm.disassemble(bytecode_hex,r_format='text'))
# PUSH1 0x60
# PUSH1 0x40
# MSTORE
# PUSH1 0x0
# CALLDATALOAD
# PUSH29 0x100000000000000000000000000000000000000000000000000000000
# SWAP1
# DIV
# DUP1
# PUSH4 0x5fd8c710
# EQ
# PUSH2 0x4f
# JUMPI
# ...
# SWAP2
# SWAP1
# POP
# JUMP
Control Flow Analysis
from octopus.analysis.graph import CFGGraph
from octopus.platforms.ETH.cfg import EthereumCFG
# ethernaut0 bytecode
file_name = "examples/ETH/evm_bytecode/Zeppelin_Hello_ethernaut0.bytecode"
# read file
with open(file_name) as f:
bytecode_hex = f.read()
# create the CFG
cfg = EthereumCFG(bytecode_hex)
# generic visualization api
# generate graph.dot and graph.pdf file
graph = CFGGraph(cfg)
graph.view()
# or directly using the cfg binding
# cfg.visualize()
# and you can get a simplify cfg representation using
# cfg.visualize(simplify=True) or graph.view(simplify=True)
IR conversion (SSA)
# The conversion to SSA is already done by the SSAEmulator
# when the CFG is reconstruct
# by default you have just to visualize it
from octopus.platforms.ETH.cfg import EthereumCFG
# ethernaut0 bytecode
file_name = "examples/ETH/evm_bytecode/Zeppelin_Hello_ethernaut0.bytecode"
# read file
with open(file_name) as f:
bytecode_hex = f.read()
# create the CFG
cfg = EthereumCFG(bytecode_hex)
# SSA visualization
cfg.visualize(ssa=True)
Ethereum (WASM)
Explorer
from octopus.platforms.ETH.explorer import EthereumInfuraExplorer
from octopus.platforms.ETH.explorer import INFURA_KOVAN
# connection to ROPSTEN network (testnet)
explorer = EthereumInfuraExplorer("bHuaQhX91nkQBac8Wtgj",
network=INFURA_KOVAN)
# connection to MAINNET network (mainnet)
# explorer = EthereumInfuraExplorer("bHuaQhX91nkQBac8Wtgj")
# test infura access
block_number = explorer.eth_blockNumber()
print('blockNumber = %d' % block_number)
# retrieve code of this smart contract
addr = "0x1120e596b173d953ba52ce262f73ce3734b0e40e"
code = explorer.eth_getCode(addr)
print()
print(code)
# blockNumber = 8803487
#
# 0x0061736d0100000001090260000060027f7f00021a0203656e7603726574000103656e76066d656d6f7279020102100303020000040501700101010501000601000708010463616c6c00010a120205001002000b0a00418008410b1000000b0b1201004180080b0b48656c6c6f20776f726c64000b076c696e6b696e6703010b0066046e616d65015f060003726574010570616e6963020463616c6c032f5f5a4e3134707761736d5f657468657265756d3365787433726574313768363034643830393864313638366338304504066465706c6f790511727573745f626567696e5f756e77696e64
Disassembler
Disassembly of a Wasm module:
from octopus.platforms.ETH.disassembler import EthereumDisassembler
FILE = "examples/ETH/wasm/helloworld_kovan.bytecode"
with open(FILE, 'r') as f:
module_bytecode = f.read()
disasm = EthereumDisassembler(arch='wasm')
# return list of functions instructions (list)
print(disasm.disassemble_module(module_bytecode))
#[[<octopus.arch.wasm.instruction.WasmInstruction object at 0x7efc0ceaa898>], [<octopus.arch.wasm.instruction.WasmInstruction object at 0x7efc0ceaa7b8>, <octopus.arch.wasm.instruction.WasmInstruction object at 0x7efc0ceaa780>, <octopus.arch.wasm.instruction.WasmInstruction object at 0x7efc0ceaa748>, <octopus.arch.wasm.instruction.WasmInstruction object at 0x7efc0ceaa6d8>, <octopus.arch.wasm.instruction.WasmInstruction object at 0x7efc0ceaa710>]]
print()
# return text of functions code
print(disasm.disassemble_module(module_bytecode, r_format='text'))
# func 0
# end
#
# func 1
# call 1
# i32.const 1036
# i32.const 232
# call 0
# end
Disassembly of wasm bytecode:
from octopus.platforms.ETH.disassembler import EthereumDisassembler
# bytecode in WebAssembly is the function code (i.e. function body)
bytecode = b'\x02\x7fA\x18\x10\x1cA\x00\x0f\x0b'
# create a WasmDisassembler object
disasm = EthereumDisassembler(bytecode, arch='wasm')
# disassemble bytecode into a list of WasmInstruction
# attributes r_format='list' by default
print(disasm.disassemble())
#[<octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4904eb8>, <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4904278>, <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4904390>, <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4904ef0>, <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4904f60>, <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4901048>]
print()
print(disasm.disassemble(r_format='reverse'))
#{0: <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4901048>, 1: <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4904240>, 2: <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4904f60>, 3: <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4904ef0>, 4: <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4904278>, 5: <octopus.arch.wasm.instruction.WasmInstruction object at 0x7f85e4904390>}
print()
print(disasm.disassemble(r_format='text'))
# block -1
# i32.const 24
# call 28
# i32.const 0
# return
# end
ModuleAnalyzer
from octopus.arch.wasm.analyzer import WasmModuleAnalyzer
FILE = "examples/ETH/wasm/helloworld_kovan.bytecode"
with open(FILE, 'r') as f:
module_bytecode = f.read()
# return list of functions instructions (list)
# attributes analysis=True by default
analyzer = WasmModuleAnalyzer(module_bytecode)
# show analyzer attributes
print(analyzer.func_prototypes)
# [('ret', 'i32 i32', '', 'import'), ('$func1', '', '', 'local'), ('call', '', '', 'export')]
print()
print(analyzer.exports)
# [{'field_str': 'call', 'kind': 0, 'index': 2}]
print()
print(analyzer.imports_func)
# [('env', 'ret', 1)]
print()
print(analyzer.datas)
# [{'data': b'Hello world', 'index': 0, 'offset': None, 'size': 11},
# {'data': b'\x00asm\x01\x00\x00\x00\x01\t\x02`\x00\x00`\x02\x7f\x7f\x00\x02\x1a\x02\x03env\x03ret\x00\x01\x03env\x06memory\x02\x01\x02\x10\x03\x03\x02\x00\x00\x04\x05\x01p\x01\x01\x01\x05\x01\x00\x06\x01\x00\x07\x08\x01\x04call\x00\x01\n\x12\x02\x05\x00\x10\x02\x00\x0b\n\x00A\x80\x08A\x0b\x10\x00\x00\x0b\x0b\x12\x01\x00A\x80\x08\x0b\x0bHello world\x00\x0b\x07linking\x03\x01\x0b\x00f\x04name\x01_\x06\x00\x03ret\x01\x05panic\x02\x04call\x03/_ZN14pwasm_ethereum3ext3ret17h604d8098d1686c80E\x04\x06deploy\x05\x11rust_begin_unwind',
# 'index': 0,
# 'offset': None,
# 'size': 232}]
Control Flow Analysis
from octopus.platforms.ETH.cfg import EthereumCFG
# HelloWorld on Kovan Parity Network
file_name = "examples/ETH/wasm/helloworld_kovan.bytecode"
# read file
with open(file_name) as f:
bytecode_hex = f.read()
# create the CFG
cfg = EthereumCFG(bytecode_hex, arch='wasm')
cfg.visualize()
Functions' instructions analytics
from octopus.platforms.ETH.cfg import EthereumCFG
# HelloWorld on Kovan Parity Network
file_name = "examples/ETH/wasm/helloworld_kovan.bytecode"
# read file
with open(file_name) as f:
bytecode_hex = f.read()
# create the CFG
cfg = EthereumCFG(bytecode_hex, arch='wasm')
# visualization
cfg.visualize_instrs_per_funcs()
Call Flow Analysis
from octopus.platforms.ETH.cfg import EthereumCFG
# HelloWorld on Kovan Parity Network
file_name = "examples/ETH/wasm/helloworld_kovan.bytecode"
# read file
with open(file_name) as f:
bytecode_hex = f.read()
# create the CFG
cfg = EthereumCFG(bytecode_hex, arch='wasm')
# visualization
cfg.visualize_call_flow()
Legend:
IR conversion (SSA)
# TODO
NEO
Explorer
from octopus.platforms.NEO.explorer import NeoExplorerRPC
# get list nodes here: http://monitor.cityofzion.io/
explorer = NeoExplorerRPC(host='seed2.neo.org')
# get current number of block on the blockchain
print(explorer.getblockcount())
# 2534868
# get information on a contract
# lock smart contract address: d3cce84d0800172d09c88ccad61130611bd047a4
contract = explorer.getcontractstate("d3cce84d0800172d09c88ccad61130611bd047a4")
print(contract)
# {'author': 'Erik Zhang',
# 'code_version': '2.0',
# 'description': 'Lock 2.0',
# 'email': 'erik@neo.org',
# 'hash': '0xd3cce84d0800172d09c88ccad61130611bd047a4',
# 'name': 'Lock',
# 'parameters': ['Integer', 'PublicKey', 'Signature'],
# 'properties': {'dynamic_invoke': False, 'storage': False},
# 'returntype': 'Boolean',
# 'script': '56c56b6c766b00527ac46c766b51527ac46c766b52527ac4616168184e656f2e426c6f636b636861696e2e4765744865696768746168184e656f2e426c6f636b636861696e2e4765744865616465726c766b53527ac46c766b00c36c766b53c36168174e656f2e4865616465722e47657454696d657374616d70a06c766b54527ac46c766b54c3640e00006c766b55527ac4621a006c766b51c36c766b52c3617cac6c766b55527ac46203006c766b55c3616c7566',
# 'version': 0}
# smart contract code in contract['script']
print(contract['script'])
Disassembler
from octopus.platforms.NEO.disassembler import NeoDisassembler
# lock contract
file_name = "examples/NEO/samples/Lock.bytecode"
# read file
with open(file_name) as f:
bytecode = f.read()
disasm = NeoDisassembler()
print(disasm.disassemble(bytecode, r_format='text'))
# PUSH6
# NEWARRAY
# TOALTSTACK
# FROMALTSTACK
# DUP
# TOALTSTACK
# PUSH0
# PUSH2
# ROLL
# SETITEM
# FROMALTSTACK
# ....
# PICKITEM
# NOP
# FROMALTSTACK
# DROP
# RET
Control Flow Analysis
from octopus.analysis.graph import CFGGraph
from octopus.platforms.NEO.cfg import NeoCFG
# lock contract
file_name = "examples/NEO/samples/Lock.bytecode"
# read file
with open(file_name) as f:
raw = f.read()
# create neo cfg - automatic static analysis
cfg = NeoCFG(raw)
# graph visualization
graph = CFGGraph(cfg, filename="Lock_cfg")
graph.view_functions()
EOS
Explorer
from octopus.platforms.EOS.explorer import EosExplorer
host = "api.cypherglass.com"
# by defaul the port is 8888
explo = EosExplorer(host=host)
# get info about the node
explo.get_info()
'''
{'block_cpu_limit': 180289,
'block_net_limit': 1045680,
'chain_id': 'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906',
'head_block_id': '018d6e2bcf6295126cd74cf694b5cca3529eefc42b334b394ef87c3a43876739',
'head_block_num': 26045995,
'head_block_producer': 'eosswedenorg',
'head_block_time': '2018-11-09T14:11:29.500',
'last_irreversible_block_id': '018d6cdcff78bbd9f25c605b02fb67c47a337ece78ddcf73089cee4bf6a410ee',
'last_irreversible_block_num': 26045660,
'server_version': 'c71d2245',
'server_version_string': 'mainnet-1.3.0',
'virtual_block_cpu_limit': 38092879,
'virtual_block_net_limit': 1048576000}
'''
explo.get_block(1337)
'''
{'action_mroot': 'bcb9763baa3bbf98ed36379b4be0ecb2d9cd21c75df01729c63b2b021001c10c',
'block_extensions': [],
'block_num': 1337,
'confirmed': 0,
'header_extensions': [],
'id': '00000539d17a03af7126e073be4c4d99a72b7f58793cf2c87b9bfd41b6c711fb',
'new_producers': None,
'previous': '00000538b374c1cbfaeed7253ad3075ddc72a28f0a0515301fc1bbed675f2316',
'producer': 'eosio',
'producer_signature': 'SIG_K1_K5jWf36t6j454Hb2fGuV37YTwMTvuQ51ZPBtpru8Ud2axtMTEauWyvtpJuTpnvqzReUndDgEDXvoeEP4jdj2bpnYKBt6g2',
'ref_block_prefix': 1944069745,
'schedule_version': 0,
'timestamp': '2018-06-09T12:09:21.500',
'transaction_mroot': '0000000000000000000000000000000000000000000000000000000000000000',
'transactions': []}
'''
Disassembler
from octopus.platforms.EOS.disassembler import EosDisassembler
# complete wasm module
file_name = "examples/EOS/samples/eos_ping.wasm"
# read file
with open(file_name, 'rb') as f:
raw = f.read()
# just disassembly
disasm = EosDisassembler()
# because we provide full module bytecode
# we need to use disassemble_module()
# otherwise just disassemble() is enough
text = disasm.disassemble_module(raw, r_format="text")
print(text)
# func 0
# get_local 0
# get_local 1
# i32.const 32
# call 12
# i32.eqz
# end
#
# func 1
# get_local 0
# i64.load 3, 0
# get_local 0
# i64.load 3, 8
# call 6
# end
#
# func 2
# ...
# end
#
# ...
ModuleAnalyzer
from octopus.platforms.EOS.analyzer import EosAnalyzer
# complete wasm module
file_name = "examples/EOS/samples/eos_ping.wasm"
with open(file_name, 'rb') as f:
module_bytecode = f.read()
# return list of functions instructions (list)
# attributes analysis=True by default
analyzer = EosAnalyzer(module_bytecode)
# show analyzer attributes
print(analyzer.func_prototypes)
#[('action_data_size', '', 'i32', 'import'), ('eosio_assert', 'i32 i32', '', 'import'), ('eosio_exit', 'i32', '', 'import'), ('memcpy', 'i32 i32 i32', 'i32', 'import'), ('prints', 'i32', '', 'import'), ('read_action_data', 'i32 i32', 'i32', 'import'), ('require_auth2', 'i64 i64', '', 'import'), ('_ZeqRK11checksum256S1_', 'i32 i32', 'i32', 'export'), ('_ZN5eosio12require_authERKNS_16permission_levelE', 'i32', '', 'export'), ('apply', 'i64 i64 i64', '', 'export'), ('$func10', 'i32 i64', '', 'local'), ('$func11', 'i32 i32', 'i32', 'local'), ('memcmp', 'i32 i32 i32', 'i32', 'export'), ('malloc', 'i32', 'i32', 'export'), ('$func14', 'i32 i32', 'i32', 'local'), ('$func15', 'i32', 'i32', 'local'), ('free', 'i32', '', 'export'), ('$func17', '', '', 'local')]
print()
print(analyzer.exports)
# [{'field_str': 'memory', 'kind': 2, 'index': 0}, {'field_str': '_ZeqRK11checksum256S1_', 'kind': 0, 'index': 7}, {'field_str': '_ZN5eosio12require_authERKNS_16permission_levelE', 'kind': 0, 'index': 8}, {'field_str': 'apply', 'kind': 0, 'index': 9}, {'field_str': 'memcmp', 'kind': 0, 'index': 12}, {'field_str': 'malloc', 'kind': 0, 'index': 13}, {'field_str': 'free', 'kind': 0, 'index': 16}]
print()
print(analyzer.imports_func)
# [('env', 'action_data_size', 3), ('env', 'eosio_assert', 5), ('env', 'eosio_exit', 2), ('env', 'memcpy', 6), ('env', 'prints', 2), ('env', 'read_action_data', 4), ('env', 'require_auth2', 1)]
Control Flow Analysis
from octopus.platforms.EOS.cfg import EosCFG
from octopus.analysis.graph import CFGGraph
# complete wasm module
file_name = "examples/EOS/samples/eos_ping.wasm"
# read file
with open(file_name, 'rb') as f:
raw = f.read()
# create the cfg
cfg = EosCFG(raw)
# visualize
graph = CFGGraph(cfg)
graph.view_functions()
Call Flow Analysis
from octopus.platforms.EOS.cfg import EosCFG
# complete wasm module
file_name = "examples/EOS/samples/eos_ping.wasm"
# read file
with open(file_name, 'rb') as f:
raw = f.read()
# create the cfg
cfg = EosCFG(raw)
# visualize
cfg.visualize_call_flow()
Functions' instructions analytics
from octopus.platforms.EOS.cfg import EosCFG
# complete wasm module
file_name = "examples/EOS/samples/eos_ping.wasm"
# read file
with open(file_name, 'rb') as f:
raw = f.read()
# create the cfg
cfg = EosCFG(raw)
# visualize
cfg.visualize_instrs_per_funcs()
Bitcoin
Explorer
from octopus.platforms.BTC.explorer import BitcoinExplorerRPC
RPC_USER = 'test'
RPC_PASSWORD = 'test'
RPC_HOST = 'localhost'
host = '%s:%s@%s' % (RPC_USER, RPC_PASSWORD, RPC_HOST)
explorer = BitcoinExplorerRPC(host)
explorer.getbestblockhash()
# '00000000000000000012085cfe8c79bcdacf81fbd82f6ab52c3cb3a454d4987c'
explorer.getblockcount()
#550859
Disassembler
from octopus.platforms.BTC.disassembler import BitcoinDisassembler
# Witness Script
file_name = "examples/BTC/witness_script.hex"
# read file
with open(file_name) as f:
bytecode = f.read()
disasm = BitcoinDisassembler()
print(disasm.disassemble(bytecode, r_format='text'))
# 0
# OP_ROT
# OP_ROT
# 2
# 0203f4d01d0b35588638631ebb7d46d8387fd1aeb3dbecfdd3faf7c056b023c833
# 03aa6677e3ce1bd634f4f2e1cd60a60af002e1b30484d4d1611b183b16d391ee96
# 03bf164811abb8c91ed39e58d4e307f86cb4e487c83f727a2c482bc71a0f96f1db
# 3
# OP_CHECKMULTISIG
Please find examples in examples folder.
Publications and Videos
- BLACKALPS 2018 Reversing and Vulnerability research of Ethereum Smart Contracts
- Devcon iv. Reversing Ethereum Smart Contracts to find out what's behind EVM bytecode
- hack.lu 2018 Reversing and Vulnerability research of Ethereum Smart Contracts
- ToorCon XX - 2018 Reversing Ethereum Smart Contracts (Introduction)
- ToorCon XX - 2018 Dissection of WebAssembly module
- REcon Montreal 2018: Reverse Engineering Of Blockchain Smart Contracts
Authors
Patrick Ventuzelo - Creator - @Pat_Ventuzelo
See also the list of contributors who participated in this project.
License
This project is licensed under the MIT License - see the LICENSE file for details
Acknowledgments
Sponsor:
Inspired by:
Trainings & Contact
Patrick Ventuzelo - @pat_ventuzelo - Independent Security Researcher / Trainer.
Consulting & trainings:
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file octopus-0.3.5.tar.gz
.
File metadata
- Download URL: octopus-0.3.5.tar.gz
- Upload date:
- Size: 1.8 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.2.0 pkginfo/1.5.0.1 requests/2.23.0 setuptools/39.0.1 requests-toolbelt/0.9.1 tqdm/4.48.0 CPython/3.7.5
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | ac80d5ca5b204e8e93f269b472756d6a8680739f9e1f9fb8509814757ea14c76 |
|
MD5 | 76c0895788f53e8effe2029f1afbc3f5 |
|
BLAKE2b-256 | 1d0b50b60aecbf8cae9bc3641110176bb14ca2580e155b1b39151f26c259a3b6 |