首页
社区
课程
招聘
[原创]苹果Carplay 自定义壁纸实现逻辑分析
发表于: 2025-6-26 10:22 1081

[原创]苹果Carplay 自定义壁纸实现逻辑分析

obaby 活跃值
20
2025-6-26 10:22
1081

虽然在开车的时候,多数时间不会停留在carplay的桌面。然而,当第一次切进来的时候显示的那个桌面背景图片,着实不怎么喜欢。于是就想着能够换掉这个壁纸。

网上搜了一下,基本都是一年前的文章,或者说最新的文章都是一年前的。这就比较尴尬了。
而至于实现工具和方法,到处都是抄来抄去的文章,第一步基本都是安装巨魔助手,Troll app,448K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6@1M7X3!0D9L8s2y4@1L8%4u0W2i4K6u0W2j5i4m8H3
这是一个越狱的应用商店。通过这个越狱的商店安装AirWall,在air wall里面设置壁纸。
ceeK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6G2L8X3g2B7j5h3W2D9j5Y4u0W2j5h3E0Q4x3X3g2U0L8$3#2Q4x3V1k6T1L8r3!0Y4i4K6u0r3j5h3W2J5N6$3q4D9L8q4)9J5c8R3`.`.

这一切看起来似乎完美,但是,这个troll store app 最高支持到ios 17,我现在的18没有越狱,也不想越狱。那么又该怎么搞呢?


自然是自签名,目前爱思助手之类的貌似不支持普通的appstore账号签名安装了。
不过可以通过下面的工具,签名应用然后通过爱思助手安装。

不过可以通过下面的工具安装:

e76K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6A6k6i4S2E0L8#2)9J5k6h3y4G2L8g2)9J5c8Y4y4A6k6r3g2D9L8$3q4V1L8s2W2Q4x3V1j5`.


然而,签名安装之后却发现了另外一个问题,那就是卡在了加载界面。一直在获取目录,后面就进行不下去了。


上网搜了一下,都是说什么连接carplay之后在设置,然而,这就是句废话。连上了也没什么用。
不过,这个app体积不大,直接拉出来。扔到hopper里面看下实现逻辑,也并不复杂。
直接通过目录来获取的当前壁纸,同样替换壁纸也是直接写入文件实现的。

看F5之后的代码就更直观了。


导出代码,直接扔给cursor,让cursor根据f5代码拆分重构代码。

现在代码逻辑就明朗了,更换壁纸的方法主要在AirawWallpaper.m中:

#pragma mark - Helper Methods

- (void)checkingPath {
    // 检查壁纸路径
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *basePath = @"/var/mobile/Containers/Data/Application/";
    
    // 检查是否有权限访问
    if (![fileManager isWritableFileAtPath:basePath]) {
        UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"权限错误"
                                                                      message:@"无法访问系统目录,请确保设备已越狱并授予了正确的权限。"
                                                               preferredStyle:UIAlertControllerStyleAlert];
        [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]];
        [self presentViewController:alert animated:YES completion:nil];
        return;
    }
    
    NSError *error = nil;
    NSArray *contents = [fileManager contentsOfDirectoryAtPath:basePath error:&error];
    if (error) {
        NSLog(@"Error reading directory: %@", error);
        return;
    }
    
    NSMutableArray *validPaths = [NSMutableArray array];
    for (NSString *path in contents) {
        if ([path containsString:@"com.apple.CarPlayApp.wallpaper-images"]) {
            NSString *fullPath = [basePath stringByAppendingPathComponent:path];
            BOOL isDirectory;
            if ([fileManager fileExistsAtPath:fullPath isDirectory:&isDirectory] && isDirectory) {
                [validPaths addObject:path];
            }
        }
    }
    
    if (validPaths.count > 0) {
        self.FullCache = [basePath stringByAppendingPathComponent:validPaths[0]];
        [self.tableView reloadData];
    } else {
        UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"错误"
                                                                      message:@"未找到 CarPlay 壁纸目录,请确保已正确安装 CarPlay 应用。"
                                                               preferredStyle:UIAlertControllerStyleAlert];
        [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]];
        [self presentViewController:alert animated:YES completion:nil];
    }
}

