2017-04-22 52 views
4

我正在为Dalvik字节码写入工具,该代码为各种方法调用条目执行一些日志记录。具体来说,在各种方法调用站点上,我将插入一组指令来收集参数,将它们放入一个Object[]数组中,然后将其传递给日志记录功能。Dalvik验证器中的参考与精确参考

这一切都很好,我已经实现并获得了大部分应用程序的所有功能。但我遇到一个特别令人费解的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不是一个指令的开始,并且(据我所知)没有任何地方r5与String应该是一个对象冲突。

回答

3

如果仔细观察错误,它会告诉您,您正在传递一个Object,其中String是预期的。无论如何,除非你发布导致问题的实际字节码,否则没有太多可以说的。

您确定0x570指向指令的中间位置吗?它不应该。无论如何,你要调试它的方式是看看相关的指令,并找出为什么r5是一个Object,当它应该是一个String。或者你可以发布字节码,所以我可以看看。

编辑:现在你已经发布的代码,其实也有导致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是字符串。特别是,在指令:AE0上,v5被传递给一个带有String的函数。

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

0xAE0正是两次0x570,这也解释了在错误显示偏移,一旦你调整代码单位JesusFreke建议。

请注意,这不一定是唯一的破解代码路径,它只是我在查看代码时发现的第一个代码路径。但是,一条糟糕的路径足以将v5的类型与JSONException相整合,并将其转化为Object。

+0

这里的字节码的转储,通过杰布完成:https://gist.github.com/kmicinski/c8382f0521b19643bb24379d91c47d36 正如你所看到的,0x570是不是存在一个字节码指令的开始。另外,据我所知,在这个方法的任何地方'r5'都没有以不正确的方式被使用(尽管控制流是有一定难度的,这就是为什么有一个精确的错误指数会有所帮助) –

+0

@KristopherMicinski我编辑过我的帖子中有一个控制流示例,它不正确地导致v5是一个非String对象。 – Antimony

+0

谢谢!哇,你真的超越了,代码真的很难解释。我一定会仔细看看并接受你的答案。 –

2

0x570很可能是代码单元的偏移量,每个代码单元为两个字节。所以字节偏移量实际上是0xAE0,它与一条指令相对应,并且该指令确实引用了v5。

我期待发生的事情是有一些代码存储在v5中的字符串,但还有另一个代码路径合并在字符串存储在v5中的位置和它使用的位置之间,并且代码路径具有不同的对象类型存储在v5中。当代码路径合并时,它使用这两种类型的公共超类作为寄存器的类型。所以如果这两种类型完全无关,java.lang.Object将成为超类。

,你能做些什么来调试这个问题是运行baksmali使用--register-info ARGS,DEST,FULLMERGE选项(也--code-offsets,所以你可以很容易地找到0xAE0),然后从0xAE0向后看,看看V5的类型设置为一个目的。

+0

如果你可以在我的回答中提到的注册信息发布baksmali反汇编,我可以看看。或者,而是让你看看什么:) – JesusFreke

+0

非常感谢帮助,@JesusFreke。实际上,我有一个控制流分析来帮助突出显示这些事情,因此了解代码偏移项是我真正需要的! (我会研究这个并回复你..!) –

+0

非字符串对象来自异常处理程序,它在v5中存储异常,然后最终到达AE0而不重新分配v5。查看我的帖子了解详情。 – Antimony

2

我想补充一下我的答案,因为其他人对捐献他们的时间非常有帮助,以回答一个棘手的问题,可能不会推广太多!

由于@Antimony指出的,在我的代码开始在异常处理程序,存储例外v5控制路径(导致v5是一个Object),然后goto“DA点异常处理程序。该异常处理程序导致v5被用作字符串,导致验证错误。

在应用程序的原始代码中,那个goto的目标上的唯一一个很薄的指令是return-void指令。正因为如此,Dalvik验证程序并没有将路径传播到异常处理程序。

不幸的是,当我重写这个应用程序时,它导致该异常处理程序的目标不仅仅包含这个指令,通过该块进行验证原因并进入捕获的异常处理程序。特别是,在return-void之前,我插入了一个调用Logger.logMethodExit,验证者然后假定可以将控制权转移回异常处理程序(在这种情况下为:BF0),并最终转移到v5被用作字符串的地方。在原来的应用程序中,它被杀死了(在gen/kill数据流意义上)。但重写后,我将这个额外的调用包括在内,以打破数据流不变式... Crud。

我想我知道如何在我的实现中解决这个问题,但确实很痛苦!

更一般的教训在这里:

  • 验证误差补偿其实只是2 *字节码

  • 与JVM字节码内指数的Dalvik字节码考虑操作码的一个子集不抛出,包括return 。这将影响到数据流分析

  • 精确的参考意味着什么被约束对象的一个​​基本块特定的细化,并在另一个的Object(虽然这个错误似乎有点深奥我..)

  • 当你重写字节码时,你需要知道你隐式解决的gen/kill集合,特别是return-*指令会立即杀死所有东西,而跳到try..内的基本块的开头将继续保留那些事情生活。

+0

有趣的是,你遇到了这个角落的情况。在Java字节码中,每条指令都被认为可能抛出,包括返回。但是,Dalvik试图变得更聪明,并声明只有一部分操作码可以抛出。我不记得名单,但显然,回报被认为是不投掷。 – Antimony

+0

你碰巧记得你是怎么想出来的?只是通过阅读编译器源代码?我已经阅读了一些,但是没有达到你显然有这样的程度,所以你的输入是绝对有用的:-)否则我不会这样想。 –

+2

在尝试模仿寄存器类型推断逻辑的同时,通过dalvik字节码验证器读取数据时,我遇到了这种情况(只有某些指令可以抛出),同时实现了baksmali的解码功能。参见例如https://android.googlesource.com/platform/dalvik/+/kitkat-release/opcode-gen/bytecode.txt中的“throw”指令标志 – JesusFreke