The premise

I wrote this article for two purposes. One is to tell the majority of people who still insist on Android development to continue to work hard, and the other is to give yourself an end to this year’s technical output. The most important thing is that I want to open source what I have learned for your reference and learning.

The paper

Then let’s enter the theme, in the current market, most people realize this function through Xpose, this article is also based on Xpose, as we all know, the premise of using Xpose is to Root the phone, now there are some Up, to complete this function. The famous VirtualXpose realizes loading the Xpose plug-in by simulating the Android native environment internally, and has reached the Root environment hook process, but it is still a little unstable, and the technology is more complicated. There is also a is you should also understand the Tai Chi Xposed, is through the second package Apk implanted code. However, the author does not have open source code, and many plug-ins are not available, you must ask the author to send you the activation code to debug your plug-in. I, too, am a techie, and I wondered if I could build something like this. And that’s where this article comes in.

The principle of

The principle is roughly divided into the following five parts, the following will not detail the specific implementation process, only the core content, otherwise this article will be too long, if you want to know the specific details of the article can be ait me in the comment area.

Since the Xpose framework is not loaded by Root, I chose to implement Tai Chi instead of VirtualXpose to build a virtual Android environment. The way of Tai Chi is completed by secondary packaging Apk and embedding code. Since we have chosen secondary packaging to complete this function, the following five parts are actually the flow of our process.

Say for a long time why have not said about wechat grab red envelope, wechat grab red envelope is nothing more than to write an Xpose plug-in, Hook wechat internal code to achieve automatic red envelope. As long as we let wechat load Xpose frame, and then install a grab red envelope plug-in to complete the work, but how to non-root environment wechat has this function! That is to modify the wechat source code, modify the internal DEX file, let the wechat start to load the Xpose frame, and then our mobile phone to grab a red envelope plug-in, after the second packaging, wechat cold start will pull up the Xpose frame, naturally will load to grab the red envelope plug-in. So as to achieve non-root mobile phone wechat grab red packets.

How to make wechat grab red envelopes

In fact, I may have deviated a bit here. I wrote an article about Hook wechat Moments before. You are interested to know. It teaches you how to find the source code step by step, insert hook point, hook data. The same is true for grabbing red envelopes in wechat. As long as we find the code of getting red envelopes in wechat, we can automatically grab red envelopes. Below I will also give you a source code, about wechat grab red envelope plug-in, and the latest version of wechat is also supported, here is not much of the narrative.

Wechat reverse circle of friends

Wechat grab red envelopes

How do I load the Xpose frame

After Xpose Frame 7.0, the author did not work on the project

To load the Xpose framework, it’s important to know how it works. The great thing about the Xpose framework is that it can dynamically hijack the Android platform. How can xpose frame be dynamically hijacked? According to the installation method and script of Xpose, we can know that it is controlled to zygote process by replacing app_process program in our mobile phone (from this point we know that it must be root phone to be replaced). Have seen the Android system source code brother should know that all App processes are out of zygote fork. Since Xpose controls the Zygote process, it’s no surprise that we’re grabbing our App process page. When the app_process starts, it loads a jar package (xposedBridge.jar). By looking at the source code, you can see what the main () entry does.

/**
	 * Called when native methods and other things are initialized, but before preloading classes etc.
	 * @hide
	 */
	@SuppressWarnings("deprecation")
	protected static void main(String[] args) {
		// Initialize the Xposed framework and modules
		try {
			if(! hadInitErrors()) { initXResources(); SELinuxHelper.initOnce(); SELinuxHelper.initForProcess(null); runtime = getRuntime(); XPOSED_BRIDGE_VERSION = getXposedVersion();if (isZygote) {
					XposedInit.hookResources();
					XposedInit.initForZygote();
				}

				XposedInit.loadModules();
			} else {
				Log.e(TAG, "Not initializing Xposed because of previous errors");
			}
		} catch (Throwable t) {
			Log.e(TAG, "Errors during Xposed initialization", t);
			disableHooks = true;
		}

		// Call the original startup code
		if (isZygote) {
			ZygoteInit.main(args);
		} else{ RuntimeInit.main(args); }}Copy the code