当然,上面这段代码的错误提示是我让cursor加上的。原来的并没有这段,这个是f5的代码:

int ___30-[AirawWallpaper checkingPath]_block_invoke(int arg0) {
    r31 = r31 - 0xc0;
    saved_fp = r29;
    stack[-8] = r30;
    var_80 = arg0;
    var_18 = [[NSFileManager defaultManager] retain];
    r0 = [var_18 enumeratorAtPath:@"/var/mobile/Containers/Data/Application/"];
    r29 = &saved_fp;
    var_20 = [r0 retain];
    var_28 = [@"" retain];
    do {
            r0 = [var_20 nextObject];
            r29 = r29;
            r0 = [r0 retain];
            r8 = var_28;
            var_28 = r0;
            [r8 release];
            if (r0 == 0x0) {
                break;
            }
            if ([var_28 rangeOfString:@"com.apple.CarPlayApp.wallpaper-images"] == 0x7fffffffffffffff) {
                continue;
            }
            r0 = [@"/var/mobile/Containers/Data/Application/" stringByAppendingPathComponent:var_28];
            r29 = r29;
            [var_18 fileExistsAtPath:[r0 retain] isDirectory:r29 - 0x29];
            if ((var_29 & 0x1) != 0x0) {
                    [*(var_80 + 0x20) addObject:var_28];
            }
            objc_storeStrong(r29 - 0x48, 0x0);
    } while (true);
    r11 = *(var_80 + 0x28);
    *(&var_78 + 0x10) = 0x100007ae0;
    *(&var_78 + 0x18) = 0x100014200;
    *(&var_78 + 0x20) = [*(var_80 + 0x30) retain];
    *(&var_78 + 0x28) = [*(var_80 + 0x20) retain];
    dispatch_async(r11, &var_78);
    objc_storeStrong(&var_78 + 0x28, 0x0);
    objc_storeStrong(&var_78 + 0x20, 0x0);
    objc_storeStrong(r29 - 0x28, 0x0);
    objc_storeStrong(r29 - 0x20, 0x0);
    r0 = objc_storeStrong(r29 - 0x18, 0x0);
    return r0;
}

为了这些功能尝试添加自定义权限:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "7e7K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6h3q4H3M7r3I4W2i4K6u0W2j5$3!0E0i4K6u0r3c8q4c8p5M7#2)9J5c8W2m8J5L8%4m8W2M7Y4c8&6e0r3W2K6N6q4)9J5k6o6q4Q4x3X3f1H3i4K6u0W2k6s2c8V1i4K6t1$3M7i4g2G2N6q4)9K6b7W2)9J5y4X3N6@1i4K6y4n7
<plist version="1.0">
<dict>
    <!-- 基本权限 -->
    <key>application-identifier</key>
    <string>49U329UV5Q.by.oba.airwall</string>
    
    <key>com.apple.developer.team-identifier</key>
    <string>49U329UV5Q</string>
    
    <key>get-task-allow</key>
    <true/>
    
    <key>keychain-access-groups</key>
    <array>
        <string>49U329UV5Q.*</string>
        <string>com.apple.token</string>
    </array>
    
    <!-- 容器访问权限 (私有API) -->
    <key>com.apple.private.security.container-manager</key>
    <true/>
    
    <key>com.apple.private.security.disk-device-access</key>
    <true/>
    
    <key>com.apple.private.security.system-container</key>
    <true/>
    
    <!-- 文件系统完全访问 -->
    <key>com.apple.private.security.no-container</key>
    <true/>
    
    <key>com.apple.private.security.no-sandbox</key>
    <true/>
    
    <!-- TCC绕过权限 -->
    <key>com.apple.private.tcc.allow</key>
    <array>
        <string>kTCCServiceSystemPolicyAllFiles</string>
        <string>kTCCServiceSystemPolicyDesktopFolder</string>
        <string>kTCCServiceSystemPolicyDocumentsFolder</string>
        <string>kTCCServiceSystemPolicyDownloadsFolder</string>
        <string>kTCCServiceAppleEvents</string>
    </array>
    
    <!-- 根权限访问 -->
    <key>com.apple.private.security.storage.SystemPolicyAllFiles</key>
    <true/>
    
    <!-- 平台应用权限 -->
    <key>platform-application</key>
    <true/>
    
    <!-- 跳过验证 -->
    <key>com.apple.private.skip-library-validation</key>
    <true/>
    
    <!-- 任务访问权限 -->
    <key>task_for_pid-allow</key>
    <true/>
    
    <!-- 进程调试权限 -->
    <key>com.apple.system-task-ports</key>
    <true/>
    
    <!-- 网络权限 -->
    <key>com.apple.security.network.client</key>
    <true/>
    <key>com.apple.security.network.server</key>
    <true/>
