133 lines
3.4 KiB
Python
133 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|
#
|
|
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
|
#
|
|
|
|
from abc import abstractmethod
|
|
|
|
from unicorn import Uc
|
|
from unicorn.unicorn import UcContext
|
|
from capstone import Cs
|
|
from keystone import Ks
|
|
|
|
from qiling import Qiling
|
|
from qiling.const import QL_ARCH, QL_ENDIAN
|
|
from .register import QlRegisterManager
|
|
from .utils import QlArchUtils
|
|
|
|
|
|
class QlArch:
|
|
type: QL_ARCH
|
|
bits: int
|
|
|
|
def __init__(self, ql: Qiling):
|
|
self.ql = ql
|
|
self.utils = QlArchUtils(ql)
|
|
|
|
@property
|
|
@abstractmethod
|
|
def uc(self) -> Uc:
|
|
"""Get unicorn instance bound to arch.
|
|
"""
|
|
|
|
pass
|
|
|
|
@property
|
|
@abstractmethod
|
|
def regs(self) -> QlRegisterManager:
|
|
"""Architectural registers.
|
|
"""
|
|
|
|
pass
|
|
|
|
@property
|
|
def pointersize(self) -> int:
|
|
return self.bits // 8
|
|
|
|
def stack_push(self, value: int) -> int:
|
|
"""Push a value onto the architectural stack.
|
|
|
|
Args:
|
|
value: a numeric value to push
|
|
|
|
Returns: the top of stack after pushing the value
|
|
"""
|
|
|
|
self.regs.arch_sp -= self.pointersize
|
|
self.ql.mem.write_ptr(self.regs.arch_sp, value)
|
|
|
|
return self.regs.arch_sp
|
|
|
|
def stack_pop(self) -> int:
|
|
"""Pop a value from the architectural stack.
|
|
|
|
Returns: the value at the top of stack
|
|
"""
|
|
|
|
data = self.ql.mem.read_ptr(self.regs.arch_sp)
|
|
self.regs.arch_sp += self.pointersize
|
|
|
|
return data
|
|
|
|
def stack_read(self, offset: int) -> int:
|
|
"""Peek the architectural stack at a specified offset from its top, without affecting
|
|
the top of the stack.
|
|
|
|
Note that this operation violates the FIFO property of the stack and may be used cautiously.
|
|
|
|
Args:
|
|
offset: offset in bytes from the top of the stack, not necessarily aligned to the
|
|
native stack item size. the offset may be either positive or netagive, where
|
|
a 0 value means retrieving the value at the top of the stack
|
|
|
|
Returns: the value at the specified address
|
|
"""
|
|
|
|
return self.ql.mem.read_ptr(self.regs.arch_sp + offset)
|
|
|
|
def stack_write(self, offset: int, value: int) -> None:
|
|
"""Write a value to the architectural stack at a specified offset from its top, without
|
|
affecting the top of the stack.
|
|
|
|
Note that this operation violates the FIFO property of the stack and may be used cautiously.
|
|
|
|
Args:
|
|
offset: offset in bytes from the top of the stack, not necessarily aligned to the
|
|
native stack item size. the offset may be either positive or netagive, where
|
|
a 0 value means overwriting the value at the top of the stack
|
|
"""
|
|
|
|
self.ql.mem.write_ptr(self.regs.arch_sp + offset, value)
|
|
|
|
# Unicorn's CPU state save
|
|
def save(self) -> UcContext:
|
|
return self.uc.context_save()
|
|
|
|
# Unicorn's CPU state restore method
|
|
def restore(self, saved_context: UcContext):
|
|
self.uc.context_restore(saved_context)
|
|
|
|
@property
|
|
@abstractmethod
|
|
def disassembler(self) -> Cs:
|
|
"""Get disassembler instance bound to arch.
|
|
"""
|
|
|
|
pass
|
|
|
|
@property
|
|
@abstractmethod
|
|
def assembler(self) -> Ks:
|
|
"""Get assembler instance bound to arch.
|
|
"""
|
|
|
|
pass
|
|
|
|
@property
|
|
@abstractmethod
|
|
def endian(self) -> QL_ENDIAN:
|
|
"""Get processor endianess.
|
|
"""
|
|
|
|
pass
|