Skip to content

Commit

Permalink
Merge pull request #69 from sledgeh4w/split-out-instruction-and-fixup
Browse files Browse the repository at this point in the history
Split out instruction and fixup modules
  • Loading branch information
sledgeh4w authored Apr 27, 2024
2 parents 85fa04e + c9a197b commit a148096
Show file tree
Hide file tree
Showing 10 changed files with 397 additions and 435 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
include:
- { name: Linux, python: '3.10', os: ubuntu-latest }
- { name: Windows, python: '3.10', os: windows-latest }
- { name: Mac, python: '3.10', os: macos-latest }
- { name: Mac, python: '3.10', os: macos-13 }
- { name: '3.11', python: '3.11', os: ubuntu-latest }
- { name: '3.9', python: '3.9', os: ubuntu-latest }
- { name: '3.8', python: '3.8', os: ubuntu-latest }
Expand Down
120 changes: 13 additions & 107 deletions src/chomper/core.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import logging
import re
from functools import wraps
from typing import Callable, Dict, List, Optional, Tuple, Union

Expand All @@ -21,6 +20,7 @@
from . import const
from .arch import arm_arch, arm64_arch
from .exceptions import EmulatorCrashedException, SymbolMissingException
from .instruction import AutomicInstruction
from .memory import MemoryManager
from .types import Module, Symbol
from .log import get_logger
Expand All @@ -42,7 +42,7 @@ class Chomper:
enable_objc: Enable Objective-C runtime of iOS.
enable_ui_kit: Enable UIKit framework of iOS.
trace_inst: Print log when any instruction is executed. The emulator will
call disassembler in real time to display the assembly instructions,
call disassembler in real time to output the assembly instructions,
so this will slow down the emulation.
trace_symbol_calls: Print log when any symbol is called.
"""
Expand Down Expand Up @@ -379,12 +379,13 @@ def crash(self, message: str, from_exc: Optional[Exception] = None):

raise EmulatorCrashedException(message)

def trace_symbol_call_callback(self, *args):
def trace_symbol_call_callback(
self, uc: Uc, address: int, size: int, user_data: dict
):
"""Trace symbol call."""
user_data = args[-1]
symbol = user_data["symbol"]

if self.arch == const.ARCH_ARM:
if self.arch == arm_arch:
ret_addr = self.uc.reg_read(arm_const.UC_ARM_REG_LR)
else:
ret_addr = self.uc.reg_read(arm64_const.UC_ARM64_REG_LR)
Expand Down Expand Up @@ -427,14 +428,18 @@ def _interrupt_callback(self, uc: Uc, intno: int, user_data: dict):
return

elif intno in (1, 4):
# Handle cpu exceptions
address = self.uc.reg_read(self.arch.reg_pc)
inst = next(self.cs.disasm_lite(uc.mem_read(address, 4), 0))
result = self._handle_atomic_inst(inst)
code = uc.mem_read(address, 4)

if result:
try:
AutomicInstruction(self, code).execute()
self.uc.reg_write(arm64_const.UC_ARM64_REG_PC, address + 4)
return

except ValueError:
pass

self.crash("Unhandled interruption %s" % (intno,))

def _dispatch_syscall(self):
Expand All @@ -453,105 +458,6 @@ def _dispatch_syscall(self):

self.crash("Unhandled system call")

def _handle_atomic_inst(self, inst: Tuple[int, int, str, str]) -> bool:
"""Handle atomic instructions.
The iOS system libraries will use some atomic instructions from ARM v8.1.
However, Unicorn doesn't support these instructions, so we need to do some
simple simulation.
Returns:
True if the instruction is handled, False otherwise.
"""
inst_set = ["ldxr", "ldadd", "ldset", "swp", "cas"]

if not any((inst[2].startswith(t) for t in inst_set)):
return False

match = re.match(r"(\w+), \[(\w+)]", inst[3])

if not match:
match = re.match(r"(\w+), (\w+), \[(\w+)]", inst[3])

if not match:
return False

if inst[2].endswith("b"):
op_bits = 8
elif re.search(r"w(\d+)", inst[3]):
op_bits = 32
else:
op_bits = 64

regs = []

for reg in match.groups():
attr = f"UC_ARM64_REG_{reg.upper()}"
regs.append(getattr(arm64_const, attr))

return self._exec_atomic_inst(inst[2], op_bits, regs)

def _exec_atomic_inst(self, inst: str, op_bits: int, regs: List[int]) -> bool:
"""Execute an atomic instruction."""
exec_func_map = {
"ldxr": self._exec_inst_ldxr,
"ldadd": self._exec_inst_ldadd,
"ldset": self._exec_inst_ldset,
"swp": self._exec_inst_swp,
"cas": self._exec_inst_cas,
}

read_func = getattr(self, f"read_u{op_bits}")
write_func = getattr(self, f"write_u{op_bits}")

addr = self.uc.reg_read(regs[-1])
value = read_func(addr)

for key, exec_func in exec_func_map.items():
if inst.startswith(key):
result = exec_func(regs[0], regs[1], value)

if result is not None:
write_func(addr, result % (2**op_bits))

return True

return False

def _exec_inst_ldxr(self, s: int, t: int, v: int) -> Optional[int]:
"""Execute `ldxr` instruction."""
self.uc.reg_write(s, v)
return None

def _exec_inst_ldadd(self, s: int, t: int, v: int) -> Optional[int]:
"""Execute `ldadd` instruction."""
self.uc.reg_write(t, v)
v += self.uc.reg_read(s)
return v

def _exec_inst_ldset(self, s: int, t: int, v: int) -> Optional[int]:
"""Execute `ldset` instruction."""
self.uc.reg_write(t, v)
v |= self.uc.reg_read(s)
return v

def _exec_inst_swp(self, s: int, t: int, v: int) -> Optional[int]:
"""Execute `swp` instruction."""
self.uc.reg_write(t, v)
v = self.uc.reg_read(s)
return v

def _exec_inst_cas(self, s: int, t: int, v: int) -> Optional[int]:
"""Execute `cas` instruction."""
n = self.uc.reg_read(s)

self.uc.reg_write(s, v)

if n == v:
return self.uc.reg_read(t)

return None

def add_inst_trace(self, module: Module):
"""Add instruction trace for the module."""
self.uc.hook_add(
Expand Down
78 changes: 78 additions & 0 deletions src/chomper/instruction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import re

from unicorn.unicorn import arm64_const


class AutomicInstruction:
"""Execute an atomic instruction (ldxr, ldadd, ldset, swp, cas).
The iOS system libraries will use some atomic instructions from ARM v8.1.
However, Unicorn doesn't support these instructions, so we need to simulation
them ourselves.
"""

supports = ("ldxr", "ldadd", "ldset", "swp", "cas")

def __init__(self, emu, code: bytes):
self.emu = emu

self._inst = next(self.emu.cs.disasm_lite(code, 0))

if not any((self._inst[2].startswith(t) for t in self.supports)):
raise ValueError("Unsupported instruction: %s" % self._inst[0])

match = re.match(r"(\w+), \[(\w+)]", self._inst[3])

if not match:
match = re.match(r"(\w+), (\w+), \[(\w+)]", self._inst[3])

if not match:
raise ValueError("Invalid instruction: %s" % self._inst[3])

# Parse operation registers
self._regs = []

for reg in match.groups():
attr = f"UC_ARM64_REG_{reg.upper()}"
self._regs.append(getattr(arm64_const, attr))

# Parse operation bits
if self._inst[2].endswith("b"):
self._op_bits = 8
elif re.search(r"w(\d+)", self._inst[3]):
self._op_bits = 32
else:
self._op_bits = 64

def execute(self):
address = self.emu.uc.reg_read(self._regs[-1])
value = self.emu.read_int(address, self._op_bits // 8)

result = None

if self._inst[2].startswith("ldxr"):
self.emu.uc.reg_write(self._regs[0], value)

elif self._inst[2].startswith("ldadd"):
self.emu.uc.reg_write(self._regs[1], value)
result = value + self.emu.uc.reg_read(self._regs[0])

elif self._inst[2].startswith("ldset"):
self.emu.uc.reg_write(self._regs[1], value)
result = value | self.emu.uc.reg_read(self._regs[0])

elif self._inst[2].startswith("swp"):
self.emu.uc.reg_write(self._regs[1], value)
result = self.emu.uc.reg_read(self._regs[0])

elif self._inst[2].startswith("cas"):
n = self.emu.uc.reg_read(self._regs[0])

self.emu.uc.reg_write(self._regs[0], value)

if n == value:
result = self.emu.uc.reg_read(self._regs[1])

if result is not None:
result %= 2**self._op_bits
self.emu.write_int(address, result, self._op_bits // 8)
5 changes: 5 additions & 0 deletions src/chomper/os/android/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
hooks: Dict[str, UC_HOOK_CODE_TYPE] = {}


def get_hooks() -> Dict[str, UC_HOOK_CODE_TYPE]:
"""Returns a dictionary of default hooks."""
return hooks.copy()


def register_hook(symbol_name: str):
"""Decorator to register a hook function for a given symbol name."""

Expand Down
6 changes: 4 additions & 2 deletions src/chomper/os/android/os.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from chomper.abc import BaseOs
from chomper.os.android.hooks import hooks
from chomper.os.android.hooks import get_hooks
from chomper.os.android.loader import ELFLoader


Expand All @@ -11,9 +11,11 @@ def __init__(self, emu, **kwargs):

self.loader = ELFLoader(emu)

self._setup_hooks()

def _setup_hooks(self):
"""Initialize the hooks."""
self.emu.hooks.update(hooks)
self.emu.hooks.update(get_hooks())

def initialize(self):
"""Initialize the environment."""
Expand Down
3 changes: 3 additions & 0 deletions src/chomper/os/ios/const.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
# System call numbers in iOS

SYS_GETPID = 0x14
SYS_GETUID = 0x18
SYS_GETEUID = 0x19
SYS_ACCESS = 0x21
SYS_GETEGID = 0x2B
SYS_GETTIMEOFDAY = 0x74
SYS_CSOPS = 0xA9
SYS_SYSCTL = 0xCA
SYS_SHM_OPEN = 0x10A
SYS_SYSCTLBYNAME = 0x112
SYS_GETTID = 0x11E
SYS_ISSETUGID = 0x147
SYS_PROC_INFO = 0x150
SYS_STAT64 = 0x152
SYS_LSTAT64 = 0x154
SYS_GETENTROPY = 0x1F4
Expand Down
Loading

0 comments on commit a148096

Please sign in to comment.