Reliable app reinforcement sharing

Posted by devork on Wed, 09 Feb 2022 16:52:26 +0100

Let's take a look at the general process first

Reinforced aerial view

1. Write the encryption method as a tool method for subsequent encryption and decryption preparation.

2. Write proxy Application (ProxyApplication) as the pseudo entry of apk after reinforcement. (when ProxyApplication is used as a pseudo entry, decrypt the encrypted apk and reload it into classLoader)

3. The Application:name tag of the AndroidManifest file of apk to be encrypted is changed to ProxyApplication through the line, and the real Application entry and version number are declared with the tag.

4. Package the files in steps 1 and 2 into aar package.

5. Unzip the aar package (in aarTemp folder), and compile the unzipped jar file into a dex file (entry. dex) (machine code file recognized by Android virtual machine).

6. Unzip the apk to be encrypted (in apkTemp folder), traverse the unzipped folder, take out all dex files, encrypt all dex files with the encryption method in step 1, and replace the original unencrypted dex.
*Note: entry DEX is in aarTemp and is not encrypted

7. Copy the dex file in aarTemp to apkTemp file, and compress apkTemp into apk file.

8. Align & sign (for normal use)
Attach relevant codes

    public class Main {
        public static void main(String[] args) {
            //Step 4: unzip arr (including encryption and decryption tools and ProxyApplication.java)
            File aarFile = new File("core/build/outputs/aar/core-debug.aar");
            File aarTemp = new File("lib/temp");
            Zip.unZip(aarFile, aarTemp);
            // Generate classes dex
            File classesJar = new File(aarTemp, "classes.jar");
            File classesDex = new File(aarTemp, "classes.dex");
            Process process = null;
            //dx --dex --output out.dex in.jar
            try {
                process = Runtime.getRuntime().exec("cmd /c  dx --dex --output " + classesDex.getAbsolutePath()
                        + " " + classesJar.getAbsolutePath());
                process.waitFor();
                if (process.exitValue() != 0) {
                    System.out.println("dex error");
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //Step 6: unzip apk
            File apkFile = new File("app/build/outputs/apk/debug/app-debug.apk");
            File apkTemp = new File("lib/Apktemp");
            Zip.unZip(apkFile, apkTemp);
            ArrayList<File> dexFiles = new ArrayList<>();
            for (File file : apkTemp.listFiles()) {
                if (file.getName().endsWith("dex")) {
                    dexFiles.add(file);
                }
            }
            //Encrypt dex in apk
            AES.init(AES.DEFAULT_PWD);
            for (File dexFile : dexFiles) {
                try {
                    byte[] bytes = Utils.getBytes(dexFile);
    
                    byte[] encrypt = AES.encrypt(bytes);
                    FileOutputStream fos = new FileOutputStream(new File(apkTemp,
                            "secret-" + dexFile.getName()));
                    fos.write(encrypt);
                    fos.flush();
                    fos.close();
                    dexFile.delete();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            classesDex.renameTo(new File("lib/Apktemp", "classes.dex"));
            File unSignedApk = new File("app/build/outputs/apk/debug/app-unsigned.apk");
            //Step 7: compress apkTemp into unsightApk
            try {
                Zip.zip(apkTemp, unSignedApk);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            //Step 8: align signatures
            File alignedApk = new File("app/build/outputs/apk/debug/app-unsigned-aligned.apk");
            try {
                process = Runtime.getRuntime().exec("cmd /c zipalign -v -p 4 " + unSignedApk.getAbsolutePath()
                        + " " + alignedApk.getAbsolutePath());
    
                process.waitFor();
                if (process.exitValue() != 0) {
                    System.out.println("zipalign error");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            File signedApk=new File("app/build/outputs/apk/debug/app-signed-aligned.apk");
            File jks=new File("mykeystore.jks");
            try {
                process=Runtime.getRuntime().exec("cmd /c apksigner sign --ks "+jks.getAbsolutePath()
                        +" --ks-key-alias key0 --ks-pass pass:11111111 --key-pass pass:11111111 --out "
                        +signedApk.getAbsolutePath()+" "+alignedApk.getAbsolutePath());
                process.waitFor();
                if(process.exitValue()!=0){
                    System.out.println("sign error");
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println("over ");
        }
    }

ProxyApplication:
Discussion 1: as the only unencrypted dex file, how does ProxyApplication load the encrypted dex file into the ClassLoader?
ProxyApplication Trilogy:
1. Obtain the encrypted apk.
2. Un zip and decrypt the dex file.
3 store the new dex file index in the classloader.

The above process involves loading the dex file into the class loader. Let's briefly understand the class loading mechanism.
Premise: android ClassLoader has two types: system class loader and custom loader.
1)BootClassLoader:
The Android class will be used to preload the Android system.
2)DexClassLoader
Load the dex file and the compressed package containing the dex file
3)PathClassLoader
Load system classes and application classes
4) InMemoryClassLoader:
New to Android for loading dex in memory
ยท
·Classloader is an abstract class that defines the main functions of classloader. BootClassLoader is its inner class
·SecureClassLoader is not the implementation class of ClassLoader, which expands the functions of ClassLoader in terms of permissions
·BaseDexClassLoader inherits ClassLoader, but it is an abstract class. PathClassLoader, dexclassloader and inmemoryclassloader all inherit it and implement class functions respectively
·Parental entrustment model
(Speaker: first of all, judge whether the Class has been loaded. If not, instead of looking for it from itself, delegate to the parent loader to find whether there is a loading target Class. If not, recurse to the parent Class in turn until the topmost ClassLoader Class. If found, return the Class directly. If not, continue to find the findClass to the child loader in turn...)
advantage:
1. Avoid repeated loading
2. Protect security.
(sand carving A creates A custom class named android.view.View, which may make the original View of the system unavailable. But there is another layer of protection. The virtual machine will think that two classes with the same class name and loaded by the same class loader are the same class.)

Take a demo print to see what the class loader of the application is:

Here you can see the PathClassLoader as the loader.

Loading process of ClassLoader:
ClassLoader.java

      protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
                // First, check if the class has already been loaded
            //Find whether the class has been loaded
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    try {
                        //First judge whether the parent class exists
                        if (parent != null) {
                            c = parent.loadClass(name, false);
                        } else {
                            //If it doesn't exist, find it in the self layer
                            c = findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException e) {
                        // ClassNotFoundException thrown if class not found
                        // from the non-null parent class loader
                    }
    
                    if (c == null) {
                        // If still not found, then invoke findClass in order
                        // to find the class.
                        //If the class is not found in the delegation process, the sentence will be executed
                        c = findClass(name);
                    }
                }
                //Return directly if loaded
                return c;
        }

BaseDexClassLoader.java

     @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
            //Call findClass of pathList
            Class c = pathList.findClass(name, suppressedExceptions);
            if (c == null) {
                ClassNotFoundException cnfe = new ClassNotFoundException(
                        "Didn't find class \"" + name + "\" on path: " + pathList);
                for (Throwable t : suppressedExceptions) {
                    cnfe.addSuppressed(t);
                }
                throw cnfe;
            }
            return c;
        }

Let's see what the pathList object is

     /**
         * Constructs an instance.
         *
         * dexFile must be an in-memory representation of a full dexFile.
         *
         * @param dexFiles the array of in-memory dex files containing classes.
         * @param parent the parent class loader
         *
         * @hide
         */
        public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
            // TODO We should support giving this a library search path maybe.
            super(parent);
            //Initialization within the constructor is a DexPathList object
            this.pathList = new DexPathList(this, dexFiles);
        }

Next, let's see how the DexPathList object stores the loaded class es

     /**
         * Construct an instance.
         *
         * @param definingContext the context in which any as-yet unresolved
         * classes should be defined
         *
         * @param dexFiles the bytebuffers containing the dex files that we should load classes from.
         */
        public DexPathList(ClassLoader definingContext, ByteBuffer[] dexFiles) {
           ...
            this.definingContext = definingContext;
            // TODO It might be useful to let in-memory dex-paths have native libraries.
            this.nativeLibraryDirectories = Collections.emptyList();
            this.systemNativeLibraryDirectories =
                    splitPaths(System.getProperty("java.library.path"), true);
            this.nativeLibraryPathElements = makePathElements(this.systemNativeLibraryDirectories);
    
            ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
            //Store all the saved dex files in the dexElements object
            this.dexElements = makeInMemoryDexElements(dexFiles, suppressedExceptions);
            if (suppressedExceptions.size() > 0) {
                this.dexElementsSuppressedExceptions =
                        suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
            } else {
                dexElementsSuppressedExceptions = null;
            }
        }
    

Next, let's see where dexElements are sacred!?
This is the object declaration of dexElements

      /**
         * List of dex/resource (class path) elements.
         * Should be called pathElements, but the Facebook app uses reflection
         * to modify 'dexElements' (http://b/7726934).
         */
        private Element[] dexElements;

Here's the point:

     /**
         * Element of the dex/resource path. Note: should be called DexElement, but apps reflect on
         * this.
         */
        /*package*/ static class Element {
            /**
             * A file denoting a zip file (in case of a resource jar or a dex jar), or a directory
             * (only when dexFile is null).
             */
            private final File path;
    
            private final DexFile dexFile;
    
            private ClassPathURLStreamHandler urlHandler;
            private boolean initialized;
    
            /**
             * Element encapsulates a dex file. This may be a plain dex file (in which case dexZipPath
             * should be null), or a jar (in which case dexZipPath should denote the zip file).
             */
            public Element(DexFile dexFile, File dexZipPath) {
                this.dexFile = dexFile;
                this.path = dexZipPath;
            }
    
            public Element(DexFile dexFile) {
                this.dexFile = dexFile;
                this.path = null;
            }
    
            public Element(File path) {
              this.path = path;
              this.dexFile = null;
            }
            ....
         }

From the above code, you can see that Element stores the instance of dex file and the corresponding path.

Come back ~ from basedexclassloader findClass()->DexPathList. findClass()
Just look at dexpathlist Implementation content of findclass()

    public Class<?> findClass(String name, List<Throwable> suppressed) {
    //Traverse dexElements, findClass()
            for (Element element : dexElements) {
            /
                Class<?> clazz = element.findClass(name, definingContext, suppressed);
                if (clazz != null) {
                    return clazz;
                }
            }
    
            if (dexElementsSuppressedExceptions != null) {
                suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
            }
            return null;
        }

Look at element findClass()

     public Class<?> findClass(String name, ClassLoader definingContext,
                    List<Throwable> suppressed) {
                return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
                        : null;
            }

dexFile.loadClassBinaryName()

      public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
            return defineClass(name, loader, mCookie, this, suppressed);
        }
    
        private static Class defineClass(String name, ClassLoader loader, Object cookie,
                                         DexFile dexFile, List<Throwable> suppressed) {
            Class result = null;
            try {
    
                //Call native
                result = defineClassNative(name, loader, cookie, dexFile);
            } catch (NoClassDefFoundError e) {
                if (suppressed != null) {
                    suppressed.add(e);
                }
            } catch (ClassNotFoundException e) {
                if (suppressed != null) {
                    suppressed.add(e);
                }
            }
            return result;
        }

The native method will no longer be analyzed. From this wave of code analysis, we can find an important turning point dexElements(Element array). Whenever we find the application Class, we will traverse this array, find the destination dex file, and then get the destination Class.
Back to reinforcement
Thus, we merge the decrypted dex file into the dexElements object (Element array) through reflection.
As shown below:
The above figure corresponds to the following codes:

    public class ProxyApplication extends Application {
    
        //Define the storage path of the decrypted file
        private String app_name;
        private String app_version;
    
        /**
         * ActivityThread The first method called after Application is created.
         * You can decrypt it in this method and give the dex to android to load
         */
        @Override
        protected void attachBaseContext(Context base) {
            super.attachBaseContext(base);
            //Get the metadata filled in by the user
            getMetaData();
    
            //Get the currently encrypted APK file
            File apkFile=new File(getApplicationInfo().sourceDir);
            //Unzip apk app_ name+"_"+ app_ The contents in the version directory need root permission to be used
            File versionDir = getDir(app_name+"_"+app_version,MODE_PRIVATE);
            File appDir=new File(versionDir,"app");
            File dexDir=new File(appDir,"dexDir");
    
            Log.e("ProxyApplication", "attachBaseContext:first "+apkFile.getAbsolutePath() );
            Log.e("ProxyApplication", "attachBaseContext:sec "+versionDir.getAbsolutePath() );
    
            //Get the Dex file we need to load
            List<File> dexFiles=new ArrayList<>();
            //Decrypt (preferably MD5 file verification)
            if(!dexDir.exists() || dexDir.list().length==0){
                //Unzip apk to appDir
                Zip.unZip(apkFile,appDir);
                //Get all files in the directory
                File[] files=appDir.listFiles();
                for (File file : files) {
                    String name=file.getName();
                    if(name.endsWith(".dex") && !TextUtils.equals(name,"classes.dex")){
                        try{
                            AES.init(AES.DEFAULT_PWD);
                            //Read file contents
                            byte[] bytes=Utils.getBytes(file);
                            //decrypt
                            byte[] decrypt=AES.decrypt(bytes);
                            //Write to the specified directory
                            FileOutputStream fos=new FileOutputStream(file);
                            fos.write(decrypt);
                            fos.flush();
                            fos.close();
                            dexFiles.add(file);
    
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                }
            }else{
                for (File file : dexDir.listFiles()) {
                    dexFiles.add(file);
                }
            }
    
            try{
                //2. Load the decrypted file into the system
                loadDex(dexFiles,versionDir);
            }catch (Exception e){
                e.printStackTrace();
            }
    
    
        }
    
        private void loadDex(List<File> dexFiles, File versionDir) throws Exception{
            //1. Get pathlist
            Field pathListField = Utils.findField(getClassLoader(), "pathList");
            Object pathList = pathListField.get(getClassLoader());
            //2. Get the array dexElements
            Field dexElementsField=Utils.findField(pathList,"dexElements");
            Object[] dexElements=(Object[])dexElementsField.get(pathList);
            //3. Reflect to the method of initializing dexElements
            Method makeDexElements=Utils.findMethod(pathList,"makePathElements",List.class,File.class,List.class);
    
            ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
            Object[] addElements=(Object[])makeDexElements.invoke(pathList,dexFiles,versionDir,suppressedExceptions);
    
            //Merge array
            Object[] newElements= (Object[])Array.newInstance(dexElements.getClass().getComponentType(),dexElements.length+addElements.length);
            System.arraycopy(dexElements,0,newElements,0,dexElements.length);
            System.arraycopy(addElements,0,newElements,dexElements.length,addElements.length);
    
            //Replace the element array in the classloader
            dexElementsField.set(pathList,newElements);
        }
    
        private void getMetaData() {
            try{
                ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(
                        getPackageName(), PackageManager.GET_META_DATA);
                Bundle metaData=applicationInfo.metaData;
                if(null!=metaData){
                    if(metaData.containsKey("app_name")){
                        app_name=metaData.getString("app_name");
                    }
                    if(metaData.containsKey("app_version")){
                        app_version=metaData.getString("app_version");
                    }
                }
    
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
```java

(tinker Hot fix: new join dex go dexElements)  
**Discussion 2: Initial cold start ProxyApplication Process has been ProxyApplication As the entrance, how can the subsequent cold start be replaced into a real one MyApplication As a real application portal? And ProxyApplication As the initial entry, the relevant information has been initialized ProxyApplication Information, how to replace it will be real MyApplication?**  
The premise here needs a rough understanding app Cold start process (in launcher Click on start app As a starting point:  
![Insert picture description here](https://img-blog.csdnimg.cn/20200924143543282.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3OTc3MDcw,size_16,color_FFFFFF,t_70#pic_center)

1.  first launcher adopt startActivityAsUser()Request open activity,Then again ContextImpl Pass in binder obtain ActivityManager Service, call to ActvityManagerService of startActivityAsUser(). 
    
2.  then AMS A series of judgments will be made before starting the application process. For example, whether the current application process has been started. If it does not exist, report to the Zygote Start the new process.
    
3.  zygote After the process receives the new process request,  
    1)adopt classLoader instantiation  ActivityThread Object,  
    2)binder Thread pool started.
    
    (_ams The process and method involving premise judgment before judging the relevant opening process binder The start-up process is complex, and there are many knowledge points, but it is not necessary to be related to the key logic of the reinforcement topic here, so I'll simply cover it._)
    
4.  ActivityThread After the object is initialized,  
    1)Turn on the of the main thread looper Cycle,  
    2)request ams Bind Application
    

Back to the goal of reinforcement theme->stay ProxyApplication After initialization, how will the real MyApplication To replace ProxyApplication?  
There are two main purposes to follow up the source code  
1.have a look ProxyApplication How is it initialized?  
2.After initialization, how can it be used as an object by subsequent processes?

On the source code! from ams call ActivityThread binding Application Start watching
```java
    private void handleBindApplication(AppBindData data) {
    ...
    		Application app;
            final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
            final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
            try {
                // If the app is being launched for full backup or restore, bring it up in
                // a restricted environment with the base application class.
                //data.info is a LoadedApk type object. LoadedApk is the representation of APK in memory. You can get the ClassLoader, data mDataDir, function list, ApplicationInfo and other information of the code
                //Build the Application instance through LoadedApk's makeApplication()
                app = data.info.makeApplication(data.restrictedBackupMode, null);
    
                // Propagate autofill compat state
                app.setAutofillCompatibilityEnabled(data.autofillCompatibilityEnabled);
                //Assign the value of the built app to mInitialApplication.
                //To find the replacement target TODO TARGET:
                //  ActivityThread. Java - > minitialapplication object
                mInitialApplication = app;
                 // don't bring up providers in restricted mode; they may depend on the
                // app's custom Application class
                if (!data.restrictedBackupMode) {
                    if (!ArrayUtils.isEmpty(data.providers)) {
                        //If you pass in App - > here, you should follow in and see how to use app..
                        installContentProviders(app, data.providers);
                        // For process that contains content providers, we want to
                        // ensure that the JIT is enabled "at some point".
                        mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
                    }
                }
    ...
      			try {
                    //Execute onCreat() of ProxyApplication here
                    mInstrumentation.callApplicationOnCreate(app);
                } catch (Exception e) {
                    if (!mInstrumentation.onException(app, e)) {
                        throw new RuntimeException(
                          "Unable to create application " + app.getClass().getName()
                          + ": " + e.toString(), e);
                    }
                }
                ...
    }

Next, let's take a look at what LoadedApk's makeApplication() does

    public Application makeApplication(boolean forceDefaultAppClass,
                Instrumentation instrumentation) {
    ...
    		 Application app = null;
            //mApplicationInfo.className; Get the value declared in the Application Name file
            String appClass = mApplicationInfo.className;
            if (forceDefaultAppClass || (appClass == null)) {
                appClass = "android.app.Application";
            }
    
            try {
                java.lang.ClassLoader cl = getClassLoader();
                if (!mPackageName.equals("android")) {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                            "initializeJavaContextClassLoader");
                    initializeJavaContextClassLoader();
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                }
                //Create Context
                ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
                //Instrumentation is a tool class for managing and executing the activity declaration cycle
                //Via M activitythread Mminstrumentation creates an application object
                //The created app object is essentially proxyapplication -- > to replace all fields using the app object (ProxyApplication)
                app = mActivityThread.mInstrumentation.newApplication(
                        cl, appClass, appContext);
                //Used app to set appcontext setOuterContext
                //Find replacement target todo TARGET: 
                // Replace the mOuterContext object of ContextImpl > > ContextImpl Java - > mOuterContext object
                appContext.setOuterContext(app);
    		} catch (Exception e) {
                if (!mActivityThread.mInstrumentation.onException(app, e)) {
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    throw new RuntimeException(
                        "Unable to instantiate application " + appClass
                        + ": " + e.toString(), e);
                }
            }
            //app is used to add mmactivitythread Allapplications queue
            //Find replacement target todo TARGET:
            //Replace the millapplications queue of ActivityThread > > ActivityThread - > millapplications
            mActivityThread.mAllApplications.add(app);
            //Find replacement target todo TARGET:
            //Replace LoadedApk's mApplication Object > > LoadedApk - > mApplication
            mApplication = app;
    
    ...
    }

Next, let's take a look at the installContentProviders() of ActivityThread and what is done by passing in the application object

    private void installContentProviders(
                Context context, List<ProviderInfo> providers) {
          ...
          //Pass in context to installProvider()
                ContentProviderHolder cph = installProvider(context, null, cpi,
                        false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
                if (cph != null) {
                    cph.noReleaseNeeded = true;
                    results.add(cph);
                }
            }
            ...
    }

installProvider()

    private ContentProviderHolder installProvider(context context,
                ContentProviderHolder holder, ProviderInfo info,
                boolean noisy, boolean noReleaseNeeded, boolean stable) {
    ...
     			Context c = null;
                ApplicationInfo ai = info.applicationInfo;
                //ai, the information obtained from ProxyApplication
                //->If context getPackageName()
                //   If you don't get ProxyApplication, you can change the assignment behavior of c
                //Here, you can override the getPackageName method of context,
                //Make the output PackageName the real MyApplication
                if (context.getPackageName().equals(ai.packageName)) {
                    c = context;
                } else if (mInitialApplication != null &&
                        mInitialApplication.getPackageName().equals(ai.packageName)) {
                    c = mInitialApplication;
                } else {
                    try {
                    //The object is constructed here
                        c = context.createPackageContext(ai.packageName,
                                Context.CONTEXT_INCLUDE_CODE);
                    } catch (PackageManager.NameNotFoundException e) {
                        // Ignore
                    }
                }
    
    ...            
    }

After the above code, we found four object variables that need to be replaced and one rewriting method.
arrangement:

  1. Create a new real MyApplication object to replace the ProxyApplication object
  2. ActivityThread. Java - > minitialapplication object
  3. ContextImpl. Java - > moutercontext object
  4. Millapplications queue of ActivityThread > > ActivityThread - > millapplications
  5. LoadedApk's mApplication Object > > LoadedApk - > mApplication
  6. context.getPackageName() override
    ->How to create a new MyApplication object can be constructed by imitating the steps of how to create a ProxyApplication object in the source code
    Create a new Application object in instrumentation Java implementation:
     public Application newApplication(ClassLoader cl, String className, Context context)
                throws InstantiationException, IllegalAccessException, 
                ClassNotFoundException {
            Application app = getFactory(context.getPackageName())
                    .instantiateApplication(cl, className);
            app.attach(context);
            return app;
        }

Follow up instantiateApplication(): appcomponentfactory java

    public @NonNull Application instantiateApplication(@NonNull ClassLoader cl,
                @NonNull String className)
                throws InstantiationException, IllegalAccessException, ClassNotFoundException {
                //Get the class through classLoader, and then perform newInstance  
            return (Application) cl.loadClass(className).newInstance();
        }

So we can get the Application object in a similar way

     //Get the context ContextImpl passed in by attachBaseContext(context)
            Context baseContext = getBaseContext();
            //Create a user's real application (MyApplication)
            Class<?> delegateClass = Class.forName(app_name);
            realApplication = (Application) delegateClass.newInstance();
            //Get the attach() method
            Method attach = Application.class.getDeclaredMethod("attach", Context.class);
            attach.setAccessible(true);
            attach.invoke(realApplication, baseContext);

->Object replacement can be realized through reflection. Here, you need to find an entry point and get which object to start with?
In proxyapplication The context object can be obtained in Java. The following objects can be found through the context implementation class ContextImpl.

Thus, the context is used as the starting point for reflection.

Attach proxyapplication Java full code, including the code to replace the target object

    
    public class ProxyApplication extends Application {
    
        //Define the storage path of the decrypted file
        private String app_name;
        private String app_version;
    
        /**
         * ActivityThread The first method called after Application is created.
         * You can decrypt it in this method and give the dex to android to load
         */
        @Override
        protected void attachBaseContext(Context base) {
            super.attachBaseContext(base);
            //Get the metadata filled in by the user
            getMetaData();
    
            //Get the currently encrypted APK file
            File apkFile = new File(getApplicationInfo().sourceDir);
    
            //Unzip apk app_ name+"_"+ app_ The contents in the version directory need boot permission to be used
            File versionDir = getDir(app_name + "_" + app_version, MODE_PRIVATE);
            File appDir = new File(versionDir, "app");
            File dexDir = new File(appDir, "dexDir");
    
    
            //Get the Dex file we need to load
            List<File> dexFiles = new ArrayList<>();
            //Decrypt (preferably MD5 file verification)
            if (!dexDir.exists() || dexDir.list().length == 0) {
                //Unzip apk to appDir
                Zip.unZip(apkFile, appDir);
                //Get all files in the directory
                File[] files = appDir.listFiles();
                for (File file : files) {
                    String name = file.getName();
                    if (name.endsWith(".dex") && !TextUtils.equals(name, "classes.dex")) {
                        try {
                            AES.init(AES.DEFAULT_PWD);
                            //Read file contents
                            byte[] bytes = Utils.getBytes(file);
                            //decrypt
                            byte[] decrypt = AES.decrypt(bytes);
                            //Write to the specified directory
                            FileOutputStream fos = new FileOutputStream(file);
                            fos.write(decrypt);
                            fos.flush();
                            fos.close();
                            dexFiles.add(file);
    
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            } else {
                for (File file : dexDir.listFiles()) {
                    dexFiles.add(file);
                }
            }
    
            try {
                //2. Load the decrypted file into the system
                loadDex(dexFiles, versionDir);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
    
        }
    
        private void loadDex(List<File> dexFiles, File versionDir) throws Exception {
            //1. Get pathlist
            Field pathListField = Utils.findField(getClassLoader(), "pathList");
            Object pathList = pathListField.get(getClassLoader());
            //2. Get the array dexElements
            Field dexElementsField = Utils.findField(pathList, "dexElements");
            Object[] dexElements = (Object[]) dexElementsField.get(pathList);
            //3. Reflect to the method of initializing dexElements
            Method makeDexElements = Utils.findMethod(pathList, "makePathElements", List.class, File.class, List.class);
    
            ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
            Object[] addElements = (Object[]) makeDexElements.invoke(pathList, dexFiles, versionDir, suppressedExceptions);
    
            //Merge array
            Object[] newElements = (Object[]) Array.newInstance(dexElements.getClass().getComponentType(), dexElements.length + addElements.length);
            System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);
            System.arraycopy(addElements, 0, newElements, dexElements.length, addElements.length);
    
            //Replace the element array in the classloader
            dexElementsField.set(pathList, newElements);
        }
    
        private void getMetaData() {
            try {
                ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(
                        getPackageName(), PackageManager.GET_META_DATA);
                Bundle metaData = applicationInfo.metaData;
                if (null != metaData) {
                    if (metaData.containsKey("app_name")) {
                        app_name = metaData.getString("app_name");
                    }
                    if (metaData.containsKey("app_version")) {
                        app_version = metaData.getString("app_version");
                    }
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    
        /**
         * Start replacing application
         */
        @Override
        public void onCreate() {
            Log.e("proxyApp", "onCreate: " + Log.getStackTraceString(new Throwable()));
            super.onCreate();
            try {
                bindRealApplicatin();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * Let the code go into the third paragraph in if
         *
         * @return
         */
        @Override
        public String getPackageName() {
            if (!TextUtils.isEmpty(app_name)) {
                return "";
            }
            return super.getPackageName();
        }
    
        @Override
        public Context createPackageContext(String packageName, int flags) throws PackageManager.NameNotFoundException {
            if (TextUtils.isEmpty(app_name)) {
                return super.createPackageContext(packageName, flags);
            }
            try {
                bindRealApplicatin();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return realApplication;
    
        }
    
        boolean isBindReal;
        Application realApplication;
    
        private void bindRealApplicatin() throws Exception {
            if (isBindReal) {
                return;
            }
            if (TextUtils.isEmpty(app_name)) {
                return;
            }
            //Get the context ContextImpl passed in by attachBaseContext(context)
            Context baseContext = getBaseContext();
            //Create a user's real application (MyApplication)
            Class<?> delegateClass = Class.forName(app_name);
            realApplication = (Application) delegateClass.newInstance();
            //Get the attach() method
            Method attach = Application.class.getDeclaredMethod("attach", Context.class);
            attach.setAccessible(true);
            attach.invoke(realApplication, baseContext);
    
    //        Contextimpl - > moutercontext (APP) is obtained through the attachBaseContext callback parameter of Application
            Class<?> contextImplClass = Class.forName("android.app.ContextImpl");
            //Get the mOuterContext property
            Field mOuterContextField = contextImplClass.getDeclaredField("mOuterContext");
            mOuterContextField.setAccessible(true);
            mOuterContextField.set(baseContext, realApplication);
    
    //        Activitythread - > mainthread attribute of minitialapplication contextimpl
            Field mMainThreadField = contextImplClass.getDeclaredField("mMainThread");
            mMainThreadField.setAccessible(true);
            Object mMainThread = mMainThreadField.get(baseContext);
    
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Field mInitialApplicationField = activityThreadClass.getDeclaredField("mInitialApplication");
            mInitialApplicationField.setAccessible(true);
            mInitialApplicationField.set(mMainThread, realApplication);
    
    //        Activitythread - > mMainThread attribute of mllapplications (ArrayList) contextimpl
            Field mAllApplicationsField = activityThreadClass.getDeclaredField("mAllApplications");
            mAllApplicationsField.setAccessible(true);
            ArrayList<Application> mAllApplications = (ArrayList<Application>) mAllApplicationsField.get(mMainThread);
            mAllApplications.remove(this);
            mAllApplications.add(realApplication);
    
    //        Loadedapk ------ > mPackageInfo attribute of mpapplication contextimpl
            Field mPackageInfoField = contextImplClass.getDeclaredField("mPackageInfo");
            mPackageInfoField.setAccessible(true);
            Object mPackageInfo = mPackageInfoField.get(baseContext);
    
            Class<?> loadedApkClass = Class.forName("android.app.LoadedApk");
            Field mApplicationField = loadedApkClass.getDeclaredField("mApplication");
            mApplicationField.setAccessible(true);
            mApplicationField.set(mPackageInfo, realApplication);
    
            //Modify applicationinfo classname looadedapk
            Field mApplicationInfoField = loadedApkClass.getDeclaredField("mApplicationInfo");
            mApplicationInfoField.setAccessible(true);
            ApplicationInfo mApplicationInfo = (ApplicationInfo) mApplicationInfoField.get(mPackageInfo);
            mApplicationInfo.className = app_name;
    
            realApplication.onCreate();
            isBindReal = true;
        }
    }
    

check before acceptance:

You can use getApplication() and getApplicationContext() to print out the four components to see if ProxyApplication has been replaced by MyApplication.

For example:


(* Note: the broadcast receiver onReceive (context, context, intent), the context in the parameter, is the ReceiverRestrictedContext type, inherits the ContextWrapper, and cannot be used for registerReceiver and bindService. For detailed analysis, please see another blog post: [framework] understand various contexts of android )

Congratulations, learning and sharing, finally finished! Will you watch it again, please!!

Transferred from: https://blog.csdn.net/qq_37977070/article/details/108495821

Topics: Android security