We reverse mobile 1s under android. How to add a little functionality and ditch a few evenings

I must say right away that despite the loud, as someone might think, title of the article - there will not be any direct hardcore at all. And also, despite the fact that reversing is most often associated with information security, there will again be nothing like this, because there was no need to bypass any protections (with the exception of a small minification). There are none of them. Mobile 1s is a free product in which no checks, protections and paid functions are built-in.







So I appeal to those who hope to see the use of deobfuscators, the use of Frida / Xposed, other tricky software - you will not find anything interesting for yourself. Here we will just use apktool, baksmali, apksigner, adb, jadx, console, text editor, javac, d8 and that's it.







I also want to disappoint in advance those who were waiting for a deep analysis of the platform, or its strong modification. Only small edits will be made to just a few files, and actually even these few files I did not make out thoroughly, all at the top.







How it all started



Iโ€™ll tell you a bit why I suddenly got the idea to somehow get into the mobile phone for 1s. At the moment, Iโ€™ve been doing native development for android for a year now, but before that I worked for 4 years as a programmer, and for the last year and a half, we often worked specifically with the mobile platform. Despite the fact that she met basic needs, she also had a lot of minuses (in addition to the programming language). In particular, it was impossible to humanly embed any external library, at least by regular means and with our then store of knowledge. Everything was also very sad, for example, with the functionality of displaying labels on a map. The whole possibility of its configuration was to specify text for labels when you tap on them. At that time, the only way to somehow get around this was to use the special object "HTML Document Field", but there were problems with it. At the time of working with 1s, all my knowledge in the native development for android was in a HelloWorld pair, so I didnโ€™t even think of reversing the mobile phone with 1s, we either didnโ€™t solve various questions from customers about the non-standard extension of 1s, or we saw very simple native ones the applications that were placed next to it and crookedly / obliquely integrated with 1s (and the 1s license agreement seems to prohibit edits in the platform itself).







So the first reason to do 1c reversal was that I became interested and what I can do with the current knowledge base. I must say right away that the experience of native development was not to say what is needed, in everyday work almost nothing of what will be described below is not found. So, in principle, probably any average 1snik sitting for several days / weeks would be able to figure it all out.







The second reason is that I just wanted to try picking other people's apk, because before that I was missing this rather wide layer of knowledge, and since it was necessary to start somewhere, it occurred to me just 1s.







The first steps



The first thing I did, even when the idea of โ€‹โ€‹reversing 1s only vaguely wandered in my head, I simply dragged the apk file of the application onto the AndroidStudio window with the mouse. I want to say right away that I felt a little sad, because most of the 1c code is written in C ++ and lies in .so libraries, and it is more difficult to reverse them, and it was not interesting. But the classes.dex file completely captured my attention, especially since its modest size made it possible for me to assume that it would be easy to reverse it. In general, it turned out that way. By the way, an interesting consequence of the fact that most of the code in C ++ is that a lot of methods and classes have avoided processing with ProGuard. It is difficult to do an interop with a minified code;).







Here is what I saw in the studio window (disassembled the x86 version to work with the emulator and not the real device)







As you can see in the screenshot above, the application was hardly minified (in terms of renaming classes and methods). Plus, you can see that there is very little code in java and so libraries occupy almost all the space.







Having pushed a list of classes for some time, I saw a curious MapImpl class that casts suspicion that it was he who was responsible for such a sad situation with customizing the display of labels.







MapImpl Structure







The list of methods and their signatures inspired hope that everything would be very simple, and I decided to look into the smali code, after which I worked hard and went to read the list of smali commands, and how to read / write it. However, it was at this point that I decided that the matter promises to be simple and short-lived, and accordingly, here it is, an occasion to play around with reversing. Having decided for himself to devote a couple of nights to this (how cruelly I was mistaken), I went to bed with a calm soul.







Making plans



