Framework--PackageManagerService--签名部分

  如果一个菲系统签名的APK,安装系统中需要platform的权限,如何做?思路有以下几个:

  • 修改APK的签名,也就是伪装签名,(常用在游戏破解)

  • 增加用户分组,需要Linux系统底层支持,改起来比较烦。类似与一个文件系统chmod 777这可以让所有用户去读写的方式。

  • 伪装APP,需要依附系统APP,取得对应的context。

  • 最简单的方法(可以改FW源码)

        我们这里对Android的系统packagemanager做下分析,用第一种方式去做,当然方法不止一种。最简单的方式是在install过程中将APK签名给替换掉。

        首先找到install方法:

    private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {

        final int installFlags = args.installFlags;

        final String installerPackageName = args.installerPackageName;

        final String volumeUuid = args.volumeUuid;

        final File tmpPackageFile = new File(args.getCodePath());

        final boolean forwardLocked = ((installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0);

        final boolean onExternal = (((installFlags & PackageManager.INSTALL_EXTERNAL) != 0)

                || (args.volumeUuid != null));

        final boolean instantApp = ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0);

        final boolean fullApp = ((installFlags & PackageManager.INSTALL_FULL_APP) != 0);

        final boolean forceSdk = ((installFlags & PackageManager.INSTALL_FORCE_SDK) != 0);

        final boolean virtualPreload =

                ((installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);

        boolean replace = false;

        int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;

        if (args.move != null) {

            // moving a complete application; perform an initial scan on the new install location

            scanFlags |= SCAN_INITIAL;

        //…..

        //这里是对package的解析,Android是一个Linux系统,本身也支持最小安装包PKG的形式install,常见于各种主机系统

     // Retrieve PackageSettings and parse package

        final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY

                | PackageParser.PARSE_ENFORCE_CODE

                | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)

                | (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0)

                | (instantApp ? PackageParser.PARSE_IS_EPHEMERAL : 0)

                | (forceSdk ? PackageParser.PARSE_FORCE_SDK : 0);

        PackageParser pp = new PackageParser();               



        pp.setSeparateProcesses(mSeparateProcesses);

        pp.setDisplayMetrics(mMetrics);

        pp.setCallback(mPackageParserCallback);



        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");

        final PackageParser.Package pkg;

        try {

            //该方法可以进去看一下,里面有各种读取file的信息,解压APK,变成PKG

            pkg = pp.parsePackage(tmpPackageFile, parseFlags);

        } catch (PackageParserException e) {

            res.setError("Failed parse during installPackageLI", e);

            return;

        } finally {

            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);

        }

        //pkg解压成功后,就可以替换签名了



        // sepyioth 的代码

         pkg.mSignatures = mPlatformPackage.mSignatures;

        后面是对PKG的解析,最后写入packagelist中。

        2、较为温柔的方法:

        改签名,同时替换pkg中applicationinfo的数据。在鉴权方法中加入相应的代码。

 

  • packagemanager简单解析

        packageManagerServer中值得注意的是一个handler,具体代码如下:

        安装后,看到logcat当中关于packageManagerServer的打印:     

2019-02-13 17:04:01.863 1478-1504/? I/PackageManager: Verification timed out for file:///data/app/vmdl1517622653.tmp

2019-02-13 17:04:01.865 1478-1504/? I/PackageManager: Continuing with installation of file:///data/app/vmdl1517622653.tmp

2019-02-13 17:18:25.023 1478-1504/? I/PackageManager: Verification timed out for file:///data/app/vmdl799982913.tmp

2019-02-13 17:18:25.023 1478-1504/? I/PackageManager: Continuing with installation of file:///data/app/vmdl799982913.tmp

2019-02-13 17:18:26.939 1478-1504/? I/PackageManager: Package com.glearnlite.glearnslite codePath changed from /data/app/com.glearnlite.glearnslite-vgazT9dw8ofDXzWwSi9i3A== to /data/app/com.glearnlite.glearnslite-XIym3-QRJ07wwUoF29Lzyg==; Retaining data and using new

2019-02-13 17:18:26.940 1478-1504/? W/PackageManager: Code path for com.glearnlite.glearnslite changing from /data/app/com.glearnlite.glearnslite-vgazT9dw8ofDXzWwSi9i3A== to /data/app/com.glearnlite.glearnslite-XIym3-QRJ07wwUoF29Lzyg==

2019-02-13 17:18:26.940 1478-1504/? W/PackageManager: Resource path for com.glearnlite.glearnslite changing from /data/app/com.glearnlite.glearnslite-vgazT9dw8ofDXzWwSi9i3A== to /data/app/com.glearnlite.glearnslite-XIym3-QRJ07wwUoF29Lzyg==

2019-02-13 17:23:46.198 1478-1504/? I/PackageManager: Verification timed out for file:///data/app/vmdl1435423611.tmp

2019-02-13 17:23:46.199 1478-1504/? I/PackageManager: Continuing with installation of file:///data/app/vmdl1435423611.tmp

2019-02-13 17:23:47.567 1478-1504/? I/PackageManager: Package com.glearnlite.glearnslite codePath changed from /data/app/com.glearnlite.glearnslite-XIym3-QRJ07wwUoF29Lzyg== to /data/app/com.glearnlite.glearnslite-JmuO1tKN1J8P2v4CnAVqPg==; Retaining data and using new

2019-02-13 17:23:47.568 1478-1504/? W/PackageManager: Code path for com.glearnlite.glearnslite changing from /data/app/com.glearnlite.glearnslite-XIym3-QRJ07wwUoF29Lzyg== to /data/app/com.glearnlite.glearnslite-JmuO1tKN1J8P2v4CnAVqPg==

2019-02-13 17:23:47.578 1478-1504/? W/PackageManager: Resource path for com.glearnlite.glearnslite changing from /data/app/com.glearnlite.glearnslite-XIym3-QRJ07wwUoF29Lzyg== to /data/app/com.glearnlite.glearnslite-JmuO1tKN1J8P2v4CnAVqPg==

2019-02-28 17:07:20.514 1478-1504/? I/PackageManager: Verification timed out for file:///data/app/vmdl1633228157.tmp

2019-02-28 17:07:20.515 1478-1504/? I/PackageManager: Continuing with installation of file:///data/app/vmdl1633228157.tmp

2019-02-28 17:07:22.851 1478-1504/? I/PackageManager: Package com.glearnlite.glearnslite codePath changed from /data/app/com.glearnlite.glearnslite-JmuO1tKN1J8P2v4CnAVqPg== to /data/app/com.glearnlite.glearnslite-OjYkLAV8BNY73a8fAqZG6A==; Retaining data and using new

2019-02-28 17:07:22.852 1478-1504/? W/PackageManager: Code path for com.glearnlite.glearnslite changing from /data/app/com.glearnlite.glearnslite-JmuO1tKN1J8P2v4CnAVqPg== to /data/app/com.glearnlite.glearnslite-OjYkLAV8BNY73a8fAqZG6A==

2019-02-28 17:07:22.852 1478-1504/? W/PackageManager: Resource path for com.glearnlite.glearnslite changing from /data/app/com.glearnlite.glearnslite-JmuO1tKN1J8P2v4CnAVqPg== to /data/app/com.glearnlite.glearnslite-OjYkLAV8BNY73a8fAqZG6A==

2019-03-07 15:36:03.624 1478-1504/? I/PackageManager.DexOptimizer: Running dexopt (dexoptNeeded=1) on: /data/app/vmdl124466309.tmp/base.apk pkg=com.ecarx.genesis.fastadb isa=x86 dexoptFlags=boot_complete,debuggable,public target-filter=quicken oatDir=/data/app/vmdl124466309.tmp/oat sharedLibraries=null

2019-03-07 15:36:07.071 1478-1504/? E/PackageManager: Adding duplicate shared id: 1000 name=com.ecarx.genesis.fastadb

2019-03-07 15:36:07.095 1478-1504/? W/PackageManager: com.android.server.pm.Installer$InstallerException: android.os.ServiceSpecificException: Failed to delete /data/user_de/0/com.ecarx.genesis.fastadb (code 2)

2019-03-07 15:36:07.104 1478-1504/? W/PackageManager: Package couldn't be installed in /data/app/com.ecarx.genesis.fastadb-56lTioF07krIj4Qtvk1KfA==

    com.android.server.pm.PackageManagerException: Package com.ecarx.genesis.fastadb has no signatures that match those in shared user android.uid.system; ignoring!

        at com.android.server.pm.PackageManagerService.verifySignaturesLP(PackageManagerService.java:9147)

        at com.android.server.pm.PackageManagerService.scanPackageDirtyLI(PackageManagerService.java:10373)

        at com.android.server.pm.PackageManagerService.scanPackageLI(PackageManagerService.java:10058)

        at com.android.server.pm.PackageManagerService.scanPackageTracedLI(PackageManagerService.java:10034)

        at com.android.server.pm.PackageManagerService.installNewPackageLIF(PackageManagerService.java:16935)

        at com.android.server.pm.PackageManagerService.installPackageLI(PackageManagerService.java:18176)

        at com.android.server.pm.PackageManagerService.installPackageTracedLI(PackageManagerService.java:17731)

        at com.android.server.pm.PackageManagerService.-wrap33(Unknown Source:0)

        at com.android.server.pm.PackageManagerService$6.run(PackageManagerService.java:15202)

        at android.os.Handler.handleCallback(Handler.java:789)

        at android.os.Handler.dispatchMessage(Handler.java:98)

        at android.os.Looper.loop(Looper.java:164)

        at android.os.HandlerThread.run(HandlerThread.java:65)

        at com.android.server.ServiceThread.run(ServiceThread.java:46)

 

        查阅系统源代码,发现,安装的入口有一个:  

private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {

        final int installFlags = args.installFlags;

        final String installerPackageName = args.installerPackageName;

        final String volumeUuid = args.volumeUuid;

        

        、、、、



        try (PackageFreezer freezer = freezePackageForInstall(pkgName, installFlags,

                "installPackageLI")) {

            if (replace) {

                if (pkg.applicationInfo.isStaticSharedLibrary()) {

                    // Static libs have a synthetic package name containing the version

                    // and cannot be updated as an update would get a new package name,

                    // unless this is the exact same version code which is useful for

                    // development.

                    PackageParser.Package existingPkg = mPackages.get(pkg.packageName);

                    if (existingPkg != null && existingPkg.mVersionCode != pkg.mVersionCode) {

                        res.setError(INSTALL_FAILED_DUPLICATE_PACKAGE, "Packages declaring "

                                + "static-shared libs cannot be updated");

                        return;

                    }

                }

                replacePackageLIF(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,

                        installerPackageName, res, args.installReason);

            } else {

                installNewPackageLIF(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,

                        args.user, installerPackageName, volumeUuid, res, args.installReason);

            }

        }

        。。。。。

 

         可以看到对应的出错位置,上面的dexOptimizer吧APK中的信息解析完毕,但是在Add的时候报错,首先看到源码段:

private void replaceSystemPackageLIF(PackageParser.Package deletedPackage,

            PackageParser.Package pkg, final int policyFlags, int scanFlags, UserHandle user,

            int[] allUsers, String installerPackageName, PackageInstalledInfo res,

            int installReason) {

        if (DEBUG_INSTALL) Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg

                + ", old=" + deletedPackage);

 

        final boolean disabledSystem;

 

        // Remove existing system package

        removePackageLI(deletedPackage, true);

        这一段是替换系统应用的代码段,异常爆出的代码段。

 

    /*

     * Install a non-existing package.

     */

    private void installNewPackageLIF(PackageParser.Package pkg, final int policyFlags,

            int scanFlags, UserHandle user, String installerPackageName, String volumeUuid,

            PackageInstalledInfo res, int installReason) {

        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installNewPackage");

 

        // Remember this for later, in case we need to rollback this install

        String pkgName = pkg.packageName;

 

        if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + pkg);

 

        synchronized(mPackages) {

 

        这一段是安装一个新application所在的代码入口。以新安装为例,我们找到newPacakageLIF的定义方法,找到以下出错代码段:

    try {

            //异常代码段

            PackageParser.Package newPackage = scanPackageTracedLI(pkg, policyFlags, scanFlags,

                    System.currentTimeMillis(), user);

 

            updateSettingsLI(newPackage, installerPackageName, null, res, user, installReason);

 

            if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {

                prepareAppDataAfterInstallLIF(newPackage);

 

            } else {

                // Remove package from internal structures, but keep around any

                // data that might have already existed

                deletePackageLIF(pkgName, UserHandle.ALL, false, null,

                        PackageManager.DELETE_KEEP_DATA, res.removedInfo, true, null);

            }

        } catch (PackageManagerException e) {

            res.setError("Package couldn't be installed in " + pkg.codePath, e);

        }

 

        跟踪scanPackageTracedLI这个方法到scanPackageDirtyLI方法,具体如下定义:

    private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg,

            final int policyFlags, final int scanFlags, long currentTime, @Nullable UserHandle user)

                    throws PackageManagerException {

        if (DEBUG_PACKAGE_SCANNING) {

            if ((policyFlags & PackageParser.PARSE_CHATTY) != 0)

                Log.d(TAG, "Scanning package " + pkg.packageName);

        }

 

        applyPolicy(pkg, policyFlags);

 

        assertPackageIsValid(pkg, policyFlags, scanFlags);

 

        // Initialize package source and resource directories

        final File scanFile = new File(pkg.codePath);

        final File destCodeFile = new File(pkg.applicationInfo.getCodePath());

        final File destResourceFile = new File(pkg.applicationInfo.getResourcePath());

 

        SharedUserSetting suid = null;

        PackageSetting pkgSetting = null;

 

        // Getting the package setting may have a side-effect, so if we

        // are only checking if scan would succeed, stash a copy of the

        // old setting to restore at the end.

        PackageSetting nonMutatedPs = null;

        。。。。。

}

        一步一步排查,我们找到了系统签名认证的代码段,也就是问题出错的代码段:

   } else {

                try {

                    // SIDE EFFECTS; compareSignaturesCompat() changes KeysetManagerService

                    // 这里对package的签名做了限制,如果签名认证有问题,会报异常。

                    verifySignaturesLP(signatureCheckPs, pkg);

                    // We just determined the app is signed correctly, so bring

                    // over the latest parsed certs.

                    pkgSetting.signatures.mSignatures = pkg.mSignatures;

                } catch (PackageManagerException e) {

                    if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {

                        throw e;

                    }

                    // The signature has changed, but this package is in the system

                    // image...  let's recover!

                    pkgSetting.signatures.mSignatures = pkg.mSignatures;

                    // However...  if this package is part of a shared user, but it

                    // doesn't match the signature of the shared user, let's fail.

                    // What this means is that you can't change the signatures

                    // associated with an overall shared user, which doesn't seem all

                    // that unreasonable.

                    if (signatureCheckPs.sharedUser != null) {

                        if (compareSignatures(signatureCheckPs.sharedUser.signatures.mSignatures,

                                pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) {

                            throw new PackageManagerException(

                                    INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,

                                    "Signature mismatch for shared user: "

                                            + pkgSetting.sharedUser);

                        }

                    }

                    // File a report about this.

                    String msg = "System package " + pkg.packageName

                            + " signature changed; retaining data.";

                    reportSettingsProblem(Log.WARN, msg);

                }

        接下来分析一下鉴权签名的那段函数方法:

private void verifySignaturesLP(PackageSetting pkgSetting, PackageParser.Package pkg)

            throws PackageManagerException {

        if (pkgSetting.signatures.mSignatures != null) {

            //已安装的APP,签名认证

            // Already existing package. Make sure signatures match

            boolean match = compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSignatures)

                    == PackageManager.SIGNATURE_MATCH;

            if (!match) {

                match = compareSignaturesCompat(pkgSetting.signatures, pkg)

                        == PackageManager.SIGNATURE_MATCH;

            }

            if (!match) {

                match = compareSignaturesRecover(pkgSetting.signatures, pkg)

                        == PackageManager.SIGNATURE_MATCH;

            }

            if (!match) {

                throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "

                        + pkg.packageName + " signatures do not match the "

                        + "previously installed version; ignoring!");

            }

        }

        //认证用户签名

        // Check for shared user signatures

        if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) {

            // Already existing package. Make sure signatures match

            boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,

                    pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;

            if (!match) {

                match = compareSignaturesCompat(pkgSetting.sharedUser.signatures, pkg)

                        == PackageManager.SIGNATURE_MATCH;

            }

            if (!match) {

                match = compareSignaturesRecover(pkgSetting.sharedUser.signatures, pkg)

                        == PackageManager.SIGNATURE_MATCH;

            }

            if (!match) {

                throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,

                        "Package " + pkg.packageName

                        + " has no signatures that match those in shared user "

                        + pkgSetting.sharedUser.name + "; ignoring!");

            }

        }

    }

 

        成员变量注意:

        Bool mPromoteSystemApps

        PackageParser.Package mPlatformPackage;

        verifySignaturesLP方法内,对比了pkgsetting的签名和对应安装APK的签名是否一致,首先判断是签名,然后判断shareduserid系统签名.这里可以了解到的是,以下的一个流程:

        在最后的一个地方,写入到settings的列表中的包括应用的签名信息,在APP启动的时候,不会去拿去APK本身的签名,而是用setting中记录的签名信息、userID等。

        签名信息的具体内容如下:

 

      private final byte[] mSignature;

