欺骗你的小眼睛——Android 静默安装

我们一般代码调用安装apk会写下面的代码

Intent intent = new  Intent(Intent.ACTION_VIEW);
File apkFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/1.apk");
intent.setDataAndType(Uri.fromFile(apkFile),"application/vnd.android.package-archive");
startActivity(intent);


我们安装apk时系统会分析apk,然后弹出如下图的提示



当我们点击安装后,系统会进行安装

这里我们查看系统源码来看一下系统是怎么执行安装程序的


首先我们找到PackageInstallerActivity.java,一看命名规则就知道是apk安装的界面


既然是Activity 那么我们首先来看onCreate()方法

@Override
    protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        //get intent information  获取Intent信息
        final Intent intent = getIntent();
        mPackageURI = intent.getData();//apk的uri
        mPm = getPackageManager(); 
        mPkgInfo = PackageUtil.getPackageInfo(mPackageURI);
        
        // Check for parse errors 检查apk是否有错误
        if(mPkgInfo == null) {
            Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
            showDialogInner(DLG_PACKAGE_ERROR);
            return;
        }
        
        //set view
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.install_start); //这就是上面的界面
        mInstallConfirm = findViewById(R.id.install_confirm_panel);
        mInstallConfirm.setVisibility(View.INVISIBLE);
        PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this,
                mPkgInfo.applicationInfo, mPackageURI);
        PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);
       //check setting //是否设置了 允许安装未知来源,就是设置那里的
        if(!isInstallingUnknownAppsAllowed()) {
            //ask user to enable setting first
            showDialogInner(DLG_UNKNOWN_APPS);
            return;
        }
        initiateInstall();
    }
    



然后看initiateInstall()方法

  private void initiateInstall() {
        String pkgName = mPkgInfo.packageName;
        // Check if there is already a package on the device with this name
        // but it has been renamed to something else. //检查是否有相同包名的应用
        String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName });
        if (oldName != null && oldName.length > 0 && oldName[0] != null) {
            pkgName = oldName[0];
            mPkgInfo.setPackageName(pkgName);
        }
        // Check if package is already installed. display confirmation dialog if replacing pkg检查是否已经安装过了
        try {
            mAppInfo = mPm.getApplicationInfo(pkgName,
                    PackageManager.GET_UNINSTALLED_PACKAGES);
        } catch (NameNotFoundException e) {
            mAppInfo = null;
        }
        if (mAppInfo == null) {
            startInstallConfirm();//安装
        } else {
            if(localLOGV) Log.i(TAG, "Replacing existing package:"+
                    mPkgInfo.applicationInfo.packageName);
            showDialogInner(DLG_REPLACE_APP);
        }
    }
    


来到startInstallConfirm()

    private void startInstallConfirm() {
        LinearLayout permsSection = (LinearLayout) mInstallConfirm.findViewById(R.id.permissions_section);
        LinearLayout securityList = (LinearLayout) permsSection.findViewById(
                R.id.security_settings_list);
        boolean permVisible = false;
        if(mPkgInfo != null) {
            AppSecurityPermissions asp = new AppSecurityPermissions(this, mPkgInfo);
            if(asp.getPermissionCount() > 0) {
                permVisible = true;
                securityList.addView(asp.getPermissionsView());
            }
        }
        if(!permVisible){
            permsSection.setVisibility(View.INVISIBLE);
        }
        mInstallConfirm.setVisibility(View.VISIBLE);
        mOk = (Button)findViewById(R.id.ok_button); //确认按钮
        mCancel = (Button)findViewById(R.id.cancel_button);
        mOk.setOnClickListener(this);
        mCancel.setOnClickListener(this);
    }

找点击事件 onClick()

 public void onClick(View v) {
        if(v == mOk) { //开始安装,弹出安装进度界面
            // Start subactivity to actually install the application
            Intent newIntent = new Intent();
            newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
                    mPkgInfo.applicationInfo);
            newIntent.setData(mPackageURI);
            newIntent.setClass(this, InstallAppProgress.class); //这里调到了InstallAppProgress.java
            String installerPackageName = getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
            if (installerPackageName != null) {
                newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, installerPackageName);
            }
            if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
            startActivity(newIntent);
            finish();
        } else if(v == mCancel) {
            // Cancel and finish
            finish();
        }
    }

继续跟踪 到InstallAppProgress.java   的onCreate()

  @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        Intent intent = getIntent();
        mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
        mPackageURI = intent.getData();
        initView();
    }

initView();

    public void initView() {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.op_progress);
        int installFlags = 0; //设置錐lag
        PackageManager pm = getPackageManager();
        try {
            PackageInfo pi = pm.getPackageInfo(mAppInfo.packageName, 
                    PackageManager.GET_UNINSTALLED_PACKAGES);
            if(pi != null) {
                installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; //替换已经存在的
            }
        } catch (NameNotFoundException e) {
        }
        if((installFlags & PackageManager.INSTALL_REPLACE_EXISTING )!= 0) {
            Log.w(TAG, "Replacing package:" + mAppInfo.packageName);
        }
        PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, mAppInfo,
                mPackageURI);
        mLabel = as.label;
        PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);
        mStatusTextView = (TextView)findViewById(R.id.center_text);
        mStatusTextView.setText(R.string.installing);
        mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
        mProgressBar.setIndeterminate(true);
        // Hide button till progress is being displayed
        mOkPanel = (View)findViewById(R.id.buttons_panel);
        mDoneButton = (Button)findViewById(R.id.done_button);
        mLaunchButton = (Button)findViewById(R.id.launch_button);
        mOkPanel.setVisibility(View.INVISIBLE);

        String installerPackageName = getIntent().getStringExtra(
                Intent.EXTRA_INSTALLER_PACKAGE_NAME);
        PackageInstallObserver observer = new PackageInstallObserver();
        pm.installPackage(mPackageURI, observer, installFlags, installerPackageName);//安装的代码
    }


几经周折,总算找到了。。。

下几句 关键的

         int installFlags = 0; //设置Flag
        PackageManager pm = getPackageManager();
        try {
            PackageInfo pi = pm.getPackageInfo(mAppInfo.packageName, 
                    PackageManager.GET_UNINSTALLED_PACKAGES);
            if(pi != null) {
                installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; //替换已经存在的
            }
        } catch (NameNotFoundException e) {
        }
     
        PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, mAppInfo,
                mPackageURI);
        String installerPackageName = getIntent().getStringExtra(
                Intent.EXTRA_INSTALLER_PACKAGE_NAME);
        PackageInstallObserver observer = new PackageInstallObserver();
        pm.installPackage(mPackageURI, observer, installFlags, installerPackageName);//安装的代


我们可以通过上面的代码直接进行安装,

不过我们需要android.Manifest.permission#INSTALL_PACKAGE,

此权限只有system应用才能使用.

所以此应用必须放入system/app下,

解决思路
将我们的应用伪装成系统的应用
实现步骤
共享系统UID
使用系统签名(匹配签名需要公钥和密钥两个文件生成keystore)



还有静默安装的一种方式是使用root权限  adb指令执行 install -r 进行安装