Package rekall :: Package plugins :: Package tools :: Module dynamic_profiles
[frames] | no frames]

Source Code for Module rekall.plugins.tools.dynamic_profiles

  1  # Rekall Memory Forensics 
  2  # 
  3  # Copyright 2015 Google Inc. All Rights Reserved. 
  4  # 
  5  # Authors: 
  6  # Michael Cohen <scudette@google.com> 
  7  # 
  8  # This program is free software; you can redistribute it and/or modify 
  9  # it under the terms of the GNU General Public License as published by 
 10  # the Free Software Foundation; either version 2 of the License, or (at 
 11  # your option) any later version. 
 12  # 
 13  # This program is distributed in the hope that it will be useful, but 
 14  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 15  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 16  # General Public License for more details. 
 17  # 
 18  # You should have received a copy of the GNU General Public License 
 19  # along with this program; if not, write to the Free Software 
 20  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 21  # 
 22   
 23  """This module implements dynamic profiles. 
 24   
 25  A Dynamic profile is a way of discovering certain parameters via running a 
 26  matching signature. 
 27  """ 
 28  from rekall import obj 
 29  from rekall.plugins.tools import disassembler 
 30   
 31   
32 -class DisassembleMatcher(object):
33 """A matching engine for disassembler rules. 34 35 This matcher searcher for a sequence of rules in a disassmbly and tries to 36 match a certain rule pattern to the assembly. Ultimately if the rules match, 37 the rules may extract certain parameters from the patter. 38 """ 39
40 - def __init__(self, name="", mode="AMD64", rules=None, session=None, 41 max_separation=10):
42 self.mode = mode 43 self.name = name 44 self.rules = rules 45 self.session = session 46 self.max_separation = max_separation 47 self.dis = disassembler.Capstone(self.mode, session=self.session)
48
49 - def _CheckCaptureVariables(self, vector, contexts):
50 """Checks that capture variables are consistent in the vector. 51 52 The vector is a list of disassembly lines which match the rules, e.g. 53 54 [16, 60, 61] 55 56 The context is the capture variables from these rules. In order 57 to be valid, the capture variables must all be consistent. For 58 example the following is not consistent (since var1 is RAX in 59 the first rule and RCX in the second rule): 60 61 contexts[16] 62 {'var1': u'RAX'} 63 64 contexts[60] 65 {'var1': u'RCX', 'out': u'0x88'} 66 67 contexts[61] 68 {} 69 """ 70 result = {} 71 for rule_number, item in enumerate(vector): 72 rule_context = contexts[rule_number] 73 # The capture variables in this rule only. 74 rule_capture_vars_values = {} 75 76 for k, v in rule_context[item].iteritems(): 77 var_name = k.rsplit("_", 1)[0] 78 79 # Only consider variables (start with $). 80 if not var_name.startswith("$"): 81 continue 82 83 # If this var is previously known, this match must be the same 84 # as previously found. 85 if var_name in result and v != result[var_name]: 86 return 87 88 # If this capture variable's value is the same as another 89 # capture variable's value in the same rule, exclude the 90 # match. This means that an expression like: 91 # 92 # MOV $var2, [$var1+$out] 93 # 94 # Necessarily implies that $var1 and $var2 must be different 95 # registers. 96 if (v in rule_capture_vars_values and 97 rule_capture_vars_values[v] != var_name): 98 return 99 100 result[var_name] = v 101 rule_capture_vars_values[v] = var_name 102 103 return result
104
105 - def _FindRuleIndex(self, instruction):
106 """Generate all rules that match the current instruction.""" 107 for i, rule in enumerate(self.rules): 108 context = dict( 109 instruction=instruction.text, offset=instruction.address) 110 if instruction.match_rule(rule, context): 111 yield i, context
112
113 - def GenerateVector(self, hits, vector, level):
114 """Generate possible hit vectors which match the rules.""" 115 for item in hits.get(level, []): 116 if vector: 117 if item < vector[-1]: 118 continue 119 120 if item > self.max_separation + vector[-1]: 121 break 122 123 new_vector = vector + [item] 124 125 if level + 1 == len(hits): 126 yield new_vector 127 128 elif level + 1 < len(hits): 129 for result in self.GenerateVector(hits, new_vector, level+1): 130 yield result
131
132 - def _GetMatch(self, hits, contexts):
133 """Find the first vector that matches all the criteria.""" 134 for vector in self.GenerateVector(hits, [], 0): 135 context = self._CheckCaptureVariables(vector, contexts) 136 if not context: 137 continue 138 139 return (vector, context) 140 141 return [], {}
142
143 - def MatchFunction(self, func, length=1000):
144 return self.Match( 145 func.obj_offset, func.obj_vm.read(func.obj_offset, length))
146
147 - def Match(self, offset=0, data=""):
148 hits = {} 149 contexts = {} 150 151 for hit, instruction in enumerate(self.dis.disassemble(data, offset)): 152 for rule_idx, context in self._FindRuleIndex(instruction): 153 hits.setdefault(rule_idx, []).append(hit) 154 contexts.setdefault(rule_idx, {})[hit] = context 155 156 # All the hits must match 157 if len(hits) < len(self.rules): 158 self.session.logging.error("Failed to find match for %s", self.name) 159 160 # Add some debugging messages here to make diagnosing errors easier. 161 for i, rule in enumerate(self.rules): 162 if i not in hits: 163 self.session.logging.debug("Unable to match rule: %s", rule) 164 165 return obj.NoneObject() 166 167 vector, context = self._GetMatch(hits, contexts) 168 169 if len(vector) < len(self.rules): 170 self.session.logging.error( 171 "Failed to find match for %s - Only matched %s/%s rules.", 172 self.name, len(vector), len(self.rules)) 173 return obj.NoneObject() 174 175 self.session.logging.debug("Found match for %s", self.name) 176 177 result = {} 178 for i, hit in enumerate(vector): 179 hit_data = contexts[i][hit] 180 result.update(hit_data) 181 self.session.logging.debug( 182 "%#x %s", hit_data["offset"], hit_data["instruction"]) 183 184 return result
185 186
187 -class DisassembleConstantMatcher(object):
188 """Search for the value of global constants using disassembly.""" 189
190 - def __init__(self, session, profile, name, args):
191 self.session = session 192 self.profile = profile 193 self.args = args 194 self.name = name 195 # Start address to disassemble - can be an exported function name. 196 self.start_address = args["start"] 197 198 # Disassemble capture rules. 199 self.rules = args["rules"]
200
201 - def __call__(self):
202 resolver = self.session.address_resolver 203 func = self.session.profile.Function(resolver.get_address_by_name( 204 self.start_address)) 205 206 matcher = DisassembleMatcher( 207 mode=func.mode, rules=self.rules, name=self.name, 208 session=self.session) 209 210 result = matcher.MatchFunction(func) 211 if result and "$out" in result: 212 return result["$out"]
213 214
215 -class FirstOf(object):
216 """Try a list of callables until one works."""
217 - def __init__(self, list_of_callables, **kwargs):
218 self.list_of_callables = list_of_callables 219 self.kwargs = kwargs
220
221 - def __call__(self, *args):
222 for func in self.list_of_callables: 223 result = func(*args, **self.kwargs) 224 if result != None: 225 return result
226 227
228 -class DynamicConstantProfileLoader(obj.ProfileSectionLoader):
229 """Produce a callable for a constant.""" 230 name = "$DYNAMIC_CONSTANTS" 231
232 - def LoadIntoProfile(self, session, profile, constants):
233 """Parse the constants detectors and make callables.""" 234 for constant_name, rules in constants.items(): 235 detectors = [] 236 237 # Each constant can have several different detectors. 238 for rule in rules: 239 detector_name = rule["type"] 240 detector_arg = rule["args"] 241 242 # We only support one type of detector right now. 243 if detector_name != "DisassembleConstantMatcher": 244 session.logging.error( 245 "Unimplemented detector %s", detector_name) 246 continue 247 248 detectors.append( 249 DisassembleConstantMatcher( 250 session, profile, constant_name, detector_arg)) 251 252 profile.add_constants({constant_name: FirstOf(detectors)}, 253 constants_are_absolute=True) 254 255 return profile
256 257
258 -class DisassembleStructMatcher(DisassembleConstantMatcher):
259 """Match a struct based on rules.""" 260
261 - def __call__(self, struct, member=None):
262 resolver = struct.obj_session.address_resolver 263 func = struct.obj_profile.Function(resolver.get_address_by_name( 264 self.start_address)) 265 266 matcher = DisassembleMatcher( 267 mode=func.mode, rules=self.rules, name=self.name, 268 max_separation=self.args.get("max_separation", 10), 269 session=struct.obj_session) 270 271 struct.obj_session.logging.info( 272 "DisassembleStructMatcher: %s %s", self.name, 273 self.args.get("comment", "")) 274 result = matcher.MatchFunction(func) 275 if result: 276 # Match succeeded - create a new overlay for the Struct. 277 overlay = {self.name: [None, {}]} 278 fields = overlay[self.name][1] 279 for field, field_args in self.args["fields"].iteritems(): 280 fields[field] = [result["$" + field], field_args] 281 282 # This should never happen? 283 if member not in fields: 284 return 285 286 # We calculated the types, now we add them to the profile so the 287 # next time a struct is instantiated it will be properly 288 # initialized. 289 struct.obj_profile.add_types(overlay) 290 291 # Now take care of the current struct which has already been 292 # initialized. 293 struct.members.update(struct.obj_profile.Object(self.name).members) 294 295 # Return the member from the current struct. 296 return struct.m(member)
297 298
299 -class DynamicStructProfileLoader(obj.ProfileSectionLoader):
300 """Produce a callable for a constant.""" 301 name = "$DYNAMIC_STRUCTS" 302
303 - def LoadIntoProfile(self, session, profile, data):
304 """Parse the constants detectors and make callables.""" 305 overlay = {} 306 for struct_name, signatures in data.items(): 307 detectors = {} 308 309 # Each field can have several different detectors. 310 for rule in signatures: 311 detector_name = rule["type"] 312 detector_arg = rule["args"] 313 314 # We only support one type of detector right now. 315 if detector_name != "DisassembleStructMatcher": 316 session.logging.error( 317 "Unimplemented detector %s", detector_name) 318 continue 319 320 detector = DisassembleStructMatcher( 321 None, None, struct_name, detector_arg) 322 323 # Add the detector to each field. The initial detector is a 324 # pass-through which returns the normal member if one is defined 325 # in the conventional way. If None is defined, we launch our 326 # dynamic detector - which will store the conventional member 327 # definitions as a cache. 328 def PassThrough(struct, member=None): 329 return struct.m(member)
330 331 for field in detector_arg["fields"]: 332 detectors.setdefault(field, [PassThrough]).append(detector) 333 334 # Install an overlay with the chain of detectors. 335 overlay[struct_name] = [None, {}] 336 for field in detectors: 337 overlay[struct_name][1][field] = FirstOf( 338 detectors[field], member=field) 339 340 profile.add_overlay(overlay) 341 return profile
342