blog / ios-re

How to inject a dylib into an IPA without a jailbreak — pure Go, no Python

blessed0x · 2026-09-08 · ~7 min read · xkvm-ios-injector ↗

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:

  1. Add a load command. The tweak dylib gets referenced via an LC_LOAD_DYLIB pointing at a sane install path — @rpath, @executable_path, or /usr/lib depending 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.
  2. 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:

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