binaryninja-api
binaryninja-api copied to clipboard
Ability to remove retain/autorelease ARC calls in Objective-C Binaries
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.
PR https://github.com/Vector35/workflow_objc/pull/52 addresses this, but is blocking on Vector35/binaryninja-api#5563
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.
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.
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:
- Only arm64/arm64e are supported.
- The variants of
objc_retain/objc_releasethat 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.
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).