</dict>
</plist>


既然添加了自定义的权限,那么使用这些签名工具就没办法写入权限文件了,需要自己来实现签名,现在就只能创建签名脚本签名了,来回修改多次之后,让cursor创建了一个自签名脚本,sign_with_args.sh。通过这个东西就可以快速签名文件了。

#!/bin/bash

# 变量声明(无默认值)
MOBILEPROVISION=""
APP_PATH=""
CERTIFICATE=""
BUNDLE_ID=""
TEAM_ID=""
DEVICE_UDID=""
ENTITLEMENTS_FILE=""  # 新增:自定义 Entitlements 文件路径

# 显示帮助信息
show_help() {
    echo "用法: $0 [选项]"
    echo "选项:"
    echo "  -p, --provision    mobileprovision文件路径 (必需)"
    echo "  -a, --app-path     应用路径 (必需)"
    echo "  -c, --certificate  证书名称 (必需)"
    echo "  -b, --bundle-id    Bundle ID (必需)"
    echo "  -t, --team-id      Team ID (必需)"
    echo "  -d, --device-udid  设备UDID (必需)"
    echo "  -e, --entitlements Entitlements文件路径 (可选,默认从mobileprovision提取)"
    echo "  -h, --help         显示此帮助信息"
    exit 0
}

# 检查必需参数
check_required_params() {
    local missing=0
    if [ -z "$MOBILEPROVISION" ]; then
        echo "错误: 缺少 mobileprovision 文件路径 (-p)"
        missing=1
    fi
    if [ -z "$APP_PATH" ]; then
        echo "错误: 缺少应用路径 (-a)"
        missing=1
    fi
    if [ -z "$CERTIFICATE" ]; then
        echo "错误: 缺少证书名称 (-c)"
        missing=1
    fi
    if [ -z "$BUNDLE_ID" ]; then
        echo "错误: 缺少 Bundle ID (-b)"
        missing=1
    fi
    if [ -z "$TEAM_ID" ]; then
        echo "错误: 缺少 Team ID (-t)"
        missing=1
    fi
    if [ -z "$DEVICE_UDID" ]; then
        echo "错误: 缺少设备 UDID (-d)"
        missing=1
    fi
    if [ $missing -eq 1 ]; then
        echo "请使用 -h 或 --help 查看帮助信息"
        exit 1
    fi
}

# 解析命令行参数
while [[ $# -gt 0 ]]; do
    case $1 in
        -p|--provision)
            MOBILEPROVISION="$2"
            shift 2
            ;;
        -a|--app-path)
            APP_PATH="$2"
            shift 2
            ;;
        -c|--certificate)
            CERTIFICATE="$2"
            shift 2
            ;;
        -b|--bundle-id)
            BUNDLE_ID="$2"
            shift 2
            ;;
        -t|--team-id)
            TEAM_ID="$2"
            shift 2
            ;;
        -d|--device-udid)
            DEVICE_UDID="$2"
            shift 2
            ;;
        -e|--entitlements)
            ENTITLEMENTS_FILE="$2"
            shift 2
            ;;
        -h|--help)
            show_help
            ;;
        *)
            echo "未知选项: $1"
            show_help
            ;;
    esac
