Skip to content

fix: support injection into Unity apps with dynamically loaded frameworks - #96

Merged
Lessica merged 2 commits into
Lessica:mainfrom
HuuDungg:fix/unity-framework-injection
Apr 13, 2026
Merged

fix: support injection into Unity apps with dynamically loaded frameworks#96
Lessica merged 2 commits into
Lessica:mainfrom
HuuDungg:fix/unity-framework-injection

Conversation

@HuuDungg

Copy link
Copy Markdown
Contributor

Unity-based apps (e.g. Arena of Valor) do not statically link their frameworks via LC_LOAD_DYLIB. Instead, they load frameworks at runtime using dlopen(). This caused frameworkMachOsInBundle() to return an empty intersection, leaving only the main executable (which is typically encrypted) as a candidate.

Changes:

  • Add fallback in frameworkMachOsInBundle(): when the intersection of linked dylibs and enumerated Frameworks/ is empty, use all valid Mach-O files found in Frameworks/ as candidates.
  • Also scan bare .dylib files at level 1 in Frameworks/.
  • Add detailed logging in locateAvailableMachO() to report each candidate's encryption status and file size for easier debugging.

…orks
Unity-based apps (e.g. Arena of Valor) do not statically link their
frameworks via LC_LOAD_DYLIB. Instead, they load frameworks at runtime
using dlopen(). This caused frameworkMachOsInBundle() to return an
empty intersection, leaving only the main executable (which is
typically encrypted) as a candidate.
Changes:
- Add fallback in frameworkMachOsInBundle(): when the intersection of
linked dylibs and enumerated Frameworks/ is empty, use all valid
Mach-O files found in Frameworks/ as candidates.
- Also scan bare .dylib files at level 1 in Frameworks/.
- Add detailed logging in locateAvailableMachO() to report each
candidate's encryption status and file size for easier debugging.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves Mach-O candidate discovery for injection in Unity-based iOS apps that load frameworks dynamically via dlopen(), where static LC_LOAD_DYLIB analysis can miss eligible framework binaries.

Changes:

  • Add verbose candidate logging in locateAvailableMachO() (encryption status + file size) to aid debugging selection failures.
  • Expand frameworkMachOsInBundle() scanning to include bare .dylib files directly under Frameworks/.
  • Add a fallback path: if no statically linked Mach-Os intersect with enumerated Frameworks/ contents, consider all Mach-Os found in Frameworks/ as candidates.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

FileDescription
TrollFools/InjectorV3+Inject.swiftAdds detailed per-candidate logging and selection tracing when choosing an injection target Mach-O.
TrollFools/InjectorV3+Bundle.swiftExtends framework Mach-O enumeration with a Unity-friendly fallback and scans top-level .dylib files in Frameworks/.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +72 to +90
// Scan bare dylibs at level 1 (directly in Frameworks/)
if enumerator.level == 1 && itemURL.pathExtension.lowercased() == "dylib" && isMachO(itemURL) {
allMachOsInFrameworks.append(itemURL)
enumeratedURLs.append(itemURL)
}
}
}

let machOs = linkedDylibs.intersection(enumeratedURLs)
DDLogInfo("Enumerated \(enumeratedURLs.count) items, \(allMachOsInFrameworks.count) Mach-Os in Frameworks/", ddlog: logger)

var machOs = linkedDylibs.intersection(enumeratedURLs)
DDLogInfo("Intersection: \(machOs.count) linked Mach-Os in Frameworks/", ddlog: logger)

// Fallback: if none of the Mach-Os in Frameworks/ are statically linked
// by the main binary (e.g. Unity apps use dlopen), use all available Mach-Os.
if machOs.isEmpty && !allMachOsInFrameworks.isEmpty {
DDLogWarn("No statically linked Mach-Os found, falling back to all \(allMachOsInFrameworks.count) Mach-Os in Frameworks/", ddlog: logger)
machOs = allMachOsInFrameworks
}

CopilotAIApr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback path (machOs.isEmpty => use allMachOsInFrameworks) will include every Mach-O found under Frameworks/, including libswift*.dylib and any other runtime/support dylibs that were previously excluded by resolveLoadCommand (it ignores @rpath/libswift*). This can cause the injector to select a Swift runtime dylib as the target Mach-O, which is likely unintended and can be fragile.

Consider filtering allMachOsInFrameworks (and/or machOs after fallback) to exclude libswift* and other known-ignored binaries (e.g., ignoredDylibAndFrameworkNames) before sorting/selection, so the fallback preserves the prior candidate semantics while still supporting dlopen-based apps.

Copilot uses AI. Check for mistakes.
Comment threadTrollFools/InjectorV3+Inject.swift Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@Lessica
Lessica merged commit 3b781bb into Lessica:mainApr 13, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@HuuDungg@Lessica