binaryninja-api icon indicating copy to clipboard operation
binaryninja-api copied to clipboard

Ability to remove retain/autorelease ARC calls in Objective-C Binaries

Open jonpalmisc opened this issue 3 years ago • 3 comments

There are numerous functions that produce a lot of noise in Objective-C code, examples include:

  • _objc_retainAutoreleasedReturnValue
  • _objc_retainAutorelease

These are not often the interesting bits of the function, and it might be helpful if these could be optionally hidden to produce more readable decompilation.

jonpalmisc avatar Jul 11 '22 14:07 jonpalmisc

PR https://github.com/Vector35/workflow_objc/pull/52 addresses this, but is blocking on Vector35/binaryninja-api#5563

0cyn avatar Jul 18 '23 18:07 0cyn

Workarounds exist for this:

This plugin: https://github.com/bdash/bn-objc-extras/blob/dev/src/remove_memory_management.rs implements a workflow to remove these functions .

This very legacy PR to workflow_objc also implements this: https://github.com/Vector35/workflow_objc/pull/52/files However it is likely outdated and non-functional in current versions of the product.

0cyn avatar Jun 05 '25 17:06 0cyn

My ARC removal pass works quite well. I'm happy to look at upstreaming it, though I'll need to figure out if it's more painful to rewrite it in C++ or to try to integrate Rust code with the Mach-O / shared cache plug-ins. If it stays in Rust it will need to be reworked to move off my custom instruction matching to directly using the low_level_il types.

There's one remaining rough edge where the resulting HLIL is confusing:

// TODO: Detect calls to `objc_release` that are immediately after a load of a struct field.
// It might be preferable to leave those in place since otherwise the load is left behind.

This shows up as a mystery reference to a struct field:

100069a14    id -[ScanResult init](struct ScanResult* self, SEL sel)

100069a14    {
100069a14        struct ScanResult* result = [super init];
100069a14        
100069a48        if (result) {
100069a54            struct XPScanResult* x0_1 = [XPScanResult new];
100069a58            result->xpScanResult
100069a5c            result->xpScanResult = x0_1;
100069a48        }
100069a48        
100069a78        return result;
100069a14    }

If you disable the pass you can see that the code is releasing any existing object before assigning to the field:

100069a14    id -[ScanResult init](struct ScanResult* self, SEL sel)

100069a14    {
100069a14        struct ScanResult* result = [super init];
100069a14        
100069a48        if (result) {
100069a54            struct XPScanResult* x0_1 = [XPScanResult new];
100069a58            struct XPScanResult* xpScanResult = result->xpScanResult;
100069a5c            result->xpScanResult = x0_1;
100069a64            [xpScanResult release];
100069a48        }
100069a48        
100069a78        return result;
100069a14    }

This can likely be improved after the fact.

bdash avatar Jun 05 '25 17:06 bdash

https://github.com/Vector35/binaryninja-api/pull/7440, first appearing in 5.2.8343-dev, added initial support for this. It is disabled by default since it rewrites IL in a way that changes program semantics. It can be enabled per-function via Obj-C: Remove reference counting calls in the Function Settings context menu or on a wider scale by setting core.function.objectiveC.removeMemoryManagement.

There are two limitations at present:

  1. Only arm64/arm64e are supported.
  2. The variants of objc_retain / objc_release that use a custom calling convention are not removed.

Supporting armv7/x86_64 requires generalizing the IL matching code. Supporting objc_retain_x8 and such requires generalizing the IL rewriting of it.

bdash avatar Oct 20 '25 16:10 bdash

Closing as fixed since the most common cases are handled in Binary Ninja 5.2. https://github.com/Vector35/binaryninja-api/issues/7660 tracks the cases that are not yet handled (support for x86_64 and runtime functions with custom calling conventions).

bdash avatar Nov 18 '25 19:11 bdash