-
Notifications
You must be signed in to change notification settings - Fork 53
/
database.py
8530 lines (7482 loc) · 445 KB
/
database.py
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
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
Database module
This module exposes a number of tools that can be used on a database
and on addresses within the database. There are a number of namespaces
that allow one to query information about the database as a whole, or to
read/write to an address within the database.
The base argument type for many of the utilites within this module is
the address. This can allow one to modify the colors or comments for an
address, or to read/write from the different types of data that might
exist at an address.
Some namespaces are also provided for querying the available symbolic
information that IDA has discovered about a binary. This can be used
to search and navigate the database. Some of the available namespaces
that can be used for querying are ``functions``, ``segments``,
``names``, ``imports``, ``entries``, and ``marks``.
"""
import six, builtins
import functools, operator, itertools, types
import sys, os, logging, string, bisect
import math, codecs, array as _array, fnmatch, re, ctypes
import function, segment
import structure as _structure, instruction as _instruction
import ui, internal
from internal import utils, interface, exceptions as E
import idaapi
## properties
def here():
'''Return the current address.'''
return ui.current.address()
h = utils.alias(here)
@utils.multicase()
def within():
'''Should always return true.'''
return within(ui.current.address())
@utils.multicase(ea=six.integer_types)
def within(ea):
'''Return true if address `ea` is within the bounds of the database.'''
left, right = config.bounds()
return left <= ea < right
contains = utils.alias(within)
def top():
'''Return the very lowest address within the database.'''
ea, _ = config.bounds()
return ea
def bottom():
'''Return the very highest address within the database.'''
_, ea = config.bounds()
return ea
class config(object):
"""
This namespace contains various read-only properties about the
database. This includes things such as the database boundaries,
its filename, the path to the generated database, etc. Some tools
for determining the type of the binary are also included.
"""
# cache the default value for the structure
info = idaapi.get_inf_structure()
@classmethod
def __init_info_structure__(cls, idp_modname):
information = idaapi.get_inf_structure()
if information:
logging.debug(u"{:s}.__init_info_structure__({!s}) : Successfully fetched and cached information structure for database.".format('.'.join([__name__, cls.__name__]), utils.string.escape(idp_modname, '"')))
# Display summary of the database and what it's used for.
bits = "{:d}-bit".format(64 if information.is_64bit() else 32 if information.is_32bit() else 16)
format = 'library' if information.lflags & idaapi.LFLG_IS_DLL else 'binary'
if idaapi.__version__ < 7.0:
byteorder = "{:s}-endian".format('big' if idaapi.cvar.inf.mf else 'little')
else:
byteorder = "{:s}-endian".format('big' if information.lflags & idaapi.LFLG_MSF else 'little')
if idaapi.__version__ >= 7.0:
mode = ' kernelspace' if information.lflags & idaapi.LFLG_KERNMODE else ' userspace'
else:
mode = ''
logging.warning("Initialized {tag!s} database v{version:d} for {bits:s} {byteorder:s}{mode:s} {format:s}.".format('.'.join([information.__class__.__module__, information.__class__.__name__]), tag=information.tag, bits=bits, byteorder=byteorder, mode=mode, format=format, version=information.version))
else:
logging.fatal(u"{:s}.__init_info_structure__({!s}) : Unknown error while trying to get information structure for database.".format('.'.join([__name__, cls.__name__]), utils.string.escape(idp_modname, '"')))
cls.info = information
@classmethod
def __nw_init_info_structure__(cls, nw_code, is_old_database):
logging.debug(u"{:s}.__nw_init_info_structure__({!s}) : Received notification to initialize information structure for database.".format('.'.join([__name__, cls.__name__]), ', '.join(map("{!r}".format, [nw_code, is_old_database]))))
idp_modname = idaapi.get_idp_name()
return cls.__init_info_structure__(idp_modname)
@utils.multicase()
@classmethod
def lflags(cls):
'''Return the value of the ``idainfo.lflags`` field from the database.'''
if idaapi.__version__ < 7.2:
return cls.info.lflags
return idaapi.inf_get_lflags()
@utils.multicase(mask=six.integer_types)
@classmethod
def lflags(cls, mask):
'''Return the value of the ``idainfo.lflags`` field from the database with the specified `mask`.'''
if idaapi.__version__ < 7.2:
return cls.info.lflags & mask
return idaapi.inf_get_lflags() & mask
@utils.multicase(mask=six.integer_types, value=six.integer_types)
@classmethod
def lflags(cls, mask, value):
'''Set the ``idainfo.lflags`` with the provided `mask` from the database to the specified `value`.'''
if idaapi.__version__ < 7.2:
result, cls.info.lflags = cls.info.lflags, (result & ~mask) | (value & mask)
return result
# Newer versions of IDA use the idaapi.inf_get_lflags() function.
result = idaapi.inf_get_lflags()
if not idaapi.inf_set_lflags((result & ~mask) | (value & mask)):
raise E.DisassemblerError(u"{:s}.lflags({:#x}, {:#x}) : Unable to modify the flags in idainfo.lflags ({:#x} & {:#x}) to the specified value ({:s}).".format('.'.join([__name__, cls.__name__]), result, mask, "{:#x} & {:#x}".format(value, mask) if value & ~mask else "{:#x}".format(value)))
return result
@classmethod
def filename(cls):
'''Return the filename that the database was built from.'''
res = idaapi.get_root_filename()
return utils.string.of(res)
@classmethod
def idb(cls):
'''Return the full path to the database.'''
res = idaapi.cvar.database_idb if idaapi.__version__ < 7.0 else idaapi.get_path(idaapi.PATH_TYPE_IDB)
res = utils.string.of(res)
return res.replace(os.sep, '/')
database = utils.alias(idb, 'config')
@classmethod
def module(cls):
'''Return the module name as per the windows loader.'''
res = cls.filename()
res = os.path.split(res)
return os.path.splitext(res[1])[0]
@utils.multicase()
@classmethod
def path(cls):
'''Return the absolute path to the directory containing the database.'''
res = cls.idb()
path, _ = os.path.split(res)
return path
@utils.multicase(pathname=six.string_types)
@classmethod
def path(cls, pathname, *components):
'''Return an absolute path composed of the provided `pathname` and any additional `components` relative to the directory containing the database.'''
res = cls.idb()
path, _ = os.path.split(res)
return os.path.join(path, pathname, *components)
@classmethod
def baseaddress(cls):
'''Return the baseaddress of the database.'''
return idaapi.get_imagebase()
@classmethod
def is_readonly(cls):
'''Return whether the database is read-only or not.'''
if idaapi.__version__ < 7.0:
raise E.UnsupportedVersion(u"{:s}.readonly() : This function is only supported on versions of IDA 7.0 and newer.".format('.'.join([__name__, cls.__name__])))
elif idaapi.__version__ < 7.2:
return cls.info.readonly_idb()
return idaapi.inf_readonly_idb()
readonlyQ = utils.alias(is_readonly, 'config')
@classmethod
def is_sharedobject(cls):
'''Return whether the database is a shared-object or not.'''
if idaapi.__version__ < 7.0:
raise E.UnsupportedVersion(u"{:s}.is_sharedobject() : This function is only supported on versions of IDA 7.0 and newer.".format('.'.join([__name__, cls.__name__])))
return True if cls.lflags(idaapi.LFLG_IS_DLL) else False
sharedobject = is_shared = is_dll = sharedQ = utils.alias(is_sharedobject, 'config')
@classmethod
def is_kernelspace(cls):
'''Return whether the database is using a kernelmode address space or not.'''
if idaapi.__version__ < 7.0:
raise E.UnsupportedVersion(u"{:s}.is_kernelspace() : This function is only supported on versions of IDA 7.0 and newer.".format('.'.join([__name__, cls.__name__])))
return True if cls.lflags(idaapi.LFLG_KERNMODE) else False
kernelspaceQ = kernelQ = utils.alias(is_kernelspace, 'config')
@utils.multicase()
@classmethod
def filetype(cls):
'''Return the file type identified by the loader when creating the database.'''
if idaapi.__version__ < 7.2:
return cls.info.filetype
return idaapi.inf_get_filetype()
@utils.multicase(filetype_t=six.integer_types)
@classmethod
def filetype(cls, filetype_t):
'''Set the file type identified by the loader to the specified `filetype_t`.'''
if idaapi.__version__ < 7.2:
result, cls.info.filetype = cls.info.filetype, filetype_t
return result
# Newer versions of IDA use the idaapi.inf_get_filetype() and idaapi.inf_set_filetype() functions.
result = idaapi.inf_get_filetype()
if not idaapi.inf_set_filetype(filetype_t):
raise E.DisassemblerError(u"{:s}.filetype({:#x}) : Unable to set value for idainfo.filetype to the specified value ({:#x}).".format('.'.join([__name__, cls.__name__]), filetype_t, filetype_t))
return result
@utils.multicase(FT_=six.string_types)
@classmethod
def filetype(cls, FT_):
'''Set the file type identified by the loader to the value for the string `FT_`.'''
prefix, choice = 'FT_', FT_.upper()
candidates = {prefix + choice, choice}
# Grab all of our available choices from the idc module since they're not defined anywhere else.
import idc
filtered = ((name, getattr(idc, name)) for name in dir(idc) if name.startswith(prefix))
choices = {item : value for item, value in filtered if isinstance(value, six.integer_types)}
# Find a valid choice by iterating through each one and seeing if its in our list of candidates.
iterable = (value for item, value in choices.items() if item in candidates)
value = builtins.next(iterable, None)
if value is None:
raise E.ItemNotFoundError(u"{:s}.filetype({!r}) : Unable to find the requested file type ({!r}) in the list of choices.".format('.'.join([__name__, cls.__name__]), FT_, string))
# We found it, so we can recurse into the correct case to assign it.
return cls.filetype(value)
@utils.multicase()
@classmethod
def ostype(cls):
'''Return the operating system type identified by the loader when creating the database.'''
# FIXME: this is a bitflag that should be documented in libfuncs.hpp
# which unfortunately is not included anywhere in the sdk.
if idaapi.__version__ < 7.2:
return cls.info.ostype
return idaapi.inf_get_ostype()
@utils.multicase(ostype_t=six.integer_types)
@classmethod
def ostype(cls, ostype_t):
'''Set the operating system type for the database to the specified `ostype_t`.'''
if idaapi.__version__ < 7.2:
result, cls.info.ostype = cls.info.ostype, ostype_t
return result
# Newer versions of IDA use the idaapi.inf_get_filetype() and idaapi.inf_set_filetype() functions.
result = idaapi.inf_get_ostype()
if not idaapi.inf_set_ostype(ostype_t):
raise E.DisassemblerError(u"{:s}.ostype({:#x}) : Unable to set value for idainfo.ostype to the specified value ({:#x}).".format('.'.join([__name__, cls.__name__]), ostype_t, ostype_t))
return result
@utils.multicase(OSTYPE_=six.string_types)
@classmethod
def ostype(cls, OSTYPE_):
'''Set the operating system type for the database to the value for the string `OSTYPE_`.'''
prefix, choice = 'OSTYPE_', OSTYPE_.upper()
candidates = {prefix + choice, choice}
# Grab all of our available choices from the idc module since they're not defined anywhere else.
import idc
filtered = ((name, getattr(idc, name)) for name in dir(idc) if name.startswith(prefix))
choices = {item : value for item, value in filtered if isinstance(value, six.integer_types)}
# Find a valid choice by iterating through each one and seeing if its in our list of candidates.
iterable = (value for item, value in choices.items() if item in candidates)
value = builtins.next(iterable, None)
if value is None:
raise E.ItemNotFoundError(u"{:s}.ostype({!r}) : Unable to find the requested operating system type ({!r}) in the list of choices.".format('.'.join([__name__, cls.__name__]), OSTYPE_, string))
# We found it, so we can recurse into the correct case to assign it.
return cls.ostype(value)
@utils.multicase()
@classmethod
def apptype(cls):
'''Return the application type identified by the loader when creating the database.'''
# FIXME: this is a bitflag that should be documented in libfuncs.hpp
# which unfortunately is not included anywhere in the sdk.
if idaapi.__version__ < 7.2:
return cls.info.apptype
return idaapi.inf_get_apptype()
@utils.multicase(apptype_t=six.integer_types)
@classmethod
def apptype(cls, apptype_t):
'''Set the application type for the database to the specified `apptype_t`.'''
if idaapi.__version__ < 7.2:
result, cls.info.apptype = cls.info.ostype, apptype_t
return result
# Newer versions of IDA use the idaapi.inf_get_filetype() and idaapi.inf_set_filetype() functions.
result = idaapi.inf_get_apptype()
if not idaapi.inf_set_apptype(apptype_t):
raise E.DisassemblerError(u"{:s}.apptype({:#x}) : Unable to set value for idainfo.apptype to the specified value ({:#x}).".format('.'.join([__name__, cls.__name__]), apptype_t, apptype_t))
return result
@utils.multicase(APPT_=six.string_types)
@classmethod
def apptype(cls, APPT_):
'''Set the application type for the database to the value for the string `APPT_`.'''
prefix, choice = 'APPT_', APPT_.upper()
candidates = {prefix + choice, choice}
# Grab all of our available choices from the idc module since they're not defined anywhere else.
import idc
filtered = ((name, getattr(idc, name)) for name in dir(idc) if name.startswith(prefix))
choices = {item : value for item, value in filtered if isinstance(value, six.integer_types)}
# Find a valid choice by iterating through each one and seeing if its in our list of candidates.
iterable = (value for item, value in choices.items() if item in candidates)
value = builtins.next(iterable, None)
if value is None:
raise E.ItemNotFoundError(u"{:s}.apptype({!r}) : Unable to find the requested application type ({!r}) in the list of choices.".format('.'.join([__name__, cls.__name__]), APPT_, string))
# We found it, so we can recurse into the correct case to assign it.
return cls.apptype(value)
@classmethod
def changes(cls):
'''Return the number of changes within the database.'''
if idaapi.__version__ < 7.0:
raise E.UnsupportedVersion(u"{:s}.changes() : This function is only supported on versions of IDA 7.0 and newer.".format('.'.join([__name__, cls.__name__])))
elif idaapi.__version__ < 7.2:
return cls.info.database_change_count
return idaapi.inf_get_database_change_count()
@classmethod
def processor(cls):
'''Return the name of the processor used by the database.'''
if idaapi.__version__ < 7.0:
raise E.UnsupportedVersion(u"{:s}.processor() : This function is only supported on versions of IDA 7.0 and newer.".format('.'.join([__name__, cls.__name__])))
elif hasattr(cls.info, 'procName'):
result = cls.info.procName
else:
result = idaapi.inf_get_procname()
return utils.string.of(result)
@classmethod
def compiler(cls):
'''Return the compiler that was configured for the database.'''
if idaapi.__version__ < 7.2:
return cls.info.cc
# Newer versions of IDA use the idaapi.inf_get_cc() function.
cc = idaapi.compiler_info_t()
if not idaapi.inf_get_cc(cc):
raise E.DisassemblerError(u"{:s}.processor() : Unable to fetch the value for the idainfo.cc attribute.".format('.'.join([__name__, cls.__name__])))
return cc
@classmethod
def version(cls):
'''Return the version of the database.'''
if idaapi.__version__ < 7.2:
return cls.info.version
return idaapi.inf_get_version()
@classmethod
def type(cls, typestr):
'''Evaluates a type string and returns its size according to the compiler used by the database.'''
lookup = {
'bool': 'size_b',
'short': 'size_s',
'int': 'size_i', 'float': 'size_l', 'single': 'size_l',
'long': 'size_l',
'longlong': 'size_ll', 'double': 'size_ll',
'enum': 'size_e',
'longdouble': 'size_ldbl',
'align': 'defalign', 'alignment': 'defalign',
}
string = typestr.replace(' ', '')
return getattr(cls.compiler(), lookup.get(string.lower(), typestr.lower()))
@classmethod
def bits(cls):
'''Return number of bits of the processor used by the database.'''
result = cls.lflags(idaapi.LFLG_PC_FLAT | idaapi.LFLG_64BIT)
if result & idaapi.LFLG_64BIT:
return 64
elif result & idaapi.LFLG_PC_FLAT:
return 32
return 32 if result & idaapi.LFLG_FLAT_OFF32 else 16
@classmethod
def size(cls):
'''Return the number of bytes used by the database which can be used to distinguish whether you're running 32-bit or 64-bit.'''
import ida_typeinf
# This is a trick gifted by me by rolfr through his comprehensive
# knowledge of IDA internals in order to get this attribute in the
# exact way that IDA does it. We use the ida_typeinf module instead
# of idaapi in order to preserve this tech throughout history in the
# way it was bestowed upon us...
tif = ida_typeinf.tinfo_t()
tif.create_ptr(ida_typeinf.tinfo_t(ida_typeinf.BT_VOID))
return tif.get_size()
@classmethod
def bitsize(cls):
'''Return the number of bits used by the database.'''
return 8 * cls.size()
@classmethod
def byteorder(cls):
'''Return a string representing the byte-order used by integers in the database.'''
if idaapi.__version__ < 7.0:
res = idaapi.cvar.inf.mf
return 'big' if res else 'little'
return 'big' if cls.lflags(idaapi.LFLG_MSF) else 'little'
@classmethod
def main(cls):
if idaapi.__version__ < 7.2:
return cls.info.main
return idaapi.inf_get_main()
@classmethod
def entry(cls):
'''Return the first entry point for the database.'''
if idaapi.__version__ < 7.2:
return cls.info.start_ea
return idaapi.inf_get_start_ea()
@classmethod
def margin(cls):
'''Return the current margin position for the current database.'''
return cls.info.margin if idaapi.__version__ < 7.2 else idaapi.inf_get_margin()
@classmethod
def bounds(cls):
'''Return the bounds of the current database in a tuple formatted as `(left, right)`.'''
if idaapi.__version__ < 7.2:
min, max = cls.info.minEA, cls.info.maxEA
else:
min, max = idaapi.inf_get_min_ea(), idaapi.inf_get_max_ea()
return interface.bounds_t(min, max)
class register(object):
"""
This namespace returns the available register names and their
sizes for the database.
"""
@classmethod
def names(cls):
'''Return all of the register names in the database.'''
names = idaapi.ph_get_regnames()
return [utils.string.of(item) for item in names]
@classmethod
def segments(cls):
'''Return all of the segment registers in the database.'''
sreg_first, sreg_last = (idaapi.ph_get_regFirstSreg, idaapi.ph_get_regLastSreg) if idaapi.__version__ < 7.0 else (idaapi.ph_get_reg_first_sreg, idaapi.ph_get_reg_last_sreg)
names = cls.names()
return [names[ri] for ri in builtins.range(sreg_first(), 1 + sreg_last())]
@classmethod
def codesegment(cls):
'''Return all of the code segment registers in the database.'''
res = idaapi.ph_get_regCodeSreg() if idaapi.__version__ < 7.0 else idaapi.ph_get_reg_code_sreg()
return cls.names()[res]
@classmethod
def datasegment(cls):
'''Return all of the data segment registers in the database.'''
res = idaapi.ph_get_regDataSreg() if idaapi.__version__ < 7.0 else idaapi.ph_get_reg_data_sreg()
return cls.names()[res]
@classmethod
def segmentbits(cls):
'''Return the segment register size for the database.'''
return 8 * idaapi.ph_get_segreg_size()
range = utils.alias(config.bounds, 'config')
filename, idb, module, path = utils.alias(config.filename, 'config'), utils.alias(config.idb, 'config'), utils.alias(config.module, 'config'), utils.alias(config.path, 'config')
path = utils.alias(config.path, 'config')
baseaddress = base = utils.alias(config.baseaddress, 'config')
class functions(object):
r"""
This namespace is used for listing all the functions inside the
database. By default a list is returned containing the address of
each function.
When listing functions that are matched, the following legend can be
used to identify certain characteristics about them:
`+` - The function has an implicit tag (named or typed)
`*` - The function has been explicitly tagged
`J` - The function is a wrapper or a thunk
`L` - The function was pattern matched as a library
`S` - The function is declared statically
`^` - The function does not contain a frame
`?` - The function has its stack points calculated incorrectly and may be incorrect
`T` - The function has a prototype that was applied to it manually or via decompilation
`t` - The function has a prototype that was guessed
`D` - The function has been previously decompiled
The different types that one can match functions with are the following:
`address` or `ea` - Match according to the function's address
`name` - Match according to the exact name
`like` - Filter the function names according to a glob
`regex` - Filter the function names according to a regular-expression
`typed` - Filter the functions for any that have type information applied to them
`decompiled` - Filter the functions for any that have been decompiled
`frame` - Filter the functions for any that contain a frame
`problems` - Filter the functions for any that contain problems with their stack
`library` - Filter the functions that any which were detected as a library function
`wrapper` - Filter the functions that are flagged as wrappers (thunks)
`exceptions` Filter the functions for any that either handles an exception or sets up a handler
`tagged` - Filter the functions for any that use the specified tag(s)
`predicate` - Filter the functions by passing their ``idaapi.func_t`` to a callable
Some examples of how to use these keywords are as follows::
> for ea in database.functions(): ...
> database.functions.list('*sub*')
> iterable = database.functions.iterate(regex='.*alloc')
> result = database.functions.search(like='*alloc*')
"""
__matcher__ = utils.matcher()
__matcher__.boolean('name', lambda name, item: name.lower() == item.lower(), function.by, function.name)
__matcher__.combinator('like', utils.fcompose(fnmatch.translate, utils.fpartial(re.compile, flags=re.IGNORECASE), operator.attrgetter('match')), function.by, function.name)
__matcher__.combinator('regex', utils.fcompose(utils.fpartial(re.compile, flags=re.IGNORECASE), operator.attrgetter('match')), function.by, function.name)
__matcher__.boolean('address', function.contains), __matcher__.boolean('ea', function.contains)
__matcher__.mapping('typed', operator.truth, function.top, lambda ea: idaapi.get_tinfo2(ea, idaapi.tinfo_t()) if idaapi.__version__ < 7.0 else idaapi.get_tinfo(idaapi.tinfo_t(), ea))
__matcher__.mapping('decompiled', operator.truth, function.type.is_decompiled)
__matcher__.mapping('frame', operator.truth, function.type.has_frame)
__matcher__.mapping('library', operator.truth, function.by, operator.attrgetter('flags'), utils.fpartial(operator.and_, idaapi.FUNC_LIB))
__matcher__.mapping('wrapper', operator.truth, function.by, operator.attrgetter('flags'), utils.fpartial(operator.and_, idaapi.FUNC_THUNK))
__matcher__.boolean('tagged', lambda parameter, keys: operator.truth(keys) == parameter if isinstance(parameter, bool) else operator.contains(keys, parameter) if isinstance(parameter, six.string_types) else keys & builtins.set(parameter), function.top, function.tag, operator.methodcaller('keys'), builtins.set)
__matcher__.predicate('predicate', function.by)
__matcher__.predicate('pred', function.by)
if any(hasattr(idaapi, item) for item in ['is_problem_present', 'QueueIsPresent']):
__matcher__.mapping('problems', operator.truth, function.top, utils.frpartial(function.type.has_problem, getattr(idaapi, 'PR_BADSTACK', 0xb)))
if all(hasattr(idaapi, Fname) for Fname in ['tryblks_t', 'get_tryblks']):
__matcher__.mapping('exceptions', operator.truth, function.by, lambda fn: idaapi.get_tryblks(idaapi.tryblks_t(), fn), utils.fpartial(operator.ne, 0))
# chunk matching
#__matcher__.boolean('greater', operator.le, utils.fcompose(function.chunks, functools.partial(map, builtins.list, operator.itemgetter(-1)), max)), __matcher__.boolean('gt', operator.lt, utils.fcompose(function.chunks, functools.partial(map, builtins.list, operator.itemgetter(-1)), max))
#__matcher__.boolean('less', operator.ge, utils.fcompose(function.chunks, functools.partial(map, builtins.list, operator.itemgetter(0)), min)), __matcher__.boolean('lt', operator.gt, utils.fcompose(function.chunks, functools.partial(map, builtins.list, operator.itemgetter(0)), min))
# entry point matching
__matcher__.boolean('greater', operator.le, function.top), __matcher__.boolean('gt', operator.lt, function.top)
__matcher__.boolean('less', operator.ge, function.top), __matcher__.boolean('lt', operator.gt, function.top)
def __new__(cls):
'''Return a list of all of the functions in the current database.'''
return [item for item in cls.__iterate__()]
@utils.multicase()
@classmethod
def __iterate__(cls):
'''Iterates through all of the functions in the current database (ripped from idautils).'''
left, right = config.bounds()
# find first function chunk
ch = idaapi.get_fchunk(left) or idaapi.get_next_fchunk(left)
while ch and interface.range.start(ch) < right and (ch.flags & idaapi.FUNC_TAIL) != 0:
ui.navigation.procedure(interface.range.start(ch))
ch = idaapi.get_next_fchunk(interface.range.start(ch))
# iterate through the rest of the functions in the database
while ch and interface.range.start(ch) < right:
ui.navigation.procedure(interface.range.start(ch))
if function.within(interface.range.start(ch)):
yield interface.range.start(ch)
ch = idaapi.get_next_func(interface.range.start(ch))
return
@utils.multicase(string=six.string_types)
@classmethod
@utils.string.decorate_arguments('string')
def iterate(cls, string):
'''Iterate through all of the functions in the database with a glob that matches `string`.'''
return cls.iterate(like=string)
@utils.multicase()
@classmethod
@utils.string.decorate_arguments('name', 'like', 'regex')
def iterate(cls, **type):
'''Iterate through all of the functions in the database that match the keyword specified by `type`.'''
iterable = cls.__iterate__()
for key, value in (type or {'predicate': utils.fconstant(True)}).items():
iterable = cls.__matcher__.match(key, value, iterable)
for item in iterable: yield item
@utils.multicase(string=six.string_types)
@classmethod
@utils.string.decorate_arguments('string')
def list(cls, string):
'''List all of the functions in the database with a glob that matches `string`.'''
return cls.list(like=string)
@utils.multicase()
@classmethod
@utils.string.decorate_arguments('name', 'like', 'regex')
def list(cls, **type):
'''List all of the functions in the database that match the keyword specified by `type`.'''
listable = []
# Some utility functions for grabbing counts of function attributes
Fcount_lvars = utils.fcompose(function.frame.lvars, utils.count)
Fcount_avars = utils.fcompose(function.frame.args.iterate, utils.count)
# Set some reasonable defaults here
maxentry = config.bounds()[0]
maxaddr = minaddr = maxchunks = 0
maxname = maxunmangled = chunks = marks = blocks = exits = 0
lvars = avars = refs = 0
# First pass through the list to grab the maximum lengths of the different fields
for ea in cls.iterate(**type):
func, _ = function.by(ea), ui.navigation.procedure(ea)
maxentry = max(ea, maxentry)
unmangled, realname = function.name(func), name(ea)
maxname = max(len(unmangled), maxname)
maxunmangled = max(len(unmangled), maxunmangled) if not internal.declaration.mangledQ(realname) else maxunmangled
bounds, items = function.bounds(func), [item for item in function.chunks(func)]
maxaddr, minaddr = max(max(bounds), maxaddr), max(min(bounds), minaddr)
maxchunks = max(len(items), maxchunks)
# Figure out the maximum values for each of these attributes
blocks = max(len(builtins.list(function.blocks(func, silent=True))), blocks)
exits = max(len(builtins.list(function.bottom(func))), exits)
refs = max(len(xref.up(ea)), refs)
lvars = max(Fcount_lvars(func) if idaapi.get_frame(ea) else 0, lvars)
avars = max(Fcount_avars(func), avars)
listable.append(ea)
# Collect the number of digits for everything from the first pass
cindex = utils.string.digits(len(listable), 10) if listable else 1
try: cmaxoffset = utils.string.digits(offset(maxentry), 16)
except E.OutOfBoundsError: cmaxoffset = 0
cmaxentry, cmaxaddr, cminaddr = (utils.string.digits(item, 16) for item in [maxentry, maxaddr, minaddr])
cchunks, cblocks, cexits, cavars, clvars, crefs = (utils.string.digits(item, 10) for item in [maxchunks, blocks, exits, avars, lvars, refs])
# List all the fields of every single function that was matched
for index, ea in enumerate(listable):
func, decompiledQ = function.by(ui.navigation.procedure(ea)), interface.node.aflags(ui.navigation.procedure(ea), getattr(idaapi, 'AFL_HR_DETERMINED', 0xc0000000))
tags = function.tag(ea)
# any flags that might be useful
ftagged = '-' if not tags else '*' if any(not item.startswith('__') for item in tags) else '+'
ftyped = 'D' if function.type.is_decompiled(ea) else '-' if not function.type.has_typeinfo(func) else 'T' if interface.node.aflags(ea, idaapi.AFL_USERTI) else 't'
fframe = '?' if function.type.has_problem(ea, getattr(idaapi, 'PR_BADSTACK', 0xb)) else '-' if idaapi.get_frame(ea) else '^'
fgeneral = 'J' if func.flags & idaapi.FUNC_THUNK else 'L' if func.flags & idaapi.FUNC_LIB else 'S' if func.flags & idaapi.FUNC_STATICDEF else 'F'
flags = itertools.chain(fgeneral, fframe, ftyped, ftagged)
# naming information
unmangled, realname = function.name(func), name(ea)
# chunks and boundaries
chunks = [item for item in function.chunks(func)]
bounds = function.bounds(func)
# try/except handlers
if all(hasattr(idaapi, Fname) for Fname in ['tryblks_t', 'get_tryblks']):
tb = idaapi.tryblks_t()
blkcount = idaapi.get_tryblks(tb, func)
trycount = sum(tb[i].is_cpp() for i in builtins.range(blkcount))
iterable = (tb[i].cpp() if tb[i].is_cpp() else tb[i].seh() for i in builtins.range(tb.size()))
ehcount = sum(item.size() for item in iterable)
else:
tb = None
blkcount = trycount = ehcount = 0
# now we can output everything that was found
six.print_(u"{:<{:d}s} {:+#0{:d}x} : {:#0{:d}x}..{:#0{:d}x} : {:<{:d}s} {:s} : {:<{:d}s} : refs:{:<{:d}d} args:{:<{:d}d} lvars:{:<{:d}d} blocks:{:<{:d}d} exits:{:<{:d}d}{:s}".format(
"[{:d}]".format(index), 2 + math.trunc(cindex),
offset(ea), 3 + math.trunc(cmaxoffset),
bounds[0], 2 + math.trunc(cminaddr), bounds[1], 2 + math.trunc(cmaxaddr),
"({:d})".format(len(chunks)), 2 + cchunks, ''.join(flags),
unmangled, math.trunc(maxname if internal.declaration.mangledQ(realname) else maxunmangled),
len(xref.up(ea)), crefs,
Fcount_avars(func), cavars,
Fcount_lvars(func) if idaapi.get_frame(ea) else 0, clvars,
len(builtins.list(function.blocks(func, silent=True))), cblocks,
len(builtins.list(function.bottom(func))), cexits,
" exceptions:{:d}+{:d}/{:d}".format(blkcount - trycount, trycount, ehcount) if tb else ''
))
return
@utils.multicase(string=six.string_types)
@classmethod
@utils.string.decorate_arguments('string')
def search(cls, string):
'''Search through all of the functions matching the glob `string` and return the first result.'''
return cls.search(like=string)
@utils.multicase()
@classmethod
@utils.string.decorate_arguments('name', 'like', 'regex')
def search(cls, **type):
'''Search through all of the functions within the database and return the first result matching the keyword specified by `type`.'''
query_s = utils.string.kwargs(type)
listable = [item for item in cls.iterate(**type)]
if len(listable) > 1:
messages = ((u"[{:d}] {:s}".format(i, function.name(ea))) for i, ea in enumerate(listable))
[ logging.info(msg) for msg in messages ]
f = utils.fcompose(function.by, function.name)
logging.warning(u"{:s}.search({:s}) : Found {:d} matching results. Returning the first function \"{:s}\".".format('.'.join([__name__, cls.__name__]), query_s, len(listable), utils.string.escape(f(listable[0]), '"')))
iterable = (item for item in listable)
res = builtins.next(iterable, None)
if res is None:
raise E.SearchResultsError(u"{:s}.search({:s}) : Found 0 matching results.".format('.'.join([__name__, cls.__name__]), query_s))
return res
class segments(object):
r"""
This namespace is used for listing all the segments inside the
database. By default each segment's boundaries are yielded.
The different types that one can match segments with are the following:
`name` - Match according to the true segment name
`like` - Filter the segment names according to a glob
`regex` - Filter the segment names according to a regular-expression
`index` - Match the segment by its index
`identifier` - Match the segment by its identifier (``idaapi.segment_t.name``)
`selector` - Match the segment by its selector (``idaapi.segment_t.sel``)
`greater` or `gt` - Filter the segments for any after the specified address
`less` or `lt` - Filter the segments for any before the specified address
`predicate` - Filter the segments by passing its ``idaapi.segment_t`` to a callable
Some examples of using these keywords are as follows::
> for l, r in database.segments(): ...
> database.segments.list(regex=r'\.r?data')
> iterable = database.segments.iterate(like='*text*')
> result = database.segments.search(greater=0x401000)
"""
def __new__(cls):
'''Yield the bounds of each segment within the current database.'''
for seg in segment.__iterate__():
yield interface.range.bounds(seg)
return
@utils.multicase(name=six.string_types)
@classmethod
@utils.string.decorate_arguments('name')
def list(cls, name):
'''List all of the segments defined in the database that match the glob `name`.'''
return cls.list(like=name)
@utils.multicase()
@classmethod
@utils.string.decorate_arguments('name', 'like', 'regex')
def list(cls, **type):
'''List all of the segments in the database that match the keyword specified by `type`.'''
return segment.list(**type)
@utils.multicase(name=six.string_types)
@classmethod
@utils.string.decorate_arguments('name')
def iterate(cls, name):
'''Iterate through all of the segments in the database with a glob that matches `name`.'''
return cls.iterate(like=name)
@utils.multicase()
@classmethod
@utils.string.decorate_arguments('name', 'like', 'regex')
def iterate(cls, **type):
'''Iterate through all the segments defined in the database matching the keyword specified by `type`.'''
return segment.__iterate__(**type)
@utils.multicase(name=six.string_types)
@classmethod
@utils.string.decorate_arguments('name')
def search(cls, name):
'''Search through all of the segments matching the glob `name` and return the first result.'''
return cls.search(like=name)
@utils.multicase()
@classmethod
@utils.string.decorate_arguments('name', 'like', 'regex')
def search(cls, **type):
'''Search through all of the segments within the database and return the first result matching the keyword specified by `type`.'''
return segment.search(**type)
@utils.multicase()
def instruction():
'''Return the instruction at the current address as a string.'''
return instruction(ui.current.address())
@utils.multicase(ea=six.integer_types)
def instruction(ea):
'''Return the instruction at the address `ea` as a string.'''
ash = idaapi.cvar.ash if idaapi.__version__ < 7.5 else idaapi.get_ash()
cmnt1, cmnt2 = ash.cmnt, ash.cmnt2
# first grab the disassembly and then remove all of IDA's tag information from it
insn = idaapi.generate_disasm_line(interface.address.inside(ea))
unformatted = idaapi.tag_remove(insn)
# if there's a terminating comment character, locate it, and then slice out just the comment
if cmnt2:
lindex = unformatted.rfind(cmnt1)
rindex = lindex + unformatted[lindex:].find(cmnt2) + len(cmnt2)
nocomment = unformatted if lindex < 0 else unformatted[:lindex] if rindex < 0 else (nocomment[:lindex] + nocomment[rindex:])
# there's no terminating comment character, so we just need to cull out everything after cmnt1
elif cmnt1:
index = unformatted.rfind(cmnt1)
nocomment = unformatted if index < 0 else unformatted[:index]
# if the starting cmnt1 character isn't defined, then we don't do anything.
else:
nocomment = unformatted
# combine any multiple spaces into just a single space and return it
res = utils.string.of(nocomment.strip())
return functools.reduce(lambda agg, char: agg + (('' if agg.endswith(' ') else ' ') if char == ' ' else char), res, '')
@utils.multicase()
def disassemble(**options):
'''Disassemble the instructions at the current address.'''
return disassemble(ui.current.address(), **options)
@utils.multicase(ea=six.integer_types)
def disassemble(ea, **options):
"""Disassemble the instructions at the address specified by `ea`.
If the integer `count` is specified, then return `count` number of instructions.
If the bool `comments` is true, then return the comments for each instruction as well.
"""
ea = interface.address.inside(ea)
commentQ = builtins.next((options[k] for k in ['comment', 'comments'] if k in options), False)
# grab the values we need in order to distinguish a comment
ash = idaapi.cvar.ash if idaapi.__version__ < 7.5 else idaapi.get_ash()
cmnt1, cmnt2 = ash.cmnt, ash.cmnt2
# enter a loop that goes through the number of line items requested by the user
res, count = [], options.get('count', 1)
while count > 0:
# grab the instruction and remove all of IDA's tag information from it
insn = idaapi.generate_disasm_line(ea) or ''
unformatted = idaapi.tag_remove(insn)
# check if the terminating char (cmnt2) is defined
if cmnt2:
lindex = unformatted.rfind(cmnt1)
rindex = lindex + unformatted[lindex:].find(cmnt2) + len(cmnt2)
# so that we can separate the comment out of it
nocomment = unformatted if lindex < 0 else unformatted[:lindex] if rindex < 0 else (nocomment[:lindex] + nocomment[rindex:])
comment = unformatted[lindex : lindex] if lindex < 0 else unformatted[lindex:] if rindex < 0 else comment[lindex : rindex]
# if it's not, then just use the starting char (cmnt1) to find the comment
elif cmnt1:
index = unformatted.rfind(cmnt1)
nocomment, comment = (unformatted, unformatted[index : index]) if index < 0 else (unformatted[:index], unformatted[index:])
# if this comment is undefined, then there ain't shit we can do with it,
# and we need to just append it as-is
else:
res.append(u"{:x}: {:s}".format(ea, unformatted.strip()))
# remove any surrounding spaces from the instruction
stripped = nocomment.strip()
# combine all multiple spaces together so it's single-spaced
noextraspaces = functools.reduce(lambda agg, char: agg + (('' if agg.endswith(' ') else ' ') if char == ' ' else char), utils.string.of(stripped), '')
# if we've been asked to include the comment, then first we need to clean
# it up a bit.
if commentQ:
cleaned = comment[len(cmnt1) : -len(cmnt2)] if cmnt2 else comment[len(cmnt1):]
stripped = cleaned.strip()
# then we can concatenate it with our instruction and its comment characters
withcharacters = u''.join([u"{:s} ".format(cmnt1) if cmnt1 else u'', stripped, u" {:s}".format(cmnt2) if cmnt2 else u''])
# and then we can append it to our result
res.append(u"{:x}: {:s}{:s}".format(ea, noextraspaces, u" {:s}".format(withcharacters) if stripped else ''))
# otherwise we cna simply append it to our result with the address in front
else:
res.append(u"{:x}: {:s}".format(ea, noextraspaces))
# move on to the next iteration
ea = address.next(ea) if count > 1 else address.tail(ea)
count -= 1
return '\n'.join(res)
disasm = utils.alias(disassemble)
@utils.multicase()
def read():
'''Return the bytes defined at the current selection or address.'''
address, selection = ui.current.address(), ui.current.selection()
if operator.eq(*(internal.interface.address.head(ea, silent=True) for ea in selection)):
return read(address, type.size(address))
return read(selection)
@utils.multicase(ea=six.integer_types)
def read(ea):
'''Return the number of bytes associated with the address `ea`.'''
return read(ea, type.size(ea))
@utils.multicase(ea=six.integer_types, size=six.integer_types)
def read(ea, size):
'''Return `size` number of bytes from address `ea`.'''
get_bytes = idaapi.get_many_bytes if idaapi.__version__ < 7.0 else idaapi.get_bytes
start, end = interface.address.within(ea, ea + size)
return get_bytes(ea, end - start) or b''
@utils.multicase(bounds=tuple)
def read(bounds):
'''Return the bytes within the specified `bounds`.'''
get_bytes = idaapi.get_many_bytes if idaapi.__version__ < 7.0 else idaapi.get_bytes
bounds = ea, _ = interface.bounds_t(*bounds)
return get_bytes(ea, bounds.size) or b''
@utils.multicase(data=b''.__class__)
def write(data, **persist):
'''Modify the database at the current address with the bytes specified in `data`.'''
return write(ui.current.address(), data, **persist)
@utils.multicase(ea=six.integer_types, data=b''.__class__)
def write(ea, data, **persist):
"""Modify the database at address `ea` with the bytes specified in `data`
If the bool `persist` is specified, then modify what IDA considers the original bytes.
"""
patch_bytes, put_bytes = (idaapi.patch_many_bytes, idaapi.put_many_bytes) if idaapi.__version__ < 7.0 else (idaapi.patch_bytes, idaapi.put_bytes)
ea, _ = interface.address.within(ea, ea + len(data))
originalQ = builtins.next((persist[k] for k in ['original', 'persist', 'store', 'save'] if k in persist), False)
return patch_bytes(ea, data) if originalQ else put_bytes(ea, data)
class names(object):
"""
This namespace is used for listing all of the names (symbols) within the
database. By default the `(address, name)` is yielded in its mangled form.
When listing names that are matched, the following legend can be used to
identify certain characteristics about the address of the returned name:
`I` - The symbol is residing in an import segment
`C` - The address of the symbol is marked as code
`D` - The address of the symbol is marked as data
`^` - The address of the symbol is is initialized
`+` - The symbol has an implicit tag applied to it (named or typed)
`*` - The symbol has an explicit tag applied to it
The available types that one can filter the symbols with are as follows:
`address` - Match according to the address of the symbol
`name` - Match according to the name of the unmangled symbol
`unmangled` - Filter the unmangled symbol names according to a regular-expression
`like` - Filter the symbol names according to a glob
`regex` - Filter the symbol names according to a regular-expression
`index` - Match the symbol according to its index
`function` - Filter the symbol names for any that are referring to a function
`imports` - Filter the symbol names for any that are imports
`typed` - Filter the symbol names for any that have type information applied to them
`tagged` - Filter the symbol names for any that use the specified tag(s)
`predicate` - Filter the symbols by passing their address to a callable
Some examples of using these keywords are as follows::
> list(database.names())
> database.names.list(index=31)
> iterable = database.names.iterate(like='str.*')
> result = database.names.search(name='some_really_sick_symbol_name')
"""
__matcher__ = utils.matcher()
__matcher__.mapping('address', idaapi.get_nlist_ea), __matcher__.mapping('ea', idaapi.get_nlist_ea)
__matcher__.boolean('name', lambda name, item: name.lower() == item.lower(), idaapi.get_nlist_name, internal.declaration.demangle)
__matcher__.combinator('like', utils.fcompose(fnmatch.translate, utils.fpartial(re.compile, flags=re.IGNORECASE), operator.attrgetter('match')), idaapi.get_nlist_name, utils.string.of)
__matcher__.combinator('regex', utils.fcompose(utils.fpartial(re.compile, flags=re.IGNORECASE), operator.attrgetter('match')), idaapi.get_nlist_name, utils.string.of)
__matcher__.combinator('unmangled', utils.fcompose(utils.fpartial(re.compile, flags=re.IGNORECASE), operator.attrgetter('match')), idaapi.get_nlist_name, internal.declaration.demangle)
__matcher__.combinator('demangled', utils.fcompose(utils.fpartial(re.compile, flags=re.IGNORECASE), operator.attrgetter('match')), idaapi.get_nlist_name, internal.declaration.demangle)
__matcher__.mapping('function', function.within, idaapi.get_nlist_ea)
__matcher__.mapping('imports', utils.fpartial(operator.eq, idaapi.SEG_XTRN), idaapi.get_nlist_ea, idaapi.segtype)
__matcher__.boolean('tagged', lambda parameter, keys: operator.truth(keys) == parameter if isinstance(parameter, bool) else operator.contains(keys, parameter) if isinstance(parameter, six.string_types) else keys & builtins.set(parameter), idaapi.get_nlist_ea, lambda ea: function.tag(ea) if function.within(ea) else tag(ea), operator.methodcaller('keys'), builtins.set)
__matcher__.mapping('typed', operator.truth, idaapi.get_nlist_ea, lambda ea: idaapi.get_tinfo2(ea, idaapi.tinfo_t()) if idaapi.__version__ < 7.0 else idaapi.get_tinfo(idaapi.tinfo_t(), ea))
__matcher__.predicate('predicate', idaapi.get_nlist_ea)
__matcher__.predicate('pred', idaapi.get_nlist_ea)
__matcher__.attribute('index')
def __new__(cls):
'''Iterate through all of the names in the database yielding a tuple of the address and its name.'''
for index in builtins.range(idaapi.get_nlist_size()):
res = zip([idaapi.get_nlist_ea, utils.fcompose(idaapi.get_nlist_name, utils.string.of)], 2 * [index])
yield tuple(f(x) for f, x in res)
return
@utils.multicase(string=six.string_types)
@classmethod
@utils.string.decorate_arguments('string')
def __iterate__(cls, string):
return cls.__iterate__(like=string)
@utils.multicase()
@classmethod
@utils.string.decorate_arguments('name', 'like', 'regex')
def __iterate__(cls, **type):