25 октября 2012 в 01:50

Как мирный 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). При вызове этого метода, на некий адрес dmitriyap.dyndns.org:9091 с помощью ClientHttpRequest.post() без какого-либо шифрования отсылается куча различной информации. В частности, в параметре crash отсылается переданный методу аргумент paramString. Судя по строке запроса и названиями переменных, «на той стороне» поднята Atlassian Jira, в которой метод sendBug(String paramString) создает issue сразу внося в него всю отсылаемую информацию. Т.е. по сути метод sendBug(String paramString) делает ровно то что следует из его названия — отсылает bug report'ы разработчикам в bugtracker. Вроде бы ничего страшного, многие программы так делают. Однако код самого метода вызывает вопросы:

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

Ответ на первый вопрос находится достаточно быстро. Небольшой поиск в Google дает что dmitriyap — это интернет-ник главы Mobile Services Development Department в Яндексе. Вероятно, домен dmitriyap.dyndns.org зарегистрировал именно он. Тот факт что данные никак не шифруются и отсылаются на поддомен dyndns.org, а не на какой-нибудь домен Яндекса, наводит на мысль, что вся эта система bug report'инга была сделана разработчиками Android-приложения Яндекс.Деньги наспех, «на коленке». Вероятно она использовалась в процессе разработки и не должна было попасть в релиз. Но, наверное по недосмотру, попала.

Что же с первым вопросом более менее ясно. Перейдем ко второму вопросу: что за информация передается методу sendBug(String paramString) в аргументе paramString и затем отсылается на 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 и затем отсылается на 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-лог с кучей персональных данных пользователя на какой-то dmitriyap.dyndns.org в незашифрованном виде.

В такой ситуации третий вопрос — при каких же условиях программа Яндекс.Деньги вызывает этот страшный метод sendBug(String paramString)? — становится особенно интересным. Правильный ответ получается после тщательного исследования кода приложения:

Метод sendBug(String paramString) не вызывается ни при каких условиях! Никогда!

Да-да, этот метод не вызывается никогда. Это мертвый код. Внимательное исследование (которое я здесь опускаю, ибо оно долгое и нудное) кода приложения Яндекс.Денег заставляют думать что метод sendBug(String paramString) раньше вызывался при краше native компонента libcache_local.so (компонент отвечает за взаимодействие с Яндекс.Картами). Но потом вызов убрали, хотя сам метод убрать забыли. Поэтому приложение Яндекс.Деньги никуда не отсылает никаких персональных данных. И пользователи Яндекс в безопасности.

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

Все вышесказанное я изложил в репорте Яндексу (Ticket#12092801010226151). Я написал что несмотря на то что метод sendBug(String paramString) безопасен для пользователей, само наличие этого метода в Яндекс.Деньгах — форменное безобразие. К тому же приложение пишет кучу персональных данных пользователя в logcat лог. В результате мы мило пообщались по почте с security team Яндекса — ребята оказались очень адекватные. И уже в следующем релизе Яндекс.Денег версии 1.80, который кстати вышел очень скоро, все вышеупомянутые недочеты были исправлены: приложение больше не пишет личных данных пользователей в logcat-лог и стремный метод sendBug(String paramString) убрали. Так наш мирный reverse engineering помог сделать приложение Яндекс.Деньги немного лучше.

Я надеюсь что моя история про мирный reverse engineering вас развлекла, хотя она получилась немного длинной и путанной. Извините если вдруг кому показалось что я слил концовку.

Happy debugging!

P.S. И — да, в версии 1.80 Яндекс наконец-то обсфуцировал Java-код Android-приложения Яндекс.Деньги. Давно пора было.
Дима Коваленко @dimakovalenko
карма
104,2
рейтинг 0,0
Самое читаемое Разработка

Комментарии (35)

  • +37
    Приятно видеть людей которые безвозмездно делают мир чуточку лучше.
    • +6
      Спасибо :)
  • +30
    Надо было баг-репорт отправлять прям на dmitriyap.dyndns.org, разработчики немного бы удивились :)
    • +12
      В момент исследования dmitriyap.dyndns.org был в глухом офлайне :) Да и моей целью было не показать разработчикам Яндекс.Денег какой я типа крутой хакер, а помочь сделать их продукт чуть лучше. Вроде получилось :)
      • +3
        Награду то пообещали?)
        • +10
          Если Вы о конкурсе «Охота за ошибками», то Яндекс.Деньги в нем не участвуют (странно, да?). Так что нет, награды мне не будет. Ну да мне и плевать если честно )
          • +2
            В конкурсе Яндекс.Деньги уже участвуют. Но вот мобильные приложения Денег, пока нет, к сожалению.
            • +1
              Да, я собственно это и имел ввиду. Наверное просто неясно выразился.
  • +7
    Вас не попытались захантить? :)
    • +2
      Яндекс? Нет :)
      • +2
        А кто да? Г...? ;)
        • +1
          Нет, Г… тоже не :)
  • +5
    Я не очень понимаю, любой нормальный компилятор отбрасывает неиспользуемый код, попутно выводя warning. Чей косяк, Android SDK, JDK?
    • +1
      Или этот метод всё же где-то вызывается...? Поэтому в версии 1.80 весь код и обсфуцировали. (Пробую найти теорию заговора ))
      • +3
        Думаю этот код не был отброшен из-за (пишу псевдокодом, так короче и понятнее, кому интересно — сам посмотрит как это выглядит в Smali):

        public class CoreApplication implements LocationListener {
         
          // ...неинтересный код был тут
        
          public static CoreApplication getCoreApplication() {
            if (coreApplication == null)
              throw new RuntimeException("here CoreApplication must exists!");
            return coreApplication;
          }
          
          private static void onNativeCrashed() {
            Class localClass = getCoreApplication().params.getCrashHandlerClass();
            if ((localClass != null) && (!getAppBuildIdFromNative().contains("master_market"))) {
              new RuntimeException("crashed here (native trace should follow after the Java trace)");
              new StringBuilder("App name is ").append(getAppNameFromNative());
              new StringBuilder("Build ID is ").append(getAppBuildIdFromNative());
              if (getAppBuildIdFromNative().length() > 32)
                applicationContext.startActivity(new Intent(applicationContext, localClass).setFlags(268435456));
            }
        	
        	// ...неинтересный код был тут
        }
        

        Обратите внимание на метод onNativeCrashed(). Он вызывается при краше native библиотеки которая работает с Яндекс.Картами. Метод получает класс, унаследованный от CrashHandler (в котором и как раз и живет тот самый безобразный метод sendBug(String paramString)) путём вызова

        Class localClass = getCoreApplication().params.getCrashHandlerClass();
        

        Если класс получен успешно (см. дальше условия if ((localClass != null)...) и если native-библиотека которая отвечает за взаимодействие с Яндекс.Картами вернула правильный пароль :) — стартует activity, из которой потом можно будет вызвать этот самый гадкий sendBug(String paramString).

        Но есть одно «но»! Если посмотреть на метод getCrashHandlerClass(), то мы увидим что он всегда возвращает null:

        public class MapsCoreApplicationParams extends CoreApplicationParams
        {
          // ...ля-ля-ля, не важно
          
          public Class getCrashHandlerClass()
          {
            return null;
          }
          
          // ...ля-ля-ля, тоже не важно
        }
        

        Поэтому класс никогда не будет получен, а следовательно activity не стартует и метод sendBug(String paramString) никогда не будет вызван. Как видите, тут все запутано как бразильском сериале :) Думаю поэтому компилятор не смог до конца вкурить все эти связи и стремный код на всякий случай оставил.

        Я не стал включать все эти исследования в статью — решил ограничится общей фразой про то что мол тщательное исследование кода приложения заставляет думать что ля-ля-ля…
    • +4
      Возможно, нормальный компилятор так и делает, если это сильно связанный код, но писать такой код сейчас не принято.
      А принимать решение за разработчика, и выкидывать public и protected методы нормальный компилятор не должен.
      • 0
        Возможную причину я написал выше, но может Вы и правы — дело действительно в том, что Java не склонна выкидывать public и protected методы. Честно говоря точно не знаю.
    • +1
      Компилятор в принципе не может определить, вызывается ли метод. По причине Reflection например в случае Явы. Неиспользуемый код убирается только в пределах одного метода (и то бывает чревато).
  • +1
    Хм, а в SDK нет штатного механизма отправки багрепортов? Мне кажется довольно логичным иметь возможность получить из системных логов все записи своего приложения за предыдущие N минут, и как-то через гугель закинуть их разработчику приложив к traceback'у с места падения.

    • +1
      Видимо яндекс решил не делиться с гуглем незашифрованными персональными данными в логах.
      • +1
        данные перед отправкой в штатный механизм можно и зашифровать, не должно быть проблемой.
  • +3
    Небольшой поиск в Google дает — а поиск Яндекс не дает?))
    • 0
      Вроде бы тоже даёт (ну по крайней мере нужный твиттер в первой десятке). Но с Google у меня как-то лучше любовь складывается, поэтому пользуюсь в основном им :)
      • +4
        Извиняюсь, проглядел абзац, мне показалось, что вы работаете в Яндексе. Троллинг не получился.
        • +2
          Нет, я свободный художник. Если бы я работал в Яндексе, то наверное я бы имел исходный код этого приложения и не было бы смысла заморачиваться с дизассемблированием и дебаггингом. Но так даже лучше — без исходного кода ковырять программу интереснее :)
  • 0
    «Пароль „Рыба-меч“»? :)
    • +3
      Да, он. Помнится, там ещё во время взлома какая-то девушка хакеру делала мине… ну в общем отвлекала хакера от процесса :)
      • +2
        Это было собеседование на работу.
        • +1
          А та девушка видимо была HR-менеджер :)
          • 0
            Это была Хэлли Берри, кстати.
            • +1
              Это была не Хэлли Берри, кстати;)
              Там какая то другая белая подружка глав террориста была.
              *чёрт, какую же фигню мой мозг помнит*
        • 0
          В «Социальной сети», помнится, конкурс для программистов включал работу после выпивания спиртного.

          Осталось объединить идеи :)
  • 0
    Так вот для чего каждое второе приложение из гуглплея хочет смотреть логкат. Правда я такие приложения все равно не ставлю.
    • +2
      Ну в logcat логе только системный лог (например такая-то activity стартовала, такой-то процесс завершился и т.п.) и то что приложения сами пишут туда с помощью методов из android.util.Log. По идее приложение не должно писать в лог никакой конфиденциальной информации, но на практике разработчики часто используют методы из android.util.Log в процессе разработки с целью отладки, а потом забывают убрать вызовы этих методов из релиза. Я подозреваю что с Яндекс.Деньгами 1.71 так и получилось. В версии 1.80 это пофиксили.
      • 0
        Дима, напишите мне в личку или по job.rabota@gmail.com есть вопрос, а то не могу найти контактных данных в профиле. Спасибо

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.