angr 9.2.164__cp310-abi3-macosx_11_0_arm64.whl → 9.2.166__cp310-abi3-macosx_11_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of angr might be problematic. Click here for more details.
- angr/__init__.py +1 -1
- angr/analyses/analysis.py +27 -4
- angr/analyses/cfg/cfg_fast.py +16 -1
- angr/analyses/decompiler/condition_processor.py +9 -8
- angr/analyses/decompiler/peephole_optimizations/__init__.py +2 -0
- angr/analyses/decompiler/peephole_optimizations/inlined_memcpy.py +78 -0
- angr/analyses/decompiler/peephole_optimizations/inlined_strcpy.py +67 -10
- angr/analyses/decompiler/peephole_optimizations/inlined_strcpy_consolidation.py +10 -13
- angr/analyses/deobfuscator/string_obf_finder.py +130 -32
- angr/engines/icicle.py +10 -2
- angr/rustylib.abi3.so +0 -0
- angr/state_plugins/history.py +16 -0
- angr/unicornlib.dylib +0 -0
- {angr-9.2.164.dist-info → angr-9.2.166.dist-info}/METADATA +5 -5
- {angr-9.2.164.dist-info → angr-9.2.166.dist-info}/RECORD +1409 -1408
- {angr-9.2.164.dist-info → angr-9.2.166.dist-info}/WHEEL +1 -0
- {angr-9.2.164.dist-info → angr-9.2.166.dist-info}/entry_points.txt +0 -0
- {angr-9.2.164.dist-info → angr-9.2.166.dist-info}/licenses/LICENSE +0 -0
- {angr-9.2.164.dist-info → angr-9.2.166.dist-info}/top_level.txt +0 -0
|
@@ -27,6 +27,42 @@ STEP_LIMIT_FIND = 500
|
|
|
27
27
|
STEP_LIMIT_ANALYSIS = 5000
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
ALL_X64_XMM_REGS = {
|
|
31
|
+
capstone.x86.X86_REG_XMM0,
|
|
32
|
+
capstone.x86.X86_REG_XMM1,
|
|
33
|
+
capstone.x86.X86_REG_XMM2,
|
|
34
|
+
capstone.x86.X86_REG_XMM3,
|
|
35
|
+
capstone.x86.X86_REG_XMM4,
|
|
36
|
+
capstone.x86.X86_REG_XMM5,
|
|
37
|
+
capstone.x86.X86_REG_XMM6,
|
|
38
|
+
capstone.x86.X86_REG_XMM7,
|
|
39
|
+
capstone.x86.X86_REG_XMM8,
|
|
40
|
+
capstone.x86.X86_REG_XMM9,
|
|
41
|
+
capstone.x86.X86_REG_XMM10,
|
|
42
|
+
capstone.x86.X86_REG_XMM11,
|
|
43
|
+
capstone.x86.X86_REG_XMM12,
|
|
44
|
+
capstone.x86.X86_REG_XMM13,
|
|
45
|
+
capstone.x86.X86_REG_XMM14,
|
|
46
|
+
capstone.x86.X86_REG_XMM15,
|
|
47
|
+
capstone.x86.X86_REG_XMM16,
|
|
48
|
+
capstone.x86.X86_REG_XMM17,
|
|
49
|
+
capstone.x86.X86_REG_XMM18,
|
|
50
|
+
capstone.x86.X86_REG_XMM19,
|
|
51
|
+
capstone.x86.X86_REG_XMM20,
|
|
52
|
+
capstone.x86.X86_REG_XMM21,
|
|
53
|
+
capstone.x86.X86_REG_XMM22,
|
|
54
|
+
capstone.x86.X86_REG_XMM23,
|
|
55
|
+
capstone.x86.X86_REG_XMM24,
|
|
56
|
+
capstone.x86.X86_REG_XMM25,
|
|
57
|
+
capstone.x86.X86_REG_XMM26,
|
|
58
|
+
capstone.x86.X86_REG_XMM27,
|
|
59
|
+
capstone.x86.X86_REG_XMM28,
|
|
60
|
+
capstone.x86.X86_REG_XMM29,
|
|
61
|
+
capstone.x86.X86_REG_XMM30,
|
|
62
|
+
capstone.x86.X86_REG_XMM31,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
30
66
|
class StringDeobFuncDescriptor:
|
|
31
67
|
"""
|
|
32
68
|
Describes a string deobfuscation function.
|
|
@@ -478,10 +514,12 @@ class StringObfuscationFinder(Analysis):
|
|
|
478
514
|
actual_addrs = action.actual_addrs
|
|
479
515
|
if action.type == "mem":
|
|
480
516
|
if action.action == "read":
|
|
517
|
+
assert action.size is not None
|
|
481
518
|
for a in actual_addrs:
|
|
482
519
|
for size in range(action.size.ast // 8):
|
|
483
520
|
all_global_reads.append(a + size)
|
|
484
521
|
elif action.action == "write":
|
|
522
|
+
assert action.size is not None
|
|
485
523
|
for a in actual_addrs:
|
|
486
524
|
for size in range(action.size.ast // 8):
|
|
487
525
|
all_global_writes.append(a + size)
|
|
@@ -598,16 +636,16 @@ class StringObfuscationFinder(Analysis):
|
|
|
598
636
|
type3_functions = []
|
|
599
637
|
|
|
600
638
|
for func in function_candidates:
|
|
601
|
-
if not
|
|
639
|
+
if not 1 <= len(func.block_addrs_set) < 14:
|
|
602
640
|
continue
|
|
603
641
|
|
|
604
642
|
# if it has a prototype recovered, it must have four arguments
|
|
605
|
-
if func.prototype is not None and len(func.prototype.args)
|
|
643
|
+
if func.prototype is not None and len(func.prototype.args) not in {3, 4}:
|
|
606
644
|
continue
|
|
607
645
|
|
|
608
646
|
# the function must call some other functions
|
|
609
|
-
if callgraph_digraph.out_degree[func.addr] == 0:
|
|
610
|
-
|
|
647
|
+
# if callgraph_digraph.out_degree[func.addr] == 0:
|
|
648
|
+
# continue
|
|
611
649
|
|
|
612
650
|
# take a look at its call sites
|
|
613
651
|
func_node = cfg.get_any_node(func.addr)
|
|
@@ -635,30 +673,34 @@ class StringObfuscationFinder(Analysis):
|
|
|
635
673
|
continue
|
|
636
674
|
if dec.codegen is None or not dec.codegen.text:
|
|
637
675
|
continue
|
|
676
|
+
|
|
638
677
|
if not self._like_type3_deobfuscation_function(dec.codegen.text):
|
|
639
678
|
continue
|
|
640
679
|
|
|
641
680
|
# examine the first 100 call sites and see if any of them returns a valid string
|
|
642
681
|
valid = False
|
|
682
|
+
guessed_size = False
|
|
643
683
|
for i in range(min(100, len(call_sites))):
|
|
644
684
|
call_site_block = self.project.factory.block(call_sites[i].addr)
|
|
645
685
|
if not self._is_block_setting_constants_to_stack(call_site_block):
|
|
646
686
|
continue
|
|
647
687
|
|
|
648
688
|
# simulate an execution to see if it really works
|
|
649
|
-
data = self._type3_prepare_and_execute(
|
|
650
|
-
func.addr, call_sites[i].addr, call_sites[i].function_address, cfg
|
|
689
|
+
data, guessed_size = self._type3_prepare_and_execute(
|
|
690
|
+
func.addr, call_sites[i].addr, call_sites[i].function_address, cfg # type:ignore
|
|
651
691
|
)
|
|
652
692
|
if data is None:
|
|
653
693
|
continue
|
|
654
|
-
if len(data) > 3
|
|
655
|
-
|
|
656
|
-
|
|
694
|
+
if len(data) > 3:
|
|
695
|
+
consecutive_printable_strs = self._consecutive_printable_substrings(data, min_length=4)
|
|
696
|
+
if consecutive_printable_strs:
|
|
697
|
+
valid = True
|
|
698
|
+
break
|
|
657
699
|
|
|
658
700
|
if valid:
|
|
659
701
|
desc = StringDeobFuncDescriptor()
|
|
660
702
|
desc.string_output_arg_idx = 0
|
|
661
|
-
desc.string_length_arg_idx = 1
|
|
703
|
+
desc.string_length_arg_idx = 1 if not guessed_size else None
|
|
662
704
|
desc.string_null_terminating = False
|
|
663
705
|
type3_functions.append((func.addr, desc))
|
|
664
706
|
|
|
@@ -687,11 +729,15 @@ class StringObfuscationFinder(Analysis):
|
|
|
687
729
|
if cfg is None:
|
|
688
730
|
raise AngrAnalysisError("StringObfuscationFinder needs a CFG for the analysis")
|
|
689
731
|
|
|
690
|
-
|
|
732
|
+
cfg_node = cfg.get_any_node(func_addr)
|
|
733
|
+
if cfg_node is None:
|
|
734
|
+
raise AngrAnalysisError(f"Cannot find the CFG node for function {func_addr:#x}")
|
|
735
|
+
call_sites = cfg.get_predecessors(cfg_node)
|
|
691
736
|
callinsn2content = {}
|
|
692
737
|
for idx, call_site in enumerate(call_sites):
|
|
693
738
|
_l.debug("Analyzing type 3 candidate call site %#x (%d/%d)...", call_site.addr, idx + 1, len(call_sites))
|
|
694
|
-
|
|
739
|
+
assert call_site.function_address is not None
|
|
740
|
+
data, _ = self._type3_prepare_and_execute(func_addr, call_site.addr, call_site.function_address, cfg)
|
|
695
741
|
if data:
|
|
696
742
|
callinsn2content[call_site.instruction_addrs[-1]] = data
|
|
697
743
|
# print(hex(call_site.addr), data)
|
|
@@ -722,12 +768,14 @@ class StringObfuscationFinder(Analysis):
|
|
|
722
768
|
|
|
723
769
|
@staticmethod
|
|
724
770
|
def _like_type3_deobfuscation_function(code: str) -> bool:
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
)
|
|
729
|
-
|
|
730
|
-
def _type3_prepare_and_execute(
|
|
771
|
+
has_bitwise_ops = "^" in code or ">>" in code or "<<" in code or "~" in code
|
|
772
|
+
has_loops = "do" in code or "while" in code or "for" in code
|
|
773
|
+
has_many_bitwise_ops = code.count("^") + code.count(">>") + code.count("<<") + code.count("~") > 5
|
|
774
|
+
return has_bitwise_ops and (has_loops or has_many_bitwise_ops)
|
|
775
|
+
|
|
776
|
+
def _type3_prepare_and_execute(
|
|
777
|
+
self, func_addr: int, call_site_addr: int, call_site_func_addr: int, cfg
|
|
778
|
+
) -> tuple[bytes | None, bool]:
|
|
731
779
|
blocks_at_callsite = [call_site_addr]
|
|
732
780
|
|
|
733
781
|
# backtrack from call site to include all previous consecutive blocks
|
|
@@ -773,6 +821,7 @@ class StringObfuscationFinder(Analysis):
|
|
|
773
821
|
# setup sp and bp, just in case
|
|
774
822
|
state.regs._sp = 0x7FFF0000
|
|
775
823
|
bp_set = False
|
|
824
|
+
assert prop.model.input_states is not None
|
|
776
825
|
prop_state = prop.model.input_states.get(call_site_addr, None)
|
|
777
826
|
if prop_state is not None:
|
|
778
827
|
for reg_offset, reg_width in reg_reads:
|
|
@@ -798,7 +847,7 @@ class StringObfuscationFinder(Analysis):
|
|
|
798
847
|
else:
|
|
799
848
|
simgr.step()
|
|
800
849
|
if not simgr.active:
|
|
801
|
-
return None
|
|
850
|
+
return None, False
|
|
802
851
|
|
|
803
852
|
in_state = simgr.active[0]
|
|
804
853
|
|
|
@@ -821,33 +870,63 @@ class StringObfuscationFinder(Analysis):
|
|
|
821
870
|
try:
|
|
822
871
|
ret_value = callable_0()
|
|
823
872
|
except (AngrCallableMultistateError, AngrCallableError):
|
|
824
|
-
return None
|
|
873
|
+
return None, False
|
|
825
874
|
|
|
826
875
|
out_state = callable_0.result_state
|
|
827
876
|
|
|
828
877
|
# figure out what was written
|
|
878
|
+
assert out_state is not None
|
|
829
879
|
ptr = out_state.memory.load(ret_value, size=self.project.arch.bytes, endness=self.project.arch.memory_endness)
|
|
880
|
+
if out_state.memory.load(ptr, size=4).concrete_value == 0:
|
|
881
|
+
# fall back to using the return value as the pointer
|
|
882
|
+
ptr = ret_value
|
|
883
|
+
if out_state.memory.load(ptr, size=4).concrete_value == 0:
|
|
884
|
+
# can't find a valid pointer
|
|
885
|
+
return None, False
|
|
886
|
+
|
|
830
887
|
size = out_state.memory.load(ret_value + 8, size=4, endness=self.project.arch.memory_endness)
|
|
888
|
+
guessed_size = False
|
|
889
|
+
if size.symbolic or size.concrete_value == 0 or size.concrete_value >= 1024:
|
|
890
|
+
size = 64
|
|
891
|
+
guessed_size = True
|
|
831
892
|
# TODO: Support lists with varied-length elements
|
|
832
893
|
data = out_state.memory.load(ptr, size=size, endness="Iend_BE")
|
|
833
894
|
if data.symbolic:
|
|
834
|
-
return None
|
|
895
|
+
return None, False
|
|
835
896
|
|
|
836
|
-
return out_state.solver.eval(data, cast_to=bytes)
|
|
897
|
+
return out_state.solver.eval(data, cast_to=bytes), guessed_size
|
|
837
898
|
|
|
838
899
|
@staticmethod
|
|
839
900
|
def _is_block_setting_constants_to_stack(block, threshold: int = 5) -> bool:
|
|
840
|
-
|
|
901
|
+
insn_setting_const_bytes = 0
|
|
902
|
+
xmm_has_const = False
|
|
841
903
|
for insn in block.capstone.insns:
|
|
842
|
-
if (
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
904
|
+
if insn.mnemonic.startswith("mov") and len(insn.operands) == 2:
|
|
905
|
+
if (
|
|
906
|
+
insn.operands[0].type == capstone.x86.X86_OP_MEM
|
|
907
|
+
and insn.operands[0].mem.base in {capstone.x86.X86_REG_RSP, capstone.x86.X86_REG_RBP}
|
|
908
|
+
and insn.operands[1].type == capstone.x86.X86_OP_IMM
|
|
909
|
+
):
|
|
910
|
+
# mov [rsp|rbp + offset], imm
|
|
911
|
+
insn_setting_const_bytes += 1 # FIXME: How to get the size of the mov in capstone?
|
|
912
|
+
if (
|
|
913
|
+
insn.operands[0].type == capstone.x86.X86_OP_REG
|
|
914
|
+
and insn.operands[0].reg in ALL_X64_XMM_REGS
|
|
915
|
+
and insn.operands[1].type == capstone.x86.X86_OP_MEM
|
|
916
|
+
and insn.operands[1].mem.base == capstone.x86.X86_REG_RIP
|
|
917
|
+
):
|
|
918
|
+
xmm_has_const = True
|
|
919
|
+
if (
|
|
920
|
+
xmm_has_const
|
|
921
|
+
and insn.operands[0].type == capstone.x86.X86_OP_MEM
|
|
922
|
+
and insn.operands[0].mem.base in {capstone.x86.X86_REG_RSP, capstone.x86.X86_REG_RBP}
|
|
923
|
+
and insn.operands[1].type == capstone.x86.X86_OP_REG
|
|
924
|
+
and insn.operands[1].reg in ALL_X64_XMM_REGS
|
|
925
|
+
):
|
|
926
|
+
# mov [rsp|rbp + offset], xmm0 - 31
|
|
927
|
+
insn_setting_const_bytes += 16
|
|
928
|
+
|
|
929
|
+
return insn_setting_const_bytes >= threshold
|
|
851
930
|
|
|
852
931
|
@staticmethod
|
|
853
932
|
def _is_string_reasonable(s: bytes) -> bool:
|
|
@@ -857,5 +936,24 @@ class StringObfuscationFinder(Analysis):
|
|
|
857
936
|
s = s.replace(b"\x00", b"")
|
|
858
937
|
return all(chr(ch) in string.printable for ch in s)
|
|
859
938
|
|
|
939
|
+
@staticmethod
|
|
940
|
+
def _consecutive_printable_substrings(s: bytes, min_length: int = 3) -> list[bytes]:
|
|
941
|
+
"""
|
|
942
|
+
Find all consecutive printable substrings in a string.
|
|
943
|
+
"""
|
|
944
|
+
substrings = []
|
|
945
|
+
current_substring = b""
|
|
946
|
+
for ch in s:
|
|
947
|
+
if chr(ch) in string.printable:
|
|
948
|
+
current_substring += bytes([ch])
|
|
949
|
+
else:
|
|
950
|
+
if current_substring:
|
|
951
|
+
if len(current_substring) >= min_length:
|
|
952
|
+
substrings.append(current_substring)
|
|
953
|
+
current_substring = b""
|
|
954
|
+
if current_substring:
|
|
955
|
+
substrings.append(current_substring)
|
|
956
|
+
return substrings
|
|
957
|
+
|
|
860
958
|
|
|
861
959
|
AnalysesHub.register_default("StringObfuscationFinder", StringObfuscationFinder)
|
angr/engines/icicle.py
CHANGED
|
@@ -123,7 +123,7 @@ class IcicleEngine(ConcreteEngine):
|
|
|
123
123
|
if proj is None:
|
|
124
124
|
raise ValueError("IcicleEngine requires a project to be set")
|
|
125
125
|
|
|
126
|
-
emu = Icicle(icicle_arch, PROCESSORS_DIR, True)
|
|
126
|
+
emu = Icicle(icicle_arch, PROCESSORS_DIR, True, True)
|
|
127
127
|
|
|
128
128
|
copied_registers = set()
|
|
129
129
|
|
|
@@ -174,6 +174,11 @@ class IcicleEngine(ConcreteEngine):
|
|
|
174
174
|
initial_cpu_icount=emu.cpu_icount,
|
|
175
175
|
)
|
|
176
176
|
|
|
177
|
+
# 3. Copy edge hitmap
|
|
178
|
+
edge_hitmap = state.history.last_edge_hitmap
|
|
179
|
+
if edge_hitmap is not None:
|
|
180
|
+
emu.edge_hitmap = edge_hitmap
|
|
181
|
+
|
|
177
182
|
return (emu, translation_data)
|
|
178
183
|
|
|
179
184
|
@staticmethod
|
|
@@ -221,9 +226,12 @@ class IcicleEngine(ConcreteEngine):
|
|
|
221
226
|
# Skip the last block, because it will be added by Successors
|
|
222
227
|
state.history.recent_bbl_addrs.extend([b[0] for b in emu.recent_blocks][:-1])
|
|
223
228
|
|
|
224
|
-
#
|
|
229
|
+
# 3.3. Set history.recent_instruction_count
|
|
225
230
|
state.history.recent_instruction_count = emu.cpu_icount - translation_data.initial_cpu_icount
|
|
226
231
|
|
|
232
|
+
# 3.4. Set edge hitmap
|
|
233
|
+
state.history.edge_hitmap = emu.edge_hitmap
|
|
234
|
+
|
|
227
235
|
return state
|
|
228
236
|
|
|
229
237
|
@override
|
angr/rustylib.abi3.so
CHANGED
|
Binary file
|
angr/state_plugins/history.py
CHANGED
|
@@ -59,6 +59,9 @@ class SimStateHistory(SimStatePlugin):
|
|
|
59
59
|
self.recent_syscall_count = 0 if clone is None else clone.recent_syscall_count
|
|
60
60
|
self.recent_instruction_count = -1 if clone is None else clone.recent_instruction_count
|
|
61
61
|
|
|
62
|
+
# afl-style hitmap
|
|
63
|
+
self.edge_hitmap: bytes | None = None if clone is None else clone.edge_hitmap
|
|
64
|
+
|
|
62
65
|
# satness stuff
|
|
63
66
|
self._all_constraints = ()
|
|
64
67
|
self._satisfiable = None
|
|
@@ -402,6 +405,19 @@ class SimStateHistory(SimStatePlugin):
|
|
|
402
405
|
def stack_actions(self):
|
|
403
406
|
return LambdaIterIter(self, operator.attrgetter("recent_stack_actions"))
|
|
404
407
|
|
|
408
|
+
@property
|
|
409
|
+
def last_edge_hitmap(self) -> bytes | None:
|
|
410
|
+
"""
|
|
411
|
+
Returns the last edge hitmap in the history chain, or None if there is no edge hitmap.
|
|
412
|
+
"""
|
|
413
|
+
history = self
|
|
414
|
+
while history is not None:
|
|
415
|
+
if history.edge_hitmap is not None:
|
|
416
|
+
return history.edge_hitmap
|
|
417
|
+
# Traverse to the previous state in the history chain
|
|
418
|
+
history = history.parent
|
|
419
|
+
return None
|
|
420
|
+
|
|
405
421
|
#
|
|
406
422
|
# Merging support
|
|
407
423
|
#
|
angr/unicornlib.dylib
CHANGED
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: angr
|
|
3
|
-
Version: 9.2.
|
|
3
|
+
Version: 9.2.166
|
|
4
4
|
Summary: A multi-architecture binary analysis toolkit, with the ability to perform dynamic symbolic execution and various static analyses on binaries
|
|
5
5
|
License: BSD-2-Clause
|
|
6
6
|
Project-URL: Homepage, https://angr.io/
|
|
@@ -16,12 +16,12 @@ Description-Content-Type: text/markdown
|
|
|
16
16
|
License-File: LICENSE
|
|
17
17
|
Requires-Dist: cxxheaderparser
|
|
18
18
|
Requires-Dist: GitPython
|
|
19
|
-
Requires-Dist: archinfo==9.2.
|
|
19
|
+
Requires-Dist: archinfo==9.2.166
|
|
20
20
|
Requires-Dist: cachetools
|
|
21
21
|
Requires-Dist: capstone==5.0.3
|
|
22
22
|
Requires-Dist: cffi>=1.14.0
|
|
23
|
-
Requires-Dist: claripy==9.2.
|
|
24
|
-
Requires-Dist: cle==9.2.
|
|
23
|
+
Requires-Dist: claripy==9.2.166
|
|
24
|
+
Requires-Dist: cle==9.2.166
|
|
25
25
|
Requires-Dist: mulpyplexer
|
|
26
26
|
Requires-Dist: networkx!=2.8.1,>=2.0
|
|
27
27
|
Requires-Dist: protobuf>=5.28.2
|
|
@@ -30,7 +30,7 @@ Requires-Dist: pycparser>=2.18
|
|
|
30
30
|
Requires-Dist: pydemumble
|
|
31
31
|
Requires-Dist: pyformlang
|
|
32
32
|
Requires-Dist: pypcode<4.0,>=3.2.1
|
|
33
|
-
Requires-Dist: pyvex==9.2.
|
|
33
|
+
Requires-Dist: pyvex==9.2.166
|
|
34
34
|
Requires-Dist: rich>=13.1.0
|
|
35
35
|
Requires-Dist: sortedcontainers
|
|
36
36
|
Requires-Dist: sympy
|