43    private int mHashCode;

44    private boolean mHaveHashCode;

45    private SoftReference<String> mStringRef;

46    private Certificate[] mCertificateChain;

        对以上参数加log打印后会发现:

2019-03-15 16:08:28.717 1554-1581/system_process I/sephyioths: signature :android.content.pm.Signature@6ec3a8c7size 1

2019-03-15 16:08:28.717 1554-1581/system_process I/sephyioths: mCertificates :OpenSSLRSAPublicKey{modulus=d2d2c486d96c2f18b30d16f7901f29d2ba6ec30430745b3dda69361f4eb41de7ce82f64502d9a156a8bf5a1fb9d90255f5a61c3a93ed9f279221d1bdbe8e278e9f84b254da635283cd514bf740170c137709e10a4e087d9c3622a00bafd3f108ff68d5d377dc65546d97f15141843558bec132d149c1b99e69871bae4fa1cecdafb0710bed060a198c30db7a86223ae1cfddbc63452aabcc1015af3eec0ae3804a95858b1cb281ffaaee37dce1c7aac90476fceacb0ad2a4b0db5e2c2ca982194e02afe2cae2fec6694baeb12a6ca452963ca7824dae2c6c6ecd43e6a7ba525a2ac457259fe7ae77eca393d40f8874f3f423ae435da73d0ad04754bb564dad0f,publicExponent=10001}

