Сегодня я хочу отойти от затасканных голливудских штампов про злых компьютерных взломщиков и поведать вам, дорогие читатели, о том как мирный reverse engineering помог чуть-чуть улучшить приложение Яндекс.Деньги. Надеюсь эта история пошатнет устойчивый стереотип, что reverse engineering — это обязательно плохо и нужно только нехорошим людям.
Чуть меньше месяца назад я немножко реверсил Яндекс.Деньги версии 1.71 для Android (последняя версия на тот момент). Кроме всего прочего интересного я нашел там некий загадочный метод ru.yandex.core.CrashHandler.sendBug(String paramString)
:
.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
Вот тот же метод sendBug(String paramString)
в Java-подобном псевдокоде, который после определённых манипуляций с dex файлом получается с помощью Java Decompiller:
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 не дружит, увы // ... } } }
Этот псевдокод конечно не совсем валиден с точки зрения синтаксиса языка Java, но зато он наглядно демонстрирует логику работы метода sendBug(String paramString)
. При вызове этого метода, на некий адрес http://dmitriyap.dyndns.org:9091
с помощью ClientHttpRequest.post()
без какого-либо шифрования отсылается куча различной информации. В частности, в параметре crash
отсылается переданный методу аргумент paramString
. Судя по строке запроса и названиями переменных, «на той стороне» поднята Atlassian Jira, в которой метод sendBug(String paramString)
создает issue сразу внося в него всю отсылаемую информацию. Т.е. по сути метод sendBug(String paramString)
делает ровно то что следует из его названия — отсылает bug report’ы разработчикам в bugtracker. Вроде бы ничего страшного, многие программы так делают. Однако код самого метода вызывает вопросы:
- Кому принадлежит домен
http://dmitriyap.dyndns.org
? Это явно не корпоративный домен Яндекса. - Что за информация передается методу
sendBug(String paramString)
в аргументеparamString
и потому отсылается наhttp://dmitriyap.dyndns.org
? - При каких условиях программа Яндекс.Деньги вызывает метод
sendBug(String paramString)
?
Ответ на первый вопрос находится достаточно быстро. Небольшой поиск в Google дает что dmitriyap — это интернет-ник главы Mobile Services Development Department в Яндексе. Вероятно, домен http://dmitriyap.dyndns.org
зарегистрировал именно он. Тот факт что данные никак не шифруются и отсылаются на поддомен dyndns.org, а не на какой-нибудь домен Яндекса, наводит на мысль, что вся эта система bug report’инга была сделана разработчиками Android-приложения Яндекс.Деньги наспех, «на коленке». Вероятно она использовалась в процессе разработки и не должна было попасть в релиз. Но, наверное по недосмотру, попала.
Что же с первым вопросом более менее ясно. Перейдем ко второму вопросу: что за информация передается методу sendBug(String paramString)
в аргументе paramString
и затем отсылается на http://dmitriyap.dyndns.org
? Для этого мы сначала посмотрим на код метода doInBackground(...)
анонимного внутреннего класса CrashHandler$1
:
.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
Соответствующий Java-подобный псевдокод полученный с помощью Java Decompiller:
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 выдал совсем полный бред, я эту пургу исключил что бы не смущать читателей // ... } }
Этот псевдокод опять-таки не совсем валиден с точки зрения синтаксиса языка Java, но зато из него понятно что делает doInBackground(...)
. Он запускает на Adnroid-устройстве командную строку
logcat -d -v threadtime
потом с помощью метода readAllOf(...)
(определён в том же классе) захватывает вывод и в виде строки помещает его в поле log
типа String
. Что в этой строке? А в ней кроме всего прочего куча персональны данных пользователя — история платежей, какие-то приватные куки и т.п. Вот небольшой кусочек для примера (данные тут мои и они замазаны конечно):
Откуда же в обычном logcat-логе столько персональных данных? Все дело в том что код приложения Яндекс.Деньги просто утыкан вызовами android.util.Log.d(...)
. По ходу работы приложения в лог пишется просто куча всякой информации — включая персональную информацию пользователя. Зачем? Не знаю, не знаю…
Однако вернемся ко второму вопросу. Куда же эта строка с кучей персональных данных из поля log
девается после вызова doInBackground(...)
? Вы не поверите, но она как раз и передается методу sendBug(String paramString)
в аргументе paramString
и затем отсылается на http://dmitriyap.dyndns.org
. В незашифрованном виде. Что бы в этом убедится достаточно посмотреть на код метода onPostExecute(...)
того же анонимного внутреннего класса CrashHandler$1
:
.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
Соответствующий Java-подобный псевдокод полученный с помощью Java Decompiller:
protected void onPostExecute(Void paramVoid) { this.this$0.sendBug(this.log); this.val$progress.dismiss(); this.this$0.finish(); System.exit(0); }
Вот мы и ответили на второй вопрос. Интересно получается, да? В Яндекс.Деньги есть некий метод sendBug(String paramString)
, который отсылает logcat-лог с кучей персональных данных пользователя на какой-то http://dmitriyap.dyndns.org
в незашифрованном виде.
В такой ситуации третий вопрос — при каких же условиях программа Яндекс.Деньги вызывает этот страшный метод sendBug(String paramString)
? — становится особенно интересным. Правильный ответ получается после тщательного исследования кода приложения:
Метод sendBug(String paramString) не вызывается ни при каких условиях! Никогда!
Да-да, этот метод не вызывается никогда. Это мертвый код. Исследования кода приложения Яндекс.Денег заставляют думать что метод sendBug(String paramString)
раньше вызывался при краше native компонента libcache_local.so
(компонент отвечает за взаимодействие с Яндекс.Картами). Но потом вызов убрали, хотя сам метод убрать забыли. Поэтому приложение Яндекс.Деньги никуда не отсылает никаких персональных данных. И пользователи Яндекс в безопасности.
Наверное те самые злые компьютерные взломщики в темных очках и блестящих плащах, о которых я упоминал в самом начале, сейчас разочарованы. Они вероятно ожидали что я расскажу как нашел в Яндекс.Деньгах бэкдор, а может даже дам им ключ от этого бэкдора. Но нет, ребята! Нету никакого бэкдора (по крайней мере тут). Есть просто стремный, но мертвый код, и наш мирный reverce engennering его выявил.
Все вышесказанное я изложил в репорте Яндексу (Ticket#12092801010226151). Я написал что несмотря на то что метод sendBug(String paramString)
безопасен для пользователей, само наличие этого метода в Яндекс.Деньгах — форменное безобразие. Мы очень мило пообщались по почте с security team Яндекса — ребята оказались очень адекватные. И уже в следующем релизе Яндекс.Денег версии 1.80, который кстати вышел очень скоро, все вышеупомянутые недочеты были исправлены: стремный метод sendBug(String paramString)
убрали и приложение больше не пишет личных данных пользователей в logcat-лог. Так наш мирный мирный reverce engennering помог сделать приложение Яндекс.Деньги немного лучше.
Я надеюсь что моя история про мирный reverce engennering вас развлекла, хотя она получилась немного длинной и путанной. Извините если вдруг кому показалось что я слил концовку.
Happy debugging!
ссылка на оригинал статьи http://habrahabr.ru/post/155925/
Добавить комментарий