As you can see from the appeal source code, there is a line of code ** xposedinit.loadModules ()** which, as the name implies, loads the plug-in installed on our phone. So let’s go into the source code and see what’s going on in there

/**
	 * Load a module from an APK by calling the init(String) method for all classes defined
	 * in <code>assets/xposed_init</code>.
	 */
	private static void loadModule(String apk, ClassLoader topClassLoader) {
		Log.i(TAG, "Loading modules from " + apk);

		if(! new File(apk).exists()) { Log.e(TAG," File does not exist");
			return;
		}

		DexFile dexFile;
		try {
			dexFile = new DexFile(apk);
		} catch (IOException e) {
			Log.e(TAG, " Cannot load module", e);
			return;
		}

		if(dexFile.loadClass(INSTANT_RUN_CLASS, topClassLoader) ! = null) { Log.e(TAG," Cannot load module, please disable \"Instant Run\" in Android Studio.");
			closeSilently(dexFile);
			return;
		}

		if(dexFile.loadClass(XposedBridge.class.getName(), topClassLoader) ! = null) { Log.e(TAG," Cannot load module:");
			Log.e(TAG, " The Xposed API classes are compiled into the module's APK.");
			Log.e(TAG, " This may cause strange issues and must be fixed by the module developer.");
			Log.e(TAG, " For details, see: http://api.xposed.info/using.html");
			closeSilently(dexFile);
			return;
		}

		closeSilently(dexFile);

		ZipFile zipFile = null;
		InputStream is;
		try {
			zipFile = new ZipFile(apk);
			ZipEntry zipEntry = zipFile.getEntry("assets/xposed_init");
			if (zipEntry == null) {
				Log.e(TAG, " assets/xposed_init not found in the APK");
				closeSilently(zipFile);
				return;
			}
			is = zipFile.getInputStream(zipEntry);
		} catch (IOException e) {
			Log.e(TAG, " Cannot read assets/xposed_init in the APK", e);
			closeSilently(zipFile);
			return;
		}

		ClassLoader mcl = new PathClassLoader(apk, XposedBridge.BOOTCLASSLOADER);
		BufferedReader moduleClassesReader = new BufferedReader(new InputStreamReader(is));
		try {
			String moduleClassName;
			while((moduleClassName = moduleClassesReader.readLine()) ! = null) { moduleClassName = moduleClassName.trim();if (moduleClassName.isEmpty() || moduleClassName.startsWith("#"))
					continue;

				try {
					Log.i(TAG, " Loading class "+ moduleClassName); Class<? > moduleClass = mcl.loadClass(moduleClassName);if(! IXposedMod.class.isAssignableFrom(moduleClass)) { Log.e(TAG," This class doesn't implement any sub-interface of IXposedMod, skipping it");
						continue;
					} else if (disableResources && IXposedHookInitPackageResources.class.isAssignableFrom(moduleClass)) {
						Log.e(TAG, " This class requires resource-related hooks (which are disabled), skipping it.");
						continue;
					}

					final Object moduleInstance = moduleClass.newInstance();
					if (XposedBridge.isZygote) {
						if (moduleInstance instanceof IXposedHookZygoteInit) {
							IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
							param.modulePath = apk;
							param.startsSystemServer = startsSystemServer;
							((IXposedHookZygoteInit) moduleInstance).initZygote(param);
						}

						if (moduleInstance instanceof IXposedHookLoadPackage)
							XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance));

						if (moduleInstance instanceof IXposedHookInitPackageResources)
							XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper((IXposedHookInitPackageResources) moduleInstance));
					} else {
						if (moduleInstance instanceof IXposedHookCmdInit) {
							IXposedHookCmdInit.StartupParam param = new IXposedHookCmdInit.StartupParam();
							param.modulePath = apk;
							param.startClassName = startClassName;
							((IXposedHookCmdInit) moduleInstance).initCmdApp(param);
						}
					}
				} catch (Throwable t) {
					Log.e(TAG, " Failed to load class " + moduleClassName, t);
				}
			}
		} catch (IOException e) {
			Log.e(TAG, " Failed to load module from "+ apk, e); } finally { closeSilently(is); closeSilently(zipFile); }}}Copy the code

