Справочная и точная ссылка в Dalvik Verifier

Я пишу инструментарий на байт-коде Dalvik, который выполняет некоторую регистрацию для различных записей вызова метода. В частности, на различных сайтах вызова методов я буду вставлять набор инструкций, которые собирают параметры, помещают их в массив Object[], а затем передают их функции регистрации.

Это все хорошо, я внедрил и преодолел все кладжи для большинства приложений. Но я столкнулся с одной особенно непроницаемой ошибкой верификатора Dalvik:

java.lang.VerifyError: Verifier rejected class io.a.a.g: void io.a.a.g.r() 
failed to verify: void io.a.a.g.r(): [0x570] register v5 has type Reference: 
java.lang.Object but expected Precise Reference: java.lang.String

Я посмотрел на код, генерируемый моей аппаратурой, и все, что я делаю, это помещаю регистр v5 в массив объектов.

У меня есть несколько вопросов:

  • Что такое точная ссылка и почему она несовместима со ссылками?
  • Что здесь означает смещение? [0x570] указывает на середину инструкции байт-кода, поэтому она явно не соответствует каким-либо инструкциям: инструкции там не включают v5.
  • Как мне это отладить? В идеале я хотел бы знать, что, по мнению верификатора, должно происходить, и исправить это.

РЕДАКТИРОВАТЬ:

Вот дамп байт-кода метода, о котором я говорю. https://gist.github.com/kmicinski/c8382f0521b19643bb24379d91c47d36 Как видите, 0x570 не t в начале инструкции, и (насколько я могу судить) нет места, где r5 конфликтует со строкой, где она должна быть объектом.


person Kristopher Micinski    schedule 22.04.2017    source источник


Ответы (3)


Если вы внимательно посмотрите на ошибку, она говорит вам, что вы передаете Object вместо ожидаемого String. В любом случае, больше ничего нельзя сказать, если вы не опубликуете фактический байт-код, который вызывает проблему.

Вы уверены, что 0x570 указывает на середину инструкции? Это не должно. В любом случае, способ отладки - это посмотреть на соответствующую инструкцию и выяснить, почему r5 является объектом, когда он должен быть строкой. Или вы можете опубликовать байт-код, чтобы я мог посмотреть.

Изменить: теперь, когда вы разместили код, на самом деле есть путь, который приводит к тому, что v5 является объектом, но это немного тонко

Обработчик исключений .catch JSONException {:5D8 .. :938} :BDE переходит к :BDE

Код обработчика исключений сохраняет перехваченное исключение в v5, что означает, что v5 больше не является строкой на данный момент. Затем он переходит к :162

:BDE
00000BDE  move-exception      v5
00000BE0  const               v0, 0x00488B36
00000BE6  invoke-static       Logger->logBasicBlockEntry(I)V, v0
00000BEC  goto/16             :162

:162 находится в пределах диапазона другого обработчика исключений: .catch ClassNotFoundException {:2E .. :594} :BF0

:Bf0 оставляет v5 нетронутым и переходит к :A28

:BF0
00000BF0  move-exception      v6
00000BF2  const               v0, 0x00488B3E
00000BF8  invoke-static       Logger->logBasicBlockEntry(I)V, v0
00000BFE  goto/16             :A28

:A28 — это начало блока кода, который предполагает, что v5 — это String. В частности, в инструкции :AE0 v5 передается функции, принимающей строку.

00000AE0  invoke-virtual      StringBuilder->append(String)StringBuilder, v7, v5

0xAE0 ровно в два раза больше 0x570, что объясняет смещение, показанное в ошибке, после того, как вы настроите единицы кода, как предложил JesusFreke.

Обратите внимание, что это не обязательно единственный неработающий путь кода, это просто первый, который я нашел, просматривая ваш код. Однако одного неверного пути достаточно, чтобы унифицировать тип v5 с JSONException и, следовательно, превратить его в Object.

person Antimony    schedule 22.04.2017
comment
Вот дамп байт-кода, сделанный через JEB: Как видите, 0x570 не существует как начало инструкции байт-кода. Кроме того, насколько я могу судить, r5 нигде в этом методе не используется неправильным образом (хотя поток управления немного сложен, поэтому наличие точного индекса ошибки может помочь) - person Kristopher Micinski; 22.04.2017
comment
@KristopherMicinski Я отредактировал свой пост, указав пример потока управления, который неправильно приводит к тому, что v5 не является объектом String. - person Antimony; 22.04.2017
comment
благодаря тонну! Ничего себе, вы действительно сделали все возможное, этот код действительно сложно интерпретировать. Я обязательно просмотрю это и приму ваш ответ. - person Kristopher Micinski; 23.04.2017

0x570, вероятно, является смещением в единицах кода, каждая из которых составляет два байта. Таким образом, смещение байта на самом деле равно 0xAE0, что соответствует инструкции, и эта инструкция ссылается на v5.

Я ожидаю, что происходит то, что где-то есть код, который хранит строку в v5, но есть еще один путь кода, который сливается между тем, где строка хранится в v5, и где она используется, и этот путь кода имеет другой тип объекта, хранящийся в v5 . Когда пути кода сливаются, он использует общий суперкласс двух типов в качестве типа регистра. Поэтому, если эти два типа совершенно не связаны, java.lang.Object будет суперклассом.

Чтобы отладить эту проблему, вы можете запустить baksmali с параметром --register-info ARGS,DEST,FULLMERGE (а также --code-offsets, чтобы вы могли легко найти 0xAE0), а затем просмотреть 0xAE0 назад и посмотреть, где тип v5 установлен как объект.

person JesusFreke    schedule 22.04.2017
comment
Если вы можете опубликовать разборку баксмали с информацией о регистре, как указано в моем ответе, я могу взглянуть. Или, скорее, показать вам, что искать :) - person JesusFreke; 22.04.2017
comment
Большое спасибо за помощь, @JesusFreke. На самом деле у меня есть анализ потока управления, который помогает выделить эти вещи, поэтому знание элементов смещения кода — это то, что я действительно искал! (Я посмотрю на это и вернусь к вам ..!) - person Kristopher Micinski; 22.04.2017
comment
Нестроковый объект исходит от обработчика исключений, который сохраняет исключение в v5, а затем в конечном итоге достигает AE0 без переназначения v5. Подробности смотрите в моем посте. - person Antimony; 22.04.2017
comment
Привет @JesusFreke и @Antimony! Хотел бы я принять оба ваших ответа! Я обновил свой пост ответом, описывающим, в чем в конечном итоге была проблема :-) - person Kristopher Micinski; 23.04.2017

Я хотел бы добавить свой ответ, так как другие были очень полезны, пожертвовав своим временем, чтобы ответить на сложный вопрос, который, вероятно, мало что обобщает!

Как указывает @Antimony, в моем коде был путь управления, который начинался с обработчика исключений, сохранял исключение в v5 (в результате чего v5 было Object), а затем goto'da указывал внутри исключения обработчик. Затем этот обработчик исключений вызвал использование v5 в качестве строки, что вызвало ошибку верификатора.

В исходном коде приложения единственной тонкой целью этого goto была инструкция return-void. Из-за этого верификатор Dalvik не распространял путь к обработчику исключений.

К сожалению, когда я переписал это приложение, это привело к тому, что цель этого обработчика исключений содержала больше, чем просто эту инструкцию return-void, делая причину проверки через этот блок и в перехваченный обработчик исключений. В частности, перед return-void я вставил вызов Logger.logMethodExit, который затем предполагает, что верификатор может передать управление обратно обработчику исключений (в данном случае :BF0) и, в конечном счете, в то место, где v5 использовалось как строка. В исходном приложении он был убит (в смысле потока данных gen/kill). Но при переписывании я включил этот дополнительный вызов, нарушающий инварианты потока данных... Грязь.

Я думаю, что знаю, как исправить это в моей реализации, но понять это было очень сложно!

Более общие уроки, извлеченные здесь:

  • Смещения ошибок верификатора на самом деле просто 2 * индекс в байт-коде

  • В отличие от байт-кода JVM, байт-код Dalvik считает подмножество кодов операций невыбрасываемыми, включая return. Это повлияет на анализ потока данных

  • Точная ссылка означает, что что-то ограничено определенным уточнением объекта в одном базовом блоке и Object в другом (хотя эта ошибка кажется мне немного эзотерической..)

  • Когда вы переписываете байт-код, вам нужно знать о наборах gen/kill, с которыми вы неявно работаете, и, в частности, инструкции return-* немедленно уничтожат все, в то время как переход к началу базового блока внутри try.. продолжит сохранять эти вещи живут.

person Kristopher Micinski    schedule 23.04.2017
comment
Интересно, что вы столкнулись с этим угловым случаем. В байт-коде Java считается, что каждая инструкция может вызвать выброс, включая возврат. Однако Dalvik пытается быть умнее и заявляет, что может генерировать только подмножество опкодов. Список навскидку не помню, но судя по всему возвраты считаются не бросками. - person Antimony; 23.04.2017
comment
Ты случайно не помнишь, как ты это понял? Было ли это просто чтением исходного кода компилятора? Я кое-что читал, но, по-видимому, не в такой степени, как вы, поэтому ваш вклад был абсолютно полезен :-) Не знаю, как бы я понял это иначе. - person Kristopher Micinski; 23.04.2017
comment
Я столкнулся с этим (могут выдавать только определенные инструкции) при чтении через верификатор байт-кода dalvik, пытаясь имитировать его логику вывода типа регистра, при реализации функции деодексации baksmali. См., например. флаг инструкции броска в android.googlesource.com /платформа/dalvik/+/kitkat-релиз/ - person JesusFreke; 23.04.2017
comment
@JesusFreke да, я поддерживаю Redexer (github.com/plum-umd/redexer), который — это реализация OCaml, имитирующая Baksmali. Он также регистрирует перераспределение для тяжеловесных двоичных инструментов, которые искажают типизированные инструкции (хотя, возможно, Баксмали делает это и сейчас). Я рад, что вы указали на это, так как это означает, что наш инструмент также, вероятно, неисправен. - person Kristopher Micinski; 23.04.2017
comment
Да, я думал реализовать что-то подобное, но так и не сделал. Я уверен, что это было бы очень полезно, когда вам нужно увеличить размер регистра при инструментировании кода. Ручное увеличение количества регистров может быть довольно сложным, особенно с регистрами параметров и ограничениями регистров v0-v15 некоторых инструкций, как я уверен, вы знаете :) - person JesusFreke; 23.04.2017
comment
@JesusFreke, верно! Наш инструмент корректно обрабатывает (главным образом, мы регистрируем большинство вызовов методов/возвратов/записей основных блоков) довольно большие приложения (в настоящее время работающие в Snapchat!). Я рад, что мы смогли реализовать эту функцию, но жаль, что мы продублировали работу Баксмали. Я мог бы переключиться на этот инструмент в ходе нашей работы (и если да, то я реализую этот проход в Баксмали). - person Kristopher Micinski; 23.04.2017