├── README.md ├── in └── yeelight.txt ├── record └── yeelight-restore.txt ├── out └── ProbeRecord.txt ├── ProbeRecord.txt ├── Seed.py ├── yeelight-record.txt ├── SnR.py └── Snipuzz.py /README.md: -------------------------------------------------------------------------------- 1 | # Snipuzz-py -------------------------------------------------------------------------------- /in/yeelight.txt: -------------------------------------------------------------------------------- 1 | ==================== 1 ========================== 2 | 3 | IP:192.168.0.103 4 | Port:55443 5 | Content:{"id": 0, "method": "get_prop", "params": ["power"]} 6 | 7 | ==================== 2 ========================== 8 | 9 | IP:192.168.0.103 10 | Port:55443 11 | Content:{"id": 1, "method": "set_power", "params": ["on"]} 12 | -------------------------------------------------------------------------------- /record/yeelight-restore.txt: -------------------------------------------------------------------------------- 1 | ==================== 1 ========================== 2 | 3 | IP:192.168.0.103 4 | Port:55443 5 | Content:{"id": 1, "method": "set_power", "params": ["off"]} 6 | 7 | ==================== 2 ========================== 8 | 9 | IP:192.168.0.103 10 | Port:55443 11 | Content:{"id": 2, "method": "set_name", "params": ["Test"]} 12 | 13 | ==================== 3 ========================== 14 | 15 | IP:192.168.0.103 16 | Port:55443 17 | Content:{ id: 3, method: "set_default", params: [ ] } -------------------------------------------------------------------------------- /out/ProbeRecord.txt: -------------------------------------------------------------------------------- 1 | ========Seed 0======== 2 | Message Index-0 3 | IP:192.168.0.103 4 | Port:55443 5 | Content:{"id": 0, "method": "get_prop", "params": ["power"]} 6 | 7 | Original Response 8 | {"id":0,"result":["off"]} 9 | Probe Result: 10 | PI 11 | 1 1 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 1 0 0 1 2 2 2 2 2 2 2 3 1 1 3 1 1 1 1 1 1 1 1 3 3 1 1 3 3 3 3 3 1 1 1 12 | PR and PS 13 | (0) {"id":0,"result":["off"]} 14 | 100.0 15 | (1) {"id":0, "error":{"code":-1, "message":"invalid command"}} 16 | 100.0 17 | (2) {"id":0, "error":{"code":-1, "message":"method not supported"}} 18 | 100.0 19 | (3) {"id":0, "error":{"code":-1, "message":"client quota exceeded"}} 20 | 100.0 21 | Message Index-1 22 | IP:192.168.0.103 23 | Port:55443 24 | Content:{"id": 1, "method": "set_power", "params": ["on"]} 25 | 26 | Original Response 27 | {"method":"props","params":{"bright_with_zero":50,"power":"on"}} 28 | Probe Result: 29 | PI 30 | 1 1 1 1 1 1 2 1 3 2 1 3 3 3 3 3 3 1 2 2 1 4 4 0 0 0 0 0 0 0 1 3 0 1 3 3 3 3 3 3 1 0 0 1 1 0 0 1 1 1 31 | PR and PS 32 | (0) {"id":1, "error":{"code":-1, "message":"client quota exceeded"}} 33 | 100.0 34 | (1) {"id":0, "error":{"code":-1, "message":"invalid command"}} 35 | 100.0 36 | (2) {"method":"props","params":{"bright_with_zero":50,"power":"on"}} 37 | 100.0 38 | (3) {"id":1, "error":{"code":-1, "message":"invalid command"}} 39 | 100.0 40 | (4) {"id":1, "error":{"code":-1, "message":"method not supported"}} 41 | 100.0 42 | 43 | 44 | -------------------------------------------------------------------------------- /ProbeRecord.txt: -------------------------------------------------------------------------------- 1 | ========Seed 0======== 2 | Message Index-0 3 | IP:192.168.0.103 4 | Port:55443 5 | Content:{"id": 0, "method": "get_prop", "params": ["power"]} 6 | 7 | Original Response 8 | {"id":0,"result":["off"]} 9 | 10 | 11 | Probe Result: 12 | PI 13 | 1 1 1 1 1 0 0 2 0 0 1 3 3 3 3 3 3 1 0 0 1 4 4 4 4 4 4 4 4 1 0 0 1 3 3 3 3 3 3 1 0 0 1 1 5 5 5 5 5 1 1 1 14 | PR and PS 15 | (0) {"id":0,"result":["off"]} 16 | 17 | 100.0 18 | (1) {"id":(null), "error":{"code":-1, "message":"invalid command"}} 19 | 20 | 100.0 21 | (2) {"id":method, "error":{"code":-1, "message":"invalid command"}} 22 | 23 | 100.0 24 | (3) {"id":0, "error":{"code":-1, "message":"invalid command"}} 25 | 26 | 100.0 27 | (4) {"id":0, "error":{"code":-1, "message":"client quota exceeded"}} 28 | 29 | 100.0 30 | (5) {"id":0, "result":[""]} 31 | 32 | 100.0 33 | Message Index-1 34 | IP:192.168.0.103 35 | Port:55443 36 | Content:{"id": 1, "method": "set_power", "params": ["on"]} 37 | 38 | Original Response 39 | {"id":1, "result":["ok"]} 40 | 41 | 42 | Probe Result: 43 | PI 44 | 1 1 1 1 1 0 0 2 0 0 1 3 3 3 3 3 3 1 0 0 1 0 0 0 0 0 0 0 0 0 1 4 4 1 3 3 3 3 3 3 1 4 4 1 1 4 4 1 1 1 45 | PR and PS 46 | (0) {"id":1, "error":{"code":-1, "message":"client quota exceeded"}} 47 | 48 | 100.0 49 | (1) {"id":(null), "error":{"code":-1, "message":"invalid command"}} 50 | 51 | 100.0 52 | (2) {"id":method, "error":{"code":-1, "message":"invalid command"}} 53 | 54 | 100.0 55 | (3) {"id":1, "error":{"code":-1, "message":"invalid command"}} 56 | 57 | 100.0 58 | (4) {"id":1, "result":["ok"]} 59 | 60 | 100.0 61 | 62 | 63 | -------------------------------------------------------------------------------- /Seed.py: -------------------------------------------------------------------------------- 1 | ### 2 | # 'Seed' is used to store the seeds for fuzzing process 3 | # 'Seed' attrs - [ M : Message List (Class 'Message') - to store the list of messages; 4 | # R : Response List (String) - to sotre the list of response messages corresponding to the message list (M) one-to-one by index] 5 | ### 6 | class Seed: 7 | M = [] # Message List - message type 8 | R = [] # Response List - string 9 | 10 | PR = [] # Probe message response pool - 2d list 11 | PS = [] # Probe message response similarity scores - 2d list 12 | PI = [] 13 | 14 | isMutated = False 15 | 16 | ClusterList = [] 17 | 18 | Snippet = [] 19 | 20 | 21 | def __init__(self) -> None: 22 | self.M = [] 23 | self.R = [] 24 | self.PR = [] 25 | self.PS = [] 26 | self.PI = [] 27 | self.isMutated = False 28 | self.ClusterList = [] 29 | self.Snippet = [] 30 | 31 | 32 | 33 | def append(self, message): 34 | self.M.append(message) 35 | 36 | def response(self, response): 37 | self.R.append(response) 38 | 39 | def display(self): 40 | for i in range(0, len(self.M)): 41 | print("Message index: ", i + 1) 42 | for header in self.M[i].headers: 43 | print(header, " : ", self.M[i].raw[header]) 44 | print('Response:') 45 | print(self.R[i]) 46 | if self.PR and self.PS and self.PI: 47 | print('Probe Result:') 48 | print('PI') 49 | print(self.PI[i]) 50 | print('PR and PS') 51 | for n in range(len(self.PR[i])): 52 | print("(" + str(n) + ") " + self.PR[i][n]) 53 | print(self.PS[i][n]) 54 | 55 | ### 56 | 57 | 58 | # 'Message' is used to store the message information 59 | # 'Seed' attrs - [ headers : Header List (String List) - to store all the headers in the message; 60 | # raw : Header and corresponding content (Dictionary - { Header : Content } ) - to sotre the list of response messages corresponding to the message list (M) one-to-one by index] 61 | ### 62 | class Message: 63 | headers = [] # Header List 64 | raw = {} # Header and corresponding content 65 | 66 | def __init__(self) -> None: 67 | self.headers = [] 68 | self.raw = {} 69 | 70 | def append(self, line) -> None: 71 | if ":" in line: 72 | sp = line.split(":") 73 | if sp[0] in self.headers: 74 | print("Error. Message headers '", sp[0], "' is duplicated.") 75 | else: 76 | self.headers.append(sp[0]) 77 | self.raw[sp[0]] = line[(line.index(':') + 1):] 78 | -------------------------------------------------------------------------------- /yeelight-record.txt: -------------------------------------------------------------------------------- 1 | ==================== 1 ========================== 2 | 3 | IP:192.168.0.104 4 | Port:55443 5 | Content:{"id": 0, "method": "get_prop", "params": ["power"]} 6 | 7 | ==================== 2 ========================== 8 | 9 | IP:192.168.0.104 10 | Port:55443 11 | Content:{"id": 1, "method": "set_power", "params": ["on"]} 12 | 13 | ==================== 3 ========================== 14 | 15 | IP:192.168.0.104 16 | Port:55443 17 | Content:{"id": 2, "method": "set_name", "params": ["Test"]} 18 | 19 | ==================== 4 ========================== 20 | 21 | IP:192.168.0.104 22 | Port:55443 23 | Content: {"id":3,"method":"get_prop","params":["power", "not_exist", "bright"]} 24 | 25 | ==================== 5 ========================== 26 | 27 | IP:192.168.0.104 28 | Port:55443 29 | Content: {"id":4,"method":"set_ct_abx","params":[3500, "smooth", 500]} 30 | 31 | ==================== 6 ========================== 32 | 33 | IP:192.168.0.104 34 | Port:55443 35 | Content:{"id":1,"method":"set_rgb","params":[255, "smooth", 500]} 36 | 37 | ==================== 7 ========================== 38 | 39 | IP:192.168.0.104 40 | Port:55443 41 | Content:{"id":1,"method":"set_hsv","params":[255, 45, "smooth", 500]} 42 | 43 | ==================== 8 ========================== 44 | 45 | IP:192.168.0.104 46 | Port:55443 47 | Content:{"id":1,"method":"set_bright","params":[50, "smooth", 500]} 48 | 49 | ==================== 9 ========================== 50 | 51 | IP:192.168.0.104 52 | Port:55443 53 | Content:{"id":1,"method":"set_default","params":[]} 54 | 55 | ==================== 10 ========================== 56 | 57 | IP:192.168.0.104 58 | Port:55443 59 | Content:{"id":1,"method":"start_cf","params":[ 4, 2, "1000, 2, 2700, 100, 500, 1, 255, 10, 5000, 7, 0,0, 500, 2, 5000, 1"]} 60 | 61 | ==================== 11 ========================== 62 | 63 | IP:192.168.0.104 64 | Port:55443 65 | Content:{"id":1,"method":"stop_cf","params":[]} 66 | 67 | ==================== 12 ========================== 68 | 69 | IP:192.168.0.104 70 | Port:55443 71 | Content:{"id":1, "method":"set_scene","params":["cf",0,0,"500,1,255,100,1000,1,16776960,70"]} 72 | 73 | ==================== 13 ========================== 74 | 75 | IP:192.168.0.104 76 | Port:55443 77 | Content:{"id":1,"method":"cron_add","params":[0, 15]} 78 | 79 | ==================== 14 ========================== 80 | 81 | IP:192.168.0.104 82 | Port:55443 83 | Content:{"id":1,"method":"cron_get","params":[0]} 84 | 85 | 86 | ==================== 15 ========================== 87 | 88 | IP:192.168.0.104 89 | Port:55443 90 | Content:{"id":1,"method":"cron_del","params":[0]} 91 | 92 | ==================== 16 ========================== 93 | 94 | IP:192.168.0.104 95 | Port:55443 96 | Content:{"id":1,"method":"set_adjust","params":["increase", "ct"]} 97 | 98 | ==================== 17 ========================== 99 | 100 | IP:192.168.0.104 101 | Port:55443 102 | Content:{"id":1,"method":"set_adjust","params":["increase", "ct"]} 103 | 104 | ==================== 18 ========================== 105 | 106 | IP:192.168.0.104 107 | Port:55443 108 | Content:{"id":1,"method":"adjust_bright","params":[-20, 500]} 109 | 110 | ==================== 19 ========================== 111 | 112 | IP:192.168.0.104 113 | Port:55443 114 | Content:{"id":1,"method":"adjust_color","params":[20, 500]} -------------------------------------------------------------------------------- /SnR.py: -------------------------------------------------------------------------------- 1 | import os 2 | import socket 3 | 4 | 5 | # Calculate the edit distance of two string 6 | def EditDistanceRecursive(str1, str2): 7 | edit = [[i + j for j in range(len(str2) + 1)] for i in range(len(str1) + 1)] 8 | for i in range(1, len(str1) + 1): 9 | for j in range(1, len(str2) + 1): 10 | if str1[i - 1] == str2[j - 1]: 11 | d = 0 12 | else: 13 | d = 1 14 | edit[i][j] = min(edit[i - 1][j] + 1, edit[i][j - 1] + 1, edit[i - 1][j - 1] + d) 15 | return edit[len(str1)][len(str2)] 16 | 17 | 18 | # Calculate the similarity score of two string 19 | def SimilarityScore(str1, str2): 20 | ED = EditDistanceRecursive(str1, str2) 21 | return round((1 - (ED / max(len(str1), len(str2)))) * 100, 2) 22 | 23 | 24 | class Messenger: 25 | SocketSender = socket.socket() 26 | restore = [] 27 | 28 | def __init__(self,restoreSeed) -> None: 29 | self.SocketSender = None 30 | self.restore = restoreSeed 31 | 32 | def DryRunSend(self,squence): # send a sequence of messages (work for DryRun) 33 | #squence.display() # Test only 34 | for message in squence.M: 35 | response = self.sendMessage(message) 36 | if response == "#error": 37 | return True 38 | squence.R.append(response) 39 | for message in self.restore.M: 40 | response = self.sendMessage(message) 41 | if response == "#error": 42 | return True 43 | return squence 44 | 45 | def ProbeSend(self,squence,index): # send a sequence of messages (work for Probe) 46 | for i in range(len(squence.M)): 47 | response = self.sendMessage(squence.M[i]) 48 | if response == "#error": 49 | return "#error" 50 | elif response == '#crash': 51 | return '#crash' 52 | if i == index: 53 | res = response 54 | for i in range(len(self.restore.M)): 55 | resotreResponse = self.sendMessage(self.restore.M[i]) 56 | if resotreResponse == "#error": 57 | return "#error" 58 | elif response == '#crash': 59 | return '#crash' 60 | return res 61 | 62 | def SnippetMutationSend(self,squence,index): # send a sequence of messages (work for SnippetMutate) 63 | for i in range(len(squence.M)): 64 | response = self.sendMessage(squence.M[i]) 65 | if response == "#error": 66 | return "#error" 67 | elif response == '#crash': 68 | return '#crash' 69 | if i == index: 70 | res = response 71 | 72 | for i in range(len(self.restore.M)): 73 | resotreResponse = self.sendMessage(self.restore.M[i]) 74 | if resotreResponse == "#error": 75 | return "#error" 76 | elif response == '#crash': 77 | return '#crash' 78 | 79 | pool = squence.PR[index] 80 | scores = squence.PS[index] 81 | #print("+++++") 82 | #print(res.strip()) 83 | for i in range(len(pool)): 84 | c = SimilarityScore(pool[i].strip(), res.strip()) 85 | #print(pool[i].strip()) 86 | #print(str(c)+" "+str(scores[i])) 87 | if c >= scores[i]: 88 | return "" 89 | return "#interesting-"+str(index) 90 | 91 | def sendMessage(self,message,time=0): # send a message 92 | if "IP" in message.headers and "Port" in message.headers: # socket 93 | ip = message.raw["IP"].strip() 94 | port = int(message.raw["Port"]) 95 | content = message.raw["Content"]+"\r\n" 96 | 97 | try: 98 | self.SocketSender = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 99 | #self.SocketSender.settimeout(5) 100 | self.SocketSender.connect((ip,port)) 101 | #print("Content:"+content) # Test only 102 | self.SocketSender.send(content.encode('utf8')) 103 | response = self.SocketSender.recv(1024).decode('utf8') 104 | except socket.timeout as e: 105 | if time < 3: # repeat 106 | self.sendMessage(self,message,time+1) 107 | else: 108 | return "#crash" 109 | 110 | 111 | #print("Response:"+response) # Test only 112 | return response 113 | else: 114 | print("Error : IP and Port of target should be included in input files") 115 | return "#error" # error 116 | 117 | 118 | -------------------------------------------------------------------------------- /Snipuzz.py: -------------------------------------------------------------------------------- 1 | import getopt 2 | import os 3 | import sys 4 | import time 5 | import random 6 | import time 7 | 8 | import pandas as pd 9 | from scipy.cluster import hierarchy 10 | 11 | sys.path.append(r'..') 12 | 13 | from SnR import Messenger 14 | from Seed import Message, Seed 15 | 16 | 17 | # Golbal var 18 | queue = [] 19 | restoreSeed = '' 20 | outputfold = '' 21 | 22 | 23 | 24 | # read the input file and store it as seed 25 | def readInputFile(file): 26 | s = Seed() 27 | lines = [] 28 | with open(file, 'r') as f: 29 | lines = f.read().split("\n") 30 | for i in range(0, len(lines)): 31 | # print(lines[i]) 32 | if "========" in lines[i]: 33 | mes = Message() 34 | for j in range(i + 1, len(lines)): 35 | if "========" in lines[j]: 36 | i = j 37 | break 38 | if ":" in lines[j]: 39 | mes.append(lines[j]) 40 | s.append(mes) 41 | # s.display() 42 | return s 43 | 44 | 45 | # read the input fold and store them as seeds 46 | def readInputFold(fold): 47 | seeds = [] 48 | files = os.listdir(fold) 49 | for file in files: 50 | print("Loading file: ", os.path.join(fold, file)) 51 | seeds.append(readInputFile(os.path.join(fold, file))) 52 | return seeds 53 | 54 | 55 | # Write the probe result that has been run into the output 56 | def writeRecord(queue, fold): 57 | with open(os.path.join(fold, 'ProbeRecord.txt'), 'w') as f: 58 | for i in range(len(queue)): 59 | f.writelines("========Seed " + str(i) + "========\n") 60 | for j in range(len(queue[i].M)): 61 | 62 | f.writelines("Message Index-" + str(j) + "\n") # write the message information 63 | for header in queue[i].M[j].headers: 64 | f.writelines(header + ":" + queue[i].M[j].raw[header] + '\n') 65 | f.writelines("\n") 66 | 67 | f.writelines('Original Response' + "\n") # write the original response 68 | f.writelines(queue[i].R[j]) 69 | 70 | f.writelines('Probe Result:' + "\n") # write the results of probe 71 | f.writelines('PI' + "\n") # PI 72 | for n in queue[i].PI[j]: 73 | f.write(str(n) + " ") 74 | f.writelines("\n") 75 | f.writelines('PR and PS' + "\n") 76 | for n in range(len(queue[i].PR[j])): 77 | f.writelines("(" + str(n) + ") " + queue[i].PR[j][n]) 78 | f.writelines(str(queue[i].PS[j][n]) + "\n") 79 | f.writelines("\n") 80 | f.writelines("\n") 81 | return 0 82 | 83 | 84 | # Read the probe results from the record, thus skip the probe process and directly start the mutation test. 85 | def readRecordFile(file): 86 | queue = [] 87 | with open(os.path.join(file), 'r') as f: 88 | lines = f.readlines() 89 | i = 0 90 | while i <= len(lines): 91 | if lines[i].startswith("========Seed"): 92 | seedStart = i + 1 93 | seedEnd = len(lines) 94 | for j in range(i + 1, len(lines)): 95 | if lines[i].startswith("========Seed"): 96 | seedEnd = j 97 | seed = Seed() 98 | index = seedStart 99 | 100 | while index <= seedEnd: 101 | 102 | if lines[index].startswith('Message Index'): 103 | message = Message() 104 | responseStart = seedEnd 105 | for j in range(index, seedEnd): 106 | if lines[j].startswith('Original Response'): 107 | responseStart = j 108 | break 109 | for line in lines[index + 1:responseStart - 1]: 110 | message.append(line) 111 | seed.M.append(message) 112 | index = responseStart 113 | 114 | if lines[index].startswith('Original Response'): 115 | index = index + 1 116 | seed.R.append(lines[index]) 117 | 118 | if lines[index].startswith('PI'): 119 | index = index + 1 120 | PIstr = lines[index] 121 | PI = [] 122 | for n in PIstr.strip().split(' '): 123 | PI.append(int(n)) 124 | seed.PI.append(PI) 125 | 126 | if lines[index].startswith('PR and PS'): 127 | index = index + 1 128 | ends = seedEnd 129 | PR = [] 130 | PS = [] 131 | for j in range(index, seedEnd): 132 | if lines[j].startswith('Message Index'): 133 | ends = j 134 | break 135 | for j in range(index, ends): 136 | if lines[j].startswith("("): 137 | PR.append(lines[j][3:]) 138 | elif lines[j][0].isdigit(): 139 | PS.append(float(lines[j].strip())) 140 | seed.PR.append(PR) 141 | seed.PS.append(PS) 142 | 143 | index = index + 1 144 | 145 | i = index 146 | queue.append(seed) 147 | 148 | i = i + 1 149 | return queue 150 | 151 | 152 | # Try to use the input given for a complete communication. 153 | # The func is used to test whether the input meets the requirements or whether there are other problems 154 | def dryRun(queue): 155 | global restoreSeed 156 | m = Messenger(restoreSeed) 157 | for i in range(0, len(queue)): 158 | seed = m.DryRunSend(queue[i]) 159 | queue[i] = seed 160 | return False 161 | 162 | 163 | # Calculate the edit distance of two string 164 | def EditDistanceRecursive(str1, str2): 165 | edit = [[i + j for j in range(len(str2) + 1)] for i in range(len(str1) + 1)] 166 | for i in range(1, len(str1) + 1): 167 | for j in range(1, len(str2) + 1): 168 | if str1[i - 1] == str2[j - 1]: 169 | d = 0 170 | else: 171 | d = 1 172 | edit[i][j] = min(edit[i - 1][j] + 1, edit[i][j - 1] + 1, edit[i - 1][j - 1] + d) 173 | return edit[len(str1)][len(str2)] 174 | 175 | 176 | # Calculate the similarity score of two string 177 | def SimilarityScore(str1, str2): 178 | ED = EditDistanceRecursive(str1, str2) 179 | return round((1 - (ED / max(len(str1), len(str2)))) * 100, 2) 180 | 181 | 182 | # Use heuristics to detect the meaning of each byte in the message 183 | def Probe(Seed): 184 | global restoreSeed 185 | 186 | print("*** Probe ") 187 | m = Messenger(restoreSeed) 188 | for index in range(len(Seed.M)): 189 | 190 | responsePool = [] 191 | similarityScore = [] 192 | probeResponseIndex = [] 193 | 194 | print(Seed.M[index].raw["Content"].strip()) # test only 195 | # original message 196 | response1 = m.ProbeSend(Seed, index) # send the probe message ####### 197 | time.sleep(1) 198 | response2 = m.ProbeSend(Seed, index) # send the probe message twice 199 | 200 | responsePool.append(response1) 201 | similarityScore.append(SimilarityScore(response1.strip(), response2.strip())) 202 | 203 | # probe process 204 | for i in range(0, len(Seed.M[index].raw["Content"])): 205 | temp = Seed.M[index].raw["Content"] 206 | Seed.M[index].raw["Content"] = Seed.M[index].raw["Content"].strip()[:i] + Seed.M[index].raw[ 207 | "Content"].strip()[ 208 | i + 1:] # delete ith byte 209 | 210 | response1 = m.ProbeSend(Seed, index) # send the probe message ####### 211 | time.sleep(1) 212 | response2 = m.ProbeSend(Seed, index) # send the probe message twice 213 | print(response1,end='') 214 | 215 | if responsePool: 216 | flag = True 217 | for j in range(0, len(responsePool)): 218 | target = responsePool[j] 219 | score = similarityScore[j] 220 | c = SimilarityScore(target.strip(), response1.strip()) 221 | if c >= score: 222 | flag = False 223 | probeResponseIndex.append(j) 224 | print(str(j)+" ", end='') 225 | sys.stdout.flush() 226 | break 227 | if flag: 228 | responsePool.append(response1) 229 | similarityScore.append(SimilarityScore(response1.strip(), response2.strip())) 230 | probeResponseIndex.append(j + 1) 231 | #print(j + 1) # test only 232 | 233 | Seed.M[index].raw["Content"] = temp # restore the message 234 | 235 | Seed.PR.append(responsePool) 236 | Seed.PS.append(similarityScore) 237 | Seed.PI.append(probeResponseIndex) 238 | 239 | return Seed 240 | 241 | 242 | def getFeature(response, score): 243 | feature = {} 244 | feature['a'] = 0 245 | feature['n'] = 0 246 | feature['s'] = 0 247 | length = len(response) 248 | score = score 249 | 250 | cur = '' 251 | pre = '' 252 | for i in range(len(response)): 253 | if response[i].isdigit(): 254 | cur = 'n' 255 | elif response[i].isalpha(): 256 | cur = 'a' 257 | else: 258 | cur = 's' 259 | 260 | if pre == '': 261 | pre = cur 262 | elif pre != cur: 263 | feature[pre] = feature[pre] + 1 264 | pre = cur 265 | 266 | feature[cur] = feature[cur] + 1 267 | 268 | # print(response) # test only 269 | # print([feature['a'],feature['n'],feature['s'],length,score]) # test only 270 | 271 | return [feature['a'], feature['n'], feature['s'], length, score] 272 | 273 | 274 | def formSnippets(pi, cluster, index): 275 | snippet = [] 276 | for i in range(index): 277 | c1 = int(cluster[i][0]) 278 | c2 = int(cluster[i][1]) 279 | p = int(cluster[i][3]) 280 | for j in range(len(pi)): 281 | if pi[j] == c1 or pi[j] == c2: 282 | pi[j] = p 283 | 284 | i = 0 285 | while i < len(pi)-1: 286 | j = i 287 | #print("i="+str(i)) # test only 288 | skip = True 289 | while j <= len(pi) and skip: 290 | j = j + 1 291 | #print("j=" + str(j)) # test only 292 | if pi[j] != pi[i]: 293 | snippet.append([i, j - 1]) 294 | skip = False 295 | if j == len(pi)-1: 296 | snippet.append([i, j]) 297 | skip = False 298 | i = j 299 | 300 | #print(pi) # test only 301 | #print(snippet) # test only 302 | 303 | return snippet 304 | 305 | 306 | def interesting(oldSeed,index): 307 | global queue 308 | global restoreSeed 309 | m = Messenger(restoreSeed) 310 | 311 | 312 | print(oldSeed.M[index].raw["Content"]) 313 | 314 | seed = Seed() 315 | seed.M = oldSeed.M 316 | seed = m.DryRunSend(seed) 317 | seed = Probe(seed) 318 | queue.append(seed) 319 | 320 | 321 | def writeOutput(seed): 322 | global outputfold 323 | localtime = time.localtime(time.time()) 324 | file = 'Crash-'+localtime+'.txt' 325 | 326 | with open(os.path.join(outputfold, file), 'w') as f: 327 | for i in range(len(seed)): 328 | f.writelines("Message Index-" + str(i) + "\n") # write the message information 329 | for header in seed.M[i].headers: 330 | f.writelines(header + ":" + seed.M[i].raw[header] + '\n') 331 | f.writelines("\n") 332 | print("Found a crash @ "+localtime) 333 | sys.exit() 334 | 335 | 336 | def responseHandle(seed, info): 337 | if info.startswith("#interesting"): 338 | print("~~Get Interesting in :") 339 | interesting(seed, int(info.split('-')[1])) 340 | return False 341 | if info.startswith("#error"): 342 | print("~~Something wrong with the target infomation (e.g. IP addresss or port)") 343 | if info.startswith("#crash"): 344 | writeOutput(seed) 345 | return True 346 | 347 | 348 | def SnippetMutate(seed, restoreSeed): 349 | 350 | m = Messenger(restoreSeed) 351 | 352 | for i in range(len(seed.M)): 353 | pool = seed.PR[i] 354 | poolIndex = seed.PI[i] 355 | similarityScores = seed.PS[i] 356 | 357 | featureList = [] 358 | for j in range(len(pool)): 359 | featureList.append(getFeature(pool[j].strip(), similarityScores[j])) 360 | 361 | df = pd.DataFrame(featureList) 362 | cluster = hierarchy.linkage(df, method='average', metric='euclidean') 363 | #print("Cluster:") 364 | #print(cluster) 365 | #seed.display() 366 | 367 | seed.ClusterList.append(cluster) 368 | 369 | mutatedSnippet = [] 370 | for index in range(len(cluster)): 371 | snippetsList = formSnippets(poolIndex, cluster, index) 372 | for snippet in snippetsList: 373 | if snippet not in mutatedSnippet: 374 | mutatedSnippet.append(snippet) 375 | tempMessage = seed.M[i].raw["Content"] 376 | 377 | # ======== BitFlip ======== 378 | print("--BitFlip") 379 | message = seed.M[i].raw["Content"] 380 | asc = "" 381 | for o in range(snippet[0], snippet[1]): 382 | #print(255-ord(message[o])) 383 | asc=asc+(chr(255-ord(message[o]))) 384 | #message[o] = chr(255-ord(chr(message[o]))) 385 | message = message[:snippet[0]] + asc + message[snippet[1] + 1:] 386 | seed.M[i].raw["Content"] = message 387 | responseHandle(seed, m.SnippetMutationSend(seed,i)) 388 | seed.M[i].raw["Content"] = tempMessage 389 | 390 | # ======== Empty ======== 391 | print("--Empty") 392 | message = seed.M[i].raw["Content"] 393 | message = message[:snippet[0]] + message[snippet[1]+1:] 394 | seed.M[i].raw["Content"] = message 395 | responseHandle(seed, m.SnippetMutationSend(seed,i)) 396 | seed.M[i].raw["Content"] = tempMessage 397 | 398 | # ======== Repeat ======== 399 | print("--Repeat") 400 | message = seed.M[i].raw["Content"] 401 | t = random.randint(2, 5) 402 | message = message[:snippet[0]] + message[snippet[0]:snippet[1]] * t + message[snippet[1] + 1:] 403 | seed.M[i].raw["Content"] = message 404 | responseHandle(seed, m.SnippetMutationSend(seed,i)) 405 | seed.M[i].raw["Content"] = tempMessage 406 | 407 | # ======== Interesting ======== 408 | print("--Interesting") 409 | interestingString = ['on','off','True','False','0','1'] 410 | for t in interestingString: 411 | message = seed.M[i].raw["Content"] 412 | message = message[:snippet[0]] + t + message[snippet[1] + 1:] 413 | seed.M[i].raw["Content"] = message 414 | responseHandle(seed, m.SnippetMutationSend(seed,i)) 415 | seed.M[i].raw["Content"] = tempMessage 416 | 417 | seed.Snippet.append(mutatedSnippet) 418 | return 0 419 | 420 | 421 | def Havoc(queue, restoreSeed): 422 | print("*Havoc") 423 | m = Messenger(restoreSeed) 424 | 425 | t = random.randint(0,len(queue)-1) 426 | seed = queue[t] 427 | 428 | i = random.randint(0,len(seed.M)-1) 429 | snippets = seed.Snippet[i] 430 | message = seed.M[i].raw["Content"] 431 | tempMessage = seed.M[i].raw["Content"] 432 | 433 | n = random.randint(0,len(snippets)-1) 434 | snippet = snippets[n] 435 | 436 | pick = random.randint(0,5) 437 | 438 | if pick == 0: # ======== BitFlip ======== 439 | asc = "" 440 | for o in range(snippet[0], snippet[1]): 441 | #print(255-ord(message[o])) 442 | asc=asc+(chr(255-ord(message[o]))) 443 | #message[o] = chr(255-ord(chr(message[o]))) 444 | message = message[:snippet[0]] + asc + message[snippet[1] + 1:] 445 | seed.M[i].raw["Content"] = message 446 | temp = responseHandle(seed, m.SnippetMutationSend(seed,i)) 447 | seed.M[i].raw["Content"] = tempMessage 448 | return temp 449 | 450 | elif pick == 1: # ======== Empty ========== 451 | message = seed.M[i].raw["Content"] 452 | message = message[:snippet[0]] + message[snippet[1]+1:] 453 | seed.M[i].raw["Content"] = message 454 | temp = responseHandle(seed, m.SnippetMutationSend(seed,i)) 455 | seed.M[i].raw["Content"] = tempMessage 456 | return temp 457 | 458 | elif pick == 2: # ======== Repeat ======== 459 | message = seed.M[i].raw["Content"] 460 | t = random.randint(2, 5) 461 | message = message[:snippet[0]] + message[snippet[0]:snippet[1]] * t + message[snippet[1] + 1:] 462 | seed.M[i].raw["Content"] = message 463 | temp = responseHandle(seed, m.SnippetMutationSend(seed,i)) 464 | seed.M[i].raw["Content"] = tempMessage 465 | return temp 466 | 467 | elif pick == 3: # ======== Interesting ======== 468 | interestingString = ['on','off','True','False','0','1'] 469 | interesting = random.randint(0,5) 470 | t = interestingString[interesting] 471 | message = seed.M[i].raw["Content"] 472 | message = message[:snippet[0]] + t + message[snippet[1] + 1:] 473 | seed.M[i].raw["Content"] = message 474 | temp = responseHandle(seed, m.SnippetMutationSend(seed,i)) 475 | seed.M[i].raw["Content"] = tempMessage 476 | return temp 477 | 478 | elif pick == 4: # ======== Random Bytes Flip =========== 479 | start = random.randint(0,len(message)-1) 480 | end = random.randint(start,len(message)) 481 | asc = "" 482 | for o in range(start, end): 483 | asc=asc+(chr(255-ord(message[o]))) 484 | message = message[:start] + asc + message[end + 1:] 485 | seed.M[i].raw["Content"] = message 486 | temp = responseHandle(seed, m.SnippetMutationSend(seed,i)) 487 | seed.M[i].raw["Content"] = tempMessage 488 | return temp 489 | 490 | return True 491 | 492 | 493 | 494 | def getArgs(argv): 495 | inputfold = '' 496 | outputfold = '' 497 | restorefile = '' 498 | recordfile = '' 499 | try: 500 | opts, args = getopt.getopt(argv, "hi:r:o:c:", ["ifold=", "rfile=", "ofold=", "cfile="]) 501 | except getopt.GetoptError: 502 | print('Snipuzz.py -i -r -o (-c )') 503 | sys.exit(2) 504 | for opt, arg in opts: 505 | if opt == '-h': 506 | print('test.py -i -r -o (-c )') 507 | sys.exit() 508 | elif opt in ("-i", "--ifold"): 509 | inputfold = arg 510 | elif opt in ("-r", "--rfile"): 511 | restorefile = arg 512 | elif opt in ("-o", "--ofold"): 513 | outputfold = arg 514 | elif opt in ("-c", "--cfile"): 515 | recordfile = arg 516 | if not recordfile: 517 | recordfile = 'unavailable' 518 | print('Input fold:', inputfold) 519 | print('Restore file: ', restorefile) 520 | print('Output fold:', outputfold) 521 | print('Record file:', recordfile) 522 | 523 | return inputfold, restorefile, outputfold, recordfile 524 | 525 | 526 | # python "e:/ondrive-file/OneDrive - Swinburne University/Desktop/Snipuzz-py/Snipuzz.py" -i "E:/ondrive-file/OneDrive - Swinburne University/Desktop/Snipuzz-py/in" -r "E:\ondrive-file\OneDrive - Swinburne University\Desktop\Snipuzz-py\record\yeelight-restore.txt" -o "E:\ondrive-file\OneDrive - Swinburne University\Desktop\Snipuzz-py\out" -c "E:\ondrive-file\OneDrive - Swinburne University\Desktop\Snipuzz-py\out\ProbeRecord.txt" 527 | 528 | # python "e:/ondrive-file/OneDrive - Swinburne University/Desktop/Snipuzz-py/Snipuzz.py" -i "E:/ondrive-file/OneDrive - Swinburne University/Desktop/Snipuzz-py/in" -r "E:\ondrive-file\OneDrive - Swinburne University\Desktop\Snipuzz-py\record\yeelight-restore.txt" -o "E:\ondrive-file\OneDrive - Swinburne University\Desktop\Snipuzz-py\out" 529 | 530 | def main(argv): 531 | global queue, restoreSeed, outputfold 532 | 533 | inputfold, restorefile, outputfold, recordfile = getArgs(argv) 534 | restoreSeed = readInputFile(restorefile) 535 | 536 | 537 | queue = readInputFold(inputfold) 538 | 539 | if recordfile and os.path.exists(recordfile): 540 | queue = readRecordFile(recordfile) 541 | for seed in queue: 542 | seed.display() 543 | if (dryRun(queue)): # Dry Run 544 | print('#### Dry run failed, check the inputs or connection.') 545 | sys.exit() 546 | else: 547 | queue = readInputFold(inputfold) 548 | if (dryRun(queue)): # Dry Run 549 | print('#### Dry run failed, check the inputs or connection.') 550 | sys.exit() 551 | for i in range(len(queue)): 552 | queue[i] = Probe(queue[i]) 553 | writeRecord(queue, outputfold) 554 | 555 | skip = False 556 | while (1): 557 | if not skip: 558 | i=0 559 | while i < len(queue): 560 | if not queue[i].isMutated: 561 | SnippetMutate(queue[i], restoreSeed) 562 | i=i+1 563 | skip = True 564 | skip = Havoc(queue, restoreSeed) 565 | 566 | 567 | if __name__ == "__main__": 568 | main(sys.argv[1:]) 569 | --------------------------------------------------------------------------------