Как я потерял пароль от Android keystore, но потом смог восстановить с помощью Jetbrains Idea

    Предыстория

    Жило-было в Google Play Android приложение с несколькими тысячами пользователей. Через год понадобилось его обновить. Ок, запускаем Idea, выбираем «Build» — «Generate Signed APK». Вспоминаю что за это время успел пересесть в Linux, ничего страшного, выбираю файл с ключами, ввожу ранее заботливо записанный пароль… Не подходит. Хмм… Ввожу еще раз, еще… Перебор вариантов, переспрос коллег… Всё плохо.

    В итоге потенциально три приложения зависли в Google play, ни один из вариантов не подходит. Вспоминаю, что Windows остался на dual-boot, перезагружаюсь туда, к счастью в этом экземпляре Idea остался сохраненный пароль.

    signed apk

    Решение

    Приложение успешно обновлено, но проблема с паролем осталась. Запрос в Jetbrains к сожалению не помог, поддержка ответила быстро, но ответ был в том духе что пароль восстановить не является возможным, дали ссылку на исходники и предложили сделать свой хак. Что в общем то логично.

    Ну что же, надо думать. Так как Idea это обычное java-приложение, то возникла мысль подключить свой код к тому месту, где из хранилища считываются пароли. После прочтения топика про javaagent быстро набросал свой java agent который просто записывал в файл имена всех загружаемых классов. Все что нужно чтобы Idea запускалась с java agent, это прописать в файл idea.exe.vmoptions (или idea64.exe.vmoptions) строку вида
    -javaagent:C:\projects\agent\out\artifacts\agent_jar\agent.jar 
    


    После запуска с агентом текстовый файл быстро наполнился строками вида
    com/intellij/openapi/ui/impl/DialogWrapperPeerImpl$MyDialog
    com/intellij/openapi/ui/impl/DialogWrapperPeerImpl$MyDialog$1
    com/intellij/openapi/ui/impl/DialogWrapperPeerImpl$MyDialog$DialogRootPane
    com/intellij/openapi/ui/impl/DialogWrapperPeerImpl$MyDialog$MyWindowListener
    com/intellij/openapi/ui/DialogWrapper$19
    com/intellij/openapi/ui/DialogWrapper$ErrorPaintingType
    com/intellij/ide/wizard/AbstractWizard$1
    


    Затем жму на «Generate Signed APK» и смотрю на вывод в файле:
    org/jetbrains/android/exportSignedPackage/KeystoreStep
    org/jetbrains/android/compiler/artifact/ApkSigningSettingsForm
    org/jetbrains/android/exportSignedPackage/ExportSignedPackageWizardStep
    


    Кажется, все нужное нам лежит в exportSignedPackage

    Небольшое гугление, и находим исходники 2012 г.

    Здесь нас привлекает кусочек кода:
            String password = passwordSafe.getPassword(project, KeystoreStep.class, KEY_STORE_PASSWORD_KEY);
            if (password != null) {
              myKeyStorePasswordField.setText(password);
            }
            password = passwordSafe.getPassword(project, KeystoreStep.class, KEY_PASSWORD_KEY);
            if (password != null) {
              myKeyPasswordField.setText(password);
            }
    

    Здесь видно, что пароли вытаскивается из защищенного хранилища и сохраняются в JPasswordField (стандартный контрол Swing для ввода паролей).

    Осталось всего ничего — вытащить данные из текстовых полей. В этом нам поможет Javassist — библиотека для манипулирования байт-кодом «на лету». Пишем в нашем java-agent следующий кусочек кода:
        public byte[] transform(final ClassLoader loader, String className,
                                final Class classBeingRedefined, final ProtectionDomain protectionDomain,
                                final byte[] classfileBuffer) throws IllegalClassFormatException {
            if ("javax/swing/JPasswordField".equals(className)) {
                try {
                    ClassPool cp = ClassPool.getDefault();
                    CtClass cc = cp.get("javax.swing.JPasswordField");
                    CtMethod m = cc.getDeclaredMethod("getPassword");
                    m.insertAfter("{System.out.println(\"password is: \" + $_);}");
                    byte[] byteCode = cc.toBytecode();
                    cc.detach();
                    return byteCode;
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            return classfileBuffer;
        }
    

    Что он делает? Перехватываем момент загрузки класса JPasswordField, находим в нем метод getPassword() и добавляем в конец метода наш фрагмент кода, который печатает в консоль искомый пароль ($_ это служебная переменная javassist, где лежит значение возвращаемое методом).

    Таким нехитрым способом пароли были восстановлены и спасены.

    P. S. А пароль оказался тем же самым, что и был записан, но вводился в русской раскладке. Всё было просто на самом деле…
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 29

      0
      Вопрос не по теме :)

      "javax/swing/JPasswordField".equals(className)
      


      Есть какой-то особый смысл в этом? Обычно сравнение инвертировали, чтобы случайно не опечататься и не выполнить присваивание вместо него. Но в случае с equals() такой угрозы нет. Привычка? :)
        +17
        В Java так страхуются от null. Иначе если в className вдруг окажется null, то приложение выбросит NullPointerException.
          0
          Аннотации вам в помощь! От них же, от JetBrains.
            0
            Я бы сказал, что в с случае, если логикой алгоритма не предусматривается, что className может быть null, то лучше пусть программа выбросит исключение, чем просто тихо ничего не произойдет.
            +10
            Сравнивают константу со значением, а не наоборот, чтобы в случае если значение null, не было NullPointerException. В Java начиная с 7 можно сделать так — Objects.equals(«javax/swing/JPasswordField»,className); Это будет работать при null значениях параметров (метод внутри проверяет на null).
            P.S. Не успел чуть чуть с ответом :-) Но оставлю, т.к. есть ещё инфа.
            +2
            Когда сам столкнулся с подобной проблемой, написал плагин, который при загрузке проекта, выбрасывал пароли от keystore и key в консоль. Т.к. плагины могут получить доступ к этому хранилищу.
              0
              «А пароль оказался тем же самым, что и был записан, но вводился в русской раскладке»
              Обычно так бухгалтера делают :) Не с 1С программиста начинали?
                +1
                Я вас умоляю, бухгалтера используют даты рождения :)
                А ввод не в той раскладке это как доп. мера защиты. Не обязательно же использовать словарное слово, можно выдумать свое, которое будет легко запомнить на русском, но в другой раскладке это будет набор букв (+ не забываем что до сих пор в некоторых местах пароль может содержать только английские символы). Добавить сюда пару цифр и символов и вот у вас уже вполне хороший пароль.
                  0
                  Ха-ха, нет. Просто раскладку забыл видимо поменять, потому-то второй пароль был как полагается английскими буквами
                    0
                    Была одна замечательная картинка на эту тему (не могу вставить в комментарий):

                    xkcd.com/936/
                      +4
                      Вставляю за вас
                      image
                        0
                        но на самом деле при атаке по словарю оба варианта одинаково слабы
                          0
                          Согласен, но второй вариант избавляет от проблем с раскладкой
                            0
                            Несколько преднамеренных ошибок в орфографии спасают
                      +2
                      Интересно. Наверно, Eclipse Memory Analyzer'ом (или другим анализатором) было бы нетрудно выдрать, просто сняв дамп памяти, когда окошко с паролем на экране, а потом поискав в дампе все объекты типа JPasswordField.
                        +1
                        Во-первых, я не додумался :)
                        Во-вторых, разработчики могли не ставить сохраненный пароль в JPasswordField, а брать напрямую из хранилища
                        В-третьих, писать программу все таки веселей, чем нудно искать по дампу последовательность символов
                          +2
                          Я не критикую, просто предлагаю альтернативный вариант :-) Писать программы очень весело, с этим целиком согласен. Но и по дампу искать не нудно: вводите название класса, получаете все его экземпляры. Не думаю, что в памяти очень много компонентов JPasswordField. Если бы не было сохранённого пароля, то да, было бы посложнее. Эклипс обычно не заполняет форму с сохранённым паролем: если хочешь поменять, вводи заново. В таких случаях можно подцепиться отладчиком через remote debug, поставить брейкпоинт на вход в какой-нибудь подходящий метод.
                        0
                        Можно проще. Альтернативное решение: скачать исходники IntelliJ IDEA Community Edition (ядро идеи) с гитхаба, прописать в вашу главную идею в bin/idea.exe.vmoptions две дополнительные строки
                        -Xdebug
                        -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5006

                        Запускаем первую идею, запускаем вторую идею с сорцами. Коннектимся. Ставим break point в класс DialogWrapper и вытаскиеваем то, что нужно.
                          0
                          Возможно, сработает, не спорю. Но мой метод позволяет увидеть пароль в вообще любой java-программе, где используется JPasswordField.
                          Основная мораль поста — сохраненные пароли в java это ОЧЕНЬ небезопасно.
                            0
                            Сохраненные пароли где угодно — это небезопасно. Т. к. сохраненный пароль где-то должен лежать plain text'ом. В нормальном варианте используется мастер-пароль, которым это хранилище обратимо шифруется.
                              0
                              В Idea тоже все зашифровано мастер-паролем, но в какой-то момент все равно все равно в открытом виде светится.
                                0
                                Ниже klirichek написал один из вариантов. И такое я уже встречал в части софта. Причем, количество «звездочек» может соответствовать или не соответствовать длине пароля, зависит от того, как задумал автор. И оно не сильно сложнее простой подстановки пароля в текстовое поле (один внешний индекс, чтобы определить, есть ли пароль для данного случая).
                                  +1
                                  Как написал ниже — главное если пароль должен куда-то передаться, то его можно отследить и перехватить. Да, с этим можно бороться, но так как пароль в какой-то момент времени будет в открытом виде, то из-за наличия технологии java agent и библиотек позволяющих менять байт-код на лету, данные можно считать.
                                  Поможет видимо только физическая защита компьютера :)
                                    +1
                                    Это принципиально ничем не отличается от вытаскивания этого пароля из памяти любого другого процесса под ida. Если вы хотите, чтобы ключ было невозможно скопировать — то берите внешний токен (смарт-карта), который сам выполнит подпись/шифрование не раскрывая ключа. Все остальные способы светят ключ в памяти (т. к. он нужен при работе алгоритма шифрования/подписи), а значит его можно извлечь.

                                    Просто такая атака практически может быть слишком дорогой, что понизит рентабельность предприятия по её осуществлению. В случае java она немного дешевле.
                              +2
                              Ну, на самом деле не пароль, а текст, вставленный в это поле!
                              А пароль… Например, я могу просто узнать длину и вставить туда нужное количество звёздочек. А пароль никуда не вставлять, а просто использовать по назначению.
                                +1
                                Пароль то все равно будет в какой-то момент в открытом виде, необязательно при вставке в текстовое поле. И вот этот момент можно отследить и перехватить пароль.
                                  0
                                  Поэтому правильный вариант — не сохранять пароли. Как бы вы их не зашифровали, если зашифрованный файл и хитрая программа, которая этот файл расшифровывает, лежат рядом — пароль можно расшифровать.
                                    0
                                    Добавить нечего.
                            0
                            Как обычно -вся развязка в конце

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

                            Самое читаемое