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
Here is the same method
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
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
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
Corresponding Java-like pseudo-code obtained using 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
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
But back to the second question. Where does this line with a bunch of personal data from the field
Corresponding Java-like pseudo-code obtained using Java Decompiller:
So we answered the second question. It turns out interesting, right? Yandex.Money has a certain method
In this situation, the third question is: under what conditions does the Yandex.Money program invoke this terrible method
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
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 method
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.
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:9091
using ClientHttpRequest.post()
without any encryption. In particular, in the parameter crash
the 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:- Who owns the domain
dmitriyap.dyndns.org
? This is clearly not the corporate domain of Yandex. - What kind of information is passed to the method
sendBug(String paramString)
in the argumentparamString
and therefore sent todmitriyap.dyndns.org
? - 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.org
registered 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 paramString
and 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 devicelogcat -d -v threadtime
then, using a method readAllOf(...)
(defined in the same class), it captures the output and puts it in a log
type 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
log
go after the call doInBackground(...)
? You won’t believe it, but it is just passed to the method sendBug(String paramString)
in the argument paramString
and 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.org
in 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 method
sendBug(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.