├── Analysis Tools └── WalkCheck.py ├── Method 1 - Tree Walks ├── QwertyTreeWalker.py └── qwerty_graph.txt ├── Method 2 - Combinator Script ├── 4_walk_seed.txt ├── Combinator.py └── walk.rule └── README.txt /Analysis Tools/WalkCheck.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 3 | WalkCheck.py - Checks strings and detects keyboard walks 4 | 5 | usage: WalkCheck.py [-h] [-l [L]] [-strict] [-loop] [-stats] 6 | [graph_file_name] [input] 7 | 8 | Check if string(s) are keyboard walks 9 | 10 | positional arguments: 11 | graph_file_name File with adjacency list of format {'letter': 12 | {'direction': 'letter connected'}} 13 | input File name or single string to check 14 | 15 | optional arguments: 16 | -h, --help show this help message and exit 17 | -l [L], -length [L] Walk length 18 | -strict Only find exact walks of length specified by -l option 19 | -loop Consider adjacent dublicate letters as walks 20 | -stats Do some calculations 21 | 22 | The MIT License (MIT) 23 | 24 | Copyright (c) 2014 Rich Kelley, RK5DEVMAIL[A T]gmail[D O T]com, @RGKelley5, www.frogstarworldc.com 25 | 26 | NOTE: File containing graph data structure can be found at https://github.com/Rich5/Keyboard-Walk-Generators/blob/master/Method%201%20-%20Tree%20Walks/qwerty_graph.txt 27 | 28 | ''' 29 | 30 | import argparse 31 | import os.path 32 | 33 | 34 | def walk_checker(graph, password, length=4, strict=True, loop=False): 35 | 36 | result = False 37 | path_length = 1 38 | 39 | if strict and len(password) != length: 40 | 41 | return result 42 | 43 | for i in range(len(password)): 44 | 45 | current_letter = password[i] 46 | 47 | if i < len(password)-1: 48 | 49 | next_letter = password[i+1] 50 | 51 | if current_letter in graph: 52 | 53 | if loop: 54 | 55 | if next_letter in graph[current_letter].values(): 56 | 57 | path_length += 1 58 | 59 | if path_length == length: 60 | result = True 61 | 62 | else: 63 | 64 | result = False 65 | path_length = 1 66 | 67 | else: 68 | 69 | if next_letter in graph[current_letter].values() and next_letter.lower() != current_letter.lower(): 70 | 71 | path_length += 1 72 | 73 | if path_length == length: 74 | result = True 75 | 76 | else: 77 | 78 | result = False 79 | path_length = 1 80 | 81 | return result 82 | 83 | 84 | 85 | if __name__ == "__main__": 86 | 87 | parser = argparse.ArgumentParser(description='Check if string(s) are keyboard walks') 88 | parser.add_argument('graph_file_name', nargs='?', help="File with adjacency list of format {'letter': {'direction': 'letter connected'}}", type=str) 89 | parser.add_argument('input', nargs='?', help="File name or single string to check", type=str) 90 | parser.add_argument('-l', '-length', nargs='?', help="Walk length", type=int, default=4) 91 | parser.add_argument('-strict', help="Only find exact walks of length specified by -l option", action='store_true', default=False) 92 | parser.add_argument('-loop', help="Consider adjacent dublicate letters as walks", action='store_true', default=False) 93 | parser.add_argument('-stats', help="Do some calculations", action='store_true', default=False) 94 | args = parser.parse_args() 95 | 96 | try: 97 | 98 | with open(args.graph_file_name, "r") as fin: 99 | 100 | graph = eval(fin.read()) 101 | 102 | except IOError as e: 103 | 104 | print "I/O error({0}): {1}".format(e.errno, e.strerror) 105 | 106 | 107 | if os.path.isfile(args.input): 108 | with open(args.input, "r") as fin: 109 | 110 | status = False 111 | total = 0 112 | total_true = 0 113 | for line_num,line in enumerate(fin): 114 | 115 | if len(line) >= args.l: 116 | 117 | total += 1 118 | status = walk_checker(graph, line.rstrip('\n\r'), args.l, args.strict, args.loop) 119 | 120 | if status and not args.stats: 121 | print line.rstrip('\n\r') 122 | 123 | if status and args.stats: 124 | total_true += 1 125 | 126 | 127 | if args.stats: 128 | print "Total Possible:\t " + str(total) 129 | print "Total Walks:\t " + str(total_true), "({0:.1f}%)".format(float(total_true)/float(total) * 100) 130 | 131 | else: 132 | 133 | status = walk_checker(graph, args.input.rstrip('\n\r'), args.l, args.strict, args.loop) 134 | 135 | if status: 136 | print args.input.rstrip('\n\r') 137 | -------------------------------------------------------------------------------- /Method 1 - Tree Walks/QwertyTreeWalker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | ''' 4 | 5 | Version 2.0.0 of QwertyTreeWalker supports two modes to display output, stdout and write-to-files 6 | 7 | The main process will parse the qwerty_graph datastructure provided, and split the work among a 8 | number of worker processes. Each worker process will output a file with the walks generated. If the 9 | file size exceeds 524288000 bytes then a new file will be created to continue output. Output by default 10 | will be located in an OUTPUT folder located in the same directory QwertyTreeWalker.py is being run. 11 | 12 | Commandline Arguments: 13 | ---------------------- 14 | usage: QwertyTreeWalker.py [-h] [-l [L]] [-p [P]] [-x] [-H] [--stdout][--noplain][file_name] 15 | 16 | Generate walks for Qwerty Keyboard 17 | 18 | positional arguments: 19 | file_name File with adjacency list of format {'letter':{'direction': 'letter connected'}} 20 | 21 | optional arguments: 22 | -h, --help show this help message and exit 23 | -l [L], -length [L] Walk length 24 | -p [P], -processes [P] Number of processses to divide work 25 | -x, -exclude Will trigger prompt for link exclude list 26 | -H, -hash Output NTLM hash 27 | --stdout Output to screen 28 | --noplain Do not print plain text hash 29 | 30 | The MIT License (MIT) 31 | 32 | Copyright (c) 2014 Rich Kelley, RK5DEVMAIL[A T]gmail[D O T]com, @RGKelley5 33 | 34 | Permission is hereby granted, free of charge, to any person obtaining a copy 35 | of this software and associated documentation files (the "Software"), to deal 36 | in the Software without restriction, including without limitation the rights 37 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 38 | copies of the Software, and to permit persons to whom the Software is 39 | furnished to do so, subject to the following conditions: 40 | 41 | The above copyright notice and this permission notice shall be included in 42 | all copies or substantial portions of the Software. 43 | 44 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 45 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 46 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 47 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 48 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 49 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 50 | THE SOFTWARE. 51 | 52 | 53 | ''' 54 | 55 | from math import ceil 56 | from datetime import datetime, timedelta 57 | from multiprocessing import Process, Manager, freeze_support 58 | from msvcrt import getch 59 | import threading 60 | import hashlib 61 | import binascii 62 | import argparse 63 | import time 64 | import sys 65 | import os 66 | import copy 67 | 68 | __version__ = "2.0.0" 69 | 70 | class QwertyTreeWalker: 71 | 72 | graph = {} 73 | start_time = 0 74 | chunks = 0 75 | max_depth = 0 76 | num_workers = 0 77 | exclude_list = [] 78 | hash = False 79 | no_plain = True 80 | 81 | def __init__(self, graph_data=None, build_exclude=False, plain=True, hash=False, stdout=False): 82 | 83 | self.hash = hash 84 | self.no_plain = plain 85 | self.stdout = stdout 86 | 87 | # All output will be located in the output folder 88 | self.out_folder = "OUTPUT" 89 | manager = Manager() 90 | self.stats = manager.dict() 91 | 92 | if not os.path.exists(self.out_folder): 93 | os.makedirs(self.out_folder) 94 | 95 | try: 96 | 97 | with open(graph_data, "r") as self.fin: 98 | 99 | self.graph = eval(self.fin.read()) 100 | 101 | except IOError as e: 102 | 103 | print "I/O error({0}): {1}".format(e.errno, e.strerror) 104 | 105 | # Build exclude list if necessary 106 | if build_exclude: 107 | 108 | option = 1 109 | menu = {} 110 | for direction in self.graph[self.graph.keys()[0]]: 111 | 112 | menu[option] = direction 113 | option += 1 114 | 115 | 116 | for opt in sorted(menu): 117 | 118 | print "[" + str(opt) +"] " + menu[opt] 119 | 120 | print "Enter Links to exclude as csv (EX:1,2,3)" 121 | exclude_list_entered = raw_input(">> ") 122 | 123 | try: 124 | 125 | exclude_keys = tuple(exclude_list_entered.split(",")) 126 | exclude_list = [] 127 | for key in exclude_keys: 128 | 129 | exclude_list.append(menu[int(key)]) 130 | 131 | self.exclude_list = exclude_list 132 | 133 | except KeyError as e: 134 | 135 | pass 136 | 137 | def start_workers(self, num_of_processes=1, max_depth=1): 138 | 139 | letters = [key for key in self.graph] 140 | 141 | self.num_workers = int(ceil(len(letters)/num_of_processes)) 142 | work_chunks = [letters[x:x + self.num_workers] for x in xrange(0,len(letters), self.num_workers)] 143 | 144 | # Print start message stats 145 | self.chunks = len(work_chunks) 146 | self.max_depth = max_depth 147 | self.start_time = time.time() 148 | 149 | if not self.stdout: 150 | print "\n\n**********************************************************************" 151 | print "***************** WARNING: This may take a while *********************" 152 | print "***************** Type: [S]tatus [Q]uit ******************************" 153 | print "**********************************************************************\n\n" 154 | print "[ " + str(self.max_depth) + "-step walk STARTED at:\t" + time.strftime("%Y-%m-%d-%H%M%S") + " with " + str(self.chunks) + " workers ]" 155 | 156 | if self.exclude_list: 157 | 158 | print "\nExcluding: \n" + str(self.exclude_list) + "\n at user request\n" 159 | 160 | 161 | # Spawn worker processes 162 | 163 | pid = 0 164 | procs = [] 165 | for chunk in work_chunks: 166 | 167 | p = Process(target=self.start_walking, args=(max_depth,pid,chunk)) 168 | p.daemon = True 169 | procs.append(p) 170 | self.stats[pid] = {'start_time': None, 'total_walks': None, 'walk_rate': 0, 'walks_generated': 0, 'walks_left': None, 'seconds_left': None, 'done': "GO"} 171 | p.start() 172 | pid += 1 173 | 174 | # Now collect stats and print reports 175 | time.sleep(2) # Let the other processes get going 176 | EXIT = False 177 | self.KILL = False 178 | 179 | if not self.stdout: 180 | 181 | t = threading.Thread(target=self.input_handler) # Listen for user input during run 182 | t.daemon = True 183 | t.start() 184 | 185 | while EXIT == False and self.KILL == False: 186 | 187 | workers_working = len(work_chunks) 188 | for stat in self.stats.values(): 189 | 190 | if stat['done'] == "STOP": 191 | 192 | workers_working -= 1 193 | 194 | if workers_working <= 0: 195 | 196 | EXIT = True 197 | break 198 | 199 | 200 | end_time = time.time() 201 | 202 | if not self.stdout: 203 | 204 | print "\n[ " + str(self.max_depth) + "-step walk ENDED at: \t" + time.strftime("%Y-%m-%d-%H%M%S") +" ]\t\t" 205 | print "\nWriting files. Please wait this could take several minutes.", 206 | self.print_end_stats(end_time) 207 | 208 | if self.KILL == False: 209 | 210 | for p in procs: 211 | 212 | p.join() 213 | 214 | else: 215 | 216 | for p in procs: 217 | 218 | p.terminate() 219 | 220 | 221 | 222 | def print_end_stats(self, end_time): 223 | 224 | # Print end message stats 225 | 226 | sec = 0 227 | walks_left = 0 228 | walk_rate = 0. 229 | walks_generated = 0 230 | 231 | for stat in iter(self.stats.keys()): 232 | 233 | walks_generated += long(self.stats[stat]['walks_generated']) 234 | sec += long(self.stats[stat]['seconds_left']) 235 | walks_left += long(self.stats[stat]['walks_left']) 236 | walk_rate += long(self.stats[stat]['walk_rate']) 237 | start_time = self.stats[stat]['start_time'] 238 | 239 | avg_walk_rate = walk_rate/self.chunks 240 | 241 | print "[Done]" 242 | print "\n\t[Run Stats]" 243 | print "\t\tElasped Time: " + str((end_time-self.start_time)/60) + " minutes" 244 | print "\t\t" + '{0:.8f}'.format(avg_walk_rate) + " walks/sec/worker" 245 | print "\t\t" + str(walks_generated) + " walks generated\n\n" 246 | 247 | 248 | def report_stats(self, pid, total_walks, walk_rate, walks_generated, walks_left, seconds_left, done): 249 | 250 | stats = {} 251 | stats = self.stats[pid] 252 | stats['total_walks'] = total_walks 253 | stats['walk_rate'] = walk_rate 254 | stats['walks_generated'] = walks_generated 255 | stats['walks_left'] = walks_left 256 | stats['seconds_left'] = seconds_left 257 | stats['done'] = done 258 | self.stats[pid] = stats 259 | 260 | def print_inc_stats(self): 261 | 262 | sec = 0 263 | walks_left = 0 264 | walk_rate = 0. 265 | walks_generated = 0 266 | 267 | for stat in iter(self.stats.keys()): 268 | 269 | walks_generated += long(self.stats[stat]['walks_generated']) 270 | sec += long(self.stats[stat]['seconds_left']) 271 | walks_left += long(self.stats[stat]['walks_left']) 272 | walk_rate += long(self.stats[stat]['walk_rate']) 273 | start_time = self.stats[stat]['start_time'] 274 | 275 | 276 | print '\r{0:.8f} walks/sec\t Walks: {1} Walks Left: {2}'.format(walk_rate, walks_generated, walks_left), 277 | sys.stdout.write("\r") 278 | sys.stdout.flush() 279 | 280 | def start_walking(self, max_depth, pid, letters=None): 281 | 282 | if letters == None: 283 | 284 | letters = [key for key in self.graph] 285 | 286 | 287 | total_walks = self.walks_to_completion(max_depth, letters) 288 | max_depth = max_depth - 1 # Decrement to account for start at zero 289 | walks_generated = 0 290 | 291 | # Create output file with name format _Walk_