In fact, the Xpose source code is loaded module source code. Those who have written the Xpose plug-in know that we need to define the plug-in entry in assets/ Xposed_init in the project, so that the Xpose framework will know where the plug-in is when it is read. The appeal source code also has such a line to load. ZipEntry ZipEntry = zipfile.getentry (“assets/xposed_init”) When we do loadModules while our App is initializing, you’ll see that we also support loading xpose. If we wrote an Xpose plugin for our own App, would we be able to implement hot fixes? Of course you can.

First of all, we go through the apps installed in our mobile phone and get the application information through PMS, find the apps that are XposedModule modules, and load the APK when the App starts. To enable our own project to load the Xpose plug-in

    private void loadModulsFormApk(){
        final ArrayList<String> pathList = new ArrayList<>();
        for (PackageInfo next : context.getPackageManager().getInstalledPackages(FileUtils.FileMode.MODE_IWUSR)) {
            ApplicationInfo applicationInfo2 = next.applicationInfo;
            if(applicationInfo2.enabled && applicationInfo2.metaData ! = null && applicationInfo2.metaData.containsKey("xposedmodule")) {
                String str4 = next.applicationInfo.publicSourceDir;
                String charSequence = context.getPackageManager().getApplicationLabel(next.applicationInfo).toString();
                if (TextUtils.isEmpty(str4)) {
                    str4 = next.applicationInfo.sourceDir;
                }
                pathList.add(str4);
                Log.d("XposedModuleEntry"." query installed module path -> "+ str4); }}}Copy the code

The above code will tell you which apps are Xpose plug-ins.

Speaking of here is not everyone has understood some, as long as wechat in the Application initialization, also execute this code can not complete the Xpose frame loading! Then the mobile phone installed a wechat red envelope plug-in can complete our purpose? But how to load it, then we need to modify the source code inside wechat, in order to complete this step. The following link will say how to modify the wechat internal source code.

How do I modify the Dex package

Now that you’ve seen what’s going on here. The next step is to modify the source code. As you know, the Apk we downloaded is already packed and signed. Decompressed is a pile of files, there are many dex files, wechat source code is in the dex file, we just modify the source code dex file and replace the original DEX, and then package two signature can complete this operation. It is easy to say how to modify the dex file source code. Now listen to me slowly narrate!

There are two main ways to embed code in Apk, as far as I know.

  • Through the dex2JAR project dex into Java code, after modification, and then through jar2dex into the dex file.
  • Decompile the Apk into smali, modify the smali file, and then package the modified file.

Here are two ways to try:

dex2jar github

The first dex2JAR is implemented by xpatch authors. There’s a link at the end of this article, but I’ll just talk about it briefly. Modify dex2JAR source code, operation dex file implantation code. The doTranslate() method can be found in the dex2jar file. This operation of our dex file all the source code. The implantation process is also in this method body. To write smali code, download the ASM Bytecode Viewer plugin from Android Studio and write your own code and then convert it.

We can see the ExDex2Asm class in the doTranslate() method. It does the processing for each class. We check whether it is the class we need in the entry and go ahead and copy the code we need to put in.

 private void doTranslate(final Path dist) throws IOException { DexFileNode fileNode = new DexFileNode(); . new ExDex2Asm(exceptionHandler) { public void convertCode(DexMethodNode methodNode, MethodVisitor mv) {if (methodNode.method.getOwner().equals(Dex2jar.this.applicationName) && methodNode.method.getName().equals("<clinit>")) {
                    Dex2jar.this.isApplicationClassFounded = true;
                    mv.visitMethodInsn(184, XPOSED_ENTRY_CLASS_NAME, "initXpose"."()V".false);
                }

                if((readerConfig & DexFileReader.SKIP_CODE) ! = 0 && methodNode.method.getName().equals("<clinit>")) {
                    // also skip clinit
                    return;
                }
                super.convertCode(methodNode, mv);
            }

            @Override
            public void addMethod(DexClassNode classNode, ClassVisitor cv) {
                if (classNode.className.equals(Dex2jar.this.applicationName)) {
                    Dex2jar.this.isApplicationClassFounded = true;
                    boolean hasFoundClinitMethod = false;
                    if(classNode.methods ! = null) { Iterator var4 = classNode.methods.iterator();while(var4.hasNext()) {
                            DexMethodNode methodNode = (DexMethodNode)var4.next();
                            if (methodNode.method.getName().equals("<clinit>")) {
                                hasFoundClinitMethod = true;
                                break; }}}if(! hasFoundClinitMethod) { MethodVisitor mv = cv.visitMethod(8,"<clinit>"."()V", (String)null, (String[])null);
                        mv.visitCode();
                        mv.visitMethodInsn(184, XPOSED_ENTRY_CLASS_NAME, "initXpose"."()V".false); mv.visitInsn(177); mv.visitMaxs(0, 0); mv.visitEnd(); }}}... @Override public void ir2j(IrMethod irMethod, MethodVisitor mv) { new IR2JConverter(0 ! = (V3.OPTIMIZE_SYNCHRONIZED & v3Config)).convert(irMethod, mv); } }.convertDex(fileNode, cvf); }Copy the code

