📜 ⬆️ ⬇️

How to create patches based on trusting Android security policy

Greetings to you, dear reader!


Offtop

In this topic, I would like to talk about how you can get access to what belongs to us, but indirectly.
Any software is someone's property: someone sat and moved his brains to produce the next “miracle”.
But outside of our devices there can be no talk of software. The concept of "software", in this case, will mean nothing more than "softness."
It is obvious.
That is why we have the right to get up with any software code running on our device, whatever the soul desires ...

Intro

I think it’s worth starting from the very beginning. It all started with my discovery of user dependencies of others to the notorious game of 2048 . I, of course, wondered what this game is so exciting for people, so I decided to try it myself. A few minutes later I understood its essence and this game also became interesting for me.
I have to admit that I didn’t manage to gain more than 5000 points, and until 2048 it was still too long ...
Meanwhile, the surrounding "players" easily stuffed 10-16 thousand points, and some even managed to reach 2048 in the cage.
Well, after a while, I had the idea of ​​stupidly hacking this game so as not to “kill” the time for its passage (it is too long in passing, but no less delaying). And a couple of days later, my acquaintances asked me to do the same.
This meant one thing: it's time to get down to business ...


')


What was at the beginning?

This hacking did not start with something unusual. Everything was according to a pattern: I pulled out the application's APK package, performed the primary decompilation, using apktool.jar , then the secondary one (to get the Java code from Smali), using the jd-gui tool . Nothing unusual. Then I traveled for a long time over Java classes in the hope of finding at least something useful and interesting. In addition to the "tons" of advertising and Google libraries, I did not notice anything. In the end, I managed to stumble upon the insides of this application, that is, where all the interesting things happen:

image

But, as you can see, few people will want to rummage through all this, apparently obfuscated code: the original names of classes, methods and variables are just a short set of letters. The study at this stage is at an impasse ...
I decided to quit this business and do something more useful. But a few days later I decided to return to this topic again.

Beginning of the End

I suddenly decided to just dig into the data stored by the application. It could be information about everything, starting with the color of the cells in the field and ending with the current number of points. The data structure of the application is as follows:



Now in order:



From this we conclude that only two folders are of interest: files and shared_prefs .
Well, look what's inside them.

image

image

In the first case, we are interested in the save.plist file, and in the second - the only Cocos2dxPrefsFile.xml file.
Their names speak for themselves. In order not to stretch the text, I will immediately provide information about both files:

1) save.plist
As it is easy to guess, this file is responsible for maintaining the state of the game before exiting. The state saving includes: a description of the cells of the playing field, the number of Undo and the current rating of the player.

2) Cocos2dxPrefsFile.xml
Here, the application stores data on the maximum number of points ever achieved.


One of the features is that these files are presented in a readable XML format:

1) Cocos2dxPrefsFile.xml

<?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map> <int name="Score" value="4846" /> //   <int name="ScoreSent" value="2410" /> <int name="BestBoxValue" value="512" /> <boolean name="FirstTime" value="true" /> </map> 


