Как мирный reverse engineering помог чуть-чуть улучшить приложение Яндекс.Деньги

от автора

Существует стереотип, что reverse engineering — это занятие для злых хакеров в темных очках и блестящих кожаных пальто. Под покровом ночи, в перерывах между беготней по стенам и рукопашными схватками с толпами спецназовцев, эти компьютерные нелюди творят страшные взломы программ, пентагонов и прочих баз данных. Сами взломы как правило не требуют никакой предварительной подготовки и занимают считанные секунды. Ну и конечно в процессе практически любого взлома по чОрным экранам адских хакерских ноутбуков с непонятной ОС ползут зелёные кракозяблы и/или крутится какая-то 3D-фиговина…

Сегодня я хочу отойти от затасканных голливудских штампов про злых компьютерных взломщиков и поведать вам, дорогие читатели, о том как мирный reverse engineering помог чуть-чуть улучшить приложение Яндекс.Деньги. Надеюсь эта история пошатнет устойчивый стереотип, что reverse engineering — это обязательно плохо и нужно только нехорошим людям.

Чуть меньше месяца назад я немножко реверсил Яндекс.Деньги версии 1.71 для Android (последняя версия на тот момент). Кроме всего прочего интересного я нашел там некий загадочный метод ru.yandex.core.CrashHandler.sendBug(String paramString):

Smali код метода 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:

Тот же метод в Java-подобном псевдокоде

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. Вроде бы ничего страшного, многие программы так делают. Однако код самого метода вызывает вопросы:

  1. Кому принадлежит домен http://dmitriyap.dyndns.org? Это явно не корпоративный домен Яндекса.
  2. Что за информация передается методу sendBug(String paramString) в аргументе paramString и потому отсылается на http://dmitriyap.dyndns.org?
  3. При каких условиях программа Яндекс.Деньги вызывает метод 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:

Smali код метода 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 

Соответствующий Java-подобный псевдокод полученный с помощью Java Decompiller:

Тот же метод в Java-подобном псевдокоде

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:

Smali код метода 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 

Соответствующий Java-подобный псевдокод полученный с помощью Java Decompiller:

Тот же метод в Java-подобном псевдокоде

  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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *