summaryrefslogtreecommitdiff
path: root/pwnlib/data/syscalls/generate.py
blob: b10d38a91260b4e2ab49e7a7504c090e6d42a88a (plain)
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
#!/usr/bin/env python3
from __future__ import division
import argparse
import keyword
import os

from pwnlib import constants
from pwnlib.context import context

# github.com/zachriggle/functions
from functions import functions, Function, Argument

ARCHITECTURES = ['i386', 'amd64', 'arm', 'aarch64', 'mips']

HEADER = '''
<%
import collections
import pwnlib.abi
import pwnlib.constants
import pwnlib.shellcraft
import six
%>
'''

DOCSTRING = '''
<%docstring>{name}({arguments_comma_separated}) -> str

Invokes the syscall {name}.

See 'man 2 {name}' for more information.

Arguments:
{arg_docs}
Returns:
    {return_type}
</%docstring>
'''

ARGUMENTS = """
<%page args="{arguments_default_values}"/>
"""

CALL = """
<%
    abi = pwnlib.abi.ABI.syscall()
    stack = abi.stack
    regs = abi.register_arguments[1:]
    allregs = pwnlib.shellcraft.registers.current()

    can_pushstr = {string_arguments!r}
    can_pushstr_array = {array_arguments!r}

    argument_names = {argument_names!r}
    argument_values = [{arguments_comma_separated!s}]

    # Load all of the arguments into their destination registers / stack slots.
    register_arguments = dict()
    stack_arguments = collections.OrderedDict()
    string_arguments = dict()
    dict_arguments = dict()
    array_arguments = dict()
    syscall_repr = []

    for name, arg in zip(argument_names, argument_values):
        if arg is not None:
            syscall_repr.append('%s=%s' % (name, pwnlib.shellcraft.pretty(arg, False)))

        # If the argument itself (input) is a register...
        if arg in allregs:
            index = argument_names.index(name)
            if index < len(regs):
                target = regs[index]
                register_arguments[target] = arg
            elif arg is not None:
                stack_arguments[index] = arg

        # The argument is not a register.  It is a string value, and we
        # are expecting a string value
        elif name in can_pushstr and isinstance(arg, (six.binary_type, six.text_type)):
            if isinstance(arg, six.text_type):
                arg = arg.encode('utf-8')
            string_arguments[name] = arg

        # The argument is not a register.  It is a dictionary, and we are
        # expecting K:V paris.
        elif name in can_pushstr_array and isinstance(arg, dict):
            array_arguments[name] = ['%s=%s' % (k,v) for (k,v) in arg.items()]

        # The arguent is not a register.  It is a list, and we are expecting
        # a list of arguments.
        elif name in can_pushstr_array and isinstance(arg, (list, tuple)):
            array_arguments[name] = arg

        # The argument is not a register, string, dict, or list.
        # It could be a constant string ('O_RDONLY') for an integer argument,
        # an actual integer value, or a constant.
        else:
            index = argument_names.index(name)
            if index < len(regs):
                target = regs[index]
                register_arguments[target] = arg
            elif arg is not None:
                stack_arguments[target] = arg

    # Some syscalls have different names on various architectures.
    # Determine which syscall number to use for the current architecture.
    for syscall in {syscalls!r}:
        if hasattr(pwnlib.constants, syscall):
            break
    else:
        raise Exception("Could not locate any syscalls: %r" % syscalls)
%>
    /* {name}(${{', '.join(syscall_repr)}}) */
%for name, arg in string_arguments.items():
    ${{pwnlib.shellcraft.pushstr(arg, append_null=(b'\\x00' not in arg))}}
    ${{pwnlib.shellcraft.mov(regs[argument_names.index(name)], abi.stack)}}
%endfor
%for name, arg in array_arguments.items():
    ${{pwnlib.shellcraft.pushstr_array(regs[argument_names.index(name)], arg)}}
%endfor
%for name, arg in stack_arguments.items():
    ${{pwnlib.shellcraft.push(arg)}}
%endfor
    ${{pwnlib.shellcraft.setregs(register_arguments)}}
    ${{pwnlib.shellcraft.syscall(syscall)}}
"""


def can_be_constant(arg):
    if arg.derefcnt == 0:
        return True


def can_be_string(arg):
    if arg.type == 'char' and arg.derefcnt == 1:
        return True
    if arg.type == 'void' and arg.derefcnt == 1:
        return True


