How peaceful reverse engineering helped slightly improve the Yandex.Money application

    There is a stereotype that reverse engineering is an activity for evil hackers in sunglasses and shiny leather coats. Under cover of night, in between the running around the walls and hand-to-hand fights with crowds of special forces, these computer villains make terrible hacks of programs, pentagons and other databases. Hacks themselves usually do not require any preliminary preparation and take a matter of seconds. And of course in the process of hacking almost any hour about molecular screens hell hacker notebooks incomprehensible OS crawling green krakozyably and / or spinning some 3D-figovina ...



    Today I want to move away from the well-worn Hollywood cliches about evil computer crackers and tell you, dear readers, how peaceful reverse engineering helped slightly improve the Yandex.Money application. I hope this story shakes a stable stereotype that reverse engineering is necessarily bad and only bad people need it.

    A little less than a month ago, I slightly reversed Yandex.Money version 1.71 for Android (the latest version at that time). Among other interesting things, I found there a mysterious method ru.yandex.core.CrashHandler.sendBug(String paramString):

    Smali method code sendBug (String paramString) (quite voluminous, I must say)
    .class public abstract Lru/yandex/core/CrashHandler;
    .super Landroid/app/Activity;
    .source "CrashHandler.java"
    # ...
    # неинтересный кода -  пропущено
    # ...
    .method sendBug(Ljava/lang/String;)V
        .locals 5
        .parameter "p1"
        .prologue
        .line 76
        new-instance v0, Lorg/json/JSONObject;
        .line 79
        .local v0, v0:Ljava/lang/Object;
        invoke-direct {v0}, Lorg/json/JSONObject;->()V
        .line 84
        .local v0, v0:Ljava/lang/Object;
        :try_start_5
        const-string v1, "model"
        .line 87
        .local v1, v1:Ljava/lang/Object;
        sget-object v2, Landroid/os/Build;->MODEL:Ljava/lang/String;
        .line 90
        .local v2, v2:Ljava/lang/Object;
        invoke-virtual {v0, v1, v2}, Lorg/json/JSONObject;->
            put(Ljava/lang/String;Ljava/lang/Object;)Lorg/json/JSONObject;
        .line 93
        const-string v1, "systemVersion"
        .line 95
        sget-object v2, Landroid/os/Build$VERSION;->RELEASE:Ljava/lang/String;
        .line 97
        invoke-virtual {v0, v1, v2}, Lorg/json/JSONObject;->
            put(Ljava/lang/String;Ljava/lang/Object;)Lorg/json/JSONObject;
        .line 100
        const-string v1, "component"
        .line 102
        const-string v2, "Android"
        .line 104
        invoke-virtual {v0, v1, v2}, Lorg/json/JSONObject;->
            put(Ljava/lang/String;Ljava/lang/Object;)Lorg/json/JSONObject;
        .line 107
        const-string v1, "appVersion"
        .line 109
        invoke-static {}, Lru/yandex/core/CoreApplication;->getAppBuildIdFromNative()Ljava/lang/String;
        .line 111
        move-result-object v2
        .line 113
        invoke-virtual {v0, v1, v2}, Lorg/json/JSONObject;->
            put(Ljava/lang/String;Ljava/lang/Object;)Lorg/json/JSONObject;
        .line 116
        const-string v1, "appName"
        .line 118
        invoke-static {}, Lru/yandex/core/CoreApplication;->getAppNameFromNative()Ljava/lang/String;
        .line 120
        move-result-object v2
        .line 122
        invoke-virtual {v0, v1, v2}, Lorg/json/JSONObject;->
            put(Ljava/lang/String;Ljava/lang/Object;)Lorg/json/JSONObject;
        .line 125
        const-string v1, "summary"
        .line 127
        const-string v2, "Android Native Crash"
        .line 129
        invoke-virtual {v0, v1, v2}, Lorg/json/JSONObject;->
            put(Ljava/lang/String;Ljava/lang/Object;)Lorg/json/JSONObject;
        :try_end_33
        .catch Lorg/json/JSONException; {:try_start_5 .. :try_end_33} :catch_80
        .line 137
        .end local v2           #v2:Ljava/lang/Object;
        :goto_33
        :try_start_33
        new-instance v1, Lru/yandex/core/ClientHttpRequest;
        .line 140
        .local v1, v1:Ljava/lang/Object;
        new-instance v2, Ljava/net/URL;
        .line 143
        .local v2, v2:Ljava/lang/Object;
        new-instance v3, Ljava/lang/StringBuilder;
        .line 146
        .local v3, v3:Ljava/lang/Object;
        const-string v4, "http://dmitriyap.dyndns.org:9091/rest/jconnect/latest/issue/create?project="
        .line 149
        .local v4, v4:Ljava/lang/Object;
        invoke-direct {v3, v4}, Ljava/lang/StringBuilder;->(Ljava/lang/String;)V
        .line 152
        .local v3, v3:Ljava/lang/Object;
        invoke-virtual {p0}, Lru/yandex/core/CrashHandler;->getJiraProjectName()Ljava/lang/String;
        .line 154
        move-result-object v4
        .line 156
        invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->
            append(Ljava/lang/String;)Ljava/lang/StringBuilder;
        .line 158
        move-result-object v3
        .line 160
        invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
        .line 162
        move-result-object v3
        .line 164
        invoke-direct {v2, v3}, Ljava/net/URL;->(Ljava/lang/String;)V
        .line 167
        .local v2, v2:Ljava/lang/Object;
        invoke-direct {v1, v2}, Lru/yandex/core/ClientHttpRequest;->(Ljava/net/URL;)V
        .line 171
        .local v1, v1:Ljava/lang/Object;
        const-string v2, "issue"
        .line 173
        const-string v3, "issue.json"
        .line 175
        new-instance v4, Ljava/io/ByteArrayInputStream;
        .line 178
        .local v4, v4:Ljava/lang/Object;
        invoke-virtual {v0}, Lorg/json/JSONObject;->toString()Ljava/lang/String;
        .line 180
        move-result-object v0
        .line 182
        invoke-virtual {v0}, Ljava/lang/String;->getBytes()[B
        .line 184
        move-result-object v0
        .line 186
        invoke-direct {v4, v0}, Ljava/io/ByteArrayInputStream;->([B)V
        .line 189
        .local v4, v4:Ljava/lang/Object;
        const-string v0, "application/json"
        .line 191
        invoke-virtual {v1, v2, v3, v4, v0}, Lru/yandex/core/ClientHttpRequest;->
                 setParameter(Ljava/lang/String;Ljava/lang/String;Ljava/io/InputStream;Ljava/lang/String;)V
        .line 194
        const-string v0, "crash"
        .line 196
        const-string v2, "log.txt"
        .line 198
        new-instance v3, Ljava/io/ByteArrayInputStream;
        .line 201
        .local v3, v3:Ljava/lang/Object;
        invoke-virtual {p1}, Ljava/lang/String;->toString()Ljava/lang/String;
        .line 203
        move-result-object v4
        .line 205
        invoke-virtual {v4}, Ljava/lang/String;->getBytes()[B
        .line 207
        move-result-object v4
        .line 209
        invoke-direct {v3, v4}, Ljava/io/ByteArrayInputStream;->([B)V
        .line 212
        .local v3, v3:Ljava/lang/Object;
        invoke-virtual {v1, v0, v2, v3}, Lru/yandex/core/ClientHttpRequest;->
            setParameter(Ljava/lang/String;Ljava/lang/String;Ljava/io/InputStream;)V
        .line 215
        invoke-virtual {v1}, Lru/yandex/core/ClientHttpRequest;->post()Ljava/io/InputStream;
        :try_end_7d
        .catch Ljava/io/IOException; {:try_start_33 .. :try_end_7d} :catch_7e
        .line 222
        .end local v1           #v1:Ljava/lang/Object;
        .end local v2           #v2:Ljava/lang/Object;
        .end local v3           #v3:Ljava/lang/Object;
        .end local v4           #v4:Ljava/lang/Object;
        :goto_7d
        return-void
        .line 226
        :catch_7e
        move-exception v0
        .line 228
        goto :goto_7d
        .line 232
        :catch_80
        move-exception v1
        .line 235
        .local v1, v1:Ljava/lang/Object;
        goto :goto_33
    .end method
    

    Here is the same method sendBug(String paramString)in a Java-like pseudo-code that, after certain manipulations with the dex file, is obtained using the Java Decompiller :

    Same method in Java-like pseudo-code
    package ru.yandex.core;
    # ...
    # импорт - не важно, пропущено
    # ...
    public abstract class CrashHandler extends Activity {
    # ...
    #  неинтересный код -  пропущено
    # ...
      void sendBug(String paramString) {
        JSONObject localJSONObject = new JSONObject();
        try {
          localJSONObject.put("model", Build.MODEL);
          localJSONObject.put("systemVersion", Build.VERSION.RELEASE);
          localJSONObject.put("component", "Android");
          localJSONObject.put("appVersion", CoreApplication.getAppBuildIdFromNative());
          localJSONObject.put("appName", CoreApplication.getAppNameFromNative());
          localJSONObject.put("summary", "Android Native Crash");
          try {
             ClientHttpRequest localClientHttpRequest =
                new ClientHttpRequest(
                    new URL("http://dmitriyap.dyndns.org:9091/rest/jconnect/latest/issue/create?project=" +
                    getJiraProjectName()));
            localClientHttpRequest.setParameter("issue", "issue.json", 
                    new ByteArrayInputStream(localJSONObject.toString().getBytes()), "application/json");
            localClientHttpRequest.setParameter("crash", "log.txt", 
                    new ByteArrayInputStream(paramString.toString().getBytes()));
            localClientHttpRequest.post();
            return;
          }
          catch (IOException localIOException) {
            // Тут Java Decompiller сгенерировал бред - пропущено
            // ...
          }
        }
        catch (JSONException localJSONException) {
          // Тут тоже... вообще с исключениями Java Decompiller не дружит, увы
          // ...
        }
      }
    }
    

    This pseudo code is certainly not entirely valid from the point of view of the syntax of the Java language, but it clearly demonstrates the logic of the method sendBug(String paramString). When this method is called, a bunch of various information is sent to a certain address dmitriyap.dyndns.org:9091using ClientHttpRequest.post()without any encryption. In particular, in the parameter crashthe argument passed to the method is sent paramString. Judging by the query line and the names of the variables, Atlassian Jira was raised “on the other side” , in which the method sendBug(String paramString)creates an issue immediately adding all the information sent to it. Those. in fact, the method sendBug(String paramString)does exactly what its name implies - it sends bug reports to developers in bugtracker. It seems to be okay, many programs do this. However, the code of the method itself raises questions:

    1. Who owns the domain dmitriyap.dyndns.org? This is clearly not the corporate domain of Yandex.
    2. What kind of information is passed to the method sendBug(String paramString)in the argument paramStringand therefore sent to dmitriyap.dyndns.org?
    3. Under what conditions sendBug(String paramString)does the Yandex.Money program call a method ?

    The answer to the first question is fast enough. Small Google search provides that dmitriyap - an online nickname head of Mobile Services Development Department at Yandex. Most likely, he dmitriyap.dyndns.orgregistered the domain . The fact that the data is not encrypted in any way and sent to the dyndns.org subdomain, and not to any Yandex domain, suggests that this entire bug report system was made by the developers of the Yandex.Money Android application hastily, “on the knee ". It was probably used during the development process and should not have been released. But, probably by oversight, I got it.

    What about the first question is more or less clear. We turn to the second question: what kind of information is passed to the method sendBug(String paramString)in the argument paramStringand then sent todmitriyap.dyndns.org? To do this, we first look at the method code of doInBackground(...)an anonymous inner class CrashHandler$1:

    Smali method code doInBackground (...)
    .field log:Ljava/lang/String;
    .method protected varargs doInBackground([Ljava/lang/Void;)Ljava/lang/Void;
        .locals 5
        .parameter "p1"
        .prologue
        .line 59
        const/4 v4, 0x1
        .line 64
        .local v4, v4:I
        :try_start_1
        invoke-static {}, Ljava/lang/Runtime;->getRuntime()Ljava/lang/Runtime;
        .line 66
        move-result-object v0
        .line 69
        .local v0, v0:Ljava/lang/Object;
        const/4 v1, 0x4
        .line 72
        .local v1, v1:B
        new-array v1, v1, [Ljava/lang/String;
        .line 75
        .local v1, v1:Ljava/lang/Object;
        const/4 v2, 0x0
        .line 78
        .local v2, v2:Ljava/lang/Object;
        const-string v3, "logcat"
        .line 81
        .local v3, v3:Ljava/lang/Object;
        aput-object v3, v1, v2
        .line 83
        const/4 v2, 0x1
        .line 86
        .local v2, v2:I
        const-string v3, "-d"
        .line 88
        aput-object v3, v1, v2
        .line 90
        const/4 v2, 0x2
        .line 93
        .local v2, v2:B
        const-string v3, "-v"
        .line 95
        aput-object v3, v1, v2
        .line 97
        const/4 v2, 0x3
        .line 99
        const-string v3, "threadtime"
        .line 101
        aput-object v3, v1, v2
        .line 103
        invoke-virtual {v0, v1}, Ljava/lang/Runtime;->exec([Ljava/lang/String;)Ljava/lang/Process;
        .line 105
        move-result-object v0
        .line 107
        iput-object v0, p0, Lru/yandex/core/CrashHandler$1;->process:Ljava/lang/Process;
        .line 110
        iget-object v0, p0, Lru/yandex/core/CrashHandler$1;->process:Ljava/lang/Process;
        .line 112
        invoke-virtual {v0}, Ljava/lang/Process;->getInputStream()Ljava/io/InputStream;
        .line 114
        move-result-object v0
        .line 116
        invoke-virtual {p0, v0}, Lru/yandex/core/CrashHandler$1;->
            readAllOf(Ljava/io/InputStream;)Ljava/lang/String;
        .line 118
        move-result-object v0
        .line 120
        iput-object v0, p0, Lru/yandex/core/CrashHandler$1;->log:Ljava/lang/String;
        :try_end_2e
        .catch Ljava/io/IOException; {:try_start_1 .. :try_end_2e} :catch_30
        .line 127
        .end local v2           #v2:B
        .end local v3           #v3:Ljava/lang/Object;
        :goto_2e
        const/4 v0, 0x0
        .line 130
        .local v0, v0:Ljava/lang/Object;
        return-object v0
        .line 135
        .end local v0           #v0:Ljava/lang/Object;
        .end local v1           #v1:Ljava/lang/Object;
        :catch_30
        move-exception v0
        .line 139
        .local v0, v0:Ljava/lang/Object;
        iget-object v1, p0, Lru/yandex/core/CrashHandler$1;->
            this$0:Lru/yandex/core/CrashHandler;
        .line 142
        .local v1, v1:Ljava/lang/Object;
        invoke-virtual {v0}, Ljava/io/IOException;->toString()Ljava/lang/String;
        .line 144
        move-result-object v0
        .line 146
        invoke-static {v1, v0, v4}, Landroid/widget/Toast;->
           makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
        .line 148
        move-result-object v0
        .line 150
        invoke-virtual {v0}, Landroid/widget/Toast;->show()V
        .line 152
        goto :goto_2e
    .end method
    

    Corresponding Java-like pseudo-code obtained using Java Decompiller:

    Same method in Java-like pseudo-code
    String log;
    protected Void doInBackground(Void[] paramArrayOfVoid) {
        try {
          Runtime localRuntime = Runtime.getRuntime();
          String[] arrayOfString = new String[4];
          arrayOfString[0] = "logcat";
          arrayOfString[1] = "-d";
          arrayOfString[2] = "-v";
          arrayOfString[3] = "threadtime";
          this.process = localRuntime.exec(arrayOfString);
          this.log = readAllOf(this.process.getInputStream());
          return null;
        }
        catch (IOException localIOException) {
         // Тут Java Decompiller выдал совсем полный бред, я эту пургу исключил что бы не смущать читателей
         // ...
        }
      }
    

    This pseudo code is again not entirely valid from the point of view of the syntax of the Java language, but it is clear from it what it does doInBackground(...). It launches the command line on the Adnroid device
    logcat -d -v threadtime
    then, using a method readAllOf(...)(defined in the same class), it captures the output and puts it in a logtype field as a string String. What's on this line? And in it, among other things, a lot of personal user data - payment history, some private cookies, etc. Here is a small piece for an example (the data is mine and they are covered up of course):



    Where does the usual logcat log come from with so much personal data? The thing is that the Yandex.Money application code is just studded with calls android.util.Log.d(...). In the course of the application, a bunch of all kinds of information is written to the log - including the user's personal information. What for? I don’t know, I don’t know ... It was probably used to debug the application during the development process, and then these calls were simply forgotten to be removed from the release.

    But back to the second question. Where does this line with a bunch of personal data from the field loggo after the call doInBackground(...)? You won’t believe it, but it is just passed to the method sendBug(String paramString)in the argument paramStringand then sent to dmitriyap.dyndns.org. Unencrypted. To make sure of this, just look at the method code of onPostExecute(...)the same anonymous inner class CrashHandler$1:

    Smali method code onPostExecute (...)
    .method protected onPostExecute(Ljava/lang/Void;)V
        .locals 2
        .parameter "p1"
        .prologue
        .line 188
        iget-object v0, p0, Lru/yandex/core/CrashHandler$1;->this$0:Lru/yandex/core/CrashHandler;
        .line 191
        .local v0, v0:Ljava/lang/Object;
        iget-object v1, p0, Lru/yandex/core/CrashHandler$1;->log:Ljava/lang/String;
        .line 194
        .local v1, v1:Ljava/lang/Object;
        invoke-virtual {v0, v1}, Lru/yandex/core/CrashHandler;->sendBug(Ljava/lang/String;)V
        .line 197
        iget-object v0, p0, Lru/yandex/core/CrashHandler$1;->val$progress:Landroid/app/ProgressDialog;
        .line 199
        invoke-virtual {v0}, Landroid/app/ProgressDialog;->dismiss()V
        .line 202
        iget-object v0, p0, Lru/yandex/core/CrashHandler$1;->this$0:Lru/yandex/core/CrashHandler;
        .line 204
        invoke-virtual {v0}, Lru/yandex/core/CrashHandler;->finish()V
        .line 207
        const/4 v0, 0x0
        .line 210
        .local v0, v0:Ljava/lang/Object;
        invoke-static {v0}, Ljava/lang/System;->exit(I)V
        .line 213
        return-void
    .end method
    

    Corresponding Java-like pseudo-code obtained using Java Decompiller:

    Same method in Java-like pseudo-code
      protected void onPostExecute(Void paramVoid) {
        this.this$0.sendBug(this.log);
        this.val$progress.dismiss();
        this.this$0.finish();
        System.exit(0);
      }
    

    So we answered the second question. It turns out interesting, right? Yandex.Money has a certain method sendBug(String paramString)that sends a logcat-log with a bunch of user personal data to some dmitriyap.dyndns.orgin unencrypted form.

    In this situation, the third question is: under what conditions does the Yandex.Money program invoke this terrible method sendBug(String paramString)? - becomes especially interesting. The correct answer is obtained after a careful study of the application code: the

    sendBug (String paramString) method is not called under any conditions! Never!

    Yes, this method is never called. This is dead code. A careful study (which I omit here, because it is long and tedious) of the Yandex.Money application code makes you think that the method sendBug(String paramString)used to be called when the native component crashedlibcache_local.so(the component is responsible for interacting with Yandex.Maps). But then the call was removed, although the method itself was forgotten. Therefore, the Yandex.Money application does not send any personal data anywhere. And Yandex users are safe.

    Probably the very evil computer crackers in dark glasses and shiny leather coats, which I mentioned at the very beginning, are now disappointed. They probably expected me to tell how I found a backdoor in Yandex.Money, or maybe even give them the key to this backdoor. But no guys! There is no backdoor (at least here). There is just dumb, but dead code, and our peaceful reverse engineering revealed it.

    I stated all of the above in a report to Yandex (Ticket # 12092801010226151). I wrote that despite the fact that the methodsendBug(String paramString)safe for users, the very presence of this method in Yandex.Money is a formless disgrace. In addition, the application writes a bunch of personal user data in the logcat log. As a result, we chatted nicely by mail with the Yandex security team - the guys were very adequate. And already in the next Yandex.Money release version 1.80, which by the way came out very soon, all the aforementioned shortcomings were fixed: the application no longer writes user personal data to the logcat-log and sendBug(String paramString)removed the fast-moving method . So our peaceful reverse engineering helped make Yandex.Money a little better.

    I hope that my story about peaceful reverse engineering entertained you, although it turned out to be a bit long and confusing. Sorry if it suddenly seemed to me that I had leaked the ending.

    Happy debugging!

    PS And - yes, in version 1.80 Yandex finally obfuscated the Java code of the Yandex.Money Android application. It was high time.

    Also popular now: