📜 ⬆️ ⬇️

Modification of stock firmware for Android. Part 5. Revolution c Xposed Framework

In 2012, a user with the nickname rovo89 on the XDA community published source codes and a ready-to-use framework that simplifies customization of firmware, with detailed instructions and examples, offering an alternative to the traditional, at that time, method (deodexing → disassembling → decompiling → recompiling → testing → download patches into the phone): Part 1 , Part 2 , Part 3 , Part 4

He proposed the use of separate modules that can be changed on the fly, without interfering with the source code of the firmware or its individual components. But reputable developers choir replied: " No ... nobody needs it "

Rovo did not abandon his brainchild, but continued to develop. When the KitKat version came out in 2013, the same community of eminent developers replied: " Nope, this is too dangerous ... although ... "
')
Meanwhile, Google decided to release a new version of the operating system annually. Of course, it became expensive to customize the firmware: you do not have time to make one, as a new OS version appears. So in 2014, the Lollipop version comes out and the developers finally pay attention to the framework with the thoughts: “ Maybe it’s still worth seeing what it is? It looks promising .”

In 2015, the next version (Marshmallow) was released, and most of the community, as a result, recognized the development as very worthwhile, shortening the development time for customizing stock firmware as an installation of separate modules that extend functionality. “ Wow, this is absolutely safe and there is a library of ready-made useful modules! ” They exclaimed.

Now the end of February 2017. Xposed under Nougat is still not available, and crowds of afflicted developers and users continually open topics like " Xposed does not work! I need it! Rovo, please! "

Today I will talk about the Xposed Framework.

Denial of responsibility


Any product names and trademarks mentioned in the text are the property of their respective owners. Texts of the article published for educational purposes. The authors (@Falseclock and xronofag ) are not responsible for possible losses, damage to equipment, lost profits due to attempts to play or use the information in this article. By continuing to read, the reader thereby confirms that any use of the source code may entail risks for it.

Retreat


More than three years ago, I began to write a series of articles about the modification of firmware for Android. At the moment, the relevance of these articles is very doubtful: the process is very time consuming, requires a lot of time and tools, and the slightest mistake can turn into significant problems until the phone is completely flashing.

I waited about two years in the hope that someone will still describe on Habré what the Xposed Framework is and how it can simplify the application customization process, but I suspect that few people are familiar with this wonderful creation. For my needs, I wrote dozens of modules, some even published in the general repository, which currently contains more than 1000 ready-for-use developments. Each module - this may not be a single customization, but a whole set of functions with a settings interface or additional functionality. A striking example of this is GravityBox or Sense ToolBox .

I will not describe the installation process and get root rights, but I want to describe what a framework is and how to write the simplest module.

Installation


If earlier to install third-party firmware, it was required to unlock the phone loader, then to use Xposed, the user only needs to have root rights on the phone. Even now, obtaining these rights on most devices is not a big deal: you download the application, but it itself does all the necessary manipulations and in a few minutes you become the owner of the phone with the “GOD” level.

The principle of work "on the fingers"


In the Android system there is a process called “Zygote”. This is the main executive system. Any process runs as its copy. Zygote is launched via /init.rc as soon as the main system kernel is loaded. Applications are launched via the / system / bin / app_process script, which loads the necessary classes and starts the application initialization through the declared methods.

This is where Xposed comes on the scene. When installing the framework, the modified app_process is copied to / system / bin. The essence of the modification is that an additional jar library is added to the variable environment, which can execute special methods under certain conditions and cases. For example, we can intervene as soon as the Dalvik virtual machine is created or even before the main Zygote method is called. As part of the Zygote process, we can interfere with the work of any methods, even synthetic ones, and perform any actions in their context.

Practical value Xposed


Suppose you need to change some method, and, for example, instead of the Boolean value TRUE, return FALSE. Instead of a time-consuming and costly traditional method (parsing, assembling, testing an application), you can “intercept” this class method and “implant” your Java code that will perform the necessary operation (change the value in this example) and return the desired result. At the same time, with Xposed, you can: modify or just see what data is transferred to the method, or after executing the method, find out the result of data processing and, depending on the requirements, change them or use them.

There are cases when you do not want this or that method to work at all, or you want to completely change the logic and algorithm. Xposed allows you to completely replace the method, or not to let it work at all.

In the case of resources, everything became simple. You load your resources into your module, even graphic elements, even solid xml files and when loading the module into memory you can replace them during initialization.

And now the main thing is that it is often not even necessary to change the operation of the module (or make minimal improvements) with the release of a new version of the firmware / application! This is logical, because the names of methods, classes and variables, as a rule, remain the same.

Isn't it convenient?

Module creation


The module itself is a regular apk file created in the development environment. It does not have to contain any Activity or graphic resources. In fact, there can only be one file with instructions, of course, besides the required files, and the module can work. To configure, we need to do three things:

Manifest.xml


When installing any application, Xposed checks for the presence of certain headers in Manifest.xml. If there are three necessary lines, the framework stores information about the application in its settings and then you can activate the installed module (hereinafter, examples of the code of different modules will be used, including those from known applications).

<meta-data android:name="xposedmodule" android:value="true" /> <meta-data android:name="xposedminversion" android:value="2.6*" /> <meta-data android:name="xposeddescription" android:value="Uber-Driver application patch tool" / 

The first line is clear.

In the second line specify the minimum version of the framework for working with the module. Each version is sharpened for a specific release of Android and that the module is not accidentally running on a platform for which it is not intended, the version is indicated.

The third line defines how your application will be identified in the list of available or installed modules on your phone.

/ assets / xposed_init


At the root of your application, you need to create an assets folder and put a file called xposed_init into it. Inside this file you simply write in which Java class the operation of your module is described. I used to call a class like XMain. In my case there is a line in the file

uber.hack.XMain

Java class


The class itself must contain one of the three methods for working with the framework. It is not necessary to specify everything, you can only indicate those with which you plan to modify the application or applications. Out of habit, I specify everything as a template.
 public class XMain implements IXposedHookInitPackageResources, IXposedHookLoadPackage, IXposedHookZygoteInit { public void initZygote(StartupParam startupParam) throws Throwable { } public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable { } public void handleLoadPackage( LoadPackageParam paramLoadPackageParam) throws Throwable { } } 

The initZygote method runs when your module is loaded into memory.
Very useful if your application has an Activity with the settings of your module. As a rule, your application data is saved to a file.
/data/data/YOUR_PACKAGE_NAME/shared_prefs/YOUR_PACKAGE_NAME_preferences.xml . At boot time, you can get a handle once and, while running, just read your settings from it. You can also predetermine any variables, make checks. In fact, this is an analogue of the constructor method.

The handleLoadPackage method runs when Dalvik loads the source code of anyone ! applications at startup. This is a very important point. If you have 10 different modules on your phone, then through this method, the sources of the launched application will be “run” in all 10 cases. For filtering, the usual check by package name is used. Of course, if you want to change the work of several applications, then put as many checks as you need.

 public void handleLoadPackage( LoadPackageParam paramLoadPackageParam) throws Throwable { final LoadPackageParam llpm = paramLoadPackageParam; String packageName = llpm.packageName; if (packageName.contains("ubercab.driver")) { } } 

You need the handleInitPackageResources method when you want to replace application resources while they are loaded into memory. This is the same as with handleLoadPackage - you filter by application name.

 public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable { String pkg = resparam.packageName; if (pkg.equals("com.ubercab.driver")) { } } 

It is important to specify the name of the application as it is specified in the manifest. This completes the configuration of the module in the development environment and we can proceed directly to programming.

Code examples


The main code, unless you decide to just change the colors or fonts of some application, occurs in handleLoadPackage. To do this, we first study the source code of the application of interest, figure out how to modify the code, and describe our logic.
The main logic works through the findAndHookMethod method, less often through the findAndHookConstructor and findClass. All basic methods can be peeped in the XposedHelpers class.

It looks like this:

 if (packageName.contains("ubercab.driver")) { try { XposedHelpers.findAndHookMethod( "com.ubercab.driver.feature.online.DispatchedFragment", llpm.classLoader, "onCreateView", "android.view.LayoutInflater", "android.view.ViewGroup", "android.os.Bundle", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { } } ); } catch (Throwable t) { XposedBridge.log(t); } } 

First of all, we need to wrap our “hooks” in try / catch, because if there is an error in your code, without the wrapper, the main application can complete the work with an error, and the full trace can get to the developer who becomes aware that his application imposed a module. When wrapping the whole trace, you can put Xposed into the log and understand where we have an error and why.

