androidreverse-engineeringdexsmali

(ART/DVM) Inserting an instruction just before the :try_end instruction results in VerifyError


I've been working with smali/baksmali for some time now, and I understand the instructions to a reasonable extent. Lately, I've encountered an error that is really weird. Inserting an instruction after the aligned try-catch block results in Conflicted type VerifyError at runtime. I've made some assumptions about what the cause might be, including assuming that ART doesn't accept invocations that expects moveable results, but that's not the case. I also tried inserting an sput instruction to check if it'll work, but it doesn't. But for some methods I've seen, put instructions inserted at the same position works quite well, which leads me to believe it's an alignment problem. But I don't know what rule this is based on.

From my understanding, conflicted type error occurs when the type of the register is changed probably via branching to different conditionals or goto statements. But in this case, I can't seem to figure out where the jump that'd warrant this happening.

Take this method for example

.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

Solution

  • baksmali's --register-info flag is your friend. It will add comments for each instruction with the details about the register types. It should show you exactly where the register type is becoming conflicted, including the incoming edges and the incoming types from each edge.

    baksmali d --off --register-info ARGS,DEST,FULLMERGE -b "" blah.dex

    e.g. here is the comment regarding p0 from your original source, where it becomes conflicted at the catch block, after adding an instruction (e.g. invoke-static {v4}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I) just before the 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)}

    Each of those incoming edges are from instructions within the try block that could potentially throw an exception. By adding the new instruction that can throw an exception after the move-result p0 instruction at the end of the try block, you're adding a new incoming edge to the catch block, and the value of p0 from that edge (Integer) is not compatible with the types from all the other edges, and so the merged type is considered CONFLICTED.

    The numbers in the merge comment refer to the code offset of the incoming edge. When you disassemble with the --code-offsets option (--off is a shortcut for that), baksmali will add a comment with the code offset for each instruction, so you can match up the code offset from the register info comment back to the actual instruction.

    It's worth noting that the code offset in a merge comment refers to the instruction just prior to the one that can actually throw. The instruction that throws can't have affected any registers if it throws an exception, so the incoming types are from the post-instruction types from the instruction just prior.