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.
- smallworld/__init__.py +35 -0
- smallworld/analyses/__init__.py +14 -0
- smallworld/analyses/analysis.py +88 -0
- smallworld/analyses/code_coverage.py +31 -0
- smallworld/analyses/colorizer.py +682 -0
- smallworld/analyses/colorizer_summary.py +100 -0
- smallworld/analyses/field_detection/__init__.py +14 -0
- smallworld/analyses/field_detection/field_analysis.py +536 -0
- smallworld/analyses/field_detection/guards.py +26 -0
- smallworld/analyses/field_detection/hints.py +133 -0
- smallworld/analyses/field_detection/malloc.py +211 -0
- smallworld/analyses/forced_exec/__init__.py +3 -0
- smallworld/analyses/forced_exec/forced_exec.py +87 -0
- smallworld/analyses/underlays/__init__.py +4 -0
- smallworld/analyses/underlays/basic.py +13 -0
- smallworld/analyses/underlays/underlay.py +31 -0
- smallworld/analyses/unstable/__init__.py +4 -0
- smallworld/analyses/unstable/angr/__init__.py +0 -0
- smallworld/analyses/unstable/angr/base.py +12 -0
- smallworld/analyses/unstable/angr/divergence.py +274 -0
- smallworld/analyses/unstable/angr/model.py +383 -0
- smallworld/analyses/unstable/angr/nwbt.py +63 -0
- smallworld/analyses/unstable/angr/typedefs.py +170 -0
- smallworld/analyses/unstable/angr/utils.py +25 -0
- smallworld/analyses/unstable/angr/visitor.py +315 -0
- smallworld/analyses/unstable/angr_nwbt.py +106 -0
- smallworld/analyses/unstable/code_coverage.py +54 -0
- smallworld/analyses/unstable/code_reachable.py +44 -0
- smallworld/analyses/unstable/control_flow_tracer.py +71 -0
- smallworld/analyses/unstable/pointer_finder.py +90 -0
- smallworld/arch/__init__.py +0 -0
- smallworld/arch/aarch64_arch.py +286 -0
- smallworld/arch/amd64_arch.py +86 -0
- smallworld/arch/i386_arch.py +44 -0
- smallworld/emulators/__init__.py +14 -0
- smallworld/emulators/angr/__init__.py +7 -0
- smallworld/emulators/angr/angr.py +1652 -0
- smallworld/emulators/angr/default.py +15 -0
- smallworld/emulators/angr/exceptions.py +7 -0
- smallworld/emulators/angr/exploration/__init__.py +9 -0
- smallworld/emulators/angr/exploration/bounds.py +27 -0
- smallworld/emulators/angr/exploration/default.py +17 -0
- smallworld/emulators/angr/exploration/terminate.py +22 -0
- smallworld/emulators/angr/factory.py +55 -0
- smallworld/emulators/angr/machdefs/__init__.py +35 -0
- smallworld/emulators/angr/machdefs/aarch64.py +292 -0
- smallworld/emulators/angr/machdefs/amd64.py +192 -0
- smallworld/emulators/angr/machdefs/arm.py +387 -0
- smallworld/emulators/angr/machdefs/i386.py +221 -0
- smallworld/emulators/angr/machdefs/machdef.py +138 -0
- smallworld/emulators/angr/machdefs/mips.py +184 -0
- smallworld/emulators/angr/machdefs/mips64.py +189 -0
- smallworld/emulators/angr/machdefs/ppc.py +101 -0
- smallworld/emulators/angr/machdefs/riscv.py +261 -0
- smallworld/emulators/angr/machdefs/xtensa.py +255 -0
- smallworld/emulators/angr/memory/__init__.py +7 -0
- smallworld/emulators/angr/memory/default.py +10 -0
- smallworld/emulators/angr/memory/fixups.py +43 -0
- smallworld/emulators/angr/memory/memtrack.py +105 -0
- smallworld/emulators/angr/scratch.py +43 -0
- smallworld/emulators/angr/simos.py +53 -0
- smallworld/emulators/angr/utils.py +70 -0
- smallworld/emulators/emulator.py +1013 -0
- smallworld/emulators/hookable.py +252 -0
- smallworld/emulators/panda/__init__.py +5 -0
- smallworld/emulators/panda/machdefs/__init__.py +28 -0
- smallworld/emulators/panda/machdefs/aarch64.py +93 -0
- smallworld/emulators/panda/machdefs/amd64.py +71 -0
- smallworld/emulators/panda/machdefs/arm.py +89 -0
- smallworld/emulators/panda/machdefs/i386.py +36 -0
- smallworld/emulators/panda/machdefs/machdef.py +86 -0
- smallworld/emulators/panda/machdefs/mips.py +94 -0
- smallworld/emulators/panda/machdefs/mips64.py +91 -0
- smallworld/emulators/panda/machdefs/ppc.py +79 -0
- smallworld/emulators/panda/panda.py +575 -0
- smallworld/emulators/unicorn/__init__.py +13 -0
- smallworld/emulators/unicorn/machdefs/__init__.py +28 -0
- smallworld/emulators/unicorn/machdefs/aarch64.py +310 -0
- smallworld/emulators/unicorn/machdefs/amd64.py +326 -0
- smallworld/emulators/unicorn/machdefs/arm.py +321 -0
- smallworld/emulators/unicorn/machdefs/i386.py +137 -0
- smallworld/emulators/unicorn/machdefs/machdef.py +117 -0
- smallworld/emulators/unicorn/machdefs/mips.py +202 -0
- smallworld/emulators/unicorn/unicorn.py +684 -0
- smallworld/exceptions/__init__.py +5 -0
- smallworld/exceptions/exceptions.py +85 -0
- smallworld/exceptions/unstable/__init__.py +1 -0
- smallworld/exceptions/unstable/exceptions.py +25 -0
- smallworld/extern/__init__.py +4 -0
- smallworld/extern/ctypes.py +94 -0
- smallworld/extern/unstable/__init__.py +1 -0
- smallworld/extern/unstable/ghidra.py +129 -0
- smallworld/helpers.py +107 -0
- smallworld/hinting/__init__.py +8 -0
- smallworld/hinting/hinting.py +214 -0
- smallworld/hinting/hints.py +427 -0
- smallworld/hinting/unstable/__init__.py +2 -0
- smallworld/hinting/utils.py +19 -0
- smallworld/instructions/__init__.py +18 -0
- smallworld/instructions/aarch64.py +20 -0
- smallworld/instructions/arm.py +18 -0
- smallworld/instructions/bsid.py +67 -0
- smallworld/instructions/instructions.py +258 -0
- smallworld/instructions/mips.py +21 -0
- smallworld/instructions/x86.py +100 -0
- smallworld/logging.py +90 -0
- smallworld/platforms.py +95 -0
- smallworld/py.typed +0 -0
- smallworld/state/__init__.py +6 -0
- smallworld/state/cpus/__init__.py +32 -0
- smallworld/state/cpus/aarch64.py +563 -0
- smallworld/state/cpus/amd64.py +676 -0
- smallworld/state/cpus/arm.py +630 -0
- smallworld/state/cpus/cpu.py +71 -0
- smallworld/state/cpus/i386.py +239 -0
- smallworld/state/cpus/mips.py +374 -0
- smallworld/state/cpus/mips64.py +372 -0
- smallworld/state/cpus/powerpc.py +229 -0
- smallworld/state/cpus/riscv.py +357 -0
- smallworld/state/cpus/xtensa.py +80 -0
- smallworld/state/memory/__init__.py +7 -0
- smallworld/state/memory/code.py +70 -0
- smallworld/state/memory/elf/__init__.py +3 -0
- smallworld/state/memory/elf/elf.py +564 -0
- smallworld/state/memory/elf/rela/__init__.py +32 -0
- smallworld/state/memory/elf/rela/aarch64.py +27 -0
- smallworld/state/memory/elf/rela/amd64.py +32 -0
- smallworld/state/memory/elf/rela/arm.py +51 -0
- smallworld/state/memory/elf/rela/i386.py +32 -0
- smallworld/state/memory/elf/rela/mips.py +45 -0
- smallworld/state/memory/elf/rela/ppc.py +45 -0
- smallworld/state/memory/elf/rela/rela.py +63 -0
- smallworld/state/memory/elf/rela/riscv64.py +27 -0
- smallworld/state/memory/elf/rela/xtensa.py +15 -0
- smallworld/state/memory/elf/structs.py +55 -0
- smallworld/state/memory/heap.py +85 -0
- smallworld/state/memory/memory.py +181 -0
- smallworld/state/memory/stack/__init__.py +31 -0
- smallworld/state/memory/stack/aarch64.py +22 -0
- smallworld/state/memory/stack/amd64.py +42 -0
- smallworld/state/memory/stack/arm.py +66 -0
- smallworld/state/memory/stack/i386.py +22 -0
- smallworld/state/memory/stack/mips.py +34 -0
- smallworld/state/memory/stack/mips64.py +34 -0
- smallworld/state/memory/stack/ppc.py +34 -0
- smallworld/state/memory/stack/riscv.py +22 -0
- smallworld/state/memory/stack/stack.py +127 -0
- smallworld/state/memory/stack/xtensa.py +34 -0
- smallworld/state/models/__init__.py +6 -0
- smallworld/state/models/mmio.py +186 -0
- smallworld/state/models/model.py +163 -0
- smallworld/state/models/posix.py +455 -0
- smallworld/state/models/x86/__init__.py +2 -0
- smallworld/state/models/x86/microsoftcdecl.py +35 -0
- smallworld/state/models/x86/systemv.py +240 -0
- smallworld/state/state.py +962 -0
- smallworld/state/unstable/__init__.py +0 -0
- smallworld/state/unstable/elf.py +393 -0
- smallworld/state/x86_registers.py +30 -0
- smallworld/utils.py +935 -0
- smallworld_re-1.0.0.dist-info/LICENSE.txt +21 -0
- smallworld_re-1.0.0.dist-info/METADATA +189 -0
- smallworld_re-1.0.0.dist-info/RECORD +166 -0
- smallworld_re-1.0.0.dist-info/WHEEL +5 -0
- smallworld_re-1.0.0.dist-info/entry_points.txt +2 -0
- 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,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"]
|