Skip to content

Commit

Permalink
Add ObjCUnrecognizedSelector exception
Browse files Browse the repository at this point in the history
  • Loading branch information
sledgeh4w committed Dec 11, 2024
1 parent 212bb67 commit ec0c711
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 123 deletions.
13 changes: 5 additions & 8 deletions src/chomper/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

from . import const
from .arch import arm_arch, arm64_arch
from .exceptions import EmulatorCrashedException, SymbolMissingException
from .exceptions import EmulatorCrashed, SymbolMissing
from .file import FileManager
from .instruction import AutomicInstruction
from .memory import MemoryManager
Expand Down Expand Up @@ -244,7 +244,7 @@ def find_symbol(self, symbol_name: str) -> Symbol:
if symbol.name == symbol_name:
return symbol

raise SymbolMissingException(f"{symbol_name} not found")
raise SymbolMissing(f"{symbol_name} not found")

def debug_symbol(self, address: int) -> str:
"""Format address to `libtest.so!0x1000` or `0x10000`."""
Expand Down Expand Up @@ -352,15 +352,12 @@ def crash(self, message: str, exc: Optional[Exception] = None):
EmulatorCrashedException:
"""
address = self.uc.reg_read(self.arch.reg_pc)

self.logger.error(
"Emulator crashed from: %s",
" <- ".join([self.debug_symbol(t) for t in self.backtrace()]),
)

raise EmulatorCrashedException(
f"{message} at {self.debug_symbol(address)}"
) from exc
raise EmulatorCrashed(f"{message} at {self.debug_symbol(address)}") from exc

def trace_symbol_call_callback(
self, uc: Uc, address: int, size: int, user_data: dict
Expand Down Expand Up @@ -389,8 +386,8 @@ def missing_symbol_required_callback(*args):
user_data = args[-1]
symbol_name = user_data["symbol_name"]

raise EmulatorCrashedException(
f'Missing symbol "{symbol_name}" is required, '
raise EmulatorCrashed(
f"Missing symbol '{symbol_name}' is required, "
f"you should load the library that contains it."
)

Expand Down
12 changes: 10 additions & 2 deletions src/chomper/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
class EmulatorCrashedException(Exception):
class ChomperException(Exception):
"""Base class for all Chomper exceptions."""


class EmulatorCrashed(ChomperException):
"""Emulate failed when calling functions."""


class SymbolMissingException(Exception):
class SymbolMissing(ChomperException):
"""Symbol not found from loaded modules."""


class ObjCUnrecognizedSelector(ChomperException):
"""Unrecognized selector sent when calling ObjC."""
4 changes: 2 additions & 2 deletions src/chomper/os/ios/fixup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import lief

from chomper.exceptions import SymbolMissingException
from chomper.exceptions import SymbolMissing
from chomper.types import Module


Expand Down Expand Up @@ -301,7 +301,7 @@ def fixup_dispatch_vtable(self):
offset = 0x30 + 0x8 * i
address = self.emu.read_pointer(symbol.address + offset)
self.add_refs_relocation(address)
except SymbolMissingException:
except SymbolMissing:
pass

def fixup_data_const_segment(self, binary: lief.MachO.Binary):
Expand Down
14 changes: 12 additions & 2 deletions src/chomper/os/ios/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from functools import wraps
from typing import Callable, Dict

from chomper.exceptions import SymbolMissingException
from chomper.exceptions import SymbolMissing, ObjCUnrecognizedSelector
from chomper.objc import ObjC
from chomper.utils import pyobj2cfobj

Expand Down Expand Up @@ -354,7 +354,7 @@ def hook_dlsym(uc, address, size, user_data):
try:
symbol = emu.find_symbol(symbol_name)
return symbol.address
except SymbolMissingException:
except SymbolMissing:
pass

return 0
Expand Down Expand Up @@ -563,3 +563,13 @@ def hook_mach_vm_deallocate(uc, address, size, user_data):
emu.memory_manager.free(mem)

return 0


@register_hook("+[NSObject(NSObject) doesNotRecognizeSelector:]")
@register_hook("-[NSObject(NSObject) doesNotRecognizeSelector:]")
def ns_object_does_not_recognize_selector(uc, address, size, user_data):
emu = user_data["emu"]

selector = emu.read_string(emu.get_arg(2))

raise ObjCUnrecognizedSelector(f"Unrecognized selector '{selector}'")
4 changes: 2 additions & 2 deletions src/chomper/os/ios/os.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import uuid
from typing import List

from chomper.exceptions import EmulatorCrashedException
from chomper.exceptions import EmulatorCrashed
from chomper.loader import MachoLoader
from chomper.os import BaseOs
from chomper.types import Module
Expand Down Expand Up @@ -263,7 +263,7 @@ def init_objc(self, module: Module):
try:
self.emu.call_symbol("_map_images", 1, 0, mach_header_ptrs)
self.emu.call_symbol("_load_images", 0, mach_header_ptr)
except EmulatorCrashedException:
except EmulatorCrashed:
self.emu.logger.warning("Initialize Objective-C failed.")
finally:
module.binary = None
Expand Down
43 changes: 15 additions & 28 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
from chomper.const import ARCH_ARM, ARCH_ARM64, OS_IOS
from chomper.objc import ObjC

BINARY_URL = "https://sourceforge.net/projects/chomper-emu/files/%s/download"

base_path = os.path.abspath(os.path.dirname(__file__))

android_lib_path = os.path.join(base_path, "../examples/rootfs/android/system/lib")
Expand All @@ -21,24 +19,22 @@
ios_binaries_path = os.path.join(base_path, "../examples/binaries/ios")


def download_file(url: str, filepath: str):
def download_binary_file(binary_path: str) -> str:
filepath = os.path.join(base_path, "..", binary_path)

path = Path(filepath).resolve()
if path.exists():
return
return filepath

if not path.parent.exists():
path.parent.mkdir(parents=True)
print(f"Downloading file: {url}")
urllib.request.urlretrieve(url, path)

url = "https://sourceforge.net/projects/chomper-emu/files/%s/download" % binary_path

@pytest.fixture(scope="module")
def input_str():
yield "chomper"

print(f"Downloading file: {url}")
urllib.request.urlretrieve(url, path)

@pytest.fixture(scope="module")
def input_bytes(input_str):
yield input_str.encode("utf-8")
return filepath


@pytest.fixture(scope="module")
Expand All @@ -59,11 +55,8 @@ def libz_arm(emu_arm):
@pytest.fixture(scope="module")
def libdusanwa_v4856_arm(emu_arm):
"""From com.shizhuang.duapp v4.85.6"""
binary_path = "examples/binaries/android/com.shizhuang.duapp/libdusanwa.so"
module_path = os.path.join(base_path, "..", binary_path)
download_file(
url=BINARY_URL % binary_path,
filepath=module_path,
module_path = download_binary_file(
binary_path="examples/binaries/android/com.shizhuang.duapp/libdusanwa.so"
)

yield emu_arm.load_module(
Expand All @@ -90,11 +83,8 @@ def libz_arm64(emu_arm64):
@pytest.fixture(scope="module")
def libszstone_v4945_arm64(emu_arm64):
"""From com.shizhuang.duapp v4.94.5"""
binary_path = "examples/binaries/android/com.shizhuang.duapp/libszstone.so"
module_path = os.path.join(base_path, "..", binary_path)
download_file(
url=BINARY_URL % binary_path,
filepath=module_path,
module_path = download_binary_file(
binary_path="examples/binaries/android/com.shizhuang.duapp/libszstone.so"
)

yield emu_arm64.load_module(
Expand All @@ -106,11 +96,8 @@ def libszstone_v4945_arm64(emu_arm64):
@pytest.fixture(scope="module")
def libtiny_v73021_arm64(emu_arm64):
"""From com.xingin.xhs v7.30.2.1"""
binary_path = "examples/binaries/android/com.xingin.xhs/libtiny.so"
module_path = os.path.join(base_path, "..", binary_path)
download_file(
url=BINARY_URL % binary_path,
filepath=module_path,
module_path = download_binary_file(
binary_path="examples/binaries/android/com.xingin.xhs/libtiny.so"
)

yield emu_arm64.load_module(module_path)
Expand Down
22 changes: 13 additions & 9 deletions tests/test_emulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ def test_create_buffer(emu_arm64):
assert result is not None


def test_create_string(emu_arm64, input_str):
result = emu_arm64.create_string(input_str)
def test_create_string(emu_arm64):
result = emu_arm64.create_string("chomper")

assert result is not None

Expand All @@ -71,22 +71,26 @@ def test_read_and_write_int(emu_arm64):
assert result == value


def test_read_and_write_bytes(emu_arm64, input_bytes):
def test_read_and_write_bytes(emu_arm64):
sample_bytes = b"chomper"

addr = emu_arm64.create_buffer(1024)

emu_arm64.write_bytes(addr, input_bytes)
result = emu_arm64.read_bytes(addr, len(input_bytes))
emu_arm64.write_bytes(addr, sample_bytes)
result = emu_arm64.read_bytes(addr, len(sample_bytes))

assert result == sample_bytes

assert result == input_bytes

def test_read_and_write_string(emu_arm64):
sample_str = "chomper"

def test_read_and_write_string(emu_arm64, input_str):
addr = emu_arm64.create_buffer(1024)

emu_arm64.write_string(addr, input_str)
emu_arm64.write_string(addr, sample_str)
result = emu_arm64.read_string(addr)

assert result == input_str
assert result == sample_str


@pytest.mark.usefixtures("libz_arm64")
Expand Down
26 changes: 16 additions & 10 deletions tests/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@


@pytest.mark.usefixtures("libc_arm", "libz_arm")
def test_libdusanwa_v4856_arm(emu_arm, libdusanwa_v4856_arm, input_bytes):
def test_libdusanwa_v4856_arm(emu_arm, libdusanwa_v4856_arm):
sample_bytes = b"chomper"

a1 = emu_arm.create_buffer(32)
a2 = 32
a3 = emu_arm.create_buffer(32)
a4 = emu_arm.create_buffer(32)

emu_arm.write_bytes(a1, input_bytes)
emu_arm.write_bytes(a4, input_bytes)
emu_arm.write_bytes(a1, sample_bytes)
emu_arm.write_bytes(a4, sample_bytes)

emu_arm.call_address((libdusanwa_v4856_arm.base + 0xA588) | 1, a1, a2, a3, a4)
result = emu_arm.read_bytes(a3, a2)
Expand All @@ -20,12 +22,14 @@ def test_libdusanwa_v4856_arm(emu_arm, libdusanwa_v4856_arm, input_bytes):


@pytest.mark.usefixtures("libc_arm64", "libz_arm64")
def test_libszstone_v4945_arm64(emu_arm64, libszstone_v4945_arm64, input_bytes):
a1 = emu_arm64.create_buffer(len(input_bytes))
a2 = len(input_bytes)
def test_libszstone_v4945_arm64(emu_arm64, libszstone_v4945_arm64):
sample_bytes = b"chomper"

a1 = emu_arm64.create_buffer(len(sample_bytes))
a2 = len(sample_bytes)
a3 = emu_arm64.create_buffer(1024)

emu_arm64.write_bytes(a1, input_bytes)
emu_arm64.write_bytes(a1, sample_bytes)

result_size = emu_arm64.call_address(
libszstone_v4945_arm64.base + 0x2F1C8, a1, a2, a3
Expand All @@ -36,13 +40,15 @@ def test_libszstone_v4945_arm64(emu_arm64, libszstone_v4945_arm64, input_bytes):


@pytest.mark.usefixtures("libc_arm64", "libz_arm64")
def test_libtiny_v73021_arm64(emu_arm64, libtiny_v73021_arm64, input_bytes):
def test_libtiny_v73021_arm64(emu_arm64, libtiny_v73021_arm64):
sample_bytes = b"chomper"

a1 = emu_arm64.create_buffer(32)
a2 = emu_arm64.create_buffer(32)
a3 = emu_arm64.create_buffer(32)

emu_arm64.write_bytes(a1, input_bytes * 4)
emu_arm64.write_bytes(a2, input_bytes * 4)
emu_arm64.write_bytes(a1, sample_bytes * 4)
emu_arm64.write_bytes(a2, sample_bytes * 4)

emu_arm64.call_address(libtiny_v73021_arm64.base + 0x289A4, a1, a2, a3)
result = emu_arm64.read_bytes(a3, 32)
Expand Down
23 changes: 11 additions & 12 deletions tests/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

from chomper import Chomper
from chomper.const import ARCH_ARM64
from chomper.exceptions import EmulatorCrashedException
from chomper.exceptions import EmulatorCrashed


def test_unhandled_system_call_exception():
with pytest.raises(EmulatorCrashedException, match=r"Unhandled system call.*"):
with pytest.raises(EmulatorCrashed, match=r"Unhandled system call.*"):
emu = Chomper(arch=ARCH_ARM64)
emu.hooks.pop("malloc")

Expand All @@ -19,21 +19,20 @@ def test_unhandled_system_call_exception():
emu.call_symbol("malloc")


def test_missing_symbol_required_exception(input_bytes):
with pytest.raises(EmulatorCrashedException, match=r"Missing symbol.*"):
binary_path = "examples/binaries/android/com.shizhuang.duapp/libszstone.so"
module_path = os.path.join(conftest.base_path, "..", binary_path)
conftest.download_file(
url=conftest.BINARY_URL % binary_path,
filepath=module_path,
def test_missing_symbol_required_exception():
with pytest.raises(EmulatorCrashed, match=r"Missing symbol.*"):
module_path = conftest.download_binary_file(
binary_path="examples/binaries/android/com.shizhuang.duapp/libszstone.so"
)

sample_bytes = b"chomper"

emu = Chomper(arch=ARCH_ARM64)
libszstone = emu.load_module(module_path)

a1 = emu.create_buffer(len(input_bytes))
a2 = len(input_bytes)
a1 = emu.create_buffer(len(sample_bytes))
a2 = len(sample_bytes)
a3 = emu.create_buffer(1024)

emu.write_bytes(a1, input_bytes)
emu.write_bytes(a1, sample_bytes)
emu.call_address(libszstone.base + 0x289A4, a1, a2, a3)
Loading

0 comments on commit ec0c711

Please sign in to comment.