So we have completed the code implantation, this is the first way, through the dex2JAR project to complete the code implantation of the dex file. But this only works on MacOS or Windows. This JAR will not run on Android devices and will report an error. Then look at the wue code and the APK provided by Xpatch to see how they modify the dex file on the Android device, decompilation did not get the result. However, hard work pays off. I came across a technical article and got this inspiration, which is the second way to modify the DEX file

The second is also done by modifying the Smali code. And that’s what I’m constantly looking for.

smali github

First of all, we can see that there is a project called dexlib2 in this repository, which allows me to complete the function of modifying dex source code on Android devices. Let’s take a look at this class first

You’ll find the name dexrewrite interesting. I found this class when I decompiled infinite code, but it was too confusing to start with. You can also think of this as an implementation of the method that modifies the dex class. Although this class looks and sounds key. But it’s not what we think. There are many ways to do this on the Internet. Instructions.


public void modifyDexFile(String filePath){
        DexRewriter dexRewriter = new DexRewriter(new RewriterModule(){
            @Nonnull
            @Override
            public Rewriter<Method> getMethodRewriter(@Nonnull Rewriters rewriters) {
                returnnew MethodRewriter(rewriters){ @Nonnull @Override public Method rewrite(@Nonnull Method value) { ...... Add implant operation......returnsuper.rewrite(value); }}; } @Nonnull @Override public Rewriter<ClassDef> getClassDefRewriter(@Nonnull Rewriters rewriters) {returnnew ClassDefRewriter(rewriters){ @Nonnull @Override public ClassDef rewrite(@Nonnull ClassDef classDef) { ...... Add implant operation......returnsuper.rewrite(classDef); }}; }}); dexRewriter.rewriteDexFile(DexFileFactory.loadDexFile(filePath,Opcodes.getDefault())); }Copy the code

This looks perfect. But I don’t have eggs to use. Let’s move on

That’s the key, we load the dex file by DexBackedDexFile and then we get the ClassDef collection inside of it, and then we wrapper a ClassDef subclass, and we write smali code to embed it into the subclass, It is possible to add a method or code, replacing the ClassDef and overwriting the wrapper class. Then the dclassdef file stream is re-read into the dexfile to reach the modification of the dexfile.