Having woken up in the morning, I decided that since up to this point I had never even been engaged in substituting resources in applications, not like changes in logic, I had nothing to swindle right away at replacing the activity with a map, it was better to approach the solution of the problem with small steps. After that I made myself a short list of steps that was supposed to lead me to the end point of my journey in the form of a working mobile platform with my edits. This list was as follows:







  1. Prepare 1c configuration for the mobile platform, all of whose functionality is to display two labels on the map.
  2. Unpack the apk of the mobile platform, pack without changes, make sure the configuration is working.
  3. Make small changes to smali files that do not change almost anything, but which could be seen in the logs or change the logic of work, assemble and make sure that it works.
  4. Overtake smali activity with a map in java, or write an activity with the same interface, replace activity in the application without changing functionality. As an alternative, if the first one is too lazy or complicated, write a class that will do part of the work for MapImpl and add a call to its methods from smali MapImpl.
  5. Write this article (I have long wanted to write something, but other ideas are still brewing in my head, but then it seemed that this was finally a pretty worthy topic)


Well, well, at least with the first paragraph I had no problems. I downloaded the free educational version of the platform, downloaded the mobile platform, deployed it all on my computer (1C, do you already have the educational version for linux, I just have another impulse to leave Windows, and now I have to do some work in it). The configuration is very simple, at startup it calls the ShowOnMap () function, which transmits two points on the map. Everything.







But with the second point, it turned out sadder because of my illiteracy. But what there, the problems were with all points except the first.







I repack the application without changing the code



Google immediately told me that there is such an excellent apktool tool that allows literally two commands to parse apk to .smali files, as well as to assemble them back. I decided to use it. I unpacked apk with the following command (hereinafter, despite the fact that at times I did part of the work on linux, I will give all the commands for windows):







apktool.bat d .\sourceApk\1cem-x86.apk -o .\build\unpacked
      
      





and got the unpacked directory in which a bunch of files lay, it was to work with them in the future. Packed apk back with the command







 apktool.bat b .\build\unpacked -o .\build\1cem-x86.apk
      
      





Trying to install it on an emulator







 adb install .\build\1cem-x86.apk
      
      





I got the following error:







 Performing Streamed Install adb: failed to install .\build\1cem-x86.apk: Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Failed to collect certificates from /data/app/vmdl1115375036.tmp/base.apk: Attempt to get length of null array]
      
      





In general, it was necessary to pay more attention to the base. Of course, I knew that there are signatures, and that they are divided into sales and release, but I did not know that applications are not installed without signatures at all. I decided to use my debit signature, and signing the apk team







 apksigner.bat sign --ks C:\Users\neikist\.android\debug.keystore .\build\1cem-x86.apk
      
      





I managed to run the application. True, instead of a map with two points, a gray screen with a google signature below was waiting for me. After scratching the turnip, I decided that the fact is that the key for Google maps from 1s is used, which does not work with my signature. Therefore, going to the developer's console on google, I created a new project for working with google maps api on android, got an api key, which I specified in res / values โ€‹โ€‹/ strings.xml in the google_maps_key line, and also added my share key to the permissions for the project . I repacked and re-signed apk, launched it, and finally everything worked again.







I add my logs



The next thing I wanted to do was automate the launch of the application and adding the 1c configuration to the platform. Manually, after every edit, doing this would be still a mess.







At first I googled and tried to use jadx or dex2jar utilities, so as not to bother reading smali, but to read more familiar java code, but for some reason they didnโ€™t work (later on, jadx managed to get into some kind of shamanism). I had to disassemble smali, since it turned out not to be as terrible as I was afraid.







To understand how the mobile platform receives a command from the desktop platform to start the configuration when connecting via adb, I decided to look at the entry points to the application and add the output to the logs of intents and other useful information. I decided to start with application ( com.e1c.mobile.E1cApplication



) and activity having android.intent.action.MAIN



in intent-filter ( com.e1c.mobile.App



). I was also interested in reciever com.e1c.mobile.Starter



