📜 ⬆️ ⬇️

Hack mobile online game? Easy!.

Hi, Habr! Today I will tell you about what you may encounter if you suddenly decide to get into the jungle of someone else's application on Android (in this case, online games). Adventures with viewing Java classes in .dex, learning Dalvik op codes, and even binary programming. But first things first.

Under the cut traffic to ~ 800kb, 293 of which are screenshots of the code (!)

This article is written for informational purposes only. The author is not responsible for any actions of users who read the article. Any coincidences in the article are random.

Once, on a rainy summer evening, my girlfriend and I were looking for something to occupy. I didn’t want to watch the movie, but there was no desire to get out of bed. The choice fell on a mobile toy. There were not too many basic requirements for the game:
')


Thus, we found a game from the top of Google Play. In order not to disclose names, let's call it a game N. True, as it turned out later, the third item from the list above is implemented there a little more than nothing.

Below I have hidden a brief description of the game under the spoiler, it is not necessary to read it, but it will be useful to complete the picture.

Description
The game is based on monsters. You call them, shake them and their spells, put on them runes that put up different stats. Monsters, in addition to the usual levels (levels), there is also a gradation by the stars:

1 star - max level 15
2 star - max level 20
3 start - max level 25

and so on up to 6 stars and a maximum level of 40. Having reached the maximum level, you can raise the number of stars to the monster, while its level will drop to the first. This process in the game is called “Evolve”. To do this, you will have to "eat" other certain monsters, for example:

for evolv one monster 2s -> 3s, you need to eat 2 already existing monster 2s.
4s -> 5s - you need to swallow 4 monsters 4s
5s -> 6s - 5 monsters 5s - this is very labor-intensive by the standards of the game.

Monsters can be invoked in many ways, but ultimately it all boils down to three:

  1. knock out in the location (maximum 3s, the chance is quite small)
  2. 1-3 scrolls of the call (1s-3s, respectively. 95%, that from the call you get 1s or 2s. It is worthwhile to clarify that the monsters 1s-2s are slag, and in 99% of cases they are consumed, because stats lose a lot. Scrolls fall very often, you can get 20-30 pieces per day without much zadrotstvo);
  3. 3-5 scrolls of appeal (~ 90-95% that you get 3s, 4s rarely fall, 5s monsters never fell from these scrolls. Scrolls can be bought in unlimited quantities for red crystals. They fall very rarely)


Now about the game currency:

Energy - needed for hiking in PvE locations and dungeons. Consumption - from 3 to 8, depending on location. Accumulates one in 5 minutes, often falls directly from dead mobs. There is a ceiling of energy that increases with the level of the player (not to be confused with the level of monsters) and with the help of a special building.

Arena Energy - used for trips to the arena, "PvP". About why PvP in quotes, you will learn below. 1 trip to the arena spends 1 unit of energy arena, maximum - 10, accumulates one time in half an hour.

Blue crystals - the main currency of the game. It bought things from the store, most of the buildings. It accumulates in various buildings, falls from dead mobs, give as a reward for tasks.

Red crystals are a minor currency that can be bought for real money. They can be spent on those same scrolls 3-5, on renewing energy and the arena of energy, and buying blue crystals for them. Very rarely, they fall from dead mobs or in the arena. On the day they can be earned about 30-40, by the way, one roll of 3-5 costs 75.

Points of fame - the currency that is given for winning in the arena. For it is bought a lot of interesting buildings and objects. This currency can not be purchased for any other or for donat.

Donat in the game do not crush. All is well and calmly passed / bought without an infusion of money. Donat, in fact, gives you only more scrolls, the chances of getting useless monsters from them are the same. You cannot buy a specific monster for donate (and in general it is not possible at all).

The combat system resembles Final Fantasy 7-10 or, if you prefer, HoMM is a turn-based combat, with a choice of 2-4 spell. In the dungeons from 3 to 10 levels (most often 3 or 5), at each level there is a pack of mobs, you kill - pass on, do not kill - you get everything that you managed to earn (crystals, energy, experience).

