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.11.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.11-py3-none-any.whl (83.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: ida_ios_helper-1.0.11.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.11.tar.gz
Algorithm Hash digest
SHA256 025132e72abadedf312bc556978c86a3e92a5fea4b8eff8b8a1a383ffc16bf4c
MD5 58a7eb2339df5adf35e18ed2483a5286
BLAKE2b-256 bba626b5809f2ecdc1647988c06f26a0744024a884a51b13970a86550f56589f

See more details on using hashes here.

Provenance

The following attestation bundles were made for ida_ios_helper-1.0.11.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.11-py3-none-any.whl.

File metadata

File hashes

Hashes for ida_ios_helper-1.0.11-py3-none-any.whl
Algorithm Hash digest
SHA256 059b26e96559445196ad7c94e0b8129ef9c1e8649aa45b229dbf29f8026365ff
MD5 b3b8fba34ffc0122fb7b323b767ac2e1
BLAKE2b-256 98be9f0c5bd2040ccb4eb4977f61ec3c12a1a344ce3204bf5ea88a2ef58c2521

See more details on using hashes here.

Provenance

The following attestation bundles were made for ida_ios_helper-1.0.11-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