-
Notifications
You must be signed in to change notification settings - Fork 1
/
slowtrace_helpers.py
13637 lines (11455 loc) · 513 KB
/
slowtrace_helpers.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
import collections
import inspect
import os
import traceback
import re
import subprocess
import math
# from fnvhash import fnv1a_64 as fnv
from static_vars import *
from superglobals import *
from attrdict1 import SimpleAttrDict
from collections import defaultdict
from string_between import string_between, string_between_splice
try:
from exectools import _import, _from, execfile
except ModuleNotFoundError:
from exectools import _import, _from, execfile
try:
from anytree import Node, RenderTree
except ModuleNotFoundError:
print("pip install anytree (if you want to draw any graphs)")
# _import('from circularlist import CircularList')
# _import('from ranger import *')
# causes loop in 2.7
# _import('from sfida.sf_is_flags import *')
####
## COLORS
###
# 0x1f153e - pdata start
# 0x140128 - check by protection
# 0x280128 - healed by protection
# 0x3c0128 - checked & healed by protection
# 0x200100 - unpacked on-demand by protection
# 0x280c01 - processed by `retrace`
###
try:
import ida_auto
import ida_bytes
import ida_funcs
import ida_hexrays
import ida_ida
import ida_xref
import ida_name
import idaapi
import idautils
import idc
from idc import BADADDR
from ida_bytes import DELIT_NOTRUNC
except:
class idc:
BADADDR = 0xffffffffffffffff
BADADDR = idc.BADADDR
if not idc:
import obfu
from collections import defaultdict
from di import decode_insn
from helpers import hasAnyName, hex
from idc import BADADDR, FUNCATTR_FLAGS, CIC_FUNC, DELIT_DELNAMES, SetType, SN_NOWARN, SN_AUTO, FUNCATTR_OWNER, FUNCATTR_START, FUNCATTR_END, GetDisasm, FF_REF, FF_FLOW, DEMNAM_FIRST, get_name_ea_simple
from idc import BADADDR, fl_JF, fl_JN, fl_CF, fl_CN, auto_wait, GetDisasm, is_data, is_head, isRef, hasName, hasUserName, is_qword, is_defarg0, is_off0, is_code, FUNCATTR_OWNER
from idc_bc695 import GetFunctionName, GetIdbPath, LocByName, MakeNameEx, DELIT_DELNAMES, DELIT_EXPAND, DELIT_NOTRUNC, Name, PatchByte, DelFunction, GetSpDiff, SetSpDiff, Dword, Qword, ItemHead, SegName, GetSpd, Demangle, IdaGetMnem
from idc_bc695 import GetOperandValue, SegName, LocByName, GetIdbPath, GetInputFile, ScreenEA, DELIT_EXPAND, DELIT_NOTRUNC, Wait, NextNotTail, AppendFchunk, GetFunctionName
import sfida.is_flags
from membrick import MakeSigned
from obfu_helpers import PatchBytes
from obfu_helpers import hex_byte_as_pattern_int
from sfcommon import GetFuncEnd, GetFuncStart, MakeCodeAndWait
from sftools import MyMakeFunction, MyMakeUnknown
from sftools import MyMakeUnknown, MyMakeUnkn, MyMakeFunction
from slowtrace2 import visited, get_byte, AdvanceFailure
from start import isString
# from underscoretest import _
try:
import __builtin__ as builtins
integer_types = (int, long)
string_types = (basestring, str, unicode)
string_type = unicode
byte_type = str
long_type = long
except:
import builtins
integer_types = (int,)
string_types = (str, bytes)
byte_type = bytes
string_type = str
long_type = int
long = int
execfile("fsm")
# with open(os.path.dirname(__file__) + os.sep + 'refresh.py', 'r') as f: exec(compile(f.read().replace('__BASE__', os.path.basename(__file__).replace('.py', '')).replace('__FILE__', __file__), __file__, 'exec'))
class Namespace(object):
""" For simulating full closure support
"""
pass
def A(o):
if o is None:
return []
if isinstance(o, list):
return o
if isflattenable(o) and len(list(o)) > 1:
return list(o)
if isflattenable(o):
return genAsList(o)
# list(o) will break up strings
return [o]
def S(o):
return set(A(o))
class TraceDepth(object):
_depth = 0
def __enter__(self):
TraceDepth._depth += 1
return TraceDepth._depth
def __exit__(self, exc_type, exc_value, traceback):
TraceDepth._depth -= 1
@staticmethod
def get():
return TraceDepth._depth
#
@static_vars(last_indent=0)
def printi(value, sacrificial=None, depth=None, *args, **kwargs):
g_depth = TraceDepth.get()
if sacrificial is not None:
value = ", ".join(str(x) for x in [value] + list(args))
# assert sacrificial is None
if isinstance(depth, int):
g_depth = depth
indentString = ' '
if g_depth > printi.last_indent:
indentString = indentString.replace(' ', '>')
elif g_depth < printi.last_indent:
indentString = indentString.replace(' ', '<')
printi.last_indent = g_depth
# g_output = getglobal('g_output', None)
if g_depth:
_str = indent(g_depth, value, width=0x70, indentString=indentString, n2plus=g_depth+4)
else:
_str = value
# if isinstance(g_output, list):
# g_output.append(_str)
# return
# try:
# if isinstance(g_output, Queue):
# g_output.put(_str)
# return
# except NameError:
# pass
print(_str)
# printi("[slowtrace-helpers loading]")
# stk = []
# for i in range(len(inspect.stack()) - 1, 0, -1):
# stk.append(inspect.stack()[i][3])
# printi((" -> ".join(stk)))
warn = 0
_file = os.path.abspath(__file__)
def refresh_slowtrace_helpers():
execfile(_file)
def delete_all_from(ea):
"""
Delete all segments, instructions, comments, i.e. everything
except values of bytes.
"""
# Brute-force nuke all info from all the heads
while ea != BADADDR and ea <= ida_ida.cvar.inf.max_ea:
ida_name.del_local_name(ea)
ida_name.del_global_name(ea)
func = clone_items(ida_funcs.get_func(ea))
if func:
ida_funcs.del_func_cmt(func, False)
ida_funcs.del_func_cmt(func, True)
ida_funcs.del_func(ea)
ida_bytes.del_hidden_range(ea)
PatchBytes(ea, [0] * 9)
# seg = ida_segment.getseg(ea)
# if seg:
# ida_segment.del_segment_cmt(seg, False)
# ida_segment.del_segment_cmt(seg, True)
# ida_segment.del_segm(ea, ida_segment.SEGMOD_KEEP | ida_segment.SEGMOD_SILENT)
ea = ida_bytes.idc.next_head(ea, ida_ida.cvar.inf.max_ea)
def RebuildFuncAndSubs(ea):
subs = RecurseCalled(ea)['calledLocs']
subs.reverse()
for addr in subs:
ZeroFunction(addr)
idc.auto_wait()
ZeroFunction(ea)
idc.auto_wait()
def RecurseCalled(ea=None, width=512, depth=5, data=0, makeChart=0, exe='dot', includeSubs=0, fixVtables=False):
def all_xrefs_from(funcea, iteratee=None):
if iteratee is None:
iteratee = lambda x: x
xrefs = []
for (startea, endea) in Chunks(funcea):
for head in Heads(startea, endea):
xrefs.extend([GetFuncStart(x.to) for x in idautils.XrefsFrom(head) \
if x.type in (ida_xref.fl_CF, ida_xref.fl_CN, ida_xref.fl_JF, ida_xref.fl_JN) \
and not ida_funcs.is_same_func(funcea, x.to) \
and IsFunc_(x.to)])
xrefs = list(set(xrefs))
return xrefs
def vtables_from(funcea, iteratee=None):
if iteratee is None:
iteratee = lambda x: x
xrefs = []
for (startea, endea) in Chunks(funcea):
for head in Heads(startea, endea):
xrefs.extend([x.to for x in idautils.XrefsFrom(head) \
if Name(x.to).startswith('??_7')])
xrefs = list(set(xrefs))
return xrefs
if ea is None:
ea = idc.get_screen_ea()
calledNames = list()
calledLocs = list()
visited = set([])
if isinstance(ea, list):
pending = set(ea)
initial = set([GetFuncStart(x) for x in ea])
else:
pending = set([ea])
initial = set([GetFuncStart(ea)])
count = 0
added = [1]
functionCalls = collections.defaultdict(set)
namedFunctionCalls = collections.defaultdict(set)
fwd = dict()
rev = dict()
vtables = set()
while pending and depth and len(pending) < width:
target = pending.pop()
count += 1
added[0] -= 1
if added[0] < 1:
depth -= 1
added.pop()
visited.add(target)
fnName = idc.get_func_name(target) or idc.get_name(target) or "0x%x" % ref
fnStart = GetFuncStart(target)
if fnStart < idc.BADADDR:
target = fnStart
visited.add(target)
if not fnStart in initial:
calledNames.append(fnName)
calledLocs.append(fnStart)
vtables |= set(vtables_from(fnStart))
refs = all_xrefs_from(fnStart)
refs = set(refs)
refs -= visited
size1 = len(pending)
pending |= refs
size2 = len(pending) - size1
added.append(size2)
return {'calledName': calledNames,
'calledLocs': calledLocs,
'vtables': list(vtables),
}
def func_rename_vtable_xref(ea=None, **kwargs):
"""
func_rename_vtable_xref
@param ea: linear address
"""
if isinstance(ea, list):
return [func_rename_vtable_xref(x) for x in ea]
ea = eax(ea)
o = RecurseCalled(ea, **kwargs)
vtables = _.filter(o['vtables'], lambda x, *a: 'HttpTask' in idc.get_name(x, ida_name.GN_DEMANGLED))
if len(vtables) == 1:
vtables = list(vtables)
name = re.findall(r'\w+HttpTask\w*', idc.get_name(vtables[0], ida_name.GN_DEMANGLED))
if len(name) == 1:
name = name[0]
printi("{:x} renaming to: {}".format(ea, name))
LabelAddressPlus(ea, 'uses_' + name)
else:
printi("{:x} couldn't find matching vtable name".format(ea))
else:
printi("{:x} vtables: {}".format(ea, len(vtables)))
def isListOf(o, t):
if isinstance(o, list):
if o:
if isinstance(o[0], t):
return True
return False
def RecurseCalledRange(r=None, width=512, data=0, makeChart=1, exe='dot', includeSubs=0, fixVtables=False):
from bisect import bisect_left, bisect_right, bisect
chart = []
def all_xrefs_from(startea, endea, iteratee=None, GetBlockStart=None):
if iteratee is None:
iteratee = lambda x: x
xrefs = []
for head in Heads(startea, endea):
xrefs.extend([x for x in [GetBlockStart(x.to) for x in idautils.XrefsFrom(head) \
if x.type in (
# ida_xref.fl_CF, ida_xref.fl_CN,
ida_xref.fl_JF, ida_xref.fl_JN
) \
] if x])
return list(set(xrefs))
if not isListOf(r, GenericRange):
raise ValueError('Not a list of GenericRage')
calledNames = list()
calledLocs = list()
visited = set([])
rdict = dict()
for e in r:
rdict[e.start] = (e.start, e.last)
rdict_keys = _.sort(list(rdict.keys()))
def GetBlockStart(start):
left = bisect_left(rdict_keys, start)
right = bisect_right(rdict_keys, start)
# dprint("[GetBlockStart] left, right")
result = rdict[rdict_keys[left]][0]
if start < result:
return None
printi("[GetBlockStart] start:{:x} left:{:x}, right:{:x}, result:{:x}".format(start, rdict[rdict_keys[left]][0], rdict[rdict_keys[left]][1], result))
return rdict[rdict_keys[left]][0]
depth = 0
count = 0
added = [1]
functionCalls = collections.defaultdict(set)
namedFunctionCalls = collections.defaultdict(set)
fwd = dict()
rev = dict()
assoc = defaultdict(list)
for start, end in rdict.values():
refs = all_xrefs_from(start, end, None, GetBlockStart)
for ea in refs:
# dprint("[adding] ea")
printi("[adding] ea: {}".format(ahex(ea)))
chart.append([start, ea])
assoc[start].append(ea)
assoc[ea].append(start)
used = set()
sets = []
addrs = []
addrs2 = []
for k in list(assoc.keys()):
# dprint("[debug] k")
printi("[debug] k:{}".format(ahex(k)))
addrs.clear()
addrs2.clear()
if k in assoc:
for ea in assoc[k]:
if ea not in used:
used.add(ea)
addrs.append(ea)
addrs2.append(ea)
assoc.pop(k)
while addrs:
k = addrs.pop()
if k in assoc:
for ea in assoc[k]:
if ea not in used:
used.add(ea)
addrs.append(ea)
addrs2.append(ea)
assoc.pop(k)
# dprint("[debug] addrs")
printi("[debug] addrs:{}".format(hex(addrs2)))
printi("[debug] used:{}".format(hex(list(used))))
sets.append(addrs)
# return chart
return sets
subs = []
call_list = []
for x in chart:
x[0] = hex(x[0])
x[1] = hex(x[1])
subs.append(x[0])
subs.append(x[1])
if len(x) > 2:
call_list.append('"{}" -> "{}" {};'.format(x[0], x[1], " ".join(x[2:])))
else:
call_list.append('"{}" -> "{}";'.format(x[0], x[1]))
call_list.sort()
call_list = _.uniq(call_list, True)
# colors = colorSubs(subs, colors, [fnName])
return call_list
dot = __DOT.replace('%%MEAT%%', '\n'.join(_.uniq(colors + call_list)))
r = dot_draw(dot, name="written", exe=exe)
printi("dot_draw r: {}".format(r))
if isinstance(_, tuple):
if not r[0]:
printi("dot_draw error: {}".format(r[1]))
else:
printi("dot_draw good: {}".format(r[1]))
def CheckChunks(funcea=None):
"""
CheckChunks - check all addresses in a function for shifty chunks
@param funcea: any address in the function
"""
funcea = eax(funcea)
func = ida_funcs.get_func(funcea)
if not func:
return 0
else:
funcea = func.start_ea
_chunk_starts = set([])
chunk_ends = set([])
_chunks = idautils.Chunks(funcea)
for (start_ea, end_ea) in _chunks:
_chunk_starts.add(start_ea)
chunk_ends.add(end_ea)
for _head in idautils.Heads(start_ea, end_ea):
_owners = GetChunkOwners(_head)
if len(_owners) != 1 or funcea not in _owners:
printi("[warn] function {:x}, chunk {:x}, owned by: {}".format(funcea, hex(_owners_)))
return False
if not _chunk_starts.isdisjoint(chunk_ends):
printi("[warn] function {:x} has adjoining chunks at {}".format(funcea, hex(list(_chunk_starts.intersection(chunk_ends)))))
return True
def GetAllChunks():
for cid in range(ida_funcs.get_fchunk_qty()):
func = ida_funcs.getn_fchunk(cid)
yield func.start_ea, func.end_ea
def NotHeads(start=None, end=None, predicate=None, advance=None):
"""
Get a list of heads^H^H^H^H^H anything
@param start: start address (default: inf.min_ea)
@param end: end address (default: inf.max_ea)
@param predicate: if returns True, then address added
@param advance: advance ea (default: ea += 1)
@return: list of heads between start and end
"""
if start is None: start = ida_ida.cvar.inf.min_ea
if end is None: end = ida_ida.cvar.inf.max_ea
if predicate is None or not callable(predicate):
return
if advance is None:
advance = lambda a, e: a + 1
ea = start
if not predicate(ea): # if not idc.is_head(ida_bytes.get_flags(ea)):
ea = advance(ea, end) # ea = ida_bytes.next_head(ea, end)
while ea < end and ea != ida_idaapi.BADADDR:
if predicate(ea): # if not idc.is_head(ida_bytes.get_flags(ea)):
yield ea
ea = advance(ea, end) # ea = ida_bytes.next_head(ea, end)
def TrimChunks(funcea=None):
"""
TrimChunks
@param funcea: any address in the function
"""
funcea = eax(funcea)
func = ida_funcs.get_func(funcea)
if not func:
return 0
else:
funcea = func.start_ea
chunks = idautils.Chunks(funcea)
for cstart, cend in chunks:
new_cend = None
cheads = list(idautils.Heads(cstart, cend))
cheads_len = len(cheads)
removed = 0
while cheads and (isNop(cheads[-1]) or GetMnemDi(cheads[-1]).startswith(('int',))):
removed += 1
new_cend = cheads[-1]
cheads.pop()
if new_cend:
if len(cheads) == 0:
idc.remove_fchunk(funcea, cstart)
elif IsFuncHead(cstart):
SetFuncEnd(cstart, new_cend)
else:
SetChunkEnd(cstart, new_cend)
return removed
def RemoveAllChunksAndFunctions(leave=None):
# printi("Stage #1")
# chunks = []
# for ea in range(0, ida_funcs.get_fchunk_qty()):
# chunk_ea = getn_fchunk(ea).start_ea
# chunks.append(chunk_ea)
# for ea in chunks:
# RemoveThisChunk(ea)
printi("Stage #2")
leave = A(leave)
for funcea in idautils.Functions():
# RemoveAllChunks(funcea)
ZeroFunction(funcea, 1)
def check_append_func_tail(func, ea1, ea2):
funcea = func.start_ea
fail = False
errors = []
if ea1 <= func.start_ea < ea2:
msg = "attempted to overlap funchead at {:x} ({})".format(funcea, GetFuncName(funcea))
errors.append(msg)
# if ida_funcs.get_func_chunknum(func, ea1) != -1:
# tail = ida_funcs.get_fchunk(ea1 - 1)
# printi("append_func_tail: appending instead of extending:\nappend_func_tail(0x{:x}, 0x{:x}, 0x{:x})\n[overlaps existing function chunk at 0x{:x}]".format(funcea))
# printi("executing instead: ida_funcs.set_func_end(0x{:x}, 0x{:x})".format(tail.start_ea, ea2))
# return ida_funcs.set_func_end(tail.start_ea, ea2)
all_owners = set()
for ea in range(ea1, ea2): # if len(list(idautils.Chunks(ea))) > 1 and func.start_ea in GetChunkOwners(ea) or \
if ida_funcs.get_func_chunknum(func, ea) != -1:
msg = "overlaps existing function chunk at 0x{:x}\u2013{:x}".format(GetChunkStart(ea), GetChunkEnd(ea))
errors.append(msg)
owners = GetChunkOwners(ea, includeOwner=1)
if owners:
all_owners.update(owners)
msg = "existing owners: {} ({})".format(hex(owners), GetFuncName(owners))
errors.append(msg)
func_owner = ida_funcs.get_func(ea)
if func_owner and ida_funcs.get_func_chunknum(func_owner, ea) != -1:
msg = "would overlap existing chunk #{}/{} of {} at {:x}\u2013{:x}".format(
GetChunkNumber(ea, eax(func_owner)), GetNumChunks(eax(func_owner)), GetFunctionName(eax(func_owner)), GetChunkStart(ea), GetChunkEnd(ea))
errors.append(msg)
if _.all(all_owners, lambda v, *a: v == funcea):
msg = "all owners are us, who cares"
return True
errors.append(msg)
for ea in range(ea1, ea2):
if ida_funcs.get_func_chunknum(func, ea) != -1:
ida_funcs.remove_fchunk(func, ea)
if errors:
# for error in set(errors):
# printi(error)
raise AppendChunkError(_.uniq(errors))
return True
def FindBadJumps():
for funcea in idautils.Functions():
for ea in GetFuncHeads(funcea):
if isAnyJmpOrCall(ea):
opType = idc.get_operand_type(ea, 0)
if opType in (idc.o_far, idc.o_near, idc.o_mem):
target = idc.get_operand_value(ea, 0)
if not IsValidEA(ea):
yield ea
def FindJmpChunks():
for funcea in idautils.Functions():
if GetNumChunks(funcea) > 1:
for cstart, cend in idautils.Chunks(funcea):
length = cend - cstart
if length == 2 or length == 5:
if isUnconditionalJmp(cstart):
yield cstart
def SkipJmpChunks():
patched = defaultglobal('_skipjmpchunks', set())
for refs in [xrefs_to(x) for x in FindJmpChunks()]:
count = 0
for ea in [x for x in refs if isUnconditionalJmp(x)]:
try:
jumps = SkipJumps(ea, apply=1, returnJumps=1, returnTargets=1)
count = max(count, len(jumps) - 1)
except AdvanceFailure as e:
if "couldn't create instruction" in e.args[0] or \
"couldn't get mnem from" in e.args[0] or \
"couldn't find valid insn" in e.args[0] or \
"couldn't find valid target" in e.args[0]:
printi("[SkipJmpChunks] {:x} AdvanceFailure triggering unpatch and retrace".format(ea))
funcea = GetFuncStart(ea)
idc.del_func(funcea)
UnpatchUn()
retrace(funcea)
else:
printi("[SkipJmpChunks] {:x} AdvanceFailure: {}".format(ea, e))
if count > 1:
patched.add(ea)
target = SkipJumps(ea)
for ea in [x for x in refs if isConditionalJmp(x)]:
insn_len = insn
if InsnLen(ea) == 6:
nassemble(ea, "{} 0x{:x}".format(IdaGetMnem(ea), target), apply=1)
else:
# assemble using internal ida assembler which will
# automatically create short jmps (nassemble is set to
# strict mode an will always create regular jmps unless
# otherwise specified)
targets = _.reverse(jumps[1:])
for tgt in targets:
assembled = nassemble(ea, "{} 0x{:x}".format(IdaGetMnem(ea), target))
# if len(assembled) <= InsnLen(ea):
# PatchBytes(ea, assembled, "SkipJmp")
# break
def FixAllChunks(leave=None):
printi("Stage #1")
for funcea in idautils.Functions():
for r in range(20):
if not FixChunks(funcea, leave=leave):
break
idc.auto_wait()
pass
printi("Stage #2")
chunks = []
for ea in range(0, ida_funcs.get_fchunk_qty()):
chunk_ea = ida_funcs.getn_fchunk(ea).start_ea
chunks.append(chunk_ea)
for ea in chunks:
FixChunk(ea)
def FixChunks(funcea=None, leave=None):
"""
call FixChunk for every chunk in a function
@param funcea: any address in the function
"""
funcea = eax(funcea)
func = ida_funcs.get_func(funcea)
if not func:
printi("[FixChunks] not a function: {}".format(hex(funcea)))
return 0
else:
funcea = func.start_ea
failed = fixed = 0
chunk_count_1 = func.tailqty
chunk_count_2 = len([x for x in idautils.Chunks(funcea)]) - 1
if chunk_count_1 != chunk_count_2:
printi("[FixChunks] tailqty != len(Chunk)")
if func and func.tailqty > 1:
all_chunks = [x for x in idautils.Chunks(funcea)] # if x[0] != funcea
if debug: printi("[FixChunks] total chunks: {}".format(len(all_chunks)))
for chunk_start, chunk_end in all_chunks:
_chunk_number = GetChunkNumber(chunk_start, funcea)
if debug: printi("[FixChunks] checking chunk #{} ({})".format(_chunk_number, GetChunkNumber(chunk_start)))
if _chunk_number > -1 and GetChunkNumber(chunk_start) == -1:
if debug: printi("[FixChunks] invalid chunk number #{} ({})".format(_chunk_number, GetChunkNumber(chunk_start)))
if len(GetChunkOwners(chunk_start)) == 0:
printi("[FixChunks] We have a really messed up ghost chunk at 0x{:x} belonging to 0x{:x} with no ChunkOwners".format(chunk_start, funcea))
ida_retrace(funcea)
printi("[FixChunks] Trying again after ida_retrace")
ida_retrace(funcea)
if len(GetChunkOwners(chunk_start)) == 0:
return False
printi("[FixChunks] We have a really messed up ghost chunk at 0x{:x} belonging to 0x{:x} with no ChunkOwners".format(chunk_start, funcea))
else:
return True
_tailqty = func.tailqty
_chunk_list = GetChunks(funcea)
_ida_chunks = idautils.Chunks(funcea)
if len(_chunk_list) > _chunk_number:
_cc = _chunk_list[_chunk_number]
if not GetChunkOwners(_cc['start']):
if idaapi.cvar.inf.version >= 700 and sys.version_info >= (3, 7):
printi("[FixChunk] No ChunkOwners: {:#x}".format(_cc['start']))
# printi("[FixChunk] GhostChunk: ZeroFunction {:#x}".format(funcea))
# ZeroFunction(func.start_ea)
for cs, ce in _ida_chunks:
if idc.remove_fchunk(funcea, cs):
printi("[FixChunk] GhostChunk: removed chunk {:#x}-{:#x} from {:#x}".format(cs, ce, funcea))
else:
printi("[FixChunk] GhostChunk: couldn't remove chunk {:#x}-{:#x} from {:#x}".format(cs, ce, funcea))
for cs, ce in _ida_chunks:
if not IsChunk(cs):
if idc.append_func_tail(funcea, cs, ce):
printi("[FixChunk] GhostChunk: re-appended chunk {:#x}-{:#x} to {:#x}".format(cs, ce, funcea))
else:
printi("[FixChunk] GhostChunk: couldn't append chunk {:#x}-{:#x} to {:#x}".format(cs, ce, funcea))
else:
printi("[FixChunk] GhostChunk: chunk {:#x}-{:#x} already added to {:#x}".format(cs, ce, funcea))
else:
printi("[FixChunks] Attempting dangerous thing #1: ida_funcs.append_func_tail({:x}, {:x}, {:x}".format(func.start_ea, _cc['start'], _cc['end']))
r = ida_funcs.append_func_tail(func, _cc['start'], _cc['end'])
printi("[FixChunks] Completed dangerous thing #1: {}".format(r))
if not r:
printi("[FixChunks] Attempting dangerous thing #1.1: ida_funcs.append_func_tail({:x}, {:x}, {:x}".format(func.start_ea, _cc['start'], _cc['start'] + IdaGetInsnLen(_cc['start'])))
r = ida_funcs.append_func_tail(func, _cc['start'], _cc['start'] + IdaGetInsnLen(_cc['start']))
printi("[FixChunks] Completed dangerous thing #1.1: {}".format(r))
if r:
idc.auto_wait()
if func.tailqty > _tailqty:
printi("[FixChunks] func {:x} grew from {} to {} tails".format(funcea, _tailqty, func.tailqty))
# dangerous to mess further with this function and it's chunks right now
return 1
else:
if GetChunkNumber(chunk_start) > -1:
printi("[FixChunks] func {:x} didn't grow a new tail, but it has a chunk number now".format(funcea))
return 1
else:
printi("[FixChunks] func {:x} didn't grow a new tail".format(funcea))
else:
printi("[FixChunks] #9")
return 0
if funcea not in GetChunkOwners(chunk_start):
printi("[FixChunks] We have a really messed up ghost chunk at 0x{:x} belonging to 0x{:x} with ChunkOwners: {}".format(chunk_start, funcea, GetChunkOwners(chunk_start)))
printi("[FixChunks] Func {:x} isn't owner of own chunk {:x}".format(funcea, chunk_start))
_old_owners = GetChunkOwners(chunk_start)
SetChunkOwner(chunk_start, funcea)
for _owner in _old_owners:
if not idc.remove_fchunk(_owner, chunk_start):
# make triple sure of this, as we will crash ida 7.5 if we're wrong
if _owner not in GetChunkOwners(chunk_start):
printi("[FixChunks] Attempting dangerous thing #2: ida_funcs.append_func_tail({:x}, {:x}, {:x}".format(GetFunc(_owner), chunk_start, GetChunkEnd(chunk_start)))
r = ida_funcs.append_func_tail(GetFunc(_owner), chunk_start, GetChunkEnd(chunk_start))
printi("[FixChunks] Completed dangerous thing #2: {}".format(r))
printi("[FixChunks] Attempting dangerous thing #3: idc.set_tail_owner({:x}, {:x})".format(chunk_start, _owner))
r = idc.set_tail_owner(chunk_start, _owner)
printi("[FixChunks] Completed dangerous thing #3: {}".format(r))
printi("[FixChunks] Attempting dangerous thing #4: idc.remove_fchunk({:x}, {:x})".format(_owner, chunk_start))
r = idc.remove_fchunk(_owner, chunk_start)
printi("[FixChunks] Completed dangerous thing #4: {}".format(r))
if r:
printi("[FixChunks] Managed to fix really fucked up ghost chunk")
continue
else:
pass
# if debug: printi("[FixChunks] valid chunk number #{} ({})".format(_chunk_number, GetChunkNumber(chunk_start)))
r = FixChunk(chunk_start, leave=funcea, owner=funcea, chunk_end=chunk_end)
# if r == False:
# return r
if isinstance(r, integer_types):
fixed += r
elif r == False:
failed += 1
else:
if debug: printi("[FixChunks] not a func, or no tails: {}".format(hex(funcea)))
pass
return fixed
def FixChunk(ea=None, leave=None, owner=None, chunk_end=None):
"""
Attempt to fix broken-assed chunked, as in example below:
; START OF FUNCTION CHUNK FOR loc_140CB4F38
; ADDITIONAL PARENT FUNCTION implsub_140CB4F38
loc_144429DE8: ; CODE XREF: implsub_140CB4F38
or eax, 0FFFFFFFFh
jmp loc_140CB4D74
; END OF FUNCTION CHUNK FOR loc_140CB4F38
@param ea: linear address
"""
ea = eax(ea)
if IsFuncHead(ea):
return 0
# Really invalid chunks may show up as being owned by chunkOwnersFuncNames non-function,
# often loc_14xxxxxx. So we can use this quick cheat to sort them.
if GetChunkNumber(ea) == 0:
return 0
if six.PY2:
_append_func_tail = lambda x, *a: ida_funcs.append_func_tail(GetFunc(x), *a)
else:
_append_func_tail = my_append_func_tail
chunkOwners = GetChunkOwners(ea)
_chunkOwners = GetChunkOwners(ea)
_chunkOwner = GetChunkOwner(ea) # this can turn up a different result
_chunkNumber = GetChunkNumber(ea)
if _chunkOwner == idc.BADADDR:
_chunkOwner = None
if _chunkOwner and _chunkOwner not in chunkOwners:
printi("[FixChunk] chunk:{:x} chunkOwner not in chunkOwners".format(ea))
chunkOwners.append(_chunkOwner)
_realOwner = PickChunkOwner(ea)
chunkOwners = [x for x in chunkOwners if IsValidEA(x)]
# # dprint("[FixChunk] _chunkNumber, chunkOwners, _chunkOwner, set.intersection(set(chunkOwners), [_chunkOwner])")
# print("[FixChunk] _chunkNumber:{}, _chunkOwners:{}, _chunkOwner:{}, set.intersection(set(_chunkOwners), [_chunkOwner]):{}".format(_chunkNumber, _chunkOwners, _chunkOwner, set.intersection(set(_chunkOwners), [_chunkOwner])))
if _chunkNumber == -1 and _chunkOwners and _chunkOwner and not set.intersection(set(_chunkOwners), [_chunkOwner]):
printi("[FixChunk] chunk at {:x} has conflicting and mutually exclusive owner and owners {}, {}".format(ea, hex(_chunkOwners), hex(_chunkOwner)))
# .text:0000000140A90F99 loc_140A90F99: ; CODE XREF: PED::_0x862C576661F1AAEF_ACTUAL_0_0-29DCF35↓j
# .text:0000000140A90F99 038 nop
# .text:0000000140A90F9A 038 nop ; jmp via push rbp and xchg
# .text:0000000140A90F9B 038 jmp loc_14336845F
#
# .text:0000000143C719B5 ; START OF FUNCTION CHUNK FOR NotRealSub [0x140a90fa0] (GetChunkOwner)
# .text:0000000143C719B5 ; ADDITIONAL PARENT FUNCTION CheckLoadedModules_inner [0x14345725b] (GetChunkOwners)
# .text:0000000143C719B5
# .text:0000000143C719B5 cs:
chunkOwners = GetChunkOwners(ea)
chunkParent = chunkOwners[0]
chunkAdditionalParent = _chunkOwner
chunkStart = GetChunkStart(ea)
chunkEnd = GetChunkEnd(ea)
# FixChunk(chunkStart)
idc.del_func(chunkParent)
# FixChunk(chunkStart)
idc.del_func(chunkAdditionalParent)
# FixChunk(chunkStart)
append_func_tail(chunkParent, chunkStart, chunkEnd)
remove_func_tail(GetFunc(chunkParent), chunkStart)
RemoveAllChunkOwners(chunkStart)
RemoveChunk(chunkStart)
ForceFunction(chunkAdditionalParent)
ForceFunction(chunkParent)
ida_funcs.append_func_tail(GetFunc(chunkParent), chunkStart, chunkEnd)
return idc.remove_fchunk(chunkParent, chunkStart)
# Python>chunkStart=chunkStart
# chunkStart
# Python>ce=chunkEnd
# chunkEnd
# Python>FixChunk(chunkStart)
# [FixChunk] chunk:143c719b5 chunkOwner not in chunkOwners
# [FixChunk] chunk at 143c719b5 is orphaned from ['chunkAdditionalParent', 'chunkParent']
# [FixChunk] chunk:143c719b5 invalid_owners:['chunkParent'], valid_owners:['CheckLoadedModules_inner']
# [FixChunk] Making function at 140a90fa0
# [FixChunk] Recovery mode #1 for owner 140a90fa0
# _append_func_tail(chunkParent, chunkStart, chunkEnd)
# <module> -> FixChunk
# append_func_tail(chunkParent, chunkStart, chunkEnd):
# existing owners: ['chunkAdditionalParent'] (['CheckLoadedModules_inner'])
# [FixChunk] Removing invalid_owners function at 140a90fa0
# 0x1
# Python>GetChunkOwners(), GetChunkOwner(), GetChunkNumber()
# ([chunkAdditionalParent], chunkParent, -0x1)
# Python>idc.del_func(chunkAdditionalParent)
# True
# Python>FixChunk(chunkStart)
# [GetChunkOwners] stated owner 14345725b of chunk 143c719b5 is not a function
# [FixChunk] chunk:143c719b5 chunkOwner not in chunkOwners
# [GetChunkOwners] stated owner 14345725b of chunk 143c719b5 is not a function
# [FixChunk] chunk at 143c719b5 is orphaned from ['chunkAdditionalParent', 'chunkParent']
# [FixChunk] chunk:143c719b5 invalid_owners:['chunkAdditionalParent', 'chunkParent'], valid_owners:[]
# [FixChunk] Making function at 14345725b
# [FixChunk] Recovery mode #1 for owner 14345725b
# _append_func_tail(chunkAdditionalParent, chunkStart, chunkEnd)
# <module> -> FixChunk
# append_func_tail(chunkAdditionalParent, chunkStart, chunkEnd):
# existing owners: ['chunkAdditionalParent'] (['CheckLoadedModules_inner'])
# [FixChunk] Making function at 140a90fa0
# [FixChunk] Recovery mode #1 for owner 140a90fa0
# _append_func_tail(chunkParent, chunkStart, chunkEnd)
# <module> -> FixChunk
# append_func_tail(chunkParent, chunkStart, chunkEnd):
# existing owners: ['chunkAdditionalParent'] (['CheckLoadedModules_inner'])
# [FixChunk] Removing invalid_owners function at 14345725b
# [FixChunk] Removing invalid_owners function at 140a90fa0
# [GetChunkOwners] stated owner 14345725b of chunk 143c719b5 is not a function
# 0x1
# Python>GetChunkOwners(), GetChunkOwner(), GetChunkNumber()
# [GetChunkOwners] stated owner 14345725b of chunk 143c719b5 is not a function
# ([chunkAdditionalParent], chunkParent, -0x1)
# Python>append_func_tail(chunkParent, chunkStart, chunkEnd)
# 0x0
# Python>remove_func_tail(GetFunc(chunkParent), chunkStart)
# False
# Python>RemoveAllChunkOwners(chunkStart)
# [GetChunkOwners] stated owner 14345725b of chunk 143c719b5 is not a function
# [RemoveAllChunkOwners] couldn t remove chunk 143c719b5
# Python>GetChunkOwners(), GetChunkOwner(), GetChunkNumber()
# [GetChunkOwners] stated owner 14345725b of chunk 143c719b5 is not a function
# ([chunkAdditionalParent], chunkParent, -0x1)
# Python>RemoveChunk(chunkStart)
# couldn t find function for chunk at 143c719b5
# Python>GetFuncName(chunkAdditionalParent)
# ''
# Python>GetFuncName(chunkParent)
# ''
# Python>retrace(previousFunctionToChunk, adjustStack=1)
# [retrace] funcea:5432088992
# [retrace] address:5432088992
# slowtrace2 ea=previousFunctionToChunk sub_143C719A0 {}
# 143c719a0 repl: Search: 48 8d 64 24 f8 48 89 1c 24
# Replace: ('push rbx',)
# Comment: lea rsp, qword ptr [rsp-8]; mov [rsp], rbx
# 143bc9313 replFunc: Search: 55 48 bd -1 -1 -1 -1 -1 -1 00 00 48 87 2c 24 -1 -1 48 8b -1 24 10 48 -1 -1 -1 -1 -1 -1 -1 00 00 48 0f -1 -1 48 89 -1 24 10 -1 -1 c3
# Replace: ['jg 0x140cfbfee', 'jmp 0x140a5c9b9', 'int3']
# Comment: mini-cmov
# 143bc9313 made patches (reversing head) to 143c719a0
# previousFunctionToChunk: 0x143bc9313: 8 130 fixing unexpected stack change from: nop | jmp loc_143BA85C0
# 140a5c9b9 repl: Search: 48 8d 64 24 f8 4c 89 04 24
# Replace: ('push r8',)
# Comment: lea rsp, qword ptr [rsp-8]; mov [rsp], r8
# 1434d992d repl: Search: 48 8d 64 24 f8 48 89 14 24
# Replace: ('push rdx',)
# Comment: lea rsp, qword ptr [rsp-8]; mov [rsp], rdx