前言

本文提到的相关工具和脚本同步在此:https://github.com/chenls/Crack-Android-system-signature,可以在linux中使用的一站式脚本:run.sh。

在需要使用一些系统层面的API时(如:HIDL服务),我们APK中必须在应用程序的AndroidManifest.xml中的manifest节点中加入android:sharedUserId="android.uid.system"属性,在添加此属性后,往往需要使用Android源码编译或者使用对应的签名文件对APK签名,才能正常安装。

若未使用系统签名时,安装会报如下错误:

$ adb install outputtxBJV3_double.apk 
Performing Streamed Install
adb: failed to install outputtxBJV3_double.apk: Failure [INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: Reconciliation failed...: Reconcile failed: Package com.example.test has no signatures that match those in shared user android.uid.system; ignoring!]

对应的logcat log如下:

04-15 16:38:35.099  1716  1815 W PackageManager: com.android.server.pm.PackageManagerService$ReconcileFailure: Reconcile failed: Package com.example.test has no signatures that match those in shared user android.uid.system; ignoring!
04-15 16:38:35.099  1716  1815 W PackageManager: 	at com.android.server.pm.PackageManagerService.reconcilePackagesLocked(PackageManagerService.java:16974)
04-15 16:38:35.099  1716  1815 W PackageManager: 	at com.android.server.pm.PackageManagerService.installPackagesLI(PackageManagerService.java:17366)
04-15 16:38:35.099  1716  1815 W PackageManager: 	at com.android.server.pm.PackageManagerService.installPackagesTracedLI(PackageManagerService.java:16693)
04-15 16:38:35.099  1716  1815 W PackageManager: 	at com.android.server.pm.PackageManagerService.lambda$processInstallRequestsAsync$22$PackageManagerService(PackageManagerService.java:14770)
04-15 16:38:35.099  1716  1815 W PackageManager: 	at com.android.server.pm.-$$Lambda$PackageManagerService$9znobjOH7ab0F1jsW2oFdNipS-8.run(Unknown Source:6)
04-15 16:38:35.099  1716  1815 W PackageManager: 	at android.os.Handler.handleCallback(Handler.java:938)
04-15 16:38:35.099  1716  1815 W PackageManager: 	at android.os.Handler.dispatchMessage(Handler.java:99)
04-15 16:38:35.099  1716  1815 W PackageManager: 	at android.os.Looper.loop(Looper.java:236)
04-15 16:38:35.099  1716  1815 W PackageManager: 	at android.os.HandlerThread.run(HandlerThread.java:67)
04-15 16:38:35.099  1716  1815 W PackageManager: 	at com.android.server.ServiceThread.run(ServiceThread.java:45)

从log中,我们看到在安装APK时,PackageManagerService.java中抛出了异常。我们可以对出错的地方进行修改,从而破解Android系统签名,直接安装未系统签名的APK,接下来看看具体如何操作吧!

思路

总共分为7个步骤:

  1. 通过Android源码找到PackageManagerService.java最终编译出来对应到设备的文件,也就是/system/framework/services.jar
  2. 从设备中pull出services.jar,提取services.jar中的classes.dex的文件;
  3. 使用baksmali工具将classes.dex转成smali文件;
  4. 修改smali文件中验证签名时报错的代码;
  5. 使用smali工具将smali文件转回dex文件;
  6. 将修改后的dex文件,重新打包成services.jar
  7. push修改后的services.jar到设备,然后重启,此时就可以直接安装未系统签名的APK了。

过程

查看android源码

根据报错信息"has no signatures that match those in shared user"我们可以查到对应的源码PackageManagerServiceUtils.java; 通过源码位置,我们倒推查看编译脚本。services/core/Android.bp 中编译了PackageManagerServiceUtils.java文件,生成services.core library,services/Android.bp 中依赖了services.core library,生成services library,也就是services.jar,对应到设备中/system/framework/services.jar文件。

我们要想对其原有逻辑修改,可以直接修改Android原生源码,编译后再push到设备中验证,但是由于一般的厂家都会对Android原生源码进行定制,导致源码不匹配。所以我们只能将设备当前的/system/framework/services.jar文件pull出来对其修改后,再push到原设备(注意此处需要root权限),以此保证其兼容性。

pull services.jar文件,并解压:

echo "pull /system/framework/services.jar"
adb pull /system/framework/services.jar
echo "backup services.jar to services_bak.jar"
cp services.jar services_bak.jar

echo "unzip services.jar to ./services dir"
rm -rf services
unzip services.jar -d services &>/dev/null

解压services.jar得到classes.dex

查看services文件夹内容,其中有两个dex文件:

