blob: 866eba710550aabd24cab1b2b181b8401df04e79 [file] [log] [blame]
from io import StringIO
import sys
class RawBytes(object):
"""A sequence of literal bytes to appear in an assembly language template."""
def __init__(self, *bytes):
self.bytes = bytes
def __len__(self):
return len(self.bytes)
class Field(object):
"""A variable field of bytes."""
def __init__(self, name, byte_length):
self.name = name
self.byte_length = byte_length
def __len__(self):
return self.byte_length
def c_type(self):
types = { 8: 'uint64_t', 4: 'uint32_t', 2: 'uint16_t', 1: 'uint8_t' }
return types[self.byte_length]
class ShiftField(object):
"""A field embedded at some bit shift offset in another object."""
def __init__(self, parent, shift, name, byte_length):
self.parent = parent
self.shift = shift
self.name = name
self.byte_length = byte_length
def __len__(self):
return len(self.parent)
def c_type(self):
types = { 8: 'uint64_t', 4: 'uint32_t', 2: 'uint16_t', 1: 'uint8_t' }
return types[self.byte_length]
def patch_c_type(self):
types = { 8: 'uint64_t', 4: 'uint32_t', 2: 'uint16_t', 1: 'uint8_t' }
return types[len(self.parent)]
class AssemblyTemplate(object):
"""A sequence of RawBytes and Field objects, which can be used to verify
that a given sequence of assembly instructions matches the RawBytes while
pulling out the Field values for inspection. Or for creating custom
assembly stubs, filling out Fields with runtime-determined values."""
def __init__(self, *chunks):
# Merge consecutive RawBytes elements together for efficiency of
# matching and for simplicity of template expansion.
merged_chunks = []
current_raw_bytes = []
for c in chunks:
if isinstance(c, Field) or isinstance(c, ShiftField):
# Push any raw bytes before this.
if current_raw_bytes:
merged_chunks.append(RawBytes(*current_raw_bytes))
current_raw_bytes = []
merged_chunks.append(c)
else:
current_raw_bytes.extend(c.bytes)
# Merge in trailing raw bytes.
if current_raw_bytes:
merged_chunks.append(RawBytes(*current_raw_bytes))
self.chunks = merged_chunks
def fields(self):
return [c for c in self.chunks if (isinstance(c, Field) or isinstance(c, ShiftField))]
def bytes(self):
bytes = []
for c in self.chunks:
if isinstance(c, Field):
bytes.extend([0] * len(c))
elif isinstance(c, ShiftField):
bytes.extend(c.parent.bytes)
else:
bytes.extend(c.bytes)
return bytes
templates = {
'X86SysenterVsyscallSyscallHook': AssemblyTemplate(
RawBytes(0xe9), # jmp $syscall_hook_trampoline
Field('syscall_hook_trampoline', 4),
),
'X86SyscallStubExtendedJump': AssemblyTemplate(
# This code must match the stubs in syscall_hook.S.
RawBytes(0x89, 0x25, 0x08, 0x10, 0x00, 0x70), # movl %esp,(stub_scratch_1)
RawBytes(0xBC, 0x12, 0x10, 0x00, 0x70), # mov $saved_flags+2,%esp
RawBytes(0x66, 0x9c), # pushfw
RawBytes(0x8b, 0x25, 0x08, 0x10, 0x00, 0x70), # movq (stub_scratch_1),%esp
RawBytes(0xFF, 0x05, 0x0c, 0x10, 0x00, 0x70), # incl (alt_stack_nesting_level)
RawBytes(0x83, 0x3c, 0x25, 0x0c, 0x10, 0x00, 0x70, 0x01), # cmpl 1,(alt_stack_nesting_level)
RawBytes(0x75, 0x06), # jne dont_switch
RawBytes(0x8b, 0x25, 0x00, 0x10, 0x00, 0x70), # movl (syscallbuf_stub_alt_stack),%esp
# dont_switch:
RawBytes(0xff, 0x35, 0x08, 0x10, 0x00, 0x70), # pushl (stub_scratch_1)
RawBytes(0x68), # pushl $return_addr
Field('return_addr', 4),
RawBytes(0xe9), # jmp $trampoline_relative_addr
Field('trampoline_relative_addr', 4)
),
'X86TrapInstructionStubExtendedJump': AssemblyTemplate(
# This code must match the stubs in syscall_hook.S.
RawBytes(0x89, 0x25, 0x08, 0x10, 0x00, 0x70), # movl %esp,(stub_scratch_1)
RawBytes(0xBC, 0x12, 0x10, 0x00, 0x70), # mov $saved_flags+2,%esp
RawBytes(0x66, 0x9c), # pushfw
RawBytes(0x8b, 0x25, 0x08, 0x10, 0x00, 0x70), # movq (stub_scratch_1),%esp
RawBytes(0xFF, 0x05, 0x0c, 0x10, 0x00, 0x70), # incl (alt_stack_nesting_level)
RawBytes(0x83, 0x3c, 0x25, 0x0c, 0x10, 0x00, 0x70, 0x01), # cmpl 1,(alt_stack_nesting_level)
RawBytes(0x75, 0x06), # jne dont_switch
RawBytes(0x8b, 0x25, 0x00, 0x10, 0x00, 0x70), # movl (syscallbuf_stub_alt_stack),%esp
# dont_switch:
RawBytes(0xff, 0x35, 0x08, 0x10, 0x00, 0x70), # pushl (stub_scratch_1)
RawBytes(0x68), # pushl $return_addr
Field('return_addr', 4),
RawBytes(0xb8), # movl $fake_syscall_no,%eax
Field('fake_syscall_no', 4),
RawBytes(0xe9), # jmp $trampoline_relative_addr
Field('trampoline_relative_addr', 4)
),
'X86SyscallStubRestore': AssemblyTemplate(
RawBytes(0xe9), # jmp $trampoline_relative_addr
Field('trampoline_relative_addr', 4)
),
'X64CallMonkeypatch': AssemblyTemplate(
RawBytes(0xe8), # call $relative_addr
Field('relative_addr', 4),
),
'X64JumpMonkeypatch': AssemblyTemplate(
RawBytes(0xe9), # jmp $relative_addr
Field('relative_addr', 4),
),
'X64SyscallStubExtendedJump': AssemblyTemplate(
# This code must match the stubs in syscall_hook.S.
RawBytes(0x48, 0x89, 0x24, 0x25, 0x10, 0x10, 0x00, 0x70), # movq %rsp,(stub_scratch_1)
RawBytes(0xBC, 0x1e, 0x10, 0x00, 0x70), # mov $saved_flags+2,%esp
RawBytes(0x66, 0x9c), # pushfw
RawBytes(0x48, 0x8b, 0x24, 0x25, 0x10, 0x10, 0x00, 0x70), # movq (stub_scratch_1),%rsp
RawBytes(0xFF, 0x04, 0x25, 0x18, 0x10, 0x00, 0x70), # incl (alt_stack_nesting_level)
RawBytes(0x83, 0x3c, 0x25, 0x18, 0x10, 0x00, 0x70, 0x01), # cmpl 1,(alt_stack_nesting_level)
RawBytes(0x75, 0x0a), # jne dont_switch
RawBytes(0x48, 0x8b, 0x24, 0x25, 0x00, 0x10, 0x00, 0x70), # movq (syscallbuf_stub_alt_stack),%rsp
RawBytes(0xeb, 0x07), # jmp after_adjust
# dont_switch:
RawBytes(0x48, 0x81, 0xec, 0x00, 0x01, 0x00, 0x00), # subq $256, %rsp
# after adjust
RawBytes(0xff, 0x34, 0x25, 0x10, 0x10, 0x00, 0x70), # pushq (stub_scratch_1)
RawBytes(0x50), # pushq rax (just to make space for the next 2 instructions)
RawBytes(0xc7, 0x04, 0x24), # movl $return_addr_lo,(%rsp)
Field('return_addr_lo', 4),
RawBytes(0xc7, 0x44, 0x24, 0x04), # movl $return_addr_hi,(%rsp+4)
Field('return_addr_hi', 4),
RawBytes(0xff, 0x25, 0x00, 0x00, 0x00, 0x00), # jmp *0(%rip)
Field('jump_target', 8),
),
'X64TrapInstructionStubExtendedJump': AssemblyTemplate(
# This code must match the stubs in syscall_hook.S.
RawBytes(0x48, 0x89, 0x24, 0x25, 0x10, 0x10, 0x00, 0x70), # movq %rsp,(stub_scratch_1)
RawBytes(0xBC, 0x1e, 0x10, 0x00, 0x70), # mov $saved_flags+2,%esp
RawBytes(0x66, 0x9c), # pushfw
RawBytes(0x48, 0x8b, 0x24, 0x25, 0x10, 0x10, 0x00, 0x70), # movq (stub_scratch_1),%rsp
RawBytes(0xFF, 0x04, 0x25, 0x18, 0x10, 0x00, 0x70), # incl (alt_stack_nesting_level)
RawBytes(0x83, 0x3c, 0x25, 0x18, 0x10, 0x00, 0x70, 0x01), # cmpl 1,(alt_stack_nesting_level)
RawBytes(0x75, 0x0a), # jne dont_switch
RawBytes(0x48, 0x8b, 0x24, 0x25, 0x00, 0x10, 0x00, 0x70), # movq (syscallbuf_stub_alt_stack),%rsp
RawBytes(0xeb, 0x07), # jmp after_adjust
# dont_switch:
RawBytes(0x48, 0x81, 0xec, 0x00, 0x01, 0x00, 0x00), # subq $256, %rsp
# after adjust
RawBytes(0xff, 0x34, 0x25, 0x10, 0x10, 0x00, 0x70), # pushq (stub_scratch_1)
RawBytes(0x50), # pushq rax (just to make space for the next 2 instructions)
RawBytes(0xc7, 0x04, 0x24), # movl $return_addr_lo,(%rsp)
Field('return_addr_lo', 4),
RawBytes(0xc7, 0x44, 0x24, 0x04), # movl $return_addr_hi,(%rsp+4)
Field('return_addr_hi', 4),
RawBytes(0xb8), # movl $fake_syscall_no,%eax
Field('fake_syscall_no', 4),
RawBytes(0xff, 0x25, 0x00, 0x00, 0x00, 0x00), # jmp *0(%rip)
Field('jump_target', 8),
),
'X64SyscallStubRestore': AssemblyTemplate(
RawBytes(0xff, 0x25, 0x00, 0x00, 0x00, 0x00), # jmp *0(%rip)
Field('return_addr', 8),
),
'X64DLRuntimeResolve': AssemblyTemplate(
RawBytes(0x53), # push %rbx
RawBytes(0x48, 0x89, 0xe3), # mov %rsp,%rbx
RawBytes(0x48, 0x83, 0xe4, 0xf0), # and $0xfffffffffffffff0,%rsp
),
'X64DLRuntimeResolve2': AssemblyTemplate(
RawBytes(0x53), # push %rbx
RawBytes(0x48, 0x89, 0xe3), # mov %rsp,%rbx
RawBytes(0x48, 0x83, 0xe4, 0xc0), # and $0xffffffffffffffc0,%rsp
),
'X64DLRuntimeResolvePrelude': AssemblyTemplate(
RawBytes(0xd9, 0x74, 0x24, 0xe0), # fstenv -32(%rsp)
RawBytes(0x48, 0xc7, 0x44, 0x24, 0xf4, 0x00, 0x00, 0x00, 0x00), # movq $0,-12(%rsp)
RawBytes(0xd9, 0x64, 0x24, 0xe0), # fldenv -32(%rsp)
RawBytes(0x48, 0x87, 0x1c, 0x24), # xchg (%rsp),%rbx
# r11 is destroyed anyways by _dl_runtime_resolve, so we can use it here.
RawBytes(0x49, 0x89, 0xdb), # mov %rbx,%r11
RawBytes(0x48, 0x89, 0xe3), # mov %rsp,%rbx
RawBytes(0x48, 0x83, 0xe4, 0xc0), # and $0xffffffffffffffc0,%rsp
RawBytes(0x41, 0x53), # push %r11
RawBytes(0xc3), # ret
),
'X64EndBr': AssemblyTemplate(
RawBytes(0xf3, 0x0f, 0x1e, 0xfa)
),
'X86EndBr': AssemblyTemplate(
RawBytes(0xf3, 0x0f, 0x1e, 0xfb)
),
'X64VSyscallEntry': AssemblyTemplate(
RawBytes(0x48, 0xc7, 0xc0), # movq $[addr], %rax
Field('addr', 4),
RawBytes(0xff, 0xd0) # callq *%rax
),
'X64VSyscallReplacement': AssemblyTemplate(
RawBytes(0x48, 0xc7, 0xc0), # movq $[syscallno], %rax
Field('syscallno', 4),
RawBytes(0x0f, 0x05) # syscall
),
}
def byte_array_name(name):
return '%s_bytes' % name
def generate_match_method(byte_array, template):
s = StringIO()
fields = template.fields()
field_types = [f.c_type() for f in fields]
field_names = [f.name for f in fields]
args = ', ' + ', '.join("%s* %s" % (t, n) for t, n in zip(field_types, field_names)) \
if fields else ''
s.write(' static bool match(const uint8_t* buffer %s) {\n' % (args,))
offset = 0
for chunk in template.chunks:
if isinstance(chunk, Field):
field_name = chunk.name
s.write(' memcpy(%s, &buffer[%d], sizeof(*%s));\n'
% (field_name, offset, field_name))
elif isinstance(chunk, ShiftField):
s.write(' (void)%s;' % chunk.name)
s.write(' assert(0 && "Matching not implemented for ShiftField");')
else:
s.write(' if (memcmp(&buffer[%d], &%s[%d], %d) != 0) { return false; }\n'
% (offset, byte_array, offset, len(chunk)))
offset += len(chunk)
s.write(' return true;\n')
s.write(' }')
return s.getvalue()
def generate_substitute_chunk(s, chunk, byte_array, offset):
if isinstance(chunk, Field):
field_name = chunk.name
s.write(' memcpy(&buffer[%d], &%s, sizeof(%s));\n'
% (offset, field_name, field_name))
elif isinstance(chunk, ShiftField):
generate_substitute_chunk(s, chunk.parent, byte_array, offset);
typ = chunk.patch_c_type()
field_name = chunk.name
s.write(' *((%s*)&buffer[%d]) |= (((%s)%s)<<%d);\n'
% (typ, offset, typ, field_name, chunk.shift))
else:
s.write(' memcpy(&buffer[%d], &%s[%d], %d);\n'
% (offset, byte_array, offset, len(chunk)))
offset += len(chunk)
return offset
def generate_substitute_method(byte_array, template):
s = StringIO()
fields = template.fields()
field_types = [f.c_type() for f in fields]
field_names = [f.name for f in fields]
args = ', ' + ', '.join("%s %s" % (t, n) for t, n in zip(field_types, field_names)) \
if fields else ''
s.write(' static void substitute(uint8_t* buffer %s) {\n' % (args,))
offset = 0
for chunk in template.chunks:
offset = generate_substitute_chunk(s, chunk, byte_array, offset)
s.write(' }')
return s.getvalue()
def generate_field_end_methods(byte_array, template):
s = StringIO()
offset = 0
for chunk in template.chunks:
offset += len(chunk)
if isinstance(chunk, Field):
s.write(' static const size_t %s_end = %d;\n' % (chunk.name, offset))
return s.getvalue()
def generate_size_member(byte_array):
s = StringIO()
s.write(' static const size_t size = sizeof(%s);' % byte_array)
return s.getvalue()
def generate(f):
# Raw bytes.
for name, template in templates.items():
bytes = template.bytes()
f.write('static const uint8_t %s[] = { %s };\n'
% (byte_array_name(name), ', '.join(['0x%x' % b for b in bytes])))
f.write('\n')
# Objects representing assembly templates.
for name, template in templates.items():
byte_array = byte_array_name(name)
f.write("""class %(class_name)s {
public:
%(match_method)s
%(substitute_method)s
%(field_end_methods)s
%(size_member)s
};
""" % { 'class_name': name,
'match_method': generate_match_method(byte_array, template),
'substitute_method': generate_substitute_method(byte_array, template),
'field_end_methods': generate_field_end_methods(byte_array, template),
'size_member': generate_size_member(byte_array), })
f.write('\n\n')