def can_be_array(arg):
    if arg.type == 'char' and arg.derefcnt == 2:
        return True
    if arg.type == 'void' and arg.derefcnt == 2:
        return True


def fix_bad_arg_names(func, arg):
    if arg.name == 'len':
        return 'length'

    if arg.name in ('str', 'repr') or keyword.iskeyword(arg.name):
        return arg.name + '_'

    if func.name == 'open' and arg.name == 'vararg':
        return 'mode'

    return arg.name


def get_arg_default(arg):
    return 0


def fix_rt_syscall_name(name):
    if name.startswith('rt_'):
        return name[3:]
    return name


def fix_syscall_names(name):
    # Do not use old_mmap
    if name == 'SYS_mmap':
        return ['SYS_mmap2', name]
    # Some arches don't have vanilla sigreturn
    if name.endswith('_sigreturn'):
        return ['SYS_sigreturn', 'SYS_rt_sigreturn']
    return [name]


def main(target):
    for arch in ARCHITECTURES:
        with context.local(arch=arch):
            generate_one(target)


def generate_one(target):
    SYSCALL_NAMES = [c for c in dir(constants) if c.startswith('SYS_')]

    for syscall in SYSCALL_NAMES:
        name = syscall[4:]

        # Skip anything with uppercase
        if name.lower() != name:
            print('Skipping %s' % name)
            continue

        # Skip anything that starts with 'unused' or 'sys' after stripping
        if name.startswith('unused'):
            print('Skipping %s' % name)
            continue

        function = functions.get(name, None)

        if name.startswith('rt_'):
            name = name[3:]

        # If we can't find a function, just stub it out with something
        # that has a vararg argument.
        if function is None:
            print('Stubbing out %s' % name)
            args = [Argument('int', 0, 'vararg')]
            function = Function('long', 0, name, args)

        # Some syscalls have different names on different architectures,
        # or are superceded.  We try to do the "best" thing at runtime.
        syscalls = fix_syscall_names(syscall)

        # Set up the argument string for Mako
        argument_names = []
        argument_names_ = []
        argument_defaults = []

        string_arguments = []
        array_arguments = []
        arg_docs = []

        #

        for arg in function.args:
            argname_ = fix_bad_arg_names(function, arg)
            argname = argname_.rstrip('_')
            default = get_arg_default(arg)

            if can_be_array(arg):
                array_arguments.append(argname)

            if can_be_string(arg):
                string_arguments.append(argname)

            argtype = str(arg.type) + ('*' * arg.derefcnt)
            arg_docs.append(
                '    {argname_}({argtype}): {argname}'.format(
                    argname_=argname_,
                    argname=argname,
                    argtype=argtype,
                ))

            # Mako is unable to use *vararg and *kwarg, so we just stub in
            # a whole bunch of additional arguments.
            if argname == 'vararg':
                for j in range(5):
                    argname = 'vararg_%i' % j
                    argument_names.append(argname)
                    argument_names_.append(argname)
                    argument_defaults.append('%s=%s' % (argname, None))
                break

            argument_names.append(argname)
            argument_names_.append(argname_)
            argument_defaults.append('%s=%s' % (argname_, default))

        arguments_default_values = ', '.join(argument_defaults)
        arguments_comma_separated = ', '.join(argument_names_)

        return_type = str(function.type) + ('*' * function.derefcnt)
        arg_docs = '\n'.join(arg_docs)

        template_variables = {
            'name': name,
            'arg_docs': arg_docs,
            'syscalls': syscalls,
            'arguments_default_values': arguments_default_values,
            'arguments_comma_separated': arguments_comma_separated,
            'return_type': return_type,
            'string_arguments': string_arguments,
            'array_arguments': array_arguments,
            'argument_names': argument_names,
        }

        lines = [
            HEADER,
            DOCSTRING.format(**template_variables),
            ARGUMENTS.format(**template_variables),
            CALL.format(**template_variables)
        ]

        if keyword.iskeyword(name):
            name += '_'
        with open(os.path.join(target, name + '.asm'), 'wt') as f:
            f.write('\n'.join(map(str.strip, lines)) + '\n')


if __name__ == '__main__':
    p = argparse.ArgumentParser()
    p.add_argument('target_directory')
    args = p.parse_args()
    main(args.target_directory)