-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFunctionMatrix.py
More file actions
executable file
·734 lines (657 loc) · 25.7 KB
/
Copy pathFunctionMatrix.py
File metadata and controls
executable file
·734 lines (657 loc) · 25.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
# ====================================================================================
# Function_Matrix is used to collect functions features.
# The features include function size, callee, caller, and cycles, etc. so far there're 12 in total,
# but you can add more according to your requirement. For information about how to add matrix,
# please refer to ReadMe.txt
#
# version 1.2 on 14/10/2013, by razygon, add features function name and function address
# Updated for IDA 9.2 / Python 3
# ====================================================================================
import sys
import idaapi
import idautils
import idc
import ida_bytes
import ida_kernwin
import ida_name
import ida_pro
import os
import networkx as nx
import collections
import multiprocessing
from multiprocessing import Process, freeze_support, Queue
import inspect
import pymongo
from pymongo import MongoClient
import pickle
def GetCallees(ea):
"""Get direct callees of function at ea using CodeRefsFrom."""
callees = []
for head in idautils.FuncItems(ea):
for xref in idautils.CodeRefsFrom(head, False):
func = idaapi.get_func(xref)
if func and func.start_ea != ea:
if func.start_ea not in callees:
callees.append(func.start_ea)
return callees
def GetInstruction(ea):
if ea is None:
raise Exception("Address cannot be None")
disasm = idc.GetDisasm(ea)
try:
disasm = disasm[:disasm.index(';')]
except ValueError:
pass
if disasm == '':
return None
return disasm
def DEBUG_PRINT(str):
# print('[debug info]')
# print(str)
return
def calc_path(dg, start, end, cutoff): #,count_conn
count_paths = 0
paths = nx.all_simple_paths(dg, start, end, cutoff)
DEBUG_PRINT((start, end, cutoff))
for x in paths:
count_paths = count_paths + 1
# count_conn.put(count_paths)
def DEBUG_VIEW():
global fcan
for f in fcan:
print(f)
print(fcan[f])
return
class ConfirmDialog(ida_kernwin.Form):
def __init__(self, msg):
ida_kernwin.Form.__init__(self, r"""STARTITEM 0
BUTTON YES* OK
BUTTON CANCEL Cancel
Confirm DB Operation
{note}
""", {
'note' : ida_kernwin.Form.StringLabel(msg)
})
def simple_paths_count(G, source, target, cutoff=None):
path_count = 0
if cutoff < 1:
return
visited = [source]
stack = [iter(G[source])]
import time
start = time.time()
while stack:
children = stack[-1]
child = next(children, None)
if child is None:
stack.pop()
visited.pop()
elif len(visited) < cutoff:
if child == target:
path_count = path_count + 1
DEBUG_PRINT(path_count)
if path_count >= 100:
break
elif child not in visited:
visited.append(child)
stack.append(iter(G[child]))
else: #len(visited) == cutoff:
if child == target or target in children:
path_count = path_count + 1
break
runtime = time.time() - start
if runtime > 20:
return 100
return path_count
def simple_cycles(G):
"""Find simple cycles (elementary circuits) of a directed graph.
An simple cycle, or elementary circuit, is a closed path where no
node appears twice, except that the first and last node are the same.
Two elementary circuits are distinct if they are not cyclic permutations
of each other.
Parameters
----------
G : NetworkX DiGraph
A directed graph
Returns
-------
A list of circuits, where each circuit is a list of nodes, with the first
and last node being the same.
"""
# Jon Olav Vik, 2010-08-09
def _unblock(thisnode):
"""Recursively unblock and remove nodes from B[thisnode]."""
if blocked[thisnode]:
blocked[thisnode] = False
while B[thisnode]:
_unblock(B[thisnode].pop())
def circuit(thisnode, startnode, component,result):
closed = False # set to True if elementary path is closed
path.append(thisnode)
blocked[thisnode] = True
for nextnode in component[thisnode]: # direct successors of thisnode
if nextnode == startnode:
result[startnode] = path
DEBUG_PRINT('get one result')
DEBUG_PRINT(result)
closed = True
return closed
elif not blocked[nextnode]:
if circuit(nextnode, startnode, component,result):
closed = True
if closed:
_unblock(thisnode)
else:
for nextnode in component[thisnode]:
if thisnode not in B[nextnode]: # TODO: use set for speedup?
B[nextnode].append(thisnode)
path.pop() # remove thisnode from path
return closed
path = [] # stack of nodes in current path
blocked = collections.defaultdict(bool) # vertex: blocked from search?
B = collections.defaultdict(list) # graph portions that yield no elementary circuit
result = {} # list to accumulate the circuits found
# Johnson's algorithm requires some ordering of the nodes.
# They might not be sortable so we assign an arbitrary ordering.
ordering=dict(zip(G,range(len(G))))
count = 0
for s in ordering:
DEBUG_PRINT(count)
count = count + 1
# Build the subgraph induced by s and following nodes in the ordering
subgraph = G.subgraph(node for node in G
if ordering[node] >= ordering[s])
# Find the strongly connected component in the subgraph
# that contains the least node according to the ordering
strongcomp = nx.strongly_connected_components(subgraph)
mincomp=min(strongcomp,
key=lambda nodes: min(ordering[n] for n in nodes))
component = G.subgraph(mincomp)
if component:
# smallest node in the component according to the ordering
startnode = min(component,key=ordering.__getitem__)
if startnode in result:
continue
for node in component:
blocked[node] = False
B[node][:] = []
dummy=circuit(startnode, startnode, component,result)
return result
def get_callers(ea,maxlen=None):
'''
Walk through the callers recursively starting at address ea.
maxlen is the maximum number of node the graph can contain.
Return a dictionary of list of caller addresses.
Return empty dictionary when number of node > maxlen.
'''
xtree = {}
visited = []
towalk = [ea]
while towalk:
curr = towalk.pop()
if curr not in xtree: # the start point also will record in the tree
xtree[curr] = []
if curr not in visited:
visited.append(curr)
for x in idautils.XrefsTo(curr):
caller = idaapi.get_func(x.frm)
if caller:
caller = caller.start_ea
if caller not in visited:
towalk.append(caller)
xtree[curr].append(caller)
if maxlen and len(xtree) > maxlen:
return {}
return xtree
def get_callees(ea,maxlen=None):
'''
walk through the callees recursively
'''
calleetree = {}
visited = []
towalk = [ea]
while towalk:
curr = towalk.pop()
if curr not in calleetree: # the start point also will record in the tree
calleetree[curr] = []
if curr not in visited:
visited.append(curr)
for x in GetCallees(curr):
if x not in visited:
towalk.append(x)
calleetree[curr].append(x)
if maxlen and len(calleetree) > maxlen:
return {}
return calleetree
def getsyscalls(filename):
syscalls = set()
try:
with open(filename,'rb') as fr:
syscalls = pickle.load(fr)
except:
print('oooooops, cannot load syscall list from ', filename)
raise
if not syscalls:
raise ValueError('syscalls list is empty')
return syscalls
class FunctionMatrix():
def __init__(self):
'''
one table is for one function and its xref_to functions
the table's name is the source function's name
how to store function features within the table still need consideration
'''
self.script_folder = ''
self.project_name = ''
print('---------------------', idc.ARGV[1])
arg = idc.ARGV[1]
self.script_folder = arg[arg.find('(')+2: arg.find(',')-1]
self.project_name = arg[arg.find(',')+2: arg.find(')')-1]
print('++++++++++project_name', self.project_name)
print('++++++++++script_folder',self.script_folder)
self.moduleName = ida_nalt.get_root_filename().replace('.','_') #name of current idb
if os.path.exists(self.moduleName):
#may need user's input to decide whether rewrite it or append it? this check shld be set as input in args
print('the db already exist')
clear = ConfirmDialog("Delete the current DB and create a new one?")
clear.Compile()
ok = clear.Execute()
if ok:
os.remove(self.moduleName)
else:
return
print('[Get_FunctionFeatures]moduleName: %s'%self.moduleName)
self.func_name_ea = {name:ea for ea, name in idautils.Names()} # all names within idb
self.ftable = collections.defaultdict(dict) # a dictionary stores the features of one function, will be refreshed for every function
self.exports = [] # all export functions
self.memop = {} #instructions with memory operation
self.syscalls = set()
self.priorMatrix = [('returnpoints', '_feature_returnpoints'), ('loopcount', '_feature_loopcount')]
#(ea, writemem, writetoglobal, cmpmem, loopcalc)
self.LoadExports()
print('table name: ' + self.moduleName)
def _CheckMemOp(self, ea):
'''
the itype value are defined in .\idasdk64\include\allins.hpp
op.type definition is in .\idasdk64\include\ua.hpp
const optype_t // Description Data field
o_void = 0, // No Operand ----------
o_reg = 1, // General Register (al,ax,es,ds...) reg
o_mem = 2, // Direct Memory Reference (DATA) addr
o_phrase = 3, // Memory Ref [Base Reg + Index Reg] phrase
o_displ = 4, // Memory Reg [Base Reg + Index Reg + Displacement] phrase+addr
o_imm = 5, // Immediate Value value
o_far = 6, // Immediate Far Address (CODE) addr
o_near = 7, // Immediate Near Address (CODE) addr
o_idpspec0 = 8, // IDP specific type
'''
inst = idautils.DecodeInstruction(ea)
if inst == None:
return
if inst.itype in [160,159]:
# retn 159, retf 160
self.ftable["returnpoints"].append(ea)
elif inst.itype in [122,6,209]:
# mov 122 add 6 sub 209, write memory happened at first opr
if 2<= inst[0].type <=7:
#considered as memory write
if idc.get_segm_name(inst[0].addr) == '.idata':
self.ftable["memop"].append((ea,1,1,0,0))
else:
self.ftable["memop"].append((ea,1,0,0,0))
elif inst.itype in [27,210]:
#cmp 27 test 210
if (2<= inst[0].type <=7 and inst[0].type != 5) or (2<= inst[1].type <=7 and inst[1].type != 5):
#mem cmp
self.ftable["memop"].append((ea,0,0,1,0))
elif inst.itype in [44,34]:
#inc 44 dec 34;
self.ftable["memop"].append((ea,0,0,0,1))
elif inst.itype in [16]:
# call 13
if inst[0].type == 3 or inst[0].type == 4:
self.ftable["dynamiccall"].append(ea)
def _BuildBasicBlockInfo(self, f_ea):
f_start = f_ea
f_end = idc.find_func_end(f_start)
edges = set()
boundaries = set((f_start,))
self.ftable["exceptionhandlers"] = []
self.ftable["memop"] = []
self.ftable["returnpoints"] = []
self.ftable["dynamiccall"] = []
for head in idautils.Heads(f_start, f_end):
# If the element is an instruction
comm = idc.get_cmt(head, 1)
if comm != None:
if "Exception handler" in comm:
self.ftable["exceptionhandlers"].append(head)
self._CheckMemOp(head)
if ida_bytes.is_code(ida_bytes.get_full_flags(head)):
# Get the references made from the current instruction
# and keep only the ones local to the function.
refs = idautils.CodeRefsFrom(head, 0)
refs = set(filter(lambda x: x>=f_start and x<=f_end, refs))
if refs:
# If the flow continues also to the next (address-wise)
# instruction, we add a reference to it.
next_head = idc.next_head(head, f_end)
if ida_bytes.is_flow(ida_bytes.get_full_flags(next_head)):
refs.add(next_head)
# Update the boundaries found so far.
boundaries.update(refs)
# For each of the references found, and edge is
# created.
for r in refs:
# If the flow could also come from the address
# previous to the destination of the branching
# an edge is created.
if ida_bytes.is_flow(ida_bytes.get_full_flags(r)):
edges.add((idc.prev_head(r, f_start), r))
edges.add((head, r))
# Let's build the list of (startEA, startEA) couples
# for each basic block
sorted_boundaries = sorted(boundaries, reverse = True)
end_addr = idc.prev_head(f_end, f_start)
bb_addr = []
for begin_addr in sorted_boundaries:
#just feel this algorithm is great
bb_addr.append((begin_addr, end_addr))
# search the next end_addr which could be
# farther than just the previous head
# if data are interlaced in the code
# WARNING: it assumes it won't epicly fail ;)
end_addr = idc.prev_head(begin_addr, f_start)
while not ida_bytes.is_code(ida_bytes.get_full_flags(end_addr)):
end_addr = idc.prev_head(end_addr, f_start)
# And finally return the result
bb_addr.reverse()
bb_edges = set()
DEBUG_PRINT( 'BuildBasicBlockInfo' + hex(f_ea))
for (s,e) in edges:
sblock = [(x,y) for (x,y) in bb_addr if y==s] # x==s or
eblock = [(x,y) for (x,y) in bb_addr if x==e] # or y==e
if sblock!=[] and eblock!=[]:
bb_edges.add((sblock[0],eblock[0]))
return bb_addr, bb_edges
def _BuildDiGraph(self, f_ea):
self.ftable["dg"] = nx.DiGraph()
nodes,edges = self._BuildBasicBlockInfo(f_ea)
self.ftable["dg"].add_nodes_from(nodes)
self.ftable["dg"].add_edges_from(edges)
return
def LoadExports(self):
for i, ordinal, ea, name in idautils.Entries():
if not ordinal == ea:
# by observation: ordinal == address => not exported
self.exports.append(ea)
def _feature_name(self,f_ea):
return idaapi.get_func_name(f_ea)
def _feature_ea(self, f_ea):
return hex(f_ea)
def _feature_size(self,f_ea):
'''
Just simply return size of the function
+ block number
prior feature: Null
'''
fun = idaapi.get_func(f_ea)
return fun.end_ea-fun.start_ea+1
def _feature_export(self, f_ea):
'''
prior feature: Null
'''
if f_ea in self.exports:
return 1
else:
return 0
def _featrue_callee(self, f_ea):
'''
Number of calls made by this function, just one level
prior feature: Null
'''
callees = GetCallees(f_ea)
return len(callees),callees
def _feature_callers(self,f_ea):
'''
Number of callers, recursively
prior feature: Null
'''
callers = get_callers(f_ea)
return len(callers)
def _feature_exceptionhandlers(self, f_ea):
'''
There may exist many exception filters, but some of them share one exception handler.
failed to generate isolated block..
prior feature: Null
'''
return len(self.ftable["exceptionhandlers"]),self.ftable["exceptionhandlers"]
def _feature_loopcount(self, f_ea):
'''
Generate a block graph, then get loopcount via DiGraph.number_of_selfloops()
prior feature: Null
'''
DEBUG_PRINT( 'feature_loopcount' + hex(f_ea))
loopcount = 0
mloops = set()
self.loops = {}
self.loops = simple_cycles(self.ftable["dg"])
loopcount = len(self.loops)
return loopcount
def _feature_returnpoints(self, f_ea):
'''
Number of 'ret' within the function
rets = [addr1, addr2. ...]
prior feature: Null
'''
DEBUG_PRINT("in returnpoints")
fun = idaapi.get_func(f_ea)
visited = []
retVal = []
for ret in self.ftable["returnpoints"]:
towalk = [ret]
while towalk:
curr = towalk.pop()
if curr < fun.start_ea or curr > fun.end_ea + 1:
continue
if curr not in visited:
visited.append(curr)
inst = GetInstruction(curr)
if inst is None:
continue
elif 'eax' in inst:
retVal.append((ret,curr,inst))
continue
for xto in idautils.XrefsTo(curr, 0):
DEBUG_PRINT('xto')
if xto.frm not in visited:
DEBUG_PRINT(xto.frm)
towalk.append(xto.frm)
DEBUG_PRINT(retVal)
return len(self.ftable["returnpoints"]), retVal
def _feature_paths(self, f_ea):
'''
Number of paths from startEA to 'ret'
prior feature: returnpoints
'''
return 0
paths_count = 0
start = sorted(self.ftable["dg"].nodes())[0]
DEBUG_PRINT('start')
DEBUG_PRINT(start)
cutoff = len(self.ftable["dg"])//2
if cutoff > 70:
return 100
for ret in self.ftable["returnpoints"]:
tar = None
for (x,y) in self.ftable["dg"].nodes():
if y == ret:
tar = (x,y)
break
if tar != None:
DEBUG_PRINT((start, tar, cutoff))
paths_count = paths_count + simple_paths_count(self.ftable["dg"], start, tar, cutoff)
if paths_count > 100:
break
DEBUG_PRINT(paths_count)
return paths_count
def _feature_arg(self,ea):
'''
Get arguments of the funcion, this version doesn't try idc_guess_type.
the args are stored in argstr, this list may be used in later version
prior feature: Null
'''
typeinfo = idc.get_type(ea)
if typeinfo == None:
return 0
else:
argstr = typeinfo[typeinfo.find('(')+1:typeinfo.rfind(')')].split(',')
return len(argstr)
def _feature_localvar(self, f_ea):
'''
prior feature: Null
'''
lvarcount = 0
DEBUG_PRINT( 'feature_localvar' + hex(f_ea))
try:
id = idc.get_frame_id(f_ea)
offset = idc.get_last_member(id)
except:
DEBUG_PRINT('no stack info for function {ea}'.format(ea =f_ea)) #BF96206A
return 0
while offset >= idc.get_first_member(id):
msize = idc.get_member_size(id, offset)
mname = idc.get_member_name(id, offset)
if mname == None:
offset = offset - 1
else:
lvarcount = lvarcount +1
offset = offset - msize
if mname == ' s':
lvarcount = 0
return lvarcount
def _feature_writetoglobalvar(self, f_ea):
'''
prior feature: Null
'''
lwritetog = list(filter(lambda x: x[2] == 1, self.ftable["memop"]))
for item in lwritetog:
DEBUG_PRINT(item[0])
writetog_count = len(lwritetog)
return writetog_count,lwritetog
def _feature_cmpmemory(self, f_ea):
'''
prior feature: Null
'''
lcmpmem = list(filter(lambda x: x[3] == 1, self.ftable["memop"]))
DEBUG_PRINT('IN cmpmemory')
for item in lcmpmem:
DEBUG_PRINT(hex(item[0]))
cmpmem_count = len(lcmpmem)
return cmpmem_count,lcmpmem
def _feature_ClsorInstMethod(self,f_ea):
'''
prior feature: Null
'''
func_name = idaapi.get_func_name(f_ea)
prototype = ida_name.demangle_name(func_name, 0)
if not prototype:
return 0
elif '__thiscall' in prototype:
return 1
else:
return 0
def _feature_dynamiccalls(self, default_arg):
'''
prior feature: Null
'''
return len(self.ftable["dynamiccall"]),self.ftable["dynamiccall"]
#add new feature here
def _feature_syscalls(self,f_ea):
'''
get how many system calls are made within current function, which include (may not limited)
1.direct sys call
2.indirect call from callee recursively
prior feature: null
'''
calleetree = {}
syscallcount = []
calleetree[f_ea] = get_callees(f_ea)
for ea in calleetree[f_ea]:
fname = idc.get_func_name(ea)
if fname in self.syscalls:#
syscallcount.append(fname) #better record the syscalls name of address
return len(syscallcount), syscallcount
def _feature_functiontype(self, f_ea):
'''
functiontype here is to identify the type of the function
prior feature: loopcount
'''
imflag = 0
for loop in self.loops.values():
for block in loop:
for l_ea in idautils.Heads(block[0],block[1]):
inst = idautils.DecodeInstruction(l_ea)
if inst == None:
continue
if inst.itype in [122]: # mov
if 3 == inst[0].type or 4 == inst[0].type:
imflag = 1
elif inst.itype in [124,207,107]: #movs/movsd, stos lods
imflag = 1
elif inst.itype in [16]: # call library function
istr = GetInstruction(l_ea)
if 'strcpy' in istr or 'memcpy' in istr or 'alloc' in istr or 'free' in istr:
imflag = 1
if imflag:
return 1
else:
return 0
def BuildFeatureTable(self):
'''
walk through functions in current idb, apply all feature matrix to them.
'''
DEBUG_PRINT("in BuildFeatureTable")
if self.script_folder:
filename = self.script_folder + r'\syscall.pkl'
else:
filename = r'syscall.pkl'
self.syscalls = getsyscalls(filename)
flist = self.priorMatrix
from FunctionMatrix import FunctionMatrix
# get the function list of the class
methods = inspect.getmembers(FunctionMatrix, predicate=inspect.isfunction)
for method in methods:
if '_feature_' in method[0]:
if method[0] in self.priorMatrix:
pass
flist.append((method[0][8:].strip('_'),method[0]))
DEBUG_PRINT('flist: ')
print(flist)
# connect to mongo db
client = MongoClient('localhost',24444)
db = client[self.moduleName] # can have a higher name, project name?
dbctrl = db[self.moduleName]
dbctrl.remove() # just during test/debug
for f in self.func_name_ea.values(): #functions' start addresses
fun = idaapi.get_func(f)
if fun: #
ffeatures = collections.OrderedDict()
self._BuildDiGraph(fun.start_ea)
for fl in flist:
ffeatures[fl[0]] = str(getattr(self, fl[1])(f)) # []?
DEBUG_PRINT('ffeatures')
DEBUG_PRINT(ffeatures)
dbctrl.insert(ffeatures)
else:
pass
db.collection_names()
dbctrl.count()
return
import time
start = time.time()
fm = FunctionMatrix()
fm.BuildFeatureTable()
timec = time.time()-start
print('time consuming: ', timec)
ida_pro.qexit(0)