2019-03-15 16:08:28.717 1554-1581/system_process I/sephyioths: mSigningKeys :{OpenSSLRSAPublicKey{modulus=d2d2c486d96c2f18b30d16f7901f29d2ba6ec30430745b3dda69361f4eb41de7ce82f64502d9a156a8bf5a1fb9d90255f5a61c3a93ed9f279221d1bdbe8e278e9f84b254da635283cd514bf740170c137709e10a4e087d9c3622a00bafd3f108ff68d5d377dc65546d97f15141843558bec132d149c1b99e69871bae4fa1cecdafb0710bed060a198c30db7a86223ae1cfddbc63452aabcc1015af3eec0ae3804a95858b1cb281ffaaee37dce1c7aac90476fceacb0ad2a4b0db5e2c2ca982194e02afe2cae2fec6694baeb12a6ca452963ca7824dae2c6c6ecd43e6a7ba525a2ac457259fe7ae77eca393d40f8874f3f423ae435da73d0ad04754bb564dad0f,publicExponent=10001}}

    参加对比的是publicKey.通过上述的方法,可以伪装签名安装。package的对应类

/**

5818     * Representation of a full package parsed from APK files on disk. A package

5819     * consists of a single base APK, and zero or more split APKs.

5820     */

5821    public final static class Package implements Parcelable {

5822

5823        public String packageName;

5824

增加user的方法(root 即可,或者伪装App)

        在Android 6.0之后,增加了SELinux的支持方式,破解权限没那么容易了。就算破解了上层应用的锁,也无法得到system的系统权限。查阅系统的代码,发现了权限鉴权的结构如下:

        从上面文章我们了解到了,APK->PKG部分,解析过后会把代码的dex和资源解压到固定的data路径中,然后在packages.xml中去指定路径、user、签名等信息。最后Framework会从packages.xml中读取对应的信息,然后在对应的group中区fork对应的context。但是在Android高板本中,这里的判断增加了一个,就是SELinux的鉴权机制。如果packages.xml的信息和实际底层的签名校验不对,就会导致AIT在fork context的时候会失败,可能无法获取系统的控制权导致APP无法正常启动。这里看了整个流程,不难发现两个核心配置文件:

  • plat_mac_permissions.xml

  • Plat_seapp_contexts

        这两个文件,一个是关于系统的角色判断的配置,一个是关于角色配置权限的。现在假定设想:

        

        如果一个APP,我没有platform系统签名,但是需要获取系统的权限,那么需要做些什么呢?

isSystemServer=true domain=system_server

user=system seinfo=platform domain=system_app type=system_app_data_file

user=system seinfo=genesis domain=system_app type=system_app_data_file

user=bluetooth seinfo=platform domain=bluetooth type=bluetooth_data_file

user=nfc seinfo=platform domain=nfc type=nfc_data_file

user=radio seinfo=platform domain=radio type=radio_data_file

user=shared_relro domain=shared_relro

user=shell seinfo=platform domain=shell type=shell_data_file

user=_isolated domain=isolated_app levelFrom=user

user=_app seinfo=media domain=mediaprovider name=android.process.media type=app_data_file levelFrom=user

user=_app seinfo=platform domain=platform_app type=app_data_file levelFrom=user

user=_app isV2App=true isEphemeralApp=true domain=ephemeral_app type=app_data_file levelFrom=user

user=_app isPrivApp=true domain=priv_app type=app_data_file levelFrom=user

user=_app minTargetSdkVersion=26 domain=untrusted_app type=app_data_file levelFrom=user

user=_app domain=untrusted_app_25 type=app_data_file levelFrom=user

       答案很简单,我们做个实验,首先在root的情况下,找到etc/selinux/plat_mac_permissions.xml和plat_seapp_contexts这两个文件。在上面添加一个第三方的user,比如说“genesis”,并且分配映射的user是system,domain是系统的App,并且缓存用的是系统app的data空间,如下:        然,接下去我们需要告诉系统,这个角色是什么,打开plat_mac_permissions.xml的配置文件,添加从packages.xml中获取的签名信息:

<?xml version="1.0" encoding="iso-8859-1"?><!-- AUTOGENERATED FILE DO NOT MODIFY --><policy><signer signature="308204a830820390a003020102020900b3998086d056cffa300d06092a864886f70d0101040500308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d301e170d3038303431353232343035305a170d3335303930313232343035305a308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d30820120300d06092a864886f70d01010105000382010d003082010802820101009c780592ac0d5d381cdeaa65ecc8a6006e36480c6d7207b12011be50863aabe2b55d009adf7146d6f2202280c7cd4d7bdb26243b8a806c26b34b137523a49268224904dc01493e7c0acf1a05c874f69b037b60309d9074d24280e16bad2a8734361951eaf72a482d09b204b1875e12ac98c1aa773d6800b9eafde56d58bed8e8da16f9a360099c37a834a6dfedb7b6b44a049e07a269fccf2c5496f2cf36d64df90a3b8d8f34a3baab4cf53371ab27719b3ba58754ad0c53fc14e1db45d51e234fbbe93c9ba4edf9ce54261350ec535607bf69a2ff4aa07db5f7ea200d09a6c1b49e21402f89ed1190893aab5a9180f152e82f85a45753cf5fc19071c5eec827020103a381fc3081f9301d0603551d0e041604144fe4a0b3dd9cba29f71d7287c4e7c38f2086c2993081c90603551d230481c13081be80144fe4a0b3dd9cba29f71d7287c4e7c38f2086c299a1819aa48197308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d820900b3998086d056cffa300c0603551d13040530030101ff300d06092a864886f70d01010405000382010100572551b8d93a1f73de0f6d469f86dad6701400293c88a0cd7cd778b73dafcc197fab76e6212e56c1c761cfc42fd733de52c50ae08814cefc0a3b5a1a4346054d829f1d82b42b2048bf88b5d14929ef85f60edd12d72d55657e22e3e85d04c831d613d19938bb8982247fa321256ba12d1d6a8f92ea1db1c373317ba0c037f0d1aff645aef224979fba6e7a14bc025c71b98138cef3ddfc059617cf24845cf7b40d6382f7275ed738495ab6e5931b9421765c491b72fb68e080dbdb58c2029d347c8b328ce43ef6a8b15533edfbe989bd6a48dd4b202eda94c6ab8dd5b8399203daae2ed446232e4fe9bd961394c6300e5138e3cfd285e6e4e483538cb8b1b357"><seinfo value="platform"/></signer><signer signature="308204a830820390a003020102020900f2b98e6123572c4e300d06092a864886f70d0101040500308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d301e170d3038303431353233343035375a170d3335303930313233343035375a308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d30820120300d06092a864886f70d01010105000382010d00308201080282010100ae250c5a16ef97fc2869ac651b3217cc36ba0e86964168d58a049f40ce85867123a3ffb4f6d949c33cf2da3a05c23eacaa57d803889b1759bcf59e7c6f21890ae25085b7ed56aa626c0989ef9ccd36362ca0e8d1b9603fd4d8328767926ccc090c68b775ae7ff30934cc369ef2855a2667df0c667fd0c7cf5d8eba655806737303bb624726eabaedfb72f07ed7a76ab3cb9a381c4b7dcd809b140d891f00213be401f58d6a06a61eadc3a9c2f1c6567285b09ae09342a66fa421eaf93adf7573a028c331d70601ab3af7cc84033ece7c772a3a5b86b0dbe9d777c3a48aa9801edcee2781589f44d9e4113979600576a99410ba81091259dad98c6c68ff784b8f020103a381fc3081f9301d0603551d0e04160414ca293caa8bc0ed3e542eef4205a2bff2b57e4d753081c90603551d230481c13081be8014ca293caa8bc0ed3e542eef4205a2bff2b57e4d75a1819aa48197308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d820900f2b98e6123572c4e300c0603551d13040530030101ff300d06092a864886f70d0101040500038201010084de9516d5e4a87217a73da8487048f53373a5f733f390d61bdf3cc9e5251625bfcaa7c3159cae275d172a9ae1e876d5458127ac542f68290dd510c0029d8f51e0ee156b7b7b5acdb394241b8ec78b74e5c42c5cafae156caf5bd199a23a27524da072debbe378464a533630b0e4d0ffb7e08ecb701fadb6379c74467f6e00c6ed888595380792038756007872c8e3007af423a57a2cab3a282869b64c4b7bd5fc187d0a7e2415965d5aae4e07a6df751b4a75e9793c918a612b81cd0b628aee0168dc44e47b10d3593260849d6adf6d727dc24444c221d3f9ecc368cad07999f2b8105bc1f20d38d41066cc1411c257a96ea4349f5746565507e4e8020a1a81"><seinfo value="media"/></signer><signer signature="308201dd30820146020101300d06092a864886f70d010105050030373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b3009060355040613025553301e170d3137303631343039303931375a170d3437303630373039303931375a30373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b300906035504061302555330819f300d06092a864886f70d010101050003818d0030818902818100a4167f15f36e5b4a3e952c7649eb4dd9058461eec093c56b6b3c6b53f3d812cf02174dc9390d720a71f0780ecb2ce1e7aada8db97ebbdbddcbdd919342d1a54aff03db31e77431ec804667a899bb253c9baad2f8507b3ee765b704dce747093569deb7f2186bae82d2b23ec84bb152e2543581fe7c95132c9ef1642f6766cd070203010001300d06092a864886f70d01010505000381810004721239b29e08420b53e391b65a5c7b6c43c887c7d5c9b9644a4d222c1abfb88653f1f5788fbf9df3d42df697ae91f4e7d5b7b2632ccb5550d18ba0665dfcd1a140057621ab52c1dfd38687c5870ef6b0f94025709e9040046d902225d6a69fc9773b2dcaa8b110758cc00a711dd33eedf00a2552cdc126c493eb58e5b90858"><seinfo value="ecarx"/></signer><signer signature="308201dd30820146020101300d06092a864886f70d010105050030373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b3009060355040613025553301e170d3139303431303038333031385a170d3439303430323038333031385a30373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b300906035504061302555330819f300d06092a864886f70d010101050003818d0030818902818100847921352005e08c76c02c1bdbac738c0774c77f547603298d2c890da601964af5f3938d0b0ee809b689bc5a699c818ff1c0801052816899c3da2e6568c810c5c626cd66d34a0720a663c28ef3992cddee0a312d8419bc0cd82ff9b78d749615e7fe5dc61334d0772cfe629a35a3969344f9fb4ff4e7529738a000b6e3e1c5c70203010001300d06092a864886f70d01010505000381810070c62a36ea1ef22fb7f3ad976ecbe418f34add55bf451b79227551ccf0d7994a8c870fb2248a0c5e60cf2cc4c53671eecb128170c029a49336eaaf52c6d22d06bd7638d8b4e9c24e62e894b153bc9ebd24650648f0ab636664d6864f89070b7211ff462e77681ae4c95650442503902a20e2670423f9a457811ed61681862430"><seinfo value="genesis"/></signer></policy>

 

       如此之后,编写一个应用测试,比如设置时间。就可以发现,用了自签签名的应用,有了系统的权限。想必大家也明白了,在packageManagerServer中,会读取系统的配置选项,如果开启了SELinux的鉴权权限,会在SELinuxMMAC中检测对应的value,检测的条件判断就是签名证书的publicKey。

   private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg,

            final int policyFlags, final int scanFlags, long currentTime, @Nullable UserHandle user)

                    throws PackageManagerException {

        if (DEBUG_PACKAGE_SCANNING) {

          }

        。。。。。

        if (mFoundPolicyFile) {

           SELinuxMMAC.assignSeInfoValue(pkg);

         }

         pkg.applicationInfo.uid = pkgSetting.appId;

         pkg.mExtras = pkgSetting;

        如此,在进一步,如何在系统的system.img中增设,不再依赖人工修改。这个就得去查脚本了。找到这两个文件修改的脚本点在system/sepolicy/android.mk这个脚本中,我们打开对应的代码段:

##################################

include $(CLEAR_VARS)

 

LOCAL_MODULE := plat_mac_permissions.xml

LOCAL_MODULE_CLASS := ETC

LOCAL_MODULE_TAGS := optional

LOCAL_MODULE_PATH := $(TARGET_OUT)/etc/selinux

 

include $(BUILD_SYSTEM)/base_rules.mk

 

# Build keys.conf

plat_mac_perms_keys.tmp := $(intermediates)/plat_keys.tmp

$(plat_mac_perms_keys.tmp): PRIVATE_ADDITIONAL_M4DEFS := $(LOCAL_ADDITIONAL_M4DEFS)

$(plat_mac_perms_keys.tmp): $(call build_policy, keys.conf, $(PLAT_PRIVATE_POLICY))

    @mkdir -p $(dir $@)

    $(hide) m4 -s $(PRIVATE_ADDITIONAL_M4DEFS) $^ > $@

 

all_plat_mac_perms_files := $(call build_policy, mac_permissions.xml, $(PLAT_PRIVATE_POLICY))

 

# Should be synced with keys.conf.

all_plat_keys := platform media shared testkey

all_plat_keys := $(all_keys:%=$(dir $(DEFAULT_SYSTEM_DEV_CERTIFICATE))/%.x509.pem)

 

$(LOCAL_BUILT_MODULE): PRIVATE_MAC_PERMS_FILES := $(all_plat_mac_perms_files)

$(LOCAL_BUILT_MODULE): $(plat_mac_perms_keys.tmp) $(HOST_OUT_EXECUTABLES)/insertkeys.py \

$(all_plat_mac_perms_files) $(all_plat_keys)

    @mkdir -p $(dir $@)

    $(hide) DEFAULT_SYSTEM_DEV_CERTIFICATE="$(dir $(DEFAULT_SYSTEM_DEV_CERTIFICATE))" \

        $(HOST_OUT_EXECUTABLES)/insertkeys.py -t $(TARGET_BUILD_VARIANT) -c $(TOP) $< -o $@ $(PRIVATE_MAC_PERMS_FILES)

 

all_mac_perms_files :=

all_plat_keys :=

plat_mac_perms_keys.tmp :=

        上述脚本生成了plat_mac_permissions.xml的配置,默认分配了两个角色,一个是platform和media。我们找到Mac_permissions.xml,并且在里面,我们是可以看到,Android是可以支持多个platform签名的,其次,对应的包也可以有对应的配置。但是这里只增加一个user即可:

<?xml version="1.0" encoding="utf-8"?>

<policy>

 

<!--

 

    * A signature is a hex encoded X.509 certificate or a tag defined in

      keys.conf and is required for each signer tag. The signature can

      either appear as a set of attached cert child tags or as an attribute.

    * A signer tag must contain a seinfo tag XOR multiple package stanzas.

    * Each signer/package tag is allowed to contain one seinfo tag. This tag

      represents additional info that each app can use in setting a SELinux security

      context on the eventual process as well as the apps data directory.

    * seinfo assignments are made according to the following rules:

      - Stanzas with package name refinements will be checked first.

      - Stanzas w/o package name refinements will be checked second.

      - The "default" seinfo label is automatically applied.

 

    * valid stanzas can take one of the following forms:

 

     // single cert protecting seinfo

     <signer signature="@PLATFORM" >

       <seinfo value="platform" />

     </signer>

 

     // multiple certs protecting seinfo (all contained certs must match)

     <signer>

       <cert signature="@PLATFORM1"/>

       <cert signature="@PLATFORM2"/>

       <seinfo value="platform" />

     </signer>

 

     // single cert protecting explicitly named app

     <signer signature="@PLATFORM" >

       <package name="com.android.foo">

         <seinfo value="bar" />

       </package>

     </signer>

 

     // multiple certs protecting explicitly named app (all certs must match)

     <signer>

       <cert signature="@PLATFORM1"/>

       <cert signature="@PLATFORM2"/>

       <package name="com.android.foo">

         <seinfo value="bar" />

       </package>

     </signer>

-->

 

    <!-- Platform dev key in AOSP -->

    <signer signature="@PLATFORM" >

      <seinfo value="platform" />

    </signer>

 

    <!-- Media key in AOSP -->

    <signer signature="@MEDIA" >

      <seinfo value="media" />

    </signer>

 

    <!-- user genesis add signer -->

    <signer signature="308201dd30820146020101300d06092a864886f70d010105050030373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b3009060355040613025553301e170d3139303431303038333031385a170d3439303430323038333031385a30373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b300906035504061302555330819f300d06092a864886f70d010101050003818d0030818902818100847921352005e08c76c02c1bdbac738c0774c77f547603298d2c890da601964af5f3938d0b0ee809b689bc5a699c818ff1c0801052816899c3da2e6568c810c5c626cd66d34a0720a663c28ef3992cddee0a312d8419bc0cd82ff9b78d749615e7fe5dc61334d0772cfe629a35a3969344f9fb4ff4e7529738a000b6e3e1c5c70203010001300d06092a864886f70d01010505000381810070c62a36ea1ef22fb7f3ad976ecbe418f34add55bf451b79227551ccf0d7994a8c870fb2248a0c5e60cf2cc4c53671eecb128170c029a49336eaaf52c6d22d06bd7638d8b4e9c24e62e894b153bc9ebd24650648f0ab636664d6864f89070b7211ff462e77681ae4c95650442503902a20e2670423f9a457811ed61681862430"><seinfo value="genesis"/></signer>

 

</policy>

 

        同理,app_context也这样做,否则会报错。

 

 

2019-05-22 19:49:49.679 2437-2437/? E/SELinux: seapp_context_lookup:  No match for app with uid 1000, seinfo default, name com.genesis.testapplication

2019-05-22 19:49:49.679 2437-2437/? E/SELinux: selinux_android_setcontext:  Error setting context for app with uid 1000, seinfo default:targetSdkVersion=28:complete: Success

2019-05-22 19:49:49.679 2437-2437/? E/Zygote: selinux_android_setcontext(1000, 0, "default:targetSdkVersion=28:complete", "com.genesis.testapplication") failed

2019-05-22 19:49:49.679 2437-2437/? A/zygote: jni_internal.cc:593] JNI FatalError called: frameworks/base/core/jni/com_android_internal_os_Zygote.cpp:652: selinux_android_setcontext failed

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523] Runtime aborting...

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523] Dumping all threads without appropriate locks held: thread list lock

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523] All threads:

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523] DALVIK THREADS (1):

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523] "main" prio=5 tid=1 Runnable

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   | group="" sCount=0 dsCount=0 flags=0 obj=0x7217b978 self=0xa6559000

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   | sysTid=1461 nice=0 cgrp=default sched=0/0 handle=0xaaf2a514

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   | state=? schedstat=( 0 0 0 ) utm=0 stm=0 core=0 HZ=100

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   | stack=0xbf1d1000-0xbf1d3000 stackSize=8MB

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   | held mutexes= "abort lock" "mutator lock"(shared held)

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   kernel: (couldn't read /proc/self/task/1461/stack)

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   native: (backtrace::Unwind failed for thread 1461: Thread doesn't exist)

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   at com.android.internal.os.Zygote.nativeForkAndSpecialize(Native method)

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   at com.android.internal.os.Zygote.forkAndSpecialize(Zygote.java:105)

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   at com.android.internal.os.ZygoteConnection.processOneCommand(ZygoteConnection.java:222)

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   at com.android.internal.os.ZygoteServer.runSelectLoop(ZygoteServer.java:174)

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:796)

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523] Aborting thread:

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523] "main" prio=5 tid=1 Runnable

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   | group="" sCount=0 dsCount=0 flags=0 obj=0x7217b978 self=0xa6559000

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   | sysTid=1461 nice=0 cgrp=default sched=0/0 handle=0xaaf2a514

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   | state=? schedstat=( 0 0 0 ) utm=0 stm=0 core=0 HZ=100

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   | stack=0xbf1d1000-0xbf1d3000 stackSize=8MB

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   | held mutexes= "abort lock" "mutator lock"(shared held)

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   kernel: (couldn't read /proc/self/task/1461/stack)

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   native: (backtrace::Unwind failed for thread 1461: Thread doesn't exist)

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   at com.android.internal.os.Zygote.nativeForkAndSpecialize(Native method)

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   at com.android.internal.os.Zygote.forkAndSpecialize(Zygote.java:105)

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   at com.android.internal.os.ZygoteConnection.processOneCommand(ZygoteConnection.java:222)

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   at com.android.internal.os.ZygoteServer.runSelectLoop(ZygoteServer.java:174)

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:796)

2019-05-22 19:49:49.706 2437-2437/? A/zygote: runtime.cc:523]

    

    --------- beginning of crash