2) File save.plist
(each dict tag stores data about a specific cell at a specific point in time)

 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"/> <plist version="1.0"> <dict> <key>Main</key> <array> <dict> //     <key>Index</key> <string>1</string> //  ( 0..15 ) <key>Level</key> <string>3</string> //     <key>Score</key> <string>1204</string> //   <key>MaxUndo</key> <string>2</string> // Undo </dict> <dict> <key>Index</key> <string>4</string> <key>Level</key> <string>3</string> <key>Score</key> <string>1204</string> <key>MaxUndo</key> <string>2</string> </dict> <dict> <key>Index</key> <string>0</string> <key>Level</key> <string>1</string> <key>Score</key> <string>1204</string> <key>MaxUndo</key> <string>2</string> </dict> <dict> <key>Index</key> <string>3</string> <key>Level</key> <string>1</string> <key>Score</key> <string>1204</string> <key>MaxUndo</key> <string>2</string> </dict> <dict> <key>Index</key> <string>2</string> <key>Level</key> <string>2</string> <key>Score</key> <string>1204</string> <key>MaxUndo</key> <string>2</string> </dict> <dict> <key>Index</key> <string>14</string> <key>Level</key> <string>2</string> <key>Score</key> <string>1204</string> <key>MaxUndo</key> <string>2</string> </dict> </array> <key>Steps</key> <array> <dict> <key>Main</key> <array> <dict> <key>Index</key> <string>1</string> <key>Level</key> <string>10</string> </dict> <dict> <key>Index</key> <string>4</string> <key>Level</key> <string>10</string> </dict> <dict> <key>Index</key> <string>0</string> <key>Level</key> <string>1</string> </dict> <dict> <key>Index</key> <string>3</string> <key>Level</key> <string>1</string> </dict> <dict> <key>Index</key> <string>2</string> <key>Level</key> <string>1</string> </dict> </array> </dict> <dict> <key>Main</key> <array> <dict> <key>Index</key> <string>13</string> <key>Level</key> <string>10</string> </dict> <dict> <key>Index</key> <string>12</string> <key>Level</key> <string>10</string> </dict> <dict> <key>Index</key> <string>8</string> <key>Level</key> <string>1</string> </dict> <dict> <key>Index</key> <string>15</string> <key>Level</key> <string>1</string> </dict> <dict> <key>Index</key> <string>14</string> <key>Level</key> <string>1</string> </dict> <dict> <key>Index</key> <string>10</string> <key>Level</key> <string>1</string> </dict> </array> </dict> </array> </dict> </plist> 


Now, knowing this and having the rights on the ROOT device, you can easily adjust the game to yourself.
But not everyone has access to root.
That is why you should create a patch for this application that would be available to a wide audience.

Create patch

First, a little theory.
All ANDROID applications have their own sandbox, access to which can only be obtained by this application (or root-user). Sandbox is a folder located in the heart of OS - / data / data / * . Instead of an asterisk there may be an application package name. For example, the package name of the 2048 game is com.estoty.game2048 , as you might have guessed from the slides above. Consequently, only the game (and the root) have access to the /data/data/com.estoty.game2048 folder, and, therefore, access to all the goodies listed above.
It would seem that we remain?

Probably, you come to mind creating your own application with the same package. But when compiling and installing our fake application, we get an error INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES , which says that the key with which the game was signed does not match the key with which our fake application is signed.

So, one thing remains for us: rebuild the game in the APK and sign it with our key using the tool
JARSIGNER , and then our fake application with the same key! Plus, knowing that when reinstalling Android applications, it does not delete the game data (that is, its sandbox), we can replace the game data with our data, relying on the good and trusting Android OS security policy, and then launch the original game, which would used our fake data.
But what if the game will overwrite the data when reinstalling? Well, now check it out!

Let's start with the subscription of the game with our key (certificate). To do this, you need to disassemble it standardly (decompile, if you like), then assemble it again and then sign with your key. It is assumed that you are familiar with this process. If not, then it is well painted, for example, here .

jarsigner -keystore default.keystore -storepass *** -keypass *** 2048.apk default


So, we have an APK package of the application, signed by our default key.

Next, we create a new Android application with the name, package name and certificate (key) identical to the game 2048.
Now, between our patch application and the 2048 game there are no differences at the system level (that is, for Android, these are two identical applications that can reinstall and replace each other),

Now we need to think about how the patch will work.
Of course, everything can be done simply: write static data to the Cocos2dxPrefsFile.xml and save.plist files , which, for example, set a large number of points and large numbers on the playing field. But this is not cool. I propose to make the patch dynamic, that is, so that it can, at any time without recompilation (reassembly), set the values ​​of the rating, cells of the field, etc. we need.

If you spit on the design and design, and focus only on the functionality of the patch, you get something like this:

image