About the "PvP" and "team play." As it turned out, you do not have the opportunity to play with a person or against a person. You play either on your own or on auto attack and always against the computer. Therefore, PvP is pretty boring here. It consists in the following: each player defuses 4 monsters and buys towers for protection. Getting into the arena, you fight AI, which is pretty dumb. In order to balance this, after a while, the very same towers begin to fire on you, each time more and more often.


I am a remote programmer, so I work at home. And at home there is always a good wi-fi, and I didn’t particularly think about how and when the game interacts with the server.

Until I decided to enter the game from the mobile Internet. Having run all the levels in the next dungeon, I received the message “Network connection is delayed. Resend Battle Results? (If battle results are not lost, count as loss.) ". After I clicked on the “Yes” button, the results still went to the server.

If you have read the description of the game, then much has fallen into place for you: why there is no normal PvP or two games - the game connects to the server rarely, most likely via HTTP, no sockets. And most importantly - apparently, in this game, the client calculates the results of the battle, and the server only receives them.

Immediately after experimenting with the disconnection of the Internet at various points in the game, I found out the following:



Idea 1. Forged request


The most logical idea that can come after the above.
In order to fake a request, we need the original. There are a lot of options for monitoring traffic, I chose the easiest one for me - to let traffic through the laptop and look at it all with the help of WireShark.

Instructions for turning your computer into an access point can be found here .
So that there is less excess "noise" in the logs, we close all applications and disable synchronization.
At boot time, the game loads information about promotions, partners, and friends from Facebook - in general, there is a lot of traffic and it goes to different servers, so we are not interested. Go to the location:

Request
POST /api/gateway.php HTTP/1.1 User-Agent: Dalvik/1.6.0 (***) Host: *** Connection: Keep-Alive Accept-Encoding: gzip Content-Type: application/x-www-form-urlencoded Content-Length: 556 hEuirYJfxnRGR7vITBb1x/3KpY+0DZmSWBLuxNnJn+Vqdb2eg7X+TKBKgcldJEY1A59yw8ocPnyD/A+Xec5uLtk9IEojAmPTwDiJDNu6BcRKXQpCGIildUlzpBxCSSp0PU8OgSaXNqlP+aGrXnHGoZXKVREMPRAXZPwlOGD6XIemvS5wkWrv+4GBPhO3v21CEfify4wO0x6GWUswX55TbAiLbT4Lr3C4TKXJyJQ8eujxC7Nd0OFEK8d92KbamPEFocxJotYTNuLa6qwVfdpgvaoDCCeKGXvu+z3ZZCjaruh2XMHtg6MRU0ywpB7d5qR9b4JOYRGwe2z9WHXKZesSwl7S7HsLgKtjLKz9oIRJAiTgRIWGn2WM9WaMtFSn9fXyEj31lS3JWtJ4hkgntVYh4qC6DvnTt0XJTZBXWm93Ki2FVASHDkr+tTPNZm69uBWXQ8/a768aoaTO+v+1o/ymQ8TQBDeMt79XZ8w3kGYrXC9PZh8IacSA3KPqzVQxlFeXnyzuTuqTzef9ZB9B2yc0VGD+xeIwgswiyNOjTBYqKmI= 


