Skip to content

Commit

Permalink
x86 FXSAVE & FXRSTOR support (#2511)
Browse files Browse the repository at this point in the history
* Add SMT simplifications for bitvec subtraction

* Add X86 support for FXSAVE and FXRSTOR

* Unicorn emulator: ignore floating point registers than aren't yet supported in unicorn

* Emulator: Also ignore MXCSR_MASK register

* Add logic to translate floating point registers values from (mantissa,exponent) to bitfield
  • Loading branch information
Boyan-MILANOV committed Feb 4, 2022
1 parent 9321100 commit bf2fba3
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 13 deletions.
204 changes: 197 additions & 7 deletions manticore/native/cpu/x86.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,13 @@ class AMD64RegFile(RegisterFile):
"TOP": Regspec("FPSW", int, 11, 3, False),
"FPTAG": Regspec("FPTAG", int, 0, 16, False),
"FPCW": Regspec("FPCW", int, 0, 16, False),
"FOP": Regspec("FOP", int, 0, 11, False),
"FIP": Regspec("FIP", int, 0, 64, False),
"FCS": Regspec("FCS", int, 0, 16, False),
"FDP": Regspec("FDP", int, 0, 64, False),
"FDS": Regspec("FDS", int, 0, 16, False),
"MXCSR": Regspec("MXCSR", int, 0, 32, False),
"MXCSR_MASK": Regspec("MXCSR_MASK", int, 0, 32, False),
"CF": Regspec("CF", bool, 0, 1, False),
"PF": Regspec("PF", bool, 0, 1, False),
"AF": Regspec("AF", bool, 0, 1, False),
Expand Down Expand Up @@ -355,6 +362,13 @@ class AMD64RegFile(RegisterFile):
"TOP": ("FPSW",),
"FPCW": (),
"FPTAG": (),
"FOP": (),
"FIP": (),
"FCS": (),
"FDP": (),
"FDS": (),
"MXCSR": (),
"MXCSR_MASK": (),
"FP0": (),
"FP1": (),
"FP2": (),
Expand Down Expand Up @@ -495,6 +509,13 @@ class AMD64RegFile(RegisterFile):
"FPSW",
"FPCW",
"FPTAG",
"FOP",
"FIP",
"FCS",
"FDP",
"FDS",
"MXCSR",
"MXCSR_MASK",
)

def __init__(self, *args, **kwargs):
Expand Down Expand Up @@ -554,7 +575,18 @@ def __init__(self, *args, **kwargs):
for reg in ("FP0", "FP1", "FP2", "FP3", "FP4", "FP5", "FP6", "FP7"):
self._registers[reg] = (0, 0)

for reg in ("FPSW", "FPTAG", "FPCW"):
for reg in (
"FPSW",
"FPTAG",
"FPCW",
"FOP",
"FIP",
"FCS",
"FDP",
"FDS",
"MXCSR",
"MXCSR_MASK",
):
self._registers[reg] = 0

self._cache = {}
Expand Down Expand Up @@ -626,7 +658,14 @@ def _get_flag(self, register_id, register_size, offset, size):
def _set_float(self, register_id, register_size, offset, size, reset, value):
assert size == 80
assert offset == 0
if not isinstance(value, tuple): # Add decimal here?
# Translate int bitfield into a floating point value according
# to IEEE 754 standard, 80-bit double extended precision
if isinstance(value, int):
value &= 0xFFFFFFFFFFFFFFFFFFFF # 80-bit mask
exponent = value >> 64 # Exponent is the 16 higher bits
mantissa = value & 0xFFFFFFFFFFFFFFFF # Mantissa is the lower 64 bits
value = (mantissa, exponent)
elif not isinstance(value, tuple):
raise TypeError
self._registers[register_id] = value
return value
Expand All @@ -637,7 +676,7 @@ def _get_float(self, register_id, register_size, offset, size):
return self._registers[register_id]

def _get_flags(self, reg):
""" Build EFLAGS/RFLAGS from flags """
"""Build EFLAGS/RFLAGS from flags"""

def make_symbolic(flag_expr):
register_size = 32 if reg == "EFLAGS" else 64
Expand All @@ -662,7 +701,7 @@ def make_symbolic(flag_expr):
return res

def _set_flags(self, reg, res):
""" Set individual flags from a EFLAGS/RFLAGS value """
"""Set individual flags from a EFLAGS/RFLAGS value"""
# assert sizeof (res) == 32 if reg == 'EFLAGS' else 64
for flag, offset in self._flags.items():
self.write(flag, Operators.EXTRACT(res, offset, 1))
Expand Down Expand Up @@ -731,7 +770,7 @@ def __copy__(self):

# Operand Wrapper
class AMD64Operand(Operand):
""" This class deals with capstone X86 operands """
"""This class deals with capstone X86 operands"""

