10 Commits
v1.1 ... main

Author SHA1 Message Date
√(noham)²
badb4f4784 Bump CFBundleVersion to 1.4 2026-05-28 20:11:22 +02:00
√(noham)²
05b73595d8 Add 'Delete' action to file options alert 2026-05-28 20:09:59 +02:00
√(noham)²
869e625770 Support decryption of app extensions (.appex) 2026-05-28 19:56:44 +02:00
√(noham)²
5af58eda88 Fix appPath (resolved decryption issue on tvOS 26.5), update README for tvOS 2026-05-28 19:17:56 +02:00
√(noham)²
3d40efe291 Fixed 26.2 SDK errors + AirDrop 'done' bug 2026-05-28 00:20:30 +02:00
straight-tamago
26e6ed4c8d i can't fix 2024-01-28 16:24:43 +09:00
straight-tamago
8c96736b94 Fixed an issue where the Airdrop screen could not be closed. 2024-01-28 16:06:59 +09:00
straight-tamago
491e53072f tipa 2024-01-28 15:55:29 +09:00
straight-tamago
7449682883 ふぁいる 2024-01-26 16:32:38 +09:00
straight-tamago
46184fa020 c 2024-01-26 12:22:00 +09:00
11 changed files with 196 additions and 52 deletions

View File

@@ -1,4 +1,4 @@
TARGET := appletv:clang:16.4:14.0
TARGET := appletv:clang:26.2:14.0
INSTALL_TARGET_PROCESSES = TrollDecrypt
GO_EASY_ON_ME = 1

View File