The patch includes the most "useful" features:
users will have the opportunity to change their best record, change the current one and set the numbers in each cell of the playing field!
This is more than enough.

Coddd

PS It is assumed that you have a basic understanding of the device Android-based applications. Therefore, virtually no code comments.

So, we will not do anything with the application manifest - the package name has already been installed when creating a new project, and the application name, in general, is not necessary to change. There will be no special functionality of the patch, so we also need no specific user-permissions .

By clicking on a single button, the Patch function will be called:

  private void Patch() { SharedPrefsPatch(); // 1 FilePatch(); // 2 AlertDialog.Builder alert = new AlertDialog.Builder(this); alert.setMessage("OK. Now you should install the original game without removing this app."); alert.setTitle("Success!"); alert.setCancelable(false); alert.setPositiveButton("Ready!", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); Main.this.finish(); } }); alert.show(); } 


Set a new record:

 //1 private void SharedPrefsPatch() { SharedPreferences prefs = getSharedPreferences("Cocos2dxPrefsFile", Context.MODE_WORLD_READABLE); SharedPreferences.Editor editor = prefs.edit(); editor.putInt("Score", Integer.parseInt(bestscore.getText().toString())); editor.putInt("ScoreSent", Integer.parseInt(bestscore.getText().toString())); editor.putInt("BestBoxValue", 1024); editor.putBoolean("FirstTime", true); editor.apply(); } 


And we form the playing field, as we wish (as you remember, everything is in XML format. I ask for the negligence of the format below.):

 // 2 private void FilePatch() { try { FileOutputStream fop = openFileOutput("save.plist", MODE_WORLD_READABLE); OutputStreamWriter writer = new OutputStreamWriter(fop); writer.write("" + "" + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\"/>\n" + "\n" + "<plist version=\"1.0\">\n" + " <dict>\n" + " <key>Main</key>\n" + " <array>" + "" + ""); for(int i=0;i<textboxCells.size();++i) if(textboxCells.get(i).getText().toString().trim().length()>0) writer.write("" + "" + "<dict>\n" + " <key>Index</key>\n" + " <string>"+i+"</string>\n" + " <key>Level</key>\n" + " <string>"+textboxCells.get(i).getText()+"</string>\n" + " <key>Score</key>\n" + " <string>"+currentscore.getText().toString()+"</string>\n" + " <key>MaxUndo</key>\n" + " <string>200</string>\n" + " </dict>" + ""); writer.write("" + "" + " </array>\n" + " <key>Steps</key>\n" + " <array>\n" + " <dict>\n" + "\n" + " </dict>\n" + " </array>\n" + " </dict>\n" + "</plist>" + ""); writer.flush(); writer.close(); } catch (FileNotFoundException e) { Log.i("ERRORMINOR", "********1"); } catch (IOException e) { Log.i("ERRORMINOR", "********2"); } 


Compile. Sign (certify) with the same key as game 2048.

We check the performance

1) First install the game 2048 (using ADB, for starters ):
adb install 2048.apk


image

and run:

image

Super, a game signed by a non-native key works.

2) Install the patch (Android will automatically reinstall the application, replace it with the patch, the application data, while still being saved):
adb install -r patch.apk

image

and exploit it:

image

patchim:

image

3) Reinstall our game:
adb install -r 2048.apk


and run:

image

Fine. The patch is fully functional!
At the moment, we performed all the actions in the console (for speed), but in reality everything is much simpler : just throw the game and the patch on the memory card of your device, for example, install the game, then patch and then play again. In this case, the Android system will warn you every time that the application will be reinstalled. Of course you agree.

This is how you can use the Android security policy imposed on application packages to create patches.

Here you can download the APK of the signed game.

And here is the patch (also signed)

Outro

In fact, such a breach is not only the fault of Android. I think that application developers who do not check data integrity, do not encrypt them and use them in their application with full power of attorney to the source are also to blame ...
Well, here we have considered another aspect of security that should be considered when developing our applications for the Android OS.

See you again!

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


All Articles