done

# 检查必需参数
check_required_params

# 清理函数
cleanup() {
    echo "清理临时文件..."
    rm -f temp_entitlements.plist
}

# 错误处理
handle_error() {
    echo "错误: $1"
    cleanup
    exit 1
}

# 检查文件是否存在
if [ ! -f "$MOBILEPROVISION" ]; then
    handle_error "mobileprovision 文件不存在: $MOBILEPROVISION"
fi

# 检查目录是否存在
if [ ! -d "$APP_PATH" ]; then
    handle_error "应用目录不存在: $APP_PATH"
fi

# 如果提供了自定义 Entitlements 文件,检查其是否存在
if [ ! -z "$ENTITLEMENTS_FILE" ] && [ ! -f "$ENTITLEMENTS_FILE" ]; then
    handle_error "Entitlements 文件不存在: $ENTITLEMENTS_FILE"
fi

# 验证配置文件
echo "验证配置文件..."
PROFILE_INFO=$(security cms -D -i "$MOBILEPROVISION")
if [ $? -ne 0 ]; then
    handle_error "配置文件无效"
fi

# 提取信息
echo "正在从 mobileprovision 提取信息..."
PROFILE_TEAM_ID=$(echo "$PROFILE_INFO" | plutil -extract TeamIdentifier.0 raw -)
PROFILE_APP_ID=$(echo "$PROFILE_INFO" | plutil -extract Entitlements.application-identifier raw -)
PROFILE_EXPIRATION=$(echo "$PROFILE_INFO" | plutil -extract ExpirationDate raw -)

echo "Profile Team ID: $PROFILE_TEAM_ID"
echo "Profile Application ID: $PROFILE_APP_ID"
echo "Profile Expiration: $PROFILE_EXPIRATION"

# 验证 Team ID
if [ "$PROFILE_TEAM_ID" != "$TEAM_ID" ]; then
    echo "警告: Team ID 不匹配"
    echo "Profile中的: $PROFILE_TEAM_ID"
    echo "设置的: $TEAM_ID"
    handle_error "请确保使用正确的 mobileprovision 文件"
fi

# 验证设备 UDID
echo "验证设备 UDID..."
if ! security cms -D -i "$MOBILEPROVISION" | grep -A 20 ProvisionedDevices | grep -q "$DEVICE_UDID"; then
    echo "错误: 设备 UDID $DEVICE_UDID 未包含在 provisioning profile 中"
    echo "包含的设备:"
    security cms -D -i "$MOBILEPROVISION" | grep -A 20 ProvisionedDevices
    handle_error "设备未授权"
fi
echo "设备 UDID 验证通过: $DEVICE_UDID"

# 设置完整的 Bundle ID
FULL_BUNDLE_ID="$PROFILE_APP_ID"
echo "使用 Bundle ID: $FULL_BUNDLE_ID"

# 清理旧的签名
echo "清理旧的签名..."
rm -rf "$APP_PATH/_CodeSignature" 2>/dev/null

# 复制 mobileprovision 并设置权限
echo "复制 mobileprovision..."
cp "$MOBILEPROVISION" "$APP_PATH/embedded.mobileprovision"
chmod 644 "$APP_PATH/embedded.mobileprovision"

# 处理 Entitlements
if [ ! -z "$ENTITLEMENTS_FILE" ]; then
    echo "使用自定义 Entitlements 文件: $ENTITLEMENTS_FILE"
    cp "$ENTITLEMENTS_FILE" temp_entitlements.plist
else
    echo "从 mobileprovision 提取 Entitlements..."
    security cms -D -i "$MOBILEPROVISION" | plutil -extract Entitlements xml1 -o - - > temp_entitlements.plist
fi

