Как мирный 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-приложения Яндекс.Деньги. Давно пора было.
    Метки:
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 35
    • +37
      Приятно видеть людей которые безвозмездно делают мир чуточку лучше.
    • +30
      Надо было баг-репорт отправлять прям на dmitriyap.dyndns.org, разработчики немного бы удивились :)
      • +12
        В момент исследования dmitriyap.dyndns.org был в глухом офлайне :) Да и моей целью было не показать разработчикам Яндекс.Денег какой я типа крутой хакер, а помочь сделать их продукт чуть лучше. Вроде получилось :)
        • +3
          Награду то пообещали?)
          • +10
            Если Вы о конкурсе «Охота за ошибками», то Яндекс.Деньги в нем не участвуют (странно, да?). Так что нет, награды мне не будет. Ну да мне и плевать если честно )
            • +2
              В конкурсе Яндекс.Деньги уже участвуют. Но вот мобильные приложения Денег, пока нет, к сожалению.
              • +1
                Да, я собственно это и имел ввиду. Наверное просто неясно выразился.
      • +7
        Вас не попытались захантить? :)
      • +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 есть вопрос, а то не могу найти контактных данных в профиле. Спасибо

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