Dalvik 字节码检测 - 寄存器类型合并
Dalvik bytecode instrumentation - register type merging
我正在使用 dexlib2 进行某种 dalvik 字节码检测。
但是,还有几个遗留问题。
似乎在goto指令之后发生的寄存器类型合并
并以某种方式捕获块,更准确地说是在相应的标签处
派生出意外的寄存器类型,这又会破坏检测代码。
插入的指令如下所示:
move(-wide,-object,/16,/from16) vNew, v0
const-string v0, "some string"
invoke-static, {v0}, LPathToSomeClass;->SomeMethod(Ljava/lang/String;)V
move(..) v0, vNew
因此,v0 用于保存静态函数调用的一些参数,而
vNew 是一个新的(本地)寄存器,用于存储和恢复 v0 的原始内容。提前导出v0的寄存器类型,以便导出正确
移动指令,即 move-wide、move 或 move-object。但是,当此代码包含在某些 try 块中时,检测会中断。的输出
baksmali (baksmali d -b "" --register-info ALL,FULLMERGE --offsets )
显示 const-string 指令后的 v0 类型(即 Reference,L/java/lang/String)被视为发生在相应 catch 块标签处的合并过程的输入。假设插入代码之前的类型是Reference,[I(int array)结果
类型现在是 Reference,L/java/lang/Object(这会产生验证错误),尽管最后的移动指令恢复了原始寄存器类型。
现在回答我的问题:
1) 这种合并实际发生在什么时候?
2) 为什么合并过程在const-string 指令之后考虑v0 的类型?是否考虑每条指令修改任何寄存器的类型?
3) 这个问题是否只与 try-catch 块有关?
4) try-catch块在这件事上有什么限制?
5) 除了为每个代码构造一个自己的方法以进行无参数注入之外,是否有解决此问题的方法?那么是否可以使用额外的寄存器来解决这个问题呢?
6) 我可以使用 dexlib2 try-catch 块进行检测并确定它们包含的指令集吗?
7) 是否有 notes/literature 讨论这个问题,例如合并程序和相关技术细节,例如进一步 limitations/restrictions
对于仪器?
我非常感谢在这件事上的任何帮助。提前致谢!
在 catch 块的开头合并寄存器时,try 块中的每条指令都有一个传入边沿可以抛出。只有某些指令可以抛出 - 由 CAN_THROW 操作码标志决定。
在您的特定示例中,const-string 指令之后的 invoke-static 指令可以抛出异常,因此从该指令之前到 catch 块的开头有一条边。
如果后退一步,执行可以从 try 块中任何可以抛出异常的指令跳转到 catch 块的开头。因此,catch 块中的代码必须准备好让寄存器处于与寄存器内容一致的状态,就在任何可能抛出的指令之前。
因此,例如,如果有一种可能 "jump" 从 try 块到寄存器包含原始 int 类型的 catch 块,以及另一种可能的跳转,其中它包含一个对象,则该寄存器是考虑 "conflicted",因为寄存器在代码中的那个点可能包含任何一种类型,并且这两种类型彼此不兼容。例如。原始 int 永远不能传递给需要引用类型的东西,反之亦然。并且字节码中没有用于静态寄存器类型检查的机制。
一种可能的解决方案可能是在插入检测的位置拆分 try 块,这样检测本身就不会被 try 块覆盖,但原始代码的 "sides" 都被覆盖了。请记住,在字节码中,同一个 catch 块可以被多个 try 块使用,因此您可以将原始 try 块拆分为两个,并且都引用原始 catch 块。
否则,您将不得不想出一些方法来适当地管理寄存器来避免这个问题。
至于 6),请参阅 MethodImplementation.getTryBlocks(),它将为您提供该方法中的 try 块列表。每个 try 块指定它从哪里开始,它覆盖了多少指令,以及与之关联的所有 catch 块(不同的 catch 块用于不同的异常)。
我正在使用 dexlib2 进行某种 dalvik 字节码检测。 但是,还有几个遗留问题。 似乎在goto指令之后发生的寄存器类型合并 并以某种方式捕获块,更准确地说是在相应的标签处 派生出意外的寄存器类型,这又会破坏检测代码。
插入的指令如下所示:
move(-wide,-object,/16,/from16) vNew, v0
const-string v0, "some string"
invoke-static, {v0}, LPathToSomeClass;->SomeMethod(Ljava/lang/String;)V
move(..) v0, vNew
因此,v0 用于保存静态函数调用的一些参数,而 vNew 是一个新的(本地)寄存器,用于存储和恢复 v0 的原始内容。提前导出v0的寄存器类型,以便导出正确 移动指令,即 move-wide、move 或 move-object。但是,当此代码包含在某些 try 块中时,检测会中断。的输出 baksmali (baksmali d -b "" --register-info ALL,FULLMERGE --offsets ) 显示 const-string 指令后的 v0 类型(即 Reference,L/java/lang/String)被视为发生在相应 catch 块标签处的合并过程的输入。假设插入代码之前的类型是Reference,[I(int array)结果 类型现在是 Reference,L/java/lang/Object(这会产生验证错误),尽管最后的移动指令恢复了原始寄存器类型。
现在回答我的问题:
1) 这种合并实际发生在什么时候?
2) 为什么合并过程在const-string 指令之后考虑v0 的类型?是否考虑每条指令修改任何寄存器的类型?
3) 这个问题是否只与 try-catch 块有关?
4) try-catch块在这件事上有什么限制?
5) 除了为每个代码构造一个自己的方法以进行无参数注入之外,是否有解决此问题的方法?那么是否可以使用额外的寄存器来解决这个问题呢?
6) 我可以使用 dexlib2 try-catch 块进行检测并确定它们包含的指令集吗?
7) 是否有 notes/literature 讨论这个问题,例如合并程序和相关技术细节,例如进一步 limitations/restrictions 对于仪器?
我非常感谢在这件事上的任何帮助。提前致谢!
在 catch 块的开头合并寄存器时,try 块中的每条指令都有一个传入边沿可以抛出。只有某些指令可以抛出 - 由 CAN_THROW 操作码标志决定。
在您的特定示例中,const-string 指令之后的 invoke-static 指令可以抛出异常,因此从该指令之前到 catch 块的开头有一条边。
如果后退一步,执行可以从 try 块中任何可以抛出异常的指令跳转到 catch 块的开头。因此,catch 块中的代码必须准备好让寄存器处于与寄存器内容一致的状态,就在任何可能抛出的指令之前。
因此,例如,如果有一种可能 "jump" 从 try 块到寄存器包含原始 int 类型的 catch 块,以及另一种可能的跳转,其中它包含一个对象,则该寄存器是考虑 "conflicted",因为寄存器在代码中的那个点可能包含任何一种类型,并且这两种类型彼此不兼容。例如。原始 int 永远不能传递给需要引用类型的东西,反之亦然。并且字节码中没有用于静态寄存器类型检查的机制。
一种可能的解决方案可能是在插入检测的位置拆分 try 块,这样检测本身就不会被 try 块覆盖,但原始代码的 "sides" 都被覆盖了。请记住,在字节码中,同一个 catch 块可以被多个 try 块使用,因此您可以将原始 try 块拆分为两个,并且都引用原始 catch 块。
否则,您将不得不想出一些方法来适当地管理寄存器来避免这个问题。
至于 6),请参阅 MethodImplementation.getTryBlocks(),它将为您提供该方法中的 try 块列表。每个 try 块指定它从哪里开始,它覆盖了多少指令,以及与之关联的所有 catch 块(不同的 catch 块用于不同的异常)。