Skip to content

Commit

Permalink
Support preferences on iOS (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
sledgeh4w committed May 1, 2024
1 parent 81b1648 commit 60a8db5
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 122 deletions.
74 changes: 20 additions & 54 deletions examples/example_ios_ali_vmp_sign.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from chomper import Chomper
from chomper.const import ARCH_ARM64, OS_IOS
from chomper.objc import ObjC
from chomper.utils import pyobj2nsobj

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

Expand All @@ -28,53 +29,24 @@ def decorator(uc, address, size, user_data):
return decorator


def hook_ns_bundle(emu, objc):
bundle_identifier = objc.msg_send("NSString", "stringWithUTF8String:", "com.ceair.b2m")
def hook_ns_bundle(emu):
executable_path = f"/var/containers/Bundle/Application/{uuid.uuid4()}/com.ceair.b2m/ceair_iOS_branch"

executable_path = objc.msg_send(
"NSString",
"stringWithUTF8String:",
f"/var/containers/Bundle/Application/{uuid.uuid4()}/com.ceair.b2m/ceair_iOS_branch",
)

bundle_info_directory = objc.msg_send(
"NSMutableDictionary",
"dictionaryWithObject:forKey:",
objc.msg_send("NSString", "stringWithUTF8String:", "9.4.7"),
objc.msg_send("NSString", "stringWithUTF8String:", "CFBundleShortVersionString"),
)

objc.msg_send(
bundle_info_directory,
"addObject:forKey:",
executable_path,
objc.msg_send("NSString", "stringWithUTF8String:", "CFBundleExecutable"),
)
bundle_info = {
"CFBundleShortVersionString": "9.4.7",
"CFBundleExecutable": executable_path,
}

emu.add_interceptor("-[NSBundle initWithPath:]", hook_skip)
emu.add_interceptor("-[NSBundle bundleIdentifier]", hook_retval(bundle_identifier))
emu.add_interceptor("-[NSBundle executablePath]", hook_retval(executable_path))
emu.add_interceptor("-[NSBundle infoDictionary]", hook_retval(bundle_info_directory))


def hook_ns_locale(emu, objc):
preferred_languages = objc.msg_send(
"NSArray",
"arrayWithObject:",
objc.msg_send("NSString", "stringWithUTF8String:", "zh-cn")
)

emu.add_interceptor("+[NSLocale preferredLanguages]", hook_retval(preferred_languages))
emu.add_interceptor("-[NSBundle bundleIdentifier]", hook_retval(pyobj2nsobj(emu, "com.ceair.b2m")))
emu.add_interceptor("-[NSBundle executablePath]", hook_retval(pyobj2nsobj(emu, executable_path)))
emu.add_interceptor("-[NSBundle infoDictionary]", hook_retval(pyobj2nsobj(emu, bundle_info)))


def hook_ui_device(emu, objc):
system_version = objc.msg_send("NSString", "stringWithUTF8String:", "14.4.0")
device_name = objc.msg_send("NSString", "stringWithUTF8String:", "iPhone")
device_model = objc.msg_send("NSString", "stringWithUTF8String:", "iPhone13,1")

emu.add_interceptor("-[UIDevice systemVersion]", hook_retval(system_version))
emu.add_interceptor("-[UIDevice name]", hook_retval(device_name))
emu.add_interceptor("-[UIDevice model]", hook_retval(device_model))
def hook_ui_device(emu):
emu.add_interceptor("-[UIDevice systemVersion]", hook_retval(pyobj2nsobj(emu, "14.4.0")))
emu.add_interceptor("-[UIDevice name]", hook_retval(pyobj2nsobj(emu, "iPhone")))
emu.add_interceptor("-[UIDevice model]", hook_retval(pyobj2nsobj(emu, "iPhone13,1")))


def main():
Expand All @@ -88,9 +60,8 @@ def main():

objc = ObjC(emu)

hook_ns_bundle(emu, objc)
hook_ns_locale(emu, objc)
hook_ui_device(emu, objc)
hook_ns_bundle(emu)
hook_ui_device(emu)

# Skip a file operation
emu.add_interceptor("_fopen", hook_retval(0))
Expand All @@ -99,16 +70,11 @@ def main():

ali_tiger_tally_instance = objc.msg_send("AliTigerTally", "sharedInstance")

app_key = objc.msg_send(
"NSString",
"stringWithUTF8String:",
"xPEj7uv0KuziQnXUyPIBNUjnDvvHuW09VOYFuLYBcY-jV6fgqmfy5B1y75_iSuRM5U2zNq7MRoR9N1F-UthTEgv-QBWk68gr95BrAySzWuDzt08FrkeBZWQCGyZ0iAybalYLOJEF7nkKBtmDGLewcw==",
)

objc.msg_send(ali_tiger_tally_instance, "initialize:", app_key)
app_key = "xPEj7uv0KuziQnXUyPIBNUjnDvvHuW09VOYFuLYBcY-jV6fgqmfy5B1y75_iSuRM5U2zNq7MRoR9N1F-UthTEgv-QBWk68gr95BrAySzWuDzt08FrkeBZWQCGyZ0iAybalYLOJEF7nkKBtmDGLewcw=="
objc.msg_send(ali_tiger_tally_instance, "initialize:", pyobj2nsobj(emu, app_key))

encrypt_str = objc.msg_send("NSString", "stringWithUTF8String:", '{"biClassId":["2","3","4"]}')
encrypt_bytes = objc.msg_send(encrypt_str, "dataUsingEncoding:", 1)
encrypt_str = '{"biClassId":["2","3","4"]}'
encrypt_bytes = objc.msg_send(pyobj2nsobj(emu, encrypt_str), "dataUsingEncoding:", 1)

vmp_sign = objc.msg_send(ali_tiger_tally_instance, "vmpSign:", encrypt_bytes)
logger.info("vmp sign: %s", emu.read_string(objc.msg_send(vmp_sign, "cStringUsingEncoding:", 4)))
Expand Down
33 changes: 10 additions & 23 deletions examples/example_ios_bangbang.py

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions examples/example_ios_ijm.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from chomper import Chomper
from chomper.const import ARCH_ARM64, OS_IOS
from chomper.objc import ObjC
from chomper.utils import pyobj2nsobj

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

Expand Down Expand Up @@ -42,16 +43,15 @@ def main():
emu.add_interceptor(czair.base + 0x1038F0004, hook_retval(1))

# Call encryption
encrypt_input = objc.msg_send("NSString", "stringWithUTF8String:", '{"biClassId":["2","3","4"]}')
encrypt_str = '{"biClassId":["2","3","4"]}'
encrypt_result = objc.msg_send("JMBox125", "JMBox167:JMBox501:", pyobj2nsobj(emu, encrypt_str), 1)

encrypt_result = objc.msg_send("JMBox125", "JMBox167:JMBox501:", encrypt_input, 1)
logger.info("encrypt_result: %s", emu.read_string(objc.msg_send(encrypt_result, "cStringUsingEncoding:", 4)))

# Call decryption
decrypt_data = "XKQYFMCP9Eb0IUzrQ9KaRRvTeFcYYyLcInrS/IWp6be1+VZa14GanCrzeb3DR45HW+XH0xiZLA5WUjUcXnlpM+CC6EtauUDUxCLap3QPWRyewLUosCB/ESHE7341DQca6lx5KFcP0XCkBpGlEKpACR5v7TwNBxc62auNBDvmEY422LTAUEEBrC8FDE+Y4DS2IJTLN6h9f7hdmQ4zUnY4cwyZXwgdIoH+bVuNy6TSw1JjQaFF/fLLHVZOQovrMcjtTpMZGr8xOSoW/+msiZzKwET3"
decrypt_input = objc.msg_send("NSString", "stringWithUTF8String:", decrypt_data)
decrypt_str = "XKQYFMCP9Eb0IUzrQ9KaRRvTeFcYYyLcInrS/IWp6be1+VZa14GanCrzeb3DR45HW+XH0xiZLA5WUjUcXnlpM+CC6EtauUDUxCLap3QPWRyewLUosCB/ESHE7341DQca6lx5KFcP0XCkBpGlEKpACR5v7TwNBxc62auNBDvmEY422LTAUEEBrC8FDE+Y4DS2IJTLN6h9f7hdmQ4zUnY4cwyZXwgdIoH+bVuNy6TSw1JjQaFF/fLLHVZOQovrMcjtTpMZGr8xOSoW/+msiZzKwET3"
decrypt_result = objc.msg_send("JMBox125", "JMBox167:JMBox501:", pyobj2nsobj(emu, decrypt_str), 1)

decrypt_result = objc.msg_send("JMBox125", "JMBox167:JMBox501:", decrypt_input, 1)
logger.info("decrypt_result: %s", emu.read_string(objc.msg_send(decrypt_result, "cStringUsingEncoding:", 4)))


Expand Down
18 changes: 10 additions & 8 deletions src/chomper/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,10 @@ def _setup_emulator(self, enable_vfp: bool = True):
if self.arch == arm_arch and enable_vfp:
self._enable_vfp()

def _start_emulate(self, address: int, *args: int):
def _start_emulate(self, address: int, *args: int) -> int:
"""Start emulate at the specified address."""
context = self.uc.context_save()
stop_addr = self.create_buffer(8)
# stop_addr = 0

for index, value in enumerate(args):
self.set_arg(index, value)
Expand All @@ -208,12 +208,18 @@ def _start_emulate(self, address: int, *args: int):
self.logger.info(f"Start emulate at {self.debug_symbol(address)}")
self.uc.emu_start(address, stop_addr)

return self.get_retval()

except UcError as e:
self.crash("Unknown reason", from_exc=e)

finally:
self.uc.context_restore(context)
self.free(stop_addr)

# Pass type hints
return 0

def find_module(self, name_or_addr: Union[str, int]) -> Optional[Module]:
"""Find module by name or address."""
for module in self.modules:
Expand Down Expand Up @@ -739,12 +745,8 @@ def call_symbol(self, symbol_name: str, *args: int) -> int:
symbol = self.find_symbol(symbol_name)
address = symbol.address

self._start_emulate(address, *args)

return self.get_retval()
return self._start_emulate(address, *args)

def call_address(self, address: int, *args: int) -> int:
"""Call function at the address."""
self._start_emulate(address, *args)

return self.get_retval()
return self._start_emulate(address, *args)
45 changes: 19 additions & 26 deletions src/chomper/objc.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,25 @@ class ObjC:
def __init__(self, emu):
self.emu = emu

self._class_cache = {}
self._sel_cache = {}

def get_class(self, class_name: str) -> int:
"""Get class by name."""
if class_name in self._class_cache:
return self._class_cache[class_name]

class_name_ptr = self.emu.create_string(class_name)
name_ptr = self.emu.create_string(class_name)

cls = self.emu.call_symbol("_objc_getClass", class_name_ptr)
self._class_cache[class_name] = cls
try:
return self.emu.call_symbol("_objc_getClass", name_ptr)

return cls
finally:
self.emu.free(name_ptr)

def get_sel(self, sel_name: str) -> int:
"""Get selector by name."""
if sel_name in self._sel_cache:
return self._sel_cache[sel_name]
name_ptr = self.emu.create_string(sel_name)

sel_name_ptr = self.emu.create_string(sel_name)

sel = self.emu.call_symbol("_sel_registerName", sel_name_ptr)
self._sel_cache[sel_name] = sel
try:
return self.emu.call_symbol("_sel_registerName", name_ptr)

return sel
finally:
self.emu.free(name_ptr)

def msg_send(self, receiver: Union[int, str], sel: Union[int, str], *args) -> int:
"""Send message to Objective-C runtime.
Expand All @@ -47,25 +40,25 @@ def msg_send(self, receiver: Union[int, str], sel: Union[int, str], *args) -> in
receiver = self.get_class(receiver) if isinstance(receiver, str) else receiver
sel = self.get_sel(sel) if isinstance(sel, str) else sel

c_strs = []
args_ = []
str_ptrs = []
new_args = []

for arg in args:
if isinstance(arg, str):
c_str = self.emu.create_string(arg)
str_ptr = self.emu.create_string(arg)

c_strs.append(c_str)
args_.append(c_str)
str_ptrs.append(str_ptr)
new_args.append(str_ptr)

else:
args_.append(arg)
new_args.append(arg)

try:
return self.emu.call_symbol("_objc_msgSend", receiver, sel, *args_)
return self.emu.call_symbol("_objc_msgSend", receiver, sel, *new_args)

finally:
for c_str in c_strs:
self.emu.free(c_str)
for str_ptr in str_ptrs:
self.emu.free(str_ptr)

def release(self, obj: int):
"""Release object."""
Expand Down
6 changes: 4 additions & 2 deletions src/chomper/os/ios/fixup.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,11 @@ def fixup_cfstring_section(self, binary: lief.MachO.Binary, module_base: int):
offset = 0

while offset < section.size:
address = module_base + start + offset + 0x10
self.relocate_pointer(module_base, address)
self.add_refs_relocation(start + offset)

str_ptr = module_base + start + offset + 0x10
self.relocate_pointer(module_base, str_ptr)

offset += step

def fixup_cf_uni_char_string(self, binary: lief.MachO.Binary):
Expand Down
86 changes: 86 additions & 0 deletions src/chomper/os/ios/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

from unicorn.unicorn import UC_HOOK_CODE_TYPE

from chomper.utils import pyobj2nsobj

hooks: Dict[str, UC_HOOK_CODE_TYPE] = {}


Expand Down Expand Up @@ -379,11 +381,95 @@ def hook_dispatch_async(uc, address, size, user_data):
return 0


@register_hook("_uloc_getLanguage")
def hook_uloc_get_language(uc, address, size, user_data):
return 0


@register_hook("_uloc_getScript")
def hook_uloc_get_script(uc, address, size, user_data):
return 0


@register_hook("_uloc_getCountry")
def hook_uloc_get_country(uc, address, size, user_data):
return 0


@register_hook("_uloc_getVariant")
def hook_uloc_get_variant(uc, address, size, user_data):
return 0


@register_hook("_uloc_openKeywords")
def hook_uloc_open_keywords(uc, address, size, user_data):
return 0


@register_hook("_uloc_getDisplayName")
def hook_uloc_get_display_name(uc, address, size, user_data):
return 0


@register_hook("_uloc_getDisplayLanguage")
def hook_uloc_get_display_language(uc, address, size, user_data):
return 0


@register_hook("_uenum_next")
def hook_uenum_next(uc, address, size, user_data):
return 0


@register_hook("_uenum_close")
def hook_uenum_close(uc, address, size, user_data):
return 0


@register_hook("_os_log_type_enabled")
def hook_os_log_type_enabled(uc, address, size, user_data):
return 0


@register_hook("__CFPreferencesCopyAppValueWithContainerAndConfiguration")
def hook_cf_preferences_copy_app_value_with_container_and_configuration(
uc, address, size, user_data
):
emu = user_data["emu"]

str_ptr = emu.read_pointer(emu.get_arg(0) + 0x10)
key = emu.read_string(str_ptr)

if key in emu.os.preferences:
return pyobj2nsobj(emu, emu.os.preferences[key])

return 0


@register_hook("__CFBundleCreateInfoDictFromMainExecutable")
def hook_cf_bundle_create_info_dict_from_main_executable(uc, address, size, user_data):
return 0


@register_hook("___CFXPreferencesCopyCurrentApplicationStateWithDeadlockAvoidance")
def hook_cf_x_preferences_copy_current_application_state_with_deadlock_avoidance(
uc, address, size, user_data
):
emu = user_data["emu"]

return pyobj2nsobj(emu, emu.os.preferences)


@register_hook("_CFNotificationCenterGetLocalCenter")
def hook_cf_notification_center_get_local_center(uc, address, size, user_data):
return 0


@register_hook("__CFPrefsClientLog")
def hook_cf_prefs_client_log(uc, address, size, user_data):
return 0


@register_hook("_NSLog")
def hook_ns_log(uc, address, size, user_data):
return 0
Loading

0 comments on commit 60a8db5

Please sign in to comment.