In findAndHookMethod, the first parameter is the name of the class, the second is the link to the class loader, the third is the name of the method we need, then the enumeration is followed by strings which variables are passed to the method and at the end our callback method.

Here is an example of how to get the class context and use it.

Context catching
 try{ XposedHelpers.findAndHookMethod("com.ubercab.driver.core.app.DriverApplication", llpm.classLoader, "init", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Application application = (Application) param.thisObject; Context context = application.getApplicationContext(); Intent intent = new Intent("uber.hack.ACTION_BACKGROUND"); context.sendBroadcast(intent); } }); } catch (Throwable t) { XposedBridge.log(t); } 

We catch the loading of the init method in the DriverApplication class and use param.thisObject use the context to send a broadcast message to our receiver or service.

or if it is necessary to change the variables transferred to the method, then this can be done through
beforeHookedMethod
 try{ XposedHelpers.findAndHookMethod("com.htc.htcdialer.widget.DividerDrawable", paramLoadPackageParam.classLoader, "setDividerColor", "int", "int", new XC_MethodHook() { protected void beforeHookedMethod(MethodHookParam param) throws Throwable { int paramInt1 = (Integer) param.args[0]; int paramInt2 = (Integer) param.args[1]; if (paramInt1 == 4) { if (paramInt2 == -13388315) param.args[1] = Color.RED; if (paramInt2 == -13128336) param.args[1] = Color.BLUE; } } }); } catch (Throwable t) { XposedBridge.log(t); } 


In addition to XC_MethodHook, you can use XC_MethodReplacement. The name speaks for itself. We completely replace some method with our own. Here is a typical example of when I wanted to get rid of a pop-up notification when I connect my phone via USB to a laptop.
SetUSBNotification
 findAndHookMethod("com.android.settings.PSService", paramLoadPackageParam.classLoader, "SetUSBNotification", "android.content.Context", boolean.class, new XC_MethodReplacement() { protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { return null; } }); 


In this void method, a notification was invoked. I simply replaced it and forgot that it once hurt me.

There were cases and more complicated. I used my modules to fix errors in system applications. It’s not particularly tempting to wait for the manufacturer to fix the bug in the new OTA application, so I fixed it myself.