$ tree services
services
├── classes2.dex
├── classes.dex
├── com
│   └── ...
└── META-INF
    └── MANIFEST.MF

12 directories, 12 files

根据报错信息,查找我们要修改源码对应的dex文件:

$ tree 
dex=$(grep -lr "has no signatures that match those in shared user" services)
echo "need modify file: $dex"

baksmali反编译

jar工具包:smali和baksmali,此处我们使用目前最新版本:2.5.2。

使用baksmali-2.5.2.jar反编译dex得到smali文件:

echo "dex2smali to ./out dir"
rm -rf out
java -jar baksmali-2.5.2.jar d $dex

修改smali文件

PackageManagerServiceUtils.java的源码:

public static boolean verifySignatures(PackageSetting pkgSetting,
            PackageSetting disabledPkgSetting, PackageParser.SigningDetails parsedSignatures,
            boolean compareCompat, boolean compareRecover)
            throws PackageManagerException {
    ...
    // line:689
    if (!match) {
        throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
                "Package " + packageName
                + " has no signatures that match those in shared user "
                + pkgSetting.getSharedUser().name + "; ignoring!");
    }
    ...
    // line:725
    if (!parsedSignatures.hasCommonAncestor(
            pkgSetting.getSharedUser().signatures.mSigningDetails)) {
        throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
                "Package " + packageName + " has a signing lineage "
                        + "that diverges from the lineage of the sharedUserId");
    }
    ...
}

我们可以直接修改verifySignatures()方法中如上两个if处,使其条件不成立,从而不会抛出异常。

对应到的smali代码,将if-eqz改成if-nez ,即将if==0 改成 if!=0:

.line 725
    invoke-virtual {p2, v5}, Landroid/content/pm/PackageParser$SigningDetails;->hasCommonAncestor(Landroid/content/pm/PackageParser$SigningDetails;)Z

    move-result v5

    if-eqz v5, :cond_16f // 将其改成if-nez v2, :cond_16f,即将if==0 改成 if!=0

    goto :goto_1b1

    .line 727
    :cond_16f

    ...

    const-string v3, " has a signing lineage that diverges from the lineage of the sharedUserId"

    ...

    invoke-direct {v5, v4, v3}, Lcom/android/server/pm/PackageManagerException;-><init>(ILjava/lang/String;)V

    throw v5

    ...



    .line 689
    :cond_105
    const/4 v4, -0x8

    if-eqz v2, :cond_189 // 将其改成if-nez v2, :cond_189 ,即将if==0 改成 if!=0

    ...

    :cond_189
    new-instance v5, Lcom/android/server/pm/PackageManagerException;

    ...

    const-string v3, " has no signatures that match those in shared user "

    invoke-virtual {v6, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

smali相关语法可参考:Smali基础语法总结。

对应bash修改脚本:

modify() {
    # 文件和行号
    file_and_line=$(grep -nr "$1" out)
    if [ -n "$file_and_line" ]; then
        # 文件
        file=$(echo $file_and_line | awk -F ":" '{print $1}')
        # 行号
        line_e=$(echo $file_and_line | awk -F ":" '{print $2}')
        # 倒推30行
        let "line_s=line_e-30"
        # 部分内容
        # cat $file | head -n $line_e | tail -n +$line_s
        modify_content=$(cat $file | head -n $line_e | tail -n +$line_s | grep ':cond_' | tail -1)
        # 需要修改的所在行号
        modify_content_str=$(grep -n $modify_content $file)
        modify_content_line=$(echo $modify_content_str | awk -F ":" '{print $1}')
        # 进行修改
        sed -i "${modify_content_line}s/if-eqz/if-nez/g" $file
        echo "$file:$modify_content_line  modify 'if-eqz --> if-nez'"
    else
        echo "!!! not found: $1"
    fi
}

modify "has no signatures that match those in shared user"
modify "has a signing lineage that diverges from the lineage of the sharedUserId"

smali编译

将修改后的smali文件,编译成dex文件:

echo "smali2dex to ./out.dex file"
java -jar smali-2.5.2.jar a out
echo "move ./out.dex to $dex file"
mv out.dex $dex

重新打包jar

将修改后的classes.dex,打包成jar:

echo "re tar ./services ./services.jar file"
# 也可以归档管理器直接打开,不解压替换classes2.dex
jar cvfm services.jar services/META-INF/MANIFEST.MF -C services/ . &>/dev/null

push services.jar并重启

将修改后的services.jar push到设备并重启:

echo "push ./services.jar file"
adb wait-for-device root
adb remount
adb push services.jar /system/framework/services.jar
adb reboot

总结

在有root权限情况下,我们通过smali相关工具反编译services.jar后,对其修改后,达到了直接安装需要系统签名的APK。