I recently updated my Apple TV to tvOS 26.5 to play around with the newest palera1n jailbreak release. I wanted to dump Infuse from the App Store to poke around its internals and see if I could write some custom tweaks for it. Naturally, I reached for TrollDecrypt-tvOS but it didn’t managed to decrypt the app. The tool chain just isn’t fully updated for this environment yet.

Without a working automated decryption tool, I had to fall back to the old-school manual method of dumping the app’s decrypted memory at runtime. I initially learned this from a classic 2016 blog post, and since it’s getting harder to find modern write-ups on this, I figured I’d document the process.

Finding the encrypted segments

When you download an app from the App Store, Apple wraps the main executable (and its extensions) in FairPlay DRM. The iOS/tvOS kernel decrypts this memory on the fly when the app is launched. If you try to throw the raw binary into IDA right now, you’ll just see garbage.

First, I pulled the encrypted .ipa file from the device via SSH, unzipped it, and used otool to find the exact offset and size of the encrypted sections inside the Mach-O binary.

% otool -l ./Payload/infuse.app/infuse | grep crypt
     cryptoff 1949696
    cryptsize 4096
      cryptid 1

The output tells us exactly what we need:

  • cryptid 1: The binary is currently encrypted
  • cryptoff 1949696: The encryption starts at this byte offset
  • cryptsize 4096: The total size of the encrypted chunk

Modern apps aren’t just a single binary though; they have plugins and extensions that are also encrypted. For Infuse, I checked the Top Shelf extension too:

% otool -l ./Payload/infuse.app/PlugIns/tv_shelf.appex/tv_shelf | grep crypt
     cryptoff 73728
    cryptsize 4096
      cryptid 1

You can also visually verify this load command (LC_ENCRYPTION_INFO_64) using a tool like MachOView:

MachOView showing the cryptid flag

Dumping the decrypted executable from memory

Since the kernel decrypts the binary when loading it into RAM, our goal is to attach a debugger to the running process, locate the decrypted segment in memory, and dump it straight to our disk.

First, I forwarded a port over USB to talk to the Apple TV using iproxy:

% iproxy 6666 6666

Then, I SSH’d into the Apple TV and fired up debugserver, telling it to wait for the infuse process to launch:

# debugserver 0.0.0.0:6666 --waitfor=infuse
debugserver-@(#)PROGRAM:LLDB  PROJECT:lldb-16.0.0
 for arm64.
Waiting to attach to process infuse...
Listening to port 6666 for a connection from 0.0.0.0...
Waiting for debugger instructions for process 0.

I launched Infuse on the Apple TV using the remote. The app freezes immediately because debugserver catches it and halts execution.

Back on my Mac, I fired up lldb to connect to the remote session:

% lldb
(lldbinit) platform select remote-tvos
(lldbinit) process connect connect://localhost:6666

Now we need to find out where the app was loaded in memory to defeat ASLR (Address Space Layout Randomization). I used the image list command to grab the base address of the main binary.

(lldbinit) image list infuse
[  0] 7641348F-88DA-314D-97BC-07A65AE77226 0x0000000100108000 /private/var/containers/Bundle/Application/73138C13-4B31-4C17-880B-5CE21ACD0518/infuse.app/infuse (0x0000000100108000)

The base address here is 0x0000000100108000. To find the exact memory location of the decrypted segment, we just add the cryptoff we found earlier (1949696) to this base address. LLDB is smart enough to handle the math inline, so I dumped exactly 4096 bytes starting from that location into a raw binary file:

(lldbinit) memory read --force --outfile ./decrypted.bin --binary --count 4096 0x0000000100108000+1949696

Patching the binary

We have the decrypted bytes, but they are just sitting in an isolated file. We need to stitch them back into our original downloaded binary, overwriting the encrypted garbage.

The trusty dd command is perfect for this. I piped decrypted.bin into the original infuse binary, seeking exactly to the cryptoff offset.

Important: You must use conv=notrunc so dd replaces the bytes in-place without truncating the rest of the executable!

% dd if=decrypted.bin of=Payload/infuse.app/infuse bs=1 seek=1949696 conv=notrunc

The bytes are patched! But we aren’t done yet. If we try to run this binary on a device, the kernel will see that cryptid is still set to 1. It will try to decrypt our already decrypted segment, turning it back into garbage and crashing the app instantly.

Open the patched infuse binary in MachOView, navigate to the LC_ENCRYPTION_INFO_64 block, double-click the cryptid value, and change it from 1 to 0. Save the file.

Repeat for the extension

I had to repeat the exact same process for the tv_shelf.appex extension. The only difference is telling debugserver to --waitfor=tv_shelf, triggering the top shelf UI on the Apple TV, and doing the math with its specific base address and offsets.

(lldbinit) image list tv_shelf
[  0] 1C08EEE5-33E4-36B4-81E3-7BC2659A4273 0x0000000100810000 /private/var/containers/Bundle/Application/BA9E888E-CAFB-4DB0-9161-12F218B2C380/infuse.app/PlugIns/tv_shelf.appex/tv_shelf (0x0000000100810000)
(lldbinit) memory read --force --outfile ./decrypted.bin --binary --count 4096 0x0000000100810000+73728

Stitch it back in:

% dd if=decrypted.bin of=Payload/infuse.app/PlugIns/tv_shelf.appex/tv_shelf bs=1 seek=73728 conv=notrunc

Patch the cryptid to 0 in MachOView for the extension as well.

Packing it back up

With the main binary and all extensions fully decrypted and patched, all that was left was to zip the Payload folder back into an .ipa file:

% zip -r Infuse_8.4.5_decrypted.ipa Payload

This .ipa is now DRM-free. You can throw it into IDA Pro to reverse engineer the classes, or install it on any device using TrollStore or your favorite sideloading tool to inject tweaks ;)

Bonus: patching TrollDecrypt-tvOS

As the original issue was that TrollDecrypt-tvOS wasn’t working, I created a fork, fixed the problem and added app extension decryption. If you would prefer to skip the manual process, you can use my patched version here: https://github.com/NohamR/TrollDecrypt-tvOS.