with an interesting intent filter on com.e1c.mobile.START_TEMPLATE



and com.e1c.mobile.START_CMD



. It is very similar to the fact that it accepts intents with 1c commands, and it is he who starts the configuration from the template.







Unfortunately, I could not find anything interesting in E1cApplication



, all that happens there is installing your handler on caches.







But in the other two classes, Starter



and App



, there was much more information and it turned out to be quite useful. The App.onCreate(Landroid/os/Bundle;)V



method is quite large, so I wonโ€™t give it in its entirety, I will give only the parts that interest me.







App onCreate Method Slice
 invoke-virtual {p0}, Lcom/e1c/mobile/App;->getIntent()Landroid/content/Intent; #     p1 move-result-object p1 new-instance v0, Ljava/lang/StringBuilder; invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V invoke-virtual {v0, p0}, Ljava/lang/StringBuilder;->append(Ljava/lang/Object;)Ljava/lang/StringBuilder; const-string v1, ".onCreate() " invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/Object;)Ljava/lang/StringBuilder; invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; move-result-object v0 # ,  ? invoke-static {v0}, Lcom/e1c/mobile/Utils;->V(Ljava/lang/String;)V
      
      





It can be seen from the above code section that the application receives the intent, puts a link to it in the p1



register, appends the StringBuilder



to the class name and method via StringBuilder



and passes V(Ljava/lang/String;)V



of the Utils



class to the static method. Apparently some kind of your own logger. After that, the intent is checked for additional data, and, if available, Uri



obtained from it, from which, in turn, a string is obtained by the getQuery()



method and passed to the Starter



class. onCreate



did not see onCreate



sense in further studying onCreate



, ran a glance and made sure that the actions were pretty standard. Unless the view seems to be created programmatically by a completely different class, instead of using LayoutInflater



, but I didnโ€™t dig very deeply, so everything is possible and not as I thought. The next class where I went is Starter



. Fortunately, he was mentioned in activism and interested me in the manifest.







Unfortunately, the method that was called from the activity turned out to be native ( .method static native startCmd(Ljava/lang/String;)V



), therefore I paid attention to the onRecieve



method (in which the application accepts the events it is subscribed to). I will not give the code, I was interested in it that the intent is also logged using the V(Ljava/lang/String;)V



method of the Utils



class. It turns out that itโ€™s enough to add a little smali code to this method - and I will get my logs, moreover, at the same places where the platform developers conceived them. Going to the Utils class, I immediately saw 2 empty methods, V and W. Apparently they are empty because when compiling the release version they were cut out. I just added a record of the passed string to the standard android.utils.Log



in them. To do this, change the number of required local registers from 0 to 1 (for the tag line), put this same line in the v0



register, and also registered the call to the Log



methods







Implementation of methods W and V
  .method public static V(Ljava/lang/String;)V .locals 1 const-string v0, "neikist_V" invoke-static {v0, p0}, Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I return-void .end method .method public static W(Ljava/lang/String;)V .locals 1 const-string v0, "neikist_W" invoke-static {v0, p0}, Landroid/util/Log;->w(Ljava/lang/String;Ljava/lang/String;)I return-void .end method
      
      





I launched mobile from 1s desktop 1s and received the following logs







