smallworld-re 1.0.0__py3-none-any.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.
Files changed (166) hide show
  1. smallworld/__init__.py +35 -0
  2. smallworld/analyses/__init__.py +14 -0
  3. smallworld/analyses/analysis.py +88 -0
  4. smallworld/analyses/code_coverage.py +31 -0
  5. smallworld/analyses/colorizer.py +682 -0
  6. smallworld/analyses/colorizer_summary.py +100 -0
  7. smallworld/analyses/field_detection/__init__.py +14 -0
  8. smallworld/analyses/field_detection/field_analysis.py +536 -0
  9. smallworld/analyses/field_detection/guards.py +26 -0
  10. smallworld/analyses/field_detection/hints.py +133 -0
  11. smallworld/analyses/field_detection/malloc.py +211 -0
  12. smallworld/analyses/forced_exec/__init__.py +3 -0
  13. smallworld/analyses/forced_exec/forced_exec.py +87 -0
  14. smallworld/analyses/underlays/__init__.py +4 -0
  15. smallworld/analyses/underlays/basic.py +13 -0
  16. smallworld/analyses/underlays/underlay.py +31 -0
  17. smallworld/analyses/unstable/__init__.py +4 -0
  18. smallworld/analyses/unstable/angr/__init__.py +0 -0
  19. smallworld/analyses/unstable/angr/base.py +12 -0
  20. smallworld/analyses/unstable/angr/divergence.py +274 -0
  21. smallworld/analyses/unstable/angr/model.py +383 -0
  22. smallworld/analyses/unstable/angr/nwbt.py +63 -0
  23. smallworld/analyses/unstable/angr/typedefs.py +170 -0
  24. smallworld/analyses/unstable/angr/utils.py +25 -0
  25. smallworld/analyses/unstable/angr/visitor.py +315 -0
  26. smallworld/analyses/unstable/angr_nwbt.py +106 -0
  27. smallworld/analyses/unstable/code_coverage.py +54 -0
  28. smallworld/analyses/unstable/code_reachable.py +44 -0
  29. smallworld/analyses/unstable/control_flow_tracer.py +71 -0
  30. smallworld/analyses/unstable/pointer_finder.py +90 -0
  31. smallworld/arch/__init__.py +0 -0
  32. smallworld/arch/aarch64_arch.py +286 -0
  33. smallworld/arch/amd64_arch.py +86 -0
  34. smallworld/arch/i386_arch.py +44 -0
  35. smallworld/emulators/__init__.py +14 -0
  36. smallworld/emulators/angr/__init__.py +7 -0
  37. smallworld/emulators/angr/angr.py +1652 -0
  38. smallworld/emulators/angr/default.py +15 -0
  39. smallworld/emulators/angr/exceptions.py +7 -0
  40. smallworld/emulators/angr/exploration/__init__.py +9 -0
  41. smallworld/emulators/angr/exploration/bounds.py +27 -0
  42. smallworld/emulators/angr/exploration/default.py +17 -0
  43. smallworld/emulators/angr/exploration/terminate.py +22 -0
  44. smallworld/emulators/angr/factory.py +55 -0
  45. smallworld/emulators/angr/machdefs/__init__.py +35 -0
  46. smallworld/emulators/angr/machdefs/aarch64.py +292 -0
  47. smallworld/emulators/angr/machdefs/amd64.py +192 -0
  48. smallworld/emulators/angr/machdefs/arm.py +387 -0
  49. smallworld/emulators/angr/machdefs/i386.py +221 -0
  50. smallworld/emulators/angr/machdefs/machdef.py +138 -0
  51. smallworld/emulators/angr/machdefs/mips.py +184 -0
  52. smallworld/emulators/angr/machdefs/mips64.py +189 -0
  53. smallworld/emulators/angr/machdefs/ppc.py +101 -0
  54. smallworld/emulators/angr/machdefs/riscv.py +261 -0
  55. smallworld/emulators/angr/machdefs/xtensa.py +255 -0
  56. smallworld/emulators/angr/memory/__init__.py +7 -0
  57. smallworld/emulators/angr/memory/default.py +10 -0
  58. smallworld/emulators/angr/memory/fixups.py +43 -0
  59. smallworld/emulators/angr/memory/memtrack.py +105 -0
  60. smallworld/emulators/angr/scratch.py +43 -0
  61. smallworld/emulators/angr/simos.py +53 -0
  62. smallworld/emulators/angr/utils.py +70 -0
  63. smallworld/emulators/emulator.py +1013 -0
  64. smallworld/emulators/hookable.py +252 -0
  65. smallworld/emulators/panda/__init__.py +5 -0
  66. smallworld/emulators/panda/machdefs/__init__.py +28 -0
  67. smallworld/emulators/panda/machdefs/aarch64.py +93 -0
  68. smallworld/emulators/panda/machdefs/amd64.py +71 -0
  69. smallworld/emulators/panda/machdefs/arm.py +89 -0
  70. smallworld/emulators/panda/machdefs/i386.py +36 -0
  71. smallworld/emulators/panda/machdefs/machdef.py +86 -0
  72. smallworld/emulators/panda/machdefs/mips.py +94 -0
  73. smallworld/emulators/panda/machdefs/mips64.py +91 -0
  74. smallworld/emulators/panda/machdefs/ppc.py +79 -0
  75. smallworld/emulators/panda/panda.py +575 -0
  76. smallworld/emulators/unicorn/__init__.py +13 -0
  77. smallworld/emulators/unicorn/machdefs/__init__.py +28 -0
  78. smallworld/emulators/unicorn/machdefs/aarch64.py +310 -0
  79. smallworld/emulators/unicorn/machdefs/amd64.py +326 -0
  80. smallworld/emulators/unicorn/machdefs/arm.py +321 -0
  81. smallworld/emulators/unicorn/machdefs/i386.py +137 -0
  82. smallworld/emulators/unicorn/machdefs/machdef.py +117 -0
  83. smallworld/emulators/unicorn/machdefs/mips.py +202 -0
  84. smallworld/emulators/unicorn/unicorn.py +684 -0
  85. smallworld/exceptions/__init__.py +5 -0
  86. smallworld/exceptions/exceptions.py +85 -0
  87. smallworld/exceptions/unstable/__init__.py +1 -0
  88. smallworld/exceptions/unstable/exceptions.py +25 -0
  89. smallworld/extern/__init__.py +4 -0
  90. smallworld/extern/ctypes.py +94 -0
  91. smallworld/extern/unstable/__init__.py +1 -0
  92. smallworld/extern/unstable/ghidra.py +129 -0
  93. smallworld/helpers.py +107 -0
  94. smallworld/hinting/__init__.py +8 -0
  95. smallworld/hinting/hinting.py +214 -0
  96. smallworld/hinting/hints.py +427 -0
  97. smallworld/hinting/unstable/__init__.py +2 -0
  98. smallworld/hinting/utils.py +19 -0
  99. smallworld/instructions/__init__.py +18 -0
  100. smallworld/instructions/aarch64.py +20 -0
  101. smallworld/instructions/arm.py +18 -0
  102. smallworld/instructions/bsid.py +67 -0
  103. smallworld/instructions/instructions.py +258 -0
  104. smallworld/instructions/mips.py +21 -0
  105. smallworld/instructions/x86.py +100 -0
  106. smallworld/logging.py +90 -0
  107. smallworld/platforms.py +95 -0
  108. smallworld/py.typed +0 -0
  109. smallworld/state/__init__.py +6 -0
  110. smallworld/state/cpus/__init__.py +32 -0
  111. smallworld/state/cpus/aarch64.py +563 -0
  112. smallworld/state/cpus/amd64.py +676 -0
  113. smallworld/state/cpus/arm.py +630 -0
  114. smallworld/state/cpus/cpu.py +71 -0
  115. smallworld/state/cpus/i386.py +239 -0
  116. smallworld/state/cpus/mips.py +374 -0
  117. smallworld/state/cpus/mips64.py +372 -0
  118. smallworld/state/cpus/powerpc.py +229 -0
  119. smallworld/state/cpus/riscv.py +357 -0
  120. smallworld/state/cpus/xtensa.py +80 -0
  121. smallworld/state/memory/__init__.py +7 -0
  122. smallworld/state/memory/code.py +70 -0
  123. smallworld/state/memory/elf/__init__.py +3 -0
  124. smallworld/state/memory/elf/elf.py +564 -0
  125. smallworld/state/memory/elf/rela/__init__.py +32 -0
  126. smallworld/state/memory/elf/rela/aarch64.py +27 -0
  127. smallworld/state/memory/elf/rela/amd64.py +32 -0
  128. smallworld/state/memory/elf/rela/arm.py +51 -0
  129. smallworld/state/memory/elf/rela/i386.py +32 -0
  130. smallworld/state/memory/elf/rela/mips.py +45 -0
  131. smallworld/state/memory/elf/rela/ppc.py +45 -0
  132. smallworld/state/memory/elf/rela/rela.py +63 -0
  133. smallworld/state/memory/elf/rela/riscv64.py +27 -0
  134. smallworld/state/memory/elf/rela/xtensa.py +15 -0
  135. smallworld/state/memory/elf/structs.py +55 -0
  136. smallworld/state/memory/heap.py +85 -0
  137. smallworld/state/memory/memory.py +181 -0
  138. smallworld/state/memory/stack/__init__.py +31 -0
  139. smallworld/state/memory/stack/aarch64.py +22 -0
  140. smallworld/state/memory/stack/amd64.py +42 -0
  141. smallworld/state/memory/stack/arm.py +66 -0
  142. smallworld/state/memory/stack/i386.py +22 -0
  143. smallworld/state/memory/stack/mips.py +34 -0
  144. smallworld/state/memory/stack/mips64.py +34 -0
  145. smallworld/state/memory/stack/ppc.py +34 -0
  146. smallworld/state/memory/stack/riscv.py +22 -0
  147. smallworld/state/memory/stack/stack.py +127 -0
  148. smallworld/state/memory/stack/xtensa.py +34 -0
  149. smallworld/state/models/__init__.py +6 -0
  150. smallworld/state/models/mmio.py +186 -0
  151. smallworld/state/models/model.py +163 -0
  152. smallworld/state/models/posix.py +455 -0
  153. smallworld/state/models/x86/__init__.py +2 -0
  154. smallworld/state/models/x86/microsoftcdecl.py +35 -0
  155. smallworld/state/models/x86/systemv.py +240 -0
  156. smallworld/state/state.py +962 -0
  157. smallworld/state/unstable/__init__.py +0 -0
  158. smallworld/state/unstable/elf.py +393 -0
  159. smallworld/state/x86_registers.py +30 -0
  160. smallworld/utils.py +935 -0
  161. smallworld_re-1.0.0.dist-info/LICENSE.txt +21 -0
  162. smallworld_re-1.0.0.dist-info/METADATA +189 -0
  163. smallworld_re-1.0.0.dist-info/RECORD +166 -0
  164. smallworld_re-1.0.0.dist-info/WHEEL +5 -0
  165. smallworld_re-1.0.0.dist-info/entry_points.txt +2 -0
  166. smallworld_re-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,85 @@