def __init__(self, cpu: Cpu, op):
super().__init__(cpu, op)
Expand Down Expand Up @@ -883,7 +922,7 @@ def pop(cpu, size):
# The instruction cache must be invalidated after an executable
# page was changed or removed or added
def invalidate_cache(cpu, address, size):
""" remove decoded instruction from instruction cache """
"""remove decoded instruction from instruction cache"""
cache = cpu.instruction_cache
for offset in range(size):
if address + offset in cache:
Expand All @@ -899,6 +938,23 @@ def canonicalize_instruction_name(self, instruction):
name = OP_NAME_MAP.get(name, name)
return name

def read_register_as_bitfield(self, name):
"""Read a register and return its value as a bitfield.
- if the register holds a bitvector, the bitvector object is returned.
- if the register holds a concrete value (int/float) it is returned as
a bitfield matching its representation in memory
This is mainly used to be able to write floating point registers to
memory.
"""
value = self.read_register(name)
if isinstance(value, tuple):
# Convert floating point to bitfield according to IEEE 754
# (16-bits exponent).(64-bits mantissa)
mantissa, exponent = value
value = mantissa + (exponent << 64)
return value

#
# Instruction Implementations
#
Expand Down Expand Up @@ -5676,6 +5732,37 @@ def sem_SYSCALL(cpu):
cpu.R11 = cpu.RFLAGS
raise Syscall()

def generic_FXSAVE(cpu, dest, reg_layout):
"""
Saves the current state of the x87 FPU, MMX technology, XMM, and
MXCSR registers to a 512-byte memory location specified in the
destination operand.
The content layout of the 512 byte region depends
on whether the processor is operating in non-64-bit operating modes
or 64-bit sub-mode of IA-32e mode
"""
addr = dest.address()
for offset, reg, size in reg_layout:
cpu.write_int(addr + offset, cpu.read_register_as_bitfield(reg), size)

def generic_FXRSTOR(cpu, dest, reg_layout):
"""
Reloads the x87 FPU, MMX technology, XMM, and MXCSR registers from
the 512-byte memory image specified in the source operand. This data should
have been written to memory previously using the FXSAVE instruction, and in
the same format as required by the operating modes. The first byte of the data
should be located on a 16-byte boundary.
There are three distinct layouts of the FXSAVE state map:
one for legacy and compatibility mode, a second
format for 64-bit mode FXSAVE/FXRSTOR with REX.W=0, and the third format is for
64-bit mode with FXSAVE64/FXRSTOR64
"""
addr = dest.address()
for offset, reg, size in reg_layout:
cpu.write_register(reg, cpu.read_int(addr + offset, size))

