📜 ⬆️ ⬇️

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

There is a stereotype that reverse engineering is an occupation for evil hackers with dark glasses and shiny leather coats. Under the cover of night, in the intervals between running around the walls and melee fights with the crowds of special forces, these computer nonhumans make terrible hacks of programs, pentagons and other databases. The hacks themselves usually do not require any prior preparation and take a few seconds. And, of course, in the process of almost any hacking on the H-RED screens of hellish hacker laptops with incomprehensible OS green krakozyably crawling and / or some kind of 3D-figurine is spinning ...



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

Slightly 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 some 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;-><init>()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;-><init>(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;-><init>(Ljava/lang/String;)V .line 167 .local v2, v2:Ljava/lang/Object; invoke-direct {v1, v2}, Lru/yandex/core/ClientHttpRequest;-><init>(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;-><init>([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;-><init>([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 sendBug(String paramString) method sendBug(String paramString) in a Java-like pseudocode, which, after certain manipulations with the dex file, is obtained using Java Decompiller :

The same method in a java-like pseudocode
 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 of course not entirely valid in terms of the syntax of the Java language, but it clearly demonstrates the logic of the sendBug(String paramString) method. When calling this method, to a certain address dmitriyap.dyndns.org:9091 dmitriyap.dyndns.org:9091 using ClientHttpRequest.post() without any encryption sent a bunch of different information. In particular, the paramString argument passed to the method is sent to the crash parameter. Judging by the query string and the names of the variables, “on the other side” is raised by Atlassian Jira , in which the sendBug(String paramString) method sendBug(String paramString) creates an issue immediately inserting into it all the information sent. Those. in fact, the sendBug(String paramString) 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 it. However, the code of the method itself raises questions:

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

The answer to the first question is quite fast. A small search on Google suggests that dmitriyap is the nickname of the head of the Mobile Services Development Department in Yandex. Probably domain dmitriyap.dyndns.org dmitriyap.dyndns.org it was he dmitriyap.dyndns.org registered. The fact that the data is not encrypted in any way and is sent to the dyndns.org subdomain, and not to any Yandex domain, suggests that this whole bug report system was made by the developers of the Yandex.Money Android application hastily, “on the knee ". It was probably used in the development process and should not have been released. But, probably by mistake, I got it.

As for the first question, it is less clear. Let's go to the second question: what sendBug(String paramString) information is passed to the sendBug(String paramString) method sendBug(String paramString) in the paramString argument and then sent to dmitriyap.dyndns.org dmitriyap.dyndns.org ? To do this, we first look at the code of the doInBackground(...) method of the anonymous internal 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 

The corresponding Java-like pseudo-code obtained using Java Decompiller:

The same method in a java-like pseudocode
 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    ,          // ... } } 

Again, this pseudocode is not quite valid in terms of the syntax of the Java language, but it is clear from it what doInBackground(...) does. It runs the command line on the Adnroid device.
 logcat -d -v threadtime 
then, using the readAllOf(...) method (defined in the same class), captures the output and places it in the form of a string in the field of the String type log . What is in this line? And in addition to everything else, there is a bunch of user personal data - payment history, some private cookies, etc. Here is a small piece for an example (the data here are mine and they are smeared of course):



Where does a regular logcat-log contain so much personal data? The thing is that the Yandex.Money application code is just stuck with calls android.util.Log.d(...) . In the course of the application, a bunch of all information is written to the log, including personal user information. What for? I don’t know, I don’t know ... Probably it was 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 log field go after calling doInBackground(...) ? You will not believe it, but it is just passed to the sendBug(String paramString) method sendBug(String paramString) in the paramString argument and then sent to dmitriyap.dyndns.org dmitriyap.dyndns.org . Unencrypted. To onPostExecute(...) this, just look at the code of the onPostExecute(...) method of the same anonymous internal 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 

The corresponding Java-like pseudo-code obtained using Java Decompiller:

The same method in a java-like pseudocode
  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, yes? Yandex.Money has a certain sendBug(String paramString) method sendBug(String paramString) , which sends a logcat-log with a bunch of user personal data to some dmitriyap.dyndns.org dmitriyap.dyndns.org unencrypted.

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

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

Yes, this method is never called. This is a dead code. Careful research (which I omit here, because it is long and tedious) of the Yandex.Money application code makes you think that the sendBug(String paramString) method sendBug(String paramString) previously called when the native libcache_local.so component was more beautiful (the component is responsible for interacting with Yandex.Maps). But then the call was removed, although they forgot to remove the method itself. Therefore, the Yandex.Money application does not send any personal data anywhere. And Yandex users are safe.

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

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

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

Happy debugging!

PS And - yes, in version 1.80, Yandex finally mastered the Java code of the Yandex.Money Android application. It's time to be.

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


All Articles