@@ -1,8 +1,8 @@
# TrollDecrypt
iOS IPA Decrypter for TrollStore
tvOS IPA Decrypter for TrollStore
## How to use
1. Download and install TrollDecrypt from [here](https://github.com/donato-fiore/TrollDecrypt/releases)
1. Download and install TrollDecrypt from [here](https://github.com/NohamR/TrollDecrypt-tvOS/releases)
2. Choose app you want to decrypt.
3. Once finished decrypting, you can get the `.ipa` file from inside the app.
@@ -10,12 +10,14 @@ iOS IPA Decrypter for TrollStore
1. Install [theos](https://theos.dev/docs/installation)
2. Run the commands below
```
git clone https://github.com/donato-fiore/TrollDecrypt.git
git clone https://github.com/NohamR/TrollDecrypt-tvOS.git
cd TrollDecrypt
make package
```
## Credits / Thanks
- [TrollDecrypt-tvOS](https://github.com/straight-tamago/TrollDecrypt-tvOS) by straight-tamago for the original tvOS version of TrollDecrypt
- [TrollDecrypt](https://github.com/donato-fiore/TrollDecrypt) by donato-fiore for the original version of TrollDecrypt
- [TrollDecryptor](https://github.com/wh1te4ever/TrollDecryptor) by wh1te4ever
- [dumpdecrypted](https://github.com/stefanesser/dumpdecrypted) by Stefan Esser
- [bfdecrypt](https://github.com/BishopFox/bfdecrypt) by BishopFox

View File

@@ -10,7 +10,7 @@
<string>App Icon</string>
</dict>
<key>CFBundleIdentifier</key>
<string>com.fiore.trolldecrypt</string>
<string>com.nohamr.trolldecrypt</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
@@ -18,7 +18,7 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.1</string>
<string>1.4</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>

View File

@@ -12,6 +12,6 @@
- (id)initWithPathToBinary:(NSString *)pathToBinary appName:(NSString *)appName appVersion:(NSString *)appVersion;
- (void)createIPAFile:(pid_t)pid;
- (BOOL)dumpDecryptedImage:(const struct mach_header *)image_mh fileName:(const char *)encryptedImageFilenameStr image:(int)imageNum task:(vm_map_t)targetTask;
- (BOOL)dumpDecryptedImage:(vm_address_t)imageAddress fileName:(const char *)encryptedImageFilenameStr image:(int)imageNum task:(vm_map_t)targetTask;
- (NSString *)IPAPath;
@end

View File

@@ -15,6 +15,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <spawn.h>
#include <string.h>
#include <fcntl.h>
#include <dlfcn.h>
@@ -357,9 +358,9 @@ int find_off_cryptid(const char *filePath) {
[self makeDirectories:encryptedImageFilenameStr];
//0x1518
uint32_t aslr_slide = 0;
uint64_t aslr_slide = 0;
get_image_size(imageAddress, targetTask, &aslr_slide);
NSLog(@"[trolldecrypt] aslr_slide= 0x%x", aslr_slide);
NSLog(@"[trolldecrypt] aslr_slide= 0x%llx", aslr_slide);
off_cryptid=find_off_cryptid(encryptedImageFilenameStr);
@@ -504,14 +505,14 @@ int find_off_cryptid(const char *filePath) {
if (task_for_pid(mach_task_self(), pid, &targetTask))
{
NSLog(@"[trolldecrypt] Can't execute task_for_pid! Do you have the right permissions/entitlements?\n");
exit(1);
return;
}
//numberOfImages
struct task_dyld_info dyld_info;
mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
if(task_info(targetTask, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count) != KERN_SUCCESS) exit(1);
if(task_info(targetTask, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count) != KERN_SUCCESS) return;
mach_msg_type_number_t size = sizeof(struct dyld_all_image_infos);
uint8_t* data = readProcessMemory(targetTask, dyld_info.all_image_info_addr, &size);
@@ -524,6 +525,9 @@ int find_off_cryptid(const char *filePath) {
uint32_t numberOfImages = infos->infoArrayCount;
mach_vm_address_t imageAddress = 0;
const char *appPath = [[self appPath] UTF8String];
if(strstr(appPath, "/private") == appPath) {
appPath += 8;
}
NSLog(@"[trolldecrypt] There are %d images mapped.", numberOfImages);
@@ -532,8 +536,8 @@ int find_off_cryptid(const char *filePath) {
mach_msg_type_number_t size3 = PATH_MAX;
uint8_t *fpath_addr = readProcessMemory(targetTask, (mach_vm_address_t) info[i].imageFilePath, &size3);
imageAddress = (struct mach_header *)info[i].imageLoadAddress;
const char *imageName = fpath_addr;
imageAddress = (mach_vm_address_t)info[i].imageLoadAddress;
const char *imageName = (const char *)fpath_addr;
if(!imageName || !imageAddress)
continue;
@@ -553,7 +557,7 @@ int find_off_cryptid(const char *filePath) {
}
-(NSString *)IPAPath {
return [NSString stringWithFormat:@"%@/%@_%@_decrypted.ipa", [self docPath], self.appName, self.appVersion];
return [NSString stringWithFormat:@"%@/%@_%@_decrypted.tipa", [self docPath], self.appName, self.appVersion];
}
-(void) createIPAFile:(pid_t)pid {
@@ -583,6 +587,108 @@ int find_off_cryptid(const char *filePath) {
// Replace encrypted binaries with decrypted versions
NSLog(@"[trolldecrypt] ======== START DECRYPTION PROCESS ========");
[self dumpDecrypted:pid];
// Find embedded app extensions (.appex) and decrypt them
NSLog(@"[trolldecrypt] ======== START APP EXTENSION DECRYPTION ========");
NSString *pluginsPath = [appDir stringByAppendingPathComponent:@"PlugIns"];
NSLog(@"[trolldecrypt] Looking for app extensions in: %@", pluginsPath);
if ([fm fileExistsAtPath:pluginsPath]) {
NSArray *plugins = [fm contentsOfDirectoryAtPath:pluginsPath error:nil];
for (NSString *plugin in plugins) {
if ([plugin hasSuffix:@".appex"]) {
NSString *appexPath = [pluginsPath stringByAppendingPathComponent:plugin];
NSString *executableName = [plugin stringByDeletingPathExtension];
NSString *executablePath = [appexPath stringByAppendingPathComponent:executableName];
if ([fm fileExistsAtPath:executablePath]) {
NSLog(@"[trolldecrypt] Found app extension: %@", executablePath);
pid_t appex_pid = -1;
void *handle = dlopen("/usr/lib/libSystem.dylib", RTLD_NOW);
int (*my_posix_spawnattr_init)(posix_spawnattr_t *) = (int (*)(posix_spawnattr_t *))dlsym(handle, "posix_spawnattr_init");
int (*my_posix_spawnattr_setflags)(posix_spawnattr_t *, short) = (int (*)(posix_spawnattr_t *, short))dlsym(handle, "posix_spawnattr_setflags");
int (*my_posix_spawnattr_destroy)(posix_spawnattr_t *) = (int (*)(posix_spawnattr_t *))dlsym(handle, "posix_spawnattr_destroy");
int (*my_posix_spawn)(pid_t *, const char *, void *, const posix_spawnattr_t *, char *const *, char *const *) = (int (*)(pid_t *, const char *, void *, const posix_spawnattr_t *, char *const *, char *const *))dlsym(handle, "posix_spawn");
if (my_posix_spawn && my_posix_spawnattr_init && my_posix_spawnattr_setflags) {
posix_spawnattr_t attr;
my_posix_spawnattr_init(&attr);
my_posix_spawnattr_setflags(&attr, POSIX_SPAWN_START_SUSPENDED);
const char *argv[] = {[executablePath UTF8String], NULL};
extern char **environ;
int envCount = 0;
while (environ[envCount] != NULL) envCount++;
char **new_environ = malloc((envCount + 2) * sizeof(char *));
for (int i = 0; i < envCount; i++) {
new_environ[i] = environ[i];
}
new_environ[envCount] = "XPC_SERVICE_NAME=appex_decryption";
new_environ[envCount + 1] = NULL;
int ret = my_posix_spawn(&appex_pid, [executablePath UTF8String], NULL, &attr, (char *const *)argv, new_environ);
free(new_environ);
if(my_posix_spawnattr_destroy) my_posix_spawnattr_destroy(&attr);
if (ret == 0 && appex_pid > 0) {
NSLog(@"[trolldecrypt] Spawned appex with PID %d, waiting for dyld...", appex_pid);
vm_map_t targetTask = 0;
if (task_for_pid(mach_task_self(), appex_pid, &targetTask) == KERN_SUCCESS) {
// Resume the suspended process so dyld can map the binaries into memory
kill(appex_pid, SIGCONT);
BOOL dyld_ready = NO;
for (int k = 0; k < 500; k++) {
struct task_dyld_info dyld_info;
mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
if (task_info(targetTask, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count) == KERN_SUCCESS) {
if (dyld_info.all_image_info_addr != 0) {
mach_msg_type_number_t size = sizeof(struct dyld_all_image_infos);
uint8_t* data = readProcessMemory(targetTask, dyld_info.all_image_info_addr, &size);
if (data != NULL) {
struct dyld_all_image_infos* infos = (struct dyld_all_image_infos *) data;
if (infos->infoArrayCount > 0) {
// dyld mapped the images! Suspend the process immediately.
kill(appex_pid, SIGSTOP);
dyld_ready = YES;
// Clean up allocated memory by vm_read in readProcessMemory
vm_deallocate(mach_task_self(), (vm_address_t)data, size);
break;
}
vm_deallocate(mach_task_self(), (vm_address_t)data, size);
}
}
}
usleep(1000); // 1ms
}
if (dyld_ready) {
NSLog(@"[trolldecrypt] Appex dyld mapping caught successfully, dumping!");
[self dumpDecrypted:appex_pid];
} else {
NSLog(@"[trolldecrypt] Failed to catch dyld mapping in time for appex.");
}
} else {
NSLog(@"[trolldecrypt] Can't execute task_for_pid on appex_pid %d", appex_pid);
}
kill(appex_pid, SIGKILL);
// Wait for the child to exit
int status;
waitpid(appex_pid, &status, WNOHANG);
} else {
NSLog(@"[trolldecrypt] Failed to spawn appex (error %d)", ret);
}
}
dlclose(handle);
}
}
}
}
NSLog(@"[trolldecrypt] ======== DECRYPTION COMPLETE ========");
// ZIP it up

View File

@@ -1,5 +1,7 @@
#import "TDFileManagerViewController.h"
#import "TDUtils.h"
#import "LSApplicationProxy+AltList.h"
#import <objc/runtime.h>
@implementation TDFileManagerViewController
@@ -48,12 +50,12 @@
cell.textLabel.text = self.fileList[indexPath.row];
cell.detailTextLabel.text = [dateFormatter stringFromDate:date];
cell.detailTextLabel.textColor = [UIColor systemGray2Color];
cell.detailTextLabel.textColor = [UIColor grayColor];
cell.imageView.image = [UIImage systemImageNamed:@"doc.fill"];
UILabel *label = [[UILabel alloc] init];
label.text = [NSString stringWithFormat:@"%.2f MB", [fileSize doubleValue] / 1000000.0f];
label.textColor = [UIColor systemGray2Color];
label.textColor = [UIColor grayColor];
label.font = [UIFont systemFontOfSize:12.0f];
[label sizeToFit];
label.textAlignment = NSTextAlignmentCenter;
@@ -71,6 +73,7 @@
return YES;
}
#if !TARGET_OS_TV
- (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath {
UIContextualAction *deleteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:@"Delete" handler:^(UIContextualAction *action, UIView *sourceView, void (^completionHandler)(BOOL)) {
NSString *file = self.fileList[indexPath.row];
@@ -82,16 +85,54 @@
UISwipeActionsConfiguration *swipeActions = [UISwipeActionsConfiguration configurationWithActions:@[deleteAction]];
return swipeActions;
}
#endif
UIWindow *kw2 = NULL;
UIAlertController *doneController2 = NULL;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *file = self.fileList[indexPath.row];
NSString *path = [docPath() stringByAppendingPathComponent:file];
NSURL *url = [NSURL fileURLWithPath:path];
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[url] applicationActivities:nil];
[self presentViewController:activityViewController animated:YES completion:nil];
doneController2 = [UIAlertController alertControllerWithTitle:file message:[NSString stringWithFormat:@"Location:\n%@", path] preferredStyle:UIAlertControllerStyleAlert];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Ok", @"Ok") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[kw2 removeFromSuperview];
kw2.hidden = YES;
}];
[doneController2 addAction:okAction];
UIAlertAction *openAction = [UIAlertAction actionWithTitle:@"Share IPA with Airdrop" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
NSString *filePath = path;
NSURL *url = [NSURL fileURLWithPath:filePath];
NSBundle *bundle = [NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/Sharing.framework"];
[bundle load];
NSBundle *sharingUI = [NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/SharingUI.framework"];
[sharingUI load];
id sharingView = [[objc_getClass("SFAirDropSharingViewControllerTV") alloc] initWithSharingItems:@[url]];
[self dismissViewControllerAnimated:NO completion:^{
[self presentViewController:sharingView animated:YES completion:nil];
[sharingView setCompletionHandler:^(NSError *error) {
[self dismissViewControllerAnimated:YES completion:nil];
}];
}];
[self presentViewController:sharingView animated:true completion:nil];
}];
[doneController2 addAction:openAction];
UIAlertAction *deleteFileAction = [UIAlertAction actionWithTitle:@"Delete" style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) {
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
[self refresh];
[kw2 removeFromSuperview];
kw2.hidden = YES;
}];
[doneController2 addAction:deleteFileAction];
[self presentViewController:doneController2 animated:YES completion:nil];
}
@end

View File

@@ -9,13 +9,17 @@
self.apps = appList();
self.title = @"TrollDecrypt";
#if !TARGET_OS_TV
self.navigationController.navigationBar.prefersLargeTitles = YES;
#endif
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"info.circle"] style:UIBarButtonItemStylePlain target:self action:@selector(about:)];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"folder"] style:UIBarButtonItemStylePlain target:self action:@selector(openDocs:)];
#if !TARGET_OS_TV
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:@selector(refreshApps:) forControlEvents:UIControlEventValueChanged];
self.refreshControl = refreshControl;
#endif
}
- (void)viewDidAppear:(bool)animated {
@@ -48,16 +52,18 @@
}
- (void)about:(id)sender {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"TrollDecrypt" message:@"by fiore\nIcon by @super.user\nbfdecrypt by @bishopfox\ndumpdecrypted by @i0n1c\nUpdated for TrollStore by @wh1te4ever" preferredStyle:UIAlertControllerStyleAlert];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"TrollDecrypt" message:@"by fiore\nIcon by @super.user\nbfdecrypt by @bishopfox\ndumpdecrypted by @i0n1c\nUpdated for TrollStore by @wh1te4ever\nPorting to tvOS by @straight-tamago\nFixed by NohamR" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Dismiss" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
}
#if !TARGET_OS_TV
- (void)refreshApps:(UIRefreshControl *)refreshControl {
self.apps = appList();
[self.tableView reloadData];
[refreshControl endRefreshing];
}
#endif
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
@@ -129,4 +135,4 @@
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
@end
@end

View File

@@ -42,4 +42,9 @@ NSArray *decryptedFileList(void);
NSString *docPath(void);
void fetchLatestTrollDecryptVersion(void (^completionHandler)(NSString *version));
void github_fetchLatedVersion(NSString *repo, void (^completionHandler)(NSString *latestVersion));
NSString *trollDecryptVersion(void);
NSString *trollDecryptVersion(void);
@interface SFAirDropSharingViewControllerTV : UIViewController
-(id)initWithSharingItems:(id)arg1;
-(void)setCompletionHandler:(void (^)(NSError *error))arg1;
@end

View File

@@ -3,13 +3,6 @@
#import "LSApplicationProxy+AltList.h"
#import <objc/runtime.h>
@interface SFAirDropSharingViewControllerTV : UIViewController
-(id)initWithSharingItems:(id)arg1;
-(void)setCompletionHandler:(void (^)(NSError *error))arg1;
@end
UIWindow *alertWindow = NULL;
UIWindow *kw = NULL;
UIViewController *root = NULL;
@@ -210,29 +203,20 @@ void bfinject_rocknroll(pid_t pid, NSString *appName, NSString *version) {
UIAlertAction *openAction = [UIAlertAction actionWithTitle:@"Share IPA with Airdrop" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
NSString *filePath = [dd IPAPath];
NSDictionary* airdropDictionary;
NSURL *url = [NSURL fileURLWithPath:filePath];
NSBundle *bundle = [NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/Sharing.framework"];
[bundle load];
NSString *suf = @"/System/Library/PrivateFrameworks/SharingUI.framework";
if ([[NSFileManager defaultManager] fileExistsAtPath:suf]){
NSBundle *sharingUI = [NSBundle bundleWithPath:suf];
[sharingUI load];
}
UIViewController *rvc = [[[UIApplication sharedApplication] keyWindow] rootViewController];
NSURL *url = [NSURL fileURLWithPath:filePath];
NSBundle *sharingUI = [NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/SharingUI.framework"];
[sharingUI load];
id sharingView = [[objc_getClass("SFAirDropSharingViewControllerTV") alloc] initWithSharingItems:@[url]];
[sharingView setCompletionHandler:^(NSError *error) {
NSString *sender = airdropDictionary[@"sender"];
if (sender) {
id defaultWorkspace = [objc_getClass("LSApplicationWorkspace") defaultWorkspace];
[defaultWorkspace performSelector:@selector(openApplicationWithBundleID:) withObject:(id)sender];
}
[root dismissViewControllerAnimated:true completion:nil];
}];
[rvc presentViewController:sharingView animated:true completion:nil];
[root presentViewController:sharingView animated:true completion:nil];
}];
[doneController addAction:openAction];
@@ -257,7 +241,7 @@ NSArray *decryptedFileList(void) {
NSString *file;
while (file = [directoryEnumerator nextObject]) {
if ([[file pathExtension] isEqualToString:@"ipa"]) {
if ([[file pathExtension] isEqualToString:@"ipa"] || [[file pathExtension] isEqualToString:@"tipa"]) {
NSString *filePath = [[docPath() stringByAppendingPathComponent:file] stringByStandardizingPath];
NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:filePath error:nil];
@@ -402,7 +386,7 @@ void github_fetchLatedVersion(NSString *repo, void (^completionHandler)(NSString
}
void fetchLatestTrollDecryptVersion(void (^completionHandler)(NSString *version)) {
github_fetchLatedVersion(@"donato-fiore/TrollDecrypt", completionHandler);
github_fetchLatedVersion(@"straight-tamago/TrollDecrypt-tvOS", completionHandler);
}
NSString *trollDecryptVersion(void) {

View File

@@ -1,8 +1,8 @@
Package: com.fiore.trolldecrypt
Package: com.nohamr.trolldecrypt
Name: TrollDecrypt
Version: 1.1
Version: 1.2
Architecture: iphoneos-arm
Description: An awesome application!
Maintainer: fiore
Author: fiore
Maintainer: nohamr
Author: nohamr
Section: Utilities

View File

@@ -5,7 +5,7 @@
<key>platform-application</key>
<true/>
<key>application-identifier</key>
<string>com.fiore.trolldecrypt</string>
<string>com.nohamr.trolldecrypt</string>
<key>com.apple.security.exception.files.absolute-path.read-write</key>
<array>
<string>/</string>