├── .gitignore ├── .gitmodules ├── EVMFuzz.py ├── LICENSE ├── NeoSemanticDiffFuzzer.py ├── NeoVMFuzz.py ├── README.md ├── __init__.py ├── neodiff ├── EVMDiffGenerator.py ├── NeoDiff.py ├── NeoVmDiffGenerator.py ├── PyDiffGenerator.py └── __init__.py ├── requirements.txt ├── roots21-2.pdf ├── roots21-2.png ├── setup.sh └── utils ├── EVMrun.sh ├── EVMscale.sh ├── EV_Mkill.sh ├── analyze_data.py ├── neo-python-VM.afl.py ├── neo-python-VM.py └── parse_results.py /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | out 3 | .vscode 4 | .idea 5 | RESULTS 6 | __pycache__ 7 | .env 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "go-ethereum"] 2 | path = go-ethereum 3 | url = https://github.com/ethereum/go-ethereum.git 4 | [submodule "openethereum"] 5 | path = openethereum 6 | url = https://github.com/openethereum/openethereum.git 7 | [submodule "neo-python"] 8 | path = neo-python 9 | url = https://github.com/CityOfZion/neo-python 10 | -------------------------------------------------------------------------------- /EVMFuzz.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Fuzzer for EVM 4 | 5 | import subprocess 6 | import random 7 | import struct 8 | from logzero import logger 9 | from itertools import chain 10 | import json 11 | import os 12 | import argparse 13 | import secrets 14 | import time 15 | import hashlib 16 | import binascii 17 | 18 | from neodiff.NeoDiff import FuzzerConfig, VMRunnerProcess 19 | from neodiff import EVMDiffGenerator 20 | 21 | 22 | class ENeoDiffConfig(FuzzerConfig): 23 | def pre_round(self): 24 | codes = EVMDiffGenerator.run( 25 | self.seed, 26 | self.roundsize, 27 | self.probability, 28 | self.new_typehash_file, 29 | self.typehash_file, 30 | ) 31 | for code in codes: 32 | code = binascii.hexlify(code).decode("ascii") 33 | self.vm1_queue.append(code) 34 | self.vm2_queue.append(code) 35 | 36 | def post_round(self): 37 | pass 38 | 39 | # {"opcode": 24, "ret": null, "data": "33", "vmstate": 0, "crash": false, "checksum": "cc18e0d61c85b87441efddc503708b9e8253d53d"} 40 | def clean_vm1_out(self, out): 41 | new_out = [] 42 | m = hashlib.sha1() 43 | for i in range(0, len(out) - 1): 44 | if "op" not in out[i]: 45 | continue 46 | if out[i]["op"] == 253: # REVERT 47 | continue 48 | m.update(str(out[i]["op"]).encode("ascii")) 49 | m.update(str(out[i]["gasCost"]).encode("ascii")) 50 | typehash = "" 51 | 52 | crashed = False 53 | if ("error" in out[i + 1] and len(out[i + 1]["error"]) > 0) and ( 54 | "op" not in out[i + 1] or "output" in out[i + 1] 55 | ): 56 | # logger.info(['error']) 57 | # logger.info(out[i+1]) 58 | crashed = True 59 | elif "error" in out[i] and len(out[i]["error"]) > 0: 60 | crashed = True 61 | elif ( 62 | "op" in out[i] 63 | and "stack" in out[i + 1] 64 | and "op" in out[i + 1] 65 | and out[i + 1]["op"] != 0 66 | ): 67 | # logger.info(out[i+1]) 68 | for item in out[i + 1]["stack"][::-1]: 69 | m.update(item.encode("ascii")) 70 | if len(typehash) < 2: 71 | if int(item, 16) == 1: 72 | typehash += "1" 73 | elif len(item) == 42 or len(item) == 40: 74 | typehash += "2" 75 | elif len(item) > 42: 76 | typehash += "3" 77 | elif len(item) <= 0xFFFFFFFFFFFFFFFF: 78 | typehash += "4" 79 | elif len(item) < 40: 80 | typehash += "5" 81 | m.update(out[i + 1]["memory"].rstrip("0").encode("ascii")) 82 | if len(out[i + 1]["memory"]) > 2: 83 | typehash += "6" 84 | else: 85 | pass 86 | # logger.info(out[i]) 87 | # logger.info(out[i+1]) 88 | 89 | new_out.append( 90 | { 91 | "opcode": out[i]["op"], 92 | "ret": None, 93 | "data": typehash, 94 | "crash": crashed, 95 | "checksum": m.hexdigest(), 96 | } 97 | ) 98 | # logger.info(new_out) 99 | return new_out 100 | 101 | def clean_vm2_out(self, out): 102 | return self.clean_vm1_out(out) 103 | 104 | def is_new_coverage_better(self, new_coverage, old_coverage): 105 | if len(new_coverage["vm1_code"]) < len(old_coverage["vm1_code"]): 106 | return True 107 | return False 108 | 109 | def minimize_current_coverage(self): 110 | typehashes = list(self.current_coverage.keys()) 111 | minimizing = True 112 | code_cut = 2 113 | while len(typehashes) and minimizing: 114 | 115 | code = self.current_coverage[typehashes[0]]["vm1_code"] 116 | 117 | _code = code[:code_cut] 118 | if len(_code) == 0 or code_cut > len(code): 119 | break 120 | 121 | vm1_out, vm2_out = fuzzer.run_both_with_code(_code, _code) 122 | 123 | remaining_typehashes = [] 124 | for typehash in typehashes: 125 | vm1 = self.current_coverage[typehash]["vm1"] 126 | vm2 = self.current_coverage[typehash]["vm2"] 127 | if vm1 in vm1_out and vm2 in vm2_out: 128 | self.current_coverage[typehash]["vm1_code"] = _code 129 | self.current_coverage[typehash]["vm2_code"] = _code 130 | else: 131 | remaining_typehashes.append(typehash) 132 | 133 | typehashes = remaining_typehashes 134 | code_cut += 2 135 | 136 | 137 | if __name__ == "__main__": 138 | parser = argparse.ArgumentParser() 139 | parser.add_argument("--seed", "-s", default=None, type=int, help="The initial seed") 140 | parser.add_argument("--name", "-n", default=None, type=str, help="name") 141 | parser.add_argument("--roundsize", "-r", default=1000, type=int, help="round size") 142 | parser.add_argument("--depth", "-d", default=50, type=int, help="execution depth") 143 | parser.add_argument("--probability", "-p", default=1, type=int, help="propability") 144 | 145 | args = parser.parse_args() 146 | 147 | fuzzer = ENeoDiffConfig(name=args.name) 148 | fuzzer.probability = args.probability 149 | fuzzer.roundsize = args.roundsize 150 | if args.seed: 151 | fuzzer.seed = args.seed 152 | else: 153 | fuzzer.seed = random.randint(0x0000000000000000, 0xFFFFFFFFFFFFFFFF) 154 | fuzzer.seed = args.seed 155 | 156 | fuzzer.vm1 = VMRunnerProcess( 157 | [ 158 | "./go-ethereum/build/bin/evm", 159 | "--json", 160 | "--sender", 161 | "0x00", 162 | "--receiver", 163 | "0x00", 164 | "--gas", 165 | "0x1337", 166 | ] 167 | ) 168 | fuzzer.vm1.prepare_cli = lambda code: ["--code", code, "run"] 169 | 170 | fuzzer.vm2 = VMRunnerProcess( 171 | [ 172 | "./openethereum/target/release/openethereum-evm", 173 | "--chain", 174 | "./openethereum/crates/ethcore/res/chainspec/test/istanbul_test.json", 175 | "--gas", 176 | "1337", 177 | "--json", 178 | ] 179 | ) 180 | fuzzer.vm2.prepare_cli = lambda code: ["--code", code] 181 | 182 | fuzzer.clean_exit_opcode = 0xF3 183 | 184 | fuzzer.fuzz() 185 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Fabian Fässler, Dominik Maier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /NeoSemanticDiffFuzzer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Semantic diff fuzzer for CPython against the neo boa python VM 5 | """ 6 | 7 | from neodiff import PyDiffGenerator 8 | from neodiff import NeoDiff 9 | from boa.compiler import Compiler 10 | import random 11 | import secrets 12 | import struct 13 | import shutil 14 | from logzero import logger 15 | 16 | # random.seed(1) 17 | 18 | while True: 19 | seed = secrets.randbelow(1 << 32) 20 | # logger.info() 21 | code = PyDiffGenerator.run( 22 | seed=seed, 23 | illegal=False, 24 | target="py", 25 | mayblock=False, 26 | verbose=False, 27 | evalcode=False, 28 | ) 29 | 30 | # print(code) 31 | 32 | code2 = """ 33 | def Main(): 34 | a = 1 35 | b = 2 36 | c = a+b 37 | return [1234, c] 38 | """ 39 | 40 | exec(code) 41 | python_ret = Main() 42 | if not python_ret: 43 | # logger.error('code is not valid python') 44 | continue 45 | else: 46 | # logger.info('python got: {}'.format(python_ret)) 47 | pass 48 | with open("/tmp/contract.py", "wb") as f: 49 | f.write(code.encode("utf-8")) 50 | try: 51 | code = Compiler.load_and_save("/tmp/contract.py") 52 | except: 53 | # logger.error("python code couldn't compile") 54 | continue 55 | # print(code) 56 | hashes, neo_ret = NeoDiff.run_vm(code, 2, NeoDiff.python) 57 | 58 | # print(hashes) 59 | if len(python_ret) == 2 and len(neo_ret) == 2: 60 | if python_ret[0] != neo_ret[0]: 61 | logger.info( 62 | "{} vs. {} | python PyDiffGenerator.py -s {}".format( 63 | python_ret, neo_ret, seed 64 | ) 65 | ) 66 | with open("/tmp/contract.py", "a+") as f: 67 | f.write("\n# python: {}\n# Neo: {}\n".format(python_ret, neo_ret)) 68 | shutil.copyfile("/tmp/contract.py", "SemanticDiffs/{}.py".format(seed)) 69 | # logger.info('DIFF') 70 | else: 71 | if python_ret[1] != neo_ret[1]: 72 | if ( 73 | type(python_ret[1]) == str 74 | and type(neo_ret[1]) == bytes 75 | and python_ret[1].encode("utf-8") == neo_ret[1] 76 | ): 77 | continue 78 | if ( 79 | type(python_ret[1]) == bytes 80 | and type(neo_ret[1]) == str 81 | and python_ret[1] == neo_ret[1].encode("utf-8") 82 | ): 83 | continue 84 | if ( 85 | type(python_ret[1]) == bool 86 | and python_ret[1] == True 87 | and neo_ret[1] == b"\x01" 88 | ): 89 | continue 90 | if ( 91 | type(python_ret[1]) == bool 92 | and python_ret[1] == False 93 | and neo_ret[1] == b"" 94 | ): 95 | continue 96 | if python_ret[1] == None and neo_ret[1] == b"": 97 | continue 98 | if ( 99 | type(python_ret[1]) == bool 100 | and python_ret[1] == False 101 | and neo_ret[1] == b"\x00" 102 | ): 103 | continue 104 | if ( 105 | type(python_ret[1]) == dict 106 | and python_ret[1] == {} 107 | and neo_ret[1] == b"" 108 | ): 109 | continue 110 | if ( 111 | type(python_ret[1]) == int 112 | and python_ret[1] == 0 113 | and neo_ret[1] == b"" 114 | ): 115 | continue 116 | if type(python_ret[1]) == float and int.from_bytes( 117 | neo_ret[1], "little", signed=True 118 | ) == int(python_ret[1]): 119 | continue 120 | if ( 121 | type(python_ret[1]) == list 122 | and python_ret[1] == [] 123 | and neo_ret[1] == b"" 124 | ): 125 | continue 126 | if type(python_ret[1]) == list and len(python_ret[1]) > 0: 127 | # can't detect these diffs 128 | continue 129 | if ( 130 | type(python_ret[1]) == int 131 | and int.from_bytes(neo_ret[1], "little", signed=True) 132 | == python_ret[1] 133 | ): 134 | continue 135 | logger.info( 136 | "{} vs. {} | python PyDiffGenerator.py -s {}".format( 137 | python_ret, neo_ret, seed 138 | ) 139 | ) 140 | with open("/tmp/contract.py", "a+") as f: 141 | f.write("\n# python: {}\n# Neo: {}\n".format(python_ret, neo_ret)) 142 | shutil.copyfile("/tmp/contract.py", "SemanticDiffs/{}.py".format(seed)) 143 | # input() 144 | continue 145 | else: 146 | # logger.error('neo cannot execute code') 147 | continue 148 | # python.stdin.write(b'\x00\x00\x00') 149 | # input() 150 | -------------------------------------------------------------------------------- /NeoVMFuzz.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Semantic diff fuzzer for the C# Neo VM against the NEO VM writtenn in python. 5 | """ 6 | 7 | import subprocess 8 | import random 9 | import struct 10 | from logzero import logger 11 | from itertools import chain 12 | import json 13 | import os 14 | import argparse 15 | import secrets 16 | import time 17 | import binascii 18 | 19 | from neodiff import NeoVmDiffGenerator 20 | from neodiff.NeoDiff import FuzzerConfig, VMRunnerIO 21 | 22 | class NeoDiffFuzzerConfig(FuzzerConfig): 23 | def pre_round(self): 24 | codes = NeoVmDiffGenerator.run( 25 | self.seed, 26 | self.roundsize, 27 | self.probability, 28 | self.new_typehash_file, 29 | self.typehash_file, 30 | ) 31 | for code in codes: 32 | code = binascii.hexlify(code).decode("ascii") 33 | self.vm1_queue.append(code) 34 | self.vm2_queue.append(code) 35 | 36 | def post_round(self): 37 | pass 38 | 39 | def clean_vm2_out(self, out): 40 | for i in range(0, len(out)): 41 | if len(out) > i + 1: 42 | look_ahead = out[i + 1] 43 | if look_ahead["opcode"] == None: 44 | out[i]["crash"] = True 45 | return out 46 | 47 | def clean_vm1_out(self, out): 48 | for i in range(0, len(out)): 49 | if len(out) > i + 1: 50 | look_ahead = out[i + 1] 51 | if look_ahead["opcode"] == None: 52 | out[i]["crash"] = True 53 | return out 54 | 55 | def is_new_coverage_better(self, new_coverage, old_coverage): 56 | if len(new_coverage["vm1_code"]) < len(old_coverage["vm1_code"]): 57 | return True 58 | return False 59 | 60 | def minimize_current_coverage(self): 61 | typehashes = list(self.current_coverage.keys()) 62 | minimizing = True 63 | code_cut = 2 64 | while len(typehashes) and minimizing: 65 | 66 | code = self.current_coverage[typehashes[0]]["vm1_code"] 67 | 68 | _code = code[:code_cut] 69 | if len(_code) == 0 or code_cut > len(code): 70 | break 71 | 72 | vm1_out, vm2_out = fuzzer.run_both_with_code(_code, _code) 73 | 74 | remaining_typehashes = [] 75 | for typehash in typehashes: 76 | vm1 = self.current_coverage[typehash]["vm1"] 77 | vm2 = self.current_coverage[typehash]["vm2"] 78 | if vm1 in vm1_out and vm2 in vm2_out: 79 | self.current_coverage[typehash]["vm1_code"] = _code 80 | self.current_coverage[typehash]["vm2_code"] = _code 81 | else: 82 | remaining_typehashes.append(typehash) 83 | 84 | typehashes = remaining_typehashes 85 | code_cut += 2 86 | 87 | 88 | """ 89 | python NeoVMFuzz.py --name 2k20o1p --roundsize 2000 --depth 20 --probability 1 90 | python NeoVMFuzz.py --name 2k20o20p --roundsize 2000 --depth 20 --probability 20 91 | python NeoVMFuzz.py --name 2k500o1p --roundsize 2000 --depth 500 --probability 1 92 | python NeoVMFuzz.py --name 2k500o20p --roundsize 2000 --depth 500 --probability 20 93 | """ 94 | 95 | if __name__ == "__main__": 96 | parser = argparse.ArgumentParser() 97 | parser.add_argument("--seed", "-s", default=None, type=int, help="The initial seed") 98 | parser.add_argument("--name", "-n", default=None, type=str, help="name") 99 | parser.add_argument("--roundsize", "-r", default=1000, type=int, help="round size") 100 | parser.add_argument("--depth", "-d", default=50, type=int, help="execution depth") 101 | parser.add_argument("--probability", "-p", default=1, type=int, help="propability") 102 | 103 | args = parser.parse_args() 104 | 105 | fuzzer = NeoDiffFuzzerConfig(name=args.name) 106 | fuzzer.probability = args.probability 107 | fuzzer.roundsize = args.roundsize 108 | if args.seed: 109 | fuzzer.seed = args.seed 110 | else: 111 | fuzzer.seed = random.randint(0x0000000000000000, 0xFFFFFFFFFFFFFFFF) 112 | fuzzer.seed = args.seed 113 | fuzzer.vm1 = VMRunnerIO(["python", "neo-python-VM.py", str(args.depth)]) 114 | fuzzer.vm2 = VMRunnerIO( 115 | ["mono", "./neo-vm/src/neo-vm/bin/Debug/net461/Neo.VM.exe", str(args.depth)] 116 | ) 117 | fuzzer.clean_exit_opcode = 0x66 118 | 119 | fuzzer.fuzz() 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NeoDiff 2 | 3 | NeoDiff paper 4 | 5 | This repository contains the source code for NeoDiff, a framework for differential fuzzing of Smart-Contract VMs, introduced by "Uncovering Smart Contract VM Bugs Via Differential Fuzzing". 6 | It will mutate smart contract byte code and look for differences in the VM state, leading to potential chain splits. 7 | 8 | ## Getting started 9 | 10 | To get started, run `setup.sh`. 11 | The script will check out Ethereum VMs in `go` (`go-ethereum`) and `rust` (`openethereum`), and install the needed python virtual env for NeoDiff. 12 | then, source the virtualenv using `. .env/bin/activate` and run `./utils/EVMrun.sh 1` or `./utils/EVMscale.sh [proc_count]` to start fuzzing. 13 | 14 | ## Going from here 15 | Apart from EVM, NeoDiff has been tested on Neo VMs and should be trivial to port to other VMs. 16 | Check out `NeoVMFuzz.py` and `NeoSemanticDiffFuzzer.py` for other examples. 17 | 18 | After a run, the results will be generated in `../RESULTS`. 19 | `./utils` contains some helpful scripts for evaulation. 20 | 21 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsect/NeoDiff/a2f30b7ec0a157cc41c0143c18cd68e3e02b6674/__init__.py -------------------------------------------------------------------------------- /neodiff/EVMDiffGenerator.py: -------------------------------------------------------------------------------- 1 | # Ethereum specific bytecode generator 2 | 3 | import random 4 | import struct 5 | import os 6 | import binascii 7 | from logzero import logger 8 | import secrets 9 | import json 10 | from neodiff.PyDiffGenerator import SCInteger 11 | 12 | # code = os.urandom(0xfff).hex() 13 | 14 | 15 | def random_bytes(n): 16 | return bytes(random.getrandbits(8) for _ in range(n)) 17 | 18 | 19 | def run(seed, amount=1, probability=1, new_typehash_file=None, typehash_file=None): 20 | if seed == None: 21 | seed = secrets.randbelow(1 << 32) 22 | random.seed(seed) 23 | codes = [] 24 | 25 | all_coverage = {} 26 | new_coverage = {} 27 | 28 | if typehash_file and os.path.isfile(typehash_file): 29 | with open(typehash_file) as f: 30 | all_coverage = json.loads(f.read()) 31 | 32 | if new_typehash_file and os.path.isfile(new_typehash_file): 33 | with open(new_typehash_file) as f: 34 | new_coverage = json.loads(f.read()) 35 | 36 | for _ in range(0, amount): 37 | scinteger = SCInteger(None) 38 | last_feedback = 0 39 | feedback = [] 40 | code = b"" 41 | while len(code) < 0x7F: 42 | # select what type of code generator we use 43 | c = random.randint(0, 4 + (probability) + (probability * 4)) 44 | 45 | if c == 0: 46 | # random byte number for pushing bytes 47 | pushbytesOpcode = 0x60 + random.randint(0x00, 32) 48 | pushbytesData = random_bytes(pushbytesOpcode - (0x60 - 1)) 49 | # logger.info(bytes([pushbytesOpcode])) 50 | code += bytes([pushbytesOpcode]) + pushbytesData 51 | elif c == 1: 52 | # PUSHBYTES4 random 4 or 8 byte integer push 53 | r = int(scinteger.give_val()) 54 | try: 55 | code += b"\x63" + struct.pack("i", r) 56 | except struct.error: 57 | pass 58 | try: 59 | code += b"\x67" + struct.pack("q", r) 60 | except struct.error: 61 | pass 62 | elif c == 2: 63 | # create completly random byte sequence 64 | code += random_bytes(random.randint(0x1, 0xF)) 65 | elif c >= 3 and c <= 3 + ( 66 | probability 67 | ): # pick random code from all coverage 68 | d = random.randint(0, 2) 69 | if d == 0: 70 | if len(all_coverage.keys()) > 0: 71 | rand_state = random.choice(list(all_coverage.keys())) 72 | rand_code = binascii.unhexlify( 73 | random.choice(all_coverage[rand_state])["vm1_code"] 74 | ) 75 | code += rand_code 76 | elif d == 1: 77 | if len(all_coverage.keys()) > 0: 78 | rand_state = random.choice(list(all_coverage.keys())) 79 | rand_code = binascii.unhexlify( 80 | all_coverage[rand_state][-1]["vm1_code"] 81 | ) 82 | code += rand_code 83 | elif d == 2: 84 | if len(all_coverage.keys()) > 0: 85 | rand_state = random.choice(list(all_coverage.keys())) 86 | rand_code = binascii.unhexlify( 87 | all_coverage[rand_state][-1]["vm1_code"] 88 | ) 89 | pos = random.randint(0, len(rand_code) - 1) 90 | code += rand_code[:pos] + random_bytes(1) + rand_code[pos + 1 :] 91 | elif c >= 4 + (probability) and c <= 4 + (probability) + ( 92 | probability * 4 93 | ): # pick random from new coverage 94 | d = random.randint(0, 2) 95 | if d == 0: 96 | if len(new_coverage.keys()) > 0: 97 | rand_state = random.choice(list(new_coverage.keys())) 98 | rand_code = binascii.unhexlify( 99 | random.choice(new_coverage[rand_state])["vm1_code"] 100 | ) 101 | code += rand_code 102 | elif d == 1: 103 | if len(new_coverage.keys()) > 0: 104 | rand_state = random.choice(list(new_coverage.keys())) 105 | rand_code = binascii.unhexlify( 106 | new_coverage[rand_state][-1]["vm1_code"] 107 | ) 108 | code += rand_code 109 | elif d == 2: 110 | if len(new_coverage.keys()) > 0: 111 | rand_state = random.choice(list(new_coverage.keys())) 112 | rand_code = binascii.unhexlify( 113 | new_coverage[rand_state][-1]["vm1_code"] 114 | ) 115 | pos = random.randint(0, len(rand_code) - 1) 116 | code += rand_code[:pos] + random_bytes(1) + rand_code[pos + 1 :] 117 | codes.append(code + b"\x66") # add a RET to signal successful execution 118 | return codes 119 | 120 | 121 | if __name__ == "__main__": 122 | import argparse 123 | 124 | parser = argparse.ArgumentParser() 125 | parser.add_argument("--seed", "-s", default=None, type=int, help="The initial seed") 126 | 127 | args = parser.parse_args() 128 | 129 | print(run(seed=args.seed)) 130 | -------------------------------------------------------------------------------- /neodiff/NeoDiff.py: -------------------------------------------------------------------------------- 1 | # Main classes for NeoDiff 2 | 3 | import subprocess 4 | import random 5 | import struct 6 | from logzero import logger 7 | from itertools import chain 8 | import json 9 | import os 10 | import argparse 11 | import secrets 12 | import time 13 | from filelock import Timeout, FileLock 14 | import binascii 15 | 16 | typehash_MAP = {} 17 | 18 | 19 | class open_create: 20 | def __init__(self, filename, mode="r"): 21 | self.filename = filename 22 | self.mode = mode 23 | self.fp = None 24 | 25 | def __enter__(self): 26 | os.makedirs(os.path.dirname(self.filename), exist_ok=True) 27 | self.fp = open(self.filename, self.mode) 28 | return self 29 | 30 | def __exit__(self, type, value, traceback): 31 | self.fp.close() 32 | 33 | def write(self, m): 34 | self.fp.write(m) 35 | 36 | def read(self): 37 | self.fp.read() 38 | 39 | 40 | class VMRunnerProcess: 41 | def __init__(self, cli): 42 | self.cli = cli 43 | self.vm = None 44 | self.reset() 45 | self.code = "" 46 | 47 | def reset(self): 48 | pass 49 | 50 | def exit(self): 51 | pass 52 | 53 | def prepare_cli(self, code): 54 | raise Exception("please implement or return empty []") 55 | 56 | def run_vm(self, code): 57 | self.code = code 58 | 59 | cli = self.cli + self.prepare_cli(code) 60 | # logger.info("run: {}".format(' '.join(cli))) 61 | p = subprocess.Popen(cli, stdout=subprocess.PIPE) 62 | try: 63 | out, err = p.communicate(timeout=2) 64 | except subprocess.TimeoutExpired as e: 65 | logger.exception(e) 66 | logger.info(code) 67 | out = [] 68 | err = [] 69 | except OSError as e: 70 | logger.exception(e) 71 | logger.info(code) 72 | out = [] 73 | err = [] 74 | # logger.info("{}\n{}".format(cli, out)) 75 | j = [] 76 | if out: 77 | j = [json.loads(line) for line in out.splitlines() if line.startswith(b"{")] 78 | # logger.info(j) 79 | return j 80 | 81 | 82 | class VMRunnerIO: 83 | def __init__(self, cli): 84 | self.cli = cli 85 | self.vm = None 86 | self.reset() 87 | self.code = "" 88 | 89 | def reset(self): 90 | if self.vm: 91 | self.exit() 92 | self.vm = subprocess.Popen( 93 | self.cli, stdin=subprocess.PIPE, stdout=subprocess.PIPE 94 | ) 95 | 96 | def exit(self): 97 | if self.vm: 98 | self.vm.stdin.write(b"\x00\x00") 99 | 100 | def run_vm(self, code): 101 | try: 102 | self.code = code 103 | code = binascii.unhexlify(code) 104 | 105 | code_length = struct.pack("H", len(code)) 106 | # logger.info('write code_length') 107 | self.vm.stdin.write(code_length) 108 | # logger.info('write code') 109 | self.vm.stdin.write(code) 110 | # logger.info('flush') 111 | self.vm.stdin.flush() 112 | 113 | # logger.info('read size') 114 | out_size_raw = self.vm.stdout.read(4) 115 | # logger.info(out_size_raw) 116 | out_size = struct.unpack("I", out_size_raw)[0] 117 | # logger.info(out_size) 118 | # logger.info('read out') 119 | out = self.vm.stdout.read(out_size) 120 | return json.loads(out) 121 | except Exception as e: 122 | logger.exception(e) 123 | time.sleep(1) 124 | self.reset() 125 | return [] 126 | # logger.info('load json') 127 | 128 | 129 | def minimize_code_keep_state(code, state, python, csharp): 130 | minimizing = True 131 | while minimizing: 132 | _code = code[:-1] 133 | if len(_code) > 0: 134 | vm1_out, vm2_out = run_both_with_code(code, python, csharp) 135 | if state in vm1_out and state in vm2_out: 136 | code = _code 137 | continue 138 | return code 139 | 140 | 141 | def get_typehash(state): 142 | return "{}_{}".format(state["opcode"], state["data"]) 143 | 144 | 145 | class FuzzerConfig: 146 | def __init__(self, name): 147 | self.new_typehash_file = "RESULTS/{}/fuzzer_new_typehash_map.{}.json".format( 148 | name, random.randint(0, 9999999) 149 | ) 150 | self.typehash_file = "RESULTS/{}/fuzzer_typehash_map.json".format(name) 151 | self.stats = { 152 | "execs": 0, 153 | "diffs": 0, 154 | "diffs_new": 0, 155 | "diffs_new_opcode": 0, 156 | "new_typehashes": 0, 157 | "typehashes": 0, 158 | } 159 | self.name = name 160 | self.vm1 = None 161 | self.vm2 = None 162 | self.typehash_map = {} 163 | self.new_typehash_map = {} 164 | self.current_coverage = {} 165 | self.roundsize = 1000 166 | self.vm1_queue = [] 167 | self.vm2_queue = [] 168 | self.clean_exit_opcode = None 169 | 170 | def sync_fuzzers(self): 171 | # logger.info(self.stats) 172 | 173 | # {"8_2": [{"vm1_code": "0800e331e97d992c8e", "vm2_code": "0800e331e97d992c8e", "vm1": {"opcode": 8, "ret": null, "data": "2", "vmstate": 0, "crash": false, "checksum": "3fd2b9ac23400c133f79280ff41d366263e76e96"}, "vm2": {"opcode": 8, "ret": null, "data": "2", "vmstate": 0, "crash": false, "checksum": "3fd2b9ac23400c133f79280ff41d366263e76e96"}}] 174 | if self.typehash_file and os.path.isfile(self.typehash_file): 175 | # logger.info('open self.typehash_file') 176 | with open(self.typehash_file, "r") as f: 177 | sync = f.read() 178 | if sync: 179 | j = json.loads(sync) 180 | self.typehash_map = j 181 | for typehash in self.new_typehash_map: 182 | if typehash not in self.typehash_map: 183 | self.typehash_map[typehash] = [] 184 | self.typehash_map[typehash].append( 185 | self.new_typehash_map[typehash][-1] 186 | ) 187 | 188 | with open_create(self.new_typehash_file, "w") as f: 189 | f.write(json.dumps(self.new_typehash_map)) 190 | 191 | with open_create(self.typehash_file, "w") as f: 192 | f.write(json.dumps(self.typehash_map)) 193 | 194 | tmp_state = { 195 | "execs": 0, 196 | "diffs": 0, 197 | "diffs_new": 0, 198 | "diffs_new_opcode": 0, 199 | "new_typehashes": 0, 200 | "typehashes": 0, 201 | } 202 | 203 | if os.path.isfile("RESULTS/{}/fuzzer_stats.json".format(self.name)): 204 | # logger.info('is file') 205 | with open("RESULTS/{}/fuzzer_stats.json".format(self.name), "r") as f: 206 | sync = f.read() 207 | # logger.info(sync) 208 | if sync: 209 | tmp_state = json.loads(sync) 210 | # logger.info(tmp_state) 211 | 212 | tmp_state["execs"] += self.stats["execs"] 213 | tmp_state["diffs"] += self.stats["diffs"] 214 | tmp_state["diffs_new"] += self.stats["diffs_new"] 215 | tmp_state["diffs_new_opcode"] += self.stats["diffs_new_opcode"] 216 | tmp_state["new_typehashes"] += len(self.new_typehash_map.keys()) 217 | tmp_state["typehashes"] += len(self.typehash_map.keys()) 218 | 219 | self.stats = tmp_state 220 | 221 | with open_create("RESULTS/{}/fuzzer_stats.json".format(self.name), "w") as f: 222 | f.write(json.dumps(tmp_state)) 223 | 224 | # logger.info(tmp_state) 225 | 226 | def pre_round(self, seed=None): 227 | raise Exception("implement setting up queue here") 228 | 229 | def post_round(self, seed=None): 230 | raise Exception("implement anything you want once the round is done") 231 | 232 | def run_both_consume_queue(self): 233 | vm1_code = self.get_job_vm1() 234 | vm2_code = self.get_job_vm2() 235 | return self.run_both_with_code(vm1_code, vm2_code) 236 | 237 | def run_both_with_code(self, vm1_code, vm2_code): 238 | vm1_out = self.vm1.run_vm(vm1_code) 239 | vm2_out = self.vm2.run_vm(vm2_code) 240 | self.stats["execs"] += 2 241 | vm1_out_clean = self.clean_vm1_out(vm1_out) 242 | vm2_out_clean = self.clean_vm2_out(vm2_out) 243 | return (vm1_out_clean, vm2_out_clean) 244 | 245 | def is_new_coverage_better(self, new_coverage, old_coverage): 246 | raise Exception("check which is better new_coverage, old_coverage") 247 | 248 | def minimize_current_coverage(self): 249 | raise Exception("minimize current coverage") 250 | 251 | def clean_vm1_out(self, out): 252 | return out 253 | 254 | def clean_vm2_out(self, out): 255 | return out 256 | 257 | def get_job_vm1(self): 258 | return self.vm1_queue.pop() 259 | 260 | def get_job_vm2(self): 261 | return self.vm2_queue.pop() 262 | 263 | def merge_coverage(self): 264 | for typehash in self.current_coverage.keys(): 265 | new_coverage = self.current_coverage[typehash] 266 | 267 | if typehash not in self.typehash_map: 268 | pass 269 | # logger.info('add {} to typehash_map'.format(typehash)) 270 | else: 271 | old_coverage = self.typehash_map[typehash][-1] 272 | if not self.is_new_coverage_better(new_coverage, old_coverage): 273 | continue 274 | 275 | # self.typehash_map[typehash].append(new_coverage) 276 | if typehash not in self.new_typehash_map: 277 | self.new_typehash_map[typehash] = [] 278 | # logger.info('add {} to new_typehash_map'.format(typehash)) 279 | 280 | self.new_typehash_map[typehash].append(new_coverage) 281 | 282 | def fuzz(self): 283 | loop = True 284 | round_nr = 0 285 | self.sync_fuzzers() 286 | while loop: 287 | round_nr += 1 288 | 289 | start = time.time() 290 | self.pre_round() 291 | self.new_typehash_map = {} # reset the tyephash coverage map for this round 292 | 293 | self.stats = {"execs": 0, "diffs": 0, "diffs_new": 0, "diffs_new_opcode": 0} 294 | 295 | for exec_nr in range(0, self.roundsize): 296 | if exec_nr % 100 == 0: 297 | logger.info("{}. {}/{}".format(round_nr, exec_nr, self.roundsize)) 298 | 299 | # open('/tmp/trace_now', 'a').close() 300 | ############### RUN THE VMS ############### 301 | vm1_out, vm2_out = self.run_both_consume_queue() 302 | ############### RUN THE VMS ############### 303 | # os.remove("/tmp/trace_now") 304 | 305 | self.current_coverage = {} 306 | 307 | if (len(vm1_out) == 0 and len(vm2_out) != 0) or ( 308 | len(vm1_out) != 0 and len(vm2_out) == 0 309 | ): 310 | logger.info( 311 | "we had a timeout or crash in one VM len output: {} vs. {}".format( 312 | len(vm1_out), len(vm2_out) 313 | ) 314 | ) 315 | 316 | diff_fname = "RESULTS/{}/diffs/typehash_crash".format(self.name) 317 | if not os.path.isfile(diff_fname): 318 | self.stats["diffs_new"] += 1 319 | 320 | self.stats["diffs"] += 1 321 | with open_create(diff_fname, "a+") as f: 322 | out = "-------------------\n" 323 | out += "vm1_code: {}\n".format(self.vm1.code) 324 | out += "vm1_code: {}\n".format(self.vm2.code) 325 | out += "vm1: {}\n".format(vm1_out) 326 | out += "vm2: {}\n".format(vm2_out) 327 | logger.info(diff_fname) 328 | logger.info(out) 329 | f.write(out) 330 | 331 | for i in range(0, min(len(vm1_out), len(vm2_out))): 332 | vm1 = vm1_out[i] 333 | vm2 = vm2_out[i] 334 | # at least one didn't crash and checksum diff 335 | if ( 336 | (not vm1["crash"] or not vm2["crash"]) 337 | and vm1["checksum"] != vm2["checksum"] 338 | or vm1["crash"] 339 | and not vm2["crash"] 340 | or not vm1["crash"] 341 | and vm2["crash"] 342 | ): 343 | 344 | logger.info(vm1) 345 | logger.info(vm2) 346 | 347 | diff_fname = "RESULTS/{}/diffs/typehash_{}-{}".format( 348 | self.name, get_typehash(vm1), get_typehash(vm2) 349 | ) 350 | if not os.path.isfile(diff_fname): 351 | self.stats["diffs_new"] += 1 352 | 353 | self.stats["diffs"] += 1 354 | with open_create(diff_fname, "a+") as f: 355 | out = "-------------------\n" 356 | out += "vm1_code: {}\n".format(self.vm1.code) 357 | out += "vm1_code: {}\n".format(self.vm2.code) 358 | out += "vm1: {}\n".format(vm1) 359 | out += "vm2: {}\n".format(vm2) 360 | logger.info(diff_fname) 361 | logger.info(out) 362 | f.write(out) 363 | 364 | if vm2["opcode"] == vm1["opcode"]: 365 | opdiff_fname = "RESULTS/{}/diffs/opcode_{}".format( 366 | self.name, vm2["opcode"] 367 | ) 368 | if not os.path.isfile(opdiff_fname): 369 | self.stats["diffs_new_opcode"] += 1 370 | with open_create(opdiff_fname, "a+") as f: 371 | f.write("{}\n".format(diff_fname)) 372 | break 373 | elif not vm1["crash"] and not vm2["crash"]: 374 | # if it was a normal execution without issues 375 | if self.clean_exit_opcode == None or ( 376 | self.clean_exit_opcode != None 377 | and vm1["opcode"] != self.clean_exit_opcode 378 | and vm2["opcode"] != self.clean_exit_opcode 379 | ): 380 | if get_typehash(vm1) == get_typehash(vm2): 381 | typehash = get_typehash(vm1) 382 | # remember working type hash in map 383 | if typehash not in self.current_coverage: 384 | self.current_coverage[typehash] = { 385 | "vm1_code": self.vm1.code, 386 | "vm2_code": self.vm2.code, 387 | "vm1": vm1, 388 | "vm2": vm2, 389 | } 390 | 391 | ######### MINIMIZING and COVERAGE ##### 392 | self.minimize_current_coverage() 393 | self.merge_coverage() 394 | ######### MINIMIZING and COVERAGE ##### 395 | 396 | lock = FileLock("/tmp/{}_sync_fuzzers.lock".format(self.name)) 397 | with lock: 398 | 399 | self.sync_fuzzers() 400 | 401 | with open_create( 402 | "RESULTS/{}/stats_typehashes_new".format(self.name), "a+" 403 | ) as f: 404 | out = "{}\n".format(len(self.new_typehash_map.keys())) 405 | logger.info("typehashes_new: {}".format(out.strip())) 406 | f.write(out) 407 | with open_create( 408 | "RESULTS/{}/stats_typehashes".format(self.name), "a+" 409 | ) as f: 410 | out = "{}\n".format(len(self.typehash_map.keys())) 411 | logger.info("typehashes: {}".format(out.strip())) 412 | f.write(out) 413 | 414 | with open_create("RESULTS/{}/stats_diffs".format(self.name), "a+") as f: 415 | out = "{}\n".format(self.stats["diffs"]) 416 | logger.info("diffs: {}".format(out.strip())) 417 | f.write(out) 418 | with open_create( 419 | "RESULTS/{}/stats_diffs_new".format(self.name), "a+" 420 | ) as f: 421 | out = "{}\n".format(self.stats["diffs_new"]) 422 | logger.info("diffs_new: {}".format(out.strip())) 423 | f.write(out) 424 | with open_create( 425 | "RESULTS/{}/stats_diffs_new_opcode".format(self.name), "a+" 426 | ) as f: 427 | out = "{}\n".format(self.stats["diffs_new_opcode"]) 428 | logger.info("diffs_new_opcode: {}".format(out.strip())) 429 | f.write(out) 430 | with open_create("RESULTS/{}/stats_time".format(self.name), "a+") as f: 431 | out = "{}\n".format(int(time.time() - start)) 432 | logger.info("time: {}".format(out.strip())) 433 | f.write(out) 434 | with open_create("RESULTS/{}/stats_execs".format(self.name), "a+") as f: 435 | out = "{}\n".format(self.stats["execs"]) 436 | logger.info("execs: {}".format(out.strip())) 437 | f.write(out) 438 | with open_create( 439 | "RESULTS/{}/stats_execs_per_sec".format(self.name), "a+" 440 | ) as f: 441 | out = "{}\n".format(self.stats["execs"] / (time.time() - start)) 442 | logger.info("execs_per_sec: {}".format(out.strip())) 443 | f.write(out) 444 | 445 | self.post_round() 446 | 447 | logger.info("done") 448 | python.exit() 449 | csharp.exit() 450 | exit(0) 451 | -------------------------------------------------------------------------------- /neodiff/NeoVmDiffGenerator.py: -------------------------------------------------------------------------------- 1 | # Neo Specific bytecode generator 2 | 3 | import random 4 | import struct 5 | import os 6 | import binascii 7 | from logzero import logger 8 | import secrets 9 | import json 10 | from neodiff.PyDiffGenerator import SCInteger 11 | 12 | # code = os.urandom(0xfff).hex() 13 | 14 | 15 | known_code = {} 16 | known_ops = set([]) 17 | 18 | 19 | def random_bytes(n): 20 | return bytes(random.getrandbits(8) for _ in range(n)) 21 | 22 | 23 | def createSyscall(): 24 | syscalls = [ 25 | "AntShares.Account.GetBalance", 26 | "AntShares.Account.GetScriptHash", 27 | "AntShares.Account.GetVotes", 28 | "AntShares.Account.SetVotes", 29 | "AntShares.Asset.Create", 30 | "AntShares.Asset.GetAdmin", 31 | "AntShares.Asset.GetAmount", 32 | "AntShares.Asset.GetAssetId", 33 | "AntShares.Asset.GetAssetType", 34 | "AntShares.Asset.GetAvailable", 35 | "AntShares.Asset.GetIssuer", 36 | "AntShares.Asset.GetOwner", 37 | "AntShares.Asset.GetPrecision", 38 | "AntShares.Asset.Renew", 39 | "AntShares.Attribute.GetData", 40 | "AntShares.Attribute.GetUsage", 41 | "AntShares.Block.GetTransaction", 42 | "AntShares.Block.GetTransactionCount", 43 | "AntShares.Block.GetTransactions", 44 | "AntShares.Blockchain.GetAccount", 45 | "AntShares.Blockchain.GetAsset", 46 | "AntShares.Blockchain.GetBlock", 47 | "AntShares.Blockchain.GetContract", 48 | "AntShares.Blockchain.GetHeader", 49 | "AntShares.Blockchain.GetHeight", 50 | "AntShares.Blockchain.GetTransaction", 51 | "AntShares.Blockchain.GetValidators", 52 | "AntShares.Contract.Create", 53 | "AntShares.Contract.Destroy", 54 | "AntShares.Contract.GetScript", 55 | "AntShares.Contract.GetStorageContext", 56 | "AntShares.Contract.Migrate", 57 | "AntShares.Header.GetConsensusData", 58 | "AntShares.Header.GetHash", 59 | "AntShares.Header.GetMerkleRoot", 60 | "AntShares.Header.GetNextConsensus", 61 | "AntShares.Header.GetPrevHash", 62 | "AntShares.Header.GetTimestamp", 63 | "AntShares.Header.GetVersion", 64 | "AntShares.Input.GetHash", 65 | "AntShares.Input.GetIndex", 66 | "AntShares.Output.GetAssetId", 67 | "AntShares.Output.GetScriptHash", 68 | "AntShares.Output.GetValue", 69 | "AntShares.Runtime.CheckWitness", 70 | "AntShares.Runtime.GetTrigger", 71 | "AntShares.Runtime.Log", 72 | "AntShares.Runtime.Notify", 73 | "AntShares.Storage.Delete", 74 | "AntShares.Storage.Get", 75 | "AntShares.Storage.GetContext", 76 | "AntShares.Storage.Put", 77 | "AntShares.Transaction.GetAttributes", 78 | "AntShares.Transaction.GetHash", 79 | "AntShares.Transaction.GetInputs", 80 | "AntShares.Transaction.GetOutpus", 81 | "AntShares.Transaction.GetReferences", 82 | "AntShares.Transaction.GetType", 83 | "Neo.Account.GetBalance", 84 | "Neo.Account.GetScriptHash", 85 | "Neo.Account.GetVotes", 86 | "Neo.Asset.Create", 87 | "Neo.Asset.GetAdmin", 88 | "Neo.Asset.GetAmount", 89 | "Neo.Asset.GetAssetId", 90 | "Neo.Asset.GetAssetType", 91 | "Neo.Asset.GetAvailable", 92 | "Neo.Asset.GetIssuer", 93 | "Neo.Asset.GetOwner", 94 | "Neo.Asset.GetPrecision", 95 | "Neo.Asset.Renew", 96 | "Neo.Attribute.GetData", 97 | "Neo.Attribute.GetUsage", 98 | "Neo.Block.GetTransaction", 99 | "Neo.Block.GetTransactionCount", 100 | "Neo.Block.GetTransactions", 101 | "Neo.Blockchain.GetAccount", 102 | "Neo.Blockchain.GetAsset", 103 | "Neo.Blockchain.GetBlock", 104 | "Neo.Blockchain.GetContract", 105 | "Neo.Blockchain.GetHeader", 106 | "Neo.Blockchain.GetHeight", 107 | "Neo.Blockchain.GetTransaction", 108 | "Neo.Blockchain.GetTransactionHeight", 109 | "Neo.Blockchain.GetValidators", 110 | "Neo.Contract.Create", 111 | "Neo.Contract.Destroy", 112 | "Neo.Contract.GetScript", 113 | "Neo.Contract.GetStorageContext", 114 | "Neo.Contract.IsPayable", 115 | "Neo.Contract.Migrate", 116 | "Neo.Enumerator.Concat", 117 | "Neo.Enumerator.Create", 118 | "Neo.Enumerator.Next", 119 | "Neo.Enumerator.Value", 120 | "Neo.Header.GetConsensusData", 121 | "Neo.Header.GetHash", 122 | "Neo.Header.GetIndex", 123 | "Neo.Header.GetMerkleRoot", 124 | "Neo.Header.GetNextConsensus", 125 | "Neo.Header.GetPrevHash", 126 | "Neo.Header.GetTimestamp", 127 | "Neo.Header.GetVersion", 128 | "Neo.Input.GetHash", 129 | "Neo.Input.GetIndex", 130 | "Neo.InvocationTransaction.GetScript", 131 | "Neo.Iterator.Create", 132 | "Neo.Iterator.Key", 133 | "Neo.Iterator.Keys", 134 | "Neo.Iterator.Next", 135 | "Neo.Iterator.Value", 136 | "Neo.Iterator.Values", 137 | "Neo.Output.GetAssetId", 138 | "Neo.Output.GetScriptHash", 139 | "Neo.Output.GetValue", 140 | "Neo.Runtime.CheckWitness", 141 | "Neo.Runtime.Deserialize", 142 | "Neo.Runtime.GetTime", 143 | "Neo.Runtime.GetTrigger", 144 | "Neo.Runtime.Log", 145 | "Neo.Runtime.Notify", 146 | "Neo.Runtime.Serialize", 147 | "Neo.Storage.Delete", 148 | "Neo.Storage.Find", 149 | "Neo.Storage.Get", 150 | "Neo.Storage.GetContext", 151 | "Neo.Storage.GetReadOnlyContext", 152 | "Neo.Storage.Put", 153 | "Neo.StorageContext.AsReadOnly", 154 | "Neo.Transaction.GetAttributes", 155 | "Neo.Transaction.GetHash", 156 | "Neo.Transaction.GetInputs", 157 | "Neo.Transaction.GetOutputs", 158 | "Neo.Transaction.GetReferences", 159 | "Neo.Transaction.GetType", 160 | "Neo.Transaction.GetUnspentCoins", 161 | "Neo.Transaction.GetWitnesses", 162 | "Neo.Witness.GetVerificationScript", 163 | "System.Block.GetTransaction", 164 | "System.Block.GetTransactionCount", 165 | "System.Block.GetTransactions", 166 | "System.Blockchain.GetBlock", 167 | "System.Blockchain.GetContract", 168 | "System.Blockchain.GetHeader", 169 | "System.Blockchain.GetHeight", 170 | "System.Blockchain.GetTransaction", 171 | "System.Blockchain.GetTransactionHeight", 172 | "System.Contract.Destroy", 173 | "System.Contract.GetStorageContext", 174 | "System.ExecutionEngine.GetCallingScriptHash", 175 | "System.ExecutionEngine.GetEntryScriptHash", 176 | "System.ExecutionEngine.GetExecutingScriptHash", 177 | "System.ExecutionEngine.GetScriptContainer", 178 | "System.Header.GetHash", 179 | "System.Header.GetIndex", 180 | "System.Header.GetPrevHash", 181 | "System.Header.GetTimestamp", 182 | "System.Header.GetVersion", 183 | "System.Runtime.CheckWitness", 184 | "System.Runtime.Deserialize", 185 | "System.Runtime.GetTime", 186 | "System.Runtime.GetTrigger", 187 | "System.Runtime.Log", 188 | "System.Runtime.Notify", 189 | "System.Runtime.Serialize", 190 | "System.Storage.Delete", 191 | "System.Storage.Get", 192 | "System.Storage.GetContext", 193 | "System.Storage.GetReadOnlyContext", 194 | "System.Storage.Put", 195 | "System.StorageContext.AsReadOnly", 196 | "System.Transaction.GetHash", 197 | ] 198 | syscall = random.choice(syscalls) 199 | return b"\x68" + struct.pack("B", len(syscall)) + bytes(syscall, "ascii") 200 | 201 | 202 | def run(seed, amount=1, probability=1, new_typehash_file=None, typehash_file=None): 203 | if seed == None: 204 | seed = secrets.randbelow(1 << 32) 205 | random.seed(seed) 206 | codes = [] 207 | 208 | all_coverage = {} 209 | new_coverage = {} 210 | 211 | if typehash_file and os.path.isfile(typehash_file): 212 | with open(typehash_file) as f: 213 | all_coverage = json.loads(f.read()) 214 | 215 | if new_typehash_file and os.path.isfile(new_typehash_file): 216 | with open(new_typehash_file) as f: 217 | new_coverage = json.loads(f.read()) 218 | 219 | for _ in range(0, amount): 220 | scinteger = SCInteger(None) 221 | last_feedback = 0 222 | feedback = [] 223 | code = b"" 224 | while len(code) < 0x7F: 225 | # select what type of code generator we use 226 | c = random.randint(0, 20) 227 | 228 | if c == 0: 229 | code += random_bytes(random.randint(0x1, 0xF)) 230 | elif c > 0 and c < 5: # pick random code from all coverage 231 | if len(all_coverage.keys()) > 0: 232 | rand_state = random.choice(list(all_coverage.keys())) 233 | rand_code = binascii.unhexlify( 234 | random.choice(all_coverage[rand_state])["vm1_code"] 235 | ) 236 | d = random.randint(0, 2) 237 | if d == 0: 238 | code += rand_code 239 | elif d == 1: 240 | rand_state2 = random.choice(list(all_coverage.keys())) 241 | rand_code2 = binascii.unhexlify( 242 | random.choice(all_coverage[rand_state2])["vm1_code"] 243 | ) 244 | 245 | if len(rand_code) > 3 and len(rand_code2) > 3: 246 | split1 = random.randint(1, len(rand_code) - 1) 247 | split2 = random.randint(1, len(rand_code2) - 1) 248 | code += rand_code[:split1] + rand_code2[split2:] 249 | elif d == 2: 250 | pos = random.randint(0, len(rand_code) - 1) 251 | code += rand_code[:pos] + random_bytes(1) + rand_code[pos + 1 :] 252 | elif c >= 5 and c <= 20: # pick random from new coverage 253 | if len(new_coverage.keys()) > 0: 254 | rand_state = random.choice(list(new_coverage.keys())) 255 | rand_code = binascii.unhexlify( 256 | random.choice(new_coverage[rand_state])["vm1_code"] 257 | ) 258 | d = random.randint(0, 2) 259 | if d == 0: 260 | code += rand_code 261 | elif d == 1: 262 | rand_state2 = random.choice(list(new_coverage.keys())) 263 | rand_code2 = binascii.unhexlify( 264 | random.choice(new_coverage[rand_state2])["vm1_code"] 265 | ) 266 | if len(rand_code) > 3 and len(rand_code2) > 3: 267 | split1 = random.randint(1, len(rand_code) - 1) 268 | split2 = random.randint(1, len(rand_code2) - 1) 269 | code += rand_code[:split1] + rand_code2[split2:] 270 | elif d == 2: 271 | pos = random.randint(0, len(rand_code) - 1) 272 | code += rand_code[:pos] + random_bytes(1) + rand_code[pos + 1 :] 273 | codes.append(code + b"\x66") # add a RET to signal successful execution 274 | return codes 275 | 276 | 277 | if __name__ == "__main__": 278 | import argparse 279 | 280 | parser = argparse.ArgumentParser() 281 | parser.add_argument("--seed", "-s", default=None, type=int, help="The initial seed") 282 | 283 | args = parser.parse_args() 284 | 285 | print(run(seed=args.seed)) 286 | -------------------------------------------------------------------------------- /neodiff/PyDiffGenerator.py: -------------------------------------------------------------------------------- 1 | # Python specific code generators 2 | 3 | import random 4 | import secrets 5 | import sys 6 | import struct 7 | import ast 8 | import os 9 | from logzero import logger 10 | 11 | from multiprocessing import Queue, Process 12 | 13 | 14 | WORDS = [ 15 | "e", 16 | "AA", 17 | "iwa", 18 | "epee", 19 | "abate", 20 | "reject", 21 | "satiric", 22 | "uxorious", 23 | "shibuichi", 24 | "headwaiter", 25 | "turncoatism", 26 | "pyroglutamic", 27 | "puebloization", 28 | "atlantomastoid", 29 | "inconsecutively", 30 | "hypercarburetted", 31 | "unsympathetically", 32 | "physiotherapeutics", 33 | "stereochromatically", 34 | "pneumohydropericardium", 35 | "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 36 | "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 37 | "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 38 | "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 39 | "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 40 | "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 41 | "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 42 | ] 43 | 44 | NAMES = list("abcdefghijklmnopqrstuvwxyz") 45 | 46 | 47 | class SCRVal: 48 | pass 49 | 50 | 51 | class SCString(SCRVal): 52 | def __init__(self, state): 53 | self.state = state 54 | 55 | def give_val(self, depth=0): 56 | word = random.choice(WORDS) 57 | return '"{}"'.format(word) 58 | 59 | 60 | class SCNone(SCRVal): 61 | def __init__(self, state): 62 | self.state = state 63 | 64 | def give_val(self, depth=0): 65 | return "None" 66 | 67 | 68 | class SCDict(SCRVal): 69 | def __init__(self, state): 70 | self.state = state 71 | self.used_keys = [] 72 | 73 | def give_val(self, depth=0): 74 | choice = random.randint(0, 100) 75 | dict_kv_n = 0 76 | if choice == 0 or depth > 1: 77 | return "{}" 78 | elif choice >= 1 and choice < 99: 79 | dict_kv_n = random.randint(0, 5) 80 | elif choice == 99: 81 | dict_kv_n = random.randint(0, 1024) 82 | generated_dict = [] 83 | 84 | for _ in range(0, dict_kv_n): 85 | key = random.choice(self.state.simple_types).give_val(depth + 1) 86 | val = self.state.scvalue.give_val(depth + 1) 87 | if not key in self.used_keys: 88 | self.used_keys.append(key) 89 | generated_dict.append("{}: {}".format(key, val)) 90 | return "{{{}}}".format(",".join(generated_dict)) 91 | 92 | def gen_access(self): 93 | if len(self.used_keys) > 0: 94 | return "[{}]".format(random.choice(self.used_keys)) 95 | else: 96 | return "" 97 | 98 | 99 | class SCList(SCRVal): 100 | def __init__(self, state): 101 | self.state = state 102 | self.val_generators = [ 103 | self.state.scvalue, 104 | self.state.scvariable, 105 | # ---- 106 | self.state.scinteger, 107 | self.state.scstring, 108 | self.state.scboolean, 109 | self.state.scnone, 110 | ] 111 | 112 | def give_val(self, depth=0): 113 | choice = random.randint(0, 100) 114 | list_len = 0 115 | if choice == 0 or depth > self.state.depth: 116 | return "[]" 117 | elif choice >= 1 and choice < 99: 118 | list_len = random.randint(0, 5) 119 | elif choice == 99: 120 | list_len = random.randint(0, 1024) 121 | generated_list = [] 122 | val_generator = random.choice(self.val_generators) 123 | for _ in range(0, list_len): 124 | generated_list.append(val_generator.give_val(depth + 1)) 125 | return "[{}]".format(",".join(generated_list)) 126 | 127 | def gen_access(self): 128 | # [0] [:1] [1:] [1:1] [:] 129 | choice = random.randint(0, 4) 130 | if choice == 0: 131 | return "[{}]".format(self.state.scinteger.give_val()) 132 | if choice == 1: 133 | return "[:{}]".format(self.state.scinteger.give_val()) 134 | if choice == 2: 135 | return "[{}:]".format(self.state.scinteger.give_val()) 136 | if choice == 3: 137 | return "[{}:{}]".format( 138 | self.state.scinteger.give_val(), self.state.scinteger.give_val() 139 | ) 140 | if choice == 4: 141 | return "[:{}:{}]".format( 142 | self.state.scinteger.give_val(), self.state.scinteger.give_val() 143 | ) 144 | if choice == 5: 145 | return "[{}::{}]".format( 146 | self.state.scinteger.give_val(), self.state.scinteger.give_val() 147 | ) 148 | if choice == 6: 149 | return "[{}:{}:{}]".format( 150 | self.state.scinteger.give_val(), 151 | self.state.scinteger.give_val(), 152 | self.state.scinteger.give_val(), 153 | ) 154 | 155 | 156 | class SCBoolean(SCRVal): 157 | def __init__(self, state): 158 | self.state = state 159 | 160 | def give_val(self, depth=0): 161 | word = random.choice(["True", "False"]) 162 | return "{}".format(word) 163 | 164 | 165 | class SCInteger(SCRVal): 166 | def __init__(self, state): 167 | self.state = state 168 | MAX = sys.maxsize 169 | self.special_int = [ 170 | MAX, 171 | MAX + 1, 172 | MAX - 1, 173 | -MAX - 1, 174 | -MAX, 175 | -MAX - 2, 176 | 0, 177 | 1, 178 | -1, 179 | 2, 180 | -2, 181 | 3, 182 | -3, 183 | 1024, 184 | -1024, 185 | 1025, 186 | -1025, 187 | 4096, 188 | -4096, 189 | 0xFF, 190 | 0xFFFF, 191 | 0xFFFFFF, 192 | 0xFFFFFFFF, 193 | 0x80, 194 | 0x8000, 195 | 0x800000, 196 | 0x80000000, 197 | 0x7F, 198 | 0x7FFF, 199 | 0x7FFFFF, 200 | 0x7FFFFFFF, 201 | ] 202 | 203 | def give_val(self, depth=0): 204 | choice = random.randint(0, 10) 205 | integer = 0 206 | if choice == 0: 207 | integer = random.randint(min(self.special_int), max(self.special_int)) 208 | elif choice == 1: 209 | integer = random.choice(self.special_int) 210 | elif choice >= 2: 211 | integer = random.randint(-0xFF, 0xFF) 212 | return "{}".format(integer) 213 | 214 | 215 | """ 216 | class SCFloat(SCRVal): 217 | def __init__(self, state): 218 | self.state = state 219 | CLOSE_MAX = 1.7976e308 # not the actual max but close enough 220 | MAX = sys.float_info.max 221 | MIN = sys.float_info.min 222 | self.special_val = [ 223 | MAX, 224 | MAX * MIN, 225 | CLOSE_MAX, 226 | -CLOSE_MAX, 227 | MAX + 1.0e303, # inf 228 | MAX * -2, # -inf 229 | -MAX, 230 | MAX - MAX, # 0.0 231 | -0.0, 232 | 0.0, 233 | MIN, 234 | -MIN, 235 | MAX + MIN, 236 | -MAX - MIN, 237 | 1 / 3, 238 | -1 / 3, 239 | ] # type: List[float] 240 | 241 | def give_val(self, depth=0): 242 | choice = random.randint(0, 10) 243 | val = 0.0 244 | if choice == 0: 245 | val = random.choice(self.special_val) 246 | elif choice == 1: 247 | val = float(int(random.randint(-10, 10))) 248 | elif choice < 6: 249 | f1 = float(int(random.randint(-10, 10))) 250 | f2 = float(int(random.randint(-10, 10))) 251 | if f2 == 0.0: 252 | f2 = 100.0 253 | val = f1 / f2 254 | elif choice >= 2: 255 | val = struct.unpack( 256 | "d", bytes([random.randint(0x000, 0xFF) for x in range(8)]) 257 | )[0] 258 | return "{}".format(val), SCFloat 259 | """ 260 | 261 | # ------------------------------------------------ 262 | 263 | 264 | class SCValue: 265 | def __init__(self, state): 266 | self.state = state 267 | 268 | def give_val(self, depth=0): 269 | vals = self.state.all_types + [self.state.scvariable] 270 | return random.choice(vals).give_val(depth + 1) 271 | 272 | 273 | class SCVariable: 274 | def __init__(self, state): 275 | self.state = state 276 | self.used_vars = [] 277 | 278 | def give_val(self, depth=0): 279 | if len(self.used_vars) == 0: 280 | return self.state.scvalue.give_val(depth + 1) 281 | else: 282 | return random.choice(self.used_vars) 283 | 284 | def new_var(self): 285 | var_name = random.choice(NAMES) 286 | if not var_name in self.used_vars: 287 | self.used_vars.append(var_name) 288 | return var_name 289 | 290 | def gen(self, depth=0): 291 | var_value = self.state.scvalue.give_val(depth + 1) 292 | var_name = self.new_var() 293 | return "{} = {}".format(var_name, var_value) 294 | 295 | 296 | # ------------------------------------------------ 297 | 298 | 299 | class SCOperator: 300 | def __init__(self, state): 301 | self.state = state 302 | self.a_boc = [ 303 | "+", 304 | "/", 305 | "//", 306 | "&", 307 | "^", 308 | "|", 309 | # "**", # Op Not Converted BINARY_POWER 310 | "is", 311 | "is not", 312 | "<<", 313 | "%", 314 | "*", 315 | "-", 316 | ">>", 317 | ">", 318 | "<", 319 | "==", 320 | ">=", 321 | "<=", 322 | "!=", 323 | "in", 324 | ] 325 | self.ao_b = ["+", "*", "/", "//", "-", "%", "&", "^", "|"] 326 | self.a_ob = [ 327 | # "+", # Op Not Converted UNARY_POSITIVE 328 | "-", 329 | "not", 330 | "~", 331 | ] 332 | self.access_generators = [self.state.sclist, self.state.scdict] 333 | self.oa = ["return"] 334 | self.special = ["pass"] # added based on context 335 | 336 | def gen(self, depth=0): 337 | choice = random.randint(0, 4) 338 | if len(self.special) > 0: 339 | choice = random.randint(0, 5) 340 | if choice == 0: 341 | A = self.state.scvariable.give_val() 342 | B = self.state.scvariable.give_val() 343 | C = self.state.scvariable.give_val() 344 | 345 | if random.randint(0, 1) == 0: 346 | A = self.state.scvariable.new_var() 347 | return "{} = {} {} {}".format(A, B, random.choice(self.a_boc), C) 348 | elif choice == 1: 349 | A = self.state.scvariable.give_val() 350 | B = self.state.scvariable.give_val() 351 | return "{} {}= {}".format(A, random.choice(self.ao_b), B) 352 | elif choice == 2: 353 | A = self.state.scvariable.give_val() 354 | B = self.state.scvariable.give_val() 355 | return "{} = {} {}".format(A, random.choice(self.a_ob), B) 356 | elif choice == 3: # return 357 | A = self.state.scvariable.give_val() 358 | if random.randint(0, 100) < 90: 359 | # Too many returns happening. TODO: Fix this nicer. 360 | return self.gen(depth) 361 | return "return ({},{})".format(random.randint(000000, 999999), A) 362 | elif choice == 4: 363 | A = self.state.scvariable.give_val() 364 | if random.randint(0, 1) == 0: 365 | A = self.state.scvariable.new_var() 366 | B = self.state.scvariable.give_val() 367 | return "{} = {}{}".format( 368 | A, B, random.choice(self.access_generators).gen_access() 369 | ) 370 | elif choice == 5: # only with special ops 371 | return random.choice(self.special) 372 | 373 | 374 | # ------------------------------------------------ 375 | 376 | 377 | class SCState: 378 | def __init__(self, seed=None): 379 | 380 | self.depth = 5 381 | self.scvariable = SCVariable(self) 382 | self.scvalue = SCValue(self) 383 | 384 | self.scstring = SCString(self) 385 | self.scinteger = SCInteger(self) 386 | self.scboolean = SCBoolean(self) 387 | self.scnone = SCNone(self) 388 | if seed: 389 | # logger.info("set seed: {}".format(seed)) 390 | random.seed(seed) 391 | # NEO doesn't do this. 392 | # self.scfloat = SCFloat(self) 393 | 394 | self.sclist = SCList(self) 395 | self.scdict = SCDict(self) 396 | 397 | self.scoperator = SCOperator(self) 398 | self.scconditional = SCConditional(self) 399 | 400 | self.simple_types = [self.scstring, self.scinteger, self.scboolean, self.scnone] 401 | self.complex_types = [self.sclist, self.scdict] 402 | self.all_types = self.simple_types + self.complex_types 403 | 404 | 405 | class SCConditional: 406 | def __init__(self, state): 407 | self.state = state 408 | 409 | def gen(self, depth=0): 410 | # if, if else, if elif else, while 411 | if depth > 3: 412 | return "" 413 | choice = random.randint(0, 5) 414 | codeblock = SCCodeblock() 415 | 416 | codeblock.state.scoperator.special = self.state.scoperator.special[:] 417 | for var in self.state.scvariable.used_vars: 418 | if var not in codeblock.state.scvariable.used_vars: 419 | codeblock.state.scvariable.used_vars.append(var) 420 | 421 | if choice == 0: 422 | A = self.state.scvariable.give_val() 423 | B = self.state.scvariable.give_val() 424 | 425 | return "if {} {} {}:\n{}".format( 426 | A, random.choice(self.state.scoperator.a_boc), B, codeblock.gen(depth) 427 | ) 428 | elif choice == 1: 429 | A = self.state.scvariable.give_val() 430 | B = self.state.scvariable.give_val() 431 | C = self.state.scvariable.give_val() 432 | D = self.state.scvariable.give_val() 433 | 434 | return "if {} {} {}:\n{}\n{}elif {} {} {}:\n{}".format( 435 | A, 436 | random.choice(self.state.scoperator.a_boc), 437 | B, 438 | codeblock.gen(depth), 439 | " " * (depth - 1), 440 | C, 441 | random.choice(self.state.scoperator.a_boc), 442 | D, 443 | codeblock.gen(depth), 444 | ) 445 | elif choice == 2: 446 | A = self.state.scvariable.give_val() 447 | B = self.state.scvariable.give_val() 448 | 449 | return "if {} {} {}:\n{}\n{}else:\n{}".format( 450 | A, 451 | random.choice(self.state.scoperator.a_boc), 452 | B, 453 | codeblock.gen(depth), 454 | " " * (depth - 1), 455 | codeblock.gen(depth), 456 | ) 457 | elif choice == 3: 458 | A = self.state.scvariable.give_val() 459 | B = self.state.scvariable.give_val() 460 | codeblock.state.scoperator.special += ["continue", "break"] 461 | 462 | return ( 463 | "count{depth} = 0\n" 464 | "{space}while ({A} {OP} {B}) and count{depth} < 100:\n" 465 | "{space} count{depth} += 1\n" 466 | "{block}".format( 467 | depth=depth, 468 | space=" " * (depth * 4 - 4), 469 | A=A, 470 | OP=random.choice(self.state.scoperator.a_boc), 471 | B=B, 472 | block=codeblock.gen(depth), 473 | ) 474 | ) 475 | elif choice == 4: 476 | A = self.state.scvariable.give_val() 477 | B = self.state.scvariable.give_val() 478 | codeblock.state.scoperator.special += ["continue", "break"] 479 | 480 | return "for {A} in {B}:\n" "{block}".format( 481 | depth=depth, 482 | space=" " * (depth * 4 - 4), 483 | A=A, 484 | B=B, 485 | block=codeblock.gen(depth), 486 | ) 487 | elif choice == 5: 488 | A = self.state.scvariable.give_val() 489 | codeblock.state.scoperator.special += ["continue", "break"] 490 | 491 | return ( 492 | "count{depth} = 0\n" 493 | "{space}while ({A}) and count{depth} < 100:\n" 494 | "{space} count{depth} += 1\n" 495 | "{block}".format( 496 | depth=depth, 497 | space=" " * (depth * 4 - 4), 498 | A=A, 499 | block=codeblock.gen(depth), 500 | ) 501 | ) 502 | 503 | 504 | class SCCodeblock: 505 | def __init__(self): 506 | self.state = SCState() 507 | 508 | def gen(self, depth=0): 509 | declarations = [] 510 | num_vars = random.randint(1, 4) 511 | for _ in range(0, num_vars): 512 | declarations.append(self.state.scvariable.gen(depth + 1)) 513 | num_ops = random.randint(0, 4) 514 | 515 | code = [] 516 | for _ in range(0, num_ops): 517 | code.append(self.state.scoperator.gen(depth + 1)) 518 | 519 | num_conds = 2 520 | for _ in range(0, num_conds): 521 | code.append(self.state.scconditional.gen(depth + 1)) 522 | 523 | random.shuffle(code) 524 | ret = self.state.scvariable.give_val() 525 | if random.randint(0, 100) > 40: 526 | code.append("return [{}, {}]".format(random.randint(000000, 999999), ret)) 527 | prefix = " " * depth 528 | return "{}".format("\n".join([prefix + x for x in declarations + code])) 529 | 530 | 531 | def _t_try_code(q: Queue, code: str): 532 | try: 533 | exec(code) 534 | q.put("Works") 535 | except Exception as e: 536 | q.put(e) 537 | 538 | 539 | def timed_exec(code, timeout=1): 540 | q = Queue(1) 541 | p = Process(target=_t_try_code, args=(q, code), daemon=True) 542 | p.start() 543 | try: 544 | res = q.get(timeout=timeout) 545 | except Exception as ex: 546 | p._stop() 547 | raise TimeoutError("The code timed out after {} ({})".format(timeout, ex)) 548 | if isinstance(res, Exception): 549 | raise res 550 | 551 | 552 | class SCGenContract: 553 | def __init__(self, target="py"): 554 | self.target = target 555 | pass 556 | 557 | def gen(self, depth=0): 558 | codeblock = SCCodeblock() 559 | generated_code = codeblock.gen(depth + 1) 560 | if self.target == "py": 561 | return "def Main():\n{}\n\nMain()\n".format(generated_code) 562 | elif self.target == "sc" or self.target == "plain": # for now the same 563 | return "def Main():\n{}\n".format(generated_code) 564 | else: 565 | raise ValueError("Unknown target {}".format(self.target)) 566 | 567 | def gen_valid(self, mayblock=False): 568 | tries = 0 569 | while True: 570 | code = self.gen() 571 | try: 572 | # check for validity. We'll spawn a process if we're not allowe to block 573 | if mayblock: 574 | exec(code) 575 | else: 576 | timed_exec(code) 577 | return code, tries 578 | except KeyboardInterrupt: 579 | print(code) 580 | print("#>> bye") 581 | raise 582 | except Exception as ex: 583 | tries += 1 584 | pass 585 | 586 | 587 | def run( 588 | seed=None, illegal=False, target="py", mayblock=False, verbose=False, evalcode=False 589 | ): 590 | if seed == None: 591 | seed = secrets.randbelow(1 << 32) 592 | if seed: 593 | # logger.info("seed: {}".format(seed)) 594 | random.seed(seed) 595 | 596 | state = SCState(seed=seed) 597 | contract = SCGenContract(target=target) 598 | 599 | code = None 600 | if illegal: 601 | # Just return the first fit. Yolo. 602 | code = contract.gen() 603 | # print(code) 604 | else: 605 | code, tries = contract.gen_valid(mayblock=mayblock) 606 | # print(code) 607 | if verbose: 608 | print("# Generated {} illegal code blocks before one worked.".format(tries)) 609 | 610 | if evalcode: 611 | # print("result = ", end="") 612 | sys.stdout.flush() 613 | os.system("/usr/bin/env python3 -c '{}'".format(code)) 614 | 615 | return "# Seed was {}\n\n{}".format(seed, code) 616 | 617 | 618 | if __name__ == "__main__": 619 | import argparse 620 | 621 | parser = argparse.ArgumentParser() 622 | parser.add_argument("--seed", "-s", default=None, type=int, help="The initial seed") 623 | parser.add_argument( 624 | "--evalcode", 625 | "-e", 626 | action="store_true", 627 | help="Evals the code after creation, prints result", 628 | ) 629 | parser.add_argument( 630 | "--target", 631 | "-t", 632 | default="py", 633 | type=str, 634 | choices=["py", "sc", "plain"], 635 | help="Output formatted for as NEO smart contract or regular CPython script.", 636 | ) 637 | parser.add_argument( 638 | "--illegal", 639 | "-i", 640 | action="store_true", 641 | help="Do not loop to find valid code, output first generation.", 642 | ) 643 | parser.add_argument( 644 | "--mayblock", 645 | "-m", 646 | action="store_true", 647 | help=( 648 | "Allow functions that soft-block (like large exponents).\n" 649 | "This means that the generation may never finish.\n" 650 | "However, code generation is quite a bit faster." 651 | ), 652 | ) 653 | parser.add_argument( 654 | "--verbose", "-v", action="store_true", help="Enable verbose output" 655 | ) 656 | args = parser.parse_args() 657 | if args.illegal and args.mayblock: 658 | raise ValueError("Options: '--mayblock' has no effect if '--illegal' is set.") 659 | 660 | code = run( 661 | seed=args.seed, 662 | illegal=args.illegal, 663 | target=args.target, 664 | mayblock=args.mayblock, 665 | verbose=args.verbose, 666 | evalcode=args.evalcode, 667 | ) 668 | print(code) 669 | -------------------------------------------------------------------------------- /neodiff/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsect/NeoDiff/a2f30b7ec0a157cc41c0143c18cd68e3e02b6674/neodiff/__init__.py -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aenum==2.2.1 2 | aiohttp==3.6.0 3 | aiohttp-cors==0.7.0 4 | appdirs==1.4.3 5 | astor==0.8.0 6 | async-timeout==3.0.1 7 | asynctest==0.13.0 8 | attrs==19.1.0 9 | autopep8==1.4.4 10 | base58==1.0.3 11 | bitcoin==1.1.42 12 | black==19.10b0 13 | certifi==2019.6.16 14 | chardet==3.0.4 15 | click==7.1.2 16 | coverage==4.5.4 17 | coveralls==1.8.2 18 | coz-bytecode==0.5.1 19 | docopt==0.6.2 20 | ecdsa==0.13.2 21 | filelock==3.0.12 22 | idna==2.8 23 | logzero==1.7.0 24 | mmh3==2.5.1 25 | mpmath==1.1.0 26 | multidict==4.5.2 27 | neo-python-rpc==0.2.1 28 | pathspec==0.8.0 29 | pycodestyle==2.5.0 30 | pymitter==0.2.3 31 | regex==2020.4.4 32 | requests==2.22.0 33 | toml==0.10.0 34 | typed-ast==1.4.1 35 | urllib3==1.25.3 36 | yarl==1.3.0 37 | 38 | -------------------------------------------------------------------------------- /roots21-2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsect/NeoDiff/a2f30b7ec0a157cc41c0143c18cd68e3e02b6674/roots21-2.pdf -------------------------------------------------------------------------------- /roots21-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsect/NeoDiff/a2f30b7ec0a157cc41c0143c18cd68e3e02b6674/roots21-2.png -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "getting git submodules for EVMs" 4 | git submodule init 5 | git submodule update 6 | 7 | echo "building go-ethereum (install go for this)" 8 | make -C ./go-ethereum all 9 | 10 | echo "building openethereum (install rust for this)" 11 | cd ./openethereum/bin/evmbin 12 | cargo build --release 13 | cd ../../.. 14 | 15 | echo "Installing reqs for us, creating virtualenv" 16 | virtualenv -p "$(command -v python3)" "$(pwd)/.env" 17 | . .env/bin/activate 18 | pip install -r requirements.txt 19 | 20 | echo "Installing reqs for neopython" 21 | cd neo-python 22 | pip install -r requirements.txt 23 | cd .. 24 | 25 | echo "Done building :)" 26 | echo "Activate the virtualenv with:" 27 | echo ". .env/bin/activate" 28 | -------------------------------------------------------------------------------- /utils/EVMrun.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # start EVMFuzz.py 4 | 5 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 6 | cd "SCRIPT_DIR"/.. 7 | 8 | set -x 9 | # parameter is the seed 10 | while :; do ./EVMFuzz.py --name A_20p2000 --seed "$1" --roundsize 2000 --probability 20; echo "Crashed! Restarting"; sleep 1 ; done 11 | 12 | -------------------------------------------------------------------------------- /utils/EVMscale.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Scale EVMFuzz.py to multiple cores 4 | 5 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 6 | cd "SCRIPT_DIR" 7 | 8 | . ../.env/bin/activate 9 | 10 | for i in {1..$1}; do 11 | echo "Starting $i"; 12 | ./EVMrun.sh "$i" & 13 | sleep 1 14 | done 15 | 16 | -------------------------------------------------------------------------------- /utils/EV_Mkill.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | `ps aux | grep -ie EVM | awk '{print "kill " $2}'` 4 | -------------------------------------------------------------------------------- /utils/analyze_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Creates a machine-readable csv from the RESULTS folder 4 | 5 | import os 6 | 7 | NAMES = [ 8 | "../RESULTS/A_20p2000" 9 | ] # 'a20p2000'] #'2k20o1p', '2k20o20p', '2k500o1p', '2k500o20p'] 10 | 11 | DATA = [ 12 | "execs", 13 | "diffs", 14 | "diffs_new", 15 | "execs_per_sec", 16 | "diffs_new_opcode", 17 | "time", 18 | "typehashes", 19 | "typehashes_new", 20 | ] 21 | # DATA = ['OPCODE_DIFFS'] 22 | RESULTS = {} 23 | for name in NAMES: 24 | d = {} 25 | RESULTS[name] = {} 26 | for data in DATA: 27 | data_lengths = [] 28 | _data = data 29 | with open("{}/stats_{}".format(name, _data)) as f: 30 | d[data] = f.readlines() 31 | data_lengths.append(len(d[data])) 32 | 33 | length = min(data_lengths) 34 | execs_accum = 0 35 | for i in range(0, length): 36 | execs_accum = int(d["execs"][i]) 37 | RESULTS[name][execs_accum] = {} 38 | for data in DATA: 39 | RESULTS[name][execs_accum][data] = d[data][i] 40 | 41 | for data in DATA: 42 | try: 43 | os.mkdir("_CSV") 44 | except Exception as ex: 45 | pass 46 | # print("Folder _CSV existed. This is fine.", ex) 47 | 48 | with open("_CSV/{}.csv".format(data), "w") as f: 49 | execs_accum = [] 50 | for name in NAMES: 51 | execs_accum += list(RESULTS[name].keys()) 52 | execs_accum.sort() 53 | 54 | vals_accum = {} 55 | vals = ["Execs"] 56 | for name in NAMES: 57 | vals.append(name.split("/")[-1]) 58 | f.write("{}\n".format(",".join(vals))) 59 | for key in execs_accum: 60 | vals = [str(key)] 61 | for name in NAMES: 62 | if name not in vals_accum: 63 | vals_accum[name] = 0 64 | 65 | val = RESULTS[name].get(key, None) 66 | if val: 67 | vals_accum[name] = int(float(RESULTS[name][key][data].strip())) 68 | # vals_accum[name] += int(float(RESULTS[name][key][data].strip())) 69 | vals.append(str(vals_accum[name])) 70 | else: 71 | vals.append("") 72 | print(vals) 73 | f.write("{}\n".format(",".join(vals))) 74 | -------------------------------------------------------------------------------- /utils/neo-python-VM.afl.py: -------------------------------------------------------------------------------- 1 | """ 2 | AFL support for python neo VM 3 | Run with: 4 | ```bash 5 | AFL_AUTORESUME=1 PATH="./AFLplusplus:${PATH}" py-afl-fuzz -i in -t 2000 -o out -U -m none -M main -- $(which python3) ./neo-python-VM.afl.py 6 | ``` 7 | """ 8 | 9 | import os 10 | import binascii 11 | from io import BytesIO, BufferedReader 12 | import sys 13 | import hashlib 14 | from logzero import logger 15 | from itertools import chain 16 | import json 17 | import struct 18 | import pathlib 19 | import glob 20 | import trace 21 | 22 | import os 23 | sys.path.append(os.path.dirname(os.path.abspath(__file__))+'/../neo-python/') 24 | 25 | from neo.VM.ExecutionEngine import ExecutionEngine 26 | from neo.SmartContract.ApplicationEngine import ApplicationEngine 27 | from neo.SmartContract import TriggerType 28 | from neo.VM import OpCode 29 | from neo.VM import VMState 30 | from neo.VM import InteropService 31 | from neo.Core.Cryptography.Crypto import Crypto 32 | from neo.Core.Fixed8 import Fixed8 33 | from neo.Core.IO.BinaryReader import BinaryReader 34 | 35 | 36 | #stream = open('code','rb') 37 | 38 | TYPE2BYTE = { 39 | InteropService.Integer: '1', 40 | InteropService.ByteArray: '2', 41 | InteropService.Array: '3', 42 | InteropService.Boolean: '4', 43 | InteropService.Map: '5', 44 | InteropService.Struct: '6', 45 | InteropService.InteropInterface: '7', 46 | } 47 | 48 | class Checksum: 49 | def __init__(self): 50 | self.reset() 51 | def reset(self): 52 | self.data = b'' 53 | def update(self, b): 54 | #self.sum.update(b) 55 | #logger.error(binascii.hexlify(b).decode('ascii')) 56 | self.data += b 57 | #print(self.result()) 58 | def result(self): 59 | sha = hashlib.sha1() 60 | #print(binascii.hexlify(self.data).decode('ascii')) 61 | sha.update(self.data) 62 | return sha.hexdigest() 63 | 64 | def getItemAsBytes(item): 65 | #logger.info(item) 66 | #logger.info(type(item)) 67 | if type(item) == InteropService.Struct or type(item) == InteropService.Array: 68 | out = b'' 69 | for value in item.GetArray(): 70 | out += getItemAsBytes(value) 71 | return out 72 | elif type(item) == InteropService.Map: 73 | out = b'' 74 | for key in item.Keys: 75 | out += getItemAsBytes(key) 76 | out += getItemAsBytes(item.GetItem(key)) 77 | #logger.info('parse map: '+out) 78 | return out 79 | elif type(item) == InteropService.InteropInterface: 80 | return b'InteropInterface' 81 | else: 82 | return item.GetByteArray() 83 | 84 | #code = bytes.fromhex('552a3694b5ee4e14e7e63ec0b79f3d8f4ecb64fb9f12e9d06351e33f5df3457d428c9221301c099a0638f0b5054f3a5ec21483863cd91cf2e746f755df55e831ed1b7bc54ed741c24d69aae564caf5344e1e855ea7dca16b37e36dd8fb2458e0a4e3e3f1a8bc674e3603481c3fbb142786f9b809c42d45b16c653d77096b1376f9e557f3a77e368a297e68aa4e665ef6d301323c6342e8eaca7a8889672fb6ad8f4353c43149820711a1d652576fceaeac6902de44d5e2a88e347ff11bd6b3e4dfc0eccddd375b75d62b078d17d2ed8016ea7658308261e604e28dafc28999f6c4cbe85c0c2ff52616f5ad2a089f28c12519887ed8f1b49b8c7c03988b775d') 85 | #code = bytes.fromhex('55556818')+b'System.Header.GetVersion'+bytes.fromhex('55'*1024) 86 | #code = bytes.fromhex('55555555c5c2') 87 | 88 | #code = bytes.fromhex(sys.argv[1]) 89 | #print(binascii.hexlify(code)) 90 | 91 | OPCODES = {} 92 | OPCODES_COVERAGE_NEWT = {} 93 | OPCODES_COVERAGE_OLDT = {} 94 | OPCODES_TYPEHASHES = {} 95 | 96 | def log(m): 97 | with open('coverage.log', 'a+') as f: 98 | logger.info(m) 99 | f.write("{}\n".format(m)) 100 | 101 | 102 | def log_coverage(state): 103 | #logger.info(state) 104 | if not os.path.isfile('/tmp/trace_now'): 105 | return 106 | typehash = "{}_{}".format(state['opcode'], state['data']) 107 | op = state['opcode'] 108 | 109 | if op not in OPCODES_COVERAGE_NEWT: 110 | OPCODES_COVERAGE_NEWT[op] = 0 111 | 112 | if op not in OPCODES_COVERAGE_OLDT: 113 | OPCODES_COVERAGE_OLDT[op] = 0 114 | 115 | if op not in OPCODES_TYPEHASHES: 116 | OPCODES_TYPEHASHES[op] = set() 117 | 118 | 119 | #logger.info('new unique typehash: {} gather coverage info'.format(typehash)) 120 | coverage = {} 121 | for fname in glob.glob('/tmp/neo-python.*'): 122 | #logger.info(fname) 123 | with open(fname) as f: 124 | lines = f.read().splitlines() 125 | if fname not in coverage: 126 | coverage[fname] = '' 127 | for i in range(0, len(lines)): 128 | line = lines[i] 129 | if not line.startswith('>>>>>>'): 130 | if line.strip() and line[5] == ':': 131 | #logger.info("{}: {}".format(i, repr(line))) 132 | coverage[fname] += '1' 133 | continue 134 | coverage[fname] += '0' 135 | 136 | if op not in OPCODES: 137 | OPCODES[op] = coverage 138 | 139 | #log("[{}] typehash for opcode {} is new. first coverage info".format(typehash, op)) 140 | else: 141 | NEW_NEW_COVERAGE = False 142 | for fname in coverage: 143 | NEW_COVERAGE = False 144 | if fname in OPCODES[op]: 145 | new_coverage = '' 146 | for a,b in zip(OPCODES[op][fname], coverage[fname]): 147 | if a == '1' or b=='1': 148 | new_coverage+='1' 149 | else: 150 | new_coverage+='0' 151 | 152 | if a=='0' and b=='1': 153 | NEW_COVERAGE = True 154 | NEW_NEW_COVERAGE = True 155 | if NEW_COVERAGE: 156 | #log("[{}] typehash lead to new coverage in opcode {}".format(typehash, op)) 157 | #log("old coverage: {}:{}".format(fname, OPCODES[op][fname])) 158 | #log("new coverage: {}:{}".format(fname, coverage[fname])) 159 | #log("sum coverage: {}:{}".format(fname, new_coverage)) 160 | OPCODES[op][fname] = new_coverage 161 | if NEW_NEW_COVERAGE: 162 | log("OP: {} Typehashes:{} CoverageOldTypehash:{} CoverageNewTypehash:{}".format(op, len(OPCODES_TYPEHASHES[op]), OPCODES_COVERAGE_OLDT[op], OPCODES_COVERAGE_NEWT[op])) 163 | 164 | if NEW_NEW_COVERAGE and typehash not in OPCODES_TYPEHASHES[op]: 165 | OPCODES_COVERAGE_NEWT[op] += 1 166 | if NEW_NEW_COVERAGE and typehash in OPCODES_TYPEHASHES[op]: 167 | OPCODES_COVERAGE_OLDT[op] += 1 168 | 169 | OPCODES_TYPEHASHES[op].add(typehash) 170 | OPCODES[op] = coverage 171 | with open('coverage.summary', 'w') as f: 172 | f.write("Opcode\tTypehashes\tCoverageOldTypes\tCoverageNewTypes\n") 173 | for op in OPCODES: 174 | f.write("{}\t{}\t{}\t{}\n".format(op, len(OPCODES_TYPEHASHES[op]), OPCODES_COVERAGE_OLDT[op], OPCODES_COVERAGE_NEWT[op])) 175 | #logger.info(OPCODES[op]) 176 | #exit(0) 177 | 178 | 179 | def run(code, depth=50): 180 | #logger.info(trace) 181 | out_data = [] 182 | checksum = Checksum() 183 | #stream = BytesIO(b"\x02AB") 184 | stream = BufferedReader(BytesIO(code)) 185 | accounts = None 186 | validators = None 187 | assets = None 188 | contracts = None 189 | storages = None 190 | wb = None 191 | # service = neo.StateMachine(accounts, validators, assets, contracts, storages, wb) 192 | 193 | #engine = neo.ExecutionEngine(crypto=neo.Crypto) 194 | class FakeCrypto: 195 | def Hash160(*_): 196 | #raise Exception("Not Implemented Crypto") 197 | pass 198 | 199 | def Hash256(*_): 200 | raise Exception("Not Implemented Crypto") 201 | 202 | def VerifySignature(message, signature, public_key, unhex=True): 203 | raise Exception("Not Implemented Crypto") 204 | engine = ExecutionEngine(crypto=FakeCrypto) 205 | tx = {} 206 | """ 207 | engine = ApplicationEngine( 208 | trigger_type=TriggerType.Application, 209 | container=tx, 210 | gas=Fixed8.Zero(), 211 | testMode=False, 212 | snapshot={} 213 | ) 214 | """ 215 | 216 | #engine.LoadScript(tx.Script) 217 | 218 | #success = engine.Execute() 219 | context = engine.LoadScript(script=code) 220 | 221 | context.OpReader = BinaryReader(stream) 222 | context.__OpReader = BinaryReader(stream) 223 | 224 | engine._VMState &= ~VMState.BREAK 225 | has_returned = False; 226 | i = 0 227 | while not has_returned and engine._VMState & VMState.HALT == 0 and engine._VMState & VMState.FAULT == 0: 228 | #try: 229 | 230 | peek = stream.peek(1)[0:1] 231 | if len(peek) == 0: 232 | break 233 | checksum.update(struct.pack("I", engine.CurrentContext.InstructionPointer)) 234 | opcode = None 235 | COVERAGE = False 236 | try: 237 | loop_data = {'opcode': None, 'ret': None, 'data': '', 'vmstate': 0, 'crash': False, 'checksum': ''} 238 | opcode = engine.CurrentContext.CurrentInstruction.OpCode 239 | loop_data['opcode'] = opcode[0] 240 | checksum.update(struct.pack("I", struct.unpack("B", opcode)[0])) 241 | 242 | if opcode!=b'\x66': 243 | if os.path.isfile('/tmp/trace_now'): 244 | t = trace.Trace(trace=False) 245 | t.runfunc(engine.ExecuteNext) 246 | r = t.results() 247 | r.write_results(coverdir="/tmp", show_missing=True) 248 | COVERAGE = True 249 | else: 250 | engine.ExecuteNext() 251 | #logger.info(r) 252 | else: 253 | #logger.info(engine._VMState) 254 | #logger.info(trace) 255 | 256 | for item in engine.CurrentContext._EvaluationStack.Items[-2:]: 257 | if type(item) == InteropService.Array: 258 | arr = item.GetArray() 259 | #logger.error(arr) 260 | if len(arr) == 2: 261 | ret_id, val = item.GetArray() 262 | if type(ret_id) == neo.ByteArray: 263 | b = getItemAsBytes(val) 264 | out = b'ret,'+struct.pack("II", ret_id.GetBigInteger(), len(b))+b 265 | loop_data['ret'] = (ret_id.GetBigInteger(), b) 266 | #out_data.append(loop_data) 267 | has_returned = True; 268 | 269 | loop_data['vmstate'] = engine._VMState & 0x3 270 | stack_types = '' 271 | 272 | for item in engine.CurrentContext._EvaluationStack.Items[::-1]: 273 | #if trace: 274 | # logger.info(binascii.hexlify(getItemAsBytes(item)).decode('ascii')) 275 | if len(stack_types) < 2: 276 | stack_types += TYPE2BYTE[type(item)] 277 | checksum.update(getItemAsBytes(item)) 278 | 279 | for item in engine.CurrentContext._AltStack.Items[::-1]: 280 | #if trace: 281 | # logger.info(binascii.hexlify(getItemAsBytes(item)).decode('ascii')) 282 | if len(stack_types) < 2: 283 | stack_types += TYPE2BYTE[type(item)] 284 | checksum.update(getItemAsBytes(item)) 285 | 286 | loop_data['data'] = stack_types 287 | loop_data['checksum'] = checksum.result() 288 | 289 | 290 | 291 | if engine._VMState & VMState.FAULT != 0: 292 | #logger.error("FAULT") 293 | loop_data['crash'] = True 294 | if COVERAGE: 295 | log_coverage(loop_data) 296 | out_data.append(loop_data) 297 | break 298 | 299 | if COVERAGE: 300 | log_coverage(loop_data) 301 | out_data.append(loop_data) 302 | except Exception as e: 303 | #logger.exception(e) 304 | loop_data['crash'] = True 305 | loop_data['checksum'] = checksum.result() 306 | if COVERAGE: 307 | log_coverage(loop_data) 308 | out_data.append(loop_data) 309 | break 310 | 311 | i += 1 312 | 313 | #logger.info(i) 314 | if i>depth: 315 | #logger.info('exec timeout (code length: {})'.format(len(code))) 316 | #logger.info(out_data) 317 | break 318 | 319 | return out_data 320 | #print("DONE") 321 | 322 | #code = bytes.fromhex('552a3694b5ee4e14e7e63ec0b79f3d8f4ecb64fb9f12e9d06351e33f5df3457d428c9221301c099a0638f0b5054f3a5ec21483863cd91cf2e746f755df55e831ed1b7bc54ed741c24d69aae564caf5344e1e855ea7dca16b37e36dd8fb2458e0a4e3e3f1a8bc674e3603481c3fbb142786f9b809c42d45b16c653d77096b1376f9e557f3a77e368a297e68aa4e665ef6d301323c6342e8eaca7a8889672fb6ad8f4353c43149820711a1d652576fceaeac6902de44d5e2a88e347ff11bd6b3e4dfc0eccddd375b75d62b078d17d2ed8016ea7658308261e604e28dafc28999f6c4cbe85c0c2ff52616f5ad2a089f28c12519887ed8f1b49b8c7c03988b775d') 323 | #code = bytes.fromhex('55556829')+b'System.ExecutionEngine.GetScriptContainer'+bytes.fromhex('55'*1) 324 | #code = binascii.unhexlify('55545352c5c2') 325 | #out_data = run(code, False) 326 | 327 | #print(out_data) 328 | 329 | import os 330 | import afl 331 | import sys 332 | 333 | if __name__ == "__main__": 334 | 335 | print("Moin") 336 | 337 | depth = 50 338 | while afl.loop(1000): 339 | print("Still moin") 340 | sys.stdin.seek(0) 341 | run(sys.stdin.buffer.read(), depth) 342 | os._exit(1) 343 | if len(sys.argv)>1: 344 | depth = int(sys.argv[1]) 345 | #logger.info("depth: {}".format(depth)) 346 | 347 | if len(sys.argv) > 3: 348 | if sys.argv[2] == 'code': 349 | out_data = run(binascii.unhexlify(sys.argv[3]), depth) 350 | out_json = json.dumps(out_data) 351 | print(out_json) 352 | exit(0) 353 | 354 | #while True: 355 | #logger.info("read len") 356 | raw_length = sys.stdin.buffer.read(2) 357 | length = struct.unpack("=H", raw_length)[0] 358 | #logger.info(trace) 359 | if length == 0: 360 | sys.exit(0) 361 | #logger.info("read code") 362 | code = sys.stdin.buffer.read(length) 363 | 364 | 365 | out_data = run(code, depth) 366 | out_json = json.dumps(out_data).encode('ascii') 367 | #logger.info("write size {}".format(len(out_json))) 368 | sys.stdout.buffer.write(struct.pack("I", len(out_json))) 369 | #logger.info("write json") 370 | sys.stdout.buffer.write(out_json) 371 | #logger.info("write flush") 372 | sys.stdout.buffer.flush() 373 | -------------------------------------------------------------------------------- /utils/neo-python-VM.py: -------------------------------------------------------------------------------- 1 | import os 2 | import binascii 3 | from io import BytesIO, BufferedReader 4 | import sys 5 | import hashlib 6 | from logzero import logger 7 | from itertools import chain 8 | import json 9 | import struct 10 | import pathlib 11 | import glob 12 | import trace 13 | 14 | import os 15 | 16 | sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../neo-python/") 17 | 18 | from neo.VM.ExecutionEngine import ExecutionEngine 19 | from neo.SmartContract.ApplicationEngine import ApplicationEngine 20 | from neo.SmartContract import TriggerType 21 | from neo.VM import OpCode 22 | from neo.VM import VMState 23 | from neo.VM import InteropService 24 | from neo.Core.Cryptography.Crypto import Crypto 25 | from neo.Core.Fixed8 import Fixed8 26 | from neo.Core.IO.BinaryReader import BinaryReader 27 | 28 | 29 | # stream = open('code','rb') 30 | 31 | TYPE2BYTE = { 32 | InteropService.Integer: "1", 33 | InteropService.ByteArray: "2", 34 | InteropService.Array: "3", 35 | InteropService.Boolean: "4", 36 | InteropService.Map: "5", 37 | InteropService.Struct: "6", 38 | InteropService.InteropInterface: "7", 39 | } 40 | 41 | 42 | class Checksum: 43 | def __init__(self): 44 | self.reset() 45 | 46 | def reset(self): 47 | self.data = b"" 48 | 49 | def update(self, b): 50 | # self.sum.update(b) 51 | # logger.error(binascii.hexlify(b).decode('ascii')) 52 | self.data += b 53 | # print(self.result()) 54 | 55 | def result(self): 56 | sha = hashlib.sha1() 57 | # print(binascii.hexlify(self.data).decode('ascii')) 58 | sha.update(self.data) 59 | return sha.hexdigest() 60 | 61 | 62 | def getItemAsBytes(item): 63 | # logger.info(item) 64 | # logger.info(type(item)) 65 | if type(item) == InteropService.Struct or type(item) == InteropService.Array: 66 | out = b"" 67 | for value in item.GetArray(): 68 | out += getItemAsBytes(value) 69 | return out 70 | elif type(item) == InteropService.Map: 71 | out = b"" 72 | for key in item.Keys: 73 | out += getItemAsBytes(key) 74 | out += getItemAsBytes(item.GetItem(key)) 75 | # logger.info('parse map: '+out) 76 | return out 77 | elif type(item) == InteropService.InteropInterface: 78 | return b"InteropInterface" 79 | else: 80 | return item.GetByteArray() 81 | 82 | 83 | # code = bytes.fromhex('552a3694b5ee4e14e7e63ec0b79f3d8f4ecb64fb9f12e9d06351e33f5df3457d428c9221301c099a0638f0b5054f3a5ec21483863cd91cf2e746f755df55e831ed1b7bc54ed741c24d69aae564caf5344e1e855ea7dca16b37e36dd8fb2458e0a4e3e3f1a8bc674e3603481c3fbb142786f9b809c42d45b16c653d77096b1376f9e557f3a77e368a297e68aa4e665ef6d301323c6342e8eaca7a8889672fb6ad8f4353c43149820711a1d652576fceaeac6902de44d5e2a88e347ff11bd6b3e4dfc0eccddd375b75d62b078d17d2ed8016ea7658308261e604e28dafc28999f6c4cbe85c0c2ff52616f5ad2a089f28c12519887ed8f1b49b8c7c03988b775d') 84 | # code = bytes.fromhex('55556818')+b'System.Header.GetVersion'+bytes.fromhex('55'*1024) 85 | # code = bytes.fromhex('55555555c5c2') 86 | 87 | # code = bytes.fromhex(sys.argv[1]) 88 | # print(binascii.hexlify(code)) 89 | 90 | OPCODES = {} 91 | OPCODES_COVERAGE_NEWT = {} 92 | OPCODES_COVERAGE_OLDT = {} 93 | OPCODES_TYPEHASHES = {} 94 | 95 | 96 | def log(m): 97 | with open("coverage.log", "a+") as f: 98 | logger.info(m) 99 | f.write("{}\n".format(m)) 100 | 101 | 102 | def log_coverage(state): 103 | # logger.info(state) 104 | if not os.path.isfile("/tmp/trace_now"): 105 | return 106 | typehash = "{}_{}".format(state["opcode"], state["data"]) 107 | op = state["opcode"] 108 | 109 | if op not in OPCODES_COVERAGE_NEWT: 110 | OPCODES_COVERAGE_NEWT[op] = 0 111 | 112 | if op not in OPCODES_COVERAGE_OLDT: 113 | OPCODES_COVERAGE_OLDT[op] = 0 114 | 115 | if op not in OPCODES_TYPEHASHES: 116 | OPCODES_TYPEHASHES[op] = set() 117 | 118 | # logger.info('new unique typehash: {} gather coverage info'.format(typehash)) 119 | coverage = {} 120 | for fname in glob.glob("/tmp/neo-python.*"): 121 | # logger.info(fname) 122 | with open(fname) as f: 123 | lines = f.read().splitlines() 124 | if fname not in coverage: 125 | coverage[fname] = "" 126 | for i in range(0, len(lines)): 127 | line = lines[i] 128 | if not line.startswith(">>>>>>"): 129 | if line.strip() and line[5] == ":": 130 | # logger.info("{}: {}".format(i, repr(line))) 131 | coverage[fname] += "1" 132 | continue 133 | coverage[fname] += "0" 134 | 135 | if op not in OPCODES: 136 | OPCODES[op] = coverage 137 | 138 | # log("[{}] typehash for opcode {} is new. first coverage info".format(typehash, op)) 139 | else: 140 | NEW_NEW_COVERAGE = False 141 | for fname in coverage: 142 | NEW_COVERAGE = False 143 | if fname in OPCODES[op]: 144 | new_coverage = "" 145 | for a, b in zip(OPCODES[op][fname], coverage[fname]): 146 | if a == "1" or b == "1": 147 | new_coverage += "1" 148 | else: 149 | new_coverage += "0" 150 | 151 | if a == "0" and b == "1": 152 | NEW_COVERAGE = True 153 | NEW_NEW_COVERAGE = True 154 | if NEW_COVERAGE: 155 | # log("[{}] typehash lead to new coverage in opcode {}".format(typehash, op)) 156 | # log("old coverage: {}:{}".format(fname, OPCODES[op][fname])) 157 | # log("new coverage: {}:{}".format(fname, coverage[fname])) 158 | # log("sum coverage: {}:{}".format(fname, new_coverage)) 159 | OPCODES[op][fname] = new_coverage 160 | if NEW_NEW_COVERAGE: 161 | log( 162 | "OP: {} Typehashes:{} CoverageOldTypehash:{} CoverageNewTypehash:{}".format( 163 | op, 164 | len(OPCODES_TYPEHASHES[op]), 165 | OPCODES_COVERAGE_OLDT[op], 166 | OPCODES_COVERAGE_NEWT[op], 167 | ) 168 | ) 169 | 170 | if NEW_NEW_COVERAGE and typehash not in OPCODES_TYPEHASHES[op]: 171 | OPCODES_COVERAGE_NEWT[op] += 1 172 | if NEW_NEW_COVERAGE and typehash in OPCODES_TYPEHASHES[op]: 173 | OPCODES_COVERAGE_OLDT[op] += 1 174 | 175 | OPCODES_TYPEHASHES[op].add(typehash) 176 | OPCODES[op] = coverage 177 | with open("coverage.summary", "w") as f: 178 | f.write("Opcode\tTypehashes\tCoverageOldTypes\tCoverageNewTypes\n") 179 | for op in OPCODES: 180 | f.write( 181 | "{}\t{}\t{}\t{}\n".format( 182 | op, 183 | len(OPCODES_TYPEHASHES[op]), 184 | OPCODES_COVERAGE_OLDT[op], 185 | OPCODES_COVERAGE_NEWT[op], 186 | ) 187 | ) 188 | # logger.info(OPCODES[op]) 189 | # exit(0) 190 | 191 | 192 | def run(code, depth=50): 193 | # logger.info(trace) 194 | out_data = [] 195 | checksum = Checksum() 196 | # stream = BytesIO(b"\x02AB") 197 | stream = BufferedReader(BytesIO(code)) 198 | accounts = None 199 | validators = None 200 | assets = None 201 | contracts = None 202 | storages = None 203 | wb = None 204 | # service = neo.StateMachine(accounts, validators, assets, contracts, storages, wb) 205 | 206 | # engine = neo.ExecutionEngine(crypto=neo.Crypto) 207 | class FakeCrypto: 208 | def Hash160(*_): 209 | # raise Exception("Not Implemented Crypto") 210 | pass 211 | 212 | def Hash256(*_): 213 | raise Exception("Not Implemented Crypto") 214 | 215 | def VerifySignature(message, signature, public_key, unhex=True): 216 | raise Exception("Not Implemented Crypto") 217 | 218 | engine = ExecutionEngine(crypto=FakeCrypto) 219 | tx = {} 220 | """ 221 | engine = ApplicationEngine( 222 | trigger_type=TriggerType.Application, 223 | container=tx, 224 | gas=Fixed8.Zero(), 225 | testMode=False, 226 | snapshot={} 227 | ) 228 | """ 229 | 230 | # engine.LoadScript(tx.Script) 231 | 232 | # success = engine.Execute() 233 | context = engine.LoadScript(script=code) 234 | 235 | context.OpReader = BinaryReader(stream) 236 | context.__OpReader = BinaryReader(stream) 237 | 238 | engine._VMState &= ~VMState.BREAK 239 | has_returned = False 240 | i = 0 241 | while ( 242 | not has_returned 243 | and engine._VMState & VMState.HALT == 0 244 | and engine._VMState & VMState.FAULT == 0 245 | ): 246 | # try: 247 | 248 | peek = stream.peek(1)[0:1] 249 | if len(peek) == 0: 250 | break 251 | checksum.update(struct.pack("I", engine.CurrentContext.InstructionPointer)) 252 | opcode = None 253 | COVERAGE = False 254 | try: 255 | loop_data = { 256 | "opcode": None, 257 | "ret": None, 258 | "data": "", 259 | "vmstate": 0, 260 | "crash": False, 261 | "checksum": "", 262 | } 263 | opcode = engine.CurrentContext.CurrentInstruction.OpCode 264 | loop_data["opcode"] = opcode[0] 265 | checksum.update(struct.pack("I", struct.unpack("B", opcode)[0])) 266 | 267 | if opcode != b"\x66": 268 | if os.path.isfile("/tmp/trace_now"): 269 | t = trace.Trace(trace=False) 270 | t.runfunc(engine.ExecuteNext) 271 | r = t.results() 272 | r.write_results(coverdir="/tmp", show_missing=True) 273 | COVERAGE = True 274 | else: 275 | engine.ExecuteNext() 276 | # logger.info(r) 277 | else: 278 | # logger.info(engine._VMState) 279 | # logger.info(trace) 280 | 281 | for item in engine.CurrentContext._EvaluationStack.Items[-2:]: 282 | if type(item) == InteropService.Array: 283 | arr = item.GetArray() 284 | # logger.error(arr) 285 | if len(arr) == 2: 286 | ret_id, val = item.GetArray() 287 | if type(ret_id) == neo.ByteArray: 288 | b = getItemAsBytes(val) 289 | out = ( 290 | b"ret," 291 | + struct.pack("II", ret_id.GetBigInteger(), len(b)) 292 | + b 293 | ) 294 | loop_data["ret"] = (ret_id.GetBigInteger(), b) 295 | # out_data.append(loop_data) 296 | has_returned = True 297 | 298 | loop_data["vmstate"] = engine._VMState & 0x3 299 | stack_types = "" 300 | 301 | for item in engine.CurrentContext._EvaluationStack.Items[::-1]: 302 | # if trace: 303 | # logger.info(binascii.hexlify(getItemAsBytes(item)).decode('ascii')) 304 | if len(stack_types) < 2: 305 | stack_types += TYPE2BYTE[type(item)] 306 | checksum.update(getItemAsBytes(item)) 307 | 308 | for item in engine.CurrentContext._AltStack.Items[::-1]: 309 | # if trace: 310 | # logger.info(binascii.hexlify(getItemAsBytes(item)).decode('ascii')) 311 | if len(stack_types) < 2: 312 | stack_types += TYPE2BYTE[type(item)] 313 | checksum.update(getItemAsBytes(item)) 314 | 315 | loop_data["data"] = stack_types 316 | loop_data["checksum"] = checksum.result() 317 | 318 | if engine._VMState & VMState.FAULT != 0: 319 | # logger.error("FAULT") 320 | loop_data["crash"] = True 321 | if COVERAGE: 322 | log_coverage(loop_data) 323 | out_data.append(loop_data) 324 | break 325 | 326 | if COVERAGE: 327 | log_coverage(loop_data) 328 | out_data.append(loop_data) 329 | except Exception as e: 330 | # logger.exception(e) 331 | loop_data["crash"] = True 332 | loop_data["checksum"] = checksum.result() 333 | if COVERAGE: 334 | log_coverage(loop_data) 335 | out_data.append(loop_data) 336 | break 337 | 338 | i += 1 339 | 340 | # logger.info(i) 341 | if i > depth: 342 | # logger.info('exec timeout (code length: {})'.format(len(code))) 343 | # logger.info(out_data) 344 | break 345 | 346 | return out_data 347 | # print("DONE") 348 | 349 | 350 | # code = bytes.fromhex('552a3694b5ee4e14e7e63ec0b79f3d8f4ecb64fb9f12e9d06351e33f5df3457d428c9221301c099a0638f0b5054f3a5ec21483863cd91cf2e746f755df55e831ed1b7bc54ed741c24d69aae564caf5344e1e855ea7dca16b37e36dd8fb2458e0a4e3e3f1a8bc674e3603481c3fbb142786f9b809c42d45b16c653d77096b1376f9e557f3a77e368a297e68aa4e665ef6d301323c6342e8eaca7a8889672fb6ad8f4353c43149820711a1d652576fceaeac6902de44d5e2a88e347ff11bd6b3e4dfc0eccddd375b75d62b078d17d2ed8016ea7658308261e604e28dafc28999f6c4cbe85c0c2ff52616f5ad2a089f28c12519887ed8f1b49b8c7c03988b775d') 351 | # code = bytes.fromhex('55556829')+b'System.ExecutionEngine.GetScriptContainer'+bytes.fromhex('55'*1) 352 | # code = binascii.unhexlify('55545352c5c2') 353 | # out_data = run(code, False) 354 | 355 | # print(out_data) 356 | 357 | 358 | if __name__ == "__main__": 359 | 360 | depth = 50 361 | if len(sys.argv) > 1: 362 | depth = int(sys.argv[1]) 363 | # logger.info("depth: {}".format(depth)) 364 | 365 | if len(sys.argv) > 3: 366 | if sys.argv[2] == "code": 367 | out_data = run(binascii.unhexlify(sys.argv[3]), depth) 368 | out_json = json.dumps(out_data) 369 | print(out_json) 370 | exit(0) 371 | 372 | while True: 373 | # logger.info("read len") 374 | raw_length = sys.stdin.buffer.read(2) 375 | length = struct.unpack("=H", raw_length)[0] 376 | # logger.info(trace) 377 | if length == 0: 378 | sys.exit(0) 379 | # logger.info("read code") 380 | code = sys.stdin.buffer.read(length) 381 | 382 | out_data = run(code, depth) 383 | out_json = json.dumps(out_data).encode("ascii") 384 | # logger.info("write size {}".format(len(out_json))) 385 | sys.stdout.buffer.write(struct.pack("I", len(out_json))) 386 | # logger.info("write json") 387 | sys.stdout.buffer.write(out_json) 388 | # logger.info("write flush") 389 | sys.stdout.buffer.flush() 390 | -------------------------------------------------------------------------------- /utils/parse_results.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Parses the results of a diff run for eval/postprocessing 4 | 5 | import ast 6 | import struct 7 | 8 | 9 | def parse(content: bytes) -> bytes: 10 | content = content.strip() 11 | csplit = content.split(b",", 1) 12 | current = csplit[0] 13 | if current.startswith(b"("): 14 | current = current[1:] 15 | if not len(current): 16 | return b"" 17 | 18 | elif current.startswith(b"ByteArray"): 19 | vals = csplit[1].split(b",", 1) 20 | val = vals[0] 21 | if val.endswith(b")"): 22 | val = val[:-1] 23 | if not len(val): 24 | ret = b"''" 25 | else: 26 | ret = b"0x" + binascii.hexlify(binascii.unhexlify(val)[::-1]) 27 | if len(vals) > 1: 28 | ret += b", " + parse(vals[1]) 29 | return ret 30 | 31 | elif current.startswith(b"Integer"): 32 | vals = csplit[1].split(b",", 1) 33 | val = vals[0] 34 | ret = b"" 35 | if val.endswith(b")"): 36 | val = val[:-1] 37 | if not len(val): 38 | ret += b"0" 39 | else: 40 | ret += val 41 | if len(vals) > 1: 42 | ret += b", " + parse(vals[1]) 43 | 44 | return ret + b"" 45 | 46 | elif current.startswith(b"Array"): 47 | ret = b"[" 48 | 49 | arr = csplit[1].split(b"[", 1)[1].rsplit(b"]", 1) 50 | ret += parse(arr[0]) 51 | if csplit[1] == b"[]": 52 | return b"[]" 53 | 54 | if len(arr) > 1: 55 | ret += b", " + parse(arr[1]) 56 | return ret + b"]" 57 | 58 | elif current.startswith(b"Map"): 59 | ret = b"{}" 60 | vals = csplit[1].split(b",", 1) 61 | if len(vals) > 1: 62 | ret += b", " + parse(vals[1]) 63 | return ret 64 | 65 | elif current.startswith(b"Boolean"): 66 | 67 | vals = csplit[1].split(b",", 1) 68 | if len(vals) > 1: 69 | ret += b", " + parse(vals[1]) 70 | return vals[0] 71 | 72 | raise Exception(f"Not implemented: {content}") 73 | 74 | 75 | def parse_initial(content: str): 76 | content = content.split(";", 1) 77 | ret = b"(" 78 | part1 = ast.literal_eval(content[0]) 79 | ret += parse(part1) 80 | ret += b"," 81 | part2 = ast.literal_eval(content[1]) 82 | ret += parse(part2) 83 | ret += b")" 84 | return ret 85 | 86 | 87 | def semantic_diff(python, neo): 88 | python = python.strip() 89 | neo = neo.strip() 90 | BRANCH = False 91 | VALUE = False 92 | if python and python != "None" and ";" in neo and neo.startswith("b'ByteArray,"): 93 | # print("{} vs {}".format(python, neo)) 94 | content = neo.split(";", 1) 95 | print(neo) 96 | part1 = ast.literal_eval(content[0]) 97 | branch_neo = int(parse(part1), 16) 98 | part2 = ast.literal_eval(content[1]) 99 | ret_neo = eval(parse(part2)) 100 | 101 | branch_py, ret_py = eval(python) 102 | 103 | if branch_py != branch_neo: 104 | # print(branch_py, branch_neo) 105 | BRANCH = True 106 | else: 107 | if ret_py != ret_neo: 108 | 109 | VALUE = True 110 | if type(ret_py) == str and len(ret_py) == 1 and ord(ret_py) == ret_neo: 111 | VALUE = False 112 | elif type(ret_py) == list and type(ret_neo) == list: 113 | if len(ret_py) == len(ret_neo): 114 | VALUE = False 115 | for a, b in zip(ret_py, ret_neo): 116 | if a == b: 117 | continue 118 | elif a == False and b == "": 119 | continue 120 | elif a == None and b == "": 121 | continue 122 | elif a == True and b == 1: 123 | continue 124 | VALUE = True 125 | elif type(ret_py) == bool and type(ret_neo) == int and ret_neo == 0: 126 | VALUE = False 127 | elif type(ret_py) == bool and type(ret_neo) == str and not ret_neo: 128 | VALUE = False 129 | elif ret_py == None and type(ret_neo) == str and not ret_neo: 130 | VALUE = False 131 | elif type(ret_py) == str and type(ret_neo) == int: 132 | h = hex(ret_neo) 133 | if h.endswith("L"): 134 | h = h[:-1] 135 | if binascii.unhexlify(hex(ret_neo)[2:])[::-1] == ret_py.encode( 136 | "ascii" 137 | ): 138 | VALUE = False 139 | elif type(ret_py) == int and ret_py < 0 and type(ret_neo) == int: 140 | 141 | if len(hex(ret_neo)) == 6: 142 | i = struct.unpack("h", struct.pack("H", ret_neo))[0] 143 | if i == ret_py: 144 | VALUE = False 145 | elif len(hex(ret_neo)) == 4: 146 | i = struct.unpack("b", struct.pack("B", ret_neo))[0] 147 | if i == ret_py: 148 | VALUE = False 149 | elif len(hex(ret_neo)) == 10: 150 | i = struct.unpack("i", struct.pack("I", ret_neo))[0] 151 | if i == ret_py: 152 | VALUE = False 153 | 154 | if VALUE: 155 | exit(0) 156 | return BRANCH, VALUE 157 | 158 | return BRANCH, VALUE 159 | 160 | 161 | from logzero import logger 162 | import binascii 163 | 164 | lines = None 165 | with open("ndpygen_runs.csv", "r") as f: 166 | lines = f.readlines() 167 | 168 | vmdiffs = {} 169 | vmdiff_count = 0 170 | semantic_branch_python27_csharp = 0 171 | semantic_branch_python37_csharp = 0 172 | semantic_branch_python27_python = 0 173 | semantic_branch_python37_python = 0 174 | 175 | semantic_value_python27_csharp = 0 176 | semantic_value_python37_csharp = 0 177 | semantic_value_python27_python = 0 178 | semantic_value_python37_python = 0 179 | ignore = [] 180 | for line in lines: 181 | items = line.split("\t") 182 | # print(items) 183 | if len(items) != 32: 184 | logger.error("{}".format(items)) 185 | exit(1) 186 | if items[17] == "diff": 187 | vmdiff_count += 1 188 | if items[18] not in vmdiffs: 189 | vmdiffs[items[18]] = 0 190 | vmdiffs[items[18]] += 1 191 | # print(items[17], items[18]) 192 | if items[27] == "semanticdiff": 193 | 194 | python27 = items[28] 195 | python37 = items[29] 196 | csharp = items[30] 197 | python = items[31] 198 | 199 | try: 200 | if semantic_diff(python27, csharp)[0]: 201 | semantic_branch_python27_csharp += 1 202 | if semantic_diff(python37, csharp)[0]: 203 | semantic_branch_python37_csharp += 1 204 | if semantic_diff(python27, python)[0]: 205 | semantic_branch_python27_python += 1 206 | if semantic_diff(python37, python)[0]: 207 | semantic_branch_python37_python += 1 208 | 209 | if semantic_diff(python27, csharp)[1]: 210 | semantic_value_python27_csharp += 1 211 | if semantic_diff(python37, csharp)[1]: 212 | semantic_value_python37_csharp += 1 213 | if semantic_diff(python27, python)[1]: 214 | semantic_value_python27_python += 1 215 | if semantic_diff(python37, python)[1]: 216 | semantic_value_python37_python += 1 217 | except: 218 | ignore.append(line) 219 | 220 | vmdiffs = {} 221 | vmdiff_count = 0 222 | semantic_branch_python27_csharp = 0 223 | semantic_branch_python37_csharp = 0 224 | semantic_branch_python27_python = 0 225 | semantic_branch_python37_python = 0 226 | 227 | semantic_value_python27_csharp = 0 228 | semantic_value_python37_csharp = 0 229 | semantic_value_python27_python = 0 230 | semantic_value_python37_python = 0 231 | ignored = 0 232 | for line in lines[:22000]: 233 | if line in ignore: 234 | ignored += 1 235 | continue 236 | items = line.split("\t") 237 | # print(items) 238 | if len(items) != 32: 239 | logger.error("{}".format(items)) 240 | exit(1) 241 | if items[17] == "diff": 242 | vmdiff_count += 1 243 | if items[18] not in vmdiffs: 244 | vmdiffs[items[18]] = 0 245 | vmdiffs[items[18]] += 1 246 | # print(items[17], items[18]) 247 | if items[27] == "semanticdiff": 248 | 249 | python27 = items[28] 250 | python37 = items[29] 251 | csharp = items[30] 252 | python = items[31] 253 | 254 | if semantic_diff(python27, csharp)[0]: 255 | semantic_branch_python27_csharp += 1 256 | if semantic_diff(python37, csharp)[0]: 257 | semantic_branch_python37_csharp += 1 258 | if semantic_diff(python27, python)[0]: 259 | semantic_branch_python27_python += 1 260 | if semantic_diff(python37, python)[0]: 261 | semantic_branch_python37_python += 1 262 | 263 | if semantic_diff(python27, csharp)[1]: 264 | semantic_value_python27_csharp += 1 265 | if semantic_diff(python37, csharp)[1]: 266 | semantic_value_python37_csharp += 1 267 | if semantic_diff(python27, python)[1]: 268 | semantic_value_python27_python += 1 269 | if semantic_diff(python37, python)[1]: 270 | semantic_value_python37_python += 1 271 | 272 | 273 | print("Amount of fuzzer rounds: {}".format(len(lines))) 274 | print("ignored: {}".format(ignored)) 275 | print() 276 | print( 277 | "VM Discrepancies: VM state is compared for each instruction, if the values on the stack diverge, we found a discrepancy" 278 | ) 279 | print(" - Total discrepancies: {}".format(vmdiff_count)) 280 | print(" - Different Opcodes leading to VM Discrepancies: {}".format(len(vmdiffs))) 281 | for op in vmdiffs: 282 | print(" + Differentials found with Opcode {}: {}".format(op[2:-1], vmdiffs[op])) 283 | print() 284 | print( 285 | "Semantic Differences: The returned value from the NeoVM execution is compared to CPython return values" 286 | ) 287 | 288 | print("Different Branch") 289 | print("Python2.7 vs. CSharp NeoVM: {}".format(semantic_branch_python27_csharp)) 290 | print("Python2.7 vs. Python NeoVM: {}".format(semantic_branch_python27_python)) 291 | print("Python3.7 vs. CSharp NeoVM: {}".format(semantic_branch_python37_csharp)) 292 | print("Python3.7 vs. Python NeoVM: {}".format(semantic_branch_python37_python)) 293 | print() 294 | print("Same Branch but different Values") 295 | print("Python2.7 vs. CSharp NeoVM: {}".format(semantic_value_python27_csharp)) 296 | print("Python2.7 vs. Python NeoVM: {}".format(semantic_value_python27_python)) 297 | print("Python3.7 vs. CSharp NeoVM: {}".format(semantic_value_python37_csharp)) 298 | print("Python3.7 vs. Python NeoVM: {}".format(semantic_value_python37_python)) 299 | --------------------------------------------------------------------------------