How to create patches based on trusting Android security policies

    Greetings, dear reader!


    Offtop

    In this topic, I would like to talk about how you can access what belongs to us, but indirectly.
    Any software is someone else's property: someone sat and moved their brains to produce another “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 do with any program code running on our device, everything that the soul desires ...

    Intro

    I think it’s worth starting from the very beginning. It all started with my discovery of the user dependency of others around the notorious game 2048 . Of course, I was wondering why this game so captivates people, so I decided to try it myself. After a few minutes, I understood its essence and I was also interested in this game.
    I must admit, I could not get more than 5000 points in any way, and until 2048 it was still too long ...
    Meanwhile, the surrounding "players" easily scored 10-16 thousand points, and some managed to even reach 2048 in a cage.
    Well, after some time I had the idea to stupidly hack this game so as not to “kill” the time for its passage (it is too long to complete, but no less addictive). And after a couple of days, my friends asked me about the same thing.
    It meant one thing: it’s time to get down to business ...





    What was in the beginning?

    This hacking did not start with something unusual. Everything was according to the template: I pulled out the application’s APK-package, performed the primary decompilation using apktool.jar , then the secondary one (to get Java code from Smali) using the jd-gui tool . Nothing unusual. Further, for a long, long time I surfed the 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 on the insides of this application, that is, where everything interesting happens:

    image

    But as you can see, few people would like to rummage through all this apparently obfuscated code: the original names of classes, methods and variables represent only a short set of letters. Research at this stage has come to a standstill ...
    I decided to give up 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. This could be data about everything, starting with the color of the cells in the field and ending with the current number of points. The application data structure is as follows:



    Now, in order:

    • The / cache folder is empty, and therefore is of little interest
    • In / files , at first glance, garbage is stored, but this is only at first glance
    • The / lib folder contains the Cocos2D graphics library, which, in general, is not interesting for us either.
    • Well, / shared_prefs contains the current values ​​of the SharedPreferences application


    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 you might guess, this file is responsible for saving the state of the game before exiting. The state preservation includes: a description of the cells of the playing field, the number of Undo and the current player rating.

    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><intname="Score"value="4846" />        // максимальный рейтинг
    <intname="ScoreSent"value="2410" /><intname="BestBoxValue"value="512" /><booleanname="FirstTime"value="true" /></map>


    2) File save.plist
    (each tag dictstores data about a specific cell at a certain 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"/><plistversion="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 rights on the ROOT device, you can easily adjust the game for yourself.
    But not everyone has access to root.
    That is why you should create a patch for this application, which would be accessible to a wide audience.

    Create patch

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

    You probably 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_CERTIFICATESsaying that the key with which the game was signed does not match the key with which our fake application was signed.

    So, we have only one thing left: to rebuild the game in the APK and sign it with our key using the JARSIGNER
    tool , and then our fake application with the same key! Plus, knowing that when reinstalling Android applications does not delete the game data (i.e. its sandbox), we can replace the game data with our data, relying on the good and trusting OS Android security policy, and then launch the original game, which would already used our fake data.
    But what if the game overwrites the data upon reinstallation? Well, now we’ll check it!

    Let's start by subscribing the game with our key (certificate). To do this, you need to disassemble it as a standard (decompile, if you like), and then assemble it again and after that sign it 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 application package signed by our default key.

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

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

    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 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 applications. Therefore, there are practically no comments on the code.

    So, we won’t do anything with the application manifest - the package name was already installed when creating a new project, and the application name, in general, is not necessary to change. The patch will not have any special functionality, so the specific ones user-permissionsare also useless to us.

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

    privatevoidPatch(){
            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() {
                @OverridepublicvoidonClick(DialogInterface dialog, int which){
                    dialog.cancel();
                    Main.this.finish();
                }
            });
            alert.show();
        }
    


    Set a new record:

    //1privatevoidSharedPrefsPatch(){
            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 apologize for the carelessness of the format below.):

    // 2privatevoidFilePatch(){
            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");
            }
    


    We compile. We sign (certify) with the same key as the game 2048.

    Check performance

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


    image

    and run:

    image

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

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

    image

    and use it:

    image

    patch:

    image

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


    and run:

    image

    Great. The patch is fully functional!
    At the moment, we carried out all the actions in the console (for speed), but in reality everything is much simpler : just drop the game and the patch on the memory card of your device, for example, install the game, then the patch and then the game again. In this case, the Android system will warn you every time that the application will be reinstalled. You, of course, 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 gap is not only Android's fault. I think it’s also the fault of application developers who do not check the integrity of the data, do not encrypt them and use them in their application with full power of attorney to the source ...
    Well, here we’ve examined another aspect of security that should be considered when developing our applications for Android OS

    See you soon!

    Also popular now: