blog / ios-re
How to inject a dylib into an IPA without a jailbreak — pure Go, no Python
Every "inject a tweak into an IPA" tutorial funnels you into a GUI and a Python venv. Sideloadly does the clicking for you, pyzule does the piping, and somewhere in between a dozen pip installs are quietly version-drifting. This post is the terminal version: the whole pipeline — fetch an app, inject a dylib, re-sign the container, install it on a device — in real commands, from xkvm, a single static Go binary. No jailbreak, no Python, no venv, no drama.
What injection actually is (two moving parts)
An IPA is a zip containing a .app bundle. Inside it, the binary is a Mach-O file, and "injecting a tweak" means doing exactly two things:
- Add a load command. The tweak dylib gets referenced via an
LC_LOAD_DYLIBpointing at a sane install path —@rpath,@executable_path, or/usr/libdepending on the tweak's layout. Get this wrong and the app won't launch; get the ordering wrong and it launches but crashes on load. - Rebuild the code signature. Every Mach-O in the bundle carries a signature covering its own bytes. Touch the binary and the signature is dead — install the app as-is and iOS rejects it. The signature has to be recomputed after the load command lands, which is the step most hand-rolled scripts fumble.
That's the entire trick. The GUI tools just hide those two steps behind a progress bar. A CLI doesn't need to hide anything.
Install xkvm (one line, no Go needed)
# macOS / Linux — downloads the release binary, puts it on PATH:
curl -fsSL https://raw.githubusercontent.com/blessed0x/xkvm-ios-injector/main/scripts/install.sh | bash
# Windows (PowerShell):
irm https://raw.githubusercontent.com/blessed0x/xkvm-ios-injector/main/scripts/install.ps1 | iex
# …or if you already have Go 1.26+:
go install github.com/blessed0x/xkvm-ios-injector/cmd/xkvm@latest
Sanity check: xkvm --help. If you'd rather click than type, xkvm tui asks the same questions one at a time and drives the exact same engine underneath.
The pipeline, end to end
1 · Get the app you own
You need a decrypted IPA — an app you have the right to modify. xkvm downloads straight from the App Store by Apple ID, sinfs included, no separate downloader tool:
xkvm decrypt 310633997 --apple-id you@example.com --password … # by numeric Apple ID
xkvm decrypt https://apps.apple.com/us/app/…/id310633997 # …or by link, or bundle id
xkvm decrypt --logout # forget the saved login
2 · Get the tweak
Bring your own .dylib, or resolve one straight from a Cydia repo by bundle id — dependencies included, recursively:
xkvm -i App.ipa -o Patched.ipa --fetch com.example.tweak
If you're converting an existing jailbreak package instead, the converters live in the same binary: xkvm rootless -i tweak.deb -o tweak-rootless.deb (rootful → rootless), xkvm roothide, xkvm undeb, xkvm debify.
3 · Inject and re-sign
# one tweak:
xkvm -i App.ipa -o Patched.ipa -f MyTweak.dylib
# several tweaks at once, fakesigned (AppSync / TrollStore path):
xkvm -i App.ipa -o Patched.ipa -f TweakA.dylib -f TweakB.deb -s
# swap in the real ElleKit hooking runtime, or inject the bundled
# sideload-repair dylib set (App Store / keychain login fixes):
xkvm -i App.ipa -o Patched.ipa -f Tweak.dylib --ellekit
xkvm -i App.ipa -o Patched.ipa --patch
Load commands get wired with the correct @rpath/@executable_path references and every binary is re-signed — pure-Go Apple-format signatures, deterministic: same input in, same bytes out.
4 · Check before you install
Merged tweaks crash at launch when a bundle-relative dependency doesn't resolve. Don't discover that on the phone:
xkvm check -i Patched.ipa
5 · Install over a CoreDevice tunnel, no Xcode, no SideStore
xkvm device list # who's plugged in
xkvm device pair # tap "Trust" on the phone, run again
xkvm device install Patched.ipa # stream it onto the device
xkvm device launch com.example.app # run it, get the pid
xkvm device syslog # parsed logs, Ctrl-C to stop
On iOS 17+, install/launch/screenshot need a developer tunnel — xkvm starts one in the background when an operation hits that gate and retries over it. If you'd rather run the tunnel yourself, XKVM_NO_AUTO_TUNNEL=1 prints the manual command instead. pair/info/battery/apps need no tunnel at all.
The honest part about signing
"Without a jailbreak" here means without a Python jailbreak-tooling stack — not without Apple's signature policy. A stock, non-jailbroken device still only installs apps signed by a certificate it accepts. That's the two lanes:
- Fakesign (
-s) — for AppSync or TrollStore installers, which don't validate the signature against Apple. Fast, fine for testing your own devices. - Real signing — your own dev certificate / provisioning, for a stock device. xkvm does the load-command surgery and re-signing; the certificate is still yours to supply. The
--patchrepair dylib set exists precisely because sideloaded apps lose App Store / keychain entitlements and then misbehave.
What xkvm actually kills is the environment roulette: the venv, the pip version drift, the Python-runtime assumptions. One static binary behaves the same on your laptop as it does in CI — which is exactly why the next section matters.
Where the CLI beats the GUI
Sideloadly is a great clicker. It is not a pipeline. The moment your workflow is "inject, verify, install, syslog" more than once, or across a fleet, or on a schedule, a GUI is a human in the loop with nothing to do. xkvm's whole surface is scriptable — flags, exit codes, deterministic output — so the same commands that work on your desk run in a GitHub Actions matrix. That's the angle the tutorial farms can't serve: they write for people who want to click once. This is for people who want it to repeat.
→ xkvm-ios-injector on GitHub · related: pyzule-rw vs xkvm — the comparison · back to the arsenal