Fixed bugs
 public static void tweak_fix98918() { // -------------------------------------------------- // https://android-review.googlesource.com/#/c/98918/ // -------------------------------------------------- try { final Class<?> TaskRecord = XposedHelpers.findClass("com.android.server.am.TaskRecord", null); XposedHelpers.findAndHookMethod(TaskRecord, "setFrontOfTask", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { Object mActivities = XposedHelpers.getObjectField(param.thisObject, "mActivities"); boolean foundFront = false; final int numActivities = (Integer) XposedHelpers.callMethod(mActivities, "size"); for (int activityNdx = 0; activityNdx < numActivities; activityNdx++) { final Object r = XposedHelpers.callMethod(mActivities, "get", activityNdx); if (foundFront || XposedHelpers.getBooleanField(r, "finishing")) { XposedHelpers.setBooleanField(r, "frontOfTask", false); } else { XposedHelpers.setBooleanField(r, "frontOfTask", true); foundFront = true; } } if (!foundFront && numActivities > 0) { Object get = XposedHelpers.callMethod(mActivities, "get", 0); XposedHelpers.setBooleanField(get, "frontOfTask", true); } return null; } }); } catch (Throwable t) { XposedBridge.log(t); } } // -------------------------------------------------- // https://android-review.googlesource.com/#/c/81970/ // -------------------------------------------------- public static void tweak_fix81970() { try { final Class<?> ActiveServices = XposedHelpers.findClass("com.android.server.am.ActiveServices", null); XposedHelpers.findAndHookMethod(ActiveServices, "killServicesLocked", "com.android.server.am.ProcessRecord", "boolean", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { // XposedBridge.log("killServicesLocked happen"); Boolean DEBUG_SERVICE = (Boolean) XposedHelpers.getStaticBooleanField(ActiveServices, "DEBUG_SERVICE"); String TAG = (String) XposedHelpers.getStaticObjectField(ActiveServices, "TAG"); Object app = param.args[0]; boolean allowRestart = (Boolean) param.args[1]; Object services = XposedHelpers.getObjectField(app, "services"); int size = (Integer) XposedHelpers.callMethod(services, "size"); // First clear app state from services. for (int i = size - 1; i >= 0; i--) { Object sr = XposedHelpers.callMethod(services, "valueAt", i); Object stats = XposedHelpers.getObjectField(sr, "stats"); synchronized (XposedHelpers.callMethod(stats, "getBatteryStats")) { XposedHelpers.callMethod(stats, "stopLaunchedLocked"); } Object sr_app = XposedHelpers.getObjectField(sr, "app"); Boolean persistent = XposedHelpers.getBooleanField(sr_app, "persistent"); Boolean stopIfKilled = XposedHelpers.getBooleanField(sr, "stopIfKilled"); if (sr_app != null && !persistent && stopIfKilled) { Object sr_app_services = XposedHelpers.getObjectField(sr_app, "services"); XposedHelpers.callMethod(sr_app_services, "remove", sr); } XposedHelpers.setObjectField(sr, "app", null); XposedHelpers.setObjectField(sr, "isolatedProc", null); XposedHelpers.setObjectField(sr, "executeNesting", 0); XposedHelpers.callMethod(sr, "forceClearTracker"); Object mDestroyingServices = XposedHelpers.getObjectField(param.thisObject, "mDestroyingServices"); Boolean check = (Boolean) XposedHelpers.callMethod(mDestroyingServices, "remove", sr); if (check) { if (DEBUG_SERVICE) Slog.v(TAG, "killServices remove destroying " + sr); } Object bindings = XposedHelpers.getObjectField(sr, "bindings"); final int numClients = (Integer) XposedHelpers.callMethod(bindings, "size"); for (int bindingi = numClients - 1; bindingi >= 0; bindingi--) { Object IntentBindRecord = XposedHelpers.callMethod(bindings, "valueAt", bindingi); if (DEBUG_SERVICE) Slog.v(TAG, "Killing binding " + IntentBindRecord + ": shouldUnbind=" + XposedHelpers.getObjectField(IntentBindRecord, "hasBound")); XposedHelpers.setObjectField(IntentBindRecord, "binder", null); XposedHelpers.setObjectField(IntentBindRecord, "requested", false); XposedHelpers.setObjectField(IntentBindRecord, "received", false); XposedHelpers.setObjectField(IntentBindRecord, "hasBound", false); } } // Clean up any connections this application has to // other // services. Object connections = XposedHelpers.getObjectField(app, "connections"); size = (Integer) XposedHelpers.callMethod(connections, "size"); for (int i = size - 1; i >= 0; i--) { Object ConnectionRecord = XposedHelpers.callMethod(connections, "valueAt", i); XposedHelpers.callMethod(param.thisObject, "removeConnectionLocked", ConnectionRecord, app, null); } XposedHelpers.callMethod(connections, "clear"); Object smap = XposedHelpers.callMethod(param.thisObject, "getServiceMap", XposedHelpers.getObjectField(app, "userId")); // Now do remaining service cleanup. services = XposedHelpers.getObjectField(app, "services"); size = (Integer) XposedHelpers.callMethod(services, "size"); for (int i = size - 1; i >= 0; i--) { Object sr = XposedHelpers.callMethod(services, "valueAt", i); Object mServicesByName = XposedHelpers.getObjectField(smap, "mServicesByName"); if (XposedHelpers.callMethod(mServicesByName, "get", XposedHelpers.getObjectField(sr, "name")) != sr) { Object cur = XposedHelpers.callMethod(mServicesByName, "get", XposedHelpers.getObjectField(sr, "name")); Slog.wtf(TAG, "Service " + sr + " in process " + app + " not same as in map: " + cur); Object app_services = XposedHelpers.getObjectField(app, "services"); XposedHelpers.callMethod(app_services, "removeAt", i); continue; } // Any services running in the application may // need to be // placed back in the pending list. Object serviceInfo = XposedHelpers.getObjectField(sr, "serviceInfo"); Object applicationInfo = XposedHelpers.getObjectField(serviceInfo, "applicationInfo"); if (allowRestart && XposedHelpers.getIntField(sr, "crashCount") >= 2 && (XposedHelpers.getIntField(applicationInfo, "flags") & ApplicationInfo.FLAG_PERSISTENT) == 0) { Slog.w(TAG, "Service crashed " + XposedHelpers.getIntField(sr, "crashCount") + " times, stopping: " + sr); EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH, XposedHelpers.getObjectField(sr, "userId"), XposedHelpers.getObjectField(sr, "crashCount"), XposedHelpers.getObjectField(sr, "shortName"), XposedHelpers.getObjectField(app, "pid")); XposedHelpers.callMethod(param.thisObject, "bringDownServiceLocked", sr); } else if (!allowRestart) { XposedHelpers.callMethod(param.thisObject, "bringDownServiceLocked", sr); } else { boolean canceled = (Boolean) XposedHelpers.callMethod(param.thisObject, "scheduleServiceRestartLocked", sr, true); // Should the service remain running? Note // that in the // extreme case of so many attempts to // deliver a command // that it failed we also will stop it here. if (XposedHelpers.getBooleanField(sr, "startRequested") && (XposedHelpers.getBooleanField(sr, "stopIfKilled") || canceled)) { Object pendingStarts = XposedHelpers.getObjectField(sr, "pendingStarts"); if ((Integer) XposedHelpers.callMethod(pendingStarts, "size") == 0) { XposedHelpers.setBooleanField(sr, "startRequested", false); if (XposedHelpers.getObjectField(sr, "tracker") != null) { Object tracker = XposedHelpers.getObjectField(sr, "tracker"); Object mAm = XposedHelpers.getObjectField(param.thisObject, "mAm"); Object mProcessStats = XposedHelpers.getObjectField(mAm, "mProcessStats"); XposedHelpers.callMethod(tracker, "setStarted", false, XposedHelpers.callMethod(mProcessStats, "getMemFactorLocked"), SystemClock.uptimeMillis()); } if (!XposedHelpers.getBooleanField(sr, "hasAutoCreateConnections")) { // Whoops, no reason to restart! XposedHelpers.callMethod(param.thisObject, "bringDownServiceLocked", sr); } } } } } if (!allowRestart) { Object app_services = XposedHelpers.getObjectField(app, "services"); XposedHelpers.callMethod(app_services, "clear"); // Make sure there are no more restarting // services for this // process. Object mRestartingServices = XposedHelpers.getObjectField(param.thisObject, "mRestartingServices"); for (int i = (Integer) XposedHelpers.callMethod(mRestartingServices, "size") - 1; i >= 0; i--) { Object r = XposedHelpers.callMethod(mRestartingServices, "get", i); String processName = (String) XposedHelpers.getObjectField(r, "processName"); Object serviceInfo = XposedHelpers.getObjectField(r, "serviceInfo"); Object applicationInfo = XposedHelpers.getObjectField(serviceInfo, "applicationInfo"); Object info = XposedHelpers.getObjectField(app, "info"); if (processName.equals((String) XposedHelpers.getObjectField(app, "processName")) && XposedHelpers.getIntField(applicationInfo, "uid") == XposedHelpers.getIntField(info, "uid")) { XposedHelpers.callMethod(mRestartingServices, "remove", i); XposedHelpers.callMethod(param.thisObject, "clearRestartingIfNeededLocked", r); } } Object mPendingServices = XposedHelpers.getObjectField(param.thisObject, "mPendingServices"); for (int i = (Integer) XposedHelpers.callMethod(mPendingServices, "size") - 1; i >= 0; i--) { Object r = XposedHelpers.callMethod(mPendingServices, "get", i); String processName = (String) XposedHelpers.getObjectField(r, "processName"); Object serviceInfo = XposedHelpers.getObjectField(r, "serviceInfo"); Object applicationInfo = XposedHelpers.getObjectField(serviceInfo, "applicationInfo"); Object info = XposedHelpers.getObjectField(app, "info"); if (processName.equals((String) XposedHelpers.getObjectField(app, "processName")) && XposedHelpers.getIntField(applicationInfo, "uid") == XposedHelpers.getIntField(info, "uid")) { XposedHelpers.callMethod(mPendingServices, "remove", i); } } } // Make sure we have no more records on the stopping // list. Object mDestroyingServices = XposedHelpers.getObjectField(param.thisObject, "mDestroyingServices"); int i = (Integer) XposedHelpers.callMethod(mDestroyingServices, "size"); while (i > 0) { i--; Object sr = XposedHelpers.callMethod(mDestroyingServices, "get", i); if (XposedHelpers.getObjectField(sr, "app") == app) { XposedHelpers.callMethod(sr, "forceClearTracker"); XposedHelpers.callMethod(mDestroyingServices, "remove", i); if (DEBUG_SERVICE) Slog.v(TAG, "killServices remove destroying " + sr); } } Object executingServices = XposedHelpers.getObjectField(app, "executingServices"); XposedHelpers.callMethod(executingServices, "clear"); return null; } }); } catch (Throwable t) { XposedBridge.log(t); } } 


This is how I allowed all applications to write information on an SD card, when this functionality was cut down for security purposes on KitKat.

hookSDcardPermission
 try { XposedHelpers.findAndHookMethod("com.android.server.SystemConfig", paramLoadPackageParam.classLoader, "readPermission", "org.xmlpull.v1.XmlPullParser", "java.lang.String", new XC_MethodHook() { protected void afterHookedMethod(MethodHookParam param) throws Throwable { String permission = (String) param.args[1]; if (permission.equals("android.permission.WRITE_EXTERNAL_STORAGE")) { Class<?> process = XposedHelpers.findClass("android.os.Process", null); int gid = (Integer) XposedHelpers.callStaticMethod(process, "getGidForName", "media_rw"); Object mPermissions = XposedHelpers.getObjectField(param.thisObject, "mPermissions"); Object localPermissionEntry = XposedHelpers.callMethod(mPermissions, "get", permission.intern()); int[] gids = (int[]) XposedHelpers.getObjectField(localPermissionEntry, "gids"); XposedHelpers.setObjectField(localPermissionEntry, "gids", ArrayUtils.appendInt(gids, gid)); } } }); } catch (Throwable t) { XposedBridge.log(t); } 


Access to variables and class methods is done through the XposedHelpers class. For example:

 Object mActivity = XposedHelpers.getObjectField(param.thisObject, "mActivity"); 

If the object is an accessible imported class, then the resulting object can be immediately cast to the desired type.

 (Activity) mActivity = (Activity) XposedHelpers.getObjectField(param.thisObject, "mActivity"); 

and facilitate further code. If the object is some type of internal class that is not available to us for import, further use of its methods and properties is also available through XposedHelpers.

In summary, we can change the static and final variables of any class, anytime, anywhere. Check variables passed to methods, change them before execution, change the result of individual methods, or completely replace them with your own code.

In most cases, it is enough to catch the desired method and modify or change its logic as soon as the application is launched. But there is a known problem with the limit of the number of methods in one DEX file, therefore many bulky applications have 3-5 additional DEX files. Here is hidden underwater rock. It's pretty easy to get around it:

multi dex
 public void handleLoadPackage( LoadPackageParam paramLoadPackageParam) throws Throwable { final LoadPackageParam llpm = paramLoadPackageParam; String packageName = paramLoadPackageParam.packageName; if (packageName.contains("ubercab.driver")) { //    DEX  try{ XposedHelpers.findAndHookMethod("com.ubercab.driver.feature.main.MainActivity", llpm.classLoader, "onNewIntent", "android.content.Intent", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { //    } }); } catch (Throwable t) { XposedBridge.log(t); } //    DEX . XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { //     ,     DEX . try { XposedHelpers.findAndHookMethod( "com.ubercab.driver.feature.online.DispatchedFragment", llpm.classLoader, "onCreateView", "android.view.LayoutInflater", "android.view.ViewGroup", "android.os.Bundle", new XC_MethodHook() { //    } ); } catch (Throwable t) { XposedBridge.log(t); } } }); } } 


As for obfuscated applications, at first glance you need to rewrite the module with each new application release. There is always a way out of this situation, but this is a separate topic. The basic principle is to find a class that is not obfuscated and backtracking to determine the names of classes and methods.

Conclusion


I gave the basic tricks and solutions to the problems of programming that I encountered in the course of the development of various modules. I see no reason to analyze each case in detail, since experienced programmers just need to use the guides from the author here and here .

With this I plan to finish the cycle of articles about the modification of firmware, but the theme of the practical application of Xposed, I hope, is not closed. There are plans for a large article about how I worked as a Uber driver and developed the Xposed module, which gave me extensive information about both the upcoming trip and during the order, which, to my surprise, is not provided in the standard application. He got a very, very interesting experience and made conclusions: both about the quality of the Uber architecture itself, and about what information is transmitted through the application about users and how, perhaps, the company plans to monetize its service and the data obtained in the future.

Source: https://habr.com/ru/post/318202/


All Articles