Answer
 HTTP/1.1 200 OK Server: nginx Date: Sat, 19 Jul 2014 15:04:16 GMT Content-Type: application/octet-stream; charset=utf-8 Content-Length: 1048 Connection: close X-Powered-By: PHP/5.4.11 Cache-Control: no-cache, must-revalidate Pragma: no-cache vyb3BQeGoRphqgM/QCIBv0m2Ms5lBf3iUEKauCdpgRCXWjyITAUa+t1w36VzBxlkZlYF+jH+Lmw+hmPbLtKj2pBidUrZ0CO44AsQ2erYG+m0n2WK8SY5m7Ioar1gNtim67rwpZQeGzP+dUvLE5T3Q/3iqua8dHNAhkrsrYyHYevbh535JODq0Qwd5y8ZQtsYQnl+Vtc2YeL6O3hvUc6UEKNHmc79saLz+PhH64nRA7xih+OLip3FGrgJdgDTTNBn2xBxyRmTeuoD1LqwKa/eWfDkwoA9IOMoc9NozoRyiUDRZjaya0VZH+k6DR4lRGMrDJtmBwjIWfAIbEg5K3xUEPtJIvU7QryaDTqZT+FOgQRr1sZ5MFzoDzJ+titAfbKNKZxF7QUDbAzi+o3j6pzZkioPbWMotsUWKUM/IOrfKcRGIV1yd0w8sGHhh9m7wDwbFA1/RCpifJBV528TZ9Ql3P3gQGvvqnGf5n88BpKXo3IF5+T3Fr9QXijR4kJ5H9MxltRCLB3DU41XZ9bQuP9o7IvWkt+TGvByAW4bI3JkLz855R7AzSoyrVfYPaguKH7yvJX6cQkm5GDqYRCwFA+mRQmHg6AEYbBS2M+DrZ1U/UvdB5bMY+sLDEPE6MLVeRadFjNzFlKkQxWUlGmzD06ia9kCOoyC9du4bCRTziH03qNK4m1tgwqw2jsUtu8I+HTK30YibHmrUWdqtfyIm0EqiXKI8ZsZYxsG1qeqYQNnQ9HIYxpYRXIETlLBl1Fs3bY1tXoUxetaNDVhxW/PMvFCLSJJCNJ6V8iJRieA0o23hqAKsJpDRANz8oEJ0vNKubXn7HeXq48UzkStqobE/UotzQ8ocdVBW7MgErF5DzjWLxgMjB3pnKiOFl8pqacD6qFWSywQtcF8xJ2RQMuEefusrrfFi67e5PLSdJW2utMIMAudJyllcOK4wNur6fo18J6zHyjzSklIuPhzPn2XQj+FcgUh1pU0wLKhuWq39PFCi4ekupSLt7j0VSZCoKDmKQO4q1b/SpeA5Bb7lW5TvgRSsg== 



At first glance it is clear that this is Base64. But by typing the text into the first decoder, I received complete nonsense, although I was expecting JSON (just kidding, it would be too naive - from the application 10kk + downloads).

We need to go deeper ©

Download apk games (many options, I took advantage of this ). The APK file is a regular ZIP archive, there is a lot of things in it, but first of all we are interested in the classes.dex file. This is the Dalvik executable format. Essentially - compiled Java classes. To open them, we need dex2jar and jd-gui . The first converts dex to jar, the second tries to restore the source code from jar.
What jd-gui will restore is pretty dreadful and read-only. You should not try to compile it. You can save and open source files from jd-gui in your favorite editor. I downloaded the 30-day IDEA trial from JetBrains, because I really love the way the search was done in their products (I use the purchased PyCharm and PHPStorm myself).

Warning for those who prefer this editor - do not set the SDK, you will fill up with errors.

From programming for android, I knew only the basics and had no particular idea where to start my searches. Therefore, I launched the search for “base64” on the project, and found a class that implements Base64 decode and encode. I was very surprised by this, because these methods were not just a wrapper for library methods, but, judging by the code, they actually implemented Base64 encoding and decoding.