@instruction
def SYSCALL(cpu):
"""
Expand Down Expand Up @@ -6518,6 +6605,44 @@ class AMD64Cpu(X86Cpu):
arch = cs.CS_ARCH_X86
mode = cs.CS_MODE_64

# CPU specific instruction behaviour
FXSAVE_layout = [
(0, "FPCW", 16),
(2, "FPSW", 16),
(4, "FPTAG", 8),
(6, "FOP", 16),
(8, "FIP", 32),
(12, "FCS", 16),
(16, "FDP", 32),
(20, "FDS", 16),
(24, "MXCSR", 32),
(28, "MXCSR_MASK", 32),
(32, "FP0", 80),
(48, "FP1", 80),
(64, "FP2", 80),
(80, "FP3", 80),
(96, "FP4", 80),
(112, "FP5", 80),
(128, "FP6", 80),
(144, "FP7", 80),
(160, "XMM0", 128),
(176, "XMM1", 128),
(192, "XMM2", 128),
(208, "XMM3", 128),
(224, "XMM4", 128),
(240, "XMM5", 128),
(256, "XMM6", 128),
(272, "XMM7", 128),
(288, "XMM8", 128),
(304, "XMM9", 128),
(320, "XMM10", 128),
(336, "XMM11", 128),
(352, "XMM12", 128),
(368, "XMM13", 128),
(384, "XMM14", 128),
(400, "XMM15", 128),
]

def __init__(self, memory: Memory, *args, **kwargs):
"""
Builds a CPU model.
Expand Down Expand Up @@ -6633,6 +6758,14 @@ def XLATB(cpu):
"""
cpu.AL = cpu.read_int(cpu.RBX + Operators.ZEXTEND(cpu.AL, 64), 8)

@instruction
def FXSAVE(cpu, dest):
return cpu.generic_FXSAVE(dest, AMD64Cpu.FXSAVE_layout)

@instruction
def FXRSTOR(cpu, src):
return cpu.generic_FXRSTOR(src, AMD64Cpu.FXSAVE_layout)


class I386Cpu(X86Cpu):
# Config
Expand All @@ -6642,6 +6775,36 @@ class I386Cpu(X86Cpu):
arch = cs.CS_ARCH_X86
mode = cs.CS_MODE_32

# CPU specific instruction behaviour
FXSAVE_layout = [
(0, "FPCW", 16),
(2, "FPSW", 16),
(4, "FPTAG", 8),
(6, "FOP", 16),
(8, "FIP", 32),
(12, "FCS", 16),
(16, "FDP", 32),
(20, "FDS", 16),
(24, "MXCSR", 32),
(28, "MXCSR_MASK", 32),
(32, "FP0", 80),
(48, "FP1", 80),
(64, "FP2", 80),
(80, "FP3", 80),
(96, "FP4", 80),
(112, "FP5", 80),
(128, "FP6", 80),
(144, "FP7", 80),
(160, "XMM0", 128),
(176, "XMM1", 128),
(192, "XMM2", 128),
(208, "XMM3", 128),
(224, "XMM4", 128),
(240, "XMM5", 128),
(256, "XMM6", 128),
(272, "XMM7", 128),
]

def __init__(self, memory: Memory, *args, **kwargs):
"""
Builds a CPU model.
Expand Down Expand Up @@ -6708,7 +6871,26 @@ def canonical_registers(self):
regs = ["EAX", "ECX", "EDX", "EBX", "ESP", "EBP", "ESI", "EDI", "EIP"]
regs.extend(["CS", "DS", "ES", "SS", "FS", "GS"])
regs.extend(
["FP0", "FP1", "FP2", "FP3", "FP4", "FP5", "FP6", "FP7", "FPCW", "FPSW", "FPTAG"]
[
"FP0",
"FP1",
"FP2",
"FP3",
"FP4",
"FP5",
"FP6",
"FP7",
"FPCW",
"FPSW",
"FPTAG",
"FOP",
"FIP",
"FCS",
"FDP",
"FDS",
"MXCSR",
"MXCSR_MASK",
]
)
regs.extend(
[
Expand Down Expand Up @@ -6760,3 +6942,11 @@ def XLATB(cpu):
:param dest: destination operand.
"""
cpu.AL = cpu.read_int(cpu.EBX + Operators.ZEXTEND(cpu.AL, 32), 8)

@instruction
def FXSAVE(cpu, dest):
return cpu.generic_FXSAVE(dest, I386Cpu.FXSAVE_layout)

@instruction
def FXRSTOR(cpu, src):
return cpu.generic_FXRSTOR(src, I386Cpu.FXSAVE_layout)
19 changes: 15 additions & 4 deletions manticore/utils/emulate.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ def __init__(self, cpu):
self._cpu = cpu
self._mem_delta = {}
self.flag_registers = {"CF", "PF", "AF", "ZF", "SF", "IF", "DF", "OF"}
# Registers to ignore when translating manticore context to unicorn
self.ignore_registers = {"FIP", "FOP", "FDS", "FCS", "FDP", "MXCSR_MASK"}
self.write_backs_disabled = False
self._stop_at = None
# Holds key of range (addr, addr + size) and value of permissions
Expand Down Expand Up @@ -144,6 +146,9 @@ def copy_memory(self, address: int, size: int):

def load_state_from_manticore(self) -> None:
for reg in self.registers:
# Ignore registers that aren't supported by unicorn
if reg in self.ignore_registers:
continue
val = self._cpu.read_register(reg)
if issymbolic(val):
from ..native.cpu.abstractcpu import ConcretizeRegister
Expand Down Expand Up @@ -251,7 +256,7 @@ def unmap_memory_callback(self, start, size):
logger.debug(f"\tParent map(s) {parent_map}")

def protect_memory_callback(self, start, size, perms):
""" Set memory protections in Unicorn correctly """
"""Set memory protections in Unicorn correctly"""
logger.debug(f"Changing permissions on {start:#x}:{start+size:#x} to '{perms}'")
self._emu.mem_protect(start, size, convert_permissions(perms))

Expand Down Expand Up @@ -399,6 +404,9 @@ def sync_unicorn_to_manticore(self):
"""
self.write_backs_disabled = True
for reg in self.registers:
# Ignore registers that aren't supported by unicorn
if reg in self.ignore_registers:
continue
val = self._emu.reg_read(self._to_unicorn_id(reg))
self._cpu.write_register(reg, val)
if len(self._mem_delta) > 0:
Expand All @@ -410,7 +418,7 @@ def sync_unicorn_to_manticore(self):
self._mem_delta = {}

def write_back_memory(self, where, expr, size):
""" Copy memory writes from Manticore back into Unicorn in real-time """
"""Copy memory writes from Manticore back into Unicorn in real-time"""
if self.write_backs_disabled:
return
if type(expr) is bytes:
Expand Down Expand Up @@ -441,7 +449,10 @@ def write_back_memory(self, where, expr, size):
)

def write_back_register(self, reg, val):
""" Sync register state from Manticore -> Unicorn"""
"""Sync register state from Manticore -> Unicorn"""
# Ignore registers that aren't supported by unicorn
if reg in self.ignore_registers:
return
if self.write_backs_disabled:
return
if issymbolic(val):
Expand All @@ -453,7 +464,7 @@ def write_back_register(self, reg, val):
self._emu.reg_write(self._to_unicorn_id(reg), val)

def update_segment(self, selector, base, size, perms):
""" Only useful for setting FS right now. """
"""Only useful for setting FS right now."""
logger.debug("Updating selector %s to 0x%02x (%s bytes) (%s)", selector, base, size, perms)
self.write_back_register("FS", selector)
self.write_back_register("FS_BASE", base)
Expand Down

0 comments on commit bf2fba3

Please sign in to comment.