├── LICENSE ├── Makefile ├── README.md ├── disas ├── capstone_32.py ├── capstone_64.py ├── disas_32.sh └── disas_64.sh ├── gui ├── __init__.py └── gui.py ├── injector.c ├── mutator.py ├── pyutil ├── __init__.py ├── colors.py └── progress.py ├── references ├── domas_breaking_the_x86_isa.pdf ├── domas_breaking_the_x86_isa_wp.pdf ├── sandsifter.gif ├── screenshot.png └── summarizer.png ├── sifter.py └── summarize.py /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Battelle Memorial Institute 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # sand sifter make file 2 | # 3 | # in x86, instructions run in 32 bit mode sometimes differ from the same 4 | # instructions run in 64 bit mode. for this reason, it can be beneficial to 5 | # fuzz both 32 and 64 bit instructions. this requires a 32 and 64 bit binary. 6 | # afaict, capstone will not let you simultaneously install both 32 and 64 bit 7 | # versions. to overcome this, we statically link to capstone. to build both a 8 | # 32 bit and 64 bit injector: 9 | # 10 | # - build and install 32 bit capstone: 11 | # ./make.sh nix32 12 | # sudo ./make.sh nix32 install 13 | # 14 | # - build the 32 bit injector: 15 | # make CFLAGS=-m32 16 | # mv injector injector_32 17 | # 18 | # - build and install 64 bit capstone: 19 | # ./make.sh 20 | # sudo ./make.sh install 21 | # 22 | # - build the 64 bit injector: 23 | # make injector 24 | # mv injector injector_64 25 | # 26 | # you can now copy injector_32 and injector_64 to 'injector' before running 27 | # ./sifter.py in order to explore that facet of the architecture. 28 | # 29 | #TODO: i don't know if i was ever able to get a statically linked capstone to 30 | # work like i describe above 31 | 32 | all: injector 33 | 34 | injector: injector.o 35 | $(CC) $(CFLAGS) $< -O3 -Wall -l:libcapstone.a -o $@ -pthread 36 | 37 | %.o: %.c 38 | $(CC) $(CFLAGS) -c $< -o $@ -Wall 39 | 40 | clean: 41 | rm *.o injector 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## s a n d s i f t e r 2 | : the x86 processor fuzzer 3 | 4 | ### Overview 5 | 6 | The sandsifter audits x86 processors for hidden instructions and hardware bugs, 7 | by systematically generating machine code to search through a processor's 8 | instruction set, and monitoring execution for anomalies. Sandsifter has 9 | uncovered secret processor instructions from every major vendor; ubiquitous 10 | software bugs in disassemblers, assemblers, and emulators; flaws in enterprise 11 | hypervisors; and both benign and security-critical hardware bugs in x86 chips. 12 | 13 | With the multitude of x86 processors in existence, the goal of the tool is to 14 | enable users to check their own systems for hidden instructions and bugs. 15 | 16 | To run a basic audit against your processor: 17 | 18 | ``` 19 | sudo ./sifter.py --unk --dis --len --sync --tick -- -P1 -t 20 | ``` 21 | 22 | ![demo_sandsifter](references/sandsifter.gif) 23 | 24 | The computer is systematically scanned for anomalous instructions. In the upper 25 | half, you can view the instructions that the sandsifter is currently testing on 26 | the processor. In the bottom half, the sandsifter reports anomalies it finds. 27 | 28 | The search will take from a few hours to a few days, depending on the speed of 29 | and complexity of your processor. When it is complete, summarize the results: 30 | 31 | ``` 32 | ./summarize.py data/log 33 | ``` 34 | 35 | ![demo_summarizer](references/summarizer.png) 36 | 37 | Typically, several million undocumented instructions on your processor will be 38 | found, but these generally fall into a small number of different groups. After 39 | binning the anomalies, the summarize tool attempts to assign each instruction to 40 | an issue category: 41 | 42 | * Software bug (for example, a bug in your hypervisor or disassembler), 43 | * Hardware bug (a bug in your CPU), or 44 | * Undocumented instruction (an instruction that exists in the processor, but is 45 | not acknowledged by the manufacturer) 46 | 47 | Press 'Q' to quit and obtain a text based summary of the system scan: 48 | 49 | The results of a scan can sometimes be difficult for the tools to automatically 50 | classify, and may require manual analysis. For help analyzing your results, feel 51 | free to send the ./data/log file to xoreaxeaxeax@gmail.com. No personal 52 | information, other than the processor make, model, and revision (from 53 | /proc/cpuinfo) are included in this log. 54 | 55 | 56 | ### Results 57 | 58 | Scanning with the sandsifter has uncovered undocumented processor features 59 | across dozens of opcode categories, flaws in enterprise hypervisors, bugs in 60 | nearly every major disassembly and emulation tool, and critical hardware bugs 61 | opening security vulnerabilities in the processor itself. 62 | 63 | Details of the results can be found in the project 64 | [whitepaper](./references/domas_breaking_the_x86_isa_wp.pdf). 65 | 66 | (TODO: detailed results enumeration here) 67 | 68 | 69 | ### Building 70 | 71 | Sandsifter requires first installing the Capstone disassembler: 72 | http://www.capstone-engine.org/. Capstone can typically be installed with: 73 | 74 | ``` 75 | sudo apt-get install libcapstone3 libcapstone-dev 76 | sudo pip install capstone 77 | ``` 78 | 79 | Sandsifter can be built with: 80 | 81 | ``` 82 | make 83 | ``` 84 | 85 | and is then run with 86 | 87 | ``` 88 | sudo ./sifter.py --unk --dis --len --sync --tick -- -P1 -t 89 | ``` 90 | 91 | ### Flags 92 | 93 | Flags are passed to the sifter with --flag, and to the injector with -- -f. 94 | 95 | Example: 96 | 97 | ``` 98 | sudo ./sifter.py --unk --dis --len --sync --tick -- -P1 -t 99 | ``` 100 | 101 | Sifter flags: 102 | 103 | ``` 104 | --len 105 | search for length differences in all instructions (instructions that 106 | executed differently than the disassembler expected, or did not 107 | exist when the disassembler expected them to 108 | 109 | --dis 110 | search for length differences in valid instructions (instructions that 111 | executed differently than the disassembler expected) 112 | 113 | --unk 114 | search for unknown instructions (instructions that the disassembler doesn't 115 | know about but successfully execute) 116 | 117 | --ill 118 | the inverse of --unk, search for invalid disassemblies (instructions that do 119 | not successfully execute but that the disassembler acknowledges) 120 | 121 | --tick 122 | periodically write the current instruction to disk 123 | 124 | --save 125 | save search progress on exit 126 | 127 | --resume 128 | resume search from last saved state 129 | 130 | --sync 131 | write search results to disk as they are found 132 | 133 | --low-mem 134 | do not store results in memory 135 | ``` 136 | 137 | Injector flags: 138 | 139 | ``` 140 | -b 141 | mode: brute force 142 | 143 | -r 144 | mode: randomized fuzzing 145 | 146 | -t 147 | mode: tunneled fuzzing 148 | 149 | -d 150 | mode: externally directed fuzzing 151 | 152 | -R 153 | raw output mode 154 | 155 | -T 156 | text output mode 157 | 158 | -x 159 | write periodic progress to stderr 160 | 161 | -0 162 | allow null dereference (requires sudo) 163 | 164 | -D 165 | allow duplicate prefixes 166 | 167 | -N 168 | no nx bit support 169 | 170 | -s seed 171 | in random search, seed value 172 | 173 | -B brute_depth 174 | in brute search, maximum search depth 175 | 176 | -P max_prefix 177 | maximum number of prefixes to search 178 | 179 | -i instruction 180 | instruction at which to start search (inclusive) 181 | 182 | -e instruction 183 | instruction at which to end search (exclusive) 184 | 185 | -c core 186 | core on which to perform search 187 | 188 | -X blacklist 189 | blacklist the specified instruction 190 | 191 | -j jobs 192 | number of simultaneous jobs to run 193 | 194 | -l range_bytes 195 | number of base instruction bytes in each sub range 196 | ``` 197 | 198 | 199 | ### Keys 200 | 201 | m: Mode - change the search mode (brute force, random, or tunnel) for the sifter 202 | 203 | q: Quit - exit the sifter 204 | 205 | p: Pause - pause or unpause the search 206 | 207 | 208 | ### Algorithms 209 | 210 | The scanning supports four different search algorithms, which can be set at the 211 | command line, or cycled via hotkeys. 212 | 213 | * Random searching generates random instructions to test; it generally produces 214 | results quickly, but is unable to find complex hidden instructions and bugs. 215 | * Brute force searching tries instructions incrementally, up to a user-specified 216 | length; in almost all situations, it performs worse than random searching. 217 | * Driven or mutation driven searching is designed to create new, increasingly 218 | complex instructions through genetic algorithms; while promising, this 219 | approach was never fully realized, and is left as a stub for future research. 220 | * Tunneling is the approach described in the presentation and white paper, and 221 | in almost all cases provides the best trade-off between thoroughness and 222 | speed. 223 | 224 | 225 | ### Tips 226 | 227 | * sudo 228 | 229 | For best results, the tool should be run as the root user. This is necessary so 230 | that the process can map into memory a page at address 0, which requires root 231 | permissions. This page prevents many instructions from seg-faulting on memory 232 | accesses, which allows a more accurate fault analysis. 233 | 234 | * Prefixes 235 | 236 | The primary limitation for the depth of an instruction search is the number 237 | of prefix bytes to explore, with each additional prefix byte increasing the 238 | search space by around a factor of 10. Limit prefix bytes with the -P flag. 239 | 240 | * Colors 241 | 242 | The interface for the sifter is designed for a 256 color terminal. While 243 | the details vary greatly depending on your terminal, this can roughly be 244 | accomplished with: 245 | 246 | ``` 247 | export TERM='xterm-256color' 248 | ``` 249 | 250 | * GUI 251 | 252 | The interface assumes the terminal is of at least a certain size; if the 253 | interface is not rendering properly, try increasing the terminal size; this 254 | can often be accomplished by decreasing the terminal font size. 255 | 256 | In some cases, it may be desirable or necessary to run the tool without the 257 | graphical front end. This can be done by running the injector directly: 258 | 259 | ``` 260 | sudo ./injector -P1 -t -0 261 | ``` 262 | 263 | To filter the results of a direct injector invocation, grep can be used. 264 | For example, 265 | 266 | ``` 267 | sudo ./injector -P1 -r -0 | grep '\.r' | grep -v sigill 268 | ``` 269 | 270 | searches for instructions for which the processor and disassembler disagreed 271 | on the instruction length (grep '\.r'), but the instruction successfully 272 | executed (grep -v sigill). 273 | 274 | * Targeted fuzzing 275 | 276 | In many cases, it is valuable to direct the fuzzer to a specific target. 277 | For example, if you suspect that an emulator has flaws around repeated 'lock' 278 | prefixes (0xf0), you could direct the fuzzer to search this region of the 279 | instruction space with the -i and -e flags: 280 | 281 | ``` 282 | sudo ./sifter.py --unk --dis --len --sync --tick -- -t -i f0f0 -e f0f1 -D -P15 283 | ``` 284 | 285 | * Legacy systems 286 | 287 | For scanning much older systems (i586 class processors, low memory systems), 288 | pass the --low-mem flag to the sifter and the -N flag to the injector: 289 | 290 | ``` 291 | sudo ./sifter.py --unk --dis --len --sync --tick --low-mem -- -P1 -t -N 292 | ``` 293 | 294 | If you observe your scans completing too quickly (for example, a scan 295 | completes in seconds), it is typically because these flags are required for 296 | the processor you are scanning. 297 | 298 | * 32 vs. 64 bit 299 | 300 | By default, sandsifter is built to target the bitness of the host operating 301 | system. However, some instructions have different behaviors when run in a 302 | 32 bit process compared to when run in a 64 bit process. To explore these 303 | scenarios, it is sometimes valuable to run a 32 bit sandsifter on a 64 bit 304 | system. 305 | 306 | To build a 32 bit sandsifter on a 64 bit system, Capstone must be installed 307 | as 32 bit; the instructions for this can be found at http://www.capstone-engine.org/. 308 | 309 | Then sandsifter must be built for a 32 bit architecture: 310 | 311 | ``` 312 | make CFLAGS=-m32 313 | ``` 314 | 315 | With this, the 32 bit instruction space can be explored on a 64 bit system. 316 | 317 | 318 | ### References 319 | 320 | * A discussion of the techniques and results can be found in the Black Hat 321 | [presentation](https://www.youtube.com/watch?v=KrksBdWcZgQ). 322 | * Technical details are described in the 323 | [whitepaper](./references/domas_breaking_the_x86_isa_wp.pdf). 324 | * Slides from the Black Hat presentation are 325 | [here](./references/domas_breaking_the_x86_isa.pdf). 326 | 327 | 328 | ### Author 329 | 330 | sandsifter is a research effort from Christopher Domas 331 | ([@xoreaxeaxeax](https://twitter.com/xoreaxeaxeax)). 332 | 333 | -------------------------------------------------------------------------------- /disas/capstone_32.py: -------------------------------------------------------------------------------- 1 | from capstone import * 2 | import sys 3 | from binascii import hexlify, unhexlify 4 | import os.path 5 | 6 | if os.path.exists(sys.argv[1]): 7 | with open(sys.argv[1], 'r') as f: 8 | byte_string = hexlify(f.read()) 9 | else: 10 | byte_string = sys.argv[1] 11 | 12 | md = Cs(CS_ARCH_X86, CS_MODE_32) 13 | try: 14 | (address, size, mnemonic, op_str) = md.disasm_lite(unhexlify(byte_string), 0, 1).next() 15 | except StopIteration: 16 | mnemonic="(unk)" 17 | op_str="" 18 | size = 0 19 | 20 | print "%s %s" % (mnemonic, op_str) 21 | print byte_string[:size*2] 22 | print "%d bytes" % size 23 | -------------------------------------------------------------------------------- /disas/capstone_64.py: -------------------------------------------------------------------------------- 1 | from capstone import * 2 | import sys 3 | from binascii import hexlify, unhexlify 4 | import os.path 5 | 6 | if os.path.exists(sys.argv[1]): 7 | with open(sys.argv[1], 'r') as f: 8 | byte_string = hexlify(f.read()) 9 | else: 10 | byte_string = sys.argv[1] 11 | 12 | md = Cs(CS_ARCH_X86, CS_MODE_64) 13 | try: 14 | (address, size, mnemonic, op_str) = md.disasm_lite(unhexlify(byte_string), 0, 1).next() 15 | except StopIteration: 16 | mnemonic="(unk)" 17 | op_str="" 18 | size = 0 19 | 20 | print "%s %s" % (mnemonic, op_str) 21 | print byte_string[:size*2] 22 | print "%d bytes" % size 23 | -------------------------------------------------------------------------------- /disas/disas_32.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo -ne `echo $1 | sed 's/\(..\)/\\\\x\1/g' ` > temp.bin 4 | 5 | echo 6 | echo "=== ndisasm ===" 7 | echo 8 | ndisasm -b32 temp.bin 9 | 10 | echo 11 | echo "=== objdump ===" 12 | echo 13 | objdump -D -b binary -mi386 temp.bin 14 | 15 | echo 16 | echo "=== capstone ===" 17 | echo 18 | python capstone_32.py $1 19 | 20 | rm temp.bin 21 | 22 | -------------------------------------------------------------------------------- /disas/disas_64.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo -ne `echo $1 | sed 's/\(..\)/\\\\x\1/g' ` > temp.bin 4 | 5 | echo 6 | echo "=== ndisasm ===" 7 | echo 8 | ndisasm -b64 temp.bin 9 | 10 | echo 11 | echo "=== objdump ===" 12 | echo 13 | objdump -D -b binary -mi386 -Mx86-64 temp.bin 14 | 15 | echo 16 | echo "=== capstone ===" 17 | echo 18 | python capstone_64.py $1 19 | 20 | rm temp.bin 21 | 22 | -------------------------------------------------------------------------------- /gui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Battelle/sandsifter/8375e6123d093629e3e4437d7903839fd0742c2a/gui/__init__.py -------------------------------------------------------------------------------- /gui/gui.py: -------------------------------------------------------------------------------- 1 | import curses 2 | import locale 3 | 4 | class Box: 5 | def __init__(self, gui, window, x, y, w, h, color): 6 | self.gui = gui 7 | self.window = window 8 | self.x = x 9 | self.y = y 10 | self.w = w 11 | self.h = h 12 | self.color = color 13 | 14 | def draw(self): 15 | self.gui.box(self.window, self.x, self.y, self.w, self.h, self.color) 16 | 17 | class TextBox: 18 | def __init__(self, gui, window, x, y, w, h, color, text, text_color, selected_color): 19 | 20 | self.gui = gui 21 | self.window = window 22 | 23 | self.x = x 24 | self.y = y 25 | self.w = w 26 | self.h = h 27 | 28 | self.scroll_index = 0 29 | self.selected_index = 0 30 | self.text = text 31 | 32 | self.text_color = text_color 33 | self.color = color 34 | self.selected_color = selected_color 35 | 36 | def scroll_up(self): 37 | if self.selected_index > 0: 38 | self.selected_index = self.selected_index - 1 39 | if self.selected_index < self.scroll_index: 40 | self.scroll_index = self.scroll_index - 1 41 | self.draw() 42 | 43 | def scroll_down(self): 44 | if self.selected_index < len(self.text) - 1: 45 | self.selected_index = self.selected_index + 1 46 | if self.selected_index > self.scroll_index + (self.h - 2) - 1: 47 | self.scroll_index = self.scroll_index + 1 48 | self.draw() 49 | 50 | def at_top(self): 51 | return self.selected_index == 0 52 | 53 | def at_bottom(self): 54 | return self.selected_index == len(self.text) - 1 55 | 56 | def scroll_top(self): 57 | self.selected_index = 0 58 | self.scroll_index = 0 59 | self.draw() 60 | 61 | def scroll_bottom(self): 62 | self.selected_index = len(self.text) - 1 63 | self.scroll_index = len(self.text) - (self.h - 2) 64 | self.scroll_index = self.scroll_index if self.scroll_index > 0 else 0 65 | self.draw() 66 | 67 | def draw(self): 68 | self.gui.box(self.window, self.x, self.y, self.w, self.h, self.color) 69 | for (i, l) in enumerate(self.text[self.scroll_index:self.scroll_index + self.h - 2]): 70 | if len(l) > self.w - 2: 71 | l = l[:self.w - 5] + "..." 72 | l = l.ljust(self.w - 2) 73 | self.window.addstr(self.y + 1 + i, self.x + 1, l, self.text_color if 74 | self.selected_index != self.scroll_index + i else self.selected_color) 75 | for j in xrange(i + 1, self.h - 2): 76 | self.window.addstr(self.y + 1 + j, self.x + 1, " " * (self.w - 2), self.text_color if 77 | self.selected_index != self.scroll_index + j else self.selected_color) 78 | self.gui.vscrollbar(self.window, self.x + self.w, self.y, self.h, 79 | self.selected_index / float(len(self.text)), self.gui.gray(1)) 80 | 81 | class Gui: 82 | GRAY_BASE = 50 83 | GRAYS = 50 84 | 85 | BLACK = 1 86 | WHITE = 2 87 | BLUE = 3 88 | RED = 4 89 | GREEN = 5 90 | 91 | COLOR_BLACK = 16 92 | COLOR_WHITE = 17 93 | COLOR_BLUE = 18 94 | COLOR_RED = 19 95 | COLOR_GREEN = 20 96 | 97 | def __init__(self, no_delay=True): 98 | self.start(no_delay) 99 | 100 | def refresh(self): 101 | self.window.refresh() 102 | 103 | def start(self, no_delay): 104 | self.window = curses.initscr() 105 | curses.start_color() 106 | curses.use_default_colors() 107 | curses.noecho() 108 | curses.cbreak() 109 | curses.curs_set(0) 110 | self.window.nodelay(no_delay) 111 | self.init_colors() 112 | self.window.bkgd(curses.color_pair(self.WHITE)) 113 | locale.setlocale(locale.LC_ALL, '') # set your locale 114 | self.code = locale.getpreferredencoding() 115 | 116 | def stop(self): 117 | curses.nocbreak(); 118 | curses.echo() 119 | curses.endwin() 120 | 121 | def get_key(self): 122 | return self.window.getch() 123 | 124 | def init_colors(self): 125 | 126 | if curses.has_colors() and curses.can_change_color(): 127 | curses.init_color(self.COLOR_BLACK, 0, 0, 0) 128 | curses.init_color(self.COLOR_WHITE, 1000, 1000, 1000) 129 | curses.init_color(self.COLOR_BLUE, 0, 0, 1000) 130 | curses.init_color(self.COLOR_RED, 1000, 0, 0) 131 | curses.init_color(self.COLOR_GREEN, 0, 1000, 0) 132 | 133 | for i in xrange(0, self.GRAYS): 134 | curses.init_color( 135 | self.GRAY_BASE + i, 136 | i * 1000 / (self.GRAYS - 1), 137 | i * 1000 / (self.GRAYS - 1), 138 | i * 1000 / (self.GRAYS - 1) 139 | ) 140 | curses.init_pair( 141 | self.GRAY_BASE + i, 142 | self.GRAY_BASE + i, 143 | self.COLOR_BLACK 144 | ) 145 | 146 | else: 147 | self.COLOR_BLACK = curses.COLOR_BLACK 148 | self.COLOR_WHITE = curses.COLOR_WHITE 149 | self.COLOR_BLUE = curses.COLOR_BLUE 150 | self.COLOR_RED = curses.COLOR_RED 151 | self.COLOR_GREEN = curses.COLOR_GREEN 152 | 153 | for i in xrange(0, self.GRAYS): 154 | curses.init_pair( 155 | self.GRAY_BASE + i, 156 | self.COLOR_WHITE, 157 | self.COLOR_BLACK 158 | ) 159 | 160 | curses.init_pair(self.BLACK, self.COLOR_BLACK, self.COLOR_BLACK) 161 | curses.init_pair(self.WHITE, self.COLOR_WHITE, self.COLOR_BLACK) 162 | curses.init_pair(self.BLUE, self.COLOR_BLUE, self.COLOR_BLACK) 163 | curses.init_pair(self.RED, self.COLOR_RED, self.COLOR_BLACK) 164 | curses.init_pair(self.GREEN, self.COLOR_GREEN, self.COLOR_BLACK) 165 | 166 | def gray(self, scale): 167 | if curses.can_change_color(): 168 | return curses.color_pair(self.GRAY_BASE + int(round(scale * (self.GRAYS - 1)))) 169 | else: 170 | return curses.color_pair(self.WHITE) 171 | 172 | def box(self, window, x, y, w, h, color): 173 | for i in xrange(1, w - 1): 174 | window.addch(y, x + i, curses.ACS_HLINE, color) 175 | window.addch(y + h - 1, x + i, curses.ACS_HLINE, color) 176 | for i in xrange(1, h - 1): 177 | window.addch(y + i, x, curses.ACS_VLINE, color) 178 | window.addch(y + i, x + w - 1, curses.ACS_VLINE, color) 179 | window.addch(y, x, curses.ACS_ULCORNER, color) 180 | window.addch(y, x + w - 1, curses.ACS_URCORNER, color) 181 | window.addch(y + h - 1, x, curses.ACS_LLCORNER, color) 182 | window.addch(y + h - 1, x + w - 1, curses.ACS_LRCORNER, color) 183 | 184 | def bracket(self, window, x, y, h, color): 185 | for i in xrange(1, h - 1): 186 | window.addch(y + i, x, curses.ACS_VLINE, color) 187 | window.addch(y, x, curses.ACS_ULCORNER, color) 188 | window.addch(y + h - 1, x, curses.ACS_LLCORNER, color) 189 | 190 | def vaddstr(self, window, x, y, s, color): 191 | for i in xrange(0, len(s)): 192 | window.addch(y + i, x, s[i], color) 193 | 194 | def vscrollbar(self, window, x, y, height, progress, color): 195 | if height < 3: 196 | return 197 | self.vaddstr(window, x, y, "-" + " " * (height - 2) + "-", color) 198 | window.addch(int(y + 1 + progress * (height - 2)), x, "|", color) 199 | 200 | def hscrollbar(self, window, x, y, width, color): 201 | window.addstr(y, x, "}", color) 202 | window.addstr(y + height - 1, x, '}', color) 203 | window.addch(y, int(x + 1 + progress * (width - 2)), curses.ACS_BLOCK, color) 204 | 205 | -------------------------------------------------------------------------------- /injector.c: -------------------------------------------------------------------------------- 1 | /* x86 instruction injector */ 2 | /* domas // @xoreaxeaxeax */ 3 | 4 | /* some logic in the fault handler requires compiling without optimizations */ 5 | 6 | #define _GNU_SOURCE 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | /* configuration */ 28 | 29 | struct { 30 | /* main limit on search is # of prefixes to explore */ 31 | bool allow_dup_prefix; 32 | int max_prefix; 33 | int brute_depth; 34 | long seed; 35 | int range_bytes; 36 | bool show_tick; 37 | int jobs; 38 | bool force_core; 39 | int core; 40 | /* run as root to allow access to [0]. this will allow most memory accesses 41 | * generated by the injector to succeed rather than fault, which permits 42 | * improved fault analysis (e.g. sigsegv will preempt sigfpe; eliminating 43 | * the initial sigsegv will allow reception of the more descriptive signals) */ 44 | bool enable_null_access; 45 | bool nx_support; 46 | } config={ 47 | .allow_dup_prefix=false, 48 | .max_prefix=0, 49 | .brute_depth=4, 50 | .seed=0, 51 | .range_bytes=0, 52 | .show_tick=false, 53 | .jobs=1, 54 | .force_core=false, 55 | .core=0, 56 | .enable_null_access=false, 57 | .nx_support=true, 58 | }; 59 | 60 | /* capstone */ 61 | 62 | #define USE_CAPSTONE true /* sifter offloads some capstone work to injector */ 63 | 64 | #if USE_CAPSTONE 65 | #include 66 | #if __x86_64__ 67 | #define CS_MODE CS_MODE_64 68 | #else 69 | #define CS_MODE CS_MODE_32 70 | #endif 71 | #endif 72 | 73 | #if USE_CAPSTONE 74 | csh capstone_handle; 75 | cs_insn *capstone_insn; 76 | #endif 77 | 78 | /* 32 vs 64 */ 79 | 80 | #if __x86_64__ 81 | #define IP REG_RIP 82 | #else 83 | #define IP REG_EIP 84 | #endif 85 | 86 | /* leave state as 0 */ 87 | /* : encourages instructions to access a consistent address (0) */ 88 | /* : avoids crashing the injector (e.g. incidental write to .data) */ 89 | /* only change when necessary for synthesizing specific instructions */ 90 | #if __x86_64__ 91 | typedef struct { 92 | uint64_t rax; 93 | uint64_t rbx; 94 | uint64_t rcx; 95 | uint64_t rdx; 96 | uint64_t rsi; 97 | uint64_t rdi; 98 | uint64_t r8; 99 | uint64_t r9; 100 | uint64_t r10; 101 | uint64_t r11; 102 | uint64_t r12; 103 | uint64_t r13; 104 | uint64_t r14; 105 | uint64_t r15; 106 | uint64_t rbp; 107 | uint64_t rsp; 108 | } state_t; 109 | state_t inject_state={ 110 | .rax=0, 111 | .rbx=0, 112 | .rcx=0, 113 | .rdx=0, 114 | .rsi=0, 115 | .rdi=0, 116 | .r8=0, 117 | .r9=0, 118 | .r10=0, 119 | .r11=0, 120 | .r12=0, 121 | .r13=0, 122 | .r14=0, 123 | .r15=0, 124 | .rbp=0, 125 | .rsp=0, 126 | }; 127 | #else 128 | typedef struct { 129 | uint32_t eax; 130 | uint32_t ebx; 131 | uint32_t ecx; 132 | uint32_t edx; 133 | uint32_t esi; 134 | uint32_t edi; 135 | uint32_t ebp; 136 | uint32_t esp; 137 | } state_t; 138 | state_t inject_state={ 139 | .eax=0, 140 | .ebx=0, 141 | .ecx=0, 142 | .edx=0, 143 | .esi=0, 144 | .edi=0, 145 | .ebp=0, 146 | .esp=0, 147 | }; 148 | #endif 149 | 150 | /* helpers */ 151 | 152 | #define STR(x) #x 153 | #define XSTR(x) STR(x) 154 | 155 | /* x86/64 */ 156 | 157 | #define UD2_SIZE 2 158 | #define PAGE_SIZE 4096 159 | #define TF 0x100 160 | 161 | /* injection */ 162 | 163 | #define USE_TF true /* leave true, except when synthesizing some specific instructions */ 164 | 165 | typedef enum { BRUTE, RAND, TUNNEL, DRIVEN } search_mode_t; 166 | search_mode_t mode=TUNNEL; 167 | 168 | void* packet_buffer; 169 | char* packet; 170 | 171 | static char stack[SIGSTKSZ]; 172 | stack_t ss = { .ss_size = SIGSTKSZ, .ss_sp = stack, }; 173 | 174 | struct { 175 | uint64_t dummy_stack_hi[256]; 176 | uint64_t dummy_stack_lo[256]; 177 | } dummy_stack __attribute__ ((aligned(PAGE_SIZE))); 178 | 179 | #define MAX_INSN_LENGTH 15 /* actually 15 */ 180 | 181 | /* fault handler tries to use fault address to make an initial guess of 182 | * instruction length; but length of jump instructions can't be determined from 183 | * trap alone */ 184 | /* set to this if something seems wrong */ 185 | #define JMP_LENGTH 16 186 | 187 | typedef struct { 188 | uint8_t bytes[MAX_INSN_LENGTH]; 189 | int len; /* the number of specified bytes in the instruction */ 190 | } insn_t; 191 | 192 | typedef struct { 193 | insn_t i; 194 | int index; 195 | int last_len; 196 | } inj_t; 197 | inj_t inj; 198 | 199 | static const insn_t null_insn={}; 200 | 201 | mcontext_t fault_context; 202 | 203 | /* feedback */ 204 | 205 | typedef enum { TEXT, RAW } output_t; 206 | output_t output=TEXT; 207 | 208 | #define TICK_MASK 0xffff 209 | 210 | #define RAW_REPORT_INSN_BYTES 16 211 | 212 | #define RAW_REPORT_DISAS_MNE false /* sifter assumes false */ 213 | #define RAW_REPORT_DISAS_MNE_BYTES 16 214 | #define RAW_REPORT_DISAS_OPS false /* sifter assumes false */ 215 | #define RAW_REPORT_DISAS_OPS_BYTES 32 216 | #define RAW_REPORT_DISAS_LEN true /* sifter assumes true */ 217 | #define RAW_REPORT_DISAS_VAL true /* sifter assumes true */ 218 | 219 | typedef struct __attribute__ ((packed)) { 220 | uint32_t valid; 221 | uint32_t length; 222 | uint32_t signum; 223 | uint32_t si_code; 224 | uint32_t addr; 225 | } result_t; 226 | result_t result; 227 | 228 | typedef struct __attribute__ ((packed)) { 229 | #if RAW_REPORT_DISAS_MNE 230 | char mne[RAW_REPORT_DISAS_MNE_BYTES]; 231 | #endif 232 | #if RAW_REPORT_DISAS_OPS 233 | char ops[RAW_REPORT_DISAS_OPS_BYTES]; 234 | #endif 235 | #if RAW_REPORT_DISAS_LEN 236 | int len; 237 | #endif 238 | #if RAW_REPORT_DISAS_VAL 239 | int val; 240 | #endif 241 | } disas_t; 242 | disas_t disas; 243 | 244 | /* blacklists */ 245 | 246 | #define MAX_BLACKLIST 128 247 | 248 | typedef struct { 249 | char* opcode; 250 | char* reason; 251 | } ignore_op_t; 252 | 253 | ignore_op_t opcode_blacklist[MAX_BLACKLIST]={ 254 | { "\x0f\x34", "sysenter" }, 255 | { "\x0f\xa1", "pop fs" }, 256 | { "\x0f\xa9", "pop gs" }, 257 | { "\x8e", "mov seg" }, 258 | { "\xc8", "enter" }, 259 | #if !__x86_64__ 260 | /* vex in 64 (though still can be vex in 32...) */ 261 | { "\xc5", "lds" }, 262 | { "\xc4", "les" }, 263 | #endif 264 | { "\x0f\xb2", "lss" }, 265 | { "\x0f\xb4", "lfs" }, 266 | { "\x0f\xb5", "lgs" }, 267 | #if __x86_64__ 268 | /* 64 bit only - intel "discourages" using this without a rex* prefix, and 269 | * so capstone doesn't parse it */ 270 | { "\x63", "movsxd" }, 271 | #endif 272 | /* segfaulting with various "mov sp" (always sp) in random synthesizing, too 273 | * tired to figure out why: 66 bc7453 */ 274 | { "\xbc", "mov sp" }, 275 | /* segfaulting with "shr sp, 1" (always sp) in random synthesizing, too tired to 276 | * figure out why: 66 d1ec */ 277 | /* haven't observed but assuming "shl sp, 1" and "sar sp, 1" fault as well */ 278 | { "\xd1\xec", "shr sp, 1" }, 279 | { "\xd1\xe4", "shl sp, 1" }, 280 | { "\xd1\xfc", "sar sp, 1" }, 281 | /* same with "rcr sp, 1", assuming same for rcl, ror, rol */ 282 | { "\xd1\xdc", "rcr sp, 1" }, 283 | { "\xd1\xd4", "rcl sp, 1" }, 284 | { "\xd1\xcc", "ror sp, 1" }, 285 | { "\xd1\xc4", "rol sp, 1" }, 286 | /* same with lea sp */ 287 | { "\x8d\xa2", "lea sp" }, 288 | /* i guess these are because if you shift esp, you wind up way outside your 289 | * address space; if you shift sp, just a little, you stay in and crash */ 290 | /* unable to resolve a constant length for xbegin, causes tunnel to stall */ 291 | { "\xc7\xf8", "xbegin" }, 292 | /* int 80 will obviously cause issues */ 293 | { "\xcd\x80", "int 0x80" }, 294 | /* as will syscall */ 295 | { "\x0f\x05", "syscall" }, 296 | /* ud2 is an undefined opcode, and messes up a length differential search 297 | * b/c of the fault it throws */ 298 | { "\x0f\xb9", "ud2" }, 299 | { NULL, NULL } 300 | }; 301 | 302 | typedef struct { 303 | char* prefix; 304 | char* reason; 305 | } ignore_pre_t; 306 | 307 | ignore_pre_t prefix_blacklist[]={ 308 | #if !__x86_64__ 309 | /* avoid overwriting tls or something in 32 bit code */ 310 | { "\x65", "gs" }, 311 | #endif 312 | { NULL, NULL } 313 | }; 314 | 315 | /* search ranges */ 316 | 317 | typedef struct { insn_t start; insn_t end; bool started; } range_t; 318 | insn_t* range_marker=NULL; 319 | range_t search_range={}; 320 | range_t total_range={ 321 | .start={.bytes={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, .len=0}, 322 | .end={.bytes={0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff}, .len=0}, 323 | .started=false 324 | }; 325 | 326 | /* processes */ 327 | 328 | pthread_mutex_t* pool_mutex=NULL; 329 | pthread_mutex_t* output_mutex=NULL; 330 | pthread_mutexattr_t mutex_attr; 331 | 332 | /* syncronized output */ 333 | 334 | #define LINE_BUFFER_SIZE 256 335 | #define BUFFER_LINES 16 336 | #define SYNC_LINES_STDOUT BUFFER_LINES /* must be <= BUFFER_LINES */ 337 | #define SYNC_LINES_STDERR BUFFER_LINES /* must be <= BUFFER_LINES */ 338 | char stdout_buffer[LINE_BUFFER_SIZE*BUFFER_LINES]; 339 | char* stdout_buffer_pos=stdout_buffer; 340 | int stdout_sync_counter=0; 341 | char stderr_buffer[LINE_BUFFER_SIZE*BUFFER_LINES]; 342 | char* stderr_buffer_pos=stderr_buffer; 343 | int stderr_sync_counter=0; 344 | 345 | /* functions */ 346 | 347 | #if USE_CAPSTONE 348 | int print_asm(FILE* f); 349 | #endif 350 | void print_mc(FILE*, int); 351 | bool is_prefix(uint8_t); 352 | int prefix_count(void); 353 | bool has_dup_prefix(void); 354 | bool has_opcode(uint8_t*); 355 | bool has_prefix(uint8_t*); 356 | void preamble(void); 357 | void inject(int); 358 | void state_handler(int, siginfo_t*, void*); 359 | void fault_handler(int, siginfo_t*, void*); 360 | void configure_sig_handler(void (*)(int, siginfo_t*, void*)); 361 | void give_result(FILE*); 362 | void usage(void); 363 | bool move_next_instruction(void); 364 | bool move_next_range(void); 365 | 366 | extern char debug, resume, preamble_start, preamble_end; 367 | static int expected_length; 368 | 369 | void sync_fprintf(FILE* f, const char *format, ...) 370 | { 371 | va_list args; 372 | va_start(args, format); 373 | 374 | if (f==stdout) { 375 | stdout_buffer_pos+=vsprintf(stdout_buffer_pos, format, args); 376 | } 377 | else if (f==stderr) { 378 | stderr_buffer_pos+=vsprintf(stderr_buffer_pos, format, args); 379 | } 380 | else { 381 | assert(0); 382 | } 383 | 384 | va_end(args); 385 | } 386 | 387 | void sync_fwrite(const void* ptr, size_t size, size_t count, FILE* f) 388 | { 389 | if (f==stdout) { 390 | memcpy(stdout_buffer_pos, ptr, size*count); 391 | stdout_buffer_pos+=size*count; 392 | } 393 | else if (f==stderr) { 394 | memcpy(stderr_buffer_pos, ptr, size*count); 395 | stderr_buffer_pos+=size*count; 396 | } 397 | else { 398 | assert(0); 399 | } 400 | } 401 | 402 | void sync_fflush(FILE* f, bool force) 403 | { 404 | if (f==stdout) { 405 | stdout_sync_counter++; 406 | if (stdout_sync_counter==SYNC_LINES_STDOUT || force) { 407 | stdout_sync_counter=0; 408 | pthread_mutex_lock(output_mutex); 409 | fwrite(stdout_buffer, stdout_buffer_pos-stdout_buffer, 1, f); 410 | fflush(f); 411 | pthread_mutex_unlock(output_mutex); 412 | stdout_buffer_pos=stdout_buffer; 413 | } 414 | } 415 | else if (f==stderr) { 416 | stderr_sync_counter++; 417 | if (stderr_sync_counter==SYNC_LINES_STDERR || force) { 418 | stderr_sync_counter=0; 419 | pthread_mutex_lock(output_mutex); 420 | fwrite(stderr_buffer, stderr_buffer_pos-stderr_buffer, 1, f); 421 | fflush(f); 422 | pthread_mutex_unlock(output_mutex); 423 | stderr_buffer_pos=stderr_buffer; 424 | } 425 | } 426 | else { 427 | assert(0); 428 | } 429 | } 430 | 431 | void zero_insn_end(insn_t* insn, int marker) 432 | { 433 | int i; 434 | for (i=marker; ibytes[i]=0; 436 | } 437 | } 438 | 439 | bool increment_range(insn_t* insn, int marker) 440 | { 441 | int i=marker-1; 442 | zero_insn_end(insn, marker); 443 | 444 | if (i>=0) { 445 | insn->bytes[i]++; 446 | while (insn->bytes[i]==0) { 447 | i--; 448 | if (i<0) { 449 | break; 450 | } 451 | insn->bytes[i]++; 452 | } 453 | } 454 | 455 | insn->len=marker; 456 | 457 | return i>=0; 458 | } 459 | 460 | void print_insn(FILE* f, insn_t* insn) 461 | { 462 | int i; 463 | for (i=0; ibytes); i++) { 464 | sync_fprintf(f, "%02x", insn->bytes[i]); 465 | } 466 | } 467 | 468 | void print_range(FILE* f, range_t* range) 469 | { 470 | print_insn(f, &range->start); 471 | sync_fprintf(f, ";"); 472 | print_insn(f, &range->end); 473 | } 474 | 475 | /* must call before forking */ 476 | void initialize_ranges(void) 477 | { 478 | if (range_marker==NULL) { 479 | range_marker=mmap(NULL, sizeof *range_marker, 480 | PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); 481 | *range_marker=total_range.start; 482 | } 483 | } 484 | 485 | void free_ranges(void) 486 | { 487 | if (range_marker!=NULL) { 488 | munmap(range_marker, sizeof *range_marker); 489 | } 490 | } 491 | 492 | bool move_next_range(void) 493 | { 494 | bool result=true; 495 | 496 | switch (mode) { 497 | case RAND: 498 | case DRIVEN: 499 | if (search_range.started) { 500 | result=false; 501 | } 502 | else { 503 | search_range=total_range; 504 | } 505 | break; 506 | case BRUTE: 507 | case TUNNEL: 508 | pthread_mutex_lock(pool_mutex); 509 | search_range.started=false; 510 | if (memcmp(range_marker->bytes, total_range.end.bytes, 511 | sizeof(range_marker->bytes))==0) { 512 | /* reached end of range */ 513 | result=false; 514 | } 515 | else { 516 | search_range.start=*range_marker; 517 | search_range.end=*range_marker; 518 | //TODO: there are search bugs here 519 | //#error make sure you don't skip over the first instruction (e.g. 000000...) 520 | //#error there's another error here somewhere... 521 | //somehow take start range from end range.. 522 | //len can mostly be taken from range_bytes WHEN YOU MOVE TO A NEW RANGE but 523 | //needs to be from total_range.start/end.len when you are deriving from that 524 | //right now len is set in total_range, and in increment_range for range.end 525 | if (!increment_range(&search_range.end, config.range_bytes)) { 526 | /* if increment rolled over, set to end */ 527 | search_range.end=total_range.end; 528 | } 529 | else if (memcmp(search_range.end.bytes, 530 | total_range.end.bytes, sizeof(search_range.end.bytes))>0) { 531 | /* if increment moved past end, set to end */ 532 | search_range.end=total_range.end; 533 | } 534 | 535 | *range_marker=search_range.end; 536 | } 537 | pthread_mutex_unlock(pool_mutex); 538 | break; 539 | default: 540 | assert(0); 541 | } 542 | 543 | return result; 544 | } 545 | 546 | #if USE_CAPSTONE 547 | int print_asm(FILE* f) 548 | { 549 | if (output==TEXT) { 550 | uint8_t* code=inj.i.bytes; 551 | size_t code_size=MAX_INSN_LENGTH; 552 | uint64_t address=(uintptr_t)packet_buffer; 553 | 554 | if (cs_disasm_iter( 555 | capstone_handle, 556 | (const uint8_t**)&code, 557 | &code_size, 558 | &address, 559 | capstone_insn) 560 | ) { 561 | sync_fprintf( 562 | f, 563 | "%10s %-45s (%2d)", 564 | capstone_insn[0].mnemonic, 565 | capstone_insn[0].op_str, 566 | (int)(address-(uintptr_t)packet_buffer) 567 | ); 568 | } 569 | else { 570 | sync_fprintf( 571 | f, 572 | "%10s %-45s (%2d)", 573 | "(unk)", 574 | " ", 575 | (int)(address-(uintptr_t)packet_buffer) 576 | ); 577 | } 578 | expected_length=(int)(address-(uintptr_t)packet_buffer); 579 | } 580 | 581 | return 0; 582 | } 583 | #endif 584 | 585 | void print_mc(FILE* f, int length) 586 | { 587 | int i; 588 | bool p=false; 589 | if (!is_prefix(inj.i.bytes[0])) { 590 | sync_fprintf(f, " "); 591 | p=true; 592 | } 593 | for (i=0; i=0x40 && x<=0x4f) /* rex */ 624 | #endif 625 | ; 626 | } 627 | 628 | int prefix_count(void) 629 | { 630 | int i; 631 | for (i=0; i1) { 656 | return true; 657 | } 658 | } 659 | 660 | return false; 661 | } 662 | 663 | //TODO: can't blacklist 00 664 | bool has_opcode(uint8_t* op) 665 | { 666 | int i, j; 667 | for (i=0; i=MAX_INSN_LENGTH || op[j]!=inj.i.bytes[i+j]) { 672 | return false; 673 | } 674 | j++; 675 | } while (op[j]); 676 | 677 | return true; 678 | } 679 | } 680 | return false; 681 | } 682 | 683 | 684 | //TODO: can't blacklist 00 685 | bool has_prefix(uint8_t* pre) 686 | { 687 | int i, j; 688 | for (i=0; iuc_mcontext; 853 | ((ucontext_t*)p)->uc_mcontext.gregs[IP]+=UD2_SIZE; 854 | } 855 | 856 | void fault_handler(int signum, siginfo_t* si, void* p) 857 | { 858 | int insn_length; 859 | ucontext_t* uc=(ucontext_t*)p; 860 | int preamble_length=(&preamble_end-&preamble_start); 861 | 862 | if (!USE_TF) { preamble_length=0; } 863 | 864 | /* make an initial estimate on the instruction length from the fault address */ 865 | insn_length= 866 | (uintptr_t)uc->uc_mcontext.gregs[IP]-(uintptr_t)packet-preamble_length; 867 | 868 | if (insn_length<0) { 869 | insn_length=JMP_LENGTH; 870 | } 871 | else if (insn_length>MAX_INSN_LENGTH) { 872 | insn_length=JMP_LENGTH; 873 | } 874 | 875 | result=(result_t){ 876 | 1, 877 | insn_length, 878 | signum, 879 | si->si_code, 880 | (signum==SIGSEGV||signum==SIGBUS)?(uint32_t)(uintptr_t)si->si_addr:(uint32_t)-1 881 | }; 882 | 883 | memcpy(uc->uc_mcontext.gregs, fault_context.gregs, sizeof(fault_context.gregs)); 884 | uc->uc_mcontext.gregs[IP]=(uintptr_t)&resume; 885 | uc->uc_mcontext.gregs[REG_EFL]&=~TF; 886 | } 887 | 888 | void configure_sig_handler(void (*handler)(int, siginfo_t*, void*)) 889 | { 890 | struct sigaction s; 891 | 892 | s.sa_sigaction=handler; 893 | s.sa_flags=SA_SIGINFO|SA_ONSTACK; 894 | 895 | sigfillset(&s.sa_mask); 896 | 897 | sigaction(SIGILL, &s, NULL); 898 | sigaction(SIGSEGV, &s, NULL); 899 | sigaction(SIGFPE, &s, NULL); 900 | sigaction(SIGBUS, &s, NULL); 901 | sigaction(SIGTRAP, &s, NULL); 902 | } 903 | 904 | /* note: this does not provide an even distribution */ 905 | void get_rand_insn_in_range(range_t* r) 906 | { 907 | static uint8_t inclusive_end[MAX_INSN_LENGTH]; 908 | int i; 909 | bool all_max=true; 910 | bool all_min=true; 911 | 912 | memcpy(inclusive_end, &r->end.bytes, MAX_INSN_LENGTH); 913 | i=MAX_INSN_LENGTH-1; 914 | while (i>=0) { 915 | inclusive_end[i]--; 916 | if (inclusive_end[i]!=0xff) { 917 | break; 918 | } 919 | i--; 920 | } 921 | 922 | for (i=0; istart.bytes[i]+1)+r->start.bytes[i]; 926 | } 927 | else if (all_max) { 928 | inj.i.bytes[i]= 929 | rand()%(inclusive_end[i]+1); 930 | } 931 | else if (all_min) { 932 | inj.i.bytes[i]= 933 | rand()%(256-r->start.bytes[i])+r->start.bytes[i]; 934 | } 935 | else { 936 | inj.i.bytes[i]= 937 | rand()%256; 938 | } 939 | all_max=all_max&&(inj.i.bytes[i]==inclusive_end[i]); 940 | all_min=all_min&&(inj.i.bytes[i]==r->start.bytes[i]); 941 | } 942 | } 943 | 944 | void init_inj(const insn_t* new_insn) 945 | { 946 | inj.i=*new_insn; 947 | inj.index=-1; 948 | inj.last_len=-1; 949 | } 950 | 951 | bool move_next_instruction(void) 952 | { 953 | int i; 954 | 955 | switch (mode) { 956 | case RAND: 957 | if (!search_range.started) { 958 | init_inj(&null_insn); 959 | get_rand_insn_in_range(&search_range); 960 | } 961 | else { 962 | get_rand_insn_in_range(&search_range); 963 | } 964 | break; 965 | case BRUTE: 966 | if (!search_range.started) { 967 | init_inj(&search_range.start); 968 | inj.index=config.brute_depth-1; 969 | } 970 | else { 971 | for (inj.index=config.brute_depth-1; inj.index>=0; inj.index--) { 972 | inj.i.bytes[inj.index]++; 973 | if (inj.i.bytes[inj.index]) { 974 | break; 975 | } 976 | } 977 | } 978 | break; 979 | case TUNNEL: 980 | if (!search_range.started) { 981 | init_inj(&search_range.start); 982 | inj.index=search_range.start.len; 983 | } 984 | else { 985 | /* not a perfect algorithm; should really look at length 986 | * patterns of oher bytes at current index, not "last" length; 987 | * also situations in which this may not dig deep enough, should 988 | * really be looking at no length changes for n bytes, not just 989 | * last byte. but it's good enough for now. */ 990 | 991 | /* if the last iteration changed the instruction length, go deeper */ 992 | /* but not if we're already as deep as the instruction goes */ 993 | //TODO: should also count a change in the signal as a reason to 994 | //go deeper 995 | if (result.length!=inj.last_len && inj.index=0 && inj.i.bytes[inj.index]==0) { 1003 | inj.index--; 1004 | if (inj.index>=0) { 1005 | inj.i.bytes[inj.index]++; 1006 | } 1007 | /* new tunnel level, reset length */ 1008 | inj.last_len=-1; 1009 | } 1010 | } 1011 | break; 1012 | case DRIVEN: 1013 | i=MAX_INSN_LENGTH; 1014 | do { 1015 | i-=fread(inj.i.bytes, 1, i, stdin); 1016 | } while (i>0); 1017 | break; 1018 | default: 1019 | assert(0); 1020 | } 1021 | search_range.started=true; 1022 | 1023 | i=0; 1024 | while (opcode_blacklist[i].opcode) { 1025 | if (has_opcode((uint8_t*)opcode_blacklist[i].opcode)) { 1026 | switch (output) { 1027 | case TEXT: 1028 | sync_fprintf(stdout, "x: "); print_mc(stdout, 16); 1029 | sync_fprintf(stdout, "... (%s)\n", opcode_blacklist[i].reason); 1030 | sync_fflush(stdout, false); 1031 | break; 1032 | case RAW: 1033 | result=(result_t){0,0,0,0,0}; 1034 | give_result(stdout); 1035 | break; 1036 | default: 1037 | assert(0); 1038 | } 1039 | return move_next_instruction(); 1040 | } 1041 | i++; 1042 | } 1043 | 1044 | i=0; 1045 | while (prefix_blacklist[i].prefix) { 1046 | if (has_prefix((uint8_t*)prefix_blacklist[i].prefix)) { 1047 | switch (output) { 1048 | case TEXT: 1049 | sync_fprintf(stdout, "x: "); print_mc(stdout, 16); 1050 | sync_fprintf(stdout, "... (%s)\n", prefix_blacklist[i].reason); 1051 | sync_fflush(stdout, false); 1052 | break; 1053 | case RAW: 1054 | result=(result_t){0,0,0,0,0}; 1055 | give_result(stdout); 1056 | break; 1057 | default: 1058 | assert(0); 1059 | } 1060 | return move_next_instruction(); 1061 | } 1062 | i++; 1063 | } 1064 | 1065 | if (prefix_count()>config.max_prefix || 1066 | (!config.allow_dup_prefix && has_dup_prefix())) { 1067 | switch (output) { 1068 | case TEXT: 1069 | sync_fprintf(stdout, "x: "); print_mc(stdout, 16); 1070 | sync_fprintf(stdout, "... (%s)\n", "prefix violation"); 1071 | sync_fflush(stdout, false); 1072 | break; 1073 | case RAW: 1074 | result=(result_t){0,0,0,0,0}; 1075 | give_result(stdout); 1076 | break; 1077 | default: 1078 | assert(0); 1079 | } 1080 | return move_next_instruction(); 1081 | } 1082 | 1083 | /* early exit */ 1084 | /* check if we are at, or past, the end instruction */ 1085 | if (memcmp(inj.i.bytes, search_range.end.bytes, sizeof(inj.i.bytes))>=0) { 1086 | return false; 1087 | } 1088 | 1089 | /* search based exit */ 1090 | switch (mode) { 1091 | case RAND: 1092 | return true; 1093 | case BRUTE: 1094 | return inj.index>=0; 1095 | case TUNNEL: 1096 | return inj.index>=0; 1097 | case DRIVEN: 1098 | return true; 1099 | default: 1100 | assert(0); 1101 | } 1102 | } 1103 | 1104 | void give_result(FILE* f) 1105 | { 1106 | uint8_t* code; 1107 | size_t code_size; 1108 | uint64_t address; 1109 | switch (output) { 1110 | case TEXT: 1111 | switch (mode) { 1112 | case BRUTE: 1113 | case TUNNEL: 1114 | case RAND: 1115 | case DRIVEN: 1116 | sync_fprintf(f, " %s", expected_length==result.length?" ":"."); 1117 | sync_fprintf(f, "r: (%2d) ", result.length); 1118 | if (result.signum==SIGILL) { sync_fprintf(f, "sigill "); } 1119 | if (result.signum==SIGSEGV) { sync_fprintf(f, "sigsegv"); } 1120 | if (result.signum==SIGFPE) { sync_fprintf(f, "sigfpe "); } 1121 | if (result.signum==SIGBUS) { sync_fprintf(f, "sigbus "); } 1122 | if (result.signum==SIGTRAP) { sync_fprintf(f, "sigtrap"); } 1123 | sync_fprintf(f, " %3d ", result.si_code); 1124 | sync_fprintf(f, " %08x ", result.addr); 1125 | print_mc(f, result.length); 1126 | sync_fprintf(f, "\n"); 1127 | break; 1128 | default: 1129 | assert(0); 1130 | } 1131 | break; 1132 | case RAW: 1133 | #if USE_CAPSTONE 1134 | code=inj.i.bytes; 1135 | code_size=MAX_INSN_LENGTH; 1136 | address=(uintptr_t)packet_buffer; 1137 | 1138 | if (cs_disasm_iter( 1139 | capstone_handle, 1140 | (const uint8_t**)&code, 1141 | &code_size, 1142 | &address, 1143 | capstone_insn) 1144 | ) { 1145 | #if RAW_REPORT_DISAS_MNE 1146 | strncpy(disas.mne, capstone_insn[0].mnemonic, RAW_DISAS_MNEMONIC_BYTES); 1147 | #endif 1148 | #if RAW_REPORT_DISAS_OPS 1149 | strncpy(disas.ops, capstone_insn[0].op_str, RAW_DISAS_OP_BYTES); 1150 | #endif 1151 | #if RAW_REPORT_DISAS_LEN 1152 | disas.len=(int)(address-(uintptr_t)packet_buffer); 1153 | #endif 1154 | #if RAW_REPORT_DISAS_VAL 1155 | disas.val=true; 1156 | #endif 1157 | } 1158 | else { 1159 | #if RAW_REPORT_DISAS_MNE 1160 | strncpy(disas.mne, "(unk)", RAW_DISAS_MNEMONIC_BYTES); 1161 | #endif 1162 | #if RAW_REPORT_DISAS_OPS 1163 | strncpy(disas.ops, " ", RAW_DISAS_OP_BYTES); 1164 | #endif 1165 | #if RAW_REPORT_DISAS_LEN 1166 | disas.len=(int)(address-(uintptr_t)packet_buffer); 1167 | #endif 1168 | #if RAW_REPORT_DISAS_VAL 1169 | disas.val=false; 1170 | #endif 1171 | } 1172 | #if RAW_REPORT_DISAS_MNE || RAW_REPORT_DISAS_OPS || RAW_REPORT_DISAS_LEN 1173 | sync_fwrite(&disas, sizeof(disas), 1, stdout); 1174 | #endif 1175 | #endif 1176 | sync_fwrite(inj.i.bytes, RAW_REPORT_INSN_BYTES, 1, stdout); 1177 | sync_fwrite(&result, sizeof(result), 1, stdout); 1178 | /* fflush(stdout); */ 1179 | break; 1180 | default: 1181 | assert(0); 1182 | } 1183 | sync_fflush(stdout, false); 1184 | } 1185 | 1186 | void usage(void) 1187 | { 1188 | printf("injector [-b|-r|-t|-d] [-R|-T] [-x] [-0] [-D] [-N]\n"); 1189 | printf("\t[-s seed] [-B brute_depth] [-P max_prefix]\n"); 1190 | printf("\t[-i instruction] [-e instruction]\n"); 1191 | printf("\t[-c core] [-X blacklist]\n"); 1192 | printf("\t[-j jobs] [-l range_bytes]\n"); 1193 | } 1194 | 1195 | void help(void) 1196 | { 1197 | printf("injector [OPTIONS...]\n"); 1198 | printf("\t[-b|-r|-t|-d] ....... mode: brute, random, tunnel, directed (default: tunnel)\n"); 1199 | printf("\t[-R|-T] ............. output: raw, text (default: text)\n"); 1200 | printf("\t[-x] ................ show tick (default: %d)\n", config.show_tick); 1201 | printf("\t[-0] ................ allow null dereference (requires sudo) (default: %d)\n", config.enable_null_access); 1202 | printf("\t[-D] ................ allow duplicate prefixes (default: %d)\n", config.allow_dup_prefix); 1203 | printf("\t[-N] ................ no nx bit support (default: %d)\n", config.nx_support); 1204 | printf("\t[-s seed] ........... in random search, seed (default: time(0))\n"); 1205 | printf("\t[-B brute_depth] .... in brute search, maximum search depth (default: %d)\n", config.brute_depth); 1206 | printf("\t[-P max_prefix] ..... maximum number of prefixes to search (default: %d)\n", config.max_prefix); 1207 | printf("\t[-i instruction] .... instruction at which to start search, inclusive (default: 0)\n"); 1208 | printf("\t[-e instruction] .... instruction at which to end search, exclusive (default: ff..ff)\n"); 1209 | printf("\t[-c core] ........... core on which to perform search (default: any)\n"); 1210 | printf("\t[-X blacklist] ...... blacklist the specified instruction\n"); 1211 | printf("\t[-j jobs] ........... number of simultaneous jobs to run (default: %d)\n", config.jobs); 1212 | printf("\t[-l range_bytes] .... number of base instruction bytes in each sub range (default: %d)\n", config.range_bytes); 1213 | } 1214 | 1215 | void init_config(int argc, char** argv) 1216 | { 1217 | int c, i, j; 1218 | opterr=0; 1219 | bool seed_given=false; 1220 | while ((c=getopt(argc,argv,"?brtdRTx0Ns:DB:P:S:i:e:c:X:j:l:"))!=-1) { 1221 | switch (c) { 1222 | case '?': 1223 | help(); 1224 | exit(-1); 1225 | break; 1226 | case 'b': 1227 | mode=BRUTE; 1228 | break; 1229 | case 'r': 1230 | mode=RAND; 1231 | break; 1232 | case 't': 1233 | mode=TUNNEL; 1234 | break; 1235 | case 'd': 1236 | mode=DRIVEN; 1237 | break; 1238 | case 'R': 1239 | output=RAW; 1240 | break; 1241 | case 'T': 1242 | output=TEXT; 1243 | break; 1244 | case 'x': 1245 | config.show_tick=true; 1246 | break; 1247 | case '0': 1248 | config.enable_null_access=true; 1249 | break; 1250 | case 'N': 1251 | config.nx_support=false; 1252 | break; 1253 | case 's': 1254 | sscanf(optarg, "%ld", &config.seed); 1255 | seed_given=true; 1256 | break; 1257 | case 'P': 1258 | sscanf(optarg, "%d", &config.max_prefix); 1259 | break; 1260 | case 'B': 1261 | sscanf(optarg, "%d", &config.brute_depth); 1262 | break; 1263 | case 'D': 1264 | config.allow_dup_prefix=true; 1265 | break; 1266 | case 'i': 1267 | i=0; 1268 | while (optarg[i*2] && optarg[i*2+1] && i=0); 1459 | if (pid==0) { 1460 | break; 1461 | } 1462 | job++; 1463 | } 1464 | 1465 | while (move_next_range()) { 1466 | /* sync_fprintf(stderr, "job: %d // range: ", job); print_range(stderr, &search_range); sync_fprintf(stderr, "\n"); sync_fflush(stderr,true); */ 1467 | while (move_next_instruction()) { 1468 | /* sync_fprintf(stderr, "job: %d // mc: ", job); print_mc(stderr, 16); sync_fprintf(stderr, "\n"); sync_fflush(stderr,true); */ 1469 | pretext(); 1470 | for (i=1; i<=MAX_INSN_LENGTH; i++) { 1471 | inject(i); 1472 | /* approach 1: examine exception type */ 1473 | /* suffers from failure to resolve length when instruction 1474 | * accesses mapped but protected memory (e.g. writes to .text section) */ 1475 | /* si_code = SEGV_ACCERR, SEGV_MAPERR, or undocumented SI_KERNEL */ 1476 | /* SI_KERNEL appears with in, out, hlt, various retf, movabs, mov cr, etc */ 1477 | /* SI_ACCERR is expected when the instruction fetch fails */ 1478 | //if (result.signum!=SIGSEGV || result.si_code!=SEGV_ACCERR) { 1479 | /* approach 2: examine exception address */ 1480 | /* correctly resolves instruction length in most foreseeable 1481 | * situations */ 1482 | if (result.addr!=(uint32_t)(uintptr_t)(packet_buffer+PAGE_SIZE)) { 1483 | break; 1484 | } 1485 | } 1486 | result.length=i; 1487 | give_result(stdout); 1488 | tick(); 1489 | } 1490 | } 1491 | 1492 | sync_fflush(stdout, true); 1493 | sync_fflush(stderr, true); 1494 | 1495 | /* sync_fprintf(stderr, "lazarus!\n"); */ 1496 | 1497 | #if USE_CAPSTONE 1498 | cs_free(capstone_insn, 1); 1499 | cs_close(&capstone_handle); 1500 | #endif 1501 | 1502 | if (config.enable_null_access) { 1503 | munmap(null_p, PAGE_SIZE); 1504 | } 1505 | 1506 | free(packet_buffer_unaligned); 1507 | 1508 | if (pid!=0) { 1509 | for (i=0; i 3: 149 | op_str = dis[3] 150 | else: 151 | op_str = "" 152 | 153 | if mnemonic == "db": 154 | mnemonic = "(unk)" 155 | insn = "" 156 | op_str = "" 157 | size = len(insn)/2 158 | 159 | return (mnemonic, op_str, size) 160 | 161 | # objdump disassembler 162 | # (objdump breaks unnecessary prefixes onto its own line, which makes parsing 163 | # the output difficult. really only useful with the -P0 flag to disallow 164 | # prefixes) 165 | def disas_objdump(b): 166 | with open("/dev/shm/shifter", "w") as f: 167 | f.write(b) 168 | if arch == "64": 169 | dis, errors = subprocess.Popen("objdump -D --insn-width=256 -b binary \ 170 | -mi386 -Mx86-64 /dev/shm/shifter | head -8 | tail -1", 171 | stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate() 172 | else: 173 | dis, errors = subprocess.Popen("objdump -D --insn-width=256 -b binary \ 174 | -mi386 /dev/shm/shifter | head -8 | tail -1", 175 | stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate() 176 | dis = dis[6:] # address 177 | raw = dis[:256*3].replace(" ","") 178 | dis = dis[256*3:].strip().split(None, 2) 179 | mnemonic = dis[0] 180 | if len(dis) > 1: 181 | op_str = dis[1] 182 | else: 183 | op_str = "" 184 | if mnemonic == "(bad)": 185 | mnemonic = "(unk)" 186 | insn = "" 187 | op_str = "" 188 | size = len(raw)/2 189 | return (mnemonic, op_str, size) 190 | 191 | def cstr2py(s): 192 | return ''.join([chr(x) for x in s]) 193 | 194 | # targeting python 2.6 support 195 | def int_to_comma(x): 196 | if type(x) not in [type(0), type(0L)]: 197 | raise TypeError("Parameter must be an integer.") 198 | if x < 0: 199 | return '-' + int_to_comma(-x) 200 | result = '' 201 | while x >= 1000: 202 | x, r = divmod(x, 1000) 203 | result = ",%03d%s" % (r, result) 204 | return "%d%s" % (x, result) 205 | 206 | def result_string(insn, result): 207 | s = "%30s %2d %2d %2d %2d (%s)\n" % ( 208 | hexlify(insn), result.valid, 209 | result.length, result.signum, 210 | result.sicode, hexlify(cstr2py(result.raw_insn))) 211 | return s 212 | 213 | class Injector: 214 | process = None 215 | settings = None 216 | command = None 217 | 218 | def __init__(self, settings): 219 | self.settings = settings 220 | 221 | def start(self): 222 | self.command = "%s %s -%c -R %s -s %d" % \ 223 | ( 224 | INJECTOR, 225 | " ".join(self.settings.args), 226 | self.settings.synth_mode, 227 | "-0" if self.settings.root else "", 228 | self.settings.seed 229 | ) 230 | self.process = subprocess.Popen( 231 | "exec %s" % self.command, 232 | shell=True, 233 | stdout=subprocess.PIPE, 234 | stdin=subprocess.PIPE, 235 | preexec_fn=os.setsid 236 | ) 237 | 238 | def stop(self): 239 | if self.process: 240 | try: 241 | os.killpg(os.getpgid(self.process.pid), signal.SIGTERM) 242 | except OSError: 243 | pass 244 | 245 | class Poll: 246 | SIGILL = 4 247 | SIGSEGV = 11 248 | SIGFPE = 8 249 | SIGBUS = 7 250 | SIGTRAP = 5 251 | 252 | def __init__(self, ts, injector, tests, command_line, sync=False, low_mem=False, search_unk=True, 253 | search_len=False, search_dis=False, search_ill=False, disassembler=disas_capstone): 254 | self.ts = ts 255 | self.injector = injector 256 | self.T = tests 257 | self.poll_thread = None 258 | self.sync = sync 259 | self.low_mem = low_mem 260 | self.search_len = search_len 261 | self.search_unk = search_unk 262 | self.search_dis = search_dis 263 | self.search_ill = search_ill 264 | self.disas = disassembler 265 | 266 | if self.sync: 267 | with open(SYNC, "w") as f: 268 | f.write("#\n") 269 | f.write("# %s\n" % command_line) 270 | f.write("# %s\n" % injector.command) 271 | f.write("#\n") 272 | f.write("# cpu:\n") 273 | cpu = get_cpu_info() 274 | for l in cpu: 275 | f.write("# %s\n" % l) 276 | f.write("# %s v l s c\n" % (" " * 28)) 277 | 278 | def start(self): 279 | self.poll_thread = threading.Thread(target=self.poll) 280 | self.poll_thread.start() 281 | 282 | def stop(self): 283 | self.poll_thread.join() 284 | while self.ts.run: 285 | time.sleep(.1) 286 | 287 | def poll(self): 288 | while self.ts.run: 289 | while self.ts.pause: 290 | time.sleep(.1) 291 | 292 | bytes_polled = self.injector.process.stdout.readinto(self.T.r) 293 | 294 | if bytes_polled == sizeof(self.T.r): 295 | self.T.ic = self.T.ic + 1 296 | 297 | error = False 298 | if self.T.r.valid: 299 | if self.search_unk and not self.T.r.disas_known and self.T.r.signum != self.SIGILL: 300 | error = True 301 | if self.search_len and self.T.r.disas_known and self.T.r.disas_length != self.T.r.length: 302 | error = True 303 | if self.search_dis and self.T.r.disas_known \ 304 | and self.T.r.disas_length != self.T.r.length and self.T.r.signum != self.SIGILL: 305 | error = True 306 | if self.search_ill and self.T.r.disas_known and self.T.r.signum == self.SIGILL: 307 | error = True 308 | if error: 309 | insn = cstr2py(self.T.r.raw_insn)[:self.T.r.length] 310 | r = copy.deepcopy(self.T.r) 311 | self.T.al.appendleft(r) 312 | if insn not in self.T.ad: 313 | if not self.low_mem: 314 | self.T.ad[insn] = r 315 | self.T.ac = self.T.ac + 1 316 | if self.sync: 317 | with open(SYNC, "a") as f: 318 | f.write(result_string(insn, self.T.r)) 319 | else: 320 | if self.injector.process.poll() is not None: 321 | self.ts.run = False 322 | break 323 | 324 | class Gui: 325 | TIME_SLICE = .01 326 | GRAY_BASE = 50 327 | TICK_MASK = 0xff 328 | RATE_Q = 100 329 | RATE_FACTOR = 1000 330 | 331 | INDENT = 10 332 | 333 | GRAYS = 50 334 | 335 | BLACK = 1 336 | WHITE = 2 337 | BLUE = 3 338 | RED = 4 339 | GREEN = 5 340 | 341 | COLOR_BLACK = 16 342 | COLOR_WHITE = 17 343 | COLOR_BLUE = 18 344 | COLOR_RED = 19 345 | COLOR_GREEN = 20 346 | 347 | def __init__(self, ts, injector, tests, do_tick, disassembler=disas_capstone): 348 | self.ts = ts; 349 | self.injector = injector 350 | self.T = tests 351 | self.gui_thread = None 352 | self.do_tick = do_tick 353 | self.ticks = 0 354 | 355 | self.last_ins_count = 0 356 | self.delta_log = deque(maxlen=self.RATE_Q) 357 | self.time_log = deque(maxlen=self.RATE_Q) 358 | 359 | self.disas = disassembler 360 | 361 | self.stdscr = curses.initscr() 362 | curses.start_color() 363 | 364 | # doesn't work 365 | # self.orig_colors = [curses.color_content(x) for x in xrange(256)] 366 | 367 | curses.use_default_colors() 368 | curses.noecho() 369 | curses.cbreak() 370 | curses.curs_set(0) 371 | self.stdscr.nodelay(1) 372 | 373 | self.sx = 0 374 | self.sy = 0 375 | 376 | self.init_colors() 377 | 378 | self.stdscr.bkgd(curses.color_pair(self.WHITE)) 379 | 380 | self.last_time = time.time() 381 | 382 | def init_colors(self): 383 | if curses.has_colors() and curses.can_change_color(): 384 | curses.init_color(self.COLOR_BLACK, 0, 0, 0) 385 | curses.init_color(self.COLOR_WHITE, 1000, 1000, 1000) 386 | curses.init_color(self.COLOR_BLUE, 0, 0, 1000) 387 | curses.init_color(self.COLOR_RED, 1000, 0, 0) 388 | curses.init_color(self.COLOR_GREEN, 0, 1000, 0) 389 | 390 | # this will remove flicker, but gives boring colors 391 | ''' 392 | self.COLOR_BLACK = curses.COLOR_BLACK 393 | self.COLOR_WHITE = curses.COLOR_WHITE 394 | self.COLOR_BLUE = curses.COLOR_BLUE 395 | self.COLOR_RED = curses.COLOR_RED 396 | self.COLOR_GREEN = curses.COLOR_GREEN 397 | ''' 398 | 399 | for i in xrange(0, self.GRAYS): 400 | curses.init_color( 401 | self.GRAY_BASE + i, 402 | i * 1000 / (self.GRAYS - 1), 403 | i * 1000 / (self.GRAYS - 1), 404 | i * 1000 / (self.GRAYS - 1) 405 | ) 406 | curses.init_pair( 407 | self.GRAY_BASE + i, 408 | self.GRAY_BASE + i, 409 | self.COLOR_BLACK 410 | ) 411 | 412 | else: 413 | self.COLOR_BLACK = curses.COLOR_BLACK 414 | self.COLOR_WHITE = curses.COLOR_WHITE 415 | self.COLOR_BLUE = curses.COLOR_BLUE 416 | self.COLOR_RED = curses.COLOR_RED 417 | self.COLOR_GREEN = curses.COLOR_GREEN 418 | 419 | for i in xrange(0, self.GRAYS): 420 | curses.init_pair( 421 | self.GRAY_BASE + i, 422 | self.COLOR_WHITE, 423 | self.COLOR_BLACK 424 | ) 425 | 426 | curses.init_pair(self.BLACK, self.COLOR_BLACK, self.COLOR_BLACK) 427 | curses.init_pair(self.WHITE, self.COLOR_WHITE, self.COLOR_BLACK) 428 | curses.init_pair(self.BLUE, self.COLOR_BLUE, self.COLOR_BLACK) 429 | curses.init_pair(self.RED, self.COLOR_RED, self.COLOR_BLACK) 430 | curses.init_pair(self.GREEN, self.COLOR_GREEN, self.COLOR_BLACK) 431 | 432 | def gray(self, scale): 433 | if curses.can_change_color(): 434 | return curses.color_pair(self.GRAY_BASE + int(round(scale * (self.GRAYS - 1)))) 435 | else: 436 | return curses.color_pair(self.WHITE) 437 | 438 | def box(self, window, x, y, w, h, color): 439 | for i in xrange(1, w - 1): 440 | window.addch(y, x + i, curses.ACS_HLINE, color) 441 | window.addch(y + h - 1, x + i, curses.ACS_HLINE, color) 442 | for i in xrange(1, h - 1): 443 | window.addch(y + i, x, curses.ACS_VLINE, color) 444 | window.addch(y + i, x + w - 1, curses.ACS_VLINE, color) 445 | window.addch(y, x, curses.ACS_ULCORNER, color) 446 | window.addch(y, x + w - 1, curses.ACS_URCORNER, color) 447 | window.addch(y + h - 1, x, curses.ACS_LLCORNER, color) 448 | window.addch(y + h - 1, x + w - 1, curses.ACS_LRCORNER, color) 449 | 450 | def bracket(self, window, x, y, h, color): 451 | for i in xrange(1, h - 1): 452 | window.addch(y + i, x, curses.ACS_VLINE, color) 453 | window.addch(y, x, curses.ACS_ULCORNER, color) 454 | window.addch(y + h - 1, x, curses.ACS_LLCORNER, color) 455 | 456 | def vaddstr(self, window, x, y, s, color): 457 | for i in xrange(0, len(s)): 458 | window.addch(y + i, x, s[i], color) 459 | 460 | def draw(self): 461 | try: 462 | self.stdscr.erase() 463 | 464 | # constants 465 | left = self.sx + self.INDENT 466 | top = self.sy 467 | top_bracket_height = self.T.IL 468 | top_bracket_middle = self.T.IL / 2 469 | mne_width = 10 470 | op_width = 45 471 | raw_width = (16*2) 472 | 473 | # render log bracket 474 | self.bracket(self.stdscr, left - 1, top, top_bracket_height + 2, self.gray(1)) 475 | 476 | # render logo 477 | self.vaddstr(self.stdscr, left - 3, top + top_bracket_middle - 5, "sand", self.gray(.2)) 478 | self.vaddstr(self.stdscr, left - 3, top + top_bracket_middle + 5, "sifter", self.gray(.2)) 479 | 480 | # refresh instruction log 481 | synth_insn = cstr2py(self.T.r.raw_insn) 482 | (mnemonic, op_str, size) = self.disas(synth_insn) 483 | self.T.il.append( 484 | ( 485 | mnemonic, 486 | op_str, 487 | self.T.r.length, 488 | "%s" % hexlify(synth_insn) 489 | ) 490 | ) 491 | 492 | # render instruction log 493 | try: 494 | for (i, r) in enumerate(self.T.il): 495 | line = i + self.T.IL - len(self.T.il) 496 | (mnemonic, op_str, length, raw) = r 497 | if i == len(self.T.il) - 1: 498 | # latest instruction 499 | # mnemonic 500 | self.stdscr.addstr( 501 | top + 1 + line, 502 | left, 503 | "%*s " % (mne_width, mnemonic), 504 | self.gray(1) 505 | ) 506 | # operands 507 | self.stdscr.addstr( 508 | top + 1 + line, 509 | left + (mne_width + 1), 510 | "%-*s " % (op_width, op_str), 511 | curses.color_pair(self.BLUE) 512 | ) 513 | # bytes 514 | if self.maxx > left + (mne_width + 1) + (op_width + 1) + (raw_width + 1): 515 | self.stdscr.addstr( 516 | top + 1 + line, 517 | left + (mne_width + 1) + (op_width + 1), 518 | "%s" % raw[0:length * 2], 519 | self.gray(.9) 520 | ) 521 | self.stdscr.addstr( 522 | top + 1 +line, 523 | left + (mne_width + 1) + (op_width + 1) + length * 2, 524 | "%s" % raw[length * 2:raw_width], 525 | self.gray(.3) 526 | ) 527 | else: 528 | # previous instructions 529 | # mnemonic, operands 530 | self.stdscr.addstr( 531 | top + 1 + line, 532 | left, 533 | "%*s %-*s" % (mne_width, mnemonic, op_width, op_str), 534 | self.gray(.5) 535 | ) 536 | # bytes 537 | if self.maxx > left + (mne_width + 1) + (op_width + 1) + (raw_width + 1): 538 | self.stdscr.addstr( 539 | top + 1 + line, 540 | left + (mne_width + 1) + (op_width + 1), 541 | "%s" % raw[0:length * 2], 542 | self.gray(.3) 543 | ) 544 | self.stdscr.addstr( 545 | top + 1 + line, 546 | left + (mne_width + 1) + (op_width + 1) + length * 2, 547 | "%s" % raw[length * 2:raw_width], 548 | self.gray(.1) 549 | ) 550 | except RuntimeError: 551 | # probably the deque was modified by the poller 552 | pass 553 | 554 | # rate calculation 555 | self.delta_log.append(self.T.ic - self.last_ins_count) 556 | self.last_ins_count = self.T.ic 557 | ctime = time.time() 558 | self.time_log.append(ctime - self.last_time) 559 | self.last_time = ctime 560 | rate = int(sum(self.delta_log)/sum(self.time_log)) 561 | 562 | # render timestamp 563 | if self.maxx > left + (mne_width + 1) + (op_width + 1) + (raw_width + 1): 564 | self.vaddstr( 565 | self.stdscr, 566 | left + (mne_width + 1) + (op_width + 1) + (raw_width + 1), 567 | top + 1, 568 | self.T.elapsed(), 569 | self.gray(.5) 570 | ) 571 | 572 | # render injection settings 573 | self.stdscr.addstr(top + 1, left - 8, "%d" % self.injector.settings.root, self.gray(.1)) 574 | self.stdscr.addstr(top + 1, left - 7, "%s" % arch, self.gray(.1)) 575 | self.stdscr.addstr(top + 1, left - 3, "%c" % self.injector.settings.synth_mode, self.gray(.5)) 576 | 577 | # render injection results 578 | self.stdscr.addstr(top + top_bracket_middle, left - 6, "v:", self.gray(.5)) 579 | self.stdscr.addstr(top + top_bracket_middle, left - 4, "%2x" % self.T.r.valid) 580 | self.stdscr.addstr(top + top_bracket_middle + 1, left - 6, "l:", self.gray(.5)) 581 | self.stdscr.addstr(top + top_bracket_middle + 1, left - 4, "%2x" % self.T.r.length) 582 | self.stdscr.addstr(top + top_bracket_middle + 2, left - 6, "s:", self.gray(.5)) 583 | self.stdscr.addstr(top + top_bracket_middle + 2, left - 4, "%2x" % self.T.r.signum) 584 | self.stdscr.addstr(top + top_bracket_middle + 3, left - 6, "c:", self.gray(.5)) 585 | self.stdscr.addstr(top + top_bracket_middle + 3, left - 4, "%2x" % self.T.r.sicode) 586 | 587 | # render instruction count 588 | self.stdscr.addstr(top + top_bracket_height + 2, left, "#", self.gray(.5)) 589 | self.stdscr.addstr(top + top_bracket_height + 2, left + 2, 590 | "%s" % (int_to_comma(self.T.ic)), self.gray(1)) 591 | # render rate 592 | self.stdscr.addstr(top + top_bracket_height + 3, left, 593 | " %d/s%s" % (rate, " " * min(rate / self.RATE_FACTOR, 100)), curses.A_REVERSE) 594 | # render artifact count 595 | self.stdscr.addstr(top + top_bracket_height + 4, left, "#", self.gray(.5)) 596 | self.stdscr.addstr(top + top_bracket_height + 4, left + 2, 597 | "%s" % (int_to_comma(self.T.ac)), curses.color_pair(self.RED)) 598 | 599 | # render artifact log 600 | if self.maxy >= top + top_bracket_height + 5 + self.T.UL + 2: 601 | 602 | # render artifact bracket 603 | self.bracket(self.stdscr, left - 1, top + top_bracket_height + 5, self.T.UL + 2, self.gray(1)) 604 | 605 | # render artifacts 606 | try: 607 | for (i, r) in enumerate(self.T.al): 608 | y = top_bracket_height + 5 + i 609 | insn_hex = hexlify(cstr2py(r.raw_insn)) 610 | 611 | # unexplainable hack to remove some of the unexplainable 612 | # flicker on my console. a bug in ncurses? doesn't 613 | # happen if using curses.COLOR_RED instead of a custom 614 | # red. doesn't happen if using a new random string each 615 | # time; doesn't happen if using a constant string each 616 | # time. only happens with the specific implementation below. 617 | #TODO: on systems with limited color settings, this 618 | # makes the background look like random characters 619 | random_string = ("%02x" % random.randint(0,100)) * (raw_width-2) 620 | self.stdscr.addstr(top + 1 + y, left, random_string, curses.color_pair(self.BLACK)) 621 | 622 | self.stdscr.addstr(top + 1 + y, left + 1, 623 | "%s" % insn_hex[0:r.length * 2], curses.color_pair(self.RED)) 624 | self.stdscr.addstr(top + 1 + y, left + 1 + r.length * 2, 625 | "%s" % insn_hex[r.length * 2:raw_width], self.gray(.25)) 626 | except RuntimeError: 627 | # probably the deque was modified by the poller 628 | pass 629 | 630 | self.stdscr.refresh() 631 | except curses.error: 632 | pass 633 | 634 | def start(self): 635 | self.gui_thread = threading.Thread(target=self.render) 636 | self.gui_thread.start() 637 | 638 | def stop(self): 639 | self.gui_thread.join() 640 | 641 | def checkkey(self): 642 | c = self.stdscr.getch() 643 | if c == ord('p'): 644 | self.ts.pause = not self.ts.pause 645 | elif c == ord('q'): 646 | self.ts.run = False 647 | elif c == ord('m'): 648 | self.ts.pause = True 649 | time.sleep(.1) 650 | self.injector.stop() 651 | self.injector.settings.increment_synth_mode() 652 | self.injector.start() 653 | self.ts.pause = False 654 | 655 | def render(self): 656 | while self.ts.run: 657 | while self.ts.pause: 658 | self.checkkey() 659 | time.sleep(.1) 660 | 661 | (self.maxy,self.maxx) = self.stdscr.getmaxyx() 662 | 663 | self.sx = 1 664 | self.sy = max((self.maxy + 1 - (self.T.IL + self.T.UL + 5 + 2))/2, 0) 665 | 666 | self.checkkey() 667 | 668 | synth_insn = cstr2py(self.T.r.raw_insn) 669 | 670 | if synth_insn and not self.ts.pause: 671 | self.draw() 672 | 673 | if self.do_tick: 674 | self.ticks = self.ticks + 1 675 | if self.ticks & self.TICK_MASK == 0: 676 | with open(TICK, 'w') as f: 677 | f.write("%s" % hexlify(synth_insn)) 678 | 679 | time.sleep(self.TIME_SLICE) 680 | 681 | def get_cpu_info(): 682 | with open("/proc/cpuinfo", "r") as f: 683 | cpu = [l.strip() for l in f.readlines()[:7]] 684 | return cpu 685 | 686 | def dump_artifacts(r, injector, command_line): 687 | global arch 688 | tee = Tee(LOG, "w") 689 | tee.write("#\n") 690 | tee.write("# %s\n" % command_line) 691 | tee.write("# %s\n" % injector.command) 692 | tee.write("#\n") 693 | tee.write("# insn tested: %d\n" % r.ic) 694 | tee.write("# artf found: %d\n" % r.ac) 695 | tee.write("# runtime: %s\n" % r.elapsed()) 696 | tee.write("# seed: %d\n" % injector.settings.seed) 697 | tee.write("# arch: %s\n" % arch) 698 | tee.write("# date: %s\n" % time.strftime("%Y-%m-%d %H:%M:%S")) 699 | tee.write("#\n") 700 | tee.write("# cpu:\n") 701 | 702 | cpu = get_cpu_info() 703 | for l in cpu: 704 | tee.write("# %s\n" % l) 705 | 706 | tee.write("# %s v l s c\n" % (" " * 28)) 707 | for k in sorted(list(r.ad)): 708 | v = r.ad[k] 709 | tee.write(result_string(k, v)) 710 | 711 | def cleanup(gui, poll, injector, ts, tests, command_line, args): 712 | ts.run = False 713 | if gui: 714 | gui.stop() 715 | if poll: 716 | poll.stop() 717 | if injector: 718 | injector.stop() 719 | 720 | ''' 721 | # doesn't work 722 | if gui: 723 | for (i, c) in enumerate(gui.orig_colors): 724 | curses.init_color(i, c[0], c[1], c[2]) 725 | ''' 726 | 727 | curses.nocbreak(); 728 | curses.echo() 729 | curses.endwin() 730 | 731 | dump_artifacts(tests, injector, command_line) 732 | 733 | if args.save: 734 | with open(LAST, "w") as f: 735 | f.write(hexlify(cstr2py(tests.r.raw_insn))) 736 | 737 | sys.exit(0) 738 | 739 | def main(): 740 | global arch 741 | def exit_handler(signal, frame): 742 | cleanup(gui, poll, injector, ts, tests, command_line, args) 743 | 744 | injector = None 745 | poll = None 746 | gui = None 747 | 748 | command_line = " ".join(sys.argv) 749 | 750 | parser = argparse.ArgumentParser() 751 | parser.add_argument("--len", action="store_true", default=False, 752 | help="search for length differences in all instructions (instructions\ 753 | that executed differently than the disassembler expected, or did not\ 754 | exist when the disassembler expected them to)" 755 | ) 756 | parser.add_argument("--dis", action="store_true", default=False, 757 | help="search for length differences in valid instructions (instructions\ 758 | that executed differently than the disassembler expected)" 759 | ) 760 | parser.add_argument("--unk", action="store_true", default=False, 761 | help="search for unknown instructions (instructions that the\ 762 | disassembler doesn't know about but successfully execute)" 763 | ) 764 | parser.add_argument("--ill", action="store_true", default=False, 765 | help="the inverse of --unk, search for invalid disassemblies\ 766 | (instructions that do not successfully execute but that the\ 767 | disassembler acknowledges)" 768 | ) 769 | parser.add_argument("--tick", action="store_true", default=False, 770 | help="periodically write the current instruction to disk" 771 | ) 772 | parser.add_argument("--save", action="store_true", default=False, 773 | help="save search progress on exit" 774 | ) 775 | parser.add_argument("--resume", action="store_true", default=False, 776 | help="resume search from last saved state" 777 | ) 778 | parser.add_argument("--sync", action="store_true", default=False, 779 | help="write search results to disk as they are found" 780 | ) 781 | parser.add_argument("--low-mem", action="store_true", default=False, 782 | help="do not store results in memory" 783 | ) 784 | parser.add_argument("injector_args", nargs=argparse.REMAINDER) 785 | 786 | args = parser.parse_args() 787 | 788 | injector_args = args.injector_args 789 | if "--" in injector_args: injector_args.remove("--") 790 | 791 | if not args.len and not args.unk and not args.dis and not args.ill: 792 | print "warning: no search type (--len, --unk, --dis, --ill) specified, results will not be recorded." 793 | raw_input() 794 | 795 | if args.resume: 796 | if "-i" in injector_args: 797 | print "--resume is incompatible with -i" 798 | sys.exit(1) 799 | 800 | if os.path.exists(LAST): 801 | with open(LAST, "r") as f: 802 | insn = f.read() 803 | injector_args.extend(['-i',insn]) 804 | else: 805 | print "no resume file found" 806 | sys.exit(1) 807 | 808 | if not os.path.exists(OUTPUT): 809 | os.makedirs(OUTPUT) 810 | 811 | injector_bitness, errors = \ 812 | subprocess.Popen( 813 | ['file', INJECTOR], 814 | stdout=subprocess.PIPE, 815 | stderr=subprocess.PIPE 816 | ).communicate() 817 | arch = re.search(r".*(..)-bit.*", injector_bitness).group(1) 818 | 819 | ts = ThreadState() 820 | signal.signal(signal.SIGINT, exit_handler) 821 | 822 | settings = Settings(args.injector_args) 823 | 824 | tests = Tests() 825 | 826 | injector = Injector(settings) 827 | injector.start() 828 | 829 | poll = Poll(ts, injector, tests, command_line, args.sync, 830 | args.low_mem, args.unk, args.len, args.dis, args.ill) 831 | poll.start() 832 | 833 | gui = Gui(ts, injector, tests, args.tick) 834 | gui.start() 835 | 836 | while ts.run: 837 | time.sleep(.1) 838 | 839 | cleanup(gui, poll, injector, ts, tests, command_line, args) 840 | 841 | if __name__ == '__main__': 842 | main() 843 | -------------------------------------------------------------------------------- /summarize.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # we had a much more automated and intelligent approach to reducing the log, but 4 | # could not come up with a reasonable way to differentiate between a modr/m byte 5 | # and an opcode byte. e.g. if the instruction is xxyy..., and changing xx or yy 6 | # changes the instruction length, is yy an opcode byte or a modr/m byte? 7 | # without being able to make this determination, we cannot succinctly summarize 8 | # the instructions. rewrote the summarizer as a more manual tool, which seems 9 | # to give pretty good results without requiring a lot of work or knowledge. 10 | 11 | #TODO: capstone's performance is still terrible. maybe i do need a 12 | # preprocessing step to disassemble everything? i'm afraid that could take 13 | # forever with how slow capstone is 14 | 15 | import subprocess 16 | from capstone import * 17 | from pyutil.progress import progress 18 | from collections import namedtuple 19 | from binascii import * 20 | import sys 21 | import time 22 | import tempfile 23 | import os 24 | import locale 25 | 26 | from gui.gui import * 27 | 28 | # TODO: some of our disassemblers don't allow us to specify a number of 29 | # instructions to disassemble. this can create an issue where, if the 30 | # disassembler believes one instruction is actually two instructions, we have no 31 | # way to know that the disassembler has made a mistake (without relying on 32 | # existing knowledge about the instruction set). for now, this is just a known 33 | # limitation. 34 | 35 | CAPSTONE = "capstone" 36 | 37 | # i wanted to bring in capstone this way so that it would match the 38 | # necessary format of the other disassemblers. however, the performance 39 | # is fairly unusable like this. we'll integrate capstone directly instead 40 | ''' 41 | "capstone": 42 | { 43 | "check_supported": False, 44 | 32: ( 45 | # disassemble 46 | "python disas/capstone_32.py" 47 | " {0}" # disassemble 48 | "| head -1" # grab disassembly 49 | , 50 | # raw 51 | "python disas/capstone_32.py" 52 | " {0}" # disassemble 53 | "| head -2" # grab raw 54 | "| tail -1" 55 | ), 56 | 64: ( 57 | # disassemble 58 | "python disas/capstone_64.py" 59 | " {0}" # disassemble 60 | "| head -1" # grab disassembly 61 | , 62 | # raw 63 | "python disas/capstone_64.py" 64 | " {0}" # disassemble 65 | "| head -2" # grab raw 66 | "| tail -1" 67 | ), 68 | }, 69 | ''' 70 | 71 | # many disassemblers break all or unexpected prefixes onto separate lines, 72 | # we need to combine these into one instruction for meaningful results 73 | disassemblers = { 74 | CAPSTONE: { "check_supported": False, 32: None, 64: None }, 75 | "ndisasm": 76 | { 77 | "check_supported": True, 78 | 32: ( 79 | # disassemble 80 | "ndisasm" 81 | " -b32 {0}" # disassemble 82 | "| tr A-Z a-z" # lowercase 83 | "| sed '/ db /Q'" # stop at invalid byte 84 | "| sed 's/[0-9a-f]* *[0-9a-f]* *//'" # crop instructions 85 | "| awk 'ORS=\" \"'" # join to one line 86 | , 87 | # raw 88 | "ndisasm" 89 | " -b32 {0}" # disassemble 90 | "| tr A-Z a-z" # lowercase 91 | "| sed '/ db /Q'" # stop at invalid byte 92 | "| sed 's/[0-9a-f]* *\\([0-9a-f]*\\) *.*/\\1/'" # crop raw 93 | "| tr -d '\\n'" # join to one line 94 | ), 95 | 64: ( 96 | # disassemble 97 | "ndisasm" 98 | " -b64 {0}" # disassemble 99 | "| tr A-Z a-z" # lowercase 100 | "| sed '/ db /Q'" # stop at invalid byte 101 | "| sed 's/[0-9a-f]* *[0-9a-f]* *//'" # crop instructions 102 | "| awk 'ORS=\" \"'" # join to one line 103 | , 104 | # raw 105 | "ndisasm" 106 | " -b64 {0}" # disassemble 107 | "| tr A-Z a-z" # lowercase 108 | "| sed '/ db /Q'" # stop at invalid byte 109 | "| sed 's/[0-9a-f]* *\\([0-9a-f]*\\) *.*/\\1/'" # crop raw 110 | "| tr -d '\\n'" # join to one line 111 | ), 112 | }, 113 | "objdump": 114 | { 115 | "check_supported": True, 116 | 32: ( 117 | # disassemble 118 | "objdump" 119 | " -D -b binary -mi386 -Mintel --no-show-raw-insn {0}" 120 | "| tr A-Z a-z" # lowercase 121 | "| grep '0:' -A 99" # crop header 122 | "| sed '/.byte /Q'" # stop at invalid byte 123 | "| sed '/(bad)/Q'" # stop at invalid byte 124 | "| sed 's/.*:\\s*//'" # crop instructions 125 | "| awk 'ORS=\" \"'" # join to one line 126 | , 127 | # raw 128 | "objdump" 129 | " -D -b binary -mi386 -Mintel --insn-width=16 {0}" 130 | "| tr A-Z a-z" # lowercase 131 | "| grep '0:' -A 99" # crop header 132 | "| sed '/.byte /Q'" # stop at invalid byte 133 | "| sed '/(bad)/Q'" # stop at invalid byte 134 | "| sed 's/.*:\s*\(\([0-9a-f][0-9a-f] \)*\).*/\1/'" # crop raw 135 | "| tr -d '\\n '" # join to one line and remove spaces 136 | ), 137 | 64: ( 138 | # disassemble 139 | "objdump" 140 | " -D -b binary -mi386 -Mx86-64 -Mintel --no-show-raw-insn {0}" 141 | "| tr A-Z a-z" # lowercase 142 | "| grep '0:' -A 99" # crop header 143 | "| sed '/.byte /Q'" # stop at invalid byte 144 | "| sed '/(bad)/Q'" # stop at invalid byte 145 | "| sed 's/.*:\\s*//'" # crop instructions 146 | "| awk 'ORS=\" \"'" # join to one line 147 | , 148 | # raw 149 | "objdump" 150 | " -D -b binary -mi386 -Mx86-64 -Mintel --insn-width=16 {0}" 151 | "| tr A-Z a-z" # lowercase 152 | "| grep '0:' -A 99" # crop header 153 | "| sed '/.byte /Q'" # stop at invalid byte 154 | "| sed '/(bad)/Q'" # stop at invalid byte 155 | "| sed 's/.*:\\s*\\(\\([0-9a-f][0-9a-f] \\)*\\).*/\\1/'" # crop raw 156 | "| tr -d '\\n '" # join to one line and remove spaces 157 | ), 158 | } 159 | } 160 | supported = {} 161 | 162 | prefixes_32 = [ 163 | 0xf0, # lock 164 | 0xf2, # repne / bound 165 | 0xf3, # rep 166 | 0x2e, # cs / branch taken 167 | 0x36, # ss / branch not taken 168 | 0x3e, # ds 169 | 0x26, # es 170 | 0x64, # fs 171 | 0x65, # gs 172 | 0x66, # data 173 | 0x67, # addr 174 | ] 175 | prefixes_64 = [ 176 | 0x40, # rex 177 | 0x41, 178 | 0x42, 179 | 0x43, 180 | 0x44, 181 | 0x45, 182 | 0x46, 183 | 0x47, 184 | 0x48, 185 | 0x49, 186 | 0x4a, 187 | 0x4b, 188 | 0x4c, 189 | 0x4d, 190 | 0x4e, 191 | 0x4f, 192 | ] 193 | 194 | # capstone 195 | md_32 = Cs(CS_ARCH_X86, CS_MODE_32) 196 | md_64 = Cs(CS_ARCH_X86, CS_MODE_64) 197 | 198 | def disassemble_capstone(arch, data): 199 | if arch == 32: 200 | m = md_32 201 | elif arch == 64: 202 | m = md_64 203 | else: 204 | return ("", "") 205 | 206 | try: 207 | (address, size, mnemonic, op_str) = m.disasm_lite(data, 0, 1).next() 208 | except StopIteration: 209 | mnemonic="(unk)" 210 | op_str="" 211 | size = 0 212 | 213 | return ("%s %s" % (mnemonic, op_str), hexlify(data[:size])) 214 | 215 | signals = { 216 | 1: "sighup", 217 | 2: "sigint", 218 | 3: "sigquit", 219 | 4: "sigill", 220 | 5: "sigtrap", 221 | 6: "sigiot", 222 | 7: "sigbus", 223 | 8: "sigfpe", 224 | 9: "sigkill", 225 | 10: "sigusr1", 226 | 11: "sigsegv", 227 | 12: "sigusr2", 228 | 13: "sigpipe", 229 | 14: "sigalrm", 230 | 15: "sigterm", 231 | 16: "sigstkflt", 232 | 17: "sigchld", 233 | 18: "sigcont", 234 | 19: "sigstop", 235 | 20: "sigtstp", 236 | 21: "sigttin", 237 | 22: "sigttou", 238 | 23: "sigurg", 239 | 24: "sigxcpu", 240 | 25: "sigxfsz", 241 | 26: "sigvtalrm", 242 | 27: "sigprof", 243 | 28: "sigwinch", 244 | 29: "sigio", 245 | 30: "sigpwr", 246 | } 247 | 248 | Result = namedtuple('Result', 'raw long_raw valid length signum sicode') 249 | #TODO: is this hashing well? 250 | CondensedResult = namedtuple('CondensedResult', 'raw valids lengths signums sicodes prefixes') 251 | 252 | ''' 253 | class CondensedResult(object): 254 | raw = None 255 | valids = None 256 | lengths = None 257 | signums = None 258 | sicodes = None 259 | prefixes = None 260 | 261 | def __init__(self, raw, valids, lengths, signums, sicodes, prefixes): 262 | self.raw = raw 263 | self.valids = valids 264 | self.lengths = lengths 265 | self.signums = signums 266 | self.sicodes = sicodes 267 | self.prefixes = prefixes 268 | ''' 269 | 270 | class Processor(object): 271 | processor = "n/a" 272 | vendor_id = "n/a" 273 | cpu_family = "n/a" 274 | model = "n/a" 275 | model_name = "n/a" 276 | stepping = "n/a" 277 | microcode = "n/a" 278 | architecture = 32 279 | 280 | class Catalog(object): 281 | def __init__(self, d, v, base='', count=0, collapsed=True, example='', 282 | valids=(), lengths=(), signums=(), sicodes=(), prefixes=()): 283 | self.d = d # dict 284 | self.v = v # values 285 | self.base = base 286 | self.count = count 287 | self.collapsed = collapsed 288 | self.example = example 289 | self.valids = valids 290 | self.lengths = lengths 291 | self.signums = signums 292 | self.sicodes = sicodes 293 | self.prefixes = prefixes 294 | 295 | def check_disassembler(name): 296 | result, errors = \ 297 | subprocess.Popen( 298 | ['which', name], 299 | stdout=subprocess.PIPE, 300 | stderr=subprocess.PIPE 301 | ).communicate() 302 | return result.strip() != "" 303 | 304 | def disassemble(disassembler, bitness, data): 305 | if supported[disassembler] and disassemblers[disassembler][bitness]: 306 | temp_file = tempfile.NamedTemporaryFile() 307 | temp_file.write(data) 308 | 309 | # disassemble 310 | result, errors = \ 311 | subprocess.Popen( 312 | disassemblers[disassembler][bitness][0].format(temp_file.name), 313 | shell=True, 314 | stdout=subprocess.PIPE, 315 | stderr=subprocess.PIPE 316 | ).communicate() 317 | 318 | disas = cleanup(result) 319 | 320 | # raw 321 | result, errors = \ 322 | subprocess.Popen( 323 | disassemblers[disassembler][bitness][1].format(temp_file.name), 324 | shell=True, 325 | stdout=subprocess.PIPE, 326 | stderr=subprocess.PIPE 327 | ).communicate() 328 | 329 | raw = cleanup(result) 330 | 331 | temp_file.close() 332 | 333 | return (disas, raw) 334 | else: 335 | return (None, None) 336 | 337 | def cleanup(disas): 338 | disas = disas.strip() 339 | disas = disas.replace(',', ', ') 340 | disas = " ".join(disas.split()) 341 | return disas 342 | 343 | def instruction_length(raw): 344 | return len(raw)/2 345 | 346 | def print_catalog(c, depth=0): 347 | for v in c.v: 348 | print " " * (depth) + hexlify(v.raw) + " " + summarize_prefixes(v) 349 | for k in c.d: 350 | print " " * depth + "%02x" % ord(k) + ":" 351 | print_catalog(c.d[k], depth+1) 352 | 353 | def strip_prefixes(i, prefixes): 354 | while i and ord(i[0]) in prefixes: 355 | i = i[1:] 356 | return i 357 | 358 | def get_prefixes(i, prefixes): 359 | p = set() 360 | for b in i: 361 | if ord(b) in prefixes: 362 | p.add(ord(b)) 363 | else: 364 | break 365 | return p 366 | 367 | def summarize(s, f="%02x"): 368 | if not s: 369 | return "" 370 | s = sorted(list(s)) 371 | l = [] 372 | start = s[0] 373 | end = start 374 | for (i, e) in enumerate(s): 375 | if i + 1 < len(s): 376 | if end + 1 == s[i + 1]: 377 | end = s[i + 1] 378 | continue 379 | if start == end: 380 | l.append(f % start) 381 | else: 382 | l.append(f % start + "-" + f % end) 383 | if i + 1 < len(s): 384 | start = s[i + 1] 385 | end = start 386 | return ",".join(l) 387 | 388 | def summarize_prefixes(i): 389 | if 0 in i.prefixes: 390 | prefixes = summarize(i.prefixes - {0}) 391 | if prefixes: 392 | prefixes = "(__," + prefixes + ")" 393 | else: 394 | prefixes = "(__)" 395 | else: 396 | prefixes = "(" + summarize(i.prefixes) + ")" 397 | return prefixes 398 | 399 | def summarize_valids(i): 400 | return "(" + summarize(i.valids, f="%d") + ")" 401 | 402 | def summarize_lengths(i): 403 | return "(" + summarize(i.lengths, f="%d") + ")" 404 | 405 | def summarize_signums(i): 406 | return "(" + summarize(i.signums, f="%d") + ")" 407 | 408 | def summarize_signames(i): 409 | return "(" + ",".join([signals[s] for s in i.signums]) + ")" 410 | 411 | def summarize_sicodes(i): 412 | return "(" + summarize(i.sicodes, f="%d") + ")" 413 | 414 | def merge_sets(instructions, attribute): 415 | s = set() 416 | for i in instructions: 417 | s = s | getattr(i, attribute) 418 | return s 419 | 420 | if __name__ == "__main__": 421 | 422 | #TODO: you need to track the WHOLE byte string and pass that to the 423 | # disassemblers - if the string was SHORTER than the disassembler thought, 424 | # i'm not passing the disas enough infomration to recover what it was 425 | # thinking 426 | 427 | # verify disassemblers are installed 428 | for disassembler in disassemblers: 429 | if disassemblers[disassembler]["check_supported"]: 430 | supported[disassembler] = check_disassembler(disassembler) 431 | else: 432 | supported[disassembler] = True 433 | 434 | instructions = [] 435 | processor = Processor() 436 | 437 | print 438 | print "beginning summarization." 439 | print "note: this process may take up to an hour to complete, please be patient." 440 | print 441 | 442 | print "loading sifter log:" 443 | with open(sys.argv[1], "r") as f: 444 | lines = f.readlines() 445 | f.seek(0) 446 | for (i, l) in enumerate(lines): 447 | progress(i, len(lines)-1, refresh=len(lines)/1000) 448 | if l.startswith("#"): 449 | #TODO: this is not robust 450 | if "arch:" in l and "64" in l: 451 | processor.architecture = 64 452 | elif "processor\t:" in l: 453 | processor.processor = l.split(":",1)[1].strip() 454 | elif "vendor_id\t:" in l: 455 | processor.vendor_id = l.split(":",1)[1].strip() 456 | elif "cpu family\t:" in l: 457 | processor.cpu_family = l.split(":",1)[1].strip() 458 | elif "model\t:" in l: 459 | processor.cpu_family = l.split(":",1)[1].strip() 460 | elif "model name\t:" in l: 461 | processor.model_name = l.split(":",1)[1].strip() 462 | elif "stepping\t:" in l: 463 | processor.stepping = l.split(":",1)[1].strip() 464 | elif "stepping\t:" in l: 465 | processor.microcode = l.split(":",1)[1].strip() 466 | continue 467 | v = l.split() 468 | r = Result(unhexlify(v[0]), unhexlify(v[5].strip("()")), int(v[1]), int(v[2]), int(v[3]), int(v[4])) 469 | instructions.append(r) 470 | 471 | # reduce prefixes 472 | 473 | prefixes = prefixes_32 474 | if processor.architecture == 64: 475 | prefixes.extend(prefixes_64) 476 | 477 | # condense prefixed instructions 478 | print "condensing prefixes:" 479 | all_results = {} # lookup table for condensed result to all results 480 | d = {} # lookup table for base instruction to instruction summary 481 | for (c, i) in enumerate(instructions): 482 | progress(c, len(instructions) - 1, refresh=len(instructions)/1000) 483 | s = strip_prefixes(i.raw, prefixes) 484 | p = get_prefixes(i.raw, prefixes) 485 | if len(s) == len(i.raw): 486 | p.add(0) 487 | if s in d: 488 | d[s].valids.add(i.valid) 489 | #d[s].lengths.add(i.length) 490 | d[s].lengths.add(len(s)) # use the stripped length 491 | d[s].signums.add(i.signum) 492 | d[s].sicodes.add(i.sicode) 493 | [d[s].prefixes.add(x) for x in p] 494 | #TODO: is this taking a long time? 495 | #all_results[d[s]].append(i) 496 | else: 497 | d[s] = CondensedResult( 498 | s, 499 | set([i.valid]), 500 | #set([i.length]), 501 | set([len(s)]), 502 | set([i.signum]), 503 | set([i.sicode]), 504 | p 505 | ) 506 | #all_results[d[s]] = [i] 507 | instructions = list(d.values()) 508 | 509 | def bin(instructions, index, base="", bin_progress=0, progress_out_of=None): 510 | valids = merge_sets(instructions, 'valids') 511 | lengths = merge_sets(instructions, 'lengths') 512 | signums = merge_sets(instructions, 'signums') 513 | sicodes = merge_sets(instructions, 'sicodes') 514 | prefixes = merge_sets(instructions, 'prefixes') 515 | 516 | c = Catalog({}, [], base=base, count=len(instructions), collapsed=True, 517 | example=instructions[0].raw, valids=valids, lengths=lengths, 518 | signums=signums, sicodes=sicodes, prefixes=prefixes) 519 | 520 | if not progress_out_of: 521 | progress_out_of = len(instructions) 522 | for i in instructions: 523 | if len(i.raw) > index: 524 | b = i.raw[index] 525 | if b in c.d: 526 | c.d[b].append(i) 527 | else: 528 | c.d[b] = [i] 529 | else: 530 | c.v.append(i) 531 | bin_progress = bin_progress + 1 532 | progress(bin_progress, progress_out_of, refresh=progress_out_of/1000) 533 | for b in c.d: 534 | (c.d[b], bin_progress) = bin(c.d[b], index + 1, base + b, bin_progress, progress_out_of) 535 | return (c, bin_progress) 536 | 537 | print "binning results:" 538 | (c,_) = bin(instructions, 0) 539 | 540 | # open first catalog entries 541 | c.collapsed = False 542 | 543 | # open known 2 byte opcode entries 544 | if '\x0f' in c.d: 545 | c.d['\x0f'].collapsed = False 546 | 547 | #TODO: 548 | # should i break this up into a summarize and browse script 549 | # that way i could pickle the summary results so i don't have to do that 550 | # every time 551 | 552 | #TODO: 553 | # in summary, something like "hardware bug" "software bug" "hidden instruction" 554 | # then in each of the catalog details, summarize that too: "(3) hardware 555 | # bugs, (20) hidden instructions, (0) software bugs" 556 | # the only downside is that requires disassembling everything from the 557 | # start... which could take a long time for any of the non-capstone ones 558 | 559 | #TODO: ideally we would have a map of the opcodes on each vendor, so we 560 | # could conclusively say "this is undocumented" for the target vendor, instead 561 | # of relying on the disassembler. but this gets thorny in a hurry 562 | 563 | def get_solo_leaf(c): 564 | assert c.count == 1 565 | if c.v: 566 | return c.v[0] 567 | else: 568 | return get_solo_leaf(c.d[c.d.keys()[0]]) 569 | 570 | def build_instruction_summary(c, index=0, summary=None, lookup=None): 571 | if not summary: 572 | summary = [] 573 | if not lookup: 574 | lookup = {} 575 | if c.count > 1: 576 | lookup[len(summary)] = c 577 | suffix = ".." * (min(c.lengths) - len(c.base)) + " " + \ 578 | ".." * (max(c.lengths) - min(c.lengths)) 579 | summary.append(" " * index + "> " + hexlify(c.base) + suffix) 580 | if not c.collapsed: 581 | for b in sorted(c.d): 582 | build_instruction_summary(c.d[b], index + 1, summary, lookup) 583 | for v in sorted(c.v): 584 | lookup[len(summary)] = v 585 | summary.append(" " * index + " " + hexlify(v.raw)) 586 | else: 587 | v = get_solo_leaf(c) 588 | lookup[len(summary)] = v 589 | summary.append(" " * index + " " + hexlify(v.raw)) 590 | return (summary, lookup) 591 | 592 | (summary, lookup) = build_instruction_summary(c) 593 | 594 | #TODO scroll window height based on screen height 595 | gui = Gui(no_delay = False) 596 | 597 | textbox = TextBox(gui, gui.window, 1, 3, 35, 30, gui.gray(.1), 598 | summary, gui.gray(.6), curses.color_pair(gui.RED)) 599 | 600 | def draw_infobox(gui, o): 601 | infobox_x = 37 602 | infobox_y = 3 603 | infobox_width = 60 604 | infobox_height = 30 605 | gui.box(gui.window, infobox_x, infobox_y, infobox_width, infobox_height, gui.gray(.3)) 606 | 607 | #TODO: (minor) this should really be done properly with windows 608 | for i in xrange(infobox_y + 1, infobox_y + infobox_height - 1): 609 | gui.window.addstr(i, infobox_x + 1, " " * (infobox_width - 2), gui.gray(0)) 610 | 611 | if type(o) == Catalog: 612 | line = infobox_y + 1 613 | 614 | gui.window.addstr(line, infobox_x + 2, "instruction group:", curses.color_pair(gui.RED)) 615 | line = line + 1 616 | 617 | g = hexlify(o.base) 618 | if not g: 619 | g = "(all)" 620 | gui.window.addstr(line, infobox_x + 2, "%s" % g, gui.gray(1)) 621 | line = line + 1 622 | 623 | line = line + 1 624 | 625 | gui.window.addstr(line, infobox_x + 2, "instructions found in this group:", gui.gray(.5)) 626 | line = line + 1 627 | gui.window.addstr(line, infobox_x + 2, "%d" % o.count, gui.gray(.8)) 628 | line = line + 1 629 | 630 | line = line + 1 631 | 632 | gui.window.addstr(line, infobox_x + 2, "example instruction from this group:", gui.gray(.5)) 633 | line = line + 1 634 | gui.window.addstr(line, infobox_x + 2, "%s" % hexlify(o.example), gui.gray(.8)) 635 | line = line + 1 636 | 637 | line = line + 1 638 | 639 | gui.window.addstr(line, infobox_x + 2, "group attribute summary:", gui.gray(.8)) 640 | line = line + 1 641 | 642 | gui.window.addstr(line, infobox_x + 2, "valid:", gui.gray(.5)) 643 | gui.window.addstr(line, infobox_x + 18, "%-30s" % summarize_valids(o), gui.gray(.8)) 644 | line = line + 1 645 | 646 | gui.window.addstr(line, infobox_x + 2, "length:", gui.gray(.5)) 647 | gui.window.addstr(line, infobox_x + 18, "%-30s" % summarize_lengths(o), gui.gray(.8)) 648 | line = line + 1 649 | 650 | gui.window.addstr(line, infobox_x + 2, "signum:", gui.gray(.5)) 651 | gui.window.addstr(line, infobox_x + 18, "%-30s" % summarize_signums(o), gui.gray(.8)) 652 | line = line + 1 653 | 654 | gui.window.addstr(line, infobox_x + 2, "signal:", gui.gray(.5)) 655 | gui.window.addstr(line, infobox_x + 18, "%-30s" % summarize_signames(o), gui.gray(.8)) 656 | line = line + 1 657 | 658 | gui.window.addstr(line, infobox_x + 2, "sicode:", gui.gray(.5)) 659 | gui.window.addstr(line, infobox_x + 18, "%-30s" % summarize_sicodes(o), gui.gray(.8)) 660 | line = line + 1 661 | 662 | gui.window.addstr(line, infobox_x + 2, "prefixes:", gui.gray(.5)) 663 | gui.window.addstr(line, infobox_x + 18, "%-30s" % summarize_prefixes(o), gui.gray(.8)) 664 | line = line + 1 665 | 666 | elif type(o) == CondensedResult: 667 | line = infobox_y + 1 668 | 669 | gui.window.addstr(line, infobox_x + 2, "instruction:", curses.color_pair(gui.RED)) 670 | line = line + 1 671 | gui.window.addstr(line, infobox_x + 2, "%-30s" % hexlify(o.raw), gui.gray(1)) 672 | line = line + 1 673 | 674 | line = line + 1 675 | 676 | gui.window.addstr(line, infobox_x + 2, "prefixes:", gui.gray(.5)) 677 | gui.window.addstr(line, infobox_x + 18, "%-30s" % summarize_prefixes(o), gui.gray(.8)) 678 | line = line + 1 679 | 680 | gui.window.addstr(line, infobox_x + 2, "valids:", gui.gray(.5)) 681 | gui.window.addstr(line, infobox_x + 18, "%-30s" % summarize_valids(o), gui.gray(.8)) 682 | line = line + 1 683 | 684 | gui.window.addstr(line, infobox_x + 2, "lengths:", gui.gray(.5)) 685 | gui.window.addstr(line, infobox_x + 18, "%-30s" % summarize_lengths(o), gui.gray(.8)) 686 | line = line + 1 687 | 688 | gui.window.addstr(line, infobox_x + 2, "signums:", gui.gray(.5)) 689 | gui.window.addstr(line, infobox_x + 18, "%-30s" % summarize_signums(o), gui.gray(.8)) 690 | line = line + 1 691 | 692 | gui.window.addstr(line, infobox_x + 2, "signals:", gui.gray(.5)) 693 | gui.window.addstr(line, infobox_x + 18, "%-30s" % summarize_signames(o), gui.gray(.8)) 694 | line = line + 1 695 | 696 | gui.window.addstr(line, infobox_x + 2, "sicodes:", gui.gray(.5)) 697 | gui.window.addstr(line, infobox_x + 18, "%-30s" % summarize_sicodes(o), gui.gray(.8)) 698 | line = line + 1 699 | 700 | line = line + 1 701 | 702 | gui.window.addstr(line, infobox_x + 2, "analysis:", curses.color_pair(gui.RED)) 703 | line = line + 1 704 | 705 | for disassembler in sorted(disassemblers): 706 | #TODO: (minor) is there a better way to do this, that doesn't 707 | # involve assuming a prefix 708 | if 0 in o.prefixes: 709 | # a no prefix version observed, use that 710 | dis_data = o.raw 711 | else: 712 | # select a prefixed version as an exemplar instruction 713 | dis_data = chr(next(iter(o.prefixes))) + o.raw 714 | 715 | if disassembler == CAPSTONE: 716 | (asm, raw) = disassemble_capstone(processor.architecture, dis_data) 717 | else: 718 | (asm, raw) = disassemble(disassembler, processor.architecture, dis_data) 719 | if not asm: 720 | asm = "(unknown)" 721 | if not raw: 722 | raw = "n/a" 723 | 724 | gui.window.addstr(line, infobox_x + 2, "%s:" % disassembler, gui.gray(.5)) 725 | line = line + 1 726 | 727 | gui.window.addstr(line, infobox_x + 4, "%-30s" % asm, gui.gray(.8)) 728 | line = line + 1 729 | gui.window.addstr(line, infobox_x + 4, "%-30s" % raw, gui.gray(.5)) 730 | line = line + 1 731 | 732 | line = line + 1 733 | 734 | while True: 735 | 736 | detail_string = \ 737 | "arch: %d / processor: %s / vendor: %s / family: %s / " \ 738 | "model: %s / stepping: %s / ucode: %s" % \ 739 | ( 740 | processor.architecture, 741 | processor.processor, 742 | processor.vendor_id, 743 | processor.cpu_family, 744 | processor.model, 745 | processor.stepping, 746 | processor.microcode, 747 | ) 748 | gui.window.addstr(1, 1, processor.model_name, gui.gray(1)) 749 | gui.window.addstr(2, 1, detail_string, gui.gray(.6)) 750 | 751 | textbox.draw() 752 | 753 | draw_infobox(gui, lookup[textbox.selected_index]) 754 | 755 | gui.window.addstr(33, 1, "j: down, J: DOWN", gui.gray(.4)) 756 | gui.window.addstr(34, 1, "k: up, K: UP", gui.gray(.4)) 757 | gui.window.addstr(35, 1, "l: expand L: all", gui.gray(.4)) 758 | gui.window.addstr(36, 1, "h: collapse H: all", gui.gray(.4)) 759 | gui.window.addstr(37, 1, "g: start G: end", gui.gray(.4)) 760 | gui.window.addstr(38, 1, "{: previous }: next", gui.gray(.4)) 761 | gui.window.addstr(39, 1, "q: quit and print", gui.gray(.4)) 762 | 763 | gui.refresh() 764 | 765 | def smooth_scroll(): 766 | # unnecessary smoother scroll effect 767 | textbox.draw() 768 | draw_infobox(gui, lookup[textbox.selected_index]) 769 | gui.refresh() 770 | time.sleep(.01) 771 | 772 | key = -1 773 | while key == -1: 774 | key = gui.get_key() 775 | if key == ord('k'): 776 | textbox.scroll_up() 777 | elif key == ord('K'): 778 | for _ in xrange(10): 779 | textbox.scroll_up() 780 | smooth_scroll() 781 | elif key == ord('j'): 782 | textbox.scroll_down() 783 | elif key == ord('J'): 784 | for _ in xrange(10): 785 | textbox.scroll_down() 786 | smooth_scroll() 787 | elif key == ord('l'): 788 | i = textbox.selected_index 789 | v = lookup[i] 790 | if type(v) == Catalog: 791 | lookup[i].collapsed = False 792 | (summary, lookup) = build_instruction_summary(c) 793 | textbox.text = summary 794 | elif key == ord('L'): 795 | def expand_all(c): 796 | c.collapsed = False 797 | for b in c.d: 798 | expand_all(c.d[b]) 799 | expand_all(c) 800 | (summary, lookup) = build_instruction_summary(c) 801 | textbox.text = summary 802 | elif key == ord('h'): 803 | i = textbox.selected_index 804 | v = lookup[i] 805 | if type(v) == Catalog: 806 | lookup[i].collapsed = True 807 | (summary, lookup) = build_instruction_summary(c) 808 | textbox.text = summary 809 | elif key == ord('H'): 810 | def collapse_all(c): 811 | c.collapsed = True 812 | for b in c.d: 813 | collapse_all(c.d[b]) 814 | collapse_all(c) 815 | (summary, lookup) = build_instruction_summary(c) 816 | textbox.text = summary 817 | elif key == ord('g'): 818 | textbox.scroll_top() 819 | elif key == ord('G'): 820 | textbox.scroll_bottom() 821 | elif key == ord('{'): 822 | textbox.scroll_up() 823 | while not textbox.at_top() and \ 824 | type(lookup[textbox.selected_index]) != Catalog: 825 | textbox.scroll_up() 826 | smooth_scroll() 827 | elif key == ord('}'): 828 | textbox.scroll_down() 829 | while not textbox.at_bottom() and \ 830 | type(lookup[textbox.selected_index]) != Catalog: 831 | textbox.scroll_down() 832 | smooth_scroll() 833 | elif key == ord('q'): 834 | break 835 | 836 | gui.stop() 837 | 838 | os.system('clear') 839 | 840 | title = "PROCESSOR ANALYSIS SUMMARY" 841 | width = 50 842 | print "=" * width 843 | print " " * ((width - len(title)) / 2) + title 844 | print "=" * width 845 | print 846 | print processor.model_name 847 | print 848 | print " arch: %d" % processor.architecture 849 | print " processor: %s" % processor.processor 850 | print " vendor_id: %s" % processor.vendor_id 851 | print " cpu_family: %s" % processor.cpu_family 852 | print " model: %s" % processor.model 853 | print " stepping: %s" % processor.stepping 854 | print " microcode: %s" % processor.microcode 855 | print 856 | 857 | #TODO: 858 | # high level summary at end: 859 | # undocumented instructions found: x 860 | # software bugs detected: x 861 | # hardware bugs detected: x 862 | for x in summary: 863 | print x 864 | 865 | --------------------------------------------------------------------------------