Launch Results
  2019-10-25 19:21:31.537 29960-29960/? V/neikist_V: com.e1c.mobile.Starter@139df1c.onReceive( android.app.ReceiverRestrictedContext@346b725, Intent { act=com.e1c.mobile.START_TEMPLATE flg=0x400010 cmp=com.e1c.mobile/.Starter (has extras) } ) 2019-10-25 19:21:31.537 29960-29960/? V/neikist_V: context.getPackageName() = com.e1c.mobile 2019-10-25 19:21:31.537 29960-29960/? V/neikist_V: intent.getAction() = com.e1c.mobile.START_TEMPLATE 2019-10-25 19:21:31.537 29960-29960/? V/neikist_V: App.sActivity = null 2019-10-25 19:21:31.537 29960-29960/? V/neikist_V: templatePath = /sdcard/Download/mobilegeoMA/1cema.xml 2019-10-25 19:21:31.537 29960-29960/? V/neikist_V: debugUrl = tcp://127.0.0.1:1560 2019-10-25 19:21:42.216 29960-29960/com.e1c.mobile V/neikist_V: com.e1c.mobile.App@423609b.onCreate() Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.e1c.mobile/.App bnds=[35,252][237,505] } 2019-10-25 19:21:42.651 29960-29960/com.e1c.mobile V/neikist_V: com.e1c.mobile.App$4@4c70381.run() adMobAppId = ca-app-pub-3940256099942544~3347511713 2019-10-25 19:21:42.687 29960-30009/com.e1c.mobile V/neikist_V: App.requestPermission() shouldShowRequestPermissionRationale = false 2019-10-25 19:21:46.138 29960-30009/com.e1c.mobile V/neikist_V: App.isLowBattery() sLastBatteryPercent = 1.0
      
      





As you can see - there is all the necessary information to automate the launch of the application through adb. True, at this point I caught a double facespalm. Firstly, I finally picked up the keys with which jadx mastered the translation in java (it is clear that I would have to write in smali anyway, but still). And the second facespalm turned out to be the fact that I realized that I was tormenting the developer's platform in vain (needed only for development and debugging of configurations), it would be more correct to reverse the release builds of the first applications, and there are semi-ready gradle projects for assembly, there is a file with a list of dependencies and others buns. I was a little sad about this - and still decided to finish what I started. Anyway, for the sake of the fan, Iโ€™m doing all this, and not for the sake of practical benefit.







Platform launch through adb



I decided to automate the installation and launch through the adb application using gradle. Anyway, by that time I already had a small list of shuffles written (unpacking, copying modified resources and smali files to the directory with the unpacked application, packaging, signature, in fact all tasks are just runners for console commands). The following commands added to existing tasks







  adb install ./build/1cem-x86.apk adb shell pm grant com.e1c.mobile android.permission.READ_EXTERNAL_STORAGE adb push src/1cConfiguration /storage/emulated/0/Download adb shell am start -n com.e1c.mobile/.App -a android.intent.action.MAIN -c android.intent.category.LAUNCHER adb shell am broadcast -n com.e1c.mobile/.Starter -a com.e1c.mobile.START_TEMPLATE -e templatepath /storage/emulated/0/Download/1cConfiguration/1cema.xml
      
      





By the above sequence of commands, we install apk, give the installed application permission to read the disk, copy the 1s configuration to the device, and give commands to start 1s and load the configuration. This does not work by the way on all versions of the android, but I tested on api 28, and accordingly on it this sequence does everything correctly. On younger versions there may be problems with the issuance of rights.







I am involved in data processing from 1s and modify tags



Here I had several options for the further development of events.







  1. I directly rule MapImpl.smali, which promised to be a rather difficult task, given that the library for working with Google maps is quite seriously minified, and I would have to write everything in the smali syntax.
  2. I use the MapImpl.java file obtained thanks to jadx, make changes to it, use the classes I use to replace it with stubs, distill into smali, replace the file, and pack it. The option seemed tense to me since it really hurt to have a lot of plugs, and besides, there were problems with some of them that I didnโ€™t want to pick.
  3. I combine approaches 1 and 2, insert the calls of a special extension class MapImplExtenstion.java in smali code that extends part of the logic - as a result, I chose this option, simply because it is simpler than the first and second options, and seemed to me even a little more complicated than option 4, but less laborious (yeah, dream, naive).
  4. In general, replace Google maps with something else, for example Yandex. To do all the work in another project, simply by repeating the MapImpl signatures, then minify unpack, and just drop the finished smali files before packing the application to the appropriate places. There are no problems with minified libraries, but you would have to register to get a key for cards, create a separate project, bother with connecting resources and more.
  5. Same as option 4, but replace Google with ... Google. But here I had suspicions that it would be possible to minify 1 in 1 maps sdk, and there was no desire to experiment.