2019-05-22 19:49:49.706 2437-2437/? A/libc: Fatal signal 6 (SIGABRT), code -6 in tid 2437 (main), pid 2437 (main)

2019-05-22 19:49:49.730 2444-2444/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

2019-05-22 19:49:49.730 2444-2444/? A/DEBUG: Build fingerprint: 'Android/aosp_x86/generic_x86:8.1.0/OPM7.181205.001/genesi05201435:eng/test-keys'

2019-05-22 19:49:49.730 2444-2444/? A/DEBUG: Revision: '0'

2019-05-22 19:49:49.730 2444-2444/? A/DEBUG: ABI: 'x86'

2019-05-22 19:49:49.730 2444-2444/? A/DEBUG: pid: 2437, tid: 2437, name: main  >>> zygote <<<

2019-05-22 19:49:49.730 2444-2444/? A/DEBUG: signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------

2019-05-22 19:49:49.731 2444-2444/? A/DEBUG: Abort message: 'jni_internal.cc:593] JNI FatalError called: frameworks/base/core/jni/com_android_internal_os_Zygote.cpp:652: selinux_android_setcontext failed'

2019-05-22 19:49:49.731 2444-2444/? A/DEBUG:     eax 00000000  ebx 00000985  ecx 00000985  edx 00000006