The first thought that came to me was that the creators wrote something of their own, which is similar to Base64, but is encoded differently. Since the code looks awful (methods on thousands of lines, goto's, instructions in methods immediately after return and other joys of life), I could not rewrite it. Then I remembered that the server was written in PHP, and decided not to despair, because containing two of my own Base64 implementations on two very different platforms is quite expensive for development. A little later, I started Base64 to Java and realized that in standard Java libraries (version 6 and 7) there is no base64 encoding, which finally drove away my fears about an alternative implementation.

Looking for the use of this class, I went to another - StringEncrypter, which implemented several methods, but the main ones were decrypt and encrypt. Skimming the decrypt method, I realized that this was what I needed. The data was decrypted from base64, run through AES / CBC / PKCS7Padding and returned. It remained only to find the key that was used for Cipher and the initial vector (Initialization vector).

For this, I began to look for the use of these methods. And I found out that the StringEncrypter class is not used anywhere. This surprised me a lot, but I thought it was jd-gui flaws.

I started searching the project again, this time I immediately looked for Cipher. There were many results, and, wandering through them, I came across a file, the source code of which, apparently, could not be recovered. Instead of the code hung "INTERNAL ERROR". Having started the search for this “INTERNAL ERROR” project, I got 55 results. It became clear why I could not find the use of some classes. Among these files there was one with the interesting name ActiveUserNetwork.

We need to go deeper ©

I guessed that further - only the assembler. So it happened.

Dalvik VM has a lot of op codes , and, in fact, smali code is quite user-friendly to read, especially if you’ve been tinkering with assembly language.

This time we will need smali and baksmali . Backsmali converts the classes.dex file to the source folder, while preserving the hierarchy and folder and file names. At first, it will be much easier to understand the smali code by opening the same java file (if, of course, jd-gui could decompile it). There are enough resources on the Internet where you can find code examples, here , for example, it shows what arrays look like in smali and for / switch instructions.

But let's return to our game, and specifically to the ActiveUserNetwork file that interested me. Here everything was found - Cipher, methods encrypt and decrypt, Base64, (by the way, here it was used from the android.utils library) and even the constant “http: //***.com/gateway.php”. Yes, this is not /api/gateway.php, but at least something. By the way, the search for "api / gateway.php" did not give anything even by smali code, but I was not particularly upset because I saw that StringBuilder is often used.
There are no highlights of the smali code in Habré (frankly, there are few of them at all), so I’ll upload large chunks of this code with screenshots.

So, the decrypt method:



Explanation of the code: in the first line is the standard description of the method: what it takes and what it returns. The method takes 2 parameters - a string and a byte array (here it looks like [B). Returns a byte array.
The .locals directive indicates the number of registers that the method will use, not counting its parameters. In addition to this directive, there is a similar one, which is called .registers , it defines the number of registers that are used by the method, including method parameters. Those. In general, .registers = .locals + params. In this case, if you declare the number of registers through the directive .registers, the method parameters fall into the latest registers. Registers are accessed via v0, v1, v2, and so on, parameters are p0, p1, and so on.
This method is static and is called without an object, otherwise there would be 3 parameters, the first of which would be the object for which the method (this) is called. The following two directives may be missing, these are the names of the parameters. The .annotaions directive declares additional information about the method, in this case, the thrown exception. The .prologue directive says that the body of the method goes further.
Given all this, the first 11 lines are converted into one line of Java code:

 public static byte[] decrypt(String key, byte[] data) throws Exception { 


Looking into the table of op-codes and remembering that in invoke-virtual the first parameter always passes the object itself, unlike invoke-static, “literally” rewrites the method in Java:

 public static byte[] decrypt(String key, byte[] data) throws Exception { String v1 = "AES/CBC/PKCS7Padding"; Cipher cipher; cipher = Cipher.getInstance(v1); int v1_1 = 2; SecretKeySpec v2 = createSecretKey(key); AlgorithmParameterSpec v3 = spec; cipher.init(v1_1, v2, v3); byte[] v1_2 = cipher.doFinal(data); return v1_2; } 


Here spec is a static variable of the ActiveUserNetwork class, it is initialized in the class constructor

 .line 78 new-instance v0, Ljavax/crypto/spec/IvParameterSpec; const/16 v1, 0x10 new-array v1, v1, [B invoke-direct {v0, v1}, Ljavax/crypto/spec/IvParameterSpec;-><init>([B)V sput-object v0, Lcom/com2us/module/activeuser/ActiveUserNetwork;->spec:Ljava/security/spec/AlgorithmParameterSpec; 


I replaced this constructor code with the getSpec method. We decrypt method to a normal form:

 public static byte[] decrypt(String key, byte[] data) throws Exception { String alg = "AES/CBC/PKCS7Padding"; Cipher cipher = Cipher.getInstance(alg); SecretKeySpec secretKeySpec = createSecretKey(key); cipher.init(2, secretKeySpec, getSpec()); return cipher.doFinal(data); } 


So, we need to deal with the createSecretKey and getSpec methods.

 public static AlgorithmParameterSpec getSpec() { byte[] v1 = new byte[16]; return new IvParameterSpec(v1); } 


This is the converted code from the constructor. It was already at night, and the number 0x10 my brain translated into the decimal system as "10." It's good that I decided to double-check in the calculator, otherwise I would wait for disappointment :)

Method createSecretKey (here, by the way, a typo in the name, we will fix it too)



The method is quite simple, converted to

 public static SecretKeySpec createSecretKey(String key) { return new SecretKeySpec(key.getBytes(), "AES"); } 


Great, it remains only to find out what is transmitted to the method with a key and data.
The processNetworkTask method is responsible for processNetworkTask ; it simultaneously sends the request (with encrypt and Base64 encode) and receives the answer. The method is voluminous (1k lines), so I will lay out only the assembly of the pieces of interest to us ( v18 is an object of the org.apache.http.HttpResponse class)



In short:
Take the header value REQ-TIMESTAMP , call the createHash("MD5", header_value) method createHash("MD5", header_value) . From the returned string, we get the substring from the first character to the sixteenth, and this substring is passed to the decrypt method with the key. The data transfers the byte array from Base64.decode() .

So, we have everything in our hands, except for the createHash method.
smali code:



This method is already heavier for perception: there is a cycle, exceptions, and conditions. But to make mistakes in cryptographic methods is impossible. This construction alone is worth:

 const/4 v7, 0x1 new-array v7, v7, [Ljava/lang/Object; const/4 v8, 0x0 aget-byte v9, v3, v1 invoke-static {v9}, Ljava/lang/Byte;->valueOf(B)Ljava/lang/Byte; move-result-object v9 aput-object v9, v7, v8 


And it turns into a banal Byte v9 = mdByte[i];
As a hint: almost always when you see an increment ( add-int/lit8 v1, v1, 0x1 ) before goto is a for loop. Final Java code:

 public static String createHash(String algorithm, byte[] data) { try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(data); byte[] mdByte = md.digest(); String mdString = ""; int i = 0; int len = mdByte.length; for (i = 0; i < len; i++) { StringBuilder v5 = new StringBuilder(mdString); String v6 = "%02x"; Byte v9 = mdByte[i]; v6 = String.format(v6, v9); v5.append(v6); mdString = v5.toString(); } return mdString; } catch (NoSuchAlgorithmException e) { return ""; } } 


Putting it all together. I created a new application and threw everything in MainActivity:

 public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String b64 = ""; // Base64 request body String req_ts = ""; // REQ-TIMESTAMP header byte[] decodeBase64Byte = Base64.decode(b64, 4); String hash = createHash("MD5", req_ts.getBytes()); hash = hash.substring(0, 16); try { Log.d("MYAPP", "Decrypted: " + new String(decrypt(hash, decodeBase64Byte))); } catch (Exception e) { Log.d("MYAPP", e.getClass() + "-" + e.getMessage()); } } 


Now back to why we all do it. The attentive reader might have noticed that there was no header REQ-TIMESTAMP in either the answer or the query that were mentioned above. However, there the request goes to / gateway, not to / api / gateway. Requests to / gateway go during application initialization. There are only two of them, deciphering them, I got ... nothing. No, there was data about the device, the MAC address and even whether the tablet was ruled. But I did not get anything worthwhile. Requests to / api / gateway came from somewhere else and were in no way connected to / gateway.

While I was dealing with smali code, I made several more attempts in different directions, before I came to the decision to deal with all the methods and rewrite them in Java.

Attempt 1: do not look for how the key is generated, but simply make a request with it to your server. Smali code can be changed and compiled back, so the idea was simple - before decrypt we make a request to your server, passing the key as a GET parameter, and then we look at the logs of the web server.
Looking for how to compile smali code I found apktool . This tool is able to parse the apk file right away on the smali code, and also collect it all back into apk.

 ./apktool decode ~/Downloads/***.apk ~/Documents/out/ ./apktool build ~/Documents/out/ ~/Downloads/***.new.apk 


But when you try to install a new application, you get an error:

 ./adb install -r ~/Downloads/***.new.apk Failure [INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATE] 


On stackoverflow, it is advised to remove the application manually, and then install it again, but that did not save me. Making a new key, for this we need the keytool and jarsigner (included in the openjdk package)

 keytool -genkey -keystore ~/debug.keystore -validity 10000 -alias debug jarsigner -keystore ~/debug.keystore -verbose ~/Downloads/***.new.apk debug 


Important note - jarsigner behaves differently in jdk version 6 and 7, and on version 7 the command will swear at alias. I did not find a solution to this problem and installed an additional version 6 for myself.

After that, the installation of the application will pass. But, unfortunately, the application (even if it was not modified, but simply decompiled, compiled, sub-set, installed) immediately crashed. I guess that the server somehow checks the signatures, but I will be glad if someone specifies in the comments. This idea had to be left.

Attempt 2: if there is no desire to poke around in smali code (and I didn’t have it, I thought it was a problem for ~ 5 hours), then you can do it easier. In your application, create an empty method with the same interface as the one you want to copy, create an apk, decompile, copy the body of the method, collect it back. Each such iteration takes a lot of time. Therefore, it will be faster to learn op codes. It is clear that you will not be able to open the source code of the assembled application.

This article was not written in one day, and I write this remark a week after the paragraph above. The way of describing the interface and copying smali code into the method body helped me a lot when I could not recover the source code of the key generation method. To simplify your life and reduce the time of iteration, you can combine everything into one team

 apktool build ~/myapp/ ~/myapp.apk && jarsigner -keystore ~/debug.keystore -verbose ~/myapp.apk debug && adb install -r ~/myapp.apk && adb shell am start -n "com.example.myapp/com.example.myapp.MyActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER && adb shell logcat MYAPP:D *:S 


Idea 2. We make a supermonstra.


Well, if it does not work through the request - let's go the other way.
New monsters are loaded into the game not through an update in Google Play, but through an intragame update. It means that they are stored somewhere outside apk, and theoretically we can modify them.
It was not necessary to search for a long folder - /sdcard/Android/data/com.***/files/patch/
There were all the sprites and sounds, and most importantly - a lot of files with the names of monsters and the extension. Dat. We open it with a hex editor and skim through it - no headers or lines that the eye would catch. I took the first monster of the first level, looked at his HP and began to search / replace these bytes in the hope that the file is not encrypted. Found 7 matches. By successively replacing them, I received 4 crash games and 3 "nothing has changed." Sorry, encrypted.
But something has to decipher it! We are looking for the "\ .dat \ b" in the regexp code (to exclude in the results methods starting with "data"). Only the CommonData.dat file is located. This file is hidden in the /data/data/com.***/ folder (if you have no data in the / data folder, you need root access).
The file is encrypted and has a size of 1kb. It is clear that there is nothing standing there, however, the encryption algorithm may be the same. This time I will not upload the code, it takes about a thousand lines. Most importantly, a string based on ANDROID_ID is passed to the decrypt key. After opening the file, the MAC address was found (again). Having experienced the same algorithm on monster files, I received an error.
Sadness longing.

Hi, assembler!


However, in the same folder / data / data / com. * * * / Lib / I came across the .so library. I already saw them in apk and saw them connected in the MainActivity (honestly, all this time I really hoped that I wouldn't have to pick them). There were two libraries - libgame.so, libcom ***. So. The second one weighed very little and did not carry any value for me. I opened the first with a hex editor and in half a minute I found the line “http: //***.com/gateway/api.php”.

We need to go deeper ©

I hope you are not tired yet :) Because we start practically from the very beginning.
Frankly, at this stage I spent about 20-30 hours of my time. If you are not familiar with processor op-codes, registers and memory, you will also be stuck here for a long time. I was saved only by my persistence and desire to prove that a person is steeper than some kind of application.
A lot of time was spent on the selection of tools and techniques. And if the remaining article will save someone a few hours in the future - it will be great.

Tools.
Ida Pro 6.1+ - the main tool for debugging. Since version 6.1 it comes with the android_server file and the ability to remotely debug Android applications.
gdbserver is another tool for remote debugging.
At this stage, root access to the device is required.

We upload both servers to the device:

 adb push gdbserver /data/local/tmp adb push android_server /data/local/tmp 


We configure port forwarding on localhost:

 adb forward tcp:5039 tcp:5039 adb forward tcp:23945 tcp:23945 


We set the necessary rights:

 adb shell su chmod 755 /data/local/tmp/gdbserver chmod 755 /data/local/tmp/android_server 


The basic idea (thanks to this topic , it saved me a ton of time):
  1. connect through android_server, find the library, remember the offset;
  2. load the library in Ida with the specified offset;
  3. Ida analyzes, sets function names, call schedules and even tries to reproduce source code;
  4. run the application on the device;
  5. already analyzed library we load in application through gdbserver
  6. set breakpoints, analyze. If the application crashes, goto 4 .


Why are two servers used for remote debugging? android_server can "beautifully" show loaded libraries and the offset for the desired library is in it very quickly. But breakpoint does not work in it. But they work fine in gdb. It would seem possible to look for info sharedlibrary through the gdb client using info sharedlibrary , but this did not work for me.

The location of the desired settings in Ida
You can load a file with offset through File > Open > > Manual load . In the same window it is necessary to indicate the type of processor.
Selecting a remote debugger: Debugger > Switch debugger .
Connection setup for debugger: Debugger > Process options . Here we set localhost and the port of the server that is being used.


Another important point:
If the application crashes, you (with some probability) will have to do all the steps from the first. It's all about ASLR technology. To disable it, run in the shell:

 echo 0 > /proc/sys/kernel/randomize_va_space 

Attention! This greatly affects the security of your device. Remember to remember the value of this parameter and return it to the place after your experiments.

So, a more detailed action plan:
  1. Run android_server:

     adb shell su /data/local/tmp/andoid_server 

  2. Open Ida without downloading anything, go to Debugger > Attach to > Remote ARM/Android Debugger .
  3. Select the desired application from the list.
  4. We are looking for our library (there are many options, but the quickest thing I could do was find it with my eyes - by scrolling and browsing. Search is slow, jump to the label does not always work.
  5. Remember the library offset ( 5D699000 in my case).
  6. Disconnect from the process ( Debugger > Detach from process ). The process on the device remains alive.
  7. Open the file, set the desired offset ( 0:5D699000 in my case).
  8. While Ida is analyzing the file, kill android_server and prepare gdbserver:

     adb shell su /data/local/tmp/gdbserver --attach :5039 1234 

    (The --attach option tells the server to join the already running process: 5039 is the port number, 1234 is the pid of the process that can be seen with the usual ps in the shell).
  9. Changing Ida settings to work with gdbserver.
  10. Connect to the process.


Now, if everything is done correctly, the library code, which Ida analyzed, will go to the right place.

Having studied the list of functions from the library, I found a very entertaining group that was responsible for interacting with the server. Here are some of the features:

All these functions formed JSON, filled it, and called the sub_5D839994 function. This feature is the cornerstone of communicating with the server. It encrypts strings, packs it in base64 and sends data.
It was hard to mess with cryptography even in more or less understandable smali code. Immediately it was hell. Although I found the key that is used for encryption, I still get lost in the search for IV.
But since we already hang on the application debugger, it is enough for us to intercept the line before encryption, change it and continue the application. And the server will leave a modified string.
, ( sub_5D839994 ) ( ), , , , .
, . 2 : sub_5D839994 , , , – AESConvertEncode , .

, , . , – . , AESConvertEncode , … ! . R0:

 { "command": "BattleArenaResult", "id": 1234567, "session_key": "***", "win": 2, "unit_status": [ {"unit_id": 1,"result": 2}, {"unit_id": 2,"result": 1}, {"unit_id": 3,"result": 1}, {"unit_id": 4,"result": 1} ], "unit_list": [ {"unit_id": 123456781, "pos_id": 1}, {"unit_id": 123456782, "pos_id": 2}, {"unit_id": 123456783, "pos_id": 3}, {"unit_id": 123456784, "pos_id": 4} ], "position": {"island_id": 1, "pos_x": 14, "pos_y": 22} } 


, 99.(9)%, – , , .
, , – , unit_status . , , id-result (2 – , 1 – ). , . , , , , – . , :
, . , , . : , , ? , «» , «» , , - .

, , , . , – . , , , – (, : , , , + . ). , / . , .

, , , – , . . , . , – . . :

Stage 1 – 3 ( , )
Stage 2 – 4 ( )
Stage 3 – 3 ( , )
, JSON , :

 "unit_status": [ // Stage 1 {"unit_id": 1,"result": 2}, {"unit_id": 2,"result": 2}, {"unit_id": 3,"result": 2}, // Stage 2 {"unit_id": 4,"result": 2}, {"unit_id": 5,"result": 1}, {"unit_id": 6,"result": 1}, {"unit_id": 7,"result": 1} ] 


, - , , . «win» «dead», . , , . . :)
, , 100%.

. , - , . , , . This is not very convenient. , : «win» , «result» .

, «win» JSON. , .

 MOVS R0, R6 BLX __floatsidf BL cJSON_CreateNumber LDR R1, =(unk_5D8C3240 – 0x5D84666E) ; "win" MOVS R2, R0 LDR R0, [SP,#0x30+var_2C] ADD R1, PC BL cJSON_AddItemToObject 


, R6 , – . .
, , . , , R6 2. , 2 (0x2) «2» (0x32 ASCII).
– . , Ida ASM , .

? !

, - , . .
hex-, .
MOVS R0, R6 ; 321
, 1C32 . :
0001 1100 0011 0000
, . ARM ( ), Thumb Thumb-2.
, MOVS , . , ADDS R0, R6, #0 . Ida , - .

 0001110 000 110 000 ADDS Imm Rn Rd 


ADDS –
Imm – immediate value. , .
Rn – , .
Rd – destination register. , .

, . - , , , :
SUBS R0, R6, #1
SUBS R6 R0 . , R6 , R0 .
:

 0001111 001 110 000 SUBS Imm Rn Rd 


– 1E70 . – 701E . .
:
MOVS R0, R6
:
SUBS R0, R6, #1

AESConvertEncode , .

, .
, .

 ADD R8, PC ; "unit_id" MOV R9, R3 ADD R9, PC ; "result" loc_5D8466E2 BL cJSON_CreateObject MOVS R4, R0 LDMIA R7!, {R0,R1} BLX __floatundidf BL cJSON_CreateNumber MOV R1, R8 MOVS R2, R0 MOVS R0, R4 BL cJSON_AddItemToObject LDMIA R6!, {R0} BLX __floatsidf BL cJSON_CreateNumber MOV R1, R9 MOVS R2, R0 MOVS R0, R4 BL cJSON_AddItemToObject ADDS R5, #1 MOV R0, R10 MOVS R1, R4 BL cJSON_AddItemToArray CMP R5, R11 BNE loc_5D8466E2 


for.

R11 , :

 ADDS R5, #1 ;   CMP R5, R11 ;      BNE loc_5D8466E2 ;    -    



– R6 . LDMIA R6 , R6 R0 . , R0 – MOVS R0, #2

 00100 000 00000010 MOVS Rd Imm 


Hex – 2002 . ( 0220 ) .
: , …









Profit! , . — . . , , .

Instead of conclusion


- , , . , . , . : shared object. — ? C/C++ , . " sub_xxxxxxxx ", , , . .

, .
.
— .

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


All Articles