Following the third option, I created 2 subdirectories in src: java, for MapImplExtenstion.java and stubJava for files that are needed only for compilation. Since I decided to tweak the labels a bit, I was interested in the following two fields:







Fields Xj: Ljava / util / ArrayList and Xk: Ljava / util / ArrayList
  .field private final Xj:Ljava/util/ArrayList; .annotation system Ldalvik/annotation/Signature; value = { "Ljava/util/ArrayList<", "Lcom/google/android/gms/maps/model/LatLng;", ">;" } .end annotation .end field .field private Xk:Ljava/util/ArrayList; .annotation system Ldalvik/annotation/Signature; value = { "Ljava/util/ArrayList<", "Lcom/google/android/gms/maps/model/MarkerOptions;", ">;" } .end annotation .end field
      
      





Using jadx to decompile in java, I found in decompiled code a method responsible for filling them







KN method
  void kN() { Bundle extras = getIntent().getExtras(); if (extras != null) { this.Sd = extras.getLong("native"); String[] stringArray = extras.getStringArray("titles"); double[] doubleArray = extras.getDoubleArray("coords"); if (doubleArray != null && doubleArray.length > 0) { for (int i = 0; i < stringArray.length; i++) { int i2 = i * 2; LatLng latLng = new LatLng(doubleArray[i2], doubleArray[i2 + 1]); this.Xj.add(latLng); this.Xk.add(new MarkerOptions().c(latLng).dS(stringArray[i])); } } } }
      
      





Accordingly, the MapImplExtension class has added the ArrayList [] kN (String [] titles, double [] coordinates) method which in the first element of the array will return the list that will be placed in Xj, and in the second, the list for Xk.







Class MapImplExtension
  package com.e1c.mobile; import java.util.ArrayList; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.MarkerOptions; class MapImplExtension { private MapImpl mapImpl; MapImplExtension(MapImpl mapImpl){ this.mapImpl = mapImpl; } ArrayList[] kN(String[] titles, double[] coordinates) { ArrayList[] result = new ArrayList[2]; ArrayList<LatLng> xj = new ArrayList<>(); ArrayList<MarkerOptions> xk = new ArrayList<>(); result[0] = xj; result[1] = xk; int coordinatesOffset; if (coordinates != null && coordinates.length > 0) { for (int i = 0; i < titles.length; i++) { coordinatesOffset = i * 2; LatLng latlng = new LatLng(coordinates[coordinatesOffset], coordinates[coordinatesOffset + 1]); xj.add(latlng); xk.add(new MarkerOptions().c(latlng).dS(" \n" + titles[i])); } } return result; } }
      
      





Compiled with the following commands, first in class, then in dex, then decompiled in smali, so that later it can be packed with other files







  javac -encoding UTF-8 -d .\build -source 1.8 -target 1.8 -sourcepath .\src\stubJava .\src\java\com\e1c\mobile\MapImplExtenstion.java d8.bat .\build\com\e1c\mobile\MapImplExtension.class --output .\build java -jar C:\Path\to\baksmali\baksmali.jar d .\build\classes.dex -o .\build\unpackedOwn
      
      





Added the extension field of type of our new class to MapImpl.smali, and added its initialization







Adding a field
 #    .field private extension:Lcom/e1c/mobile/MapImplExtension; #  ,  return-void new-instance v0, Lcom/e1c/mobile/MapImplExtension; invoke-direct {v0, p0}, Lcom/e1c/mobile/MapImplExtension;-><init>(Lcom/e1c/mobile/MapImpl;)V iput-object v0, p0, Lcom/e1c/mobile/MapImpl;->extension:Lcom/e1c/mobile/MapImplExtension;
      
      