2019-05-22 19:49:49.731 2444-2444/? A/DEBUG:     esi a64bd800  edi 00000985

2019-05-22 19:49:49.731 2444-2444/? A/DEBUG:     xcs 00000073  xds 0000007b  xes 0000007b  xfs 0000003b  xss 0000007b

2019-05-22 19:49:49.731 2444-2444/? A/DEBUG:     eip aadfaac4  ebp 0f2ba46a  esp bf9caac8  flags 00000286

2019-05-22 19:49:49.958 2444-2444/? A/DEBUG: backtrace:

2019-05-22 19:49:49.958 2444-2444/? A/DEBUG:     #00 pc 00000ac4  [vdso:aadfa000] (__kernel_vsyscall+16)

2019-05-22 19:49:49.958 2444-2444/? A/DEBUG:     #01 pc 0001edf8  /system/lib/libc.so (syscall+40)

2019-05-22 19:49:49.958 2444-2444/? A/DEBUG:     #02 pc 0001f073  /system/lib/libc.so (abort+115)

2019-05-22 19:49:49.958 2444-2444/? A/DEBUG:     #03 pc 0054d5bb  /system/lib/libart.so (art::Runtime::Abort(char const*)+603)

2019-05-22 19:49:49.958 2444-2444/? A/DEBUG:     #04 pc 0011fb23  /system/lib/libart.so (_ZNSt3__110__function6__funcIPFvPKcENS_9allocatorIS5_EES4_EclEOS3_+35)

2019-05-22 19:49:49.958 2444-2444/? A/DEBUG:     #05 pc 0065f36b  /system/lib/libart.so (

        最后,整个App就可以在没有platform的情况下使用system的APP了。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值