feat: add detecor code

This commit is contained in:
SunnyQjm committed 2024-06-15 11:27:29 +08:00
1 parent 6ed28aa202
commit 91db5e272b
40 files changed
+9918

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
# Part of the code uses [Vandal] (https://github.com/usyd-blockchain/vandal) as reference.
# For any question, please contact Mengya Zhang <zhang.9407@osu.edu>.
+187
View File
@@ -0,0 +1,187 @@
"""
Script for Processing Smart Contract Transactions
This script processes smart contract transactions from a specified input folder.
It reads each transaction file, builds a Transactional Abstract Control Flow Graph (TAC EFG),
and exports the related facts based on a set of target opcodes. The script maintains a log
of any errors encountered during processing and saves the progress to continue from the last
processed file in case of interruption. The error log and progress are saved in CSV and text
files, respectively.
To run the script, use the command: `python3 analyze3.py`
Author: Yangyi Zou
Date: Dec 2023
Usage: python3 analyze3.py
"""
import os
import subprocess
import argparse
import logging
import sys
from os.path import abspath, dirname, join
import src.exporter as exporter
import src.tac_efg as tac_efg
import src.settings as settings
import csv, threading, queue, time
import pdb,traceback
# 初始化错误日志和进度跟踪
INTERMIDIATE_FOLDER = 'intermediate'
LOGGING_FOLDER = 'logging'
if not os.path.exists(LOGGING_FOLDER):
os.makedirs(LOGGING_FOLDER)
if not os.path.exists(INTERMIDIATE_FOLDER):
os.makedirs(INTERMIDIATE_FOLDER)
progress_file_path = os.path.join(INTERMIDIATE_FOLDER,'progress_test.txt')
error_log_file_path = os.path.join(INTERMIDIATE_FOLDER,'error_log_test.csv')
progress_file = open(progress_file_path, 'a') if os.path.exists(progress_file_path) else open(progress_file_path, 'w')
error_log_file_mode = 'a' if os.path.exists(error_log_file_path) else 'w'
error_log_file = open(error_log_file_path, error_log_file_mode, newline='')
error_writer = csv.writer(error_log_file)
if error_log_file_mode == 'w':
error_writer.writerow(['Transaction', 'Error'])
def log_error(transaction, error):
"""记录错误到 CSV 文件"""
error_writer.writerow([transaction[:-4], str(error)])
error_log_file.flush()
def setup_file_logger(filename):
"""为每个文件设置独立的日志处理器"""
log_filename = os.path.join(LOGGING_FOLDER, filename[:-4] + '_error.log')
file_handler = logging.FileHandler(log_filename, mode='w') # 使用写入模式,覆盖旧日志
formatter = logging.Formatter('%(levelname)s:%(name)s:%(message)s')
file_handler.setFormatter(formatter)
# 获取根日志器并添加新的文件处理器
logger = logging.getLogger()
logger.addHandler(file_handler)
# 返回文件处理器以便之后移除
return file_handler
def remove_file_logger(file_handler):
"""移除指定的文件日志处理器"""
logger = logging.getLogger()
logger.removeHandler(file_handler)
file_handler.close()
def update_progress(transaction):
"""更新进度文件"""
with open(progress_file_path, 'w') as progress_file:
progress_file.write(transaction)
# 设置超时时间(以秒为单位)
timeout_seconds = 30 # 示例:2分钟
def process_file(filename, line_number):
# pdb.set_trace()
# 为当前文件设置日志处理器
file_handler = setup_file_logger(filename)
"""处理单个文件的函数,同时显示行号"""
start_time = time.time()
try:
input_file = os.path.join(input_folder, filename)
output_folder = os.path.join('./facts/fact_' + filename[:-4])
if not os.path.exists(output_folder):
os.makedirs(output_folder)
with open(input_file, "r") as f:
efg = tac_efg.TACGraph.from_opcode(f)
# 假设 export 操作是耗时操作,需要放在超时检测中
if time.time() - start_time < timeout_seconds:
exporter.EFGTsvExporter(efg).export(output_dir=output_folder, out_opcodes=target_opcodes)
else:
raise TimeoutError("Processing exceeded time limit.")
# 计算处理时间并打印信息
processing_time = time.time() - start_time
print(f"完成处理文件 Processed {filename} in {processing_time:.2f} seconds")
update_progress(filename)
except TimeoutError as te:
log_error(filename, str(te))
print(f"Line {line_number}: {filename} skipped due to timeout.")
except Exception as e:
logging.error(f"Error processing file {filename}: {e}", exc_info=True)
log_error(filename, str(e))
print(f"Exception type: {type(e)}")
print(f"Exception message: {e}")
traceback.print_exc() # 这需要先导入 traceback 模块
remove_file_logger(file_handler)
# 修改 file_processing_worker 函数,以传递行号到 process_file
def file_processing_worker():
"""线程工作函数,处理文件队列中的文件"""
line_number = 0
while True:
filename = file_queue.get()
print("------------------------------------------------------------------------------------------------------")
print(f"Line number:{line_number} Processing file: {filename}")
if filename is None:
break
line_number += 1
process_file(filename, line_number)
file_queue.task_done()
# 设置日志等级
# logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level)
logging.basicConfig(level=logging.DEBUG, format='%(levelname)s:%(name)s:%(message)s')
# 设置目标操作码
target_opcodes = ['CREATE', 'BALANCE', 'CALLER', 'CALLVALUE', 'STOP', 'RETURN', 'REVERT', 'ORIGIN', 'CALLDATALOAD', 'EQ', 'TIMESTAMP', 'NUMBER', 'DIFFICULTY', 'COINBASE', 'BLOCKHASH', 'GASLIMIT', 'EXTCODESIZE', 'SELFDESTRUCT', 'JUMPI', 'JUMP', 'JUMPDEST', 'SSTORE', 'SLOAD', 'CALL', 'DELEGATE', 'CALLCODE', 'STATICCALL']
#####################################################
#####################################################
# 设置输入文件夹路径
# input_folder = "/home/yangyi/database/TxSpector/detector/test" # For test debug
# input_folder = "test" # For test debug
input_folder = "/home/yangyi/database/smart_contract-dataset/Tx_tuples"
# 设置线程数和文件队列
num_threads = 1
file_queue = queue.Queue()
#####################################################
#####################################################
# 创建并启动线程
threads = []
for _ in range(num_threads):
thread = threading.Thread(target=file_processing_worker)
thread.start()
threads.append(thread)
# 读取上次处理的进度
last_processed = None
if os.path.exists(progress_file_path):
with open(progress_file_path, 'r') as progress_file:
last_processed = progress_file.read().strip()
# 将文件添加到队列(在启动线程之后)
for filename in sorted(os.listdir(input_folder)):
if filename.endswith('.txt') and (not last_processed or filename > last_processed):
file_queue.put(filename)
# 等待队列清空
file_queue.join()
# 停止工作线程
for _ in range(num_threads):
file_queue.put(None)
for thread in threads:
thread.join()
print(" ==================------------- Processing complete -------------==================")
error_log_file.close()
+125
View File
@@ -0,0 +1,125 @@
import os
import subprocess
import argparse
import logging
import requests, json, time
import sys
from os.path import abspath, dirname, join
import src.exporter as exporter
import src.tac_efg as tac_efg
import src.settings as settings
def start_logo():
print("="*145)
print("{:^7}|{:^69}|{:^46}|{:^19}|".format("Line", "Transaction Hash", "Receiver contract hash","Time"))
print("{:^7}|{:^68}|{:^45}|{:^18}|".format("Number", "\U0001F4B0", "\U0001F4B5", "\U000023F0"))
print("-"*145)
def line_show(lineNumber:int, transaction_hash:str, receiver_hash, exec_time: str):
print("{:^7}|{:^69}|{:^46}|{:^19}|".format(lineNumber, transaction_hash, receiver_hash, exec_time))
def end_logo():
print("="*141)
def format_time(seconds):
days, seconds = divmod(seconds, 86400)
hours, seconds = divmod(seconds, 3600)
minutes, seconds = divmod(seconds, 60)
seconds = round(seconds,3)
if days > 0:
return f"{int(days)}d {int(hours)}h {int(minutes)}m {seconds}s"
elif hours > 0:
return f"{int(hours)}h {int(minutes)}m {seconds}s"
elif minutes > 0:
return f"{int(minutes)}m, {seconds}s"
else:
return f"{seconds} seconds"
def debug_trace_receiver(tx_hash):
"""
Get the receiver contract hash
"""
URL = "http://localhost:8545"
headers = {"Content-Type": "application/json"}
data = {
"jsonrpc": "2.0",
"method": "eth_getTransactionByHash",
"params": [tx_hash],
"id": 1
}
response = requests.post(URL, headers=headers, data=json.dumps(data))
result = response.json()
try:
to_address = result.get('result',{}).get('to','No address found')
except Exception as e:
print(f"show the error: {e}")
return to_address # Return the 'to' address as a string
if __name__ == "__main__":
# Set up logger, with appropriate log level depending on verbosity.
log_level = logging.WARNING
logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level)
target_opcodes = ['CREATE', 'BALANCE', 'CALLER', 'CALLVALUE', 'STOP', 'RETURN', 'REVERT', 'ORIGIN', 'CALLDATALOAD',
'EQ', 'TIMESTAMP', 'NUMBER', 'DIFFICULTY', 'COINBASE', 'BLOCKHASH', 'GASLIMIT', 'EXTCODESIZE',
'SELFDESTRUCT', 'JUMPI', 'JUMP', 'JUMPDEST', 'SSTORE', 'SLOAD', 'CALL', 'DELEGATE', 'CALLCODE',
'STATICCALL']
# Set the path to your input folder and the path to the analyze_geth.sh script
input_folder = './Reenter_OPCODE'
script_path = './bin/analyze_geth.sh'
start_logo()
number = 0
# Iterate over all files in the input folder
for filename in os.listdir(input_folder):
start_time = time.time()
number += 1
if filename.endswith('.txt'): # Check if the file is a .txt file
input_file = os.path.join(input_folder, filename)
trans_hash = filename[:-4]
output_folder = os.path.join('./facts/fact_' + trans_hash) # Removing '.txt' and appending 'fact_'
# Create the output folder if it doesn't exist
if not os.path.exists(output_folder):
os.makedirs(output_folder)
# Build TAC EFG from input file
try:
logging.info("Reading from '%s'.", input_file)
with open(input_file, "r") as f:
# print(f" \033[92m{number}\033[0m {trans_hash}",end=" ")
efg = tac_efg.TACGraph.from_opcode(f)
logging.info("Initial EFG generation completed.")
# Catch a Control-C and exit with UNIX failure status 1
except KeyboardInterrupt:
print(f"\n\nUser interrupted processing at transaction hash: {trans_hash[:-4]}")
print("Exiting program.")
sys.exit(1)
# Generate facts file
exporter.EFGTsvExporter(efg).export(output_dir=output_folder,
out_opcodes=target_opcodes)
# Generate the receiver hash sc_addr.facts
try:
to_address = debug_trace_receiver(trans_hash)
output_path = os.path.join(output_folder, "sc_addr.facts")
with open(output_path, "w") as output_file:
if to_address:
output_file.write(to_address) # Write the 'to' address directly
else:
output_file.write("No address found")
except subprocess.CalledProcessError as e:
print(f"Error processing {filename}: {e.stderr.decode()}")
end_time = time.time()
formatted_time = format_time(end_time - start_time)
line_show(number,trans_hash,to_address,formatted_time)
end_logo()
+8
View File
@@ -0,0 +1,8 @@
INFO:root:
+------------------------------+
| TxSpector Detector v0.0.1 |
| (c) The Ohio State University |
+------------------------------+
INFO:root:Reading from '<stdin>'.
CRITICAL:root:
Interrupted by user
+409
View File
@@ -0,0 +1,409 @@
0;PUSH1;128
2;PUSH1;64
4;MSTORE;
5;CALLVALUE;0
6;DUP1;
7;ISZERO;0
8;PUSH2;16
11;JUMPI;1,16
16;JUMPDEST;
17;POP;0
18;PUSH1;4
20;CALLDATASIZE;4
21;LT;4,4
22;PUSH2;54
25;JUMPI;0,54
26;PUSH1;0
28;CALLDATALOAD;36288808125440614087156239300432291571913474525407125660472422305149220814848
29;PUSH1;224
31;SHR;36288808125440614087156239300432291571913474525407125660472422305149220814848,224
32;DUP1;
33;PUSH4;1346026703
38;EQ;1346026703,1346026703
39;PUSH2;59
42;JUMPI;1,59
59;JUMPDEST;
60;PUSH2;67
63;PUSH2;99
66;JUMP;99
99;JUMPDEST;
100;PUSH1;0
102;DUP1;
103;PUSH1;0
105;SWAP1;
106;SLOAD;0
107;SWAP1;
108;PUSH2;256
111;EXP;0,256
112;SWAP1;
113;DIV;1,347364959843557222306392896278436377038921993872
114;PUSH20;1461501637330902918203684832716283019655932542975
135;AND;347364959843557222306392896278436377038921993872,1461501637330902918203684832716283019655932542975
136;PUSH20;1461501637330902918203684832716283019655932542975
157;AND;347364959843557222306392896278436377038921993872,1461501637330902918203684832716283019655932542975
158;PUSH4;4037859285
163;PUSH1;64
165;MLOAD;128
166;DUP2;
167;PUSH4;4294967295
172;AND;4037859285,4294967295
173;PUSH1;224
175;SHL;4037859285,224
176;DUP2;
177;MSTORE;
178;PUSH1;4
180;ADD;128,4
181;PUSH1;32
183;PUSH1;64
185;MLOAD;128
186;DUP1;
187;DUP4;
188;SUB;128,132
189;DUP2;
190;PUSH1;0
192;DUP8;
193;DUP1;
194;EXTCODESIZE;375
195;ISZERO;375
196;DUP1;
197;ISZERO;0
198;PUSH2;206
201;JUMPI;1,206
206;JUMPDEST;
207;POP;0
208;GAS;4397
0;PUSH1;128
2;PUSH1;64
4;MSTORE;
5;CALLVALUE;0
6;DUP1;
7;ISZERO;0
8;PUSH2;16
11;JUMPI;1,16
16;JUMPDEST;
17;POP;0
18;PUSH1;4
20;CALLDATASIZE;4
21;LT;4,4
22;PUSH2;54
25;JUMPI;0,54
26;PUSH1;0
28;CALLDATALOAD;108860470973059015388586700352357298692816548327441413882039699691817989570560
29;PUSH1;224
31;SHR;108860470973059015388586700352357298692816548327441413882039699691817989570560,224
32;DUP1;
33;PUSH4;1490037513
38;EQ;4037859285,1490037513
39;PUSH2;59
42;JUMPI;0,59
43;DUP1;
44;PUSH4;4037859285
49;EQ;4037859285,4037859285
50;PUSH2;89
53;JUMPI;1,89
89;JUMPDEST;
90;PUSH2;97
93;PUSH2;125
96;JUMP;125
125;JUMPDEST;
126;PUSH1;0
128;DUP1;
129;TIMESTAMP;1694821674
130;SWAP1;
131;POP;0
132;PUSH1;10
134;PUSH1;0
136;SLOAD;0
137;DUP3;
138;PUSH2;147
141;SWAP2;
142;SWAP1;
143;PUSH2;212
146;JUMP;212
212;JUMPDEST;
213;PUSH1;0
215;PUSH2;223
218;DUP3;
219;PUSH2;264
222;JUMP;264
264;JUMPDEST;
265;PUSH1;0
267;DUP2;
268;SWAP1;
269;POP;0
270;SWAP2;
271;SWAP1;
272;POP;1694821674
273;JUMP;223
223;JUMPDEST;
224;SWAP2;
225;POP;1694821674
226;PUSH2;234
229;DUP4;
230;PUSH2;264
233;JUMP;264
264;JUMPDEST;
265;PUSH1;0
267;DUP2;
268;SWAP1;
269;POP;0
270;SWAP2;
271;SWAP1;
272;POP;1694821657
273;JUMP;234
234;JUMPDEST;
235;SWAP3;
236;POP;1694821657
237;DUP3;
238;DUP3;
239;LT;1694821657,1694821674
240;ISZERO;0
241;PUSH2;253
244;JUMPI;1,253
253;JUMPDEST;
254;DUP3;
255;DUP3;
256;SUB;1694821657,1694821674
257;SWAP1;
258;POP;0
259;SWAP3;
260;SWAP2;
261;POP;1694821657
262;POP;1694821674
263;JUMP;147
147;JUMPDEST;
148;LT;10,17
149;ISZERO;0
150;PUSH2;158
153;JUMPI;1,158
158;JUMPDEST;
159;TIMESTAMP;1694821674
160;SWAP1;
161;POP;1694821674
162;PUSH1;1
164;SLOAD;1
165;SWAP2;
166;POP;0
167;POP;1694821674
168;SWAP1;
169;JUMP;97
97;JUMPDEST;
98;PUSH1;64
100;MLOAD;128
101;PUSH2;110
104;SWAP2;
105;SWAP1;
106;PUSH2;185
109;JUMP;185
185;JUMPDEST;
186;PUSH1;0
188;PUSH1;32
190;DUP3;
191;ADD;32,128
192;SWAP1;
193;POP;0
194;PUSH2;206
197;PUSH1;0
199;DUP4;
200;ADD;0,128
201;DUP5;
202;PUSH2;170
205;JUMP;170
170;JUMPDEST;
171;PUSH2;179
174;DUP2;
175;PUSH2;264
178;JUMP;264
264;JUMPDEST;
265;PUSH1;0
267;DUP2;
268;SWAP1;
269;POP;0
270;SWAP2;
271;SWAP1;
272;POP;12
273;JUMP;179
179;JUMPDEST;
180;DUP3;
181;MSTORE;
182;POP;12
183;POP;128
184;JUMP;206
206;JUMPDEST;
207;SWAP3;
208;SWAP2;
209;POP;12
210;POP;128
211;JUMP;110
110;JUMPDEST;
111;PUSH1;64
113;MLOAD;128
114;DUP1;
115;SWAP2;
116;SUB;128,160
117;SWAP1;
118;RETURN;32,128
209;CALL;1,0
210;ISZERO;1
211;DUP1;
212;ISZERO;0
213;PUSH2;226
216;JUMPI;1,226
226;JUMPDEST;
227;POP;0
228;POP;132
229;POP;4037859285
230;POP;347364959843557222306392896278436377038921993872
231;PUSH1;64
233;MLOAD;128
234;RETURNDATASIZE;32
235;PUSH1;31
237;NOT;31
238;PUSH1;31
240;DUP3;
241;ADD;31,32
242;AND;115792089237316195423570985008687907853269984665640564039457584007913129639904,63
243;DUP3;
244;ADD;32,128
245;DUP1;
246;PUSH1;64
248;MSTORE;
249;POP;160
250;DUP2;
251;ADD;32,128
252;SWAP1;
253;PUSH2;262
256;SWAP2;
257;SWAP1;
258;PUSH2;379
261;JUMP;379
379;JUMPDEST;
380;PUSH1;0
382;PUSH1;32
384;DUP3;
385;DUP5;
386;SUB;128,160
387;SLT;32,32
388;ISZERO;0
389;PUSH2;397
392;JUMPI;1,397
397;JUMPDEST;
398;PUSH1;0
400;PUSH2;411
403;DUP5;
404;DUP3;
405;DUP6;
406;ADD;0,128
407;PUSH2;358
410;JUMP;358
358;JUMPDEST;
359;PUSH1;0
361;DUP2;
362;MLOAD;12
363;SWAP1;
364;POP;0
365;PUSH2;373
368;DUP2;
369;PUSH2;582
372;JUMP;582
582;JUMPDEST;
583;PUSH2;591
586;DUP2;
587;PUSH2;536
590;JUMP;536
536;JUMPDEST;
537;PUSH1;0
539;DUP2;
540;SWAP1;
541;POP;0
542;SWAP2;
543;SWAP1;
544;POP;12
545;JUMP;591
591;JUMPDEST;
592;DUP2;
593;EQ;12,12
594;PUSH2;602
597;JUMPI;1,602
602;JUMPDEST;
603;POP;12
604;JUMP;373
373;JUMPDEST;
374;SWAP3;
375;SWAP2;
376;POP;160
377;POP;128
378;JUMP;411
411;JUMPDEST;
412;SWAP2;
413;POP;0
414;POP;0
415;SWAP3;
416;SWAP2;
417;POP;160
418;POP;128
419;JUMP;262
262;JUMPDEST;
263;SWAP1;
264;POP;0
265;PUSH32;67711100502772707642132795203347290566490950481849279840619261695477678726252
298;DUP2;
299;PUSH1;64
301;MLOAD;160
302;PUSH2;311
305;SWAP2;
306;SWAP1;
307;PUSH2;477
310;JUMP;477
477;JUMPDEST;
478;PUSH1;0
480;PUSH1;32
482;DUP3;
483;ADD;32,160
484;SWAP1;
485;POP;0
486;PUSH2;498
489;PUSH1;0
491;DUP4;
492;ADD;0,160
493;DUP5;
494;PUSH2;435
497;JUMP;435
435;JUMPDEST;
436;PUSH2;444
439;DUP2;
440;PUSH2;536
443;JUMP;536
536;JUMPDEST;
537;PUSH1;0
539;DUP2;
540;SWAP1;
541;POP;0
542;SWAP2;
543;SWAP1;
544;POP;12
545;JUMP;444
444;JUMPDEST;
445;DUP3;
446;MSTORE;
447;POP;12
448;POP;160
449;JUMP;498
498;JUMPDEST;
499;SWAP3;
500;SWAP2;
501;POP;12
502;POP;160
503;JUMP;311
311;JUMPDEST;
312;PUSH1;64
314;MLOAD;160
315;DUP1;
316;SWAP2;
317;SUB;160,192
318;SWAP1;
319;LOG1;67711100502772707642132795203347290566490950481849279840619261695477678726252,32,160
320;POP;12
321;JUMP;67
67;JUMPDEST;
68;STOP;
+42
View File
@@ -0,0 +1,42 @@
#!/bin/bash
# BSD 3-Clause License
#
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if [ "$#" -ne 2 ]; then
echo "Usage: analyze.sh trace_file output_file"
exit
fi
set -x
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
# Here are the opcodes that we need to detect attacks, you can add or delete them
$DIR/decompile_geth -o CREATE BALANCE CALLER CALLVALUE STOP RETURN REVERT ORIGIN CALLDATALOAD EQ TIMESTAMP NUMBER DIFFICULTY COINBASE BLOCKHASH GASLIMIT EXTCODESIZE SELFDESTRUCT JUMPI JUMP JUMPDEST SSTORE SLOAD CALL DELEGATE CALLCODE STATICCALL -n -t $2 $1
#$DIR/decompile_geth -o CREATE BALANCE CALLER CALLVALUE STOP RETURN REVERT ORIGIN CALLDATALOAD EQ TIMESTAMP NUMBER DIFFICULTY COINBASE BLOCKHASH GASLIMIT EXTCODESIZE SELFDESTRUCT JUMPI JUMP JUMPDEST SSTORE SLOAD CALL DELEGATE CALLCODE STATICCALL -t $2 $1
#$DIR/decompile_geth -o CREATE BALANCE CALLER CALLVALUE STOP RETURN REVERT ORIGIN CALLDATALOAD EQ TIMESTAMP NUMBER DIFFICULTY COINBASE BLOCKHASH GASLIMIT EXTCODESIZE SELFDESTRUCT JUMPI JUMP JUMPDEST SSTORE SLOAD MSTORE MLAOD CALL DELEGATE CALLCODE STATICCALL PUSH1 -n -t $2 $1
+167
View File
@@ -0,0 +1,167 @@
#!/usr/bin/env python3
# BSD 3-Clause License
#
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# Standard lib imports
import argparse
import logging
import sys
from os.path import abspath, dirname, join
# Prepend .. to $PATH so the project modules can be imported below
# 用于将以太坊虚拟机(EVM)字节码反汇编为三地址代码进行程序分析
src_path = join(dirname(abspath(__file__)), "..")
sys.path.insert(0, src_path)
# Local project imports
import src.exporter as exporter
import src.tac_efg as tac_efg
import src.settings as settings
# Version string to display with -v
VERSION = """\
+------------------------------+
| TxSpector Detector v0.0.1 |
| (c) The Ohio State University |
+------------------------------+\
"""
# Define a version() function in case we want dynamic version strings later
def version():
return VERSION
# Configure argparse
parser = argparse.ArgumentParser(
description="An EVM bytecode disassembly that generates"
"three-address code for program analysis.")
parser.add_argument("-t",
"--tsv",
nargs="?",
const="",
metavar="DIR",
default=None,
help="generate tab-separated .facts files for Souffle "
"and write files to the specified directory, which "
"will be recursively created if it does not exist "
"(current working directory by default).")
parser.add_argument("-o",
"--opcodes",
nargs="*",
default=[],
metavar="OPCODE",
help="If producing tsv output, also include relations "
"encoding all occurrences of the specified "
"list of opcodes. Opcode X will be stored in "
"op_X.facts.")
parser.add_argument("-v",
"--verbose",
action="store_true",
help="emit verbose debug output to stderr.")
parser.add_argument("-vv",
"--prolix",
action="store_true",
help="higher verbosity level, including extra debug "
"messages from dataflow analysis and elsewhere.")
parser.add_argument("-n",
"--no_out",
action="store_true",
help="do not output decompiled graph.")
parser.add_argument("-V",
"--version",
action="store_true",
help="show program's version number and exit.")
parser.add_argument("infile",
nargs="?",
type=argparse.FileType("r"),
default=sys.stdin,
help="file from which decompiler input should be read "
"(stdin by default).")
parser.add_argument("outfile",
nargs="?",
type=argparse.FileType("w"),
default=sys.stdout,
help="file to which decompiler output should be written "
"(stdout by default).")
# Parse the arguments.
args = parser.parse_args()
# Set up logger, with appropriate log level depending on verbosity.
log_level = logging.WARNING
if args.prolix:
log_level = logging.DEBUG
elif args.verbose:
log_level = logging.INFO
logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level)
# Handle --version
if args.version:
sys.exit(1)
# Always show version for log_level >= LOW
logging.info("\n" + version())
# Build TAC EFG from input file
try:
logging.info("Reading from '%s'.", args.infile.name)
efg = tac_efg.TACGraph.from_opcode(args.infile)
logging.info("Initial EFG generation completed.")
# Catch a Control-C and exit with UNIX failure status 1
except KeyboardInterrupt:
logging.critical("\nInterrupted by user")
sys.exit(1)
# Generate output using the requested exporter(s)
# blocks, textual version of efg
if not args.no_out:
logging.info("Writing string output.")
print(exporter.EFGStringExporter(efg).export(), file=args.outfile)
# Generate facts file
if args.tsv is not None:
logging.info("Writing TSV output.")
exporter.EFGTsvExporter(efg).export(output_dir=args.tsv,
out_opcodes=args.opcodes)
View File
Whitespace-only changes.
+735
View File
@@ -0,0 +1,735 @@
0;PUSH1;128
2;PUSH1;64
4;MSTORE;
5;PUSH1;4
7;CALLDATASIZE;4
8;LT;4,4
9;PUSH2;67
12;JUMPI;0,67
13;PUSH1;0
15;CALLDATALOAD;71634460638095550392100722737204534410467526076113882865062923086234512261120
16;PUSH1;224
18;SHR;71634460638095550392100722737204534410467526076113882865062923086234512261120,224
19;DUP1;
20;PUSH4;302407648
25;EQ;2657069820,302407648
26;PUSH2;288
29;JUMPI;0,288
30;DUP1;
31;PUSH4;2467045379
36;EQ;2657069820,2467045379
37;PUSH2;331
40;JUMPI;0,331
41;DUP1;
42;PUSH4;2657069820
47;EQ;2657069820,2657069820
48;PUSH2;396
51;JUMPI;1,396
396;JUMPDEST;
397;PUSH2;404
400;PUSH2;518
403;JUMP;518
518;JUMPDEST;
519;PUSH8;1000000000000000000
528;CALLVALUE;1000000000000000000
529;LT;1000000000000000000,1000000000000000000
530;ISZERO;0
531;PUSH2;615
534;JUMPI;1,615
615;JUMPDEST;
616;PUSH1;0
618;DUP1;
619;SLOAD;0
620;SWAP1;
621;PUSH2;256
624;EXP;0,256
625;SWAP1;
626;DIV;1,1148000406285257731517353366331601018510389802278
627;PUSH20;1461501637330902918203684832716283019655932542975
648;AND;1148000406285257731517353366331601018510389802278,1461501637330902918203684832716283019655932542975
649;PUSH20;1461501637330902918203684832716283019655932542975
670;AND;1148000406285257731517353366331601018510389802278,1461501637330902918203684832716283019655932542975
671;PUSH4;3504541104
676;PUSH8;1000000000000000000
685;PUSH1;64
687;MLOAD;128
688;DUP3;
689;PUSH4;4294967295
694;AND;3504541104,4294967295
695;PUSH1;224
697;SHL;3504541104,224
698;DUP2;
699;MSTORE;
700;PUSH1;4
702;ADD;128,4
703;PUSH1;0
705;PUSH1;64
707;MLOAD;128
708;DUP1;
709;DUP4;
710;SUB;128,132
711;DUP2;
712;DUP6;
713;DUP9;
714;DUP1;
715;EXTCODESIZE;1063
716;ISZERO;1063
717;DUP1;
718;ISZERO;0
719;PUSH2;727
722;JUMPI;1,727
727;JUMPDEST;
728;POP;0
729;GAS;90637
0;PUSH1;128
2;PUSH1;64
4;MSTORE;
5;PUSH1;4
7;CALLDATASIZE;4
8;LT;4,4
9;PUSH2;78
12;JUMPI;0,78
13;PUSH1;0
15;CALLDATALOAD;94482241256677223720308674365448432550660581809977780367718865401173155774464
16;PUSH1;224
18;SHR;94482241256677223720308674365448432550660581809977780367718865401173155774464,224
19;DUP1;
20;PUSH4;669136355
25;EQ;3504541104,669136355
26;PUSH2;106
29;JUMPI;0,106
30;DUP1;
31;PUSH4;773487949
36;EQ;3504541104,773487949
37;PUSH2;207
40;JUMPI;0,207
41;DUP1;
42;PUSH4;1872738698
47;EQ;3504541104,1872738698
48;PUSH2;266
51;JUMPI;0,266
52;DUP1;
53;PUSH4;3504541104
58;EQ;3504541104,3504541104
59;PUSH2;309
62;JUMPI;1,309
309;JUMPDEST;
310;PUSH2;317
313;PUSH2;843
316;JUMP;843
843;JUMPDEST;
844;CALLVALUE;1000000000000000000
845;PUSH1;0
847;DUP1;
848;CALLER;354605892528587159537909970736718245026464010688
849;PUSH20;1461501637330902918203684832716283019655932542975
870;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
871;PUSH20;1461501637330902918203684832716283019655932542975
892;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
893;DUP2;
894;MSTORE;
895;PUSH1;32
897;ADD;0,32
898;SWAP1;
899;DUP2;
900;MSTORE;
901;PUSH1;32
903;ADD;32,32
904;PUSH1;0
906;SHA3;71705274935761785479379189425703652296799616776219122606543621932795362146736
907;PUSH1;0
909;DUP3;
910;DUP3;
911;SLOAD;71705274935761785479379189425703652296799616776219122606543621932795362146736
912;ADD;1000000000000000000,0
913;SWAP3;
914;POP;1000000000000000000
915;POP;0
916;DUP2;
917;SWAP1;
918;SSTORE;1000000000000000000,71705274935761785479379189425703652296799616776219122606543621932795362146736
919;POP;1000000000000000000
920;CALLVALUE;1000000000000000000
921;PUSH1;1
923;PUSH1;0
925;DUP3;
926;DUP3;
927;SLOAD;1
928;ADD;1000000000000000000,1000000000000000000
929;SWAP3;
930;POP;1000000000000000000
931;POP;0
932;DUP2;
933;SWAP1;
934;SSTORE;2000000000000000000,1
935;POP;2000000000000000000
936;JUMP;317
317;JUMPDEST;
318;STOP;
730;CALL;1,0
731;ISZERO;1
732;DUP1;
733;ISZERO;0
734;PUSH2;747
737;JUMPI;1,747
747;JUMPDEST;
748;POP;0
749;POP;132
750;POP;1000000000000000000
751;POP;3504541104
752;POP;1148000406285257731517353366331601018510389802278
753;PUSH1;0
755;DUP1;
756;SLOAD;0
757;SWAP1;
758;PUSH2;256
761;EXP;0,256
762;SWAP1;
763;DIV;1,1148000406285257731517353366331601018510389802278
764;PUSH20;1461501637330902918203684832716283019655932542975
785;AND;1148000406285257731517353366331601018510389802278,1461501637330902918203684832716283019655932542975
786;PUSH20;1461501637330902918203684832716283019655932542975
807;AND;1148000406285257731517353366331601018510389802278,1461501637330902918203684832716283019655932542975
808;PUSH4;773487949
813;PUSH8;1000000000000000000
822;PUSH1;64
824;MLOAD;128
825;DUP3;
826;PUSH4;4294967295
831;AND;773487949,4294967295
832;PUSH1;224
834;SHL;773487949,224
835;DUP2;
836;MSTORE;
837;PUSH1;4
839;ADD;128,4
840;DUP1;
841;DUP3;
842;DUP2;
843;MSTORE;
844;PUSH1;32
846;ADD;132,32
847;SWAP2;
848;POP;1000000000000000000
849;POP;132
850;PUSH1;0
852;PUSH1;64
854;MLOAD;128
855;DUP1;
856;DUP4;
857;SUB;128,164
858;DUP2;
859;PUSH1;0
861;DUP8;
862;DUP1;
863;EXTCODESIZE;1063
864;ISZERO;1063
865;DUP1;
866;ISZERO;0
867;PUSH2;875
870;JUMPI;1,875
875;JUMPDEST;
876;POP;0
877;GAS;54621
0;PUSH1;128
2;PUSH1;64
4;MSTORE;
5;PUSH1;4
7;CALLDATASIZE;36
8;LT;4,36
9;PUSH2;78
12;JUMPI;0,78
13;PUSH1;0
15;CALLDATALOAD;20853193852723734048814770637610870652489083209607696777694672748161695528627
16;PUSH1;224
18;SHR;20853193852723734048814770637610870652489083209607696777694672748161695528627,224
19;DUP1;
20;PUSH4;669136355
25;EQ;773487949,669136355
26;PUSH2;106
29;JUMPI;0,106
30;DUP1;
31;PUSH4;773487949
36;EQ;773487949,773487949
37;PUSH2;207
40;JUMPI;1,207
207;JUMPDEST;
208;CALLVALUE;0
209;DUP1;
210;ISZERO;0
211;PUSH2;219
214;JUMPI;1,219
219;JUMPDEST;
220;POP;0
221;PUSH2;264
224;PUSH1;4
226;DUP1;
227;CALLDATASIZE;36
228;SUB;4,36
229;PUSH1;32
231;DUP2;
232;LT;32,32
233;ISZERO;0
234;PUSH2;242
237;JUMPI;1,242
242;JUMPDEST;
243;DUP2;
244;ADD;32,4
245;SWAP1;
246;DUP1;
247;DUP1;
248;CALLDATALOAD;1000000000000000000
249;SWAP1;
250;PUSH1;32
252;ADD;4,32
253;SWAP1;
254;SWAP3;
255;SWAP2;
256;SWAP1;
257;POP;36
258;POP;4
259;POP;36
260;PUSH2;444
263;JUMP;444
444;JUMPDEST;
445;DUP1;
446;PUSH1;0
448;DUP1;
449;CALLER;354605892528587159537909970736718245026464010688
450;PUSH20;1461501637330902918203684832716283019655932542975
471;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
472;PUSH20;1461501637330902918203684832716283019655932542975
493;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
494;DUP2;
495;MSTORE;
496;PUSH1;32
498;ADD;0,32
499;SWAP1;
500;DUP2;
501;MSTORE;
502;PUSH1;32
504;ADD;32,32
505;PUSH1;0
507;SHA3;71705274935761785479379189425703652296799616776219122606543621932795362146736
508;SLOAD;71705274935761785479379189425703652296799616776219122606543621932795362146736
509;LT;1000000000000000000,1000000000000000000
510;ISZERO;0
511;PUSH2;519
514;JUMPI;1,519
519;JUMPDEST;
520;PUSH1;0
522;CALLER;354605892528587159537909970736718245026464010688
523;PUSH20;1461501637330902918203684832716283019655932542975
544;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
545;DUP3;
546;PUSH1;64
548;MLOAD;128
549;DUP1;
550;PUSH1;0
552;ADD;128,0
553;SWAP1;
554;POP;128
555;PUSH1;0
557;PUSH1;64
559;MLOAD;128
560;DUP1;
561;DUP4;
562;SUB;128,128
563;DUP2;
564;DUP6;
565;DUP8;
566;GAS;51884
0;PUSH1;128
2;PUSH1;64
4;MSTORE;
5;PUSH1;4
7;CALLDATASIZE;0
8;LT;4,0
9;PUSH2;67
12;JUMPI;1,67
67;JUMPDEST;
68;JUMPDEST;
69;PUSH8;1000000000000000000
78;PUSH1;0
80;DUP1;
81;SLOAD;0
82;SWAP1;
83;PUSH2;256
86;EXP;0,256
87;SWAP1;
88;DIV;1,1148000406285257731517353366331601018510389802278
89;PUSH20;1461501637330902918203684832716283019655932542975
110;AND;1148000406285257731517353366331601018510389802278,1461501637330902918203684832716283019655932542975
111;PUSH20;1461501637330902918203684832716283019655932542975
132;AND;1148000406285257731517353366331601018510389802278,1461501637330902918203684832716283019655932542975
133;BALANCE;1000000000000000000
134;LT;1000000000000000000,1000000000000000000
135;PUSH2;286
138;JUMPI;0,286
139;PUSH1;0
141;DUP1;
142;SLOAD;0
143;SWAP1;
144;PUSH2;256
147;EXP;0,256
148;SWAP1;
149;DIV;1,1148000406285257731517353366331601018510389802278
150;PUSH20;1461501637330902918203684832716283019655932542975
171;AND;1148000406285257731517353366331601018510389802278,1461501637330902918203684832716283019655932542975
172;PUSH20;1461501637330902918203684832716283019655932542975
193;AND;1148000406285257731517353366331601018510389802278,1461501637330902918203684832716283019655932542975
194;PUSH4;773487949
199;PUSH8;1000000000000000000
208;PUSH1;64
210;MLOAD;128
211;DUP3;
212;PUSH4;4294967295
217;AND;773487949,4294967295
218;PUSH1;224
220;SHL;773487949,224
221;DUP2;
222;MSTORE;
223;PUSH1;4
225;ADD;128,4
226;DUP1;
227;DUP3;
228;DUP2;
229;MSTORE;
230;PUSH1;32
232;ADD;132,32
233;SWAP2;
234;POP;1000000000000000000
235;POP;132
236;PUSH1;0
238;PUSH1;64
240;MLOAD;128
241;DUP1;
242;DUP4;
243;SUB;128,164
244;DUP2;
245;PUSH1;0
247;DUP8;
248;DUP1;
249;EXTCODESIZE;1063
250;ISZERO;1063
251;DUP1;
252;ISZERO;0
253;PUSH2;261
256;JUMPI;1,261
261;JUMPDEST;
262;POP;0
263;GAS;40551
0;PUSH1;128
2;PUSH1;64
4;MSTORE;
5;PUSH1;4
7;CALLDATASIZE;36
8;LT;4,36
9;PUSH2;78
12;JUMPI;0,78
13;PUSH1;0
15;CALLDATALOAD;20853193852723734048814770637610870652489083209607696777694672748161695528627
16;PUSH1;224
18;SHR;20853193852723734048814770637610870652489083209607696777694672748161695528627,224
19;DUP1;
20;PUSH4;669136355
25;EQ;773487949,669136355
26;PUSH2;106
29;JUMPI;0,106
30;DUP1;
31;PUSH4;773487949
36;EQ;773487949,773487949
37;PUSH2;207
40;JUMPI;1,207
207;JUMPDEST;
208;CALLVALUE;0
209;DUP1;
210;ISZERO;0
211;PUSH2;219
214;JUMPI;1,219
219;JUMPDEST;
220;POP;0
221;PUSH2;264
224;PUSH1;4
226;DUP1;
227;CALLDATASIZE;36
228;SUB;4,36
229;PUSH1;32
231;DUP2;
232;LT;32,32
233;ISZERO;0
234;PUSH2;242
237;JUMPI;1,242
242;JUMPDEST;
243;DUP2;
244;ADD;32,4
245;SWAP1;
246;DUP1;
247;DUP1;
248;CALLDATALOAD;1000000000000000000
249;SWAP1;
250;PUSH1;32
252;ADD;4,32
253;SWAP1;
254;SWAP3;
255;SWAP2;
256;SWAP1;
257;POP;36
258;POP;4
259;POP;36
260;PUSH2;444
263;JUMP;444
444;JUMPDEST;
445;DUP1;
446;PUSH1;0
448;DUP1;
449;CALLER;354605892528587159537909970736718245026464010688
450;PUSH20;1461501637330902918203684832716283019655932542975
471;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
472;PUSH20;1461501637330902918203684832716283019655932542975
493;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
494;DUP2;
495;MSTORE;
496;PUSH1;32
498;ADD;0,32
499;SWAP1;
500;DUP2;
501;MSTORE;
502;PUSH1;32
504;ADD;32,32
505;PUSH1;0
507;SHA3;71705274935761785479379189425703652296799616776219122606543621932795362146736
508;SLOAD;71705274935761785479379189425703652296799616776219122606543621932795362146736
509;LT;1000000000000000000,1000000000000000000
510;ISZERO;0
511;PUSH2;519
514;JUMPI;1,519
519;JUMPDEST;
520;PUSH1;0
522;CALLER;354605892528587159537909970736718245026464010688
523;PUSH20;1461501637330902918203684832716283019655932542975
544;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
545;DUP3;
546;PUSH1;64
548;MLOAD;128
549;DUP1;
550;PUSH1;0
552;ADD;128,0
553;SWAP1;
554;POP;128
555;PUSH1;0
557;PUSH1;64
559;MLOAD;128
560;DUP1;
561;DUP4;
562;SUB;128,128
563;DUP2;
564;DUP6;
565;DUP8;
566;GAS;38034
0;PUSH1;128
2;PUSH1;64
4;MSTORE;
5;PUSH1;4
7;CALLDATASIZE;0
8;LT;4,0
9;PUSH2;67
12;JUMPI;1,67
67;JUMPDEST;
68;JUMPDEST;
69;PUSH8;1000000000000000000
78;PUSH1;0
80;DUP1;
81;SLOAD;0
82;SWAP1;
83;PUSH2;256
86;EXP;0,256
87;SWAP1;
88;DIV;1,1148000406285257731517353366331601018510389802278
89;PUSH20;1461501637330902918203684832716283019655932542975
110;AND;1148000406285257731517353366331601018510389802278,1461501637330902918203684832716283019655932542975
111;PUSH20;1461501637330902918203684832716283019655932542975
132;AND;1148000406285257731517353366331601018510389802278,1461501637330902918203684832716283019655932542975
133;BALANCE;0
134;LT;1000000000000000000,0
135;PUSH2;286
138;JUMPI;1,286
286;JUMPDEST;
287;STOP;
567;CALL;1,0
568;SWAP3;
569;POP;354605892528587159537909970736718245026464010688
570;POP;128
571;POP;1000000000000000000
572;RETURNDATASIZE;0
573;DUP1;
574;PUSH1;0
576;DUP2;
577;EQ;0,0
578;PUSH2;615
581;JUMPI;1,615
615;JUMPDEST;
616;PUSH1;96
618;SWAP2;
619;POP;0
620;JUMPDEST;
621;POP;0
622;POP;96
623;SWAP1;
624;POP;0
625;DUP1;
626;PUSH2;739
629;JUMPI;1,739
739;JUMPDEST;
740;DUP2;
741;PUSH1;0
743;DUP1;
744;CALLER;354605892528587159537909970736718245026464010688
745;PUSH20;1461501637330902918203684832716283019655932542975
766;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
767;PUSH20;1461501637330902918203684832716283019655932542975
788;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
789;DUP2;
790;MSTORE;
791;PUSH1;32
793;ADD;0,32
794;SWAP1;
795;DUP2;
796;MSTORE;
797;PUSH1;32
799;ADD;32,32
800;PUSH1;0
802;SHA3;71705274935761785479379189425703652296799616776219122606543621932795362146736
803;PUSH1;0
805;DUP3;
806;DUP3;
807;SLOAD;71705274935761785479379189425703652296799616776219122606543621932795362146736
808;SUB;1000000000000000000,1000000000000000000
809;SWAP3;
810;POP;1000000000000000000
811;POP;0
812;DUP2;
813;SWAP1;
814;SSTORE;0,71705274935761785479379189425703652296799616776219122606543621932795362146736
815;POP;0
816;DUP2;
817;PUSH1;1
819;PUSH1;0
821;DUP3;
822;DUP3;
823;SLOAD;1
824;SUB;1000000000000000000,2000000000000000000
825;SWAP3;
826;POP;1000000000000000000
827;POP;0
828;DUP2;
829;SWAP1;
830;SSTORE;1000000000000000000,1
831;POP;1000000000000000000
832;POP;1
833;POP;1000000000000000000
834;JUMP;264
264;JUMPDEST;
265;STOP;
264;CALL;1,0
265;ISZERO;1
266;DUP1;
267;ISZERO;0
268;PUSH2;281
271;JUMPI;1,281
281;JUMPDEST;
282;POP;0
283;POP;164
284;POP;773487949
285;POP;1148000406285257731517353366331601018510389802278
286;JUMPDEST;
287;STOP;
567;CALL;1,0
568;SWAP3;
569;POP;354605892528587159537909970736718245026464010688
570;POP;128
571;POP;1000000000000000000
572;RETURNDATASIZE;0
573;DUP1;
574;PUSH1;0
576;DUP2;
577;EQ;0,0
578;PUSH2;615
581;JUMPI;1,615
615;JUMPDEST;
616;PUSH1;96
618;SWAP2;
619;POP;0
620;JUMPDEST;
621;POP;0
622;POP;96
623;SWAP1;
624;POP;0
625;DUP1;
626;PUSH2;739
629;JUMPI;1,739
739;JUMPDEST;
740;DUP2;
741;PUSH1;0
743;DUP1;
744;CALLER;354605892528587159537909970736718245026464010688
745;PUSH20;1461501637330902918203684832716283019655932542975
766;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
767;PUSH20;1461501637330902918203684832716283019655932542975
788;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
789;DUP2;
790;MSTORE;
791;PUSH1;32
793;ADD;0,32
794;SWAP1;
795;DUP2;
796;MSTORE;
797;PUSH1;32
799;ADD;32,32
800;PUSH1;0
802;SHA3;71705274935761785479379189425703652296799616776219122606543621932795362146736
803;PUSH1;0
805;DUP3;
806;DUP3;
807;SLOAD;71705274935761785479379189425703652296799616776219122606543621932795362146736
808;SUB;1000000000000000000,0
809;SWAP3;
810;POP;1000000000000000000
811;POP;0
812;DUP2;
813;SWAP1;
814;SSTORE;115792089237316195423570985008687907853269984665640564039456584007913129639936,71705274935761785479379189425703652296799616776219122606543621932795362146736
815;POP;115792089237316195423570985008687907853269984665640564039456584007913129639936
816;DUP2;
817;PUSH1;1
819;PUSH1;0
821;DUP3;
822;DUP3;
823;SLOAD;1
824;SUB;1000000000000000000,1000000000000000000
825;SWAP3;
826;POP;1000000000000000000
827;POP;0
828;DUP2;
829;SWAP1;
830;SSTORE;0,1
831;POP;0
832;POP;1
833;POP;1000000000000000000
834;JUMP;264
264;JUMPDEST;
265;STOP;
878;CALL;1,0
879;ISZERO;1
880;DUP1;
881;ISZERO;0
882;PUSH2;895
885;JUMPI;1,895
895;JUMPDEST;
896;POP;0
897;POP;164
898;POP;773487949
899;POP;1148000406285257731517353366331601018510389802278
900;JUMP;404
404;JUMPDEST;
405;STOP;
+290
View File
@@ -0,0 +1,290 @@
0;PUSH1;128
2;PUSH1;64
4;MSTORE;
5;CALLVALUE;0
6;DUP1;
7;ISZERO;1
8;PUSH2;10
11;JUMPI;1,10
16;JUMPDEST;
17;POP;0
18;PUSH1;4
20;CALLDATASIZE;4
21;LT;4,4
22;PUSH2;54
25;JUMPI;0,36
26;PUSH1;0
28;CALLDATALOAD;36288808125440614087156239300432291571913474525407125660472422305149220814848
29;PUSH1;224
31;SHR;36288808125440614087156239300432291571913474525407125660472422305149220814848,224
32;DUP1;
33;PUSH4;1346026703
38;EQ;1346026703,1346026703
39;PUSH2;59
42;JUMPI;1,59
59;JUMPDEST;
60;PUSH2;43
63;PUSH2;141
66;JUMP;141
141;JUMPDEST;
142;PUSH1;0
144;DUP1;
145;PUSH1;0
147;DUP1;
148;SLOAD;1034896644053684534091083175701429597294878387947
149;SWAP1;
150;PUSH2;256
153;EXP;0,256
154;SWAP1;
155;DIV;1,1034896644053684534091083175701429597294878387947
156;PUSH20;1461501637330902918203684832716283019655932542975
177;AND;1034896644053684534091083175701429597294878387947,1461501637330902918203684832716283019655932542975
178;PUSH20;1461501637330902918203684832716283019655932542975
199;AND;1034896644053684534091083175701429597294878387947,1461501637330902918203684832716283019655932542975
200;PUSH4;4037859285
205;PUSH1;64
207;MLOAD;128
208;DUP2;
209;PUSH2;4294967295
214;AND;4037859285;4294967295
215;PUSH1;224
217;SHR;4037859285,224
218;DUP2;
219;MSTORE
220;PUSH1;4
222;ADD;128,4
223;PUSH1;32
225;PUSH1;64
227;MLOAD;128
228;DUP1;
229;DUP4;
230;SUB;128,132
231;DUP2;
232;PUSH1;0
234;DUP8;
235;DUP1;
236;EXTCODESIZE;513
237;ISZERO;513
238;DUP1;
239;ISZERO;0
240;PUSH2;248
243;JUMPI;1,248
248;JUMPDEST;
249;POP;0
250;GAS;36091
0;PUSH1;128
2;PUSH1;64
4;MSTORE;
5;CALLVALUE;0
6;DUP1;
7;ISZERO;1
8;PUSH2;10
11;JUMPI;1,10
16;JUMPDEST;
17;POP;0
18;PUSH1;4
20;CALLDATASIZE;4
21;LT;4,4
22;PUSH2;54
25;JUMPI;0,36
26;PUSH1;0
28;CALLDATALOAD;108860470973059015388586700352357298692816548327441413882039699691817989570560
29;PUSH1;224
31;SHR;108860470973059015388586700352357298692816548327441413882039699691817989570560,224
32;DUP1;
33;PUSH4;1490037513
38;EQ;4037859285,1490037513
39;PUSH2;59
42;JUMPI;0,59
43;DUP1;
44;PUSH4;4037859285
49;EQ;4037859285,4037859285
50;PUSH2;59
53;JUMPI;1,59
89;JUMPDEST;
90;PUSH1;0
93;PUSH2;125
96;JUMP;125
125;JUMPDEST;
126;PUSH1;0
128;DUP1;
129;SLOAD;0
130;TIMESTAMP;1692939836
131;EQ;0,1692939836
132;ISZERO;1
133;PUSH2;141
136;JUMPI;1,8d
141;JUMPDEST;
142;PUSH32;52189957839671922653803054351540587877049321209015323053385286709118451475657
175;TIMESTAMP;1692939836
176;PUSH1;64
178;MLOAD;128
179;DUP1;
180;DUP3;
181;DUP2;
182;MSTORE;
183;PUSH1;32
185;ADD;80,20
186;SWAP2;
187;POP;1692939836
188;POP;128
189;PUSH1;64
191;MLOAD;128
192;DUP1;
193;SWAP2;
194;SUB;128,160
195;SWAP1;
196;LOG1;52189957839671922653803054351540587877049321209015323053385286709118451475657,32,128
197;TIMESTAMP;1692939836
198;PUSH1;0
200;DUP2;
201;SWAP1;
202;SSTORE;1692939836,0
203;POP;1692939836
204;PUSH1;0
206;PUSH1;7
208;TIMESTAMP;1692939836
209;DUP2;
210;PUSH2;215
213;JUMPI;7;215
215;JUMPDEST;
216;MOD;0
217;EQ;0,0
218;PUSH2;331
221;JUMPI;1,331
331;JUMPDEST;
332;PUSH32;52189957839671922653803054351540587877049321209015323053385286709118451475657
365;TIMESTAMP;1692939836
366;PUSH1;64
368;MLOAD;128
369;DUP1;
370;DUP3;
371;DUP2;
372;MSTORE;
373;PUSH1;32
375;ADD;80,20
376;SWAP2;
377;POP;1692939836
378;POP;128
379;PUSH1;64
381;MLOAD;128
382;DUP1;
383;SWAP2;
384;SUB;128,160
385;SWAP1;
386;LOG1;52189957839671922653803054351540587877049321209015323053385286709118451475657,32,128
387;PUSH32;52189957839671922653803054351540587877049321209015323053385286709118451475657
420;PUSH1;7
422;TIMESTAMP;1692939836
423;DUP2;
424;PUSH2;429
427;JUMPI;7,429
429;JUMPDEST;
430;MOD;0
431;PUSH1;64
433;MLOAD;128
434;DUP1;
435;DUP3;
436;DUP2;
437;MSTORE;
438;PUSH1;32
440;ADD;80,20
441;SWAP2;
442;POP;0
443;POP;128
444;PUSH1;64
446;MLOAD;128
447;DUP1;
448;SWAP2;
449;SUB;128,160
450;SWAP1;
451;LOG1;52189957839671922653803054351540587877049321209015323053385286709118451475657,32,128
452;PUSH1;1
454;SLOAD;1
455;SWAP1;
456;POP;0
457;SWAP1;
458;JUMP;61
97;JUMPDEST;
98;PUSH1;64
100;MLOAD;128
101;DUP1;
102;DUP3;
103;DUP2;
104;MSTORE;
105;PUSH1;32
107;ADD;128,32
108;SWAP2;
109;POP;12
110;POP;128
111;PUSH1;64
113;MLOAD;128
114;DUP1;
115;SWAP2;
116;SUB;128,160
117;SWAP1;
118;RETURN;32;128
251;CALL;1,0
252;ISZERO;0
254;DUP1;
255;ISZERO;1
256;PUSH2;268
259;JUMPI;1,268
268;JUMPDEST;
265;POP;0
270;POP;132
271;POP;4037859285
272;POP;1034896644053684534091083175701429597294878387947
273;PUSH1;64
275;MLOAD;128
276;RETURNDATASIZE;20
277;PUSH1;32
279;DUP2;
280;LT;20,20
281;ISZERO;1
282;PUSH2;290
285;JUMPI;1,290
290;JUMPDEST;
291;DUP2;
292;ADD;32;128
293;SWAP1;
294;DUP1;
295;DUP1;
296;MLOAD;c
297;SWAP1;
298;PUSH1;32
300;ADD;80,20
301;SWAP1;
302;SWAP3;
303;SWAP2;
304;SWAP1;
305;POP;a0
306;POP;128
307;POP;a0
308;SWAP1;
309;POP;0
310;DUP1;
311;SWAP2;
312;POP;0
313;POP;12
314;SWAP1;
315;JUMP;43
67;JUMPDEST;
68;PUSH1;64
70;MLOAD;128
71;DUP1;
72;DUP3;
73;DUP2;
74;MSTORE;
75;PUSH1;32
77;ADD;80,20
78;SWAP2;
79;POP;12
80;POP;128
81;PUSH1;64
83;MLOAD;128
84;DUP1;
85;SWAP2;
86;SUB;128,160
87;SWAP1;
88;RETURN;32;128
+32
View File
@@ -0,0 +1,32 @@
import sys
import json
need_check = [
"ADD", "MUL", "SUB", "DIV", "SDIV", "MOD", "SMOD", "ADDMOD", "MULMOD", "EXP", "SIGNEXTEND", "LT", "GT", "SLT",
"SGT", "EQ", "ISZERO", "AND", "OR", "XOR", "NOT", "BYTE", "SHL", "SHR", "SAR",
"CALLDATACOPY", "CODECOPY", "EXTCODECOPY", "RETURNDATACOPY", "PREVRANDAO", "POP", "SSTORE", "JUMPI", "LOG0"
]
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python3 check_stack.py <path_to_reconstruct_json>")
sys.exit(1)
filepath = sys.argv[1]
codes = {
}
vars = {
}
with open(filepath, 'r') as f:
value = f.read()
codes = json.loads(value)
for code in codes:
if codes[code]["opcode"] in need_check:
arg_count = len(codes[code]["args"])
if codes[code]["args"] != codes[code]["stacks"][-arg_count:]:
if codes[code]["opcode"] == "CALLDATACOPY":
continue
print(codes[code]["args"], codes[code]["stacks"][-arg_count:])
print(codes[code])
print("=====================================")
exit(1)
Executable
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
# 设置日志文件夹的路径
LOGGING_DIR="logging"
# 遍历logging文件夹中的所有文件
for file in "$LOGGING_DIR"/*; do
# 检查文件是否包含"ERROR"
if ! grep -q "Traceback" "$file"; then
# 如果不包含"ERROR",则删除文件
echo "Deleting $file as it does not contain Traceback "
rm "$file"
else
echo "$file contains ERROR, not deleting"
fi
done
+51
View File
@@ -0,0 +1,51 @@
import sys
from graphviz import Digraph
import src.tac_efg as tac_efg
import src.exporter as exporter
def block_k_depth(block):
return f"{block.evm_ops[0].call_depth}"
def block_k(block):
dividing_line = "⋮"
variable = "" if block.evm_ops[-1].value is None else block.evm_ops[-1].value
# return f"{block.evm_ops[0].call_depth}-{block.entry}({block.evm_ops[0].opcode.name})->{block.exit}({block.evm_ops[-1].opcode.name})"
return f"{block.evm_ops[0].call_depth}\n{block.entry};{block.evm_ops[0].opcode.name}; {block.evm_ops[0].value}\n{dividing_line}\n{block.exit}; {block.evm_ops[-1].opcode.name}; {variable}"
def draw_cfg(efg):
dot = Digraph(comment='CFG')
dot.format = "svg"
for block in efg.blocks:
dot.node(block_k(block), block_k(block), shape='box')
for edge in efg.edge_list():
dot.edge(block_k(edge[0]), block_k(edge[1]))
dot.render('cfg')
def draw_depth_cfg(efg):
dot = Digraph(comment='depth-CFG')
dot.format = "svg"
for block in efg.blocks:
dot.node(block_k_depth(block), block_k_depth(block), shape='box')
for edge in efg.edge_list():
dot.edge(block_k_depth(edge[0]), block_k_depth(edge[1]))
dot.render('depth-cfg')
if __name__ == '__main__':
with open(sys.argv[1], "r") as f:
efg = tac_efg.TACGraph.from_opcode(f)
print(efg)
exporter.EFGDotExporter(efg).export(out_filename="suicidal_ganache.html")
# draw_cfg(efg)
# draw_depth_cfg(efg)
+51
View File
@@ -0,0 +1,51 @@
"""
Script for Adding sc_addr smart contract to address to the facts folder
To run the script, use the command: `python3 extract_sc_addr.py`
Author: Yangyi Zou
Date: Dec 2023
Usage: python3 extract_sc_addr.py
"""
import os
import requests
def fetch_transaction_details(transaction_hash: str):
url = 'http://localhost:8545'
headers = {'Content-Type': 'application/json'}
params = {
"jsonrpc": "2.0",
"method": "eth_getTransactionByHash",
"params": [transaction_hash],
"id": 1
}
try:
response = requests.post(url, json=params, headers=headers)
response_json = response.json()
if 'result' in response_json and response_json['result']:
return response_json['result']
else:
print(f"No result found for transaction hash: {transaction_hash}")
except requests.exceptions.RequestException as e:
print(f"Request failed for transaction hash: {transaction_hash}, error: {e}")
return None
def extract_to_address_from_facts(folder_path='facts'):
count = 0
for root, dirs, files in os.walk(folder_path):
for subfolder in dirs:
transaction_hash = subfolder.split('_')[-1]
transaction_details = fetch_transaction_details(transaction_hash)
if transaction_details and 'to' in transaction_details:
to_address = transaction_details['to']
file_path = os.path.join(root, subfolder, 'sc_addr.facts')
try:
with open(file_path, 'w') as file:
file.write(to_address)
count += 1
print(f"Processed {count}: {subfolder} -> sc_addr.facts")
except IOError as e:
print(f"Failed to write to {file_path}, error: {e}")
if __name__ == "__main__":
extract_to_address_from_facts()
+175
View File
@@ -0,0 +1,175 @@
"""
Script for Processing Smart Contract Transactions
This script processes smart contract transactions from a specified input folder.
It reads each transaction file, builds a Transactional Abstract Control Flow Graph (TAC EFG),
and exports the related facts based on a set of target opcodes. The script maintains a log
of any errors encountered during processing and saves the progress to continue from the last
processed file in case of interruption. The error log and progress are saved in CSV and text
files, respectively.
To run the script, use the command: `python3 analyze3.py`
Author: Yangyi Zou
Date: May 19 2024
Usage: python3 analyze3.py
"""
import time
import io
import os
import subprocess
import argparse
import logging
import sys
from os.path import abspath, dirname, join
import src.exporter as exporter
import src.tac_efg_lab as tac_efg
import src.settings as settings
import csv, threading, queue, time
import pdb,traceback
import psycopg2.pool
from KEY import *
import threading
import queue
from tqdm import tqdm
# 设置目标操作码
target_opcodes = ['CREATE', 'BALANCE', 'CALLER', 'CALLVALUE', 'STOP', 'RETURN', 'REVERT', 'ORIGIN', 'CALLDATALOAD', 'EQ', 'TIMESTAMP', 'NUMBER', 'DIFFICULTY', 'COINBASE', 'BLOCKHASH', 'GASLIMIT', 'EXTCODESIZE', 'SELFDESTRUCT', 'JUMPI', 'JUMP', 'JUMPDEST', 'SSTORE', 'SLOAD', 'CALL', 'DELEGATE', 'CALLCODE', 'STATICCALL']
# TODO 1: Initialize database connection pool, error logging, and progress tracking
def create_connection_pool():
return psycopg2.pool.ThreadedConnectionPool(1, 10, user=USER, password=PASSWORD, host=HOST, port=PORT, database=DATABASE)
conn_pool = create_connection_pool()
def update_progress(tx_hash, conn_pool):
with conn_pool.getconn() as conn:
with conn.cursor() as cursor:
cursor.execute("DELETE FROM framework_progress")
cursor.execute("INSERT INTO framework_progress (last_processed_tx_hash) VALUES (%s)", (tx_hash,))
conn.commit()
conn_pool.putconn(conn)
def log_error(tx_hash, error, conn_pool):
"""Logs an error for a transaction in the database"""
with conn_pool.getconn() as conn:
with conn.cursor() as cursor:
cursor.execute("INSERT INTO error_log (tx_hash, error_message) VALUES (%s, %s)", (tx_hash, error))
conn_pool.putconn(conn)
# TODO 2: Fetch transaction data
def get_last_processed_tx_hash(conn_pool):
"""获取上次处理的最后一个事务哈希。"""
with conn_pool.getconn() as conn:
with conn.cursor() as cursor:
cursor.execute("SELECT last_processed_tx_hash FROM framework_progress LIMIT 1")
result = cursor.fetchone()
conn_pool.putconn(conn)
return result[0] if result else None
def fetch_transactions(conn_pool,start_after_hash=None,limit=200000):
"""Fetches transactions from the database."""
with conn_pool.getconn() as conn:
with conn.cursor() as cursor:
if start_after_hash:
cursor.execute("SELECT * FROM tx_rtl_part_b_dedup WHERE tx_hash > %s ORDER BY tx_hash ASC LIMIT %s", (start_after_hash,limit))
else:
cursor.execute("SELECT tx_hash, rtl_dedup, to_address FROM tx_rtl_part_b_dedup ORDER BY tx_hash ASC LIMIT %s", (limit,))
transactions = cursor.fetchall()
conn_pool.putconn(conn)
return transactions
def setup_directories(tx_hash):
"""Sets up directories for output based on the transaction hash."""
output_folder = os.path.join('./facts_test', f'fact_{tx_hash}')
if not os.path.exists(output_folder):
os.makedirs(output_folder)
return output_folder
# TODO 3: Process transactions
def process_transaction(tx_hash, rtl, to_address, output_folder, conn_pool):
"""Processes a transaction and exports the facts."""
start_time = time.time()
try:
rtl_file_like = io.StringIO(rtl)
efg = tac_efg.TACGraph.from_opcode(rtl_file_like)
# print(f"this is the efg content,{efg}")
# 假设 export 操作是耗时操作,需要放在超时检测中
if time.time() - start_time < timeout_seconds:
exporter.EFGTsvExporter(efg).export(output_dir=output_folder, out_opcodes=target_opcodes)
facts_file_path = os.path.join(output_folder, 'sc_addr.facts')
with open(os.path.join(output_folder, 'sc_addr.facts'), 'w') as f:
f.write(to_address)
else:
raise TimeoutError("Processing exceeded time limit.")
processing_time = time.time() - start_time
print(f"Processed {tx_hash} in {processing_time:.2f} seconds")
update_progress(tx_hash, conn_pool)
except TimeoutError as te:
log_error(tx_hash, str(te), conn_pool)
except Exception as e:
logging.error(f"Error processing {tx_hash}: {e}", exc_info=True)
log_error(tx_hash, str(e), conn_pool)
traceback.print_exc()
# TODO 5: Setup and start threads
def start_processing_thread(conn_pool, num_threads=1):
"""Starts a pool of threads to process transactions."""
threads = []
for _ in range(num_threads):
thread = threading.Thread(target=file_processing_worker, args=(conn_pool, pbar))
thread.start()
threads.append(thread)
return threads
# Ensure global constants are defined
timeout_seconds = 40 # Example: 30 seconds timeout for processing each transaction
# TODO 6: Main execution block
if __name__ == "__main__":
last_processed_hash = get_last_processed_tx_hash(conn_pool)
transactions = fetch_transactions(conn_pool, start_after_hash=last_processed_hash)
file_queue = queue.Queue()
# Prepare tqdm progress bar
pbar = tqdm(total=len(transactions), desc='Processing Transactions', unit='tx')
# Update your file_processing_worker function to update tqdm progress upon each task completion
# TODO 4: Set threading and parallel processing mechanisms
def file_processing_worker(conn_pool, pbar):
"""Thread worker function that processes files from the queue."""
while True:
tx_data = file_queue.get()
if tx_data is None: # Correctly handle the queue's end signal
file_queue.task_done()
break
tx_hash, rtl, to_address = tx_data
print(f"Now processing {tx_hash}")
output_folder = setup_directories(tx_hash)
process_transaction(tx_hash, rtl, to_address, output_folder, conn_pool)
file_queue.task_done()
pbar.update(1)
# Enqueue transactions
for tx in transactions:
file_queue.put(tx)
# Start threads
threads = start_processing_thread(conn_pool, num_threads=1)
# Wait for all items in the queue to be processed
file_queue.join()
# Stop all threads
for _ in range(len(threads)):
file_queue.put(None)
for thread in threads:
thread.join()
print("Processing complete.")
+50
View File
@@ -0,0 +1,50 @@
import subprocess
import time
import os
import csv
def get_last_processed_filename(progress_file_path):
try:
with open(progress_file_path, "r") as file:
return file.read().strip()
except FileNotFoundError:
return None
def set_last_processed_filename(progress_file_path, filename):
with open(progress_file_path, "w") as file:
file.write(filename)
def is_all_processed(input_folder, progress_file_path):
last_processed = get_last_processed_filename(progress_file_path)
if not last_processed:
return False
filenames = sorted(os.listdir(input_folder))
return last_processed not in filenames or filenames.index(last_processed) == len(filenames) - 1
def log_error(error_log_file, transaction, error_message):
with open(error_log_file, 'a', newline='') as file:
writer = csv.writer(file)
writer.writerow([transaction, error_message])
def run_script(input_folder, progress_file_path, error_log_file):
while not is_all_processed(input_folder, progress_file_path):
last_processed = get_last_processed_filename(progress_file_path)
try:
subprocess.run(['python3', 'analyze3.py'], check=True)
except subprocess.CalledProcessError as e:
print("Script crashed. Restarting...")
log_error(error_log_file, last_processed if last_processed else "unknown", "Script crashed: " + str(e))
filenames = sorted(os.listdir(input_folder))
next_file_index = filenames.index(last_processed) + 1 if last_processed in filenames else 0
if next_file_index < len(filenames):
next_file = filenames[next_file_index]
set_last_processed_filename(progress_file_path, next_file)
time.sleep(1)
print("All files have been processed.")
if __name__ == "__main__":
input_folder = "/home/yangyi/database/smart_contract-dataset/Tx_tuples"
progress_file_path = 'progress.txt'
error_log_file = 'error_log.csv'
run_script(input_folder, progress_file_path, error_log_file)
+47
View File
@@ -0,0 +1,47 @@
import sys
import json
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python3 reconstruct.py <path_to_log_file>")
sys.exit(1)
log_file = sys.argv[1]
codes = {
}
vars = {
}
with open(log_file, 'r') as f:
lines = f.readlines()
idx = 0
for line in lines:
if line.startswith('INFO:root:OP:'):
line = line.replace("INFO:root:OP:", "").strip()
stacks, op = line.split("->")
stacks = [item.strip() for item in stacks.replace("[", "").replace("]", "").split(",")]
op = op.strip()
ops = op.split(" ")
pc, opcode, args = ops[0], ops[1], ops[2:]
codes[idx] = {
"pc": pc,
"opcode": opcode,
"args": args,
"stacks": stacks
}
idx += 1
elif line.startswith("WARNING:root:"):
line = line.replace("WARNING:root:", "").strip()
name, value = line.split("=")
name = name.strip()
value = value.strip()
vars[name] = value
# Apply vars
for pc, code in codes.items():
for i, arg in enumerate(code["stacks"]):
if arg in vars:
code["stacks"][i] = vars[arg]
import json
# print(vars)
print(json.dumps(codes, indent=4))
+98
View File
@@ -0,0 +1,98 @@
// BSD 3-Clause License
//
// Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Note: Reentrancy
#include "types.dl"
#include "opcode.dl"
// Filter and obtain the pairs (x, y) with the dependency relationship
.decl depends(x:Variable, y:Variable)
depends(x, x) :-
use(x, _, _, _, _, _).
depends(x, x) :-
def(x, _, _, _, _).
depends(x, y) :-
def(x, stmt, _, x_cd, x_cn),
use(y, stmt, _, _, x_cd, x_cn).
depends(x, z) :-
depends(x, y),
depends(y, z).
// Collect all the call related opcodes info
.decl CallOperator(target_val:Value, call_loc:number, call_cd:number, call_cn:number) //zoey - target_val: target address, call_loc: call location, call_cd: call depth, call_cn: call number
CallOperator(target_val, call_loc, call_cd, call_cn) :-
(op_CALL(_, _, target_var, _, _, _, _, _, _, call_loc, call_cd, call_cn);
op_STATICCALL(_, _, target_var, _, _, _, _, _, call_loc, call_cd, call_cn); //zoey - staticcall for testing why it is wrong
// op_STATICCALL(_, _, target_var, _, _, _, _, _, _, call_loc, call_cd, call_cn);
op_DELEGATE(_, _, target_var, _, _, _, _, _, _, call_loc, call_cd, call_cn);
op_CALLCODE(_, _, target_var, _, _, _, _, _, _, call_loc, call_cd, call_cn)),
value(target_var, target_val).
// Check the second condition. A opcode sstore needs to in the returned call of the call where the sstore appears.
.decl ReenResult(sload_loc: number, jumpi_loc:number, sload_depth:number, sload_call_number:number,
sstore_loc:number, sstore_depth:number, sstore_cn:number, sstore_sc_addr:Value, sload_sc_addr:Value)
.output ReenResult
ReenResult(sload_loc, jumpi_loc, sload_depth, sload_call_number, sstore_loc, sstore_depth, sstore_cn, sstore_sc_addr, sload_sc_addr):-
// Step1: Get sload
// a) its call depth must be greater than 2
// b) it must have the same call depth and call number with jumpi, which depends on sload
op_SLOAD(_, sload_addr_var, sload_val_var, sload_loc, sload_depth, sload_call_number),
sload_depth > 2,
op_JUMPI(_, _, jumpi_cond_var, jumpi_loc, sload_depth, sload_call_number),
depends(jumpi_cond_var, sload_val_var),
// Step2: Get sstore
// a) sstore call depth must be less than sload call depth
op_SSTORE(_, sstore_addr_var, _, sstore_loc, sstore_depth, sstore_cn),
sload_depth >= sstore_depth + 2,
sstore_loc > sload_loc,
value(sstore_addr_var, sstore_addr_var_val),
value(sload_addr_var, sload_addr_var_val),
sstore_addr_var_val = sload_addr_var_val,
// Step3: Make sure sload and sstore are from the same contract
((sstore_depth = 1,
sc_addr(sstore_sc_addr));
(sstore_depth != 1,
CallOperator(sstore_sc_addr, _, sstore_depth-1 , call_cn),
call_cn >= sstore_cn)
),
CallOperator(sload_sc_addr, _, sload_depth-1 , call_cn),
call_cn >= sload_call_number,
sstore_sc_addr = sload_sc_addr.
+79
View File
@@ -0,0 +1,79 @@
// BSD 3-Clause License
//
// Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Note: Unchecked return call value
#include "types.dl"
#include "opcode.dl"
// Filter and obtain the pairs (x, y) with the dependency relationship
.decl depends(x:Variable, y:Variable)
depends(x, x) :-
use(x, _, _, _, _, _).
depends(x, x) :-
def(x, _, _, _, _).
depends(x, y) :-
def(x, stmt, _, x_cd, x_cn),
use(y, stmt, _, _, x_cd, x_cn).
depends(x, z) :-
depends(x, y),
depends(y, z).
// Step1: Obtain all the call and their return values
.decl Step1(gas_var:Variable, target_var:Variable, value_var:Variable, call_success:Variable, call_loc:number, call_cn:number)
.output Step1
Step1(gas_var, target_var, value_var, call_success, call_loc, call_cn) :-
op_CALL(_, gas_var, target_var, value_var, _, _, _, _, call_success, call_loc, 1, call_cn).
// Step2: Find all the calls with jumpi, which means the call is checked in some if-else conditions
.decl Step2(call_loc:number, call_cn:number)
.output Step2
Step2(call_loc, call_cn) :-
Step1(_, _, _, call_success, call_loc, call_cn),
op_JUMPI(_, _, jumpi_cond_var, jumpi_loc, 1, call_cn),
jumpi_loc > call_loc,
depends(jumpi_cond_var, call_success).
// Step3: Get all the calls and remove ones that are checked so that we can get all the unchecked calls
.decl Step3(call_loc:number, call_cn:number, cs_val:Value, gas_val:Value, target_val:Value, value_val:Value)
.output Step3
Step3(call_loc, call_cn, cs_val, gas_val, target_val, value_val) :-
Step1(gas_var, target_var, value_var, cs_success, call_loc, call_cn),
!Step2(call_loc, _),
value(cs_success, cs_val),
value(gas_var, gas_val),
value(target_var, target_val),
value(value_var, value_val).
+72
View File
@@ -0,0 +1,72 @@
// BSD 3-Clause License
//
// Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Note: Failed send
#include "types.dl"
#include "opcode.dl"
// Filter and obtain the pairs (x, y) with the dependency relationship
.decl depends(x:Variable, y:Variable)
depends(x, x) :-
use(x, _, _, _, _, _).
depends(x, x) :-
def(x, _, _, _, _).
depends(x, y) :-
def(x, stmt, _, x_cd, x_cn),
use(y, stmt, _, _, x_cd, x_cn).
depends(x, z) :-
depends(x, y),
depends(y, z).
.decl FailedSendResult(revert_loc:number, call_loc:number, target_val:Value, value_val:Value)
.output FailedSendResult
FailedSendResult(revert_loc, call_loc, target_val, value_val):-
// Step 2: The result should be revert.
// Tip: Step 2 is moved to the front is to optimize the performance
op_REVERT(_, _, _, revert_loc, 1, _),
// Step 1: There should be a send, which includes a call opcode and value is not null. In addition, the call return value should be failure.
op_CALL(_, _, target_var, value_var, _, _, _, _, call_success, call_loc, 1, _),
!value(value_var, "0x0"),
value(call_success, "0x0"),
// Step 3: The call return value should be checked
op_JUMPI(_, _, jumpi_cond_var, jumpi_loc, 1, _),
jumpi_loc > call_loc,
revert_loc > jumpi_loc,
depends(jumpi_cond_var, call_success),
value(target_var, target_val),
value(value_var, value_val).
+64
View File
@@ -0,0 +1,64 @@
// BSD 3-Clause License
//
// Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Note: Timestamp Dependence, similar for the other dependeces, such as block state
#include "types.dl"
#include "opcode.dl"
// Filter and obtain the pairs (x, y) with the dependency relationship
.decl depends(x:Variable, y:Variable)
depends(x, x) :-
use(x, _, _, _, _, _).
depends(x, x) :-
def(x, _, _, _, _).
depends(x, y) :-
def(x, stmt, _, x_cd, x_cn),
use(y, stmt, _, _, x_cd, x_cn).
depends(x, z) :-
depends(x, y),
depends(y, z).
.decl TimestampDependenceResult(bsd_loc:number, bsd_val:Value, jumpi_loc:number)
.output TimestampDependenceResult
TimestampDependenceResult(bsd_loc, bsd_val, jumpi_loc):-
// Step1: Obtain all the timestamp opcodes
op_TIMESTAMP(_, bsd_var, bsd_loc, 1, _),
// Step2: This opcode should determine a path
op_JUMPI(_, _, jumpi_cond_var, jumpi_loc, _, _),
jumpi_loc > bsd_loc,
depends(jumpi_cond_var, bsd_var),
value(bsd_var, bsd_val).
+85
View File
@@ -0,0 +1,85 @@
// BSD 3-Clause License
//
// Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Note: Unsecured balance
#include "types.dl"
#include "opcode.dl"
.decl depends(x:Variable, y:Variable)
depends(x, x) :-
use(x, _, _, _, _, _).
depends(x, x) :-
def(x, _, _, _, _).
depends(x, y) :-
def(x, stmt, _, x_cd, x_cn),
use(y, stmt, _, _, x_cd, x_cn).
depends(x, z) :-
depends(x, y),
depends(y, z).
// no caller, just call
.decl Situation1(c:number)
.output Situation1
Situation1(c) :-
c=count:{op_CALLER(_, _, _, 1, _)}.
// Step1: detect all the call satisifies the conditions where the target address is not CONST.
.decl Step1( target_var:Variable, calldataload_var:Variable, value_var:Variable, call_success:Variable, call_loc:number, callvalue_opcode:Opcode)
.output Step1
Step1(target_var, calldataload_var, value_var, call_success, call_loc, callvalue_opcode) :-
op_CALL(_, _, target_var, value_var, _, _, _, _, call_success, call_loc, 1, _),
op_CALLDATALOAD(_, _, calldataload_var, calldataload_loc, 1, _),
depends(target_var, calldataload_var),
call_loc > calldataload_loc,
!value(value_var, "0x0"),
def(value_var, _, call_value_loc, _, _),
op(_, callvalue_opcode, call_value_loc),
callvalue_opcode != "CALLVALUE".
// Step3: find all the calls removing all the calls in Step2
.decl Step3(call_loc:number, calldataload_var:Variable, calvalue_opcode:Opcode, target_val:Value, value_val:Value, success_val:Value, count_caller:number)
.output Step3
Step3(call_loc, calldataload_var, callvalue_opcode, target_val, value_val, success_val, count_caller) :-
Step1(target_var, calldataload_var, value_var, call_success, call_loc, callvalue_opcode),
Situation1(count_caller),
(count_caller = 0;
count_caller = 1,
1 = count:{use(target_var, _, _, _, 1, _)},
op_CALLER(_, target_var, _, 1, _);
count_caller = 1,
0 = count:{use(target_var, _, _, _, 1, _)}),
value(target_var, target_val),
value(value_var, value_val),
value(call_success, success_val).
+75
View File
@@ -0,0 +1,75 @@
// BSD 3-Clause License
//
// Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Note: Misuse of origin
#include "types.dl"
#include "opcode.dl"
.decl depends(x:Variable, y:Variable)
depends(x, x) :-
use(x, _, _, _, _, _).
depends(x, x) :-
def(x, _, _, _, _).
depends(x, y) :-
def(x, stmt, _, x_cd, x_cn),
use(y, stmt, _, _, x_cd, x_cn).
depends(x, z) :-
depends(x, y),
depends(y, z).
// Step 1: there is some origin opcode, which is correctly compared with the msg.sender.
.decl Step1(ori_loc:number, ori_val:Value, caller_loc:number, caller_val:Value, used_loc:number, used_op:Opcode)
.output Step1
Step1(ori_loc, ori_val, caller_loc, caller_val, used_loc, used_op) :-
op_ORIGIN(_, ori_var, ori_loc, 1, _),
use(ori_var, _, _, used_loc, 1, _),
op_CALLER(_, caller_var, caller_loc, 1, _),
use(caller_var, _, _, used_loc, 1, _),
op(_, used_op, used_loc),
value(ori_var, ori_val),
value(caller_var, caller_val).
// Step 2: remove the origins in the step 1
// Step 3: for the left origins, they should be used by a jumpi or a sstore.
.decl MisuseOriginResult(ori_loc:number, ori_val:Value, loc:number, opcode:Opcode)
.output MisuseOriginResult
MisuseOriginResult(ori_loc, ori_val, loc, opcode):-
op_ORIGIN(_, ori_var, ori_loc, 1, _),
!Step1(ori_loc, _ ,_ , _ ,_, _),
(op_JUMPI(_, _, var, loc, 1, _);
op_SSTORE(_, _, var, loc, 1, _)),
loc> ori_loc,
depends(var, ori_var),
value(ori_var, ori_val),
op(_, opcode, loc).
+68
View File
@@ -0,0 +1,68 @@
// BSD 3-Clause License
//
// Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Note: Suicidal contract
#include "types.dl"
#include "opcode.dl"
.decl depends(x:Variable, y:Variable)
depends(x, x) :-
use(x, _, _, _, _, _).
depends(x, x) :-
def(x, _, _, _, _).
depends(x, y) :-
def(x, stmt, _, x_cd, x_cn),
use(y, stmt, _, _, x_cd, x_cn).
depends(x, z) :-
depends(x, y),
depends(y, z).
//Call depth is restricted to 1.
.decl Step1(self_loc:number, caller_loc:number, jumpi_loc:number)
.output Step1
Step1(self_loc, caller_loc, jumpi_loc) :-
op_SELFDESTRUCT(_, _, self_loc, 1, _),
op_CALLER(_, caller_addr, caller_loc, 1, _),
op_JUMPI(_, _, jumpi_cond_var, jumpi_loc, 1, _),
jumpi_loc > caller_loc,
self_loc > jumpi_loc,
depends(jumpi_cond_var, caller_addr).
.decl SuicidalResult(benefit_val:Value, self_loc:number)
.output SuicidalResult
SuicidalResult(benefit_val, self_loc):-
op_SELFDESTRUCT(_, benefit_addr, self_loc, 1, _),
!Step1(self_loc, _, _),
value(benefit_addr, benefit_val).
+62
View File
@@ -0,0 +1,62 @@
#include "types.dl"
#include "opcode.dl"
.decl depends(x:Variable, y:Variable)
depends(x, x) :-
use(x, _, _, _, _, _).
depends(x, x) :-
def(x, _, _, _, _).
depends(x, y) :-
def(x, stmt, _, x_cd, x_cn),
use(y, stmt, _, _, x_cd, x_cn).
depends(x, z) :-
depends(x, y),
depends(y, z).
//Helper function: gas-dependent.
//This line of code is considered gas dependent if it uses some variables that depend on the gas operation.
.decl GasDep(gas_var:Variable, loc:number)
GasDep(gas_var, loc) :-
use(var, _, _, loc, _, _),
depends(var, gas_var),
def(gas_var, _, gas_loc, _, _),
op(_, "GAS", gas_loc).
//Requirement 1. There is an Ether transfer. In the transaction, there should be at least one CALL related opcode, whose Ether value is greater than zero and call return value is `success`.
.decl CallOperator(gas_var:Variable, value_var:Variable, call_loc:number, call_cd:number, call_cn:number)
CallOperator(gas_var, value_var, call_loc, call_cd, call_cn) :-
(op_CALL(_, gas_var, _, value_var, _, _, _, _, success_var, call_loc, call_cd, call_cn);
op_STATICCALL(_, gas_var, _, value_var, _, _, _, _, success_var, call_loc, call_cd, call_cn);
op_DELEGATE(_, gas_var, _, value_var, _, _, _, _, success_var, call_loc, call_cd, call_cn);
op_CALLCODE(_, gas_var, _, value_var, _, _, _, _, success_var, call_loc, call_cd, call_cn)),
value(success_var, "0x1"),
!value(value_var, "0x0").
//Requirement 2: There is an SSTORE after the external call returns, i.e., there is a state change (i.e., updates of a storage variable) after the call. Besides, the value of ether transferred should depend on the storage variable.
.decl NormalReen(gas_var:Variable, call_loc:number, sstore_loc:number, call_cd:number, call_cn:number)
NormalReen(gas_var, call_loc, sstore_loc, call_cd, call_cn) :-
CallOperator(gas_var, value_var, call_loc, call_cd, call_cn),
op_SSTORE(_, sstore_addr_var, _, sstore_loc, sstore_depth, sstore_cn),
call_cd = sstore_depth,
call_cn = sstore_cn,
sstore_loc > call_loc,
depends(value_var, sstore_addr_var).
//Gas-dependent Reentrancy
//Requirement 3.1 : gas-dependent
.decl GasDepReen(gas_var:Variable, call_loc:number, sstore_loc:number, call_cd:number, call_cn:number)
.output GasDepReen
GasDepReen(gas_var, call_loc, sstore_loc, call_cd, call_cn) :-
NormalReen(gas_var, call_loc, sstore_loc, call_cd, call_cn),
GasDep(gas_var, call_loc).
//Reentrancy with Constant Gas
//Requirement 3.2 : with constant gas
.decl GasConstantReen(gas_var:Variable, call_loc:number, sstore_loc:number, call_cd:number, call_cn:number)
.output GasConstantReen
GasConstantReen(gas_var, call_loc, sstore_loc, call_cd, call_cn) :-
NormalReen(gas_var, call_loc, sstore_loc, call_cd, call_cn),
!GasDep(gas_var, call_loc).
+139
View File
@@ -0,0 +1,139 @@
// BSD 3-Clause License
//
// Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// INPUT
.decl edge(h:Statement, t:Statement)
.decl def(var:Variable, stmt:Statement, loc:number, cd:number, cn:number)
.decl use(var:Variable, stmt:Statement, i:number, loc:number, cd:number, cn:number)
.decl op(stmt:Statement, op:Opcode, loc:number)
.decl value(var:Variable, val:Value)
.decl op_SLOAD(stmt:Statement, arg_loc:Variable, val:Variable, loc:number, call_depth: number, call_number:number)
.decl op_JUMPI(stmt:Statement, dest:Variable, cond:Variable, loc:number, call_depth:number, call_number:number)
.decl op_SSTORE(stmt:Statement, arg_addr:Variable, arg_val:Variable, loc:number, call_depth: number, call_number:number)
.decl op_LT(stmt:Statement, left_var:Variable, right_var:Variable, res_var:Variable, loc:number, call_depth:number, call_number:number)
.decl op_GT(stmt:Statement, left_var:Variable, right_var:Variable, res_var:Variable, loc:number, call_depth:number, call_number:number)
.decl op_SLT(stmt:Statement, left_var:Variable, right_var:Variable, res_var:Variable, loc:number, call_depth:number, call_number:number)
.decl op_SGT(stmt:Statement, left_var:Variable, right_var:Variable, res_var:Variable, loc:number, call_depth:number, call_number:number)
.decl op_EQ(stmt:Statement, left_var:Variable, right_var:Variable, res_var:Variable, loc:number, call_depth:number, call_number:number)
.decl op_SUB(stmt:Statement, left_var:Variable, right_var:Variable, res_var:Variable, loc:number, call_depth:number, call_number:number)
.decl op_ADD(stmt:Statement, left_var:Variable, right_var:Variable, res_var:Variable, loc:number, call_depth:number, call_number:number)
.decl op_MUL(stmt:Statement, left_var:Variable, right_var:Variable, res_var:Variable, loc:number, call_depth:number, call_number:number)
.decl op_EXP(stmt:Statement, left_var:Variable, right_var:Variable, res_var:Variable, loc:number, call_depth:number, call_number:number)
.decl op_CALLDATALOAD(stmt:Statement, index:Variable, res:Variable, loc:number, call_depth:number, call_number:number)
.decl op_EXTCODESIZE(stmt:Statement, addr:Variable, size:Variable, loc:number, call_depth:number, call_number:number)
.decl op_TIMESTAMP(stmt:Statement, res_var:Variable, loc:number, call_depth:number, call_number:number)
.decl op_NUMBER(stmt:Statement, res_var:Variable, loc:number, call_depth:number, call_number:number)
.decl op_COINBASE(stmt:Statement, res_var:Variable, loc:number, call_depth:number, call_number:number)
.decl op_DIFFICULTY(stmt:Statement, res_var:Variable, loc:number, call_depth:number, call_number:number)
.decl op_GASLIMIT(stmt:Statement, res_var:Variable, loc:number, call_depth:number, call_number:number)
.decl op_ORIGIN(stmt:Statement, res_var:Variable, loc:number, call_depth:number, call_number:number)
.decl op_CALL(stmt:Statement, gas:Variable, target:Variable, value:Variable, data_start:Variable, data_length:Variable, return_start:Variable, return_length:Variable, success:Variable, loc:number, call_depth:number, call_number:number)
.decl op_CALLCODE(stmt:Statement, gas:Variable, target:Variable, value:Variable, data_start:Variable, data_length:Variable, return_start:Variable, return_length:Variable, success:Variable, loc:number, call_depth:number, call_number:number)
.decl op_DELEGATECALL(stmt:Statement, gas:Variable, target:Variable, value:Variable, data_start:Variable, data_length:Variable, return_start:Variable, return_length:Variable, success:Variable, loc:number, call_depth:number, call_number:number)
.decl op_DELEGATE(stmt:Statement, gas:Variable, target:Variable, value:Variable, data_start:Variable, data_length:Variable, return_start:Variable, return_length:Variable, success:Variable, loc:number, call_depth:number, call_number:number)
//.decl op_STATICCALL(stmt:Statement, gas:Variable, target:Variable, value:Variable, data_start:Variable, data_length:Variable, return_start:Variable, return_length:Variable, success:Variable, loc:number, call_depth:number, call_number:number)
.decl op_STATICCALL(stmt:Statement, gas:Variable, target:Variable, data_start:Variable, data_length:Variable, return_start:Variable, return_length:Variable, success:Variable, loc:number, call_depth:number, call_number:number) // zoey - op_STATICCALL for testing
.decl op_SELFDESTRUCT(stmt:Statement, addr:Variable, loc:number, call_depth:number, call_number:number)
.decl op_REVERT(stmt:Statement, var1:Variable, var2:Variable, loc:number, call_depth:number, call_number:number)
.decl sc_addr(address:Value)
.decl op_STOP(stmt:Statement, loc:number, call_depth:number, call_number:number)
.decl op_RETURN(stmt:Statement, offset:Variable, length:Variable, loc:number, call_depth:number, call_number:number)
.decl op_CALLER(stmt:Statement, caller:Variable, loc:number, call_depth:number, call_number:number)
.input edge
.input def
.input use
.input op
.input value
.input op_SLOAD
.input op_JUMPI
.input op_SSTORE
.input op_LT
.input op_GT
.input op_SLT
.input op_SGT
.input op_EQ
.input op_SUB
.input op_ADD
.input op_MUL
.input op_EXP
.input op_CALLDATALOAD
.input op_EXTCODESIZE
.input op_TIMESTAMP
.input op_NUMBER
.input op_COINBASE
.input op_DIFFICULTY
.input op_GASLIMIT
.input op_ORIGIN
.input op_CALL
.input op_CALLCODE
.input op_DELEGATECALL
.input op_DELEGATE
.input op_STATICCALL
.input op_SELFDESTRUCT
.input op_REVERT
.input sc_addr
.input op_STOP
.input op_RETURN
.input op_CALLER
+33
View File
@@ -0,0 +1,33 @@
// BSD 3-Clause License
//
// Copyright (c) 2016, 2017, The University of Sydney. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.type Statement
.type Variable
.type Opcode
.type Value
+358
View File
@@ -0,0 +1,358 @@
# BSD 3-Clause License
#
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""cfg.py: Base classes for representing Control Flow Graphs (CFGs)"""
"""控制流图抽象基类。
类定义了许多方法,可以添加和删除基本块,添加和删除控制流边,查找基本块等。
这个类还定义了一个虚拟的“访问者”接口,使得可以对控制流图进行遍历。
这个类的属性包括:
blocks:存储所有基本块对象的列表。
root:指向控制流图的根基本块对象的引用。
这个类的方法包括:
remove_block():从图中删除一个基本块,并断开所有与其相连的边。
add_block():向图中添加一个基本块。
has_edge():判断一个从一个基本块到另一个基本块的边是否存在。
remove_edge():删除一个从一个基本块到另一个基本块的边。
add_edge():添加一个从一个基本块到另一个基本块的边。
get_blocks_by_pc():返回包含给定程序计数器值的基本块列表。
get_block_by_ident():返回具有指定标识符的基本块(如果存在)。
recalc_preds():重新计算基本块的前继列表。
reaches():确定一个基本块是否可以到达任何给定的目标基本块。
transitive_closure():返回从输入地址可到达的块列表。
remove_unreachable_blocks():删除从程序入口点不可达的所有块。
edge_list():返回一个包含图中所有边的元组列表。"""
import abc
import typing as t
import src.patterns as patterns
class ControlFlowGraph(patterns.Visitable):
"""Abstract base class for a Control Flow Graph (CFG)"""
__STR_SEP = "\n\n-----\n\n"
@abc.abstractmethod
def __init__(self):
"""Create a new empty ControlFlowGraph"""
self.blocks = []
"""List of BasicBlock objects"""
self.root = None
"""The root BasicBlock object, or None for the empty graph"""
def __len__(self):
return len(self.blocks)
def __str__(self):
return self.__STR_SEP.join(str(b) for b in self.blocks)
def remove_block(self, block: 'BasicBlock') -> None:
"""
Remove the given block from the graph, disconnecting all incident edges.
"""
if block == self.root:
self.root = None
for p in list(block.preds):
self.remove_edge(p, block)
for s in list(block.succs):
self.remove_edge(block, s)
self.blocks.remove(block)
def add_block(self, block: 'BasicBlock') -> None:
"""
Add the given block to the graph, assuming it does not already exist.
"""
if block not in self.blocks:
self.blocks.append(block)
def has_edge(self, head: 'BasicBlock', tail: 'BasicBlock') -> bool:
"""
True iff the edge between head and tail exists in the graph.
"""
return tail in head.succs
def remove_edge(self, head: 'BasicBlock', tail: 'BasicBlock') -> None:
"""Remove the CFG edge that goes from head to tail."""
if tail in head.succs:
head.succs.remove(tail)
if head in tail.preds:
tail.preds.remove(head)
def add_edge(self, head: 'BasicBlock', tail: 'BasicBlock'):
"""Add a CFG edge that goes from head to tail."""
if tail not in head.succs:
head.succs.append(tail)
if head not in tail.preds:
tail.preds.append(head)
def get_blocks_by_pc(self, pc: int) -> t.List['BasicBlock']:
"""Return the blocks whose spans include the given program counter value."""
blocks = []
for block in self.blocks:
if block.entry <= pc <= block.exit:
blocks.append(block)
return blocks
def get_block_by_ident(self, ident: str) -> 'BasicBlock':
"""Return the block with the specified identifier, if it exists."""
for block in self.blocks:
if block.ident() == ident:
return block
return None
def recalc_preds(self) -> None:
"""
Given a cfg where block successor lists are populated,
also repopulate the predecessor lists, after emptying them.
"""
for block in self.blocks:
block.preds = []
for block in self.blocks:
for successor in block.succs:
successor.preds.append(block)
def reaches(self, block: 'BasicBlock', dests: t.Iterable['BasicBlock']) -> bool:
"""
Determines if a block can reach any of the given destination blocks
Args:
block: Any block that is part of the tac_cfg the class was initialised with
dests: A list of dests to check reachability with
"""
if block in dests:
return True
queue = [block]
traversed = []
while queue:
curr_block = queue.pop()
traversed.append(curr_block)
for b in dests:
if b in curr_block.succs:
return True
for b in curr_block.succs:
if b not in traversed:
queue.append(b)
return False
def transitive_closure(self, origin_addresses: t.Iterable[int]) \
-> t.Iterable['BasicBlock']:
"""
Return a list of blocks reachable from the input addresses.
Args:
origin_addresses: the input addresses blocks from which are reachable
to be returned.
"""
# Populate the work queue with the origin blocks for the transitive closure.
queue = []
for address in origin_addresses:
for block in self.get_blocks_by_pc(address):
if block not in queue:
queue.append(block)
reached = []
# Follow all successor edges until we can find no more new blocks.
while queue:
block = queue.pop()
reached.append(block)
for succ in block.succs:
if succ not in queue and succ not in reached:
queue.append(succ)
return reached
def remove_unreachable_blocks(self, origin_addresses: t.Iterable[int] = [0]) \
-> t.Iterable['BasicBlock']:
"""
Remove all blocks not reachable from the program entry point.
NB: if not all jumps have been resolved, unreached blocks may actually
be reachable.
Args:
origin_addresses: default value: [0], entry addresses, blocks from which
are unreachable to be deleted.
Returns:
An iterable of the blocks which were removed.
"""
reached = self.transitive_closure(origin_addresses)
removed = []
for block in list(self.blocks):
if block not in reached:
removed.append(block)
self.remove_block(block)
return removed
def edge_list(self) -> t.Iterable[t.Tuple['BasicBlock', 'BasicBlock']]:
"""
Returns:
a list of the CFG's edges, with each edge in the form
`(pred, succ)` where pred and succ are object references.
"""
return [(p, s) for p in self.blocks for s in p.succs]
def sorted_traversal(self, key=lambda b: b.entry, reverse=False) -> t.Generator['BasicBlock', None, None]:
"""
Generator for a sorted shallow copy of BasicBlocks contained in this graph.
Args:
key: A function of one argument that is used to extract a comparison key
from each block. By default, the comparison key is
:obj:`BasicBlock.entry`.
reverse: If set to `True`, then the blocks are sorted as if each
comparison were reversed. Default is `False`.
Returns:
A generator of :obj:`BasicBlock` objects, yielded in order according to
`key` and `reverse`.
"""
# Create a new list of sorted blocks and yield from it
yield from sorted(self.blocks, key=key, reverse=reverse)
def accept(self, visitor: patterns.Visitor,
generator: t.Generator['BasicBlock', None, None] = None):
"""
Visitor design pattern: accepts a Visitor instance and visits every node
in the CFG in an arbitrary order.
Args:
visitor: instance of a Visitor
generator: generator from which :obj:`BasicBlock` objects will be
retrieved when recursing. By default the blocks are recursed in
an arbitrary order.
"""
super().accept(visitor)
generator = generator or self.blocks
if len(self.blocks) > 0 and visitor.can_visit(type(self.blocks[0])):
for b in generator:
b.accept(visitor)
@property
def has_unresolved_jump(self) -> bool:
"""True iff any block in this cfg contains an unresolved jump."""
return any(b.has_unresolved_jump for b in self.blocks)
class BasicBlock(patterns.Visitable):
"""
Abstract base class for a single basic block (node) in a CFG. Each block has
references to its predecessor and successor nodes in the graph structure.
A BasicBlock must contain exactly one entry point at the start and
exactly one exit point at the end, with no branching in between.
That is, program flow must be linear/sequential within a basic block.
Args:
entry (int, default None): entry index.
exit (int, default None): exit index.
Raises:
ValueError: if entry or exit is a negative int.
"""
_STR_SEP = "---"
@abc.abstractmethod
def __init__(self, entry: int = None, exit: int = None):
if entry is not None and entry < 0:
raise ValueError("entry must be a positive integer or zero")
if exit is not None and exit < 0:
raise ValueError("exit must be a positive integer or zero")
self.entry = entry
"""Index of the first operation contained in this node."""
self.exit = exit
"""Index of the last operation contained in this node."""
self.preds = []
"""List of nodes which pass control to this node (predecessors)."""
self.succs = []
"""List of nodes which receive control from this node (successors)."""
self.has_unresolved_jump = False
"""True if the node contains a jump whose destination is a variable."""
self.ident_suffix = ""
"""
Extra information to be appended to this block's identifier.
Used, for example, to differentiate duplicated blocks.
"""
def __len__(self):
"""Returns the number of lines of code contained within this block."""
if self.exit is None or self.entry is None:
return 0
return self.exit - self.entry
def __str__(self):
entry, exit = map(lambda n: hex(n) if n is not None else 'Unknown',
(self.entry, self.exit))
b_id = self.ident() if self.entry is not None else "Unidentified"
head = "Block {}\n[{}:{}]".format(b_id, entry, exit)
pred = "Predecessors: [{}]".format(", ".join(b.ident() for b in sorted(self.preds)))
succ = "Successors: [{}]".format(", ".join(b.ident() for b in sorted(self.succs)))
unresolved = "\nHas unresolved jump." if self.has_unresolved_jump else ""
return "\n".join([head, self._STR_SEP, pred, succ]) + unresolved
def __lt__(self, other):
"""
Compare BasicBlocks based on their entry program counter values.
"""
if self.entry is None or other.entry is None:
return False
return (self.entry < other.entry) or \
(self.entry == other.entry and self.ident_suffix < other.ident_suffix)
def ident(self) -> str:
"""
Returns this block's unique identifier, which is its entry value.
Raises:
ValueError if the block's entry is None.
"""
if self.entry is None:
raise ValueError("Can't compute ident() for block with unknown entry")
return hex(self.entry) + self.ident_suffix
+420
View File
@@ -0,0 +1,420 @@
# BSD 3-Clause License
#
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""opcodes.py: Definitions of all EVM opcodes, and related utility functions."""
class OpCode:
"""An EVM opcode."""
def __init__(self, name: str, code: int, pop: int, push: int):
"""
Args:
name (str): Human-readable opcode, such as opcode PUSH1.
code (int): The instruction byte itself.
pop (int): The number of stack elements this op pops.
push (int): The number of stack elements this op pushes.
"""
self.name = name
self.code = code
self.pop = pop
self.push = push
def stack_delta(self) -> int:
"""Return the net effect on the stack size of running this operation."""
return self.push - self.pop
def __str__(self) -> str:
return self.name
def __repr__(self) -> str:
return "<{0} object {1}, {2}>".format(
self.__class__.__name__,
hex(id(self)),
self.__str__()
)
def __eq__(self, other) -> bool:
return self.code == other.code
def __hash__(self) -> int:
return self.code.__hash__()
# Special cases for kind one, such as CALLVALUE
# Those opcodes do not need anything from stack, but will give related dynamic info
def is_kind_one(self) -> bool:
special = {ADDRESS.name, ORIGIN.name, CALLER.name, CALLVALUE.name, CALLDATASIZE.name,
CODESIZE.name, GASPRICE.name, RETURNDATASIZE.name, COINBASE.name, TIMESTAMP.name,
NUMBER.name, DIFFICULTY.name, GASLIMIT.name, PC.name, MSIZE.name, GAS.name}
return self.name in special
# Special cases for kind two, such as CALLDATALOAD
# Need one or more stack arguments and related dynamic info
def is_kind_two(self) -> bool:
special = {SHA3.name, BALANCE.name, CALLDATALOAD.name, EXTCODESIZE.name, BLOCKHASH.name}
return self.name in special
# Special cases for part of kind three load, SLOAD
def is_kind_three_load(self) -> bool:
special = {SLOAD.name}
return self.name in special
# Special cases for part of kind three store, like MSTORE
def is_kind_three_store_one(self) -> bool:
special = {MSTORE.name, MSTORE8.name, SSTORE.name}
return self.name in special
# Special cases for some other store operations, they are special
# since they do need some arguments from the stack and then get the related data
# to store them into the memory
def is_kind_three_store_two(self) -> bool:
special = {CALLDATACOPY.name, CODECOPY.name, EXTCODECOPY.name, RETURNDATACOPY.name}
return self.name in special
# Special cases for four call opcodes
def is_kind_four(self) -> bool:
special = {CALL.name, CALLCODE.name, DELEGATECALL.name, STATICCALL.name}
return self.name in special
def is_kind_five(self) -> bool:
special = {CREATE.name, CREATE2.name}
return self.name in special
def op_pc_gap(self) -> int:
if self.is_push():
return int(self.name[4]) + 1
else:
return 1
def is_push(self) -> bool:
"""Predicate: opcode is a push operation."""
return PUSH1.code <= self.code <= PUSH32.code
def is_swap(self) -> bool:
"""Predicate: opcode is a swap operation."""
return SWAP1.code <= self.code <= SWAP16.code
def is_dup(self) -> bool:
"""Predicate: opcode is a dup operation."""
return DUP1.code <= self.code <= DUP16.code
def is_log(self) -> bool:
"""Predicate: opcode is a log operation."""
return LOG0.code <= self.code <= LOG4.code
def is_missing(self) -> bool:
return self.code not in BYTECODES
def is_invalid(self) -> bool:
return (self.code == INVALID.code) or self.is_missing()
# Zoey - adding shift operation
# def is_shift(self) -> bool:
# """Predicate: opcode is a shift operation."""
# return SHL.code <= self.code <= SAR.code
def is_arithmetic(self) -> bool:
"""Predicate: opcode's result can be calculated from its inputs alone."""
return (ADD.code <= self.code <= SIGNEXTEND.code) or \
(LT.code <= self.code <= BYTE.code) or \
(SHA3.code <= self.code <= SHA3.code) \
# or self.is_shift() # Zoey - adding shift operation
def is_memory(self) -> bool:
"""Predicate: opcode operates on memory"""
return MLOAD.code <= self.code <= MSTORE8.code
def is_storage(self) -> bool:
"""Predicate: opcode operates on storage ('the tape')"""
return SLOAD.code <= self.code <= SSTORE.code
def is_call(self) -> bool:
"""Predicate: opcode calls an external contract"""
return self in (CALL, CALLCODE, DELEGATECALL, STATICCALL,)
def alters_flow(self) -> bool:
"""Predicate: opcode alters EVM control flow."""
return (self.code in (JUMP.code, JUMPI.code,)) or self.possibly_halts()
def is_exception(self) -> bool:
"""Predicate: opcode causes the EVM to throw an exception."""
return (self.code in (THROW.code, THROWI.code, REVERT.code)) \
or self.is_invalid()
def halts(self) -> bool:
"""Predicate: opcode causes the EVM to halt."""
halt_codes = (
STOP.code,
RETURN.code,
SELFDESTRUCT.code,
THROW.code,
REVERT.code,
)
return (self.code in halt_codes) or self.is_invalid()
def possibly_halts(self) -> bool:
"""Predicate: opcode MAY cause the EVM to halt. (halts + THROWI)"""
return self.halts() or self.code == THROWI.code
def push_len(self) -> int:
"""Return the number of bytes the given PUSH instruction pushes."""
return self.code - PUSH1.code + 1 if self.is_push() else 0
def log_len(self) -> int:
"""Return the number of topics the given LOG instruction includes."""
return self.code - LOG0.code if self.is_log() else 0
# Construct all EVM opcodes
# Arithmetic Ops and STOP
STOP = OpCode("STOP", 0x00, 0, 0)
ADD = OpCode("ADD", 0x01, 2, 1)
MUL = OpCode("MUL", 0x02, 2, 1)
SUB = OpCode("SUB", 0x03, 2, 1)
DIV = OpCode("DIV", 0x04, 2, 1)
SDIV = OpCode("SDIV", 0x05, 2, 1)
MOD = OpCode("MOD", 0x06, 2, 1)
SMOD = OpCode("SMOD", 0x07, 2, 1)
ADDMOD = OpCode("ADDMOD", 0x08, 3, 1)
MULMOD = OpCode("MULMOD", 0x09, 3, 1)
EXP = OpCode("EXP", 0x0a, 2, 1)
SIGNEXTEND = OpCode("SIGNEXTEND", 0x0b, 2, 1)
# Comparison and Bitwise Logic
LT = OpCode("LT", 0x10, 2, 1)
GT = OpCode("GT", 0x11, 2, 1)
SLT = OpCode("SLT", 0x12, 2, 1)
SGT = OpCode("SGT", 0x13, 2, 1)
EQ = OpCode("EQ", 0x14, 2, 1)
ISZERO = OpCode("ISZERO", 0x15, 1, 1)
AND = OpCode("AND", 0x16, 2, 1)
OR = OpCode("OR", 0x17, 2, 1)
XOR = OpCode("XOR", 0x18, 2, 1)
NOT = OpCode("NOT", 0x19, 1, 1)
BYTE = OpCode("BYTE", 0x1a, 2, 1)
SHA3 = OpCode("SHA3", 0x20, 2, 1)
# ZOEY - adding shift operation
SHL = OpCode("SHL", 0x1b, 2, 1) # 0x1b is the opcode for SHL
SHR = OpCode("SHR", 0x1c, 2, 1) # 0x1c is the opcode for SHR
SAR = OpCode("SAR", 0x1d, 2, 1) # 0x1d is the opcode for SAR
# Update OPCODES dictionary
# Environmental Information
ADDRESS = OpCode("ADDRESS", 0x30, 0, 1)
BALANCE = OpCode("BALANCE", 0x31, 1, 1)
ORIGIN = OpCode("ORIGIN", 0x32, 0, 1)
CALLER = OpCode("CALLER", 0x33, 0, 1)
CALLVALUE = OpCode("CALLVALUE", 0x34, 0, 1)
CALLDATALOAD = OpCode("CALLDATALOAD", 0x35, 1, 1)
CALLDATASIZE = OpCode("CALLDATASIZE", 0x36, 0, 1)
CALLDATACOPY = OpCode("CALLDATACOPY", 0x37, 3, 0)
CODESIZE = OpCode("CODESIZE", 0x38, 0, 1)
CODECOPY = OpCode("CODECOPY", 0x39, 3, 0)
GASPRICE = OpCode("GASPRICE", 0x3a, 0, 1)
EXTCODESIZE = OpCode("EXTCODESIZE", 0x3b, 1, 1)
EXTCODECOPY = OpCode("EXTCODECOPY", 0x3c, 4, 0)
# Block Information
BLOCKHASH = OpCode("BLOCKHASH", 0x40, 1, 1)
COINBASE = OpCode("COINBASE", 0x41, 0, 1)
TIMESTAMP = OpCode("TIMESTAMP", 0x42, 0, 1)
NUMBER = OpCode("NUMBER", 0x43, 0, 1)
DIFFICULTY = OpCode("DIFFICULTY", 0x44, 0, 1)
GASLIMIT = OpCode("GASLIMIT", 0x45, 0, 1)
# Stack, Memory, Storage, Flow
POP = OpCode("POP", 0x50, 1, 0)
MLOAD = OpCode("MLOAD", 0x51, 1, 1)
MSTORE = OpCode("MSTORE", 0x52, 2, 0)
MSTORE8 = OpCode("MSTORE8", 0x53, 2, 0)
SLOAD = OpCode("SLOAD", 0x54, 1, 1)
SSTORE = OpCode("SSTORE", 0x55, 2, 0)
JUMP = OpCode("JUMP", 0x56, 1, 0)
JUMPI = OpCode("JUMPI", 0x57, 2, 0)
PC = OpCode("PC", 0x58, 0, 1)
MSIZE = OpCode("MSIZE", 0x59, 0, 1)
GAS = OpCode("GAS", 0x5a, 0, 1)
JUMPDEST = OpCode("JUMPDEST", 0x5b, 0, 0)
PUSH1 = OpCode("PUSH1", 0x60, 0, 1)
PUSH2 = OpCode("PUSH2", 0x61, 0, 1)
PUSH3 = OpCode("PUSH3", 0x62, 0, 1)
PUSH4 = OpCode("PUSH4", 0x63, 0, 1)
PUSH5 = OpCode("PUSH5", 0x64, 0, 1)
PUSH6 = OpCode("PUSH6", 0x65, 0, 1)
PUSH7 = OpCode("PUSH7", 0x66, 0, 1)
PUSH8 = OpCode("PUSH8", 0x67, 0, 1)
PUSH9 = OpCode("PUSH9", 0x68, 0, 1)
PUSH10 = OpCode("PUSH10", 0x69, 0, 1)
PUSH11 = OpCode("PUSH11", 0x6a, 0, 1)
PUSH12 = OpCode("PUSH12", 0x6b, 0, 1)
PUSH13 = OpCode("PUSH13", 0x6c, 0, 1)
PUSH14 = OpCode("PUSH14", 0x6d, 0, 1)
PUSH15 = OpCode("PUSH15", 0x6e, 0, 1)
PUSH16 = OpCode("PUSH16", 0x6f, 0, 1)
PUSH17 = OpCode("PUSH17", 0x70, 0, 1)
PUSH18 = OpCode("PUSH18", 0x71, 0, 1)
PUSH19 = OpCode("PUSH19", 0x72, 0, 1)
PUSH20 = OpCode("PUSH20", 0x73, 0, 1)
PUSH21 = OpCode("PUSH21", 0x74, 0, 1)
PUSH22 = OpCode("PUSH22", 0x75, 0, 1)
PUSH23 = OpCode("PUSH23", 0x76, 0, 1)
PUSH24 = OpCode("PUSH24", 0x77, 0, 1)
PUSH25 = OpCode("PUSH25", 0x78, 0, 1)
PUSH26 = OpCode("PUSH26", 0x79, 0, 1)
PUSH27 = OpCode("PUSH27", 0x7a, 0, 1)
PUSH28 = OpCode("PUSH28", 0x7b, 0, 1)
PUSH29 = OpCode("PUSH29", 0x7c, 0, 1)
PUSH30 = OpCode("PUSH30", 0x7d, 0, 1)
PUSH31 = OpCode("PUSH31", 0x7e, 0, 1)
PUSH32 = OpCode("PUSH32", 0x7f, 0, 1)
DUP1 = OpCode("DUP1", 0x80, 1, 2)
DUP2 = OpCode("DUP2", 0x81, 2, 3)
DUP3 = OpCode("DUP3", 0x82, 3, 4)
DUP4 = OpCode("DUP4", 0x83, 4, 5)
DUP5 = OpCode("DUP5", 0x84, 5, 6)
DUP6 = OpCode("DUP6", 0x85, 6, 7)
DUP7 = OpCode("DUP7", 0x86, 7, 8)
DUP8 = OpCode("DUP8", 0x87, 8, 9)
DUP9 = OpCode("DUP9", 0x88, 9, 10)
DUP10 = OpCode("DUP10", 0x89, 10, 11)
DUP11 = OpCode("DUP11", 0x8a, 11, 12)
DUP12 = OpCode("DUP12", 0x8b, 12, 13)
DUP13 = OpCode("DUP13", 0x8c, 13, 14)
DUP14 = OpCode("DUP14", 0x8d, 14, 15)
DUP15 = OpCode("DUP15", 0x8e, 15, 16)
DUP16 = OpCode("DUP16", 0x8f, 16, 17)
SWAP1 = OpCode("SWAP1", 0x90, 2, 2)
SWAP2 = OpCode("SWAP2", 0x91, 3, 3)
SWAP3 = OpCode("SWAP3", 0x92, 4, 4)
SWAP4 = OpCode("SWAP4", 0x93, 5, 5)
SWAP5 = OpCode("SWAP5", 0x94, 6, 6)
SWAP6 = OpCode("SWAP6", 0x95, 7, 7)
SWAP7 = OpCode("SWAP7", 0x96, 8, 8)
SWAP8 = OpCode("SWAP8", 0x97, 9, 9)
SWAP9 = OpCode("SWAP9", 0x98, 10, 10)
SWAP10 = OpCode("SWAP10", 0x99, 11, 11)
SWAP11 = OpCode("SWAP11", 0x9a, 12, 12)
SWAP12 = OpCode("SWAP12", 0x9b, 13, 13)
SWAP13 = OpCode("SWAP13", 0x9c, 14, 14)
SWAP14 = OpCode("SWAP14", 0x9d, 15, 15)
SWAP15 = OpCode("SWAP15", 0x9e, 16, 16)
SWAP16 = OpCode("SWAP16", 0x9f, 17, 17)
# Logging
LOG0 = OpCode("LOG0", 0xa0, 2, 0)
LOG1 = OpCode("LOG1", 0xa1, 3, 0)
LOG2 = OpCode("LOG2", 0xa2, 4, 0)
LOG3 = OpCode("LOG3", 0xa3, 5, 0)
LOG4 = OpCode("LOG4", 0xa4, 6, 0)
# System Operations
CREATE = OpCode("CREATE", 0xf0, 3, 1)
CREATE2 = OpCode("CREATE2", 0xf5, 4, 1)
CALL = OpCode("CALL", 0xf1, 7, 1)
CALLCODE = OpCode("CALLCODE", 0xf2, 7, 1)
RETURN = OpCode("RETURN", 0xf3, 2, 0)
DELEGATECALL = OpCode("DELEGATECALL", 0xf4, 6, 1)
INVALID = OpCode("INVALID", 0xfe, 0, 0)
SELFDESTRUCT = OpCode("SELFDESTRUCT", 0xff, 1, 0)
# New Byzantinium OpCodes for block.number >= BYZANTIUM_FORK_BLKNUM
REVERT = OpCode("REVERT", 0xfd, 2, 0)
RETURNDATASIZE = OpCode("RETURNDATASIZE", 0x3d, 0, 1)
RETURNDATACOPY = OpCode("RETURNDATACOPY", 0x3e, 3, 0)
STATICCALL = OpCode("STATICCALL", 0xfa, 6, 1)
# TAC Operations
# These are not EVM opcodes, but they are used by the three-address code
NOP = OpCode("NOP", -1, 0, 0)
CONST = OpCode("CONST", -2, 0, 0)
LOG = OpCode("LOG", -3, 0, 0)
THROW = OpCode("THROW", -4, 0, 0)
THROWI = OpCode("THROWI", -5, 0, 0)
# Produce mappings from names and instruction codes to opcode objects
OPCODES = {
code.name: code
for code in globals().values()
if isinstance(code, OpCode)
}
"""Dictionary mapping of opcode string names to EVM OpCode objects"""
# Handle incorrect opcode name from go-ethereum disasm
OPCODES["TXGASPRICE"] = OPCODES["GASPRICE"]
BYTECODES = {code.code: code for code in OPCODES.values()}
"""Dictionary mapping of byte values to EVM OpCode objects"""
def opcode_by_name(name: str) -> OpCode:
"""
Mapping: Retrieves the named OpCode object (case-insensitive).
Throws:
LookupError: if there is no opcode defined with the given name.
"""
name = name.upper()
if name not in OPCODES:
raise LookupError("No opcode named '{}'.".format(name))
return OPCODES[name]
def opcode_by_value(val: int) -> OpCode:
"""
Mapping: Retrieves the OpCode object with the given value.
Throws:
LookupError: if there is no opcode defined with the given value.
"""
if val not in BYTECODES:
raise LookupError("No opcode with value '0x{:02X}'.".format(val))
return BYTECODES[val]
def missing_opcode(val: int) -> OpCode:
"""
Produces a new OpCode with the given value, as long as that is
an unknown code.
Throws:
ValueError: if there is an opcode defined with the given value.
"""
if val in BYTECODES:
raise ValueError("Opcode {} exists.")
return OpCode("MISSING", val, 0, 0)
+235
View File
@@ -0,0 +1,235 @@
# BSD 3-Clause License
#
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""evm_efg.py: parse the transaction trace and build a execution flow graph (efg)"""
"""解析以太坊交易跟踪(trace)并构建执行流图(Execution Flow Graph,EFG)的代码。
该代码由以下几个类组成:
EVMBasicBlock:表示执行流图中的一个基本块,包括图结构中其父节点和子节点。
EVMBasicBlock类继承自 cfg.BasicBlock 类,它表示控制流图中的一个基本块,包括其入口和出口,以及其父和子节点。
EVMOp:表示一个EVM(以太坊虚拟机)操作。
blocks_from_ops:从一系列 EVM 操作中生成基本块。
解析以太坊交易跟踪后,可以通过 blocks_from_ops 函数得到一个基本块的列表。
每个基本块都包含一些 EVM 操作(以太坊虚拟机操作),这些操作被认为是连续的。
每个操作都由一个EVMOp对象表示。
EVMOp类表示一个EVM操作,<PC,opcode, value, alue_extra, depth, loc,call_number>:
程序计数器(PC):一个指向操作代码的指针。
操作码(opcode):操作的类型,如 ADD、MUL、JUMP 等。
值(value):操作数,如果是 PUSH 操作,它就是一个常量整数值。
扩展值(value_extra):特定类型的操作的附加参数,如 CALL、CALLCODE、DELEGATECALL 和 STATICCALL。
调用深度(call_depth):调用的智能合约的深度。
操作码计数器(loc):操作的位置。
调用编号(call_number):在 EVM 执行过程中,每个调用都分配了一个唯一的编号。
blocks_from_ops 函数将一系列 EVMOp 对象转换成 EVMBasicBlock 对象,并返回一个包含所有基本块的列表。
函数遍历一系列 EVMOp 对象,每当它遇到 CALL 操作时,就增加 call_number。
每个操作都将自己分配给一个基本块(EVMBasicBlock),该基本块由变量 current 表示。每个基本块将操作作为其成员存储在其 evm_ops 属性中。
"""
import typing as t
import src.cfg as cfg
import src.opcodes as opcodes
class EVMBasicBlock(cfg.BasicBlock):
"""
Represents a single basic block in the Execution Flow Graph (EFG), including
its parent and child nodes in the graph structure.
"""
def __init__(self, entry: int = None, exit: int = None,
evm_ops: t.List['EVMOp'] = None):
"""
Creates a new basic block containing operations between the
specified entry and exit instruction counters (inclusive).
Args:
entry: block entry point program counter
exit: block exit point program counter
evm_ops: a sequence of operations that constitute this BasicBlock's code. Default empty.
"""
super().__init__(entry, exit)
self.evm_ops = evm_ops if evm_ops is not None else []
"""List of EVMOps contained within this EVMBasicBlock"""
def __str__(self):
"""Returns a string representation of this block and all ops in it."""
super_str = super().__str__()
op_seq = "\n".join(str(op) for op in self.evm_ops)
return "\n".join([super_str, self._STR_SEP, op_seq])
def split(self, entry: int) -> 'EVMBasicBlock':
"""
Splits current block into a new block, starting at the specified
entry op index. Returns a new EVMBasicBlock with no preds or succs.
Args:
entry: unique index of EVMOp from which the block should be split. The
EVMOp at this index will become the first EVMOp of the new BasicBlock.
"""
# Create the new block.
new = type(self)(entry, self.exit, self.evm_ops[entry - self.entry:])
# Update the current node.
self.exit = entry - 1
self.evm_ops = self.evm_ops[:entry - self.entry]
# Update the block pointer in each line object
self.__update_evmop_refs()
new.__update_evmop_refs()
return new
def __update_evmop_refs(self):
# Update references back to parent block for each opcode
# This needs to be done when a block is split
for op in self.evm_ops:
op.block = self
class EVMOp:
"""
Represents a single EVM operation.
"""
def __init__(self, pc: int, opcode: opcodes.OpCode, value: int = None, value_extra: int = None,
call_depth: int = None, loc: int = None, call_number: int = None):
"""
Create a new EVMOp object from the given params which should correspond to
disasm output.
"""
# Programming counter
self.pc = pc
# VM operation code
self.opcode = opcode
# Constant int value or None. Not only PUSH opcode has this value
self.value = value
# Four call opcodes has a special type: 0,1, they need extra type
# value_extra is used to store more arguments for call, callcode, delegatecall, staticcall
self.value_extra = value_extra
# Call depth is the depth of the called smart contracts
self.call_depth = call_depth
# Loc is the programming opcode counter
self.loc = loc
# Call number, if you are interested in the details, please refer to our paper
self.call_number = call_number
# The default string of the opcodes
def __str__(self):
if self.value is None:
return "{0} {1}".format(self.pc, self.opcode)
else:
if self.value_extra is None:
return "{0} {1} {2}".format(self.pc, self.opcode, hex(self.value))
else:
return "{0} {1} {2} {3}".format(self.pc, self.opcode, hex(self.value), hex(self.value_extra))
def __repr__(self):
return "<{0} object {1}: {2}>".format(
self.__class__.__name__,
hex(id(self)),
self.__str__()
)
# Convert the trace (sequence of opcodes) into basic blocks
def blocks_from_ops(ops: t.Iterable[EVMOp]) -> t.Iterable[EVMBasicBlock]:
"""
Process a sequence of EVMOps and create a sequence of EVMBasicBlocks.
Args:
ops: sequence of EVMOps to be put into blocks.
Returns:
List of BasicBlocks from the input opcodes.
"""
blocks = []
# details for block currently being processed
entry, exit = (0, len(ops) - 1) if len(ops) > 0 \
else (None, None)
current = EVMBasicBlock(entry, exit)
call_number = 0
for i, op in enumerate(ops):
if op.pc == 0 and i != 0:
call_number += 1
op.call_number = call_number
# Linear scan of all EVMOps to create initial EVMBasicBlocks
call_depth = 0
for i, op in enumerate(ops):
op.block = current
current.evm_ops.append(op)
# Remove all the intra blocks and only focus on the inter edges
# add a condition to create a new block when encountering the new contract 0;
if op.pc == 0 and i == 0:
call_depth = 1
elif op.pc == 0 and i != 0:
call_depth += 1
new = current.split(i)
blocks.append(current)
current = new
# Add CREATE and CREATE2
elif op.opcode.is_kind_four() or op.opcode.is_kind_five():
# Make sure conditions such as 238;ADD 239;CALL will not be split
if ops[i-1].call_number == op.call_number \
and op.pc - ops[i - 1].pc == ops[i - 1].opcode.op_pc_gap() \
and not ops[i-1].opcode.possibly_halts():
pass
else:
call_depth -= 1
new = current.split(i)
blocks.append(current)
current = new
# Always add last block if its last instruction does not alter flow
elif i == len(ops) - 1:
blocks.append(current)
op.loc = i
op.call_depth = call_depth
return blocks
+563
View File
@@ -0,0 +1,563 @@
# BSD 3-Clause License
#
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""exporter.py: abstract classes for exporting decompiler state"""
import abc
import csv
import logging
import os
import src.cfg as efg
import src.function as function
import src.opcodes as opcodes
import src.patterns as patterns
import src.tac_efg as tac_efg
import time
class Exporter(abc.ABC):
def __init__(self, source: object):
"""
Args:
source: object instance to be exported
"""
self.source = source
@abc.abstractmethod
def export(self):
"""
Exports the source object to an implementation-specific format.
"""
class EFGTsvExporter(Exporter, patterns.DynamicVisitor):
"""
Writes logical relations of the given TAC EFG to local directory.
Args:
efg: the graph to be written to logical relations.
"""
def __init__(self, efg: tac_efg.TACGraph):
"""
Generates .facts files of the given TAC EFG to local directory.
Args:
efg: source TAC EFG to be exported to separate fact files.
"""
super().__init__(efg)
self.defined = []
"""
A list of pairs (op.pc, variable) that specify variable definition sites.
"""
self.reads = []
"""
A list of pairs (op.pc, variable) that specify all usage sites.
"""
self.writes = []
"""
A list of pairs (op.pc, variable) that specify all write locations.
"""
self.__output_dir = None
def __generate(self, filename, entries):
path = os.path.join(self.__output_dir, filename)
with open(path, 'w') as f:
writer = csv.writer(f, delimiter='\t', lineterminator='\n')
for e in entries:
writer.writerow(e)
def __generate_blocks_ops(self, out_opcodes):
# Write a mapping from operation addresses to corresponding opcode names;
# a mapping from operation addresses to the block they inhabit;
# any specified opcode listings.
ops = []
# block_nums = []
# initialization, empty
op_rels = {opcode: list() for opcode in out_opcodes}
# Facts pattern: pc + arguments + value name (value is like CALLVALUE's value that will be pushed into stack)
for block in self.source.blocks:
for op in block.tac_ops:
ops.append((hex(op.pc), op.opcode.name, op.loc))
if op.opcode.name in out_opcodes:
if op.has_lhs():
output_tuple = tuple([hex(op.pc)] + [arg.value.name for arg in op.args] +
[op.lhs.name] + [op.loc] + [op.call_depth] + [op.call_number])
else:
output_tuple = tuple([hex(op.pc)] + [arg.value.name for arg in op.args] +
[op.loc] + [op.call_depth] + [op.call_number])
op_rels[op.opcode.name].append(output_tuple)
# op.facts contain all the opcodes from the bytecode
self.__generate("op.facts", ops)
# generate all the needed opcode.facts
for opcode in op_rels:
self.__generate("op_{}.facts".format(opcode), op_rels[opcode])
def __generate_def_use_value(self):
# get the exact values of all the variables
# Mapping from variable names to the addresses they were defined at.
define = []
# Mapping from variable names to the addresses they were used at.
use = []
# Mapping from variable names to their possible values.
value = []
for block in self.source.blocks:
for op in block.tac_ops:
# If it's an assignment op, we have a def site
if isinstance(op, tac_efg.TACAssignOp):
define.append((op.lhs.name, hex(op.pc), op.loc, op.call_depth, op.call_number))
# And we can also find its values here.
if op.lhs.values.is_finite:
for val in op.lhs.values:
value.append((op.lhs.name, hex(val)))
# Special cases for kind one, such as CALLVALUE
# if op.opcode != opcodes.CONST:
if op.opcode != opcodes.CONST:
# The args constitute use sites.
for i, arg in enumerate(op.args):
name = arg.value.name
use.append((name, hex(op.pc), i + 1, op.loc, op.call_depth, op.call_number))
# Finally, note where each stack variable might have been defined,
# and what values it can take on.
# This includes some duplication for stack variables with multiple def
# sites. This can be done marginally more efficiently.
for var in block.entry_stack:
if not var.def_sites.is_const and var.def_sites.is_finite:
name = block.ident() + ":" + var.name
for loc in var.def_sites:
define.append((name, hex(loc.pc), op.loc, loc.call_depth, loc.call_number))
if var.values.is_finite:
for val in var.values:
value.append((name, hex(val)))
self.__generate("def.facts", define)
self.__generate("use.facts", use)
self.__generate("value.facts", value)
def export(self, output_dir: str = "", out_opcodes=[]):
"""
Args:
output_dir: location to write the output to.
out_opcodes: a list of opcode names all occurences thereof to output,
with the names of all argument variables.
"""
if output_dir != "":
os.makedirs(output_dir, exist_ok=True)
self.__output_dir = output_dir
# Generate facts for the interested opcodes, such as MSTORE ...
self.__generate_blocks_ops(out_opcodes)
# Generate facts for the def.facts (where the V1 V2 ... is defined)
# use.fats stores where the V1 V2 ... used; value stores what the V1 V2 ... values are
# Previous code
self.__generate_def_use_value()
# change code - zoey to add a timeout seting
# start_time = time.time()
# if output_dir != "":
# os.makedirs(output_dir, exist_ok=True)
# self.__output_dir = output_dir
# try:
# # 检查是否超时
# if time.time() - start_time > timeout_seconds:
# raise TimeoutError("Export process exceeded time limit.")
# # 以下为您原有的 export 逻辑
# self.__generate_blocks_ops(out_opcodes)
# self.__generate_def_use_value()
# except TimeoutError as te:
# # 处理超时的逻辑
# print(f"Export process exceeded time limit: {te}")
# # 除超时外的其他异常处理
# except Exception as e:
# print(f"An error occurred during export: {e}")
class EFGStringExporter(Exporter, patterns.DynamicVisitor):
"""
Prints a textual representation of the given EFG to stdout.
Args:
efg: source EFG to be printed.
ordered: if True (default), print BasicBlocks in order of entry.
"""
__BLOCK_SEP = "\n\n================================\n\n"
def __init__(self, efg: efg.ControlFlowGraph, ordered: bool = True):
super().__init__(efg)
self.ordered = ordered
self.blocks = []
self.source.accept(self)
def visit_ExecutionFlowGraph(self, efg):
"""
Visit the EFG root
"""
pass
def visit_BasicBlock(self, block):
"""
Visit a BasicBlock in the EFG
"""
self.blocks.append((block.entry, str(block)))
def export(self):
"""
Print a textual representation of the input EFG to stdout.
"""
if self.ordered:
self.blocks.sort(key=lambda n: n[0])
blocks = self.__BLOCK_SEP.join(n[1] for n in self.blocks)
functions = ""
if self.source.function_extractor is not None:
functions = self.__BLOCK_SEP + str(self.source.function_extractor)
return blocks + functions
class EFGDotExporter(Exporter):
"""
Generates a dot file for drawing a pretty picture of the given EFG.
Args:
efg: source EFG to be exported to dot format.
"""
def __init__(self, efg: efg.ControlFlowGraph):
super().__init__(efg)
def export(self, out_filename: str = "efg.dot"):
"""
Export the EFG to a dot file.
Certain blocks will have coloured outlines:
Green: contains a RETURN operation;
Blue: contains a STOP operation;
Red: contains a THROW, THROWI, INVALID, or missing operation;
Purple: contains a SELFDESTRUCT operation;
Orange: contains a CALL, CALLCODE, or DELEGATECALL operation;
Brown: contains a CREATE operation.
A node with a red fill indicates that its stack size is large.
Args:
out_filename: path to the file where dot output should be written.
If the file extension is a supported image format,
attempt to generate an image using the `dot` program,
if it is in the user's `$PATH`.
"""
import networkx as nx
efg = self.source
G = efg.nx_graph()
# Annotate each node with its basic block's internal data for later display
# if rendered in html.
nx.set_node_attributes(G, "id", {i: i
for i, block in enumerate(efg.blocks)})
block_strings = {}
for i, block in enumerate(efg.blocks):
block_string = str(block)
def_site_string = "\n\nDef sites:\n"
for v in block.entry_stack.value:
def_site_string += str(v) \
+ ": {" \
+ ", ".join(str(d) for d in v.def_sites) \
+ "}\n"
block_strings[i] = block_string + def_site_string
nx.set_node_attributes(G, "tooltip", block_strings)
# Write non-dot files using pydot and Graphviz
if "." in out_filename and not out_filename.endswith(".dot"):
pdG = nx.nx_pydot.to_pydot(G)
extension = out_filename.split(".")[-1]
# If we're producing an html file, write a temporary svg to build it from
# and then delete it.
if extension == "html":
html = svg_to_html(pdG.create_svg().decode("utf-8"), efg.function_extractor)
if not out_filename.endswith(".html"):
out_filename += ".html"
with open(out_filename, 'w') as page:
logging.info("Drawing EFG image to '%s'.", out_filename)
page.write(html)
else:
pdG.set_margin(0)
pdG.write(out_filename, format=extension)
# Otherwise, write a regular dot file using pydot
else:
try:
if out_filename == "":
out_filename = "efg.html"
nx.nx_pydot.write_dot(G, out_filename)
logging.info("Drawing EFG image to '%s'.", out_filename)
except:
logging.info("Graphviz missing. Falling back to dot.")
if out_filename == "":
out_filename = "efg.dot"
nx.nx_pydot.write_dot(G, out_filename)
logging.info("Drawing EFG image to '%s'.", out_filename)
def svg_to_html(svg: str, function_extractor: function.FunctionExtractor = None) -> str:
"""
Produces an interactive html page from an svg image of a EFG.
Args:
svg: the string of the SVG to process
function_extractor: a FunctionExtractor object containing functions
to annotate the graph with.
Returns:
HTML string of interactive web page source for the given EFG.
"""
lines = svg.split("\n")
page = []
page.append("""
<html>
<body>
<style>
.node
{
transition: all 0.05s ease-out;
}
.node:hover
{
stroke-width: 1.5;
cursor:pointer
}
.node:hover
ellipse
{
fill: #EEE;
}
textarea#infobox {
position: fixed;
display: block;
top: 0;
right: 0;
}
.dropbutton {
padding: 10px;
border: none;
}
.dropbutton:hover, .dropbutton:focus {
background-color: #777777;
}
.dropdown {
margin-right: 5px;
position: fixed;
top: 5px;
right: 0px;
}
.dropdown-content {
background-color: white;
display: none;
position: absolute;
width: 70px;
box-shadow: 0px 5px 10px 0px rgba(0,0,0,0.2);
z-index: 1;
}
.dropdown-content a {
color: black;
padding: 8px 10px;
text-decoration: none;
font-size: 10px;
display: block;
}
.dropdown-content a:hover { background-color: #f1f1f1; }
.show { display:block; }
</style>
""")
for line in lines[3:]:
page.append(line)
page.append("""<textarea id="infobox" disabled=true rows=40 cols=80></textarea>""")
# Create a dropdown list of functions if there are any.
if function_extractor is not None:
page.append("""<div class="dropdown">
<button onclick="showDropdown()" class="dropbutton">Functions</button>
<div id="func-list" class="dropdown-content">""")
for i, f in enumerate(function_extractor.functions):
if f.is_private:
page.append('<a id=f_{0} href="javascript:highlightFunction({0})">private #{0}</a>'.format(i))
else:
if f.signature:
page.append(
'<a id=f_{0} href="javascript:highlightFunction({0})">public {1}</a>'.format(i, f.signature))
else:
page.append('<a id=f_{0} href="javascript:highlightFunction({0})">fallback</a>'.format(i))
page.append("</div></div>")
page.append("""<script>""")
if function_extractor is not None:
func_map = {i: [b.ident() for b in f.body]
for i, f in enumerate(function_extractor.functions)}
page.append("var func_map = {};".format(func_map))
page.append("var highlight = new Array({}).fill(0);".format(len(func_map)))
page.append("""
// Set info textbox contents to the title of the given element, with line endings replaced suitably.
function setInfoContents(element){
document.getElementById('infobox').value = element.getAttribute('xlink:title').replace(/\\\\n/g, '\\n');
}
// Make all node anchor tags in the svg clickable.
for (var el of Array.from(document.querySelectorAll(".node a"))) {
el.setAttribute("onclick", "setInfoContents(this);");
}
const svg = document.querySelector('svg')
const NS = "http://www.w3.org/2000/svg";
const defs = document.createElementNS( NS, "defs" );
// IIFE add filter to svg to allow shadows to be added to nodes within it
(function(){
defs.innerHTML = makeShadowFilter()
svg.insertBefore(defs,svg.children[0])
})()
function colorToID(color){
return color.replace(/[^a-zA-Z0-9]/g,'_')
}
function makeShadowFilter({color = 'black',x = 0,y = 0, blur = 3} = {}){
return `
<filter id="filter_${colorToID(color)}" x="-40%" y="-40%" width="250%" height="250%">
<feGaussianBlur in="SourceAlpha" stdDeviation="${blur}"/>
<feOffset dx="${x}" dy="${y}" result="offsetblur"/>
<feFlood flood-color="${color}"/>
<feComposite in2="offsetblur" operator="in"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
`
}
// Shadow toggle functions, with filter caching
function addShadow(el, {color = 'black', x = 0, y = 0, blur = 3}){
const id = colorToID(color);
if(!defs.querySelector(`#filter_${id}`)){
const d = document.createElementNS(NS, 'div');
d.innerHTML = makeShadowFilter({color, x, y, blur});
defs.appendChild(d.children[0]);
}
el.style.filter = `url(#filter_${id})`
}
function removeShadow(el){
el.style.filter = ''
}
function hash(n) {
var str = n + "rainbows" + n + "please" + n;
var hash = 0;
for (var i = 0; i < str.length; i++) {
hash = (((hash << 5) - hash) + str.charCodeAt(i)) | 0;
}
return hash > 0 ? hash : -hash;
};
function getColor(n, sat="80%", light="50%") {
const hue = hash(n) % 360;
return `hsl(${hue}, ${sat}, ${light})`;
}
// Add shadows to function body nodes, and highlight functions in the dropdown list
function highlightFunction(i) {
for (var n of Array.from(document.querySelectorAll(".node ellipse"))) {
removeShadow(n);
}
highlight[i] = !highlight[i];
const entry = document.querySelector(`.dropdown-content a[id='f_${i}']`)
if (entry.style.backgroundColor) {
entry.style.backgroundColor = null;
} else {
entry.style.backgroundColor = getColor(i, "60%", "90%");
}
for (var j = 0; j < highlight.length; j++) {
if (highlight[j]) {
const col = getColor(j);
for (var id of func_map[j]) {
var n = document.querySelector(`.node[id='${id}'] ellipse`);
addShadow(n, {color:`${col}`});
}
}
}
}
// Show the dropdown elements when it's clicked.
function showDropdown() {
document.getElementById("func-list").classList.toggle("show");
}
window.onclick = function(event) {
if (!event.target.matches('.dropbutton')) {
var items = Array.from(document.getElementsByClassName("dropdown-content"));
for (var item of items) {
item.classList.remove('show');
}
}
}
</script>
</html>
</body>
""")
return "\n".join(page)
+428
View File
@@ -0,0 +1,428 @@
# BSD 3-Clause License
#
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""function.py:
Classes for identifying and exporting functions in the control flow graph.
Tested and developed on Solidity version 0.4.11"""
import typing as t
import src.memtypes as memtypes
import src.opcodes as opcodes
import src.tac_efg as tac_cfg
# 用于提取solidity合约中的函数
"""
Function 类表示一个 Solidity 函数及其相关的元数据, FunctionExtractor 类用于从一个已经生成的 TAC cfg 中提取 Solidity 函数。
Function 类具有以下属性:
body: 一个列表,表示该函数的主体部分由哪些 TAC 基本块组成。
start_block: 一个指向 TAC 基本块的指针,表示该函数的起始块。
end_block: 一个指向 TAC 基本块的指针,表示该函数的结束块。
mapping: 一个字典,将函数主体中的前驱基本块映射到后继基本块。
signature: 一个字符串,表示函数的签名。
is_private: 一个布尔值,表示该函数是否为私有函数。
FunctionExtractor 类具有以下属性:
cfg: 一个 TAC cfg,表示需要提取函数的 cfg。
public_functions: 一个列表,表示公共函数列表。
private_functions: 一个列表,表示私有函数列表。
invoc_pairs: 一个字典,将调用站点映射到返回地址。
mark_body(): 一个静态方法,用于标记函数主体中的基本块。
functions: 返回所有函数列表。
extract(): 提取私有和公共函数。
mark_functions(): 将提取的函数主体标记为具有唯一标识符后缀。
extract_public_functions(): 提取暴露给合约的 Solidity 公共函数。
get_public_function(): 确定从给定块开始的函数。
"""
class Function:
"""
A representation of a Solidity function with associated metadata.
"""
def __init__(self):
self.body = []
self.start_block = None
self.end_block = None
self.mapping = {} # a mapping of preds to succs of the function body.
self.signature = ""
self.is_private = False
def __str__(self):
if self.is_private:
sig = "Private function"
else:
if self.signature:
sig = "Public function signature: " + self.signature
else:
sig = "Public fallback function"
entry_block = "Entry block: " + self.start_block.ident()
if self.end_block is not None:
exit_block = "Exit block: " + self.end_block.ident()
else:
exit_block = "Exit block: None identified"
body = "Body: " + ", ".join(b.ident() for b in sorted(self.body))
return "\n".join([sig, entry_block, exit_block, body])
class FunctionExtractor:
"""A class for extracting Solidity functions from an already generated TAC cfg."""
def __init__(self, cfg: tac_cfg.TACGraph):
self.cfg = cfg # the tac_cfg that this operates on
self.public_functions = []
self.private_functions = []
self.invoc_pairs = {} # a mapping from invocation sites to return addresses
def __str__(self) -> str:
"""
Returns a string representation of all the functions in the graph
after extraction has been performed.
Returns:
A string summary of all the functions in the control flow graph
"""
return "\n".join(["Function {}:\n{}\n".format(str(i), str(func))
for i, func in enumerate(self.functions)])
@staticmethod
def mark_body(path: t.List[tac_cfg.TACBasicBlock], num: int) -> None:
"""
Marks every block in the path with the given number.
Used for marking function bodies.
"""
for block in path:
block.ident_suffix += "_F" + str(num)
@property
def functions(self):
return self.public_functions + self.private_functions
def extract(self) -> None:
"""Extracts private and public functions"""
self.private_functions.extend(self.extract_private_functions())
self.public_functions.extend(self.extract_public_functions())
def mark_functions(self) -> None:
"""Mark extracted function bodies with unique identifier suffixes."""
for i, func in enumerate(self.functions):
self.mark_body(func.body, i)
def extract_public_functions(self) -> t.Iterable[Function]:
"""
Return a list of the solidity public functions exposed by the contract of this cfg.
Returns:
A list of the extracted functions.
"""
# Find the function signature variable holding call data 0,
# at the earliest query to that location in the program.
load_list = []
load_block = None
for block in sorted(self.cfg.blocks):
load_list = [op for op in block.tac_ops
if op.opcode == opcodes.CALLDATALOAD
and op.args[0].value.const_value == 0]
if len(load_list) != 0:
load_block = block
break
if len(load_list) == 0:
return []
sig_var = load_list[0].lhs
# Follow the signature until it's transformed into its final shape.
for o in load_block.tac_ops:
if not isinstance(o, tac_cfg.TACAssignOp) or id(sig_var) not in [id(a.value) for a in o.args]:
continue
if o.opcode == opcodes.EQ:
break
sig_var = o.lhs
# Find all the places the function signature is compared to a constant
func_sigs = []
fallthroughs = []
for b in self.cfg.blocks:
for o in b.tac_ops:
if not isinstance(o, tac_cfg.TACAssignOp) or id(sig_var) not in [id(a.value) for a in o.args]:
continue
if o.opcode == opcodes.EQ:
sig = [a.value for a in o.args if id(a.value) != id(sig_var)][0]
# Append the non-fallthrough successor to the function sig list
for succ in [s for s in b.succs if s != b.fallthrough]:
func_sigs.append((succ, hex(sig.const_value)))
# Save the fallthrough location so the last one can be added as
# the fallback function
if b.fallthrough is not None:
fallthroughs.append(b.fallthrough)
# Add the fallback function
if fallthroughs:
func_sigs.append((fallthroughs[-1], ""))
return [self.get_public_function(s[0], signature=s[1]) for s in func_sigs]
def get_public_function(self, block: tac_cfg.TACBasicBlock,
signature: str = "") -> Function:
"""
Identifies the function starting with the given block
Args:
block: A BasicBlock to be used as the starting block for the function.
signature: The solidity hashed function signature associated with the function to be extracted.
Returns:
A Function object containing the blocks composing the function body.
"""
body = []
queue = [block]
end_block = None
cur_block = None # A placeholder for prev_block
jump = False # Keeps track of whether we have just jumped or not
pre_jump_block = block
while len(queue) > 0:
prev_block = cur_block
cur_block = queue.pop(0)
# jump over function bodies
for f in self.functions:
if cur_block in f.body and not jump:
cur_block = self.__jump_to_next_loc(cur_block, body,
prev_block.exit_stack)
if cur_block in f.body and jump:
# If we jumped, the previous block is actually from before the jump
cur_block = self.__jump_to_next_loc(cur_block, body,
pre_jump_block.exit_stack)
jump = False
# In case we didn't find a new block to jump to
if cur_block is None:
continue
# Jump over other invocation sites after adding them to body
if cur_block in self.invoc_pairs:
jump = True
pre_jump_block = cur_block
if cur_block not in body:
body.append(cur_block)
cur_block = self.invoc_pairs[cur_block]
# Since an invocation site always has a successor,
# it can't be an end block for the function
if len(cur_block.succs) == 0:
end_block = cur_block
if cur_block not in body:
body.append(cur_block)
for b in cur_block.succs:
queue.append(b)
f = Function()
f.body = body
f.start_block = block
# Assuming that there will only be one end_block
f.end_block = end_block
f.signature = signature
f.is_private = False
return f
def __jump_to_next_loc(self, block: tac_cfg.TACBasicBlock,
body: t.List[tac_cfg.TACBasicBlock],
exit_stack: memtypes.VariableStack) -> tac_cfg.TACBasicBlock:
"""
Helper method to jump over private functions during public function
identification. Uses BFS to find the next available block that is not
in a function.
Args:
block: The current block to be tested as being in the function body
body: The body of the current function being identified.
exit_stack: The stack of the previous block, or block before that after
a previous jump over a function. If a block is in this
exit_stack, then it is the next block in the flow of the
function currently being identified.
"""
queue = [block]
visited = [block]
while len(queue) > 0:
block = queue.pop()
if len(block.succs) == 0:
return block
visited.append(block)
in_func = False
for f in self.functions:
if block in f.body:
in_func = True
break
# Check that the block is not related to another function,
# and we haven't visited it yet, and that it is in the exit stack
# We can discard blocks in the body of the function being identified
# since we want to discover new blocks, not old ones
if not in_func and block not in self.invoc_pairs.keys() and \
block.ident() in str(exit_stack) and block not in body:
return block
for succ in block.succs:
if succ not in visited:
queue.append(succ)
return None
def extract_private_functions(self) -> t.List[tac_cfg.TACBasicBlock]:
"""
Extracts private functions
Returns:
A list of of Function objects: the private functions identified in the cfg
"""
# Get invocation site -> return block mappings
start_blocks = []
pair_list = []
for block in reversed(self.cfg.blocks):
invoc_pairs = self.is_private_func_start(block)
if invoc_pairs:
start_blocks.append(block)
pair_list.append(invoc_pairs)
# Store invocation site - return pairs for later usage
for d in pair_list:
for key in d.keys():
self.invoc_pairs[key] = d[key]
# Find the Function body itself
private_funcs = list()
for i, block in enumerate(start_blocks):
return_blocks = list(pair_list[i].values())
f = self.find_func_body(block, return_blocks, pair_list)
if not f or len(f.body) == 1: # Can't have a function with 1 block in EVM
continue
if f is not None:
f.is_private = True
private_funcs.append(f)
return private_funcs
def is_private_func_start(self, block: tac_cfg.TACBasicBlock) -> t.Dict[
tac_cfg.TACBasicBlock, tac_cfg.TACBasicBlock]:
"""
Determines the invocation and return points of the function beginning
with the given block, if it exists.
Args:
block: a BasicBlock to be tested as the possible beginning of a function
Returns:
A mapping of invocation sites to return blocks of the function,
if the given block is the start of a function.
None if the block is not the beginning of a function.
"""
# if there are multiple paths converging, this is a possible function start
preds = list(sorted(block.preds))
if (len(preds) <= 1) or len(list(block.succs)) == 0:
return None
func_succs = [] # a list of what succs to look out for.
func_mapping = {} # mapping of predecessors -> successors of function
params = [] # list of parameters
for pre in preds:
if pre not in block.succs:
# Check for at least 2 push opcodes and net stack gain
push_count = 0
for evm_op in pre.evm_ops:
if evm_op.opcode.is_push():
push_count += 1
if push_count <= 1:
return None
if len(pre.delta_stack) == 0:
return None
for val in list(pre.delta_stack):
ref_block = self.cfg.get_block_by_ident(str(val))
# Ensure that the block pointed to by the block exists and is reachable
if ref_block is not None and self.cfg.reaches(pre, [ref_block]):
func_mapping[pre] = ref_block
func_succs.append(ref_block)
break
params.append(val)
if not func_succs or len(func_mapping) == 1:
return None
# We have our start
return func_mapping
def find_func_body(self, block: tac_cfg.TACBasicBlock,
return_blocks: t.List[tac_cfg.TACBasicBlock],
invoc_pairs: t.List[tac_cfg.TACBasicBlock]) -> 'Function':
"""
Assuming the block is a definite function start, identifies all paths
from start to end using BFS
Args:
block: the start block of a function.
return_blocks: the list of return blocks of the function.
invoc_pairs: a mapping of all other invocation and return point pairs
to allow jumping over other functions.
Returns:
A function object representing the function starting with the given block
"""
# Traverse down levels with BFS until we hit a block that has the return
# addresses specified above
body = []
queue = [block]
end = False
while len(queue) > 0:
curr_block = queue.pop(0)
# When we call a function, we just jump to the return address
for entry in invoc_pairs:
if curr_block in entry:
body.append(curr_block)
curr_block = entry[curr_block]
cur_succs = [b for b in curr_block.succs]
if set(return_blocks).issubset(set(cur_succs)):
end = True
if curr_block not in body and self.cfg.reaches(curr_block, return_blocks):
body.append(curr_block)
for b in curr_block.succs:
if b not in return_blocks:
queue.append(b)
if end:
f = Function()
f.start_block = block
poss_exit_blocks = [sorted(b.preds) for b in return_blocks]
exit_blocks = set(poss_exit_blocks[0])
for preds in poss_exit_blocks:
exit_blocks = exit_blocks & set(preds)
# We assume the end_block is the last block in the disasm from all candidates
f.end_block = sorted(exit_blocks).pop()
f.succs = sorted(return_blocks)
f.preds = sorted(block.preds)
f.body = body
return f
return None
+363
View File
@@ -0,0 +1,363 @@
# BSD 3-Clause License
#
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""lattice.py: define lattices for use in meet-over-paths calculations.
We will take bottom elements to mean maximal value constraint
(uninitialised, or empty set), while top elements will be taken to mean a
maximally-unconstrained element (all possible values, universal set)."""
import abc
import functools
import itertools
import types
import typing as t
from copy import copy
"""Lattice是一种代数结构,它包含一个偏序集和两个操作 meet 和 join。这个代码定义了LatticeElement类和BoundedLatticeElement类,并在BoundedLatticeElement类中实现了具有上下界的LatticeElement。类IntLatticeElement实现了一个特定的具有整数元素的BoundedLatticeElement。下面我们将这些类的实现逐一解释。
LatticeElement(抽象基类):定义了两个抽象方法 meet 和 join,分别代表 meet-over-paths 中的 meet 和 join 操作。在 meet 和 join 中,类的实例(即 LatticeElement 的子类)是其参数。该类还包括比较值的方法和其他辅助方法。 LatticeElement 是 BoundedLatticeElement 和 IntLatticeElement 类的基类。
BoundedLatticeElement(LatticeElement 的子类):它是 LatticeElement 的具体实现,实现了具有上下界的LatticeElement。BoundedLatticeElement 定义了 is_top 和 is_bottom 属性和对应的方法,用于检测该实例是否是最大或最小元素。该类还定义了两个类方法 meet_all 和 join_all,分别计算一组 LatticeElement 的 meet 和 join 操作。该类还包括有关顶部和底部元素的抽象方法 _top_val 和 _bottom_val,以及 top 和 bottom 静态方法,它们分别返回顶部和底部元素的实例。IntLatticeElement 类实现了一个特定的具有整数元素的 BoundedLatticeElement。
IntLatticeElement(BoundedLatticeElement 的子类):它实现了具有整数元素的 BoundedLatticeElement。因为整数之间没有偏序,所以这个 Lattice 中的整数被视为不可比较的元素。它还实现了一个帮助函数is_int,用于检查给定的 LatticeElement 是否为整数元素(而不是顶部或底部元素)。"""
class LatticeElement(abc.ABC):
def __init__(self, value):
"""
Construct a lattice element with the given value.
Args:
value: the value of this LatticeElement
"""
self.value = value
@abc.abstractclassmethod
def meet(cls, a: 'LatticeElement', b: 'LatticeElement') -> 'LatticeElement':
"""Return the infimum of the given elements."""
@classmethod
def meet_all(cls, elements: t.Iterable['LatticeElement'],
initial: 'LatticeElement' = None) -> 'LatticeElement':
"""
Return the infimum of the given iterable of elements.
Args:
elements: a sequence of elements whose common meet to obtain
initial: an additional element to meet with the rest.
An empty sequence will result in this value, if provided.
"""
if initial is not None:
return functools.reduce(
lambda a, b: cls.meet(a, b),
elements,
initial
)
return functools.reduce(
lambda a, b: cls.meet(a, b),
elements,
)
@abc.abstractclassmethod
def join(cls, a: 'LatticeElement', b: 'LatticeElement') -> 'LatticeElement':
"""Return the infimum of the given elements."""
@classmethod
def join_all(cls, elements: t.Iterable['LatticeElement'],
initial: 'LatticeElement' = None) -> 'LatticeElement':
"""
Return the supremum of the given iterable of elements.
Args:
elements: a sequence of elements whose common join to obtain
initial: an additional element to join with the rest.
An empty sequence will result in this value, if provided.
"""
if initial is not None:
return functools.reduce(
lambda a, b: cls.join(a, b),
elements,
initial
)
return functools.reduce(
lambda a, b: cls.join(a, b),
elements,
)
def __eq__(self, other):
return self.value == other.value
def __str__(self):
return str(self.value)
def __repr__(self):
return "<{0} object {1}, {2}>".format(
self.__class__.__name__,
hex(id(self)),
str(self)
)
class BoundedLatticeElement(LatticeElement):
"""An element from a lattice with defined Top and Bottom elements."""
TOP_SYMBOL = "⊤"
BOTTOM_SYMBOL = "⊥"
def __init__(self, value):
"""
Construct a bounded lattice element with the given value.
Args:
value: the value this lattice element should take.
"""
super().__init__(value)
@classmethod
def meet_all(cls,
elements: t.Iterable['BoundedLatticeElement']) -> 'BoundedLatticeElement':
"""
Take the meet of all elements in the given sequence.
An empty sequence produces Top.
"""
return super().meet_all(elements, cls.top())
@classmethod
def join_all(cls,
elements: t.Iterable['BoundedLatticeElement']) -> 'BoundedLatticeElement':
"""
Take the join of all elements in the given sequence.
An empty sequence produces Bottom.
"""
return super().join_all(elements, cls.bottom())
@property
def is_top(self):
"""True if this element is Top."""
return self.value == self._top_val()
@property
def is_bottom(self):
"""True if this element is Bottom."""
return self.value == self._bottom_val()
def __str__(self):
if self.is_top:
return self.TOP_SYMBOL
elif self.is_bottom:
return self.BOTTOM_SYMBOL
else:
return str(self.value)
@abc.abstractclassmethod
def _top_val(cls):
"""Return the Top value of this lattice."""
@abc.abstractclassmethod
def _bottom_val(cls):
"""Return the Bottom value of this lattice."""
@classmethod
def top(cls) -> 'BoundedLatticeElement':
"""Return the Top lattice element."""
return cls(cls._top_val())
@classmethod
def bottom(cls) -> 'BoundedLatticeElement':
"""Return the Bottom lattice element."""
return cls(cls._bottom_val())
def widen_to_top(self):
"""Set this element's value to Top without changing anything else."""
self.value = self._top_val()
class IntLatticeElement(BoundedLatticeElement):
"""
An element of the lattice defined by augmenting
the (unordered) set of integers with top and bottom elements.
Integers are incomparable with one another, while Top and Bottom
compare superior and inferior with every other element, respectively.
"""
def __init__(self, value: int):
"""
Args:
value: the integer this element contains, if it is not Top or Bottom.
"""
super().__init__(value)
def is_int(self) -> bool:
"""True iff this lattice element is neither Top nor Bottom."""
return not (self.is_top or self.is_bottom)
def __add__(self, other):
if self.is_int() and other.is_int():
return IntLatticeElement(self.value + other.value)
return self.bottom()
@classmethod
def _top_val(cls):
return cls.TOP_SYMBOL
@classmethod
def _bottom_val(cls):
return cls.BOTTOM_SYMBOL
@classmethod
def meet(cls,
a: 'IntLatticeElement', b: 'IntLatticeElement') -> 'IntLatticeElement':
"""Return the infimum of the given elements."""
if a.is_bottom or b.is_bottom:
return cls.bottom()
if a.is_top:
return copy(b)
if b.is_top:
return copy(a)
if a.value == b.value:
return copy(a)
return cls.bottom()
@classmethod
def join(cls,
a: 'IntLatticeElement', b: 'IntLatticeElement') -> 'IntLatticeElement':
"""Return the supremum of the given elements."""
if a.is_top or b.is_top:
return cls.top()
if a.is_bottom:
return copy(b)
if b.is_bottom:
return copy(a)
if a.value == b.value:
return copy(a)
return cls.top()
class SubsetLatticeElement(BoundedLatticeElement):
"""
A subset lattice element. The top element is the complete set of all
elements, the bottom is the empty set, and other elements are subsets of top.
"""
def __init__(self, value: t.Iterable):
"""
Args:
value: an iterable of elements which will compose the value of this
lattice element. It will be converted to a set, so duplicate
elements and ordering are ignored.
"""
super().__init__(set(value))
def __len__(self):
if self.is_top:
# TODO: determine if this is the right thing here. TOP has unbounded size.
return 0
return len(self.value)
def __iter__(self):
if self.is_top:
raise TypeError("Top lattice element cannot be iterated.")
return iter(self.value)
def map(self, f: types.FunctionType) -> 'SubsetLatticeElement':
"""
Return the result of applying a function to each of this element's values.
Incidentally, this could be seen as special case of cartesian_map().
"""
if self.is_top:
return copy(self)
return type(self)([f(val) for val in self.value])
@classmethod
def cartesian_map(cls, f: types.FunctionType,
elements: t.Iterable['SubsetLatticeElement']) \
-> 'SubsetLatticeElement':
"""
Apply the given function to each tuple of members in the product of the
input elements, and return the resulting lattice element.
The function's arity must match the number of input elements.
For example, for a binary function, and input elements a, b, the result is
the element defined by the set f(u, v) for each u in a, v in b.
"""
# Symbolic manipulations could be performed here as some operations might
# constrain the results, even if some input set is unconstrained.
if any([e.is_top for e in elements]):
return cls.top()
prod = itertools.product(*(list(e) for e in elements))
return cls([f(*args) for args in prod])
@classmethod
def _top_val(cls):
return set(cls.TOP_SYMBOL)
@classmethod
def _bottom_val(cls):
return set()
@classmethod
def meet(cls, a: 'SubsetLatticeElement',
b: 'SubsetLatticeElement') -> 'SubsetLatticeElement':
"""Return the set intersection of the given elements."""
if a.is_top:
return copy(b)
if b.is_top:
return copy(a)
return cls(a.value & b.value)
@classmethod
def join(cls, a: 'SubsetLatticeElement',
b: 'SubsetLatticeElement') -> 'SubsetLatticeElement':
"""Return the set union of the given elements."""
if a.is_top or b.is_top:
return cls.top()
return cls(a.value | b.value)
@property
def is_const(self) -> bool:
"""True iff this variable has exactly one possible value."""
return self.is_finite and len(self) == 1
@property
def is_finite(self) -> bool:
"""
True iff this variable has a finite and nonzero number of possible values.
"""
return not (self.is_top or self.is_bottom)
+664
View File
@@ -0,0 +1,664 @@
# BSD 3-Clause License
#
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""memtypes.py: Symbolic representations of ways of storing information
in the ethereum machine."""
"""
定义了两个变量 VAR_DEFAULT_NAME 和 VAR_RESULT_NAME。
定义了一个抽象类 Location,表示存储位置,包含标识符、是否为常量、是否为不受约束、可存储的值等属性,以及一个 Variable 类,表示一个符号变量,包含变量的名称、大小、可能的值等属性,并重载了部分操作符,比如相等操作、取值操作等。其中 meet 和 join 方法分别返回两个变量的交集和并集
继承了LatticeElement类的VariableStack类,它定义了一个堆栈数据结构,用于保存TAC(三地址代码)变量。该堆栈还是一个格,因此meet和join在从栈顶向下逐个元素地操作时定义。
该堆栈被视为具有无限容量,空槽向下无限延伸。堆栈中的空槽被解释为具有底部值的变量,以便对于格定义而言,一个空堆栈将是该格的底部,而填有顶部变量的堆栈将是其顶部。因此,我们拥有了一个有界的格,但我们不需要BoundedLatticeElement类所涉及的额外复杂性。
该类中包含的方法有:
init : 类的初始化方法,定义了空堆栈的起始状态、堆栈的最大容量、最小最大容量以及调用的深度。
iter : 类的迭代器方法,从堆栈的顶部向下迭代。
str : 类的字符串表示方法,返回由每个元素的字符串表示组成的列表。
len : 类的长度方法,返回堆栈中的元素数。
eq : 类的相等方法,如果两个堆栈中的元素相等则返回True,否则返回False。
copy : 创建一个堆栈的浅拷贝。
metafy : 将所有无限制的变量转换为元变量,其标签是它们当前的堆栈位置。
peek : 返回从堆栈顶部开始的第n个元素,不弹出任何元素。
push : 将一个变量推入堆栈。
pop : 如果存在符号堆栈,则将其弹出变量,否则从底部生成变量。
push_many : 将一系列元素推入堆栈中,低索引元素首先推入。
pop_many : 从堆栈中弹出并返回n个元素,第一个弹出的元素位于低索引位置。
dup : 将堆栈的第n-1个元素复制到堆栈的顶部。
swap : 交换堆栈[0]与堆栈[n]。
set_max_size : 设置堆栈的最大容量。
"""
import abc
import copy
import typing as t
from itertools import zip_longest, dropwhile
from src.lattice import LatticeElement, SubsetLatticeElement as ssle
import pdb
VAR_DEFAULT_NAME = "Var"
"""The fallback name when creating a fresh variable."""
VAR_RESULT_NAME = "Res"
"""The name to apply to variables resulting from an arithmetic operation."""
class Location(abc.ABC):
"""A generic storage location: variables, memory, static storage."""
@property
def identifier(self) -> str:
"""Return the string identifying this object."""
return str(self)
@property
def is_const(self) -> bool:
"""True if the set of possible values this Location stores is known."""
return False
@property
def is_unconstrained(self) -> bool:
"""
True iff this variable could take on all possible values.
"""
return self.values.is_top
@property
def values(self) -> ssle:
"""
Return the set of values this location may contain.
Generically, this set is unconstrained.
"""
return ssle.top()
class Variable(ssle, Location):
"""
A symbolic variable whose value is supposed to be
the result of some TAC operation. Its size is 32 bytes.
"""
SIZE = 32
"""Variables are 32 bytes in size."""
CARDINALITY = 2 ** (SIZE * 8)
"""
The number of distinct values this variable could contain.
The maximum integer representable by this Variable is then CARDINALITY - 1.
"""
def __init__(self, values: t.Iterable = None, name: str = VAR_DEFAULT_NAME,
def_sites: ssle = ssle.bottom()):
# pdb.set_trace()
"""
Args:
values: the set of values this variable could take.
name: the name that uniquely identifies this variable.
def_sites: a set of locations (TACLocRefs) where this variable
was possibly defined.
"""
# Make sure the input values are not out of range.
#TODO: test by zoey
# print(values)
# print("Debugging memtypes.py in __init__:")
# if values is not None:
# for i, v in enumerate(values):
# if v is None:
# print(f"Item at index {i} in values is None")
# else:
# print(f"Item at index {i} is {v} and its type is {type(v)}")
# print(name) # zoey->print register
# 添加日志记录以调查 values 中的 None 值 - Zoey
if values is not None and any(v is None for v in values):
print(f"\x1b[0;30;41m 警告:在创建 Variable '{name}' 时发现 values 中存在 None 值。相关 values: {values} \x1b[0m ")
# 执行原有逻辑
mod = [] if values is None else [v % self.CARDINALITY for v in values]
super().__init__(value=mod)
self.name = name
self.def_sites = def_sites
def __deepcopy__(self, memodict={}):
if self.is_top:
return type(self).top(self.name, copy.deepcopy(self.def_sites, memodict))
if self.is_bottom:
return type(self).bottom(self.name, copy.deepcopy(self.def_sites, memodict))
return type(self)(copy.deepcopy(self.value, memodict),
self.name,
copy.deepcopy(self.def_sites, memodict))
# Note: type(self) dynamically obtains the Variable class.
# Hence, no explicit Variable constructor reference required.
@property
def values(self) -> ssle:
"""The value set this Variable contains."""
return self
@values.setter
def values(self, vals: t.Iterable):
"""
Set this Variable's value set, ensuring that they are all in range.
Args:
vals: an iterable of values that this Variable will hold
"""
self.value = ssle(v % self.CARDINALITY for v in vals).value
@property
def identifier(self) -> str:
"""Return the string identifying this object."""
return self.name
@property
def is_true(self) -> bool:
"""
True iff all values contained in this variable are nonzero.
N.B. is_true is not the inverse of is_false, as Variables are not bivalent.
"""
if not self.is_finite:
return False
return not any(c == 0 for c in self)
@property
def is_false(self) -> bool:
"""
True iff all values contained in this variable are zero.
N.B. is_false is not the inverse of is_true, as Variables are not bivalent.
"""
if not self.is_finite:
return False
return not any(c != 0 for c in self)
def __str__(self):
if self.is_unconstrained:
return self.identifier
if self.is_const:
return hex(self.const_value)
val_str = ", ".join(hex(val) for val in sorted(self.value))
return "{{{}}}".format(val_str)
def __repr__(self):
return "<{0} object {1}, {2}>".format(
self.__class__.__name__,
hex(id(self)),
self.__str__()
)
def __eq__(self, other):
return self.value == other
def __hash__(self):
if self.is_top:
return hash(self.TOP_SYMBOL) ^ hash(self.name)
else:
# frozenset because plain old sets are unhashable
return hash(frozenset(self.value)) ^ hash(self.name)
@classmethod
def meet(cls, a: 'Variable', b: 'Variable') -> 'Variable':
"""
Return a Variable whose values and def sites are the
intersections of the inputs value and def site sets.
"""
vals = ssle.meet(a, b)
sites = ssle.meet(a.def_sites, b.def_sites)
if vals.is_top:
return cls.top(def_sites=sites)
return cls(values=vals, def_sites=sites)
@classmethod
def join(cls, a: 'Variable', b: 'Variable') -> 'Variable':
"""
Return a Variable whose values and def sites are the
unions of the inputs value and def site sets.
"""
vals = ssle.join(a, b)
sites = ssle.join(a.def_sites, b.def_sites)
if vals.is_top:
return cls.top(def_sites=sites)
return cls(values=vals, def_sites=sites)
@classmethod
def top(cls, name=VAR_DEFAULT_NAME, def_sites: ssle = ssle.bottom()) -> 'Variable':
"""
Return a Variable with Top value, and optionally set its name.
Args:
name: the name of the new variable.
def_sites: a set of locations where this variable was possibly defined.
"""
result = cls(name=name, def_sites=def_sites)
result.value = cls._top_val()
return result
@classmethod
def bottom(cls, name=VAR_DEFAULT_NAME, def_sites: ssle = ssle.bottom()) -> 'Variable':
"""
Return a Variable with Bottom value, and optionally set its name.
Args:
name: the name of the new variable.
def_sites: a set of locations where this variable was possibly defined.
"""
return cls(values=cls._bottom_val(), name=name, def_sites=def_sites)
@property
def const_value(self):
"""If this variable is constant, return its value."""
if not self.is_const:
return None
return next(iter(self))
def complement(self) -> 'Variable':
"""
Return the signed two's complement interpretation of this constant's values.
"""
return type(self)(values=self.value.map(self.twos_comp),
name=VAR_RESULT_NAME)
@classmethod
def twos_comp(cls, v: int) -> int:
"""
Return the signed two's complement interpretation of the given integer.
"""
return v - cls.CARDINALITY if v & (cls.CARDINALITY >> 1) else v
# EVM arithmetic operations follow.
# For comparison operators, "True" and "False" are represented by Constants
# with the value 1 and 0 respectively.
# Op function names should be identical to the opcode names themselves.
@classmethod
def arith_op(cls, opname: str, args: t.Iterable['Variable'],
name=VAR_RESULT_NAME) -> 'Variable':
"""
Apply the named arithmetic operation to the given Variables' values
in all permutations, and return a Variable containing the result.
Args:
opname: the EVM operation to apply.
args: a sequence of Variables whose length matches the
arity of the specified operation.
name: the name of the result Variable.
"""
result = ssle.cartesian_map(getattr(cls, opname), args)
return cls(values=result, name=name)
@classmethod
def ADD(cls, l: int, r: int) -> int:
"""Return the sum of the inputs."""
return l + r
@classmethod
def MUL(cls, l: int, r: int) -> int:
"""Return the product of the inputs."""
return l * r
@classmethod
def SUB(cls, l: int, r: int) -> int:
"""Return the difference of the inputs."""
return l - r
@classmethod
def DIV(cls, l: int, r: int) -> int:
"""Return the quotient of the inputs."""
return 0 if (r == 0) else (l // r)
@classmethod
def SDIV(cls, l: int, r: int) -> int:
"""Return the signed quotient of the inputs."""
l_val, r_val = cls.twos_comp(l), cls.twos_comp(r)
sign = 1 if ((l_val * r_val) >= 0) else -1
return 0 if (r_val == 0) else (sign * (abs(l_val) // abs(r_val)))
@classmethod
def MOD(cls, v: int, m: int) -> int:
"""Modulo operator."""
return 0 if (m == 0) else (v % m)
@classmethod
def SMOD(cls, v: int, m: int) -> int:
"""Signed modulo operator. The output takes the sign of v."""
v_val, m_val = cls.twos_comp(v), cls.twos_comp(m)
sign = 1 if (v_val >= 0) else -1
return 0 if (m == 0) else (sign * (abs(v_val) % abs(m_val)))
@classmethod
def ADDMOD(cls, l: int, r: int, m: int) -> int:
"""Modular addition: return (l + r) modulo m."""
return 0 if (m == 0) else ((l + r) % m)
@classmethod
def MULMOD(cls, l: int, r: int, m: int) -> int:
"""Modular multiplication: return (l * r) modulo m."""
return 0 if (m == 0) else ((l * r) % m)
@classmethod
def EXP(cls, b: int, e: int) -> int:
"""Exponentiation: return b to the power of e."""
return b ** e
@classmethod
def SIGNEXTEND(cls, b: int, v: int) -> int:
"""
Return v, but with the high bit of its b'th byte extended all the way
to the most significant bit of the output.
"""
pos = 8 * (b + 1)
mask = int("1" * ((cls.SIZE * 8) - pos) + "0" * pos, 2)
val = 1 if (v & (1 << (pos - 1))) > 0 else 0
return (v & mask) if (val == 0) else (v | ~mask)
@classmethod
def LT(cls, l: int, r: int) -> int:
"""Less-than comparison."""
return 1 if (l < r) else 0
@classmethod
def GT(cls, l: int, r: int) -> int:
"""Greater-than comparison."""
return 1 if (l > r) else 0
@classmethod
def SLT(cls, l: int, r: int) -> int:
"""Signed less-than comparison."""
return 1 if (cls.twos_comp(l) < cls.twos_comp(r)) else 0
@classmethod
def SGT(cls, l: int, r: int) -> int:
"""Signed greater-than comparison."""
return 1 if (cls.twos_comp(l) > cls.twos_comp(r)) else 0
@classmethod
def EQ(cls, l: int, r: int) -> int:
"""Equality comparison."""
return 1 if (l == r) else 0
@classmethod
def ISZERO(cls, v: int) -> int:
"""1 if the input is zero, 0 otherwise."""
return 1 if (v == 0) else 0
@classmethod
def AND(cls, l: int, r: int) -> int:
"""Bitwise AND."""
return l & r
@classmethod
def OR(cls, l: int, r: int) -> int:
"""Bitwise OR."""
return l | r
@classmethod
def XOR(cls, l: int, r: int) -> int:
"""Bitwise XOR."""
return l ^ r
@classmethod
def NOT(cls, v: int) -> int:
"""Bitwise NOT."""
return ~v
@classmethod
def BYTE(cls, b: int, v: int) -> int:
"""Return the b'th byte of v."""
return (v >> (248 - b * 8)) & 0xFF
@classmethod
def SHL(cls, shift: int, value: int) -> int:
return value << shift
@classmethod
def SHR(cls, shift: int, value: int) -> int:
return value >> shift
@classmethod
def SAR(cls, shift: int, value: int) -> int:
return value // (2 ** shift)
class MetaVariable(Variable):
"""A Variable to stand in for Variables."""
def __init__(self, name: str, payload=None, def_sites: ssle = ssle.bottom()):
"""
Args:
name: the name of the new MetaVariable
payload: some information to carry along with this MetaVariable.
def_sites: a set of locations where this variable was possibly defined.
"""
super().__init__(values=self._bottom_val(), name=name, def_sites=def_sites)
self.value = self._top_val()
"""
The value of this MetaVariable.
MetaVariables are taken to have unconstrained value sets.
"""
self.payload = payload
def __str__(self):
return self.identifier
def __deepcopy__(self, memodict={}):
return type(self)(self.name,
self.payload,
copy.deepcopy(self.def_sites, memodict))
class VariableStack(LatticeElement):
"""
A stack that holds TAC variables.
It is also a lattice, so meet and join are defined, and they operate
element-wise from the top of the stack down.
The stack is taken to be of infinite capacity, with empty slots extending
indefinitely downwards. An empty stack slot is interpreted as a Variable
with Bottom value, for the purposes of the lattice definition.
Thus an empty stack would be this lattice's Bottom, and a stack "filled" with
Top Variables would be its Top.
We therefore have a bounded lattice, but we don't need the extra complexity
associated with the BoundedLatticeElement class.
"""
DEFAULT_MAX = 1024
"""
The default maximum size of a variable stack.
Any further elements pushed to a stack that is at its capacity are discarded.
"""
DEFAULT_MIN_MAX_SIZE = 20
"""The minimum maximum size of a variable stack."""
def __init__(self, state: t.Iterable[Variable] = None,
max_size=DEFAULT_MAX, min_max_size=DEFAULT_MIN_MAX_SIZE,
call_depth: int = None):
super().__init__([] if state is None else list(state))
self.empty_pops = 0
"""The number of times the stack was popped while empty."""
self.min_max_size = min_max_size
"""
The minimum size of this variable stack's maximum size.
Taking the meet of two stacks produces a stack whose maximum size is the
smaller of the two, but at least as large as this value.
"""
self.max_size = max_size
"""
The maximum size of this variable stack before it overflows.
Pushing to a full stack has no effect.
"""
self.set_max_size(max_size)
self.call_depth = call_depth
def __iter__(self):
"""Iteration occurs from head of stack downwards."""
return iter(reversed(self.value))
def __str__(self):
return "[{}]".format(", ".join(str(v) for v in self.value))
def __len__(self):
return len(self.value)
def __eq__(self, other):
return len(self) == len(other) and \
all(v1 == v2 for v1, v2 in
zip(reversed(self.value), reversed(other.value)))
def copy(self) -> 'VariableStack':
"""
Produce a copy of this stack, without deep copying
the variables it contains.
"""
new_stack = type(self)()
new_stack.value = copy.copy(self.value)
new_stack.empty_pops = self.empty_pops
new_stack.max_size = self.max_size
return new_stack
def metafy(self) -> None:
"""
Turn all unconstrained variables into metavariables whose labels
are their current stack position.
"""
for i in range(len(self)):
var = self.value[-(i + 1)]
if var.is_unconstrained:
self.value[-(i + 1)] = self.__new_metavar(i, def_sites=var.def_sites)
@staticmethod
def __new_metavar(n: int, def_sites: ssle = ssle.bottom()) -> MetaVariable:
"""Return a MetaVariable with the given payload and a corresponding name."""
return MetaVariable(name="S{}".format(n), payload=n, def_sites=def_sites)
def peek(self, n: int = 0) -> Variable:
"""Return the n'th element from the top without popping anything."""
if n >= len(self):
return self.__new_metavar(n - len(self) + self.empty_pops)
return self.value[-(n + 1)]
def push(self, var: Variable) -> None:
"""Push a variable to the stack."""
if len(self.value) < self.max_size:
self.value.append(var)
def pop(self) -> Variable:
"""
Pop a variable off our symbolic stack if one exists, otherwise
generate a variable from past the bottom.
"""
if len(self.value):
return self.value.pop()
else:
self.empty_pops += 1
return self.__new_metavar(self.empty_pops - 1)
def push_many(self, vs: t.Iterable[Variable]) -> None:
"""
Push a sequence of elements onto the stack.
Low index elements are pushed first.
"""
for v in vs:
self.push(v)
def pop_many(self, n: int) -> t.List[Variable]:
"""
Pop and return n items from the stack.
First-popped elements inhabit low indices.
"""
return [self.pop() for _ in range(n)]
def dup(self, n: int) -> None:
"""Place a copy of stack[n-1] on the top of the stack."""
items = self.pop_many(n)
duplicated = [items[-1]] + items
self.push_many(reversed(duplicated))
def swap(self, n: int) -> None:
"""Swap stack[0] with stack[n]."""
items = self.pop_many(n)
swapped = [items[-1]] + items[1:-1] + [items[0]]
self.push_many(reversed(swapped))
def set_max_size(self, n: int) -> None:
"""Set this stack's maximum capacity."""
new_size = max(self.min_max_size, n)
self.max_size = new_size
self.value = self.value[-new_size:]
@classmethod
def meet(cls, a: 'VariableStack', b: 'VariableStack') -> 'VariableStack':
"""
Return the meet of the given stacks, taking the element-wise meets of their
contained Variables from the top down.
"""
pairs = zip_longest(reversed(a.value), reversed(b.value),
fillvalue=Variable.bottom())
max_size = a.max_size if a.max_size < b.max_size else b.max_size
return cls(dropwhile(lambda x: x.is_bottom,
[Variable.meet(*p) for p in pairs][::-1]),
max_size)
@classmethod
def join(cls, a: 'VariableStack', b: 'VariableStack') -> 'VariableStack':
"""
Return the join of the given stacks, taking the element-wise joins of their
contained Variables from the top down.
"""
pairs = zip_longest(reversed(a.value), reversed(b.value),
fillvalue=Variable.bottom())
max_size = a.max_size if a.max_size > b.max_size else b.max_size
return cls([Variable.join(*p) for p in pairs][::-1], max_size)
@classmethod
def join_all(cls, elements: t.Iterable['VariableStack']) -> 'VariableStack':
"""
Return the common meet of the given sequence; an empty sequence
yields an empty stack.
"""
return super().join_all(elements, initial=VariableStack())
+434
View File
@@ -0,0 +1,434 @@
# BSD 3-Clause License
#
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""opcodes.py: Definitions of all EVM opcodes, and related utility functions."""
class OpCode:
"""An EVM opcode."""
def __init__(self, name: str, code: int, pop: int, push: int):
"""
Args:
name (str): Human-readable opcode, such as opcode PUSH1.
code (int): The instruction byte itself.
pop (int): The number of stack elements this op pops.
push (int): The number of stack elements this op pushes.
"""
self.name = name
self.code = code
self.pop = pop
self.push = push
def stack_delta(self) -> int:
"""Return the net effect on the stack size of running this operation."""
return self.push - self.pop
def __str__(self) -> str:
return self.name
def __repr__(self) -> str:
return "<{0} object {1}, {2}>".format(
self.__class__.__name__,
hex(id(self)),
self.__str__()
)
def __eq__(self, other) -> bool:
return self.code == other.code
def __hash__(self) -> int:
return self.code.__hash__()
# Special cases for kind one, such as CALLVALUE
# Those opcodes do not need anything from stack, but will give related dynamic info
# TODO: adding SELFBALANCE, CHAINID, BASEFEE for the current EVM opcode version - zoey
def is_kind_one(self) -> bool:
special = {ADDRESS.name, ORIGIN.name, CALLER.name, CALLVALUE.name, CALLDATASIZE.name,
CODESIZE.name, GASPRICE.name, RETURNDATASIZE.name, COINBASE.name, TIMESTAMP.name,
NUMBER.name, DIFFICULTY.name, GASLIMIT.name, PC.name, MSIZE.name, GAS.name,
SELFBALANCE.name, CHAINID.name,BASEFEE.name}
return self.name in special
# Special cases for kind two, such as CALLDATALOAD
# Need one or more stack arguments and related dynamic info
# TODO: adding BALANCE for the current EVM opcode version - zoey
def is_kind_two(self) -> bool:
special = {SHA3.name, BALANCE.name, CALLDATALOAD.name, EXTCODESIZE.name, EXTCODEHASH.name, BLOCKHASH.name,
BALANCE.name}
return self.name in special
# Special cases for part of kind three load, SLOAD
def is_kind_three_load(self) -> bool:
special = {SLOAD.name,MLOAD.name} # zoey 2.19 - add MLOAD.name as it should work as the SLOAD
return self.name in special
# Special cases for part of kind three store, like MSTORE
def is_kind_three_store_one(self) -> bool:
special = {MSTORE.name, MSTORE8.name, SSTORE.name}
return self.name in special
# Special cases for some other store operations, they are special
# since they do need some arguments from the stack and then get the related data
# to store them into the memory
def is_kind_three_store_two(self) -> bool:
special = {CALLDATACOPY.name, CODECOPY.name, EXTCODECOPY.name, RETURNDATACOPY.name}
return self.name in special
# Special cases for four call opcodes
def is_kind_four(self) -> bool:
special = {CALL.name, CALLCODE.name, DELEGATECALL.name, STATICCALL.name}
return self.name in special
def is_kind_five(self) -> bool:
special = {CREATE.name, CREATE2.name}
return self.name in special
def op_pc_gap(self) -> int:
if self.is_push():
return int(self.name[4]) + 1
else:
return 1
def is_push(self) -> bool:
"""Predicate: opcode is a push operation."""
# return PUSH1.code <= self.code <= PUSH32.code
return PUSH0.code <= self.code <= PUSH32.code # TODO: adding PUSH0 for EIP-3855 - zoey
def is_swap(self) -> bool:
"""Predicate: opcode is a swap operation."""
return SWAP1.code <= self.code <= SWAP16.code
def is_dup(self) -> bool:
"""Predicate: opcode is a dup operation."""
return DUP1.code <= self.code <= DUP16.code
def is_log(self) -> bool:
"""Predicate: opcode is a log operation."""
return LOG0.code <= self.code <= LOG4.code
def is_missing(self) -> bool:
return self.code not in BYTECODES
def is_invalid(self) -> bool:
return (self.code == INVALID.code) or self.is_missing()
def is_arithmetic(self) -> bool:
"""Predicate: opcode's result can be calculated from its inputs alone."""
return (ADD.code <= self.code <= SIGNEXTEND.code) or \
(LT.code <= self.code <= SAR.code)
def is_memory(self) -> bool:
"""Predicate: opcode operates on memory"""
return MLOAD.code <= self.code <= MSTORE8.code
def is_storage(self) -> bool:
"""Predicate: opcode operates on storage ('the tape')"""
return SLOAD.code <= self.code <= SSTORE.code
def is_call(self) -> bool:
"""Predicate: opcode calls an external contract"""
return self in (CALL, CALLCODE, DELEGATECALL, STATICCALL,)
def alters_flow(self) -> bool:
"""Predicate: opcode alters EVM control flow."""
return (self.code in (JUMP.code, JUMPI.code,)) or self.possibly_halts()
def is_exception(self) -> bool:
"""Predicate: opcode causes the EVM to throw an exception."""
return (self.code in (THROW.code, THROWI.code, REVERT.code)) \
or self.is_invalid()
def halts(self) -> bool:
"""Predicate: opcode causes the EVM to halt."""
halt_codes = (
STOP.code,
RETURN.code,
SELFDESTRUCT.code,
THROW.code,
REVERT.code,
)
return (self.code in halt_codes) or self.is_invalid()
def possibly_halts(self) -> bool:
"""Predicate: opcode MAY cause the EVM to halt. (halts + THROWI)"""
return self.halts() or self.code == THROWI.code
def push_len(self) -> int:
"""Return the number of bytes the given PUSH instruction pushes."""
if self.code == PUSH0.code: #
return PUSH1.code # this if statement is added for EIP-3855 PUSH0- zoey
return self.code - PUSH1.code + 1 if self.is_push() else 0
def log_len(self) -> int:
"""Return the number of topics the given LOG instruction includes."""
return self.code - LOG0.code if self.is_log() else 0
# Construct all EVM opcodes
# Arithmetic Ops and STOP
STOP = OpCode("STOP", 0x00, 0, 0)
ADD = OpCode("ADD", 0x01, 2, 1)
MUL = OpCode("MUL", 0x02, 2, 1)
SUB = OpCode("SUB", 0x03, 2, 1)
DIV = OpCode("DIV", 0x04, 2, 1)
SDIV = OpCode("SDIV", 0x05, 2, 1)
MOD = OpCode("MOD", 0x06, 2, 1)
SMOD = OpCode("SMOD", 0x07, 2, 1)
ADDMOD = OpCode("ADDMOD", 0x08, 3, 1)
MULMOD = OpCode("MULMOD", 0x09, 3, 1)
EXP = OpCode("EXP", 0x0a, 2, 1)
SIGNEXTEND = OpCode("SIGNEXTEND", 0x0b, 2, 1)
# Comparison and Bitwise Logic
LT = OpCode("LT", 0x10, 2, 1)
GT = OpCode("GT", 0x11, 2, 1)
SLT = OpCode("SLT", 0x12, 2, 1)
SGT = OpCode("SGT", 0x13, 2, 1)
EQ = OpCode("EQ", 0x14, 2, 1)
ISZERO = OpCode("ISZERO", 0x15, 1, 1)
AND = OpCode("AND", 0x16, 2, 1)
OR = OpCode("OR", 0x17, 2, 1)
XOR = OpCode("XOR", 0x18, 2, 1)
NOT = OpCode("NOT", 0x19, 1, 1)
BYTE = OpCode("BYTE", 0x1a, 2, 1)
SHL = OpCode("SHL", 0x1b, 2, 1)
SHR = OpCode("SHR", 0x1c, 2, 1)
SAR = OpCode("SAR", 0x1d, 2, 1)
SHA3 = OpCode("SHA3", 0x20, 2, 1)
KECCAK256 = OpCode("KECCAK256", 0x20, 2, 1)
# Environmental Information
ADDRESS = OpCode("ADDRESS", 0x30, 0, 1)
BALANCE = OpCode("BALANCE", 0x31, 1, 1)
ORIGIN = OpCode("ORIGIN", 0x32, 0, 1)
CALLER = OpCode("CALLER", 0x33, 0, 1)
CALLVALUE = OpCode("CALLVALUE", 0x34, 0, 1)
CALLDATALOAD = OpCode("CALLDATALOAD", 0x35, 1, 1)
CALLDATASIZE = OpCode("CALLDATASIZE", 0x36, 0, 1)
CALLDATACOPY = OpCode("CALLDATACOPY", 0x37, 3, 0)
CODESIZE = OpCode("CODESIZE", 0x38, 0, 1)
CODECOPY = OpCode("CODECOPY", 0x39, 3, 0)
GASPRICE = OpCode("GASPRICE", 0x3a, 0, 1)
EXTCODESIZE = OpCode("EXTCODESIZE", 0x3b, 1, 1)
EXTCODECOPY = OpCode("EXTCODECOPY", 0x3c, 4, 0)
# Block Information
BLOCKHASH = OpCode("BLOCKHASH", 0x40, 1, 1)
COINBASE = OpCode("COINBASE", 0x41, 0, 1)
TIMESTAMP = OpCode("TIMESTAMP", 0x42, 0, 1)
NUMBER = OpCode("NUMBER", 0x43, 0, 1)
DIFFICULTY = OpCode("DIFFICULTY", 0x44, 0, 1)
GASLIMIT = OpCode("GASLIMIT", 0x45, 0, 1)
CHAINID = OpCode("CHAINID", 0x46, 0, 1) # Zoey
BASEFEE = OpCode("BASEFEE", 0x48, 0, 1) # Zoey
# Stack, Memory, Storage, Flow
POP = OpCode("POP", 0x50, 1, 0)
MLOAD = OpCode("MLOAD", 0x51, 1, 1)
MSTORE = OpCode("MSTORE", 0x52, 2, 0)
MSTORE8 = OpCode("MSTORE8", 0x53, 2, 0)
SLOAD = OpCode("SLOAD", 0x54, 1, 1)
SSTORE = OpCode("SSTORE", 0x55, 2, 0)
JUMP = OpCode("JUMP", 0x56, 1, 0)
JUMPI = OpCode("JUMPI", 0x57, 2, 0)
PC = OpCode("PC", 0x58, 0, 1)
MSIZE = OpCode("MSIZE", 0x59, 0, 1)
GAS = OpCode("GAS", 0x5a, 0, 1)
JUMPDEST = OpCode("JUMPDEST", 0x5b, 0, 0)
PUSH1 = OpCode("PUSH1", 0x60, 0, 1)
PUSH2 = OpCode("PUSH2", 0x61, 0, 1)
PUSH3 = OpCode("PUSH3", 0x62, 0, 1)
PUSH4 = OpCode("PUSH4", 0x63, 0, 1)
PUSH5 = OpCode("PUSH5", 0x64, 0, 1)
PUSH6 = OpCode("PUSH6", 0x65, 0, 1)
PUSH7 = OpCode("PUSH7", 0x66, 0, 1)
PUSH8 = OpCode("PUSH8", 0x67, 0, 1)
PUSH9 = OpCode("PUSH9", 0x68, 0, 1)
PUSH10 = OpCode("PUSH10", 0x69, 0, 1)
PUSH11 = OpCode("PUSH11", 0x6a, 0, 1)
PUSH12 = OpCode("PUSH12", 0x6b, 0, 1)
PUSH13 = OpCode("PUSH13", 0x6c, 0, 1)
PUSH14 = OpCode("PUSH14", 0x6d, 0, 1)
PUSH15 = OpCode("PUSH15", 0x6e, 0, 1)
PUSH16 = OpCode("PUSH16", 0x6f, 0, 1)
PUSH17 = OpCode("PUSH17", 0x70, 0, 1)
PUSH18 = OpCode("PUSH18", 0x71, 0, 1)
PUSH19 = OpCode("PUSH19", 0x72, 0, 1)
PUSH20 = OpCode("PUSH20", 0x73, 0, 1)
PUSH21 = OpCode("PUSH21", 0x74, 0, 1)
PUSH22 = OpCode("PUSH22", 0x75, 0, 1)
PUSH23 = OpCode("PUSH23", 0x76, 0, 1)
PUSH24 = OpCode("PUSH24", 0x77, 0, 1)
PUSH25 = OpCode("PUSH25", 0x78, 0, 1)
PUSH26 = OpCode("PUSH26", 0x79, 0, 1)
PUSH27 = OpCode("PUSH27", 0x7a, 0, 1)
PUSH28 = OpCode("PUSH28", 0x7b, 0, 1)
PUSH29 = OpCode("PUSH29", 0x7c, 0, 1)
PUSH30 = OpCode("PUSH30", 0x7d, 0, 1)
PUSH31 = OpCode("PUSH31", 0x7e, 0, 1)
PUSH32 = OpCode("PUSH32", 0x7f, 0, 1)
DUP1 = OpCode("DUP1", 0x80, 1, 2)
DUP2 = OpCode("DUP2", 0x81, 2, 3)
DUP3 = OpCode("DUP3", 0x82, 3, 4)
DUP4 = OpCode("DUP4", 0x83, 4, 5)
DUP5 = OpCode("DUP5", 0x84, 5, 6)
DUP6 = OpCode("DUP6", 0x85, 6, 7)
DUP7 = OpCode("DUP7", 0x86, 7, 8)
DUP8 = OpCode("DUP8", 0x87, 8, 9)
DUP9 = OpCode("DUP9", 0x88, 9, 10)
DUP10 = OpCode("DUP10", 0x89, 10, 11)
DUP11 = OpCode("DUP11", 0x8a, 11, 12)
DUP12 = OpCode("DUP12", 0x8b, 12, 13)
DUP13 = OpCode("DUP13", 0x8c, 13, 14)
DUP14 = OpCode("DUP14", 0x8d, 14, 15)
DUP15 = OpCode("DUP15", 0x8e, 15, 16)
DUP16 = OpCode("DUP16", 0x8f, 16, 17)
SWAP1 = OpCode("SWAP1", 0x90, 2, 2)
SWAP2 = OpCode("SWAP2", 0x91, 3, 3)
SWAP3 = OpCode("SWAP3", 0x92, 4, 4)
SWAP4 = OpCode("SWAP4", 0x93, 5, 5)
SWAP5 = OpCode("SWAP5", 0x94, 6, 6)
SWAP6 = OpCode("SWAP6", 0x95, 7, 7)
SWAP7 = OpCode("SWAP7", 0x96, 8, 8)
SWAP8 = OpCode("SWAP8", 0x97, 9, 9)
SWAP9 = OpCode("SWAP9", 0x98, 10, 10)
SWAP10 = OpCode("SWAP10", 0x99, 11, 11)
SWAP11 = OpCode("SWAP11", 0x9a, 12, 12)
SWAP12 = OpCode("SWAP12", 0x9b, 13, 13)
SWAP13 = OpCode("SWAP13", 0x9c, 14, 14)
SWAP14 = OpCode("SWAP14", 0x9d, 15, 15)
SWAP15 = OpCode("SWAP15", 0x9e, 16, 16)
SWAP16 = OpCode("SWAP16", 0x9f, 17, 17)
# Logging
LOG0 = OpCode("LOG0", 0xa0, 2, 0)
LOG1 = OpCode("LOG1", 0xa1, 3, 0)
LOG2 = OpCode("LOG2", 0xa2, 4, 0)
LOG3 = OpCode("LOG3", 0xa3, 5, 0)
LOG4 = OpCode("LOG4", 0xa4, 6, 0)
# System Operations
CREATE = OpCode("CREATE", 0xf0, 3, 1)
CREATE2 = OpCode("CREATE2", 0xf5, 4, 1)
CALL = OpCode("CALL", 0xf1, 7, 1)
CALLCODE = OpCode("CALLCODE", 0xf2, 7, 1)
RETURN = OpCode("RETURN", 0xf3, 2, 0)
DELEGATECALL = OpCode("DELEGATECALL", 0xf4, 6, 1)
INVALID = OpCode("INVALID", 0xfe, 0, 0)
SELFDESTRUCT = OpCode("SELFDESTRUCT", 0xff, 1, 0)
# New Byzantinium OpCodes for block.number >= BYZANTIUM_FORK_BLKNUM
REVERT = OpCode("REVERT", 0xfd, 2, 0)
RETURNDATASIZE = OpCode("RETURNDATASIZE", 0x3d, 0, 1)
RETURNDATACOPY = OpCode("RETURNDATACOPY", 0x3e, 3, 0)
STATICCALL = OpCode("STATICCALL", 0xfa, 6, 1)
# TAC Operations
# These are not EVM opcodes, but they are used by the three-address code
NOP = OpCode("NOP", -1, 0, 0)
CONST = OpCode("CONST", -2, 0, 0)
LOG = OpCode("LOG", -3, 0, 0)
THROW = OpCode("THROW", -4, 0, 0)
THROWI = OpCode("THROWI", -5, 0, 0)
# TODO: adding when debuging - zoey
SELFBALANCE = OpCode("SELFBALANCE", 0x47, 0, 1)
EXTCODEHASH = OpCode("EXTCODEHASH", 0x3f, 1, 1)
PUSH0 = OpCode("PUSH0", 0x5f, 0, 1)
# Assuming you want PUSH0 to push 0 bytes onto the stack, which is unusual for EVM
# PUSH0 = OpCode("PUSH0", [unique_value], 0, 1)
# Produce mappings from names and instruction codes to opcode objects
OPCODES = {
code.name: code
for code in globals().values()
if isinstance(code, OpCode)
}
"""Dictionary mapping of opcode string names to EVM OpCode objects"""
# Handle incorrect opcode name from go-ethereum disasm
OPCODES["TXGASPRICE"] = OPCODES["GASPRICE"]
BYTECODES = {code.code: code for code in OPCODES.values()}
"""Dictionary mapping of byte values to EVM OpCode objects"""
def opcode_by_name(name: str) -> OpCode:
"""
Mapping: Retrieves the named OpCode object (case-insensitive).
Throws:
LookupError: if there is no opcode defined with the given name.
"""
name = name.upper()
if name not in OPCODES:
raise LookupError("No opcode named '{}'.".format(name))
return OPCODES[name]
def opcode_by_value(val: int) -> OpCode:
"""
Mapping: Retrieves the OpCode object with the given value.
Throws:
LookupError: if there is no opcode defined with the given value.
"""
if val not in BYTECODES:
raise LookupError("No opcode with value '0x{:02X}'.".format(val))
return BYTECODES[val]
def missing_opcode(val: int) -> OpCode:
"""
Produces a new OpCode with the given value, as long as that is
an unknown code.
Throws:
ValueError: if there is an opcode defined with the given value.
"""
if val in BYTECODES:
raise ValueError("Opcode {} exists.")
return OpCode("MISSING", val, 0, 0)
+177
View File
@@ -0,0 +1,177 @@
# BSD 3-Clause License
#
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""patterns.py: abstract base classes for the various design patterns"""
import abc
import inspect
class Visitable(abc.ABC):
"""
Provides an interface for an object which can accept a :obj:`Visitor`.
"""
def accept(self, visitor: 'Visitor'):
"""
Accepts a :obj:`Visitor` and calls :obj:`Visitor.visit`
Args:
visitor: the :obj:`Visitor` to accept
"""
visitor.visit(self)
class Visitor(abc.ABC):
"""
Visitor design pattern abstract base class.
"""
@abc.abstractmethod
def visit(self, target: Visitable, *args, **kwargs):
"""
Visits the given object.
Args:
target: object to visit.
"""
def can_visit(self, type_):
"""
Checks if this :obj:`Visitor` can visit an object of the given `type_`.
By default a :obj:`Visitor` can visit all types, so subclasses of
:obj:`Visitor` should override this method if necessary.
Args:
type_ (type): a valid Python :obj:`type` to be checked.
Returns:
True (by default)
"""
return True
class DynamicVisitor(Visitor):
"""
Visitor base class which dynamically calls a specialised visit method based
on the target's type at runtime.
Example:
Subclassing :obj:`DynamicVisitor`::
class PrinterDynamicVisitor(DynamicVisitor):
def visit_str(self, string:str):
print(string)
def visit_int(self, integer:int):
print("{:08b}".format(integer))
def visit_object(self, obj:object):
print(obj)
pdv = PrinterDynamicVisitor()
pdv.visit("hello")
pdv.visit(5)
"""
def __init__(self):
super().__init__()
# Don't allow instantiation of DynamicVisitor itself
if type(self) is DynamicVisitor:
raise NotImplementedError("DynamicVisitor must be sub-classed")
def visit(self, target: Visitable, *args, **kwargs):
"""
Dispatches to a method called visit_TYPE where TYPE is the dynamic type
(or the nearest parent type) of the `target`.
Args:
target: object to visit.
*args: arguments to be passed to the type-specific visit method.
**kwargs: optional/keyword arguments to be passed to the type-specific
visit method.
"""
# Try to find a visit method for our target's type
visit_method = self.__get_visit_method(type(target))
# If we found a visit method, call it and return its returned value
if visit_method is not None:
return visit_method(target, *args, **kwargs)
# If no matching visit_TYPE method exists, call _no_visit_found
return self._no_visit_found(target, *args, **kwargs)
def can_visit(self, type_):
"""
Checks if this :obj:`DynamicVisitor` can visit an object of the given
`type_`.
Args:
type_ (type): a valid Python :obj:`type` to be checked.
Returns:
True if the current :obj:`DynamicVisitor` can visit the specified
`type_` or False otherwise.
"""
return self.__get_visit_method(type_) is not None
def __get_visit_method(self, type_):
"""
Returns a visit method for the given type_, or None if none could be
found.
"""
# Try all the type names in the target's MRO
for base in inspect.getmro(type_):
visit_name = "visit_{}".format(base.__name__)
# If we found a matching visit_TYPE method, return it
if hasattr(self, visit_name):
visit_method = getattr(self, visit_name)
return visit_method
# Not found => return None
return None
def _no_visit_found(self, target, *args, **kwargs):
"""
Called when no matching visit_TYPE method exists for the target's type.
Raises a TypeError by default and should be overridden if different
behaviour is desired by a derived class.
Args:
target: object passed to :obj:`visit`
*args: arguments passed to :obj:`visit`
**kwargs: keyword arguments passed to :obj:`visit`
Raises:
TypeError
"""
raise TypeError("could not find a visit method for target type {}"
.format(type(target).__name__))
+254
View File
@@ -0,0 +1,254 @@
# BSD 3-Clause License
#
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""settings.py: dataflow analysis settings.
The user can change these settings in bin/config.ini or by providing command
line flags to override them.
Default settings are stored in src/default_config.ini.
max_iterations:
The maximum number of graph analysis iterations.
Lower is faster, but potentially less precise.
A negative value means no limit. No limit by default.
bailout_seconds:
Begin to terminate the analysis loop if it's looking to take more time
than specified. Bailing out early may mean the analysis is not able
to reach a fixed-point, so the results may be less precise.
This is not a hard cap, as subsequent analysis steps are required,
and at least one iteration will always be performed.
A negative value means no cap on the running time.
No cap by default.
remove_unreachable:
Upon completion of the analysis, if there are blocks unreachable from the
contract root, remove them. False by default.
merge_unreachable:
Upon completion of the analysis, if there are blocks unreachable from the
contract root, merge them. True by default.
die_on_empty_pop:
Raise an exception if an empty stack is popped. False by default.
skip_stack_on_overflow:
Do not apply changes to exit stacks after a symbolic overflow occurrs
in their blocks. True by default.
reinit_stacks:
Reinitialise all blocks' exit stacks to be empty. True by default.
hook_up_stack_vars:
After completing the analysis, propagate entry stack values into blocks.
True by default.
hook_up_jumps:
Connect any new edges that can be inferred after performing the analysis.
True by default.
mutate_jumps:
JUMPIs with known conditions become JUMPs (or are deleted).
For example, a JUMPI with a known-true condition becomes a JUMP.
False by default.
generate_throws:
JUMP and JUMPI instructions with invalid destinations become THROW and
THROWIs. False by default.
final_mutate_jumps:
Mutate jumps in the final analysis phase. False by default.
final_generate_throws:
generate throws in the final analysis phase. True by default.
mutate_blockwise:
Hook up stack vars and/or hook up jumps after each block rather than after
the whole analysis is complete. True by default.
clamp_large_stacks:
If stacks start growing deeper without more of the program's control flow
graph being inferred for sufficiently many iterations, we can freeze the
maximum stack size in order to save computation.
True by default.
clamp_stack_minimum:
Stack sizes will not be clamped smaller than this value. Default value is 20.
widen_variables:
If any computed variable's number of possible values exceeds a given
threshold, widen its value to Top. True by default.
widen_threshold:
Whenever the result of an operation may take more than this number of
possible values, then widen the result variable's value to the Top lattice
value (treat its value as unconstrained).
Default value is 10.
set_valued_ops:
If True, apply arithmetic operations to variables with multiple values;
otherwise, only apply them to variables whose value takes only one
value.
Disable to gain speed at the cost of precision. True by default.
analytics:
If True, dataflow analysis will return a dict of information about
the contract, otherwise return an empty dict.
Disabling this might yield a slight speed improvement. False by default.
extract_functions:
If True, attempt to extract solidity functions.
mark_functions:
If true, tag block names with the function(s) they belong to.
strict:
If true, then unrecognised opcodes and invalid disassembly
will not be skipped, but will result in an error.
Note: If we have already reached complete information about our stack CFG
structure and stack states, we can use die_on_empty_pop and reinit_stacks
to discover places where empty stack exceptions will be thrown.
"""
# The settings - these are None until initialised by import_config
max_iterations = None
bailout_seconds = None
remove_unreachable = None
merge_unreachable = None
die_on_empty_pop = None
skip_stack_on_overflow = None
reinit_stacks = None
hook_up_stack_vars = None
hook_up_jumps = None
mutate_jumps = None
generate_throws = None
final_mutate_jumps = None
final_generate_throws = None
mutate_blockwise = None
clamp_large_stacks = None
clamp_stack_minimum = None
widen_variables = None
widen_threshold = None
set_valued_ops = None
analytics = None
extract_functions = None
mark_functions = None
strict = None
# A reference to this module for retrieving its members; import sys like this so that it does not appear in _names_.
_module_ = __import__("sys").modules[__name__]
# The names of all the settings defined above.
_names_ = [s for s in dir(_module_) if not (s.startswith("_"))]
# Set up the types of the various settings, so they can be converted
# correctly when being read from config.
_types_ = {n: ("int" if n in ["max_iterations", "bailout_seconds",
"clamp_stack_minimum", "widen_threshold"]
else "bool") for n in _names_}
# A stack for saving and restoring setting configurations.
_stack_ = []
# Imports and definitions appearing below the definition of _names_
# do not appear in that list, by design. Don't move them up.
import logging
import sys
from os.path import dirname, normpath, join
_dir_ = dirname(__file__)
# Default settings are stored here.
_DEFAULT_LOC_ = normpath(join(_dir_, "../src/default_config.ini"))
# User settings are located here, and will override default settings.
_CONFIG_LOC_ = normpath(join(_dir_, "../bin/config.ini"))
def _get_dict_():
"""
Return the current module's dictionary of members so the settings can be
dynamically accessed by name.
"""
return _module_.__dict__
def save():
"""Push the current setting configuration to the stack."""
sd = _get_dict_()
_stack_.append({n: sd[n] for n in _names_})
def restore():
"""Restore the setting configuration from the top of the stack."""
_get_dict_().update(_stack_.pop())
def set_from_string(setting_name: str, value: str):
"""
Assign to the named setting the given value, first converting that value
to the type appropriate for that setting.
Names and values are not case sensitive.
"""
name = setting_name.lower()
val = value.lower()
if name not in _names_:
logging.error('Unrecognised setting "%s".', setting_name)
sys.exit(1)
if _types_[name] == "int":
_get_dict_()[name] = int(val)
elif _types_[name] == "bool":
if val in {"1", "yes", "true", "on"}:
_get_dict_()[name] = True
elif val in {"0", "no", "false", "off"}:
_get_dict_()[name] = False
else:
logging.error('Cannot interpret value "%s" as boolean for setting "%s"',
value, setting_name)
sys.exit(1)
else:
logging.error('Unknown type "%s" for setting "%s".', setting_name)
sys.exit(1)
def import_config(filepath: str = _CONFIG_LOC_):
"""
Import settings from the given configuration file.
This should be called before running the decompiler.
"""
import configparser
config = configparser.ConfigParser()
with open(_DEFAULT_LOC_) as default:
config.read_file(default)
config.read(filepath)
for name in _names_:
set_from_string(name, config.get("settings", name))
+1015
View File
File diff suppressed because it is too large. Load diff
+830
View File
@@ -0,0 +1,830 @@
# BSD 3-Clause License
#
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""tac_efg.py: generate a Intermediate Representation based execution flow graph."""
"""这份脚本实现了从EFG convert to IR的过程
该代码由以下几个类组成:
TACGraph:基于TAC(Three-Address Code,三地址码)的执行流图(Execution Flow Graph,EFG),用于分析以太坊智能合约的字节码(Bytecode)
TACBasicBlock:基于TAC的基本块(Basic Block),用于分析以太坊智能合约的字节码
TACOp,TACOpConst:基于TAC的操作码(Operation Code,OpCode),用于分析以太坊智能合约的字节码。
TACOp代表了三地址代码中的一条操作,就是三地址代码中的一条指令,它执行某种计算或操作
TACAssignOp代表一条分配操作,将一个值存储到指定的内存位置或寄存器中
TACArg包含了操作的参数信息
TACLocRef包含了一个基本块内特定指令的程序计数器引用
Destackifier将EVMBasicBlocks转换为TACBasicBlocks
"""
from collections import defaultdict
import copy
import logging
import typing as t
import src.cfg as cfg
import src.evm_efg as evm_efg
import src.memtypes as mem
import src.opcodes as opcodes
import src.patterns as patterns
import src.settings as settings
from src.lattice import SubsetLatticeElement as ssle
POSTDOM_END_NODE = "END"
"""The name of the synthetic end node added for post-dominator calculations."""
UNRES_DEST = "?"
"""The name of the unresolved jump destination auxiliary node."""
def remove_0x(temptstr):
if temptstr.startswith("0x"):
return temptstr[2:]
return temptstr
class TACGraph(cfg.ControlFlowGraph):
"""
A execution flow graph holding Three-Address Code blocks and the edges between them.
"""
def __init__(self, evm_blocks: t.Iterable[evm_efg.EVMBasicBlock]):
"""
Construct a TAC execution flow graph from a given sequence of EVM blocks.
Immediately after conversion, constants will be propagated and folded
through arithmetic operations, and EFG edges will be connected up, wherever
they can be inferred.
Args:
evm_blocks: an iterable of EVMBasicBlocks to convert into TAC form.
"""
super().__init__()
# Convert the input EVM blocks to TAC blocks.
destack = Destackifier()
# Create a whole global stack to store all the tempt_stacks to deal with multiple smart contracts
stacks = []
for i, b in enumerate(evm_blocks):
tac_block = destack.convert_block(b, stacks)
self.blocks.append(tac_block)
"""The sequence of TACBasicBlocks contained in this graph."""
for b in self.blocks:
b.cfg = self
self.root = next((b for b in self.blocks if b.entry == 0), None)
"""
The root block of this EFG.
The entry point will always be at index 0, if it exists.
"""
self.split_node_succs = {}
"""
A mapping from addresses to addresses storing all successors of a
block at the time it was split. At merge time these edges can be restored.
"""
self.function_extractor = None
"""
A FunctionExtractor object, which encapsulates solidity functions and extracts logic.
"""
# Add some fields to store stack and memory values
# This function is not used in our work
self.stack_values = defaultdict(dict)
self.memory = bytearray()
# Propagate constants and add EFG edges.
# Assgin the opcode related value to memory and stack
self.apply_operations()
# Connect all the edges
for i, b in enumerate(self.blocks):
b.index = i
self.connectEFGNode()
# Accept extracted argument from the opcode
@classmethod
def from_opcode(cls, opcode: t.Iterable) -> 'TACGraph':
"""
Construct and return a TACGraph from the given geth opcode.
Args:
opcode: a sequence of EVM opcode, either in a hexadecimal string format or a byte array.
"""
original_opcodes = []
for l in opcode:
if len(l.strip()) > 0:
# One example of the opcode line: 0; PUSH1; 0x64, which represents <PC; OPCODE NAME; ARGS>
args = l.strip().split(";")
if args[2] == "":
original_opcodes.append(evm_efg.EVMOp(int(args[0]), opcodes.opcode_by_name(args[1]), None, None))
else:
if "," in args[2]:
args_extras = args[2].strip().split(",")
original_opcodes.append(evm_efg.EVMOp(int(args[0]), opcodes.opcode_by_name(args[1]),
int(args_extras[0]), int(args_extras[1])))
else:
if opcodes.opcode_by_name(args[1]).name == opcodes.SELFDESTRUCT.name:
addr = int(args[2], 0)
original_opcodes.append(evm_efg.EVMOp(int(args[0]), opcodes.opcode_by_name(args[1]), addr, None))
else:
original_opcodes.append(evm_efg.EVMOp(int(args[0]), opcodes.opcode_by_name(args[1]), int(args[2]), None))
# Obtain evm opcode based EFG firstly
basic_blocks = evm_efg.blocks_from_ops(original_opcodes)
return cls(basic_blocks)
@property
def tac_ops(self):
for block in self.blocks:
for op in block.tac_ops:
yield op
@property
def last_op(self):
return max((b.last_op for b in self.blocks),
key=lambda o: o.pc)
def apply_operations(self, use_sets=False) -> None:
"""
Propagate and fold constants through the arithmetic TAC instructions
in this EFG.
If use_sets is True, folding will also be done on Variables that
possess multiple possible values, performing operations in all possible
combinations of values.
"""
for block in self.blocks:
# Add stack and memory for the whole TAC-based EFG and store the related value from geth to memory, stack
block.apply_operations(self.stack_values, self.memory, use_sets)
def connectEFGNode(self):
if len(list(self.blocks)) > 1:
for i, block in enumerate(self.blocks):
if i == 0:
block.preds = []
block.succs = [self.blocks[i + 1]]
elif i == len(self.blocks)-1:
block.preds = [self.blocks[i - 1]]
block.succs = []
else:
block.preds = [self.blocks[i - 1]]
block.succs = [self.blocks[i + 1]]
def extract_functions(self):
"""
Attempt to extract solidity functions from this contract.
Call this after having already called prop_vars_between_blocks() on efg.
"""
import src.function as function
fe = function.FunctionExtractor(self)
fe.extract()
self.function_extractor = fe
class TACBasicBlock(evm_efg.EVMBasicBlock):
"""
A basic block containing both three-address code, and its
equivalent EVM code, along with information about the transformation
applied to the stack as a consequence of its execution.
"""
def __init__(self, entry_pc: int, exit_pc: int,
tac_ops: t.List['TACOp'],
evm_ops: t.List[evm_efg.EVMOp],
delta_stack: mem.VariableStack,
cfg=None, index=None):
"""
Args:
entry_pc: The pc of the first byte in the source EVM block
exit_pc: The pc of the last byte in the source EVM block
tac_ops: A sequence of TACOps whose execution is equivalent to the source
EVM code.
evm_ops: the source EVM code.
delta_stack: A stack describing the change in the stack state as a result
of running this block.
This stack contains the new items inhabiting the top of
stack after execution, along with the number of items
removed from the stack.
cfg: The TACGraph to which this block belongs.
Entry and exit variables should span the entire range of values enclosed
in this block, taking care to note that the exit address may not be an
instruction, but an argument of a PUSH.
The range of pc values spanned by all blocks in a CFG should be a
continuous range from 0 to the maximum value with no gaps between blocks.
If the input stack state is known, obtain the exit stack state by
popping off delta_stack.empty_pops items and add the delta_stack items
to the top.
"""
super().__init__(entry_pc, exit_pc, evm_ops)
self.tac_ops = tac_ops
"""A sequence of TACOps whose execution is equivalent to the source EVM
code"""
self.delta_stack = delta_stack
"""
A stack describing the stack state changes caused by running this block.
MetaVariables named Sn symbolically denote the variable that was n places
from the top of the stack at entry to this block.
"""
self.entry_stack = mem.VariableStack()
"""Holds the complete stack state before execution of the block."""
self.exit_stack = mem.VariableStack()
"""Holds the complete stack state after execution of the block."""
self.symbolic_overflow = False
"""
Indicates whether a symbolic stack overflow has occurred in dataflow
analysis of this block.
"""
self.cfg = cfg
"""The TACGraph to which this block belongs."""
self.index = index
def __str__(self):
super_str = super().__str__()
op_seq = "\n".join(str(op) for op in self.tac_ops)
entry_stack = "Entry stack: {}".format(str(self.entry_stack))
stack_pops = "Stack pops: {}".format(self.delta_stack.empty_pops)
stack_adds = "Stack additions: {}".format(str(self.delta_stack))
exit_stack = "Exit stack: {}".format(str(self.exit_stack))
return "\n".join([super_str, self._STR_SEP, op_seq, self._STR_SEP,
entry_stack, stack_pops, stack_adds, exit_stack])
def accept(self, visitor: patterns.Visitor) -> None:
"""
Accepts a visitor and visits itself and all TACOps in the block.
Args:
visitor: an instance of :obj:`patterns.Visitor` to accept.
"""
super().accept(visitor)
if visitor.can_visit(TACOp) or visitor.can_visit(TACAssignOp):
for tac_op in self.tac_ops:
visitor.visit(tac_op)
def __deepcopy__(self, memodict={}):
"""Return a copy of this block."""
new_block = TACBasicBlock(self.entry, self.exit,
copy.deepcopy(self.tac_ops, memodict),
[copy.copy(op) for op in self.evm_ops],
copy.deepcopy(self.delta_stack, memodict))
new_block.fallthrough = self.fallthrough
new_block.has_unresolved_jump = self.has_unresolved_jump
new_block.symbolic_overflow = self.symbolic_overflow
new_block.entry_stack = copy.deepcopy(self.entry_stack, memodict)
new_block.exit_stack = copy.deepcopy(self.exit_stack, memodict)
new_block.preds = copy.copy(self.preds)
new_block.succs = copy.copy(self.succs)
new_block.ident_suffix = self.ident_suffix
new_block.cfg = self.cfg
new_block.reset_block_refs()
return new_block
@property
def last_op(self) -> 'TACOp':
"""Return the last TAC operation in this block if it exists."""
if len(self.tac_ops):
return self.tac_ops[-1]
return None
@last_op.setter
def last_op(self, op):
"""
Set the last TAC operation in this block, if there is one.
Append if one doesn't exist.
"""
if len(self.tac_ops):
self.tac_ops[-1] = op
else:
self.tac_ops.append(op)
def reset_block_refs(self) -> None:
"""Update all operations and new def sites to refer to this block."""
for op in self.evm_ops:
op.block = self
for op in self.tac_ops:
op.block = self
if isinstance(op, TACAssignOp) and isinstance(op.lhs, mem.Variable):
for site in op.lhs.def_sites:
site.block = self
def apply_operations(self, stack_values: defaultdict, memory: bytearray, use_sets=False) -> None:
"""
Propagate and fold constants through the arithmetic TAC instructions in this block.
"""
for op in self.tac_ops:
if op.opcode == opcodes.CONST:
op.lhs.values = op.args[0].value.values
# Special cases: they both belong to three_store_two.
elif op.opcode == opcodes.CALLDATACOPY or op.opcode == opcodes.CODECOPY \
or op.opcode == opcodes.RETURNDATACOPY:
arg0 = remove_0x(str(op.args[0]))
destoffset = int(arg0, 16)
arg2 = remove_0x(str(op.args[2]))
length = int(arg2, 16)
value = op.value
memory[destoffset: destoffset + length] = value.to_bytes(length, byteorder='big')
elif op.opcode == opcodes.EXTCODECOPY:
arg1 = remove_0x(str(op.args[1]))
destoffset = int(arg1, 16)
arg3 = remove_0x(str(op.args[3]))
length = int(arg3, 16)
value = op.value
memory[destoffset: destoffset + length] = value.to_bytes(length, byteorder='big')
# Special cases: cases for kind one and two, but those opcodes are not in three_store
# Those opcodes have already had their value assigned to the lhs in the __handal_evm_op
elif op.opcode.is_kind_one() or op.opcode.is_kind_two():
continue
# Special cases: SLOAD and MLOAD get their value from the geth, and these values have been assigned
elif op.opcode == opcodes.MLOAD or op.opcode == opcodes.SLOAD:
continue
# Special cases: SSTORE and MSTORE. Store variable values to the related storage and memory
elif op.opcode == opcodes.SSTORE:
var_name = "S[{}]".format(op.args[0])
var_value = op.args[1].value.values
stack_values[var_name] = var_value
elif op.opcode == opcodes.MSTORE:
arg0 = remove_0x(str(op.args[0]))
offset = int(arg0, 16)
arg1 = remove_0x(str(op.args[1]))
value = int(arg1, 16)
memory[offset: offset + 32] = value.to_bytes(32, byteorder='big')
elif op.opcode == opcodes.MSTORE8:
arg0 = remove_0x(str(op.args[0]))
offset = int(arg0, 16)
arg1 = remove_0x(str(op.args[1]))
value = int(arg1, 16)
memory[offset: offset + 1] = value.to_bytes(8, byteorder='big')
elif op.opcode.is_arithmetic():
if op.constant_args() or (op.constrained_args() and use_sets):
rhs = [arg.value for arg in op.args]
op.lhs.values = mem.Variable.arith_op(op.opcode.name, rhs).values
elif not op.lhs.is_unconstrained:
op.lhs.widen_to_top()
class TACOp(patterns.Visitable):
"""
A Three-Address Code operation.
Each operation consists of an opcode object defining its function,
a list of argument variables, and the unique program counter address
of the EVM instruction it was derived from.
"""
def __init__(self, opcode: opcodes.OpCode, args: t.List['TACArg'],
pc: int, block=None, value: int = None,
loc: int = None, call_depth: int = None, call_number: int = None):
"""
Args:
opcode: the operation being performed.
args: Variables that are operated upon.
pc: the program counter at the corresponding instruction in the original bytecode.
block: the block this operation belongs to. Defaults to None.
value: The value generated by Geth. Only kind three that stores two opcodes needs this field.
loc: the position of the opcode
call_depth: the depth of the called smart contracts
call_number: the number of the called smart contracts so far
"""
self.opcode = opcode
self.args = args
self.pc = pc
self.block = block
self.value = value
self.loc = loc
self.call_depth = call_depth
self.call_number = call_number
def __str__(self):
if self.opcode in [opcodes.MSTORE, opcodes.MSTORE8, opcodes.SSTORE]:
if self.opcode == opcodes.MSTORE:
lhs = "M[{}]".format(self.args[0])
elif self.opcode == opcodes.MSTORE8:
lhs = "M8[{}]".format(self.args[0])
else:
lhs = "S[{}]".format(self.args[0])
return "{}: {} = {}".format(hex(self.pc), lhs,
" ".join([str(arg) for arg in self.args[1:]]))
return "{}: {} {}".format(hex(self.pc), self.opcode,
" ".join([str(arg) for arg in self.args]))
def __repr__(self):
return "<{0} object {1}, {2}>".format(
self.__class__.__name__,
hex(id(self)),
self.__str__()
)
def constant_args(self) -> bool:
"""True iff each of this operations arguments is a constant value."""
return all([arg.value.is_const for arg in self.args])
def constrained_args(self) -> bool:
"""True iff none of this operations arguments is value-unconstrained."""
return all([not arg.value.is_unconstrained for arg in self.args])
@staticmethod
def has_lhs() -> bool:
return False
@classmethod
def convert_jump_to_throw(cls, op: 'TACOp') -> 'TACOp':
"""
Given a jump, convert it to a throw, preserving the condition var if JUMPI.
Otherwise, return the given operation unchanged.
"""
if op.opcode not in [opcodes.JUMP, opcodes.JUMPI]:
return op
elif op.opcode == opcodes.JUMP:
return cls(opcodes.THROW, [], op.pc, op.block)
elif op.opcode == opcodes.JUMPI:
return cls(opcodes.THROWI, [op.args[1]], op.pc, op.block)
def __deepcopy__(self, memodict={}):
new_op = type(self)(self.opcode,
copy.deepcopy(self.args, memodict),
self.pc,
self.block)
return new_op
class TACAssignOp(TACOp):
"""
A TAC operation that additionally takes a variable to which
this operation's result is implicitly bound.
"""
def __init__(self, lhs: mem.Variable, opcode: opcodes.OpCode,
args: t.List['TACArg'], pc: int, block=None,
print_name: bool = True, value_extra: int = None,
loc: int = None, call_depth: int = None, call_number: int = None):
"""
Args:
lhs: The Variable that will receive the result of this operation.
print_name: Some operations (e.g. CONST) don't need to print their
name in order to be readable.
value_extra: store the value_extra for CALL CALLCODE STATICCALL DELEGATECALL
loc: the position of the opcode
call_depth: the depth of the called smart contracts
call_number: the number of the called smart contracts so far
"""
super().__init__(opcode, args, pc, block)
self.lhs = lhs
self.print_name = print_name
self.value_extra = value_extra
self.loc = loc
self.call_depth = call_depth
self.call_number = call_number
# Special case TAC expression
# For example V4 = CALLVALUE to V4 = value content
def __str__(self):
if self.opcode in [opcodes.SLOAD, opcodes.MLOAD]:
if self.opcode == opcodes.SLOAD:
rhs = "S[{}]".format(self.args[0])
else:
rhs = "M[{}]".format(self.args[0])
return "{}: {} = {}".format(hex(self.pc), self.lhs.identifier, rhs)
elif self.opcode.is_kind_one() or self.opcode.is_kind_two():
return "{}: {} = {}".format(hex(self.pc), self.lhs.identifier, self.lhs.values)
arglist = ([str(self.opcode)] if self.print_name else []) \
+ [str(arg) for arg in self.args]
return "{}: {} = {}".format(hex(self.pc), self.lhs.identifier, " ".join(arglist))
def __deepcopy__(self, memodict={}):
"""
Return a copy of this TACAssignOp, deep copying the args and vars,
but leaving block references unchanged.
"""
new_op = type(self)(copy.deepcopy(self.lhs, memodict),
self.opcode,
copy.deepcopy(self.args, memodict),
self.pc,
self.block,
self.print_name)
return new_op
@staticmethod
def has_lhs() -> bool:
return True
class TACArg:
"""
Contains information held in an argument to a TACOp.
In particular, a TACArg may hold both the current value of an argument,
if it exists; along with the entry stack position it came from, if it did.
This allows updated/refined stack data to be propagated into the body
of a TACBasicBlock.
"""
def __init__(self, var: mem.Variable = None, stack_var: mem.MetaVariable = None):
self.var = var
"""The actual variable this arg contains."""
self.stack_var = stack_var
"""The stack position this variable came from."""
def __str__(self):
return str(self.value)
@property
def value(self):
"""
Return this arg's value if it has one, otherwise return its stack variable.
"""
if self.var is None:
if self.stack_var is None:
raise ValueError("TAC Argument has no value.")
else:
return self.stack_var
else:
return self.var
@classmethod
def from_var(cls, var: mem.Variable):
if isinstance(var, mem.MetaVariable):
return cls(stack_var=var)
return cls(var=var)
class TACLocRef:
"""Contains a reference to a program counter within a particular block."""
def __init__(self, block, pc):
self.block = block
"""The block that contains the referenced instruction."""
self.pc = pc
"""The program counter of the referenced instruction."""
def __deepcopy__(self, memodict={}):
return type(self)(self.block, self.pc)
def __str__(self):
return "{}.{}".format(self.block.ident(), hex(self.pc))
def __eq__(self, other):
return self.block == other.block and self.pc == other.pc
def __hash__(self):
return hash(self.block) ^ hash(self.pc)
def get_instruction(self):
"""Return the TACOp referred to by this TACLocRef, if it exists."""
for i in self.block.tac_ops:
if i.pc == self.pc:
return i
return None
class Destackifier:
"""Converts EVMBasicBlocks into corresponding TACBasicBlocks.
Most instructions get mapped over directly, except:
POP: generates no TAC op, but pops the symbolic stack;
PUSH: generates a CONST TAC assignment operation;
DUP, SWAP: these simply permute the symbolic stack, generate no ops;
LOG0 ... LOG4: all translated to a generic LOG instruction
Additionally, there is a NOP TAC instruction that does nothing, to represent
a block containing EVM instructions with no corresponding TAC code.
"""
def __init__(self):
# A sequence of three-address operations
self.ops = []
# The symbolic variable stack we'll be operating on.
self.stack = mem.VariableStack()
# Entry address of the current block being converted
self.block_entry = None
# The number of TAC variables we've assigned,
# in order to produce unique identifiers. Typically the same as
# the number of items pushed to the stack.
# We increment it so that variable names will be globally unique.
self.stack_vars = 0
def __fresh_init(self, evm_block: evm_efg.EVMBasicBlock) -> None:
"""Reinitialise all structures in preparation for converting a block."""
self.ops = []
self.stack = mem.VariableStack()
self.block_entry = evm_block.evm_ops[0].pc \
if len(evm_block.evm_ops) > 0 else None
def __new_var(self) -> mem.Variable:
"""Construct and return a new variable with the next free identifier."""
# Generate the new variable, numbering it by the implicit stack location
# it came from.
var = mem.Variable.top(name="V{}".format(self.stack_vars),
def_sites=ssle([TACLocRef(None, self.block_entry)]))
self.stack_vars += 1
return var
# Add the last block's stack into the following one
def convert_block(self, evm_block: evm_efg.EVMBasicBlock, stacks: [mem.VariableStack]) -> (TACBasicBlock):
"""
Given a EVMBasicBlock, produce an equivalent three-address code sequence and return the resulting TACBasicBlock.
"""
# Step1: How to use the stack
if len(evm_block.evm_ops) > 0:
first_opcode = evm_block.evm_ops[0]
if first_opcode.pc == 0:
pre_stack = mem.VariableStack(call_depth=first_opcode.call_depth)
elif first_opcode.opcode.is_kind_four() or first_opcode.opcode.is_kind_five():
pre_stack = stacks.pop()
if first_opcode.call_depth != pre_stack.call_depth:
pre_stack = stacks.pop()
self.__fresh_init(evm_block)
self.stack = pre_stack
for op in evm_block.evm_ops:
self.__handle_evm_op(op)
entry = evm_block.evm_ops[0].pc if len(evm_block.evm_ops) > 0 else None
exit = evm_block.evm_ops[-1].pc + evm_block.evm_ops[-1].opcode.push_len() \
if len(evm_block.evm_ops) > 0 else None
# If the block is empty, append a NOP before continuing.
if len(self.ops) == 0:
self.ops.append(TACOp(opcodes.NOP, [], entry))
new_block = TACBasicBlock(entry, exit, self.ops, evm_block.evm_ops,
self.stack)
# Link up new ops and def sites to the block that contains them.
new_block.reset_block_refs()
# Step2: How to add the stack
if len(evm_block.evm_ops) > 0:
first_opcode = evm_block.evm_ops[0]
last_opcode = evm_block.evm_ops[len(evm_block.evm_ops) - 1]
if first_opcode.pc == 0 and not last_opcode.opcode.possibly_halts():
stacks.append(self.stack)
if (first_opcode.opcode.is_kind_four() or first_opcode.opcode.is_kind_five())\
and not last_opcode.opcode.possibly_halts():
stacks.append(self.stack)
return new_block
def __handle_evm_op(self, op: evm_efg.EVMOp) -> None:
"""
Produce from an EVM line its corresponding TAC instruction, if there is one,
appending it to the current TAC sequence.
"""
if op.opcode.is_swap():
self.stack.swap(op.opcode.pop)
elif op.opcode.is_dup():
self.stack.dup(op.opcode.pop)
elif op.opcode == opcodes.POP:
self.stack.pop()
else:
# When generating TAC operation from evm opcode, making use of value and value_extra generated from geth
self.__gen_instruction(op)
# Use values from geth
def __gen_instruction(self, op: evm_efg.EVMOp) -> None:
"""
Given a line, generate its corresponding TAC operation,
append it to the op sequence, and push any generated
variables to the stack.
"""
inst = None
new_var = self.__new_var() if op.opcode.push == 1 else None
# Set this variable's def site
if new_var is not None:
for site in new_var.def_sites:
site.pc = op.pc
# Generate the appropriate TAC operation.
# Special cases first, followed by the fallback to generic instructions.
# Although the opcode is PUSH, vandal still marks it as CONST to do arithemetic operations.
if op.opcode.is_push():
args = [TACArg(var=mem.Variable(values=[op.value], name="C"))]
inst = TACAssignOp(new_var, opcodes.CONST, args, op.pc, print_name=False)
elif op.opcode.is_missing():
args = [TACArg(var=mem.Variable(values=[op.value], name="C"))]
inst = TACOp(op.opcode, args, op.pc)
elif op.opcode.is_log():
args = [TACArg.from_var(var) for var in self.stack.pop_many(op.opcode.pop)]
inst = TACOp(opcodes.LOG, args, op.pc)
elif op.opcode == opcodes.MSTORE:
args = [TACArg.from_var(var) for var in self.stack.pop_many(opcodes.MSTORE.pop)]
inst = TACOp(op.opcode, args, op.pc)
elif op.opcode == opcodes.MSTORE8:
args = [TACArg.from_var(var) for var in self.stack.pop_many(opcodes.MSTORE8.pop)]
inst = TACOp(op.opcode, args, op.pc)
# SLOAD is same as MLOAD, they both hasve value in the tempt file
# We will assign the real value to the storage variable
elif op.opcode == opcodes.SLOAD or op.opcode == opcodes.MLOAD:
new_var = mem.Variable(values=[op.value], name=new_var.name)
args = [TACArg.from_var(self.stack.pop())]
inst = TACAssignOp(new_var, op.opcode, args, op.pc)
elif op.opcode == opcodes.SSTORE:
args = [TACArg.from_var(var) for var in self.stack.pop_many(opcodes.SSTORE.pop)]
inst = TACOp(op.opcode, args, op.pc)
# Special cases for kind one, such as CALLVALUE
# For kind one, there are no arguments for the previous vandal, so the inst will be incomplete
# For example, 0xa CALLVALUE 0x0 will be transalated into V4 =
# Now we assign the real value to this opcode and keep its opcode
elif op.opcode.is_kind_one():
new_var = mem.Variable(values=[op.value], name=new_var.name)
args = []
inst = TACAssignOp(new_var, op.opcode, args, op.pc, print_name=False)
# Special cases for kind two, such as CALLDATALOAD
# Args have all the stack arguments, those information (stack arguments) are useless
# Since we just get the values from geth, not using them.
elif op.opcode.is_kind_two():
new_var = mem.Variable(values=[op.value], name=new_var.name)
args = [TACArg.from_var(var) for var in self.stack.pop_many(op.opcode.pop)]
inst = TACAssignOp(new_var, op.opcode, args, op.pc, print_name=False)
# Special cases for kind three store two, such as CALLDATACOPY
# There are multiple arguments in this kind of opcodes
elif op.opcode.is_kind_three_store_two():
args = [TACArg.from_var(var) for var in self.stack.pop_many(op.opcode.pop)]
inst = TACOp(op.opcode, args, op.pc, None, op.value)
# Special cases for kind four, such as call
# Field value_extra is the memory content
elif op.opcode.is_kind_four():
# op.value is success flag, value_extra is the memory content.
new_var = mem.Variable(values=[op.value], name=new_var.name)
args = [TACArg.from_var(var) for var in self.stack.pop_many(op.opcode.pop)]
inst = TACAssignOp(new_var, op.opcode, args, op.pc, None, True, op.value_extra)
elif op.opcode.is_kind_five():
new_var = mem.Variable(values=[op.value], name=new_var.name)
args = [TACArg.from_var(var) for var in self.stack.pop_many(op.opcode.pop)]
inst = TACAssignOp(new_var, op.opcode, args, op.pc, None, True, None)
elif new_var is not None:
args = [TACArg.from_var(var) for var in self.stack.pop_many(op.opcode.pop)]
inst = TACAssignOp(new_var, op.opcode, args, op.pc)
else:
args = [TACArg.from_var(var) for var in self.stack.pop_many(op.opcode.pop)]
inst = TACOp(op.opcode, args, op.pc)
# This var must only be pushed after the operation is performed.
if new_var is not None:
self.stack.push(new_var)
inst.loc = op.loc
inst.call_depth = op.call_depth
inst.call_number = op.call_number
self.ops.append(inst)
+1015
View File
File diff suppressed because it is too large. Load diff