plympton 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0c809128838470cc20b4ae9b7bac312bd736b33b
4
+ data.tar.gz: a770fb56e21f7c3f0bf1abda87a6873629f87094
5
+ SHA512:
6
+ metadata.gz: 57781fabc6343ce5c5e3fb35dea0ad55525694026d2bbfd2da24302276f2ae9f3af4696c97f45235046d84c0586304097912a3536cfe68c5a39509b52d386386
7
+ data.tar.gz: 6970e51b07c92beead632679de4b653ac6d0d4b82e1f690bb59a38600791049e5e6c6904036c07c2e1fc87038c967096c82c00da5ac1ebbcda70e01572a75eac
data/.coveralls.yml ADDED
@@ -0,0 +1 @@
1
+ service_name: travis-ci
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.0
4
+ - 2.1.2
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source "http://gemcutter.org"
2
+
3
+ # Add dependencies to develop your gem here.
4
+ # Include everything needed to run rake, tests, features, etc.
5
+ group :development do
6
+ gem "jeweler", "~> 2.0"
7
+ gem "yard", "~> 0.8"
8
+ gem "rspec", "~> 3.1"
9
+ gem "bundler", ">= 1.0"
10
+ end
11
+
12
+ gem 'nokogiri', '~> 1.6'
13
+ gem 'antlr3', '~> 1.10'
14
+ gem 'narray', '~> 0.6'
15
+ gem 'coveralls', require: false
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 rogwfu
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # plympton
2
+
3
+ A gem to read program disassembly from a YAML dump. The YAML dump is generated from an IDA Pro python script. This script is included along with this Gem (func.py)
4
+
5
+ ## Status
6
+ [![Build Status](https://travis-ci.org/rogwfu/plympton.png)](https://travis-ci.org/rogwfu/plympton)
7
+ [![Coverage Status](https://coveralls.io/repos/rogwfu/plympton/badge.png)](https://coveralls.io/r/rogwfu/plympton)
8
+ [![Dependency Status](https://www.versioneye.com/user/projects/543603aab2a9c5dd3d000092/badge.svg?style=flat)](https://www.versioneye.com/user/projects/543603aab2a9c5dd3d000092)
9
+
10
+ ## Contributing to plympton
11
+
12
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
13
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
14
+ * Fork the project
15
+ * Start a feature/bugfix branch
16
+ * Commit and push until you are happy with your contribution
17
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
18
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
19
+
20
+ ## Copyright
21
+
22
+ Copyright (c) 2014 Roger Seagle. See LICENSE.txt for
23
+ further details.
24
+
data/Rakefile ADDED
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+ require 'rake'
3
+ require 'jeweler'
4
+
5
+ Jeweler::Tasks.new do |gem|
6
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
7
+ gem.name = "plympton"
8
+ gem.homepage = "http://github.com/rogwfu/plympton"
9
+ gem.license = "MIT"
10
+ gem.summary = %Q{Reads a YAML dump of a program's disassembly from IDA Pro}
11
+ gem.description = %Q{A Gem to read program disassembly from a YAML dump. The YAML dump is generated from an ida pro python script. This script is included along with this Gem (func.py)}
12
+ gem.email = "roger.seagle@gmail.com"
13
+ gem.authors = ["Roger Seagle"]
14
+ gem.required_ruby_version = '>= 1.9.3'
15
+ end
16
+
17
+
18
+ Jeweler::RubygemsDotOrgTasks.new
19
+
20
+ require 'rspec/core'
21
+ require 'rspec/core/rake_task'
22
+ RSpec::Core::RakeTask.new(:spec) do |spec|
23
+ spec.pattern = FileList['spec/**/*_spec.rb']
24
+ end
25
+
26
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
27
+ spec.pattern = 'spec/**/*_spec.rb'
28
+ spec.rcov = true
29
+ end
30
+
31
+ task :default => :spec
32
+
33
+ #require 'rake/rdoctask'
34
+ require 'rdoc/task'
35
+ Rake::RDocTask.new do |rdoc|
36
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
37
+
38
+ rdoc.rdoc_dir = 'rdoc'
39
+ rdoc.title = "plympton #{version}"
40
+ rdoc.rdoc_files.include('README*')
41
+ rdoc.rdoc_files.include('lib/**/*.rb')
42
+ end
43
+
44
+ # Rake Task for building yard doc
45
+ require 'yard'
46
+ YARD::Rake::YardocTask.new do |t|
47
+ t.options = ['--exclude', 'bin/*']
48
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.1.0
data/bin/func-auto.py ADDED
@@ -0,0 +1,431 @@
1
+ import yaml
2
+ import idascript
3
+ import idc
4
+
5
+ class Object(yaml.YAMLObject):
6
+ yaml_tag = u'!fuzz.io,2011/Object'
7
+ def __init__(self, textSegmentStart, textSegmentEnd):
8
+ self.name = GetInputFilePath()
9
+ self.functionList = []
10
+ self.importList = []
11
+ self.textSegmentStart = textSegmentStart
12
+ self.textSegmentEnd = textSegmentEnd
13
+
14
+ #
15
+ # Pull out all information about functions
16
+ #
17
+ self.initialize_functions()
18
+
19
+ self.numFunctions = len(self.functionList)
20
+ self.numImports = len(self.importList)
21
+ self.numBlocks = self.iter_functions()
22
+ self.textSegmentStart = hex(textSegmentStart)
23
+ self.textSegmentEnd = hex(textSegmentEnd)
24
+
25
+ def initialize_functions(self):
26
+ #
27
+ # Iterate through all of the functions
28
+ #
29
+ for fn in Functions(self.textSegmentStart, self.textSegmentEnd):
30
+ tmp = Function(fn)
31
+ if not tmp.isImport:
32
+ self.functionList.append(tmp)
33
+ else:
34
+ self.importList.append(tmp)
35
+
36
+ def iter_functions(self):
37
+ blockCount = 0
38
+ for fn in self.functionList:
39
+ blockCount = blockCount + fn.iter_chunks()
40
+
41
+ return(blockCount)
42
+
43
+ #
44
+ # Create a class for a function
45
+ #
46
+ class Function(yaml.YAMLObject):
47
+ yaml_tag = u'!fuzz.io,2011/Function'
48
+ def __init__(self, effectiveAddress):
49
+ self.name = Name(effectiveAddress)
50
+ self.argSize = 0
51
+ self.numArgs = 0
52
+ self.numLocalVars = 0
53
+ self.isImport = False
54
+ self.chunkList = []
55
+ self.numChunks = 0
56
+ self.cyclomaticComplexity = 0
57
+
58
+ #
59
+ # Get the function flags, structure, and frame info
60
+ #
61
+ flags = GetFunctionFlags(effectiveAddress)
62
+ funcStruct = idaapi.get_func(effectiveAddress)
63
+ frameStruct = idaapi.get_frame(funcStruct)
64
+
65
+ # if we're not in a "real" function. set the id and ea_start manually and stop analyzing.
66
+ if not funcStruct or flags & FUNC_LIB or flags & FUNC_STATIC:
67
+ self.startAddress = hex(effectiveAddress)
68
+ self.endAddress = hex(effectiveAddress)
69
+ self.name = idaapi.get_name(effectiveAddress, effectiveAddress)
70
+ self.savedRegSize = 0
71
+ self.localVarSize = 0
72
+ self.frameSize = 0
73
+ self.retSize = 0
74
+ self.isImport = True
75
+
76
+ #
77
+ # Need to fix these if possible
78
+ #
79
+ self.argSize = 0
80
+ self.numArgs = 0
81
+ self.numLocalVars = 0
82
+ return
83
+
84
+ #
85
+ # So we know we're in a real function
86
+ #
87
+ self.startAddress = funcStruct.startEA
88
+ self.endAddress = hex(PrevAddr(funcStruct.endEA))
89
+ self.savedRegSize = funcStruct.frregs
90
+ self.localVarSize = funcStruct.frsize
91
+ self.frameSize = idaapi.get_frame_size(funcStruct)
92
+ self.retSize = idaapi.get_frame_retsize(funcStruct)
93
+
94
+ print("=========================================")
95
+ print("Frame Size %d" % self.frameSize)
96
+ print("EIP Return Size %d" % self.retSize)
97
+ print("Saved Registers Size %d" % self.savedRegSize)
98
+ print("Locals Size %d" % self.localVarSize)
99
+ print("=========================================")
100
+ print("Function name: %s" % self.name)
101
+
102
+ #
103
+ # Fixup numbers for arguments and local variables
104
+ #
105
+ self.__init_args_and_local_vars__(funcStruct, frameStruct)
106
+
107
+ #
108
+ # Initialize chunks
109
+ #
110
+ self.collect_function_chunks()
111
+ self.cyclomaticComplexity = self.calculate_cyclomatic_complexity(self.startAddress)
112
+ self.startAddress = hex(self.startAddress)
113
+
114
+ def calculate_cyclomatic_complexity (self, function_ea):
115
+ '''Calculate the cyclomatic complexity measure for a function.
116
+
117
+ Given the starting address of a function, it will find all
118
+ the basic block's boundaries and edges between them and will
119
+ return the cyclomatic complexity, defined as:
120
+
121
+ CC = Edges - Nodes + 2
122
+ http://www.openrce.org/articles/full_view/11
123
+ '''
124
+
125
+ f_start = function_ea
126
+ f_end = FindFuncEnd(function_ea)
127
+
128
+ edges = set([])
129
+ boundaries = set((f_start,))
130
+
131
+ # For each defined element in the function.
132
+ for head in Heads(f_start, f_end):
133
+
134
+ # If the element is an instruction
135
+ if isCode(GetFlags(head)):
136
+
137
+ # Get the references made from the current instruction
138
+ # and keep only the ones local to the function.
139
+ refs = CodeRefsFrom(head, 0)
140
+ refs = set(filter(lambda x: x>=f_start and x<=f_end, refs))
141
+
142
+ if refs:
143
+ # If the flow continues also to the next (address-wise)
144
+ # instruction, we add a reference to it.
145
+ # For instance, a conditional jump will not branch
146
+ # if the condition is not met, so we save that
147
+ # reference as well.
148
+ next_head = NextHead(head, f_end)
149
+ if isFlow(GetFlags(next_head)):
150
+ refs.add(next_head)
151
+
152
+ # Update the boundaries found so far.
153
+ boundaries.update(refs)
154
+
155
+ # For each of the references found, and edge is
156
+ # created.
157
+ for r in refs:
158
+ # If the flow could also come from the address
159
+ # previous to the destination of the branching
160
+ # an edge is created.
161
+ if isFlow(GetFlags(r)):
162
+ edges.add((PrevHead(r, f_start), r))
163
+ edges.add((head, r))
164
+
165
+ return len(edges) - len(boundaries) + 2
166
+
167
+ def __init_args_and_local_vars__ (self, funcStruct, frameStruct):
168
+ '''
169
+ Calculate the total size of arguments, # of arguments and # of local variables. Update the internal class member
170
+ variables appropriately. Taken directly from paimei
171
+ '''
172
+ # Initialize some local variables
173
+ args = {}
174
+ local_vars = {}
175
+
176
+ if not frameStruct:
177
+ return
178
+
179
+ argument_boundary = self.frameSize
180
+ frame_offset = frameStruct.get_member(0).soff
181
+
182
+
183
+ for i in xrange(0, frameStruct.memqty):
184
+ end_offset = frameStruct.get_member(i).soff
185
+
186
+ if i == frameStruct.memqty - 1:
187
+ begin_offset = frameStruct.get_member(i).eoff
188
+ else:
189
+ begin_offset = frameStruct.get_member(i+1).soff
190
+
191
+ frame_offset += (begin_offset - end_offset)
192
+
193
+ # grab the name of the current local variable or argument.
194
+ name = idaapi.get_member_name(frameStruct.get_member(i).id)
195
+ print("=============================")
196
+ print("Name: %s" % name)
197
+ # print "Agument Boundary: %d" % argument_boundary
198
+ # print "Frame offset: %d" % frame_offset
199
+ print("End offset: %d" % end_offset)
200
+ print("Begin Offset: %d" % begin_offset)
201
+ # print("Frame offset: %d\n" % frame_offset)
202
+ print("=============================")
203
+
204
+ if name == None:
205
+ continue
206
+
207
+ if frame_offset > argument_boundary:
208
+ args[end_offset] = name
209
+ # if name.startswith("arg_"):
210
+ # args[end_offset] = name
211
+ # self.argSize = self.argSize + (begin_offset - end_offset)
212
+ else:
213
+ # if the name starts with a space, then ignore it as it is either the stack saved ebp or eip.
214
+ # XXX - this is a pretty ghetto check.
215
+ if not name.startswith(" "):
216
+ local_vars[end_offset] = name
217
+ self.argSize = frame_offset - argument_boundary
218
+ self.numArgs = len(args)
219
+ self.numLocalVars = len(local_vars)
220
+
221
+ def iter_chunks(self):
222
+ chunkBlockCount = 0
223
+ for ch in self.chunkList:
224
+ chunkBlockCount = chunkBlockCount + len(ch.blockList)
225
+
226
+ return(chunkBlockCount)
227
+
228
+ def collect_function_chunks(self):
229
+ '''
230
+ Generate and return the list of function chunks (including the main one) for the current function. Ripped from idb2reml (Ero Carerra). Modified slightly by Roger Seagle.
231
+
232
+ @rtype: None
233
+ @return: None
234
+ '''
235
+
236
+ #
237
+ # Loop through all chunks for a function
238
+ #
239
+ iterator = idaapi.func_tail_iterator_t(idaapi.get_func(self.startAddress))
240
+ status = iterator.main()
241
+
242
+ while status:
243
+ chunk = iterator.chunk()
244
+ tmp = Chunk(chunk)
245
+ self.chunkList.append(tmp)
246
+ status = iterator.next()
247
+
248
+ #
249
+ # Create a class for a basic block
250
+ #
251
+ class Chunk(yaml.YAMLObject):
252
+ yaml_tag = u'!fuzz.io,2011/Chunk'
253
+ def __init__(self, chunk):
254
+ self.startEA = chunk.startEA
255
+ self.endEA = chunk.endEA
256
+ self.blockList = []
257
+ self.numBlocks = 0
258
+
259
+ #
260
+ # Just to get it started
261
+ #
262
+ block_start = self.startEA
263
+
264
+ #
265
+ # Might be a bug? (effective address that has code mixed in and no ret instruction
266
+ # Or effective address just calls exit (there is no return instruction!!!!)
267
+ #
268
+
269
+ #
270
+ # Break down the chunk into blocks
271
+ #
272
+ for effectiveAddress in Heads(self.startEA, self.endEA):
273
+
274
+ #
275
+ # Ignore Head if data
276
+ #
277
+ if not isCode(GetFlags(effectiveAddress)):
278
+ continue
279
+
280
+ prev_ea = PrevNotTail(effectiveAddress)
281
+ next_ea = NextNotTail(effectiveAddress)
282
+
283
+ #
284
+ # Get the list of places branched to and from
285
+ #
286
+ branchesTo = self._branches_to(effectiveAddress)
287
+ branchesFrom = self._branches_from(effectiveAddress)
288
+
289
+
290
+ # ensure that both prev_ea and next_ea reference code and not data.
291
+ while not isCode(GetFlags(prev_ea)):
292
+ prev_ea = PrevNotTail(prev_ea)
293
+
294
+ while not isCode(GetFlags(next_ea)):
295
+ next_ea = PrevNotTail(next_ea)
296
+
297
+ # if the current instruction is a ret instruction, end the current node at ea.
298
+ if idaapi.is_ret_insn(effectiveAddress):
299
+ tmp = Block(block_start, effectiveAddress, branchesTo, branchesFrom)
300
+ self.blockList.append(tmp)
301
+ block_start = next_ea
302
+
303
+ elif branchesTo and block_start != effectiveAddress:
304
+ tmp = Block(block_start, effectiveAddress, branchesTo, branchesFrom)
305
+ self.blockList.append(tmp)
306
+
307
+ # start a new block at ea.
308
+ block_start = effectiveAddress
309
+
310
+ # if there is a branch from the current instruction, end the current node at ea.
311
+ elif branchesFrom:
312
+ tmp = Block(block_start, effectiveAddress, branchesTo, branchesFrom)
313
+ self.blockList.append(tmp)
314
+
315
+ # start a new block at the next ea
316
+ block_start = next_ea
317
+
318
+ #
319
+ # Calculate the number of blocks
320
+ #
321
+ self.numBlocks = len(self.blockList)
322
+
323
+ #
324
+ # Covert addresses to hex
325
+ #
326
+ self.startEA = hex(self.startEA)
327
+ self.endEA = hex(self.endEA)
328
+
329
+ ####################################################################################################################
330
+ def _branches_from (self, ea):
331
+ '''
332
+ Enumerate and return the list of branches from the supplied address, *including* the next logical instruction.
333
+ Part of the reason why we even need this function is that the "flow" argument to CodeRefsFrom does not appear
334
+ to be functional.
335
+
336
+ @type ea: DWORD
337
+ @param ea: Effective address of instruction to enumerate jumps from.
338
+
339
+ @rtype: List
340
+ @return: List of branches from the specified address.
341
+ '''
342
+
343
+ if idaapi.is_call_insn(ea):
344
+ return []
345
+
346
+ xrefs = list(CodeRefsFrom(ea, 1))
347
+
348
+ # if the only xref from ea is next ea, then return nothing.
349
+ if len(xrefs) == 1 and xrefs[0] == NextNotTail(ea):
350
+ xrefs = []
351
+
352
+ return xrefs
353
+
354
+
355
+ ####################################################################################################################
356
+ def _branches_to (self, ea):
357
+ '''
358
+ Enumerate and return the list of branches to the supplied address, *excluding* the previous logical instruction.
359
+ Part of the reason why we even need this function is that the "flow" argument to CodeRefsTo does not appear to
360
+ be functional.
361
+
362
+ @type ea: DWORD
363
+ @param ea: Effective address of instruction to enumerate jumps to.
364
+
365
+ @rtype: List
366
+ @return: List of branches to the specified address.
367
+ '''
368
+
369
+ xrefs = []
370
+ prev_ea = PrevNotTail(ea)
371
+ prev_code_ea = prev_ea
372
+
373
+ while not isCode(GetFlags(prev_code_ea)):
374
+ prev_code_ea = PrevNotTail(prev_code_ea)
375
+
376
+ for xref in list(CodeRefsTo(ea, 1)):
377
+ if not idaapi.is_call_insn(xref) and xref not in [prev_ea, prev_code_ea]:
378
+ xrefs.append(hex(xref))
379
+
380
+ return xrefs
381
+
382
+ #
383
+ # Create a class for a basic block
384
+ #
385
+ class Block(yaml.YAMLObject):
386
+ yaml_tag = u'!fuzz.io,2011/Block'
387
+ def __init__(self, effectiveAddressStart, effectiveAddressEnd, branchesTo, branchesFrom):
388
+ self.startEA = hex(effectiveAddressStart)
389
+ self.endEA = hex(effectiveAddressEnd)
390
+ self.branchTo = branchesTo
391
+ branchFr = []
392
+
393
+ #
394
+ # Covert branches to hex addresses
395
+ #
396
+ for i in range(len(branchesFrom)):
397
+ branchFr.append(hex(branchesFrom[i]))
398
+
399
+ self.branchFrom = branchFr
400
+
401
+ #
402
+ # Get the number of instructions in the block
403
+ #
404
+ heads = [head for head in Heads(effectiveAddressStart, effectiveAddressEnd + 1) if isCode(GetFlags(head))]
405
+ self.numInstructions = len(heads)
406
+
407
+ # Wait for the analysis to stop
408
+ idaapi.autoWait()
409
+
410
+ # Create the filenames to dump and log
411
+ yamlFilename = os.environ['PWD'] + "/" + GetInputFile() + ".fz"
412
+
413
+ # Open the file
414
+ yamlFile = open(yamlFilename, 'w')
415
+
416
+ # Get the start and end of the text section
417
+ textSegmentSelector = SegByName("__text")
418
+ textSegmentStart = SegByBase(textSegmentSelector)
419
+ textSegmentEnd = SegEnd(textSegmentStart)
420
+
421
+ # Pull out all the information
422
+ disassembledObject = Object(textSegmentStart, textSegmentEnd)
423
+
424
+ # Dump the disassembled Object info in a portable format
425
+ yaml.dump(disassembledObject, yamlFile, default_flow_style=False)
426
+
427
+ # Be nice close the file
428
+ yamlFile.close()
429
+
430
+ # Exit IDA Pro
431
+ Exit(0)