summaryrefslogtreecommitdiffstats
path: root/sploit/arch.py
blob: 97c1140f59b11c4dbf5348b71c5cc7a517f13fba (plain) (blame)
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
"""
Architecture-aware utilities and global architecture config

It is common within sploit and for users of sploit to need different behavior
depending on the architecture of the target.  This module encapsulates those
behaviors and bases them on a global architecture that is also configured here.

Users can set the global arch with arch.set() and all of the methods in this
module will honor it.  An architecture can be defined through the Arch dataclass
and there are also several predefined architecture constants that can be used.
These are accessible by name at module scope. (i.e. sploit.arch.x86_64)

arch (Arch): the architecture config that sploit will use whenever it needs to
know the architecture of the target

DEFAULT_ARCH (Arch): the default architecture that arch is set to
"""

from dataclasses import dataclass

def __define_architectures():
    # All predefined architectures should be listed here
    # These will also be added to the module's namespace
    __arch_list = {
        'x86'       : Arch( 'x86', 4, 'little', 16, b'\x90'),
        'x86_64'    : Arch( 'x86', 8, 'little', 16, b'\x90'),
        'ARM'       : Arch( 'arm', 4, 'little',  8, b'\xe1\xa0\x00\x00'),
        'THUMB'     : Arch( 'arm', 4, 'little',  8, b'\x46\xc0')
    }
    globals().update(__arch_list)
    global __arch_lookup
    __arch_lookup = {(a.arch_string, a.wordsize, a.endianness) : a for a in reversed(__arch_list.values())}

@dataclass(frozen=True)
class Arch:
    """
    Dataclass of information about a target architecture

    arch_string (str): string returned by r2 iI in the arch field
    wordsize (int): the width, in bytes, of the natural unit of data
    endianness (str): byte order. either "little" or "big"
    alignment (int): the multiple, in bytes, that return addresses must exist
    on the stack
    nopcode (bytes): the exact bytes of a "do nothing" instruction
    """

    arch_string: str
    wordsize: int
    endianness: str
    alignment: int
    nopcode: bytes

    def set(self,new_arch):
        """Copy the given Arch into this instance."""
        if type(new_arch) is not Arch:
            raise TypeError(f'arch: new_arch must be an Arch: {new_arch}')
        self.__dict__.update(new_arch.__dict__)
__define_architectures()

DEFAULT_ARCH = x86_64
arch = Arch(**DEFAULT_ARCH.__dict__)

def lookup_arch(arch_string, wordsize, endianness):
    """
    Return an Arch object with the matching search parameters.

    If a predefined Arch matches the specified fields, it will be returned.
    Otherwise, None is returned.

    arch_string (str): The "arch" string returned from r2 iI.
    wordsize (int): The natural width of an int in bytes.
    endianness (str): The order of bytes in an int (either "little" or "big")
    """
    return __arch_lookup.get((arch_string, wordsize, endianness))

def sint(i):
    """Convert given int to signed int of arch.wordsize width."""
    return __int(i, True)

def uint(i):
    """Convert given int to unsigned int of arch.wordsize width."""
    return __int(i, False)

def int8(i):
    """Convert given int to signed 8 bit int."""
    return __int(i, True, 1)

def int16(i):
    """Convert given int to signed 16 bit int."""
    return __int(i, True, 2)

def int32(i):
    """Convert given int to signed 32 bit int."""
    return __int(i, True, 4)

def int64(i):
    """Convert given int to signed 64 bit int."""
    return __int(i, True, 8)

def uint8(i):
    """Convert given int to unsigned 8 bit int."""
    return __int(i, False, 1)

def uint16(i):
    """Convert given int to unsigned 16 bit int."""
    return __int(i, False, 2)

def uint32(i):
    """Convert given int to unsigned 32 bit int."""
    return __int(i, False, 4)

def uint64(i):
    """Convert given int to unsigned 64 bit int."""
    return __int(i, False, 8)

def btoi(b, byteorder=None):
    """Convert given byte array to an int."""
    byteorder = byteorder or arch.endianness
    return int.from_bytes(b, byteorder, signed=False)

def itob(i, width=None, byteorder=None):
    """Convert given int to a byte array."""
    width = width or arch.wordsize
    byteorder = byteorder or arch.endianness
    return __int(i,False,width).to_bytes(width, byteorder, signed=False)

def __int(i, signed=False, width=None):
    # type conversion from int to int of given sign and width
    i = int(i)
    width = width or arch.wordsize
    bits = 8 * width
    if signed:
        sign_bit = 1 << (bits - 1)
        return (i & (sign_bit - 1)) - (i & sign_bit)
    else:
        mask = (1 << bits) - 1
        return i & mask