Skip to main content

IDA Plugin for ease the reversing of iOS' usermode and kernelcache.

Project description

IDA iOS Helper

A plugin for IDA Pro 9.0+ to help with iOS code analysis.

Supported features

  • KernelCache
    • Calls to OSBaseClass::safeMetaCast apply type info on the result.
    • Calls to OSObject_typed_operator_new apply type info on the result.
    • When the keyboard is on a virtual call (cls->vcall()), Shift+X will show a dialog with all the possible implementations of the virtual method. It requires vtable symbols to be present.
    • When in a C++ method named Class::func, Ctrl+T will change the first argument to Class* this. Also works for Obj-C instance methods.
    • Name globals from OSSymbol::fromConst* calls, locals from get/setProperty calls, ...
    • Rename and type all global kalloc_type_view. Use their signature to mark fields as pointers for the actual types.
  • Objective-C
    • Hide memory management functions - objc_retain, objc_release, objc_autorelease, objc_retainAutoreleasedReturnValue.
      • Optimize _objc_storeStrong to an assignment.
    • collapse __os_log_impl calls.
    • Hide selectors and static classes from Objective-c calls.
    • When in Obj-C method, Ctrl+4 will show xrefs to the selector.
  • Common
    • Remove __break calls.
    • collapse blocks initializers and detect __block variables (use Alt+Shift+S to trigger detection).
    • Use Ctrl+S to jump to function by a string constant found in the code
    • Transform ranged conditions to a more readable form.
    • Try to detect outline functions and mark them as such.
    • Use Ctrl+Shift+X to find xrefs to a field inside a segment. This will decompile the whole segment and then search for the field.

Installation

  1. Install this package using your IDA's python pip: pip install ida-ios-helper
  2. copy ida-plugin.json and ida_plugin_stub.py to your IDA's plugins folder: ~/.idapro/plugins/ida-ios-helper.
  3. Restart IDA.

Examples

Solve condition constraints

Before:

if ( valueLength - 21 <= 0xFFFFFFFFFFFFFFEFLL ) 
{ 
  ... 
}

After:

if ( 4 < valueLength || valueLength < 21 )
{
  ...
}

Remove __break

Before:

    if ( ((v6 ^ (2 * v6)) & 0x4000000000000000LL) != 0 )
      __break(0xC471u);

After: removed.

Hide selectors of Obj-C calls

Before:

   -[NSFileManager removeItemAtPath:error:](
      +[NSFileManager defaultManager](&OBJC_CLASS___NSFileManager, "defaultManager"),
      "removeItemAtPath:error:",
      +[NSString stringWithUTF8String:](&OBJC_CLASS___NSString, "stringWithUTF8String:", *(_QWORD *)&buf[v5]),
      0LL);

After:

   -[NSFileManager removeItemAtPath:error:](
      +[NSFileManager defaultManager](),
      +[NSString stringWithUTF8String:](*(_QWORD *)&buf[v5]),
      0LL);

Block initializers

Before:

v10 = 0LL;
v15 = &v10;
v16 = 0x2000000000LL;
v17 = 0;
if ( a1 )
{
  x0_8 = *(NSObject **)(a1 + 16);
  v13.isa = _NSConcreteStackBlock;
  *(_QWORD *)&v13.flags = 0x40000000LL;
  v13.invoke = func_name_block_invoke;
  v13.descriptor = &stru_100211F48;
  v13.lvar3 = a1;
  v13.lvar4 = a2;
  v13.lvar1 = a3;
  v13.lvar2 = &v10;
  dispatch_sync(queue: x0_8, block: &v13);
  v11 = *((_BYTE *)v15 + 24);
}
else
{
  v11 = 0;
}
_Block_object_dispose(&v10, 8);
return v11 & 1;

After:

v10 = _byref_block_arg_init(0);
v10.value = 0;
if ( a1 )
{
  v6 = *(NSObject **)(a1 + 16);
  v9 = _stack_block_init(0x40000000, &stru_100211F48, func_name_block_invoke);
  v9.lvar3 = a1;
  v9.lvar4 = a2;
  v9.lvar1 = a3;
  v9.lvar2 = &v10;
  dispatch_sync(queue: v6, block: &v9);
  value = v10.forwarding->value;
}
else
{
  value = 0;
}
return value & 1;

Collapse os_log

Before:

  v9 = gLogObjects;
  v10 = gNumLogObjects;
  if ( gLogObjects && gNumLogObjects >= 46 )
  {
    v11 = *(NSObject **)(gLogObjects + 360);
  }
  else
  {
    v11 = (NSObject *)&_os_log_default;
    if ( ((v6 ^ (2 * v6)) & 0x4000000000000000LL) != 0 )
      __break(0xC471u);
    if ( os_log_type_enabled(oslog: (os_log_t)&_os_log_default, type: OS_LOG_TYPE_ERROR) )
    {
      *(_DWORD *)buf = 134218240;
      *(_QWORD *)v54 = v9;
      *(_WORD *)&v54[8] = 1024;
      *(_DWORD *)&v54[10] = v10;
      if ( ((v6 ^ (2 * v6)) & 0x4000000000000000LL) != 0 )
        __break(0xC471u);
      _os_log_error_impl(
        dso: (void *)&_mh_execute_header,
        log: (os_log_t)&_os_log_default,
        type: OS_LOG_TYPE_ERROR,
        format: "Make sure you have called init_logging()!\ngLogObjects: %p, gNumLogObjects: %d",
        buf: buf,
        size: 0x12u);
    }
  }
  if ( ((v6 ^ (2 * v6)) & 0x4000000000000000LL) != 0 )
    __break(0xC471u);
  if ( os_log_type_enabled(oslog: v11, type: OS_LOG_TYPE_INFO) )
  {
    if ( a1 )
      v12 = *(_QWORD *)(a1 + 8);
    else
      v12 = 0LL;
    *(_DWORD *)buf = 138412290;
    *(_QWORD *)v54 = v12;
    if ( ((v6 ^ (2 * v6)) & 0x4000000000000000LL) != 0 )
      __break(0xC471u);
    _os_log_impl(
      dso: (void *)&_mh_execute_header,
      log: v11,
      type: OS_LOG_TYPE_INFO,
      format: "Random log %@",
      buf: buf,
      size: 0xCu);
  }