public static void main(String[] args) {
        DexRewriter dexRewriter = new DexRewriter(new RewriterModule() {
            @Nonnull
            @Override
            public Rewriter<Field> getFieldRewriter(@Nonnull Rewriters rewriters) {
                System.out.println(rewriters);
                return new FieldRewriter(rewriters) {
                    @Nonnull
                    @Override
                    public Field rewrite(@Nonnull Field field) {
                        System.out.println(field.getName());
                        returnsuper.rewrite(field); }}; } @Nonnull @Override public Rewriter<Method> getMethodRewriter(@Nonnull Rewriters rewriters) {return new MethodRewriter(rewriters) {
                    @Nonnull
                    @Override
                    public Method rewrite(@Nonnull Method value) {
                        System.out.println(value.getName());
                        if (value.getName().equals("onCreate")) {
                            System.out.println("onCreate");
                            return value;
                        }
                        returnvalue; }}; }}); try { DexBackedDexFile rewrittenDexFile = DexFileFactory.loadDexFile(new File("/Users/cuieney/Downloads/classes.dex"), Opcodes.getDefault());
            dexRewriter.rewriteDexFile(rewrittenDexFile);

            DexPool dexPool = new DexPool(rewrittenDexFile.getOpcodes());
            Set<? extends DexBackedClassDef> classes = rewrittenDexFile.getClasses();
            for (ClassDef classDef : classes) {
                if (classDef.getSuperclass().equals("Landroid/app/Application;")) {
                    System.out.println(classDef.getType());
                    for (Method method : classDef.getVirtualMethods()) {
                        System.out.println("---------virtual method----------");
                        System.out.println(method.getName());
                        System.out.println(method.getParameters());

                        if (method.getName().equals("onCreate")) {
                            for (Instruction instruction : method.getImplementation().getInstructions()) {
                                System.out.println(instruction);
                            }


                            System.out.println("Initialize code onCreate");
                            ClassDefWrapper classDefWrapper;
                            classDefWrapper = new ClassDefWrapper(classDef);

                            Method onCreateMethodInjected = buildOnCreateMethod( method);
                            classDefWrapper.replaceVirtualMethod(onCreateMethodInjected);
                            classDef = classDefWrapper;
                        }
                        System.out.println("---------virtual method end----------");
                    }


                    for (Method directMethod : classDef.getDirectMethods()) {
                        System.out.println("---------Direct method----------");
                        System.out.println(directMethod.getName());
                        System.out.println(directMethod.getParameters());
                        if (directMethod.getName().equals("<clinit>")) {
                            System.out.println("Initialize code 
      
       "
      );
                            ClassDefWrapper classDefWrapper;
                            classDefWrapper = new ClassDefWrapper(classDef);

                            Method onCreateMethodInjected = buildOnCreateMethod( directMethod);
                            classDefWrapper.replaceDirectMethod(onCreateMethodInjected);
                            classDef = classDefWrapper;
                        }

                        System.out.println("---------Direct method end----------");
                    }


                }
                dexPool.internClass(classDef);
            }
            String dexfilepath = "/Users/cuieney/Downloads/";
            String outStream = "/Users/cuieney/Downloads/test.dex";

            FileOutputStream outputStream = new FileOutputStream(outStream);
            File tempFile = new File(dexfilepath, "targeet.dex"); dexPool.writeTo(new FileDataStore(tempFile)); FileInputStream = new FileInputStream(tempFile); FileInputStream = new FileInputStream(tempFile); byte[] fileData = new byte[512 * 1024]; intreadSize;
            while ((readSize = fileInputStream.read(fileData)) > 0) {
                outputStream.write(fileData, 0, readSize); } fileInputStream.close(); // Delete the temporary file tempfile.delete (); } catch (IOException e) { e.printStackTrace(); }}Copy the code

If you want to modify the App dex file on the computer, you can modify it in the first way. If you want to modify the App dex file on the device, you can modify it in the second way. However, there may be a third way or even a fourth way. The preceding description completes the modification of the dex file.

How do I sign Apk twice

Everything is ready but the east wind, so how to give our Apk for the second signature. Delete the signature file in the meta-INF folder of the original decompressed folder and compress it twice. Then we can use our own signature file to sign the installation. There are two ways to use it:

  1. We also have this signature tool jarsigner in the Java package in our computer environment, and there’s a lot of information on how to sign on the Internet and I won’t go into that here.
  2. We can use the zip-Signer App to sign on our Android devices.

conclusion

At this point, if you understand the above steps, and the code is complete, you have completed a big project. And can be comparable to the big guy.

2019 is still beautiful

2020 Looking to the future

Thank you for your open source spirit.

Specific function-related Apk will be added later

Refer to the project

Dex2jar github.com/pxb1988/dex…

Smali github.com/JesusFreke/…

Wechat grab red envelope github.com/firesunCN/W…

Xpatch github.com/WindySha/Xp…

XposedBridge github.com/rovo89/Xpos…

Tai chi github.com/taichi-fram…

SandHook (Xpose compatible with Android7.0-10.0) github.com/ganyao114/S…