And also replaced the processing in the MapImpl class of data from 1s with the processing in the MapImplExtension class







Changes in MapImpl.kN ()
  # v0 - ; v1 - ; v2 - extension; #  MapImplExtension.kN(...) iget-object v2, p0, Lcom/e1c/mobile/MapImpl;->extension:Lcom/e1c/mobile/MapImplExtension; invoke-virtual {v2, v1, v0}, Lcom/e1c/mobile/MapImplExtension;->kN([Ljava/lang/String;[D)[Ljava/util/ArrayList; # v0 -   ; v1 - | ; v2 -    move-result-object v0 const/4 v2, 0x0 aget-object v1, v0, v2 iput-object v1, p0, Lcom/e1c/mobile/MapImpl;->Xj:Ljava/util/ArrayList; const/4 v2, 0x1 aget-object v1, v0, v2 iput-object v1, p0, Lcom/e1c/mobile/MapImpl;->Xk:Ljava/util/ArrayList;
      
      





Having packed and launched the application, I joyfully saw that everything was working. But at the moment I have not done anything that would expand the capabilities of the mobile platform. Just replaced the headers at the marks on the map. But when I wanted to change the image of the tags, I broke off a lot and buried myself for quite some time in the sources and documentation.







Adding New Functionality



First, a little explanation of how to set the marker for the label. The MarkerOptions



class has a public MarkerOptions icon (BitmapDescriptor iconDescriptor)



to which an object created by one of the static methods of the BitmapDescriptorFactory



class is BitmapDescriptorFactory



.







And here, in fact, a bummer was waiting for me. Since 1c does not use this business, the corresponding classes were simply cut out during minification. I had to restore them, and it was painful and long. More precisely, the classes were there, but they were renamed and did not contain the necessary methods. By picking in the sorts of a complete library for working with maps - by signatures and constants, I found out the correspondence between the library classes and application classes, added the necessary methods and fields, rebuilt it, and was glad that this quest finally finished, since it finally worked, albeit not from the first attempt (several times mowed with signatures and confused minified classes).







The result (outwardly not very impressive). Two points with coordinates (45; 45) and (46; 46) are selected by the method of spear







Since the volume of the changed code is quite large - I will not include it in the article, I will give only the signature changes. If anyone is interested in all the edits that I made at this step - you can look in this commit .







    Lcom/google/android/gms/maps/model/MarkerOptions -   .method public final icon(Lcom/google/android/gms/maps/model/a;)Lcom/google/android/gms/maps/model/MarkerOptions;   com.google.android.gms.internal.fh   : b zza(int) throws RemoteException; b zza(String) throws RemoteException; b zzb(String) throws RemoteException; b zzi() throws RemoteException; b zza(float) throws RemoteException; b zza(Bitmap) throws RemoteException; b zzc(String) throws RemoteException;   com.google.android.gms.internal.fj     h       com.google.android.gms.maps.model.b (BitmapDescriptorFactory)    public static a fromResource(int) public static a fromAsset(String) public static a fromFile(String) public static a fromPath(String) public static a defaultMarker() public static a defaultMarker(float) public static a fromBitmap(Bitmap)      MapImplExtension.java   xk.add(new MarkerOptions().c(latlng).dS("  " + titles[i]));     MarkerOptions markerOptions = new MarkerOptions() .c(latlng) .dS("  " + titles[i]); if (i == 0) { //      markerOptions.icon(b.defaultMarker(b.HUE_YELLOW)); } xk.add(markerOptions);
      
      





To summarize - the experience was quite interesting. At least for me. I helped to figure out some tools, learned a little better about the work of the android and the bytecode format for dalvik / art, the experience of picking minified code would also be useful (there was already a case when in the release version of R8 I cut class fields that were actually used, at that time only by the method I figured it out, now it would not cause problems).







If someone is interested in repeating everything himself and possibly picking up even more - I posted on github all the sources along with a crooked gradle script that builds from the original apk modified.








All Articles