3 # BORIS (Binary Object analyzeR for Injection experimentS)
5 # Tool for parsing binaries and finding the relevant instrumentation
6 # points for a fault injection campaign (which is then coordinated by
9 # (c) 2011-2013 Björn Döbel <doebel@os.inf.tu-dresden.de>,
10 # economic rights: Technische Universität Dresden (Germany)
12 # This file is part of TUD:OS and distributed under the terms of the
13 # GNU General Public License 2.
14 # Please see the COPYING-GPL-2 file for details.
19 from pylibelf import libelf
23 #################################################
24 ### Running external programs ###
25 #################################################
29 def __init__(self, tool, options):
31 self.options = [tool] + options
35 r = subprocess.check_output(self.options)
36 except subprocess.CalledProcessError:
37 print "Could not exec %s" % self.tool
42 class NMRunner(ProgramRunner):
44 def __init__(self, binary):
45 ProgramRunner.__init__(self, "nm", ["-C", binary])
48 #################################################
50 #################################################
53 def __init__(self, nmline):
54 (self.address, self.symtype, self.name) = nmline.split(" ", 3)
55 if self.symtype not in ["T", "t"]:
58 self.address = int(self.address, 16)
59 self.end = self.address # don't know better yet
63 return (self.address, self.end)
67 return "[0x%08lx - 0x%08lx] %s" % (self.address, self.end, self.name)
70 return "[0x%08lx - 0x%08lx] %s" % (self.address, self.end, self.name)
73 #################################################
74 ### Specific FI generators ###
75 #################################################
78 for line in NMRunner(self.binary).output().split("\n"):
80 self.funcs.append(Function(line))
84 self.funcs.sort(key = lambda f : f.address)
86 # Now we can go and fix up the end address
87 for i in range(len(self.funcs) - 1):
88 self.funcs[i].end = self.funcs[i+1].address - 1
91 def __init__(self, binary):
97 def run_filter(self, funcnames=[]):
99 Filter out non-interesting functions
101 if funcnames == ["*"]:
104 self.funcs = [x for x in self.funcs if x.name in funcnames]
109 Representation of all executable segments of a file.
111 This object's main structure is a list of (segment, buffer) tuples
112 that contain a libELF segment descriptor and the respective binary
113 buffer read from the file.
115 def __init__(self, binary):
116 self.lib = libelf.Library(binary)
117 self.exec_segments = []
119 f = file(binary, "rb")
120 for s in self.lib.segments:
123 self.exec_segments.append((s, f.read(s.filesize)))
125 self.ud_obj = udis86.init()
126 self.ud_obj.set_mode(udis86.MOD_32)
127 self.ud_obj.set_syntax(udis86.UD_SYN_ATT)
129 def segment_for_address(self, address):
130 for (s, b) in self.exec_segments:
131 if s.virtual_address <= address <= (s.virtual_address + s.filesize):
133 raise ValueError("No segment containing address 0x%08lx" % address)
136 def gen_single_func(self, start, end):
138 (seg, buf) = self.segment_for_address(start)
141 start -= seg.virtual_address # idx used for iterating the input buffer
142 end -= seg.virtual_address # upper limit of the input buffer
144 self.ud_obj.set_pc(pc)
146 # udis python bindings seem weirdly broken: initially, i set buf[start:end]
147 # as the input buffer. however, at some point this broke the input stream,
148 # as the python bindings read something different from what was actually in
149 # the buffer. i figured this is somehow related to the udis input buffer
150 # being too large. therefore we use smaller 32byte chunks now
151 self.ud_obj.set_input_buffer(buf[start:start+32])
152 while start < end and self.ud_obj.disassemble() > 0:
153 if (self.filter_instruction()):
154 ret.append("0x%08lx < %s >" % (pc, self.ud_obj.insn_asm()))
155 #print hex(pc), self.ud_obj.insn_hex(), self.ud_obj.insn_asm()
156 pc += self.ud_obj.insn_len()
157 start += self.ud_obj.insn_len()
158 self.ud_obj.set_pc(pc)
159 self.ud_obj.set_input_buffer(buf[start:start+32])
166 for f in self.functions.funcs:
167 (start, end) = f.bounds()
168 ret += self.gen_single_func(start, end)
170 return [r for r in ret if random.randint(0,99) < self.probability]
174 class ValidInstructionList(BinaryObject):
176 Generates a list of all valid instruction pointers within the binary
178 def __init__(self, funclist, rand_percentage = 20):
179 BinaryObject.__init__(self, funclist.binary)
180 self.functions = funclist
181 self.probability = rand_percentage
184 def filter_instruction(self):
186 Filter out non-interesting operations
188 return self.ud_obj.insn_asm() not in ("nop ", "ret ", "leave ", "invalid ")
191 class ArithInstructionList(ValidInstructionList):
193 def __init__(self, funclist, rand_percentage = 20):
194 ValidInstructionList.__init__(self,funclist,rand_percentage)
196 adc adcb andb addb aaa aad aam aas arpl
197 call clc cld cli cmpb cmpsd cmc cmpsb cmp cdq
198 cmovae cmovbe cmovnz cmovz cwde daa das div divb
199 fmul fdiv fild fldcww fistpw fdivp fldz fucomp fnstsw fld fsub fadd
200 fistp fnstcww fucompp fstp fxch imul idiv in fmulp fidiv fchs fstpq fcomp ficompw
201 fldq fucom fsqrt fiaddw fdivr fcom fsubr fdivrq fnstsww fistpq fistp fimul
202 fimulw fisttpq fmulq fld1
203 int into iretd int1 insd mul mulb
204 ja jae jb jbe jecxz jg jge jl jle jmp jnz jns jo js jz jnp jp jno
205 hlt int3 insb lea lret lock lodsb lodsd ljmp loopnz lcall les lds lahf
207 mov movb movsd movsb movsx movsxb movzx movzxb movzxw movw
209 o16 out outsb outsd pop popad push pushad popfd pushfd
210 sbb seta setbe setg setle setnz setz stc sti sgdt scasb scasd sbbb
211 shrb shlb sldt sahf std sarb subb rorb orb ror rcl salc
212 rclb rcr rep repne ret stosb stosd test testb rol
213 xorb rolb loope rcrb xchg wait xlatb
216 def filter_instruction(self):
217 if not ValidInstructionList.filter_instruction(self):
220 opcode = self.ud_obj.insn_asm().split()[0]
222 if opcode in ["add", "sub", "inc", "incb", "dec", "shl", "shr", "and", "or", "xor",
225 elif opcode in self.invalidops.split():
234 self.mode = "gpr" # alu, rat, instr
236 self.probability = 100 # percent
240 class ArgumentParser:
242 self.conf = Configuration()
243 self.parser = argparse.ArgumentParser(description="Generate list of relevant instructions that can be used to start IGOR runs.")
244 self.parser.add_argument("file", help="binary file", default=None)
245 self.parser.add_argument("-m", "--mode", dest="mode",
246 help="gpr|alu|rat|instr", default = None)
247 self.parser.add_argument("-p", "--prob", dest="probability",
248 default=None, help="fraction of instructions that get selected finally")
249 self.parser.add_argument("-f", "--functions", dest="functions",
250 help="select only a subset of functions (comma-separated list)",
253 def parse(self, args = sys.argv[1:]):
254 ns = self.parser.parse_args(args)
255 assert ns.file is not None
256 self.conf.filename = ns.file
257 if ns.mode is not None:
258 self.conf.mode = ns.mode
259 if ns.probability is not None:
260 self.conf.probability = int(ns.probability)
261 if ns.functions != "":
262 self.conf.functions=ns.functions.split(",")
266 ff = FunctionList(conf.filename)
267 ff.run_filter(conf.functions)
268 lst = ValidInstructionList(ff,conf.probability).generate()
273 ff = FunctionList(conf.filename)
274 ff.run_filter(conf.functions)
275 lst = ArithInstructionList(ff,conf.probability).generate()
281 print "UNIMPLEMENTED:", conf.mode
291 if len(sys.argv) < 2:
294 ap = ArgumentParser()
297 generators[ap.conf.mode](ap.conf)
299 print "Unknown mode:", ap.conf.mode
301 if __name__ == "__main__":