1
+ class Error(Exception):
2
+ """Common base class for all exceptions."""
3
+
4
+
5
+ class ConfigurationError(Error):
6
+ """Raised when there is a problem with configuration."""
7
+
8
+ pass
9
+
10
+
11
+ class EmulationError(Error):
12
+ """Raised when emulation fails."""
13
+
14
+ pass
15
+
16
+
17
+ class EmulationStop(EmulationError):
18
+ """Base class for all emulation stopping exceptions."""
19
+
20
+ pass
21
+
22
+
23
+ class EmulationBounds(EmulationStop):
24
+ """Raised when execution goes out of bounds."""
25
+
26
+ pass
27
+
28
+
29
+ class EmulationExitpoint(EmulationStop):
30
+ """Raised when execution hits an exit point."""
31
+
32
+ pass
33
+
34
+
35
+ class UnsupportedRegisterError(EmulationError):
36
+ """Raised if you ask for a register unsupported by the emulator"""
37
+
38
+ pass
39
+
40
+
41
+ class SymbolicValueError(EmulationError):
42
+ """Raised if you try to collapse a symbolic value to a concrete one"""
43
+
44
+ pass
45
+
46
+
47
+ class UnsatError(EmulationError):
48
+ """Raised if a symbolic expression is unsatisfiable given constraints"""
49
+
50
+ pass
51
+
52
+
53
+ class EmulationException(EmulationError):
54
+ """Raised when the underlying emulator fails.
55
+
56
+ This wraps known exceptions thrown by an Emulator.
57
+
58
+ Arguments:
59
+ exception: The original exception thrown.
60
+ """
61
+
62
+ def __init__(self, exception: Exception):
63
+ self.exception = exception
64
+
65
+ def __repr__(self) -> str:
66
+ return f"{self.__class__.__name__}({self.exception})"
67
+
68
+
69
+ class AnalysisError(Error):
70
+ """Some kind of error in analysis."""
71
+
72
+
73
+ __all__ = [
74
+ "Error",
75
+ "ConfigurationError",
76
+ "EmulationError",
77
+ "EmulationStop",
78
+ "EmulationBounds",
79
+ "EmulationExitpoint",
80
+ "EmulationException",
81
+ "SymbolicValueError",
82
+ "UnsatError",
83
+ "UnsupportedRegisterError",
84
+ "AnalysisError",
85
+ ]
@@ -0,0 +1 @@
1
+ from .exceptions import * # noqa: F401, F403
@@ -0,0 +1,25 @@
1
+ from .. import exceptions
2
+
3
+
4
+ class UnicornEmulationError(exceptions.EmulationError):
5
+ def __init__(self, exception: Exception, pc: int, data: dict):
6
+ self.exception = exception
7
+ self.pc = pc
8
+ self.data = data
9
+
10
+ def __repr__(self) -> str:
11
+ return (
12
+ f"{self.__class__.__name__}({self.exception}, {hex(self.pc)}, {self.data})"
13
+ )
14
+
15
+
16
+ class AnalysisSetupError(exceptions.AnalysisError):
17
+ """Raised when an analysis run gets into trouble during setup."""
18
+
19
+
20
+ class AnalysisRunError(exceptions.AnalysisError):
21
+ """Raised when something goes wrong during an analysis."""
22
+
23
+
24
+ class AnalysisSignal(exceptions.Error):
25
+ """Raised to signal a non-fatal exception during an analysis."""
@@ -0,0 +1,4 @@
1
+ from . import ctypes
2
+ from .unstable import * # noqa: F401, F403
3
+
4
+ __all__ = ["ctypes"]
@@ -0,0 +1,94 @@
1
+ import ctypes
2
+
3
+ # TODO: Figure out how to type-hint a ctypes objects
4
+ # The base class for ctypes objects is private (with good reason.)
5
+ # There are hacky and likely-fragile ways to get it.
6
+
7
+
8
+ class TypedPointer(ctypes.c_void_p):
9
+ """Typed pointer class
10
+
11
+ This class fills a gap in ctypes ability to represent data.
12
+
13
+ The existing typed pointer classes are designed
14
+ as direct references to Python objects;
15
+ it's not possible to set a specific address.
16
+
17
+ c_void_p represents the correct kind of value,
18
+ but has no associated data type.
19
+
20
+ Warning:
21
+ Do not instantiate this class directly! The referenced type needs to be
22
+ bound to a class, since ctypes uses instances to represent specific
23
+ variables or fields. Use ``create_typed_pointer()`` to create a
24
+ subclass for your type.
25
+ """
26
+
27
+ _type = None
28
+
29
+ def __init__(self, *args, **kwargs):
30
+ # No idea what the signature for this should be,
31
+ # and don't want to pick one in case it changes
32
+ if self.__class__ == TypedPointer:
33
+ raise TypeError("Cannot instantiate TypedPointer directly")
34
+ # NOTE: Due to a bug, can't use super() in ctypes subclasses
35
+ ctypes.c_void_p.__init__(self, *args, **kwargs)
36
+
37
+ @property
38
+ def type(self):
39
+ """The type referenced by this pointer."""
40
+
41
+ return self._type
42
+
43
+ def __str__(self):
44
+ return f"Pointer {self.type} = {self.value}"
45
+
46
+ def __eq__(self, other):
47
+ return (
48
+ isinstance(other, TypedPointer)
49
+ and other.type == self.type
50
+ and other.value == self.value
51
+ )
52
+
53
+
54
+ _pointertypes = {
55
+ None: ctypes.c_void_p,
56
+ ctypes.c_char: ctypes.c_char_p,
57
+ ctypes.c_wchar: ctypes.c_wchar_p,
58
+ }
59
+
60
+
61
+ def create_typed_pointer(reference):
62
+ """Create a typed pointer class.
63
+
64
+ The referenced type should be any ctypes type definition, or ``None`` to
65
+ represent 'void'.
66
+
67
+ Referenced types that already have a ctypes pointer value type will return
68
+ that type, not a ``TypedPointer``::
69
+
70
+ create_typed_pointer(c_char) # returns c_char_p
71
+ create_typed_pointer(c_wchar) # returns c_wchar_p
72
+ create_typed_pointer(None) # returns c_void_p
73
+
74
+ Arguments:
75
+ reference: The ctypes object defining the referenced type.
76
+
77
+ Returns:
78
+ A subclass of ``TypedPointer`` representing your referenced type.
79
+ """
80
+ if reference in _pointertypes:
81
+ return _pointertypes[reference]
82
+ else:
83
+ # Dynamically create a new subclass of TypedPointer to represent this
84
+ # particular type
85
+ name = f"{type.__name__}Pointer"
86
+ cls = type(name, (TypedPointer,), {"type": reference})
87
+ _pointertypes[reference] = cls
88
+ return cls
89
+
90
+
91
+ __all__ = [
92
+ "TypedPointer",
93
+ "create_typed_pointer",
94
+ ]
@@ -0,0 +1 @@
1
+ from . import ghidra # noqa: F401
@@ -0,0 +1,129 @@
1
+ import logging
2
+ import typing
3
+
4
+ from ... import platforms, state
5
+
6
+ # TODO fix these imports after refactoring state module
7
+ # from ... import models, state
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ # TODO: fix the cpustate type label once refactoring state module is complete
13
+ def setup_default_libc(
14
+ flat_api: typing.Any,
15
+ libc_func_names: typing.List[str],
16
+ cpustate: typing.Any,
17
+ canonicalize: bool = True,
18
+ ) -> None:
19
+ """Map some default libc models into the cpu state.
20
+
21
+ Uses Ghdira to figure out entry points in PLT for libc fns and arranges for
22
+ those in a user-provided list to be hooked using the default models in
23
+ Smallworld. Idea is you might not want all of them mapped and so you say
24
+ just these for now.
25
+
26
+ Arguments:
27
+ flat_api: this is what gets returned by pyhidra.open_program(elf_file)
28
+ libc_func_names: list of names of libc functions
29
+ cpustate: cpu state into which to map models
30
+ """
31
+
32
+ platform = platforms.Platform(
33
+ platforms.Architecture.X86_64, platforms.Byteorder.LITTLE
34
+ )
35
+ abi = platforms.ABI.SYSTEMV
36
+
37
+ program = flat_api.getCurrentProgram()
38
+ listing = program.getListing()
39
+
40
+ # find plt section
41
+ plt = None
42
+ for block in program.getMemory().getBlocks():
43
+ if "plt" in block.getName():
44
+ plt = block
45
+
46
+ assert plt is not None
47
+
48
+ # map all requested libc default models
49
+ num_mapped = 0
50
+ num_no_model = 0
51
+ num_too_many_models = 0
52
+ for func in listing.getFunctions(True):
53
+ func_name = func.getName()
54
+ entry = func.getEntryPoint()
55
+ if not plt.contains(entry):
56
+ continue
57
+ else:
58
+ if func_name in libc_func_names: # type: ignore
59
+ try:
60
+ # func is in plt and it is a function for which we want to use a default model
61
+ int_entry = int(entry.getOffset())
62
+ model = state.models.Model.lookup(
63
+ func_name, platform, abi, int_entry
64
+ )
65
+ cpustate.map(model)
66
+ logger.debug(
67
+ f"Added libc model for {func_name} entry {int_entry:x}"
68
+ )
69
+ num_mapped += 1
70
+ except ValueError:
71
+ logger.debug(
72
+ f"As there is no default model for {func_name}, adding with null model, entry {int_entry:x}"
73
+ )
74
+ model = state.models.Model.lookup("null", platform, abi, int_entry)
75
+ cpustate.map(model)
76
+ num_no_model += 1
77
+
78
+ logger.info(
79
+ f"Libc model mappings: {num_mapped} default, {num_no_model} no model, {num_too_many_models} too many models"
80
+ )
81
+
82
+
83
+ # TODO: fix the cpustate type label once refactoring state module is complete
84
+ def setup_section(
85
+ flat_api: typing.Any,
86
+ section_name: str,
87
+ cpustate: typing.Any,
88
+ elf_file: str = "None",
89
+ ) -> bytes:
90
+ """Set up this section in cpustate, possibly using contents of elf file
91
+
92
+ Uses ghidra to get start addr / size of sections for adding them to
93
+ cpustate. If elf_file is specified, the data will come from the file (ghidra
94
+ tells us where to find it) and get mapped into cpustate memory. Else that
95
+ will be zeros.
96
+
97
+ Arguments:
98
+ flat_api: this is what gets returned by pyhidra.open_program(elf_file)
99
+ section_name: '.data' or '.txt' or '.got' or ..
100
+ cpustate: cpu state into which to map models
101
+ elf_file: name of file flat_api came from (elf)
102
+
103
+ Returns:
104
+ If elf_file is specified then cpu state for that came from
105
+ file which means we loaded the data out of the file at the
106
+ correct offset. This will be returned in that case. Else, the
107
+ section data will be 0s.
108
+
109
+ """
110
+ # note, elf_file assumed to be same as one flat_api opened
111
+ program = flat_api.getCurrentProgram()
112
+ memory = program.getMemory()
113
+ block = memory.getBlock(section_name)
114
+ address = int(block.start.getOffset())
115
+ size = int(block.size)
116
+ section_bytes = None
117
+ if elf_file == "None":
118
+ # assume we are to just zero this
119
+ section_bytes = b"\0" * size
120
+ else:
121
+ # this is file offset that block
122
+ offs_in_elf = block.getSourceInfos()[0].getFileBytesOffset()
123
+ # read actual section bytesout of elf
124
+ with open(elf_file, "rb") as e:
125
+ e.seek(offs_in_elf)
126
+ section_bytes = e.read(size)
127
+ section = state.memory.RawMemory.from_bytes(section_bytes, address)
128
+ cpustate.map(section, section_name)
129
+ return section_bytes
smallworld/helpers.py ADDED
@@ -0,0 +1,107 @@
1
+ import argparse
2
+ import logging
3
+ import typing
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ from . import analyses, emulators, exceptions, state
9
+
10
+ T = typing.TypeVar("T", bound=state.Machine)
11
+
12
+
13
+ def analyze(
14
+ machine: state.Machine,
15
+ asys: typing.List[typing.Union[analyses.Analysis, analyses.Filter]],
16
+ ) -> None:
17
+ """Run requested analyses on some code
18
+
19
+ Arguments:
20
+ machine: A state class from which emulation should begin.
21
+ asys: List of analyses and filters to run
22
+ """
23
+
24
+ filters: typing.List[analyses.Filter] = []
25
+ runners: typing.List[analyses.Analysis] = []
26
+ for analysis in asys:
27
+ if isinstance(analysis, analyses.Filter):
28
+ filters.append(analysis)
29
+ filters[-1].activate()
30
+ elif isinstance(analysis, analyses.Analysis):
31
+ runners.append(analysis)
32
+
33
+ try:
34
+ for analysis in runners:
35
+ analysis.run(machine)
36
+ except:
37
+ logger.exception("Error durring analysis")
38
+ finally:
39
+ for filter in filters:
40
+ filter.deactivate()
41
+
42
+
43
+ def fuzz(
44
+ machine: state.Machine,
45
+ input_callback: typing.Callable,
46
+ crash_callback: typing.Optional[typing.Callable] = None,
47
+ always_validate: bool = False,
48
+ iterations: int = 1,
49
+ ) -> None:
50
+ """Creates an AFL fuzzing harness.
51
+
52
+ Arguments:
53
+ cpu: A state class from which emulation should begin.
54
+ input_callback: This is called for every input. It should map the input
55
+ into the state. It should return true if the input is accepted and
56
+ false if it should be skipped.
57
+ crash_callback: This is called on crashes to validate that we do in
58
+ fact care about this crash.
59
+ always_validate: Call the crash_callback everytime instead of just on
60
+ crashes.
61
+ iterations: How many iterations to run before forking again.
62
+ """
63
+
64
+ try:
65
+ import unicornafl
66
+ except ImportError:
67
+ raise RuntimeError(
68
+ "missing `unicornafl` - afl++ must be installed manually from source"
69
+ )
70
+
71
+ arg_parser = argparse.ArgumentParser(description="AFL Harness")
72
+ arg_parser.add_argument("input_file", type=str, help="File path AFL will mutate")
73
+ args = arg_parser.parse_args()
74
+
75
+ cpu: typing.Optional[state.cpus.CPU] = None
76
+ for c in machine.members(state.cpus.CPU).items():
77
+ if cpu is not None:
78
+ raise exceptions.ConfigurationError("cannot fuzz a multi-core machine")
79
+ cpu = c
80
+ if cpu is None:
81
+ raise exceptions.ConfigurationError("cannot fuzz a zero-core machine")
82
+
83
+ emu = emulators.UnicornEmulator(cpu.platform)
84
+ machine.apply(emu)
85
+
86
+ exits = []
87
+ code = None
88
+ for code in machine.members(state.memory.code.Executable).items():
89
+ exits.extend([b.stop for b in code.bounds])
90
+
91
+ if len(exits) == 0:
92
+ if code is None:
93
+ raise exceptions.ConfigurationError("Cannot fuzz a machine with no code")
94
+ exits.append(code.base + len(code.image))
95
+
96
+ unicornafl.uc_afl_fuzz(
97
+ uc=emu.engine,
98
+ input_file=args.input_file,
99
+ place_input_callback=input_callback,
100
+ exits=exits,
101
+ validate_crash_callback=crash_callback,
102
+ always_validate=always_validate,
103
+ persistent_iters=iterations,
104
+ )
105
+
106
+
107
+ __all__ = ["analyze"]
@@ -0,0 +1,8 @@
1
+ from .hinting import * # noqa: F401, F403
2
+ from .hinting import __all__ as __hinting__
3
+ from .hints import * # noqa: F401, F403
4
+ from .hints import __all__ as __hints__
5
+ from .utils import * # noqa: F401, F403
6
+ from .utils import __all__ as __utils__
7
+
8
+ __all__ = __hinting__ + __hints__ + __utils__
@@ -0,0 +1,214 @@
1
+ import copy
2
+ import json
3
+ import logging
4
+ import sys
5
+ import typing
6
+ from dataclasses import dataclass
7
+
8
+ """
9
+ Will this appear in docs?
10
+
11
+
12
+ """
13
+
14
+ # logging re-exports
15
+ from logging import WARNING
16
+
17
+ from .. import logging as internal_logging
18
+
19
+
20
+ class Serializable:
21
+ """Base class for serialization support.
22
+
23
+ Descendants should implement the methods below to allow automatic
24
+ serialization/deserialization using the JSON encoder/decoder classes below.
25
+ """
26
+
27
+ def to_dict(self) -> dict:
28
+ raise NotImplementedError()
29
+
30
+ @classmethod
31
+ def from_dict(cls, dict):
32
+ raise NotImplementedError()
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class Hint(Serializable):
37
+ """Base class for all Hints.
38
+
39
+ Arguments:
40
+ message: A message for this Hint.
41
+ """
42
+
43
+ message: str
44
+ """A detailed description."""
45
+
46
+ def to_dict(self) -> dict:
47
+ """Convert to a dictionary which can be trivially serialized.
48
+
49
+ Returns:
50
+ A dictionary containing the hint.
51
+ """
52
+
53
+ return self.__dict__
54
+
55
+ @classmethod
56
+ def from_dict(cls, dict):
57
+ """Load from a dictionary.
58
+
59
+ Arguments:
60
+ dict: Dictionary from which to construct the hint.
61
+
62
+ Returns:
63
+ The constructed hint.
64
+ """
65
+
66
+ return cls(**dict)
67
+
68
+
69
+ class Hinter(logging.Logger):
70
+ """A custom logger that only accepts Hints."""
71
+
72
+ def _log(self, level, msg, *args, **kwargs):
73
+ if not isinstance(msg, Hint):
74
+ raise ValueError(f"{repr(msg)} is not a Hint")
75
+
76
+ return super()._log(level, msg, *args, **kwargs)
77
+
78
+
79
+ root = Hinter(name="root", level=WARNING)
80
+ Hinter.root = typing.cast(logging.RootLogger, root)
81
+ Hinter.manager = logging.Manager(Hinter.root)
82
+ Hinter.manager.loggerClass = Hinter
83
+
84
+
85
+ def get_hinter(name: typing.Optional[str] = None) -> Hinter:
86
+ """Get a hinter with the given name.
87
+
88
+ Arguments:
89
+ name: The name of the hinter to get - if `None` this returns the
90
+ root Hinter.
91
+
92
+ Returns:
93
+ A Hinter with the given name.
94
+ """
95
+
96
+ if not name or isinstance(name, str) and name == root.name:
97
+ return root
98
+
99
+ return typing.cast(Hinter, Hinter.manager.getLogger(name))
100
+
101
+
102
+ class SerializableJSONEncoder(json.JSONEncoder):
103
+ def default(self, o):
104
+ if isinstance(o, Serializable):
105
+ d = o.to_dict()
106
+ d["class"] = f"{o.__class__.__module__}.{o.__class__.__name__}"
107
+
108
+ return d
109
+ return super().default(o)
110
+
111
+
112
+ class SerializableJSONDecoder(json.JSONDecoder):
113
+ def __init__(self, *args, **kwargs):
114
+ json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)
115
+
116
+ def object_hook(self, dict):
117
+ if "class" in dict:
118
+ module, name = dict["class"].rsplit(".", 1)
119
+ cls = getattr(sys.modules[module], name)
120
+ del dict["class"]
121
+
122
+ return cls.from_dict(dict)
123
+ return dict
124
+
125
+
126
+ class JSONFormatter(logging.Formatter):
127
+ """A custom JSON formatter for json-serializable messages.
128
+
129
+ Arguments:
130
+ keys (dict): A dictionary mapping json keys to their logging format
131
+ strings.
132
+ """
133
+
134
+ def __init__(self, *args, keys=None, **kwargs):
135
+ super().__init__(*args, **kwargs)
136
+
137
+ keys = keys or {}
138
+ self.keys = {}
139
+ for name, fmt in keys.items():
140
+ self.keys[name] = logging.Formatter(fmt)
141
+
142
+ def format(self, record):
143
+ formatted = {}
144
+
145
+ for key, formatter in self.keys.items():
146
+ formatted[key] = formatter.format(record)
147
+
148
+ formatted["content"] = record.msg
149
+
150
+ hint, record.msg = record.msg, None
151
+ modified = copy.deepcopy(record)
152
+ record.msg = hint
153
+
154
+ modified.msg = json.dumps(formatted, cls=SerializableJSONEncoder)
155
+
156
+ return super().format(modified)
157
+
158
+
159
+ def setup_hinting(
160
+ level: int = logging.INFO,
161
+ verbose: bool = False,
162
+ colors: bool = True,
163
+ stream: bool = True,
164
+ file: typing.Optional[str] = None,
165
+ ) -> None:
166
+ """Set up hint handling.
167
+
168
+ Note:
169
+ This should only be called once.
170
+
171
+ Arguments:
172
+ level: Hinting level (from `hinting` module).
173
+ verbose: Enable verbose hinting mode.
174
+ colors: Enable hinting colors (if supported).
175
+ stream: Enable stream logging.
176
+ file: If provided, enable file logging to the path provided.
177
+ """
178
+
179
+ if verbose:
180
+ keys = {
181
+ "time": "%(asctime)s",
182
+ "level": "%(levelname)s",
183
+ "source": "%(name)s",
184
+ }
185
+ else:
186
+ keys = {}
187
+
188
+ root = get_hinter()
189
+ root.setLevel(level)
190
+
191
+ if stream:
192
+ format = "[%(levelchar)s] %(message)s"
193
+
194
+ if colors and sys.stderr.isatty():
195
+ format = f"%(levelcolor)s{format}{internal_logging.ColorLevelFilter.END}"
196
+
197
+ formatter = JSONFormatter(format, keys=keys)
198
+
199
+ handler: logging.Handler = logging.StreamHandler()
200
+ handler.setLevel(level)
201
+ handler.addFilter(internal_logging.CharacterLevelFilter())
202
+ handler.addFilter(internal_logging.ColorLevelFilter())
203
+ handler.setFormatter(formatter)
204
+
205
+ root.addHandler(handler)
206
+
207
+ if file:
208
+ formatter = JSONFormatter("%(message)s", keys=keys)
209
+ handler = logging.FileHandler(file, mode="w")
210
+ handler.setFormatter(formatter)
211
+ root.addHandler(handler)
212
+
213
+
214
+ __all__ = ["Hint", "Serializable", "root", "get_hinter", "setup_hinting"]