The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── 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) 
lt; -O3 -Wall -l:libcapstone.a -o $@ -pthread
36 | 
37 | %.o: %.c
38 | 	$(CC) $(CFLAGS) -c 
lt; -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/xoreaxeaxeax/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 <stdio.h>
   8 | #include <unistd.h>
   9 | #include <stdlib.h>
  10 | #include <errno.h>
  11 | #include <string.h>
  12 | #include <signal.h>
  13 | #include <time.h>
  14 | #include <execinfo.h>
  15 | #include <limits.h>
  16 | #include <ucontext.h>
  17 | #include <sys/types.h>
  18 | #include <stdint.h>
  19 | #include <stdbool.h>
  20 | #include <inttypes.h>
  21 | #include <sys/mman.h>
  22 | #include <assert.h>
  23 | #include <sched.h>
  24 | #include <pthread.h>
  25 | #include <sys/wait.h>
  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 <capstone/capstone.h>
  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; i<MAX_INSN_LENGTH; i++) {
 435 | 		insn->bytes[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; i<sizeof(insn->bytes); 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<length && i<MAX_INSN_LENGTH; i++) {
 594 | 		sync_fprintf(f, "%02x", inj.i.bytes[i]);
 595 | 		if (
 596 | 			!p && 
 597 | 			i<MAX_INSN_LENGTH-1 && 
 598 | 			is_prefix(inj.i.bytes[i]) && 
 599 | 			!is_prefix(inj.i.bytes[i+1])
 600 | 			) {
 601 | 			sync_fprintf(f, " ");
 602 | 			p=true;
 603 | 		}
 604 | 	}
 605 | }
 606 | 
 607 | /* this becomes hairy with "mandatory prefix" instructions */
 608 | bool is_prefix(uint8_t x)
 609 | {
 610 | 	return 
 611 | 		x==0xf0 || /* lock */
 612 | 		x==0xf2 || /* repne / bound */
 613 | 		x==0xf3 || /* rep */
 614 | 		x==0x2e || /* cs / branch taken */
 615 | 		x==0x36 || /* ss / branch not taken */
 616 | 		x==0x3e || /* ds */
 617 | 		x==0x26 || /* es */
 618 | 		x==0x64 || /* fs */
 619 | 		x==0x65 || /* gs */
 620 | 		x==0x66 || /* data */
 621 | 		x==0x67    /* addr */
 622 | #if __x86_64__
 623 | 		|| (x>=0x40 && x<=0x4f) /* rex */
 624 | #endif
 625 | 		;
 626 | }
 627 | 
 628 | int prefix_count(void)
 629 | {
 630 | 	int i;
 631 | 	for (i=0; i<MAX_INSN_LENGTH; i++) {
 632 | 		if (!is_prefix(inj.i.bytes[i])) {
 633 | 			return i;
 634 | 		}
 635 | 	}
 636 | 	return i;
 637 | }
 638 | 
 639 | bool has_dup_prefix(void)
 640 | {
 641 | 	int i;
 642 | 	int byte_count[256];
 643 | 	memset(byte_count, 0, 256*sizeof(int));
 644 | 
 645 | 	for (i=0; i<MAX_INSN_LENGTH; i++) {
 646 | 		if (is_prefix(inj.i.bytes[i])) {
 647 | 			byte_count[inj.i.bytes[i]]++;
 648 | 		}
 649 | 		else {
 650 | 			break;
 651 | 		}
 652 | 	}
 653 | 
 654 | 	for (i=0; i<256; i++) {
 655 | 		if (byte_count[i]>1) {
 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; i++) {
 668 | 		if (!is_prefix(inj.i.bytes[i])) {
 669 | 			j=0;
 670 | 			do {
 671 | 				if (i+j>=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; i<MAX_INSN_LENGTH; i++) {
 689 | 		if (is_prefix(inj.i.bytes[i])) {
 690 | 			j=0;
 691 | 			do {
 692 | 				if (inj.i.bytes[i]==pre[j]) {
 693 | 					return true;
 694 | 				}
 695 | 				j++;
 696 | 			} while (pre[j]);
 697 | 		}
 698 | 		else {
 699 | 			return false;
 700 | 		}
 701 | 	}
 702 | 	return false;
 703 | }
 704 | 
 705 | /* gcc doesn't allow naked inline, i hate it */
 706 | void preamble(void)
 707 | {
 708 | #if __x86_64__
 709 | 	__asm__ __volatile__ ("\
 710 | 			.global preamble_start                    \n\
 711 | 			preamble_start:                           \n\
 712 | 			pushfq                                    \n\
 713 | 			orq %0, (%%rsp)                           \n\
 714 | 			popfq                                     \n\
 715 | 			.global preamble_end                      \n\
 716 | 			preamble_end:                             \n\
 717 | 			"
 718 | 			:
 719 | 			:"i"(TF)
 720 | 			);
 721 | #else
 722 | 	__asm__ __volatile__ ("\
 723 | 			.global preamble_start                    \n\
 724 | 			preamble_start:                           \n\
 725 | 			pushfl                                    \n\
 726 | 			orl %0, (%%esp)                           \n\
 727 | 			popfl                                     \n\
 728 | 			.global preamble_end                      \n\
 729 | 			preamble_end:                             \n\
 730 | 			"
 731 | 			:
 732 | 			:"i"(TF)
 733 | 			);
 734 | #endif
 735 | }
 736 | 
 737 | void inject(int insn_size)
 738 | {
 739 | 	/* could probably fork here to avoid risk of destroying the controlling process */
 740 | 	/* only really comes up in random injection, just roll the dice for now */
 741 | 
 742 | 	int i;
 743 | 	int preamble_length=(&preamble_end-&preamble_start);
 744 | 	static bool have_state=false;
 745 | 
 746 | 	if (!USE_TF) { preamble_length=0; }
 747 | 
 748 | 	packet=packet_buffer+PAGE_SIZE-insn_size-preamble_length;
 749 | 
 750 | 	/* optimization - don't bother to write protect page */
 751 | 	//	assert(!mprotect(packet_buffer,PAGE_SIZE,PROT_READ|PROT_WRITE|PROT_EXEC));
 752 | 	for (i=0; i<preamble_length; i++) {
 753 | 		((char*)packet)[i]=((char*)&preamble_start)[i];
 754 | 	}
 755 | 	for (i=0; i<MAX_INSN_LENGTH && i<insn_size; i++) {
 756 | 		((char*)packet)[i+preamble_length]=inj.i.bytes[i];
 757 | 	}
 758 | 	//	assert(!mprotect(packet_buffer,PAGE_SIZE,PROT_READ|PROT_EXEC));
 759 | 
 760 | 	if (config.enable_null_access) {
 761 | 		/* without this we need to blacklist any instruction that modifies esp */
 762 | 		void* p=NULL; /* suppress warning */
 763 | 		memset(p, 0, PAGE_SIZE);
 764 | 	}
 765 | 
 766 | 	dummy_stack.dummy_stack_lo[0]=0;
 767 | 
 768 | 	if (!have_state) {
 769 | 		/* optimization: only get state first time */
 770 | 		have_state=true;
 771 | 		configure_sig_handler(state_handler);
 772 | 		__asm__ __volatile__ ("ud2\n");
 773 | 	}
 774 | 
 775 | 	configure_sig_handler(fault_handler);
 776 | 
 777 | #if __x86_64__
 778 | 	__asm__ __volatile__ ("\
 779 | 			mov %[rax], %%rax \n\
 780 | 			mov %[rbx], %%rbx \n\
 781 | 			mov %[rcx], %%rcx \n\
 782 | 			mov %[rdx], %%rdx \n\
 783 | 			mov %[rsi], %%rsi \n\
 784 | 			mov %[rdi], %%rdi \n\
 785 | 			mov %[r8],  %%r8  \n\
 786 | 			mov %[r9],  %%r9  \n\
 787 | 			mov %[r10], %%r10 \n\
 788 | 			mov %[r11], %%r11 \n\
 789 | 			mov %[r12], %%r12 \n\
 790 | 			mov %[r13], %%r13 \n\
 791 | 			mov %[r14], %%r14 \n\
 792 | 			mov %[r15], %%r15 \n\
 793 | 			mov %[rbp], %%rbp \n\
 794 | 			mov %[rsp], %%rsp \n\
 795 | 			jmp *%[packet]    \n\
 796 | 			"
 797 | 			: /* no output */
 798 | 			: [rax]"m"(inject_state.rax),
 799 | 			  [rbx]"m"(inject_state.rbx),
 800 | 			  [rcx]"m"(inject_state.rcx),
 801 | 			  [rdx]"m"(inject_state.rdx),
 802 | 			  [rsi]"m"(inject_state.rsi),
 803 | 			  [rdi]"m"(inject_state.rdi),
 804 | 			  [r8]"m"(inject_state.r8),
 805 | 			  [r9]"m"(inject_state.r9),
 806 | 			  [r10]"m"(inject_state.r10),
 807 | 			  [r11]"m"(inject_state.r11),
 808 | 			  [r12]"m"(inject_state.r12),
 809 | 			  [r13]"m"(inject_state.r13),
 810 | 			  [r14]"m"(inject_state.r14),
 811 | 			  [r15]"m"(inject_state.r15),
 812 | 			  [rbp]"m"(inject_state.rbp),
 813 | 			  [rsp]"i"(&dummy_stack.dummy_stack_lo),
 814 | 			  [packet]"m"(packet)
 815 | 			);
 816 | #else
 817 | 	__asm__ __volatile__ ("\
 818 | 			mov %[eax], %%eax \n\
 819 | 			mov %[ebx], %%ebx \n\
 820 | 			mov %[ecx], %%ecx \n\
 821 | 			mov %[edx], %%edx \n\
 822 | 			mov %[esi], %%esi \n\
 823 | 			mov %[edi], %%edi \n\
 824 | 			mov %[ebp], %%ebp \n\
 825 | 			mov %[esp], %%esp \n\
 826 | 			jmp *%[packet]    \n\
 827 | 			"
 828 | 			:
 829 | 			:
 830 | 			[eax]"m"(inject_state.eax),
 831 | 			[ebx]"m"(inject_state.ebx),
 832 | 			[ecx]"m"(inject_state.ecx),
 833 | 			[edx]"m"(inject_state.edx),
 834 | 			[esi]"m"(inject_state.esi),
 835 | 			[edi]"m"(inject_state.edi),
 836 | 			[ebp]"m"(inject_state.ebp),
 837 | 			[esp]"i"(&dummy_stack.dummy_stack_lo),
 838 | 			[packet]"m"(packet)
 839 | 			);
 840 | #endif
 841 | 
 842 | 	__asm__ __volatile__ ("\
 843 | 			.global resume   \n\
 844 | 			resume:          \n\
 845 | 			"
 846 | 			);
 847 | 	;
 848 | }
 849 | 
 850 | void state_handler(int signum, siginfo_t* si, void* p)
 851 | {
 852 | 	fault_context=((ucontext_t*)p)->uc_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; i<MAX_INSN_LENGTH; i++) {
 923 | 		if (all_max && all_min) {
 924 | 			inj.i.bytes[i]=
 925 | 				rand()%(inclusive_end[i]-r->start.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<result.length-1) {
 996 | 					inj.index++;
 997 | 				}
 998 | 				inj.last_len=result.length;
 999 | 
1000 | 				inj.i.bytes[inj.index]++;
1001 | 
1002 | 				while (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<MAX_INSN_LENGTH) {
1269 | 					unsigned int k;
1270 | 					sscanf(optarg+i*2, "%02x", &k);
1271 | 					total_range.start.bytes[i]=k;
1272 | 					i++;
1273 | 				}
1274 | 				total_range.start.len=i;
1275 | 				while (i<MAX_INSN_LENGTH) {
1276 | 					total_range.start.bytes[i]=0;
1277 | 					i++;
1278 | 				}
1279 | 				break;
1280 | 			case 'e':
1281 | 				i=0;
1282 | 				while (optarg[i*2] && optarg[i*2+1] && i<MAX_INSN_LENGTH) {
1283 | 					unsigned int k;
1284 | 					sscanf(optarg+i*2, "%02x", &k);
1285 | 					total_range.end.bytes[i]=k;
1286 | 					i++;
1287 | 				}
1288 | 				total_range.end.len=i;
1289 | 				while (i<MAX_INSN_LENGTH) {
1290 | 					total_range.end.bytes[i]=0;
1291 | 					i++;
1292 | 				}
1293 | 				break;
1294 | 			case 'c':
1295 | 				config.force_core=true;
1296 | 				sscanf(optarg, "%d", &config.core);
1297 | 				break;
1298 | 			case 'X':
1299 | 				j=0;
1300 | 				while (opcode_blacklist[j].opcode) {
1301 | 					j++;
1302 | 				}
1303 | 				opcode_blacklist[j].opcode=malloc(strlen(optarg)/2+1);
1304 | 				assert (opcode_blacklist[j].opcode);
1305 | 				i=0;
1306 | 				while (optarg[i*2] && optarg[i*2+1]) {
1307 | 					unsigned int k;
1308 | 					sscanf(optarg+i*2, "%02x", &k);
1309 | 					opcode_blacklist[j].opcode[i]=k;
1310 | 					i++;
1311 | 				}
1312 | 				opcode_blacklist[j].opcode[i]='\0';
1313 | 				opcode_blacklist[j].reason="user_blacklist";
1314 | 				opcode_blacklist[++j]=(ignore_op_t){NULL,NULL};
1315 | 				break;
1316 | 			case 'j':
1317 | 				sscanf(optarg, "%d", &config.jobs);
1318 | 				break;
1319 | 			case 'l':
1320 | 				sscanf(optarg, "%d", &config.range_bytes);
1321 | 				break;
1322 | 			default:
1323 | 				usage();
1324 | 				exit(-1);
1325 | 		}
1326 | 	}
1327 | 
1328 | 	if (optind!=argc) {
1329 | 		usage();
1330 | 		exit(1);
1331 | 	}
1332 | 
1333 | 	if (!seed_given) {
1334 | 		config.seed=time(0);
1335 | 	}
1336 | }
1337 | 
1338 | void pin_core(void)
1339 | {
1340 | 	if (config.force_core) {
1341 | 		cpu_set_t mask;
1342 | 		CPU_ZERO(&mask);
1343 | 		CPU_SET(config.core,&mask);
1344 | 		if (sched_setaffinity(0, sizeof(mask), &mask)) {
1345 | 			printf("error: failed to set cpu\n");
1346 | 			exit(1);
1347 | 		}
1348 | 	}
1349 | }
1350 | 
1351 | void tick(void)
1352 | {
1353 | 	static uint64_t t=0;
1354 | 	if (config.show_tick) {
1355 | 		t++;
1356 | 		if ((t&TICK_MASK)==0) {
1357 | 			sync_fprintf(stderr, "t: ");
1358 | 			print_mc(stderr, 8);
1359 | 			sync_fprintf(stderr, "... ");
1360 | 			#if USE_CAPSTONE
1361 | 			print_asm(stderr);
1362 | 			sync_fprintf(stderr, "\t");
1363 | 			#endif
1364 | 			give_result(stderr);
1365 | 			sync_fflush(stderr, false);
1366 | 		}
1367 | 	}
1368 | }
1369 | 
1370 | void pretext(void)
1371 | {
1372 | 	/* assistive output for analyzing hangs in text mode */
1373 | 	if (output==TEXT) {
1374 | 		sync_fprintf(stdout, "r: ");
1375 | 		print_mc(stdout, 8);
1376 | 		sync_fprintf(stdout, "... ");
1377 | 		#if USE_CAPSTONE
1378 | 		print_asm(stdout);
1379 | 		sync_fprintf(stdout, " ");
1380 | 		#endif
1381 | 		sync_fflush(stdout, false);
1382 | 	}
1383 | }
1384 | 
1385 | int main(int argc, char** argv)
1386 | {
1387 | 	int pid;
1388 | 	int job=0;
1389 | 	int i;
1390 | 	void* packet_buffer_unaligned;
1391 | 	void* null_p;
1392 | 
1393 | 	pthread_mutexattr_init(&mutex_attr);
1394 | 	pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
1395 | 	pool_mutex=mmap(NULL, sizeof *pool_mutex, 
1396 | 			PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
1397 | 	output_mutex=mmap(NULL, sizeof *output_mutex,
1398 | 			PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
1399 | 	pthread_mutex_init(pool_mutex, &mutex_attr);
1400 | 	pthread_mutex_init(output_mutex, &mutex_attr);
1401 | 
1402 | 	init_config(argc, argv);
1403 | 	pin_core();
1404 | 
1405 | 	srand(config.seed);
1406 | 
1407 | 	packet_buffer_unaligned=malloc(PAGE_SIZE*3);
1408 | 	packet_buffer=(void*)
1409 | 		(((uintptr_t)packet_buffer_unaligned+(PAGE_SIZE-1))&~(PAGE_SIZE-1));
1410 | 	assert(!mprotect(packet_buffer,PAGE_SIZE,PROT_READ|PROT_WRITE|PROT_EXEC));
1411 | 	if (config.nx_support) {
1412 | 		/* enabling reads and writes on the following page lets us properly
1413 | 		 * resolve the lengths of some rip-relative instructions that come up
1414 | 		 * during tunneling: e.g. inc (%rip) - if the next page has no
1415 | 		 * permissions at all, the fault from this instruction executing is
1416 | 		 * indistinguishable from the fault of the instruction fetch failing,
1417 | 		 * preventing correct length determination.  allowing read/write ensures
1418 | 		 * 'inc (%rip)' can succeed, so that we can find its length. */
1419 | 		assert(!mprotect(packet_buffer+PAGE_SIZE,PAGE_SIZE,PROT_READ|PROT_WRITE));
1420 | 	}
1421 | 	else {
1422 | 		/* on systems that don't support the no-execute bit, providing
1423 | 		 * read/write access (like above) is the same as providing execute
1424 | 		 * access, so this will not work.  on these systems, provide no access
1425 | 		 * at all - systems without NX will also not support rip-relative
1426 | 		 * addressing, and (with the proper register initializations) should not
1427 | 		 * be able to reach the following page during tunneling style fuzzing */
1428 | 		assert(!mprotect(packet_buffer+PAGE_SIZE,PAGE_SIZE,PROT_NONE));
1429 | 	}
1430 | 
1431 | #if USE_CAPSTONE
1432 | 	if (cs_open(CS_ARCH_X86, CS_MODE, &capstone_handle) != CS_ERR_OK) {
1433 | 		exit(1);
1434 | 	}
1435 | 	capstone_insn = cs_malloc(capstone_handle);
1436 | #endif
1437 | 
1438 | 	if (config.enable_null_access) {
1439 | 		null_p=mmap(0, PAGE_SIZE, PROT_READ|PROT_WRITE,
1440 | 			MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
1441 | 		if (null_p==MAP_FAILED) {
1442 | 			printf("null access requires running as root\n");
1443 | 			exit(1);
1444 | 		}
1445 | 	}
1446 | 
1447 | 	/*
1448 | 	setvbuf(stdout, NULL, _IONBF, 0);
1449 | 	setvbuf(stderr, NULL, _IONBF, 0);
1450 | 	*/
1451 | 
1452 | 	sigaltstack(&ss, 0);
1453 | 
1454 | 	initialize_ranges();
1455 | 
1456 | 	for (i=0; i<config.jobs-1; i++) {
1457 | 		pid=fork();
1458 | 		assert(pid>=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<config.jobs-1; i++) {
1510 | 			wait(NULL);
1511 | 		}
1512 | 		free_ranges();
1513 | 		pthread_mutex_destroy(pool_mutex);
1514 | 		pthread_mutex_destroy(output_mutex);
1515 | 	}
1516 | 
1517 | 	return 0;
1518 | }
1519 | 


--------------------------------------------------------------------------------
/mutator.py:
--------------------------------------------------------------------------------
  1 | # instruction mutator
  2 | 
  3 | #
  4 | # github.com/xoreaxeaxeax/sandsifter // domas // @xoreaxeaxeax
  5 | #
  6 | 
  7 | # 
  8 | # this is a basic example of a mutator to control the x86 injector.  in
  9 | # practice, the tunneling mode of the injector performs far better than any
 10 | # random or mutating strategy can, but this provides a mutation approach if
 11 | # desired.
 12 | #
 13 | 
 14 | import sys
 15 | import subprocess
 16 | import random
 17 | from struct import *
 18 | from capstone import *
 19 | from collections import namedtuple
 20 | from collections import deque
 21 | 
 22 | Result = namedtuple('Result', 'valid length signum sicode')
 23 | 
 24 | class insn:
 25 |     raw = ""
 26 |     processed = False
 27 |     pad = ""
 28 |     mnemonic = ""
 29 |     op_str = ""
 30 |     r = Result(False, 0, 0, 0)
 31 | 
 32 | q = deque()
 33 | 
 34 | SEEDS = 10
 35 | MUTATIONS = 10
 36 | 
 37 | injector = None
 38 | 
 39 | prefixes=[
 40 |     "\xf0", # lock
 41 |     "\xf2", # repne / bound
 42 |     "\xf3", # rep
 43 |     "\x2e", # cs / branch taken
 44 |     "\x36", # ss / branch not taken
 45 |     "\x3e", # ds
 46 |     "\x26", # es
 47 |     "\x64", # fs
 48 |     "\x65", # gs
 49 |     "\x66", # data
 50 |     "\x67"  # addr
 51 |     ]
 52 | 
 53 | injector_bitness, errors = subprocess.Popen(['file', 'injector'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
 54 | arch = re.search(r".*(..)-bit.*", injector_bitness).group(1)
 55 | 
 56 | if arch == "64":
 57 |     # rex prefixes
 58 |     prefixes.extend([
 59 |         "\x40", "\x41", "\x42", "\x43", "\x44", "\x45", "\x46", "\x47",
 60 |         "\x48", "\x49", "\x4a", "\x4b", "\x4c", "\x4d", "\x4e", "\x4f"
 61 |         ])
 62 | 
 63 | def rand_byte():
 64 |     return chr(random.randint(0,255))
 65 | 
 66 | # generate an approximate seed instruction
 67 | # it is probably fine to just randomize the whole thing
 68 | def generate_seed():
 69 |     b=""
 70 | 
 71 |     # prefix
 72 |     if random.randint(0,1)==1:
 73 |         b+=random.choice(prefixes)
 74 | 
 75 |     # opcode
 76 |     o = random.randint(1,3)
 77 |     if o==1:
 78 |         b+=rand_byte()
 79 |     elif o==2:
 80 |         b+="\x0f"
 81 |         b+=rand_byte()
 82 |     elif o==3:
 83 |         b+="\x0f\x38"
 84 |         b+=rand_byte()
 85 | 
 86 |     # modr/m
 87 |     b+=rand_byte()
 88 | 
 89 |     # sib
 90 | 
 91 |     # disp
 92 |     b+="".join(rand_byte() for _ in range(4))
 93 | 
 94 |     # imm
 95 |     b+="".join(rand_byte() for _ in range(4))
 96 | 
 97 |     return b
 98 | 
 99 | def fix(b):
100 |     if len(b) < INSN_BYTES:
101 |         return b + "".join(rand_byte() for _ in range(INSN_BYTES-len(b)))
102 |     else:
103 |         return b[:INSN_BYTES]
104 | 
105 | def mutate(b):
106 |     mutation = random.randint(1,5)
107 | 
108 |     if mutation == 1:
109 |         # insert random byte
110 |         i = random.randint(0,len(b)-1)
111 |         b = b[:i] + rand_byte() + b[i:]
112 |     elif mutation == 2:
113 |         # delete random byte
114 |         i = random.randint(0,len(b)-1)
115 |         b = b[:i] + b[i+1:]
116 |     elif mutation == 3:
117 |         # increment random byte
118 |         i = random.randint(0,len(b)-1)
119 |         b = b[:i] + chr((ord(b[i])+1)%256) + b[i+1:]
120 |     elif mutation == 4:
121 |         # decrement random byte
122 |         i = random.randint(0,len(b)-1)
123 |         b = b[:i] + chr((ord(b[i])-1)%256) + b[i+1:]
124 |     elif mutation == 5:
125 |         # overwrite random byte
126 |         i = random.randint(0,len(b)-1)
127 |         b = b[:i] + rand_byte() + b[i+1:]
128 |     else:
129 |         raise
130 | 
131 |     if not b:
132 |         b = rand_byte()
133 |     
134 |     return b
135 | 
136 | def init_mutator():
137 |     random.seed()
138 |     for i in range(1, SEEDS):
139 |         s = insn()
140 |         s.raw = generate_seed()
141 |         q.append(s)
142 | 
143 | def disas(b):
144 |     try:
145 |         (address, size, mnemonic, op_str) = md.disasm_lite(b, 0x1000, 1).next()
146 |     except StopIteration:
147 |         mnemonic="(unk)"
148 |         op_str=""
149 |     return (mnemonic, op_str)
150 | 
151 | def run(b):
152 |     injector.stdin.write(b)
153 |     o = injector.stdout.read(INSN_BYTES)
154 |     o = injector.stdout.read(4*RET_INTS)
155 |     return Result(*unpack('iiii', o))
156 | 
157 | def process(i):
158 |     i.processed = True
159 |     i.pad = fix(i.raw)
160 |     (i.mnemonic, i.op_str) = disas(i.pad)
161 |     sys.stdout.write("%s ... %10s %-45s " % ("".join("{:02x}".format(ord(c)) for c in i.pad[:8]), i.mnemonic, i.op_str))
162 |     sys.stdout.flush()
163 |     i.r = run(i.pad)
164 |     sys.stdout.write("%3d %3d %3d %3d" % (i.r.valid, i.r.length, i.r.signum, i.r.sicode))
165 |     sys.stdout.flush()
166 | 
167 | 
168 | INSN_BYTES = 32
169 | RET_INTS = 4
170 | 
171 | init_mutator()
172 | 
173 | if arch == "64":
174 |     md = Cs(CS_ARCH_X86, CS_MODE_64)
175 | else:
176 |     md = Cs(CS_ARCH_X86, CS_MODE_32)
177 | 
178 | injector = subprocess.Popen("./injector -d -R", shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
179 | 
180 | while True:
181 |     s = q.popleft()
182 | 
183 |     if not s.processed:
184 |         process(s)
185 | 
186 |     found_new = False
187 | 
188 |     if s.r.valid:
189 |         for i in range(1, MUTATIONS):
190 |             t = insn()
191 |             t.raw = mutate(s.raw)
192 |             process(t)
193 | 
194 |             if t.r.valid:
195 |                 if s.r.length != t.r.length or s.r.signum != t.r.signum or s.r.sicode != t.r.sicode:
196 |                     q.append(t)
197 |                     found_new = True
198 |                     sys.stdout.write(" !")
199 |                 else:
200 |                     sys.stdout.write(" x")
201 |             else:
202 |                 sys.stdout.write(" x")
203 | 
204 |             sys.stdout.write(" %6d" % len(q))
205 |             sys.stdout.write("\n")
206 | 
207 |         if found_new:
208 |             # this was a good seed
209 |             q.append(s)
210 | 
211 | try:
212 |     injector.kill()
213 | except OSError:
214 |     pass
215 | 
216 | 


--------------------------------------------------------------------------------
/pyutil/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xoreaxeaxeax/sandsifter/8375e6123d093629e3e4437d7903839fd0742c2a/pyutil/__init__.py


--------------------------------------------------------------------------------
/pyutil/colors.py:
--------------------------------------------------------------------------------
 1 | class colors:
 2 |     off="\033[0m"
 3 | 
 4 |     black="\033[0;30m"
 5 |     red="\033[0;31m"
 6 |     green="\033[0;32m"
 7 |     yellow="\033[0;33m"
 8 |     blue="\033[0;34m"
 9 |     purple="\033[0;35m"
10 |     cyan="\033[0;36m"
11 |     white="\033[0;37m"
12 | 


--------------------------------------------------------------------------------
/pyutil/progress.py:
--------------------------------------------------------------------------------
 1 | import sys
 2 | 
 3 | PROGRESS_BAR_WIDTH = 40
 4 | PROGRESS_LINE_MAX = 1024
 5 | 
 6 | def progress(i, n, text="", refresh=1, unknown=False):
 7 |     if not n:
 8 |         n = 1
 9 |         i = 1
10 |     if refresh < 1:
11 |         refresh = 1
12 |     if i % refresh == 0 or i == n:
13 |         c = (i * PROGRESS_BAR_WIDTH / n) % (PROGRESS_BAR_WIDTH + 1)
14 |         if unknown:
15 |             bar = " " * (c - 1) + "=" * (1 if c else 0) + " " * (PROGRESS_BAR_WIDTH-c)
16 |             percent = ""
17 |         else:
18 |             bar = "=" * c + " " * (PROGRESS_BAR_WIDTH-c)
19 |             percent = "%5.1f%%" % (float(i) * 100 / n)
20 |         sys.stdout.write("[%s] %s %s   " % ( \
21 |                 bar,
22 |                 percent,
23 |                 "- %s" % text if text else ""
24 |                 ))
25 |         sys.stdout.flush()
26 |         sys.stdout.write("\b" * PROGRESS_LINE_MAX)
27 |         if not unknown and i == n:
28 |             sys.stdout.write("\n")
29 | 


--------------------------------------------------------------------------------
/references/domas_breaking_the_x86_isa.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xoreaxeaxeax/sandsifter/8375e6123d093629e3e4437d7903839fd0742c2a/references/domas_breaking_the_x86_isa.pdf


--------------------------------------------------------------------------------
/references/domas_breaking_the_x86_isa_wp.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xoreaxeaxeax/sandsifter/8375e6123d093629e3e4437d7903839fd0742c2a/references/domas_breaking_the_x86_isa_wp.pdf


--------------------------------------------------------------------------------
/references/sandsifter.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xoreaxeaxeax/sandsifter/8375e6123d093629e3e4437d7903839fd0742c2a/references/sandsifter.gif


--------------------------------------------------------------------------------
/references/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xoreaxeaxeax/sandsifter/8375e6123d093629e3e4437d7903839fd0742c2a/references/screenshot.png


--------------------------------------------------------------------------------
/references/summarizer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xoreaxeaxeax/sandsifter/8375e6123d093629e3e4437d7903839fd0742c2a/references/summarizer.png


--------------------------------------------------------------------------------
/sifter.py:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/python
  2 | 
  3 | # instruction injector frontend
  4 | 
  5 | #
  6 | # github.com/xoreaxeaxeax/sandsifter // domas // @xoreaxeaxeax
  7 | #
  8 | 
  9 | # run as sudo for best results
 10 | 
 11 | import signal
 12 | import sys
 13 | import subprocess
 14 | import os
 15 | from struct import *
 16 | from capstone import *
 17 | from collections import namedtuple
 18 | from collections import deque
 19 | import threading
 20 | import time
 21 | import curses
 22 | from binascii import hexlify
 23 | import re
 24 | import random
 25 | import argparse
 26 | import code
 27 | import copy
 28 | from ctypes import *
 29 | 
 30 | INJECTOR = "./injector"
 31 | arch = ""
 32 | 
 33 | OUTPUT = "./data/"
 34 | LOG  = OUTPUT + "log"
 35 | SYNC = OUTPUT + "sync"
 36 | TICK = OUTPUT + "tick"
 37 | LAST = OUTPUT + "last"
 38 | 
 39 | class ThreadState:
 40 |     pause = False
 41 |     run = True
 42 | 
 43 | class InjectorResults(Structure):
 44 |     _fields_ = [('disas_length', c_int),
 45 |                 ('disas_known', c_int),
 46 |                 ('raw_insn', c_ubyte * 16),
 47 |                 ('valid', c_int),
 48 |                 ('length', c_int),
 49 |                 ('signum', c_int),
 50 |                 ('sicode', c_int),
 51 |                 ('siaddr', c_int),
 52 | 		]
 53 | 
 54 | class Settings:
 55 |     SYNTH_MODE_RANDOM = "r"
 56 |     SYNTH_MODE_BRUTE = "b"
 57 |     SYNTH_MODE_TUNNEL = "t"
 58 |     synth_mode = SYNTH_MODE_RANDOM
 59 |     root = False
 60 |     seed = 0
 61 |     args = ""
 62 | 
 63 |     def __init__(self, args):
 64 |         if "-r" in args:
 65 |             self.synth_mode = self.SYNTH_MODE_RANDOM
 66 |         elif "-b" in args:
 67 |             self.synth_mode = self.SYNTH_MODE_BRUTE
 68 |         elif "-t" in args:
 69 |             self.synth_mode = self.SYNTH_MODE_TUNNEL
 70 |         self.args = args
 71 |         self.root = (os.geteuid() == 0)
 72 |         self.seed = random.getrandbits(32)
 73 | 
 74 |     def increment_synth_mode(self):
 75 |         if self.synth_mode == self.SYNTH_MODE_BRUTE:
 76 |             self.synth_mode = self.SYNTH_MODE_RANDOM
 77 |         elif self.synth_mode == self.SYNTH_MODE_RANDOM:
 78 |             self.synth_mode = self.SYNTH_MODE_TUNNEL
 79 |         elif self.synth_mode == self.SYNTH_MODE_TUNNEL:
 80 |             self.synth_mode = self.SYNTH_MODE_BRUTE
 81 | 
 82 | class Tests:
 83 |     r = InjectorResults() # current result
 84 |     IL=20 # instruction log len
 85 |     UL=10 # artifact log len
 86 |     il = deque(maxlen=IL) # instruction log
 87 |     al = deque(maxlen=UL) # artifact log
 88 |     ad = dict() # artifact dict
 89 |     ic = 0 # instruction count
 90 |     ac = 0 # artifact count
 91 |     start_time = time.time()
 92 | 
 93 |     def elapsed(self):
 94 |         m, s = divmod(time.time() - self.start_time, 60)
 95 |         h, m = divmod(m, 60)
 96 |         return "%02d:%02d:%02d.%02d" % (h, m, int(s), int(100*(s-int(s))) )
 97 | 
 98 | class Tee(object):
 99 |     def __init__(self, name, mode):
100 |         self.file = open(name, mode)
101 |         self.stdout = sys.stdout
102 |         sys.stdout = self
103 |     def __del__(self):
104 |         sys.stdout = self.stdout
105 |         self.file.close()
106 |     def write(self, data):
107 |         self.file.write(data)
108 |         self.stdout.write(data)
109 | 
110 | # capstone disassembler
111 | md = None
112 | def disas_capstone(b):
113 |     global md, arch
114 |     if not md:
115 |         if arch == "64":
116 |             md = Cs(CS_ARCH_X86, CS_MODE_64)
117 |         else:
118 |             md = Cs(CS_ARCH_X86, CS_MODE_32)
119 |     try:
120 |         (address, size, mnemonic, op_str) = md.disasm_lite(b, 0, 1).next()
121 |     except StopIteration:
122 |         mnemonic="(unk)"
123 |         op_str=""
124 |         size = 0
125 |     return (mnemonic, op_str, size)
126 | 
127 | # ndisasm disassembler
128 | # (ndidsasm breaks unnecessary prefixes onto its own line, which makes parsing
129 | # the output difficult.  really only useful with the -P0 flag to disallow
130 | # prefixes)
131 | def disas_ndisasm(b):
132 |     b = ''.join('\\x%02x' % ord(c) for c in b)
133 |     if arch == "64":
134 |         dis, errors = subprocess.Popen("echo -ne '%s' | ndisasm -b64 - | head -2" % b,
135 |                 stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()
136 |     else:
137 |         dis, errors = subprocess.Popen("echo -ne '%s' | ndisasm -b32 - | head -2" % b,
138 |                 stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()
139 |     dis = dis.split("\n")
140 |     extra = dis[1]
141 |     dis = dis[0].split(None, 4)
142 |     if extra.strip()[0] == '-':
143 |         dis[1] = dis[1] + extra.strip()[1:]
144 | 
145 |     address = dis[0]
146 |     insn = dis[1]
147 |     mnemonic = dis[2]
148 |     if len(dis) > 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 | 


--------------------------------------------------------------------------------