after:

  if ( oslog_info_enabled() )
  {
    if ( a1 )
      v4 = *(_QWORD *)(a1 + 8);
    else
      v4 = 0LL;
    oslog_info("Random log %@", v4);
  }

Automatic casts with OSBaseClass::safeMetaCast

Before:

 OSObject *v5;
 v5 = OSBaseClass::safeMetaCast(a2, &IOThunderboltController::metaClass);

After:

 IOThunderboltController *v5;
 v5 = OSDynamicCast<IOThunderboltController>(a2);

Automatic typing for OSObject_typed_operator_new

Run Edit->Plugins->iOSHelper->Locate all kalloc_type_view before.

Before:

IOAccessoryPowerSourceItemUSB_TypeC_Current *sub_FFFFFFF009B2AA14()
{
  OSObject *v0; // x19

  v0 = (OSObject *)OSObject_typed_operator_new(&UNK_FFFFFFF007DBC480, size: 0x38uLL);
  OSObject::OSObject(this: v0, &IOAccessoryPowerSourceItemUSB_TypeC_Current::gMetaclass)->__vftable = (OSObject_vtbl *)off_FFFFFFF007D941B0;
  OSMetaClass::instanceConstructed(this: &IOAccessoryPowerSourceItemUSB_TypeC_Current::gMetaclass);
  return (IOAccessoryPowerSourceItemUSB_TypeC_Current *)v0;
}

After:

IOAccessoryPowerSourceItemUSB_TypeC_Current *sub_FFFFFFF009B2AA14()
{
  IOAccessoryPowerSourceItemUSB_TypeC_Current *v0; // x19

  v0 = OSObjectTypeAlloc<IOAccessoryPowerSourceItemUSB_TypeC_Current>(0x38uLL);
  OSObject::OSObject(this: v0, &IOAccessoryPowerSourceItemUSB_TypeC_Current::gMetaclass)->__vftable = (OSObject_vtbl *)off_FFFFFFF007D941B0;
  OSMetaClass::instanceConstructed(this: &IOAccessoryPowerSourceItemUSB_TypeC_Current::gMetaclass);
  return v0;
}

Jump to virtual call

Use Shift+X on a virtual call to jump.

Jump to virtual call

Xrefs to selector

Use Ctrl+4 inside an Objective-C method to list xrefs to its selector.

Jump to selector

Call the plugin from python

import idaapi

# Call global analysis
idaapi.load_and_run_plugin("iOS Helper", 1)


# Call local analysis
def write_ea_arg(ea: int):
    n = idaapi.netnode()
    n.create("$ idaioshelper")
    n.altset(1, ea, "R")


write_ea_arg(func_ea)
idaapi.load_and_run_plugin("iOS Helper", 2)

Development

In order to have autocomplete while developing, you need to add IDA's include folder ( $IDA_INSTALLATION/python/3 ) to your IDE.

  • on Visual Studio code you can add the folder to the analyzer's extra paths in the settings.json file:
{
  "python.analysis.extraPaths": [
    "$IDA_INSTALLATION\\python\\3"
  ]
}
  • on PyCharm you can add the folder to the interpreter's paths in the project settings. Alternatively, you can create idapython.pth in $VENV_FOLDER/Lib/site-packages and add the path to it.

Inside IDA, you can use ioshelper.reload() to reload the plugin during development. If you create file name DEBUG inside src/, then you can use F2 to reload the plugin.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ida_ios_helper-1.0.9.tar.gz (857.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ida_ios_helper-1.0.9-py3-none-any.whl (82.9 kB view details)

Uploaded Python 3

File details

Details for the file ida_ios_helper-1.0.9.tar.gz.

File metadata

  • Download URL: ida_ios_helper-1.0.9.tar.gz
  • Upload date:
  • Size: 857.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for ida_ios_helper-1.0.9.tar.gz
Algorithm Hash digest
SHA256 be2241a0efa8fba5e67f23bb3f5b42f4146e070b8f70bcbb4ace53134580d744
MD5 46af561d22512a178a2d2910704010e8
BLAKE2b-256 efff207280788bcb9d980ed7e9dd36b4d97e4743c6076ddb343f5b66aec734c4

See more details on using hashes here.

Provenance

The following attestation bundles were made for ida_ios_helper-1.0.9.tar.gz:

Publisher: pypi.yaml on yoavst/ida-ios-helper

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ida_ios_helper-1.0.9-py3-none-any.whl.

File metadata

  • Download URL: ida_ios_helper-1.0.9-py3-none-any.whl
  • Upload date:
  • Size: 82.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for ida_ios_helper-1.0.9-py3-none-any.whl
Algorithm Hash digest
SHA256 50fe695f6a953613ad3e0f4f3d6c52fdad992ceb607fa04135a7587609fbd689
MD5 f0e6e05bb67e84d9e4220a70b8aac4bf
BLAKE2b-256 824cde2641ff926b19e1291eb05e613a72139d15aa1d21ce44464e0a9c3ac4ed

See more details on using hashes here.

Provenance

The following attestation bundles were made for ida_ios_helper-1.0.9-py3-none-any.whl:

Publisher: pypi.yaml on yoavst/ida-ios-helper

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page