L'outil en ligne de commande sblaunch, issu de la suite sbutils, permet d'initier le lancement d'applications iOS depuis un terminal, avec la possibilité d'injecter une URL ou des arguments spécifiques. Bien que largement utilisé dans les environnements jailbreakés, son fonctionnement repose sur des appels système privés dont la signature et l'implémentation restent obscures.
Structure du code source et appel système
Le programme s'appuie sur le framework SpringBoardServices pour interagir avec le gestionnaire d'interface iOS. La déclaration initiale suggère une fonction prenant sept paramètres, mais l'analyse binaire révèle une réalité différente.
#include <CoreFoundation/CoreFoundation.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define OPT_UNLOCK_DEVICE 0x04
#define OPT_WAIT_DEBUGGER 0x402
#define OPT_BACKGROUND 0x01
extern int SBSLaunchApplicationForDebugging(CFStringRef bundle, CFURLRef url, CFArrayRef args, CFDictionaryRef env);
extern bool SBSProcessIDForDisplayIdentifier(CFStringRef bundle, pid_t *out_pid);
int main(int argc, char *argv[]) {
int opt;
int launch_flags = OPT_UNLOCK_DEVICE;
bool show_pid = false;
const char *target_url_str = NULL;
const char *app_identifier = NULL;
while ((opt = getopt(argc, argv, "pdbu:")) != -1) {
switch (opt) {
case 'p': show_pid = true; break;
case 'd': launch_flags |= OPT_WAIT_DEBUGGER; break;
case 'b': launch_flags |= OPT_BACKGROUND; break;
case 'u': target_url_str = optarg; break;
default:
fprintf(stderr, "Syntaxe: %s [-p] [-d] [-b] [-u url] <bundleID> [args...]\n", argv[0]);
return EXIT_FAILURE;
}
}
if (optind >= argc) {
fprintf(stderr, "Erreur: identifiant du bundle manquant.\n");
return EXIT_FAILURE;
}
app_identifier = argv[optind++];
CFMutableArrayRef extra_args = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
for (int i = optind; i < argc; i++) {
CFStringRef arg_ref = CFStringCreateWithCString(kCFAllocatorDefault, argv[i], kCFStringEncodingUTF8);
CFArrayAppendValue(extra_args, arg_ref);
CFRelease(arg_ref);
}
CFStringRef bundle_ref = CFStringCreateWithCString(kCFAllocatorDefault, app_identifier, kCFStringEncodingUTF8);
CFURLRef url_ref = NULL;
if (target_url_str) {
url_ref = CFURLCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)target_url_str, strlen(target_url_str), kCFStringEncodingUTF8, NULL);
if (!url_ref) {
fprintf(stderr, "Format d'URL invalide.\n");
CFRelease(bundle_ref);
CFRelease(extra_args);
return EXIT_FAILURE;
}
}
int status = SBSLaunchApplicationForDebugging(bundle_ref, url_ref, extra_args, NULL);
if (status != 0) {
fprintf(stderr, "Échec du lancement (code: %d)\n", status);
} else if (show_pid) {
pid_t current_pid = 0;
while (!SBSProcessIDForDisplayIdentifier(bundle_ref, ¤t_pid)) {
usleep(50000);
}
printf("%d\n", current_pid);
}
if (url_ref) CFRelease(url_ref);
CFRelease(bundle_ref);
CFRelease(extra_args);
return (status == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}
Disparité entre l'en-tête et le binaire
Une analyse statique via un désassembleur met en évidence que la fonction privée ne consomme en réalité que quatre arguments, contrairement aux sept paramètres initialmeent déclarés dans les en-têtes communautaires. Les paramètres liés à la redirection de la sortie standard (stdout, stderr) et aux variables d'environnement sont ignorés ou gérés différemment par le démon SpringBoard.
int __cdecl main_reversed(int arg_count, const char **arg_vec, const char **env_vec) {
const char *bundle_id = NULL;
const char *url_input = NULL;
unsigned int exec_mode = 0x04;
char flag_pid = 0;
int parsed;
while ((parsed = getopt(arg_count, (char *const *)arg_vec, "pdbu:")) != -1) {
if (parsed == 'p') flag_pid = 1;
else if (parsed == 'd') exec_mode |= 0x402;
else if (parsed == 'b') exec_mode |= 0x01;
else if (parsed == 'u') url_input = optarg;
else return print_usage();
}
if (optind >= arg_count) return print_usage();
bundle_id = arg_vec[optind];
void *args_array = CFArrayCreateMutable(0);
for (int idx = optind + 1; idx < arg_count; idx++) {
void *str_obj = CFStringCreateWithCString(0, arg_vec[idx], 0x08000100);
CFArrayAppendValue(args_array, str_obj);
}
void *id_ref = CFStringCreateWithCString(0, bundle_id, 0x08000100);
void *url_ref = 0;
if (url_input) {
url_ref = CFURLCreateWithBytes(0, (const UInt8*)url_input, strlen(url_input), 0x08000100, 0);
if (!url_ref) {
fwrite("invalid URL\n", 1, 12, stderr);
return 1;
}
}
int res = SBSLaunchApplicationForDebugging(id_ref, url_ref, args_array, 0);
if (res) {
fprintf(stderr, "Launch failed: %d\n", res);
return 1;
}
if (flag_pid) {
pid_t out_pid;
while (!SBSProcessIDForDisplayIdentifier(id_ref, &out_pid)) usleep(50000);
printf("%d\n", out_pid);
}
return 0;
}
Privilèges et localisation de la bibliothèque
Pour que l'exécutable puisse invoquer ces routines privées sans être rejeté par le sandbox iOS, il doit embarquer une signature spécifique autorisant l'interaction avec SpringBoard :
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>com.apple.springboard.launchapplications</key>
<true/>
</dict>
</plist>
La fonction SBSLaunchApplicationForDebugging réside historiquement dans le framework privé SpringBoardServices.framework, situé sous /System/Library/PrivateFrameworks/. Sur les versions récentes d'iOS, Apple a migré ou restreint l'accès à ces symboles, les déplaçant parfois vers BackBoardServices ou les masquant derrière des vérifications de droits renforcées. L'absence du fichier binaire attendu dans les arborescentes classiques provient généralement de la mise en cache du dyld shared cache (dyld_shared_cache_arm64), qui fusionne les frameworks système en une seule image mémoire. L'extraction nécessite l'utilisation d'outils comme dsc_extractor ou dyld_shared_cache_util pour retrouver les symboles exportés.