# 显示 entitlements 内容
echo "Entitlements 内容:"
plutil -p temp_entitlements.plist

# 设置正确的文件权限
echo "设置文件权限..."
chmod -R 755 "$APP_PATH"
find "$APP_PATH" -type f -exec chmod 644 {} \;
find "$APP_PATH" -name "*.dylib" -exec chmod 755 {} \;
[ -f "$APP_PATH/AirWall" ] && chmod 755 "$APP_PATH/AirWall"
[ -f "$APP_PATH/AirWallHelper" ] && chmod 755 "$APP_PATH/AirWallHelper"
[ -f "$APP_PATH/trollstorehelper" ] && chmod 755 "$APP_PATH/trollstorehelper"

# 移除空文件(特别是0字节的PNG文件)
echo "移除空文件..."
find "$APP_PATH" -type f -size 0 -delete
echo "已移除空文件"

# 修正Info.plist中的Bundle ID
echo "修正Info.plist中的Bundle ID..."
plutil -replace CFBundleIdentifier -string "$PROFILE_APP_ID" "$APP_PATH/Info.plist"
echo "Bundle ID已设置为: $(plutil -extract CFBundleIdentifier raw "$APP_PATH/Info.plist")"

# 对可执行文件进行签名
echo "对可执行文件进行签名..."
if [ -f "$APP_PATH/AirWallHelper" ]; then
    echo "签名 AirWallHelper..."
    codesign -f -s "$CERTIFICATE" --entitlements temp_entitlements.plist "$APP_PATH/AirWallHelper" || handle_error "AirWallHelper 签名失败"
fi

if [ -f "$APP_PATH/trollstorehelper" ]; then
    echo "签名 trollstorehelper..."
    codesign -f -s "$CERTIFICATE" --entitlements temp_entitlements.plist "$APP_PATH/trollstorehelper" || handle_error "trollstorehelper 签名失败"
fi

# 对所有的 frameworks 和 dylibs 进行签名
echo "签名 frameworks 和 dylibs..."
find "$APP_PATH" -name "*.framework" -o -name "*.dylib" | while read -r file; do
    echo "签名: $file"
    codesign -f -s "$CERTIFICATE" "$file" || handle_error "Framework/dylib 签名失败: $file"
done

# 对整个应用进行签名
echo "对整个应用进行签名..."
codesign -f -s "$CERTIFICATE" --entitlements temp_entitlements.plist --deep "$APP_PATH" || handle_error "应用签名失败"

# 验证签名
echo "验证签名..."
codesign -vv -d "$APP_PATH"

# 检查_CodeSignature目录
echo "检查_CodeSignature目录..."
if [ -d "$APP_PATH/_CodeSignature" ]; then
    echo "✓ _CodeSignature 目录存在"
    ls -la "$APP_PATH/_CodeSignature/"
else
    echo "⚠️  _CodeSignature 目录不存在,这可能导致安装失败"
fi

# 显示更多签名信息
echo "显示详细签名信息..."
codesign -d --entitlements :- "$APP_PATH"

# 验证所有签名
echo "验证所有签名..."
if codesign --verify --deep --strict --verbose=4 "$APP_PATH"; then
    echo "✓ 严格验证通过"
else
    echo "⚠️  严格验证失败,但基本签名可能仍然有效"
fi

# 清理临时文件
cleanup

# 重新打包
echo "重新打包..."
rm -f signed_AirWall.ipa
zip -qry signed_AirWall.ipa Payload

echo "签名完成!"

然而,在测试的时候发现com.apple.private私有权限,签名之后安装全部被拒绝了。那么,也就是说目前是没办法在非越狱系统访问这个文件的,自然也就没法更改壁纸。

所以,要想换壁纸,那就先越狱吧。


[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!

收藏
免费 0
支持
分享
最新回复 (1)
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
哈哈 前面看得津津有味,最后发现还是得越狱
2025-10-16 21:09
0
游客
登录 | 注册 方可回帖
返回