#android #reverse-engineering #dex #smali
#Android #обратное проектирование #dex #smali
Вопрос:
Я уже некоторое время работаю со smali / baksmali, и я понимаю инструкции в разумной степени. В последнее время я столкнулся с действительно странной ошибкой. Вставка инструкции после выровненного try-catch
блока приводит к конфликту типа VerifyError во время выполнения. Я сделал некоторые предположения о том, в чем может быть причина, в том числе предположил, что ART
это не принимает вызовы, которые ожидают изменяемых результатов, но это не так. Я также попытался вставить sput
инструкцию, чтобы проверить, будет ли она работать, но это не так. Но для некоторых методов, которые я видел, put
инструкции, вставленные в ту же позицию, работают довольно хорошо, что наводит меня на мысль, что это проблема выравнивания. Но я не знаю, на каком правиле это основано.
Насколько я понимаю, ошибка конфликтующего типа возникает, когда тип регистра изменяется, вероятно, посредством ветвления на разные условные обозначения или goto
операторы. Но в этом случае я не могу понять, где произошел скачок, который оправдал бы это.
Возьмем, к примеру, этот метод
.method private static zzd(Ljava/lang/String;)I
.locals 7
const/4 v0, 0x0
const/4 v1, 0x2
const/4 v2, 0x3
const/4 v3, 0x1
:try_start_0
const-string v4, "[.-]"
.line 19
invoke-static {v4}, Lcom/a/b/c/d/zzax;->zza(Ljava/lang/String;)Lcom/a/b/c/d/zzax;
move-result-object v4
invoke-virtual {v4, p0}, Lcom/a/b/c/d/zzax;->zza(Ljava/lang/CharSequence;)Ljava/util/List;
move-result-object v4
.line 20
invoke-interface {v4}, Ljava/util/List;->size()I
move-result v5
if-ne v5, v3, :cond_0
.line 21
invoke-static {p0}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I
move-result p0
return p0
.line 22
:cond_0
invoke-interface {v4}, Ljava/util/List;->size()I
move-result v5
if-lt v5, v2, :cond_1
.line 23
invoke-interface {v4, v0}, Ljava/util/List;->get(I)Ljava/lang/Object;
move-result-object v5
check-cast v5, Ljava/lang/String;
invoke-static {v5}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I
move-result v5
const v6, 0xf4240
mul-int v5, v5, v6
.line 24
invoke-interface {v4, v3}, Ljava/util/List;->get(I)Ljava/lang/Object;
move-result-object v6
check-cast v6, Ljava/lang/String;
invoke-static {v6}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I
move-result v6
mul-int/lit16 v6, v6, 0x3e8
add-int/2addr v5, v6
.line 25
invoke-interface {v4, v1}, Ljava/util/List;->get(I)Ljava/lang/Object;
move-result-object v4
check-cast v4, Ljava/lang/String;
invoke-static {v4}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I
move-result p0
# Inserting any instruction here causes a type p0 to become a conflicted type
:try_end_0
.catch Ljava/lang/IllegalArgumentException; {:try_start_0 .. :try_end_0} :catch_0
add-int/2addr v5, p0
return v5
:catch_0
move-exception v4
const-string v5, "LibraryVersionContainer"
.line 29
invoke-static {v5, v2}, Landroid/util/Log;->isLoggable(Ljava/lang/String;I)Z
move-result v2
if-eqz v2, :cond_1
new-array v1, v1, [Ljava/lang/Object;
# Trying to insert the value of p0 into the object array causes the VerifyError
aput-object p0, v1, v0
aput-object v4, v1, v3
const-string p0, "Version code parsing failed for: %s with exception %s."
.line 31
invoke-static {p0, v1}, Ljava/lang/String;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
move-result-object p0
.line 32
invoke-static {v5, p0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
:cond_1
const/4 p0, -0x1
return p0
.end method
Ответ №1:
флаг baksmali —register-info — ваш друг. Для каждой инструкции будут добавлены комментарии с подробными сведениями о типах регистров. Это должно показать вам, где именно тип регистра конфликтует, включая входящие ребра и входящие типы с каждого ребра.
baksmali d --off --register-info ARGS,DEST,FULLMERGE -b "" blah.dex
например, вот комментарий, касающийся p0
вашего исходного источника, где он вступает в противоречие с блоком catch, после добавления инструкции (например invoke-static {v4}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I
) непосредственно перед try_end .
#p0=(Conflicted):merge{0x3:(Reference,Ljava/lang/String;),0x4:(Reference,Ljava/lang/String;),0x9:(Reference,Ljava/lang/String;),0xd:(Reference,Ljava/lang/String;),0x12:(Reference,Ljava/lang/String;),0x1d:(Reference,Ljava/lang/String;),0x22:(Reference,Ljava/lang/String;),0x23:(Reference,Ljava/lang/String;),0x2c:(Reference,Ljava/lang/String;),0x31:(Reference,Ljava/lang/String;),0x32:(Reference,Ljava/lang/String;),0x3a:(Reference,Ljava/lang/String;),0x3e:(Reference,Ljava/lang/String;),0x3f:(Reference,Ljava/lang/String;),0x44:(Integer)}
Каждое из этих входящих ребер исходит из инструкций в блоке try, которые потенциально могут вызвать исключение. Добавляя новую инструкцию, которая может вызывать исключение после move-result p0
инструкции в конце блока try , вы добавляете новое входящее ребро в блок catch, а значение p0
from этого ребра ( Integer
) несовместимо с типами из всех других ребер, и поэтому объединенный тип являетсярассмотрено CONFLICTED
.
Числа в комментарии к слиянию относятся к смещению кода входящего ребра. Когда вы выполняете дизассемблирование с помощью опции —code-offsets (—off — это ярлык для этого), baksmali добавит комментарий со смещением кода для каждой инструкции, чтобы вы могли сопоставить смещение кода из комментария register info обратно с фактической инструкцией.
Стоит отметить, что смещение кода в комментарии к слиянию относится к инструкции, непосредственно предшествующей той, которая действительно может выдавать. Выбрасываемая инструкция не может повлиять ни на какие регистры, если она генерирует исключение, поэтому входящие типы относятся к типам post-инструкций из инструкции, непосредственно предшествующей инструкции.
Комментарии:
1. Спасибо за подробное объяснение. Теперь это действительно имеет больше смысла, я ценю ваш ответ.
2. Пожалуйста, у меня вопрос, есть ли способ заставить ART выполнить метод, несмотря на VerifyError ? Используя приведенный выше пример, если я вставлю две следующие инструкции
const/4 v2, 0x5
, иinvoke-static{v2}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
мы точно знаем, что этот вызов никогда не сможет вызвать исключение, по крайней мере, в обычной среде, и это означает, что поток выполнения никогда не попадет в блок catch, где он становится конфликтным. Почему мы не можем заверить ART, что знаем, что произойдет?