环境:android 4.4.3
本人移动安全小菜一枚,最近在研究indroid的源码,发现里面原来是在dalvik里面插桩,所以就先把dalvik是如何解析指令的过程研究了一番。给人的感觉是这块要是研究透了,万能脱壳真的是可以的。因为无论dex怎么隐藏,最终还是要交给dalvik去解释执行。
Smali汇编一共有200多条不同的指令,每条指令对应的操作码对应源码在dalvik/libdex/DexOpcodes.h文件中,操作码用1个字节表示。smali汇编是不定长指令,而且对应的指令是基于“寄存器”的(每个寄存器都是32位)。(详细的dalvik指令大家看非虫大大的那本书,或者看谷歌提供的资料)。
下面主要拿c语言版本的dalvik解释器进行分析。源码的路径在dalvik/vm/mterp/out/InterpC-portable.cpp。
dalvik真正对指令解析是从dvmInterpretPortable(Thread* self)函数开始的。
下面对几个最重要的变量进行说明:
curMethod*:当前执行的方法。对应的结构体源码在dalvik/vm/oo/Object.h。
fp:这就是dalvik用来模拟寄存器的指针。
pc:pc就是用来取dalvik指令的。
inst:当前执行的指令。通过fetch(0)这个宏来获取的,注意它是2个字节大小。如果大于2个字节,就必须再次通过fetch(_offset)来取源操作数或者目的操作数,fetch(_offset)定义为:#define FETCH(_offset) (pc[(_offset)])。每一条指令执行完成之后会通过FINSH(_Offset)这个宏来调节pc,使其指向下一条指令的地址。
retval:与函数返回值有关。
methodClassDex:看赋值过程就能明白。从当前执行的方法找到当前方法所在的类,从所在的类找到它所属的dex。
DEFINE_GOTO_TABLE(handlerTable);这个宏是非常重要的。这个宏也定义在dalvik/libdex/DexOpcodes.h,
经过宏替换,相当于定义了一个数组名为handlerTable的数组,里面的每一个数据也是宏,宏的定义就在dalvik/vm/mterp/out/InterpC-portable.cpp文件中。
# define H(_op) &&op_##_op。##就是进行参数连接。最终经过宏展开后,该数组的第一个数据为:&&op_OP_NOP。其余的一样进行替换。为啥里面的数据都是void*类型,实际是因为&& 之后op_OP_NOP是标号。
下面额外说明一个关于标号的用法。因为dalvik解释器每一条指令都是goto 标号去执行的。
这个程序就会输出2222222222222 这一行。
接着对dvmInterpretPortable(Thread* self) 函数继续分析:
FINISH(0)就是对第一条指令进行解析。
ADJUST_PC(_offset)这个宏的作用实际上就是用来调节pc的位置,使pc每次指向我们要执行指令的位置。FETCH(0)前面已经说过,获得这条指令的前两个字节赋给inst。
下面看goto *handlerTable[INST_INST(inst)]这句. INST_INST(inst)也是一个宏。宏定义:#define INST_INST(_inst) ((_inst) & 0xff) 可知,这是用来取操作码的。
而操作码的数值实际上跟前面说的handlerTable索引是一一对应的。假设此时操作码值为0,根据前面提到的标号用法,此时goto *handlerTable[INST_INST(inst)]就是goto op_OP_NOP。
接着FINISH(0)往下看,下面就是每一条指令如何执行的宏。比如第一条指令为
HANDLE_OPCODE(OP_NOP) 同样也是一个宏。宏定义为:
#define HANDLE_OPCODE(_op) op_##_op:
进行替换就成了op_OP_NOP:。这就跟前面的goto连上了。
当然NOP是什么都不做,直接FINISH(1)调节pc的位置获取下一条指令了。
每一条指令执行完成之后都会调用FINISH这个宏。通过前面执行完的指令大小,设置对应的FINISH宏参数,调节pc位置。
这个就是一个最简单的指令分析了,还有一些细节性的东西没有说清楚,还请见谅。对于其它复杂的指令,大家也可以对着分析,就会发现更细致的东西,比如fp指针是如何模拟寄存器的等等。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!