发新帖

Android逆向工程——认识Dalvik opcodes

[复制链接]
39969 7
本帖最后由 Mitjavaz 于 2015-8-11 10:37 编辑

我是一名新人,这两天也发了两个很基础的东西,令大家见笑了。

我的风格就是把我认为的,我所学会的东西,迫不及待地写出来,目的是既能给需要的人带来参考,也能给自己纠正错误理解。

所以呢,今天,我依然是在写基础的东西。

因为我是新人,所以每天学的进度有限,理解能力也有限,所以后期可能会写这样的体会心得的速度会放慢。

毕竟后期慢慢深入进去,就是各种算法,各种机制,各种壳,各种注入之类的东西了。

今天,给大家带来的是Dalvik opcodes方面的东西。

相信大家经常会在反编译中看到,有时候修改个if语句,经常被if-ne和if-nez等等这样的代码整迷糊。

今天的目的就是:认识Dalvik opcodes

并且通过这个目的,为后期的:注入代码等技术手段打下扎实的基础。

而本次就直接沿用上一篇:Android逆向工程——程序修改 所提到的游戏作为实操对象。

在上一篇文章中,我们找到并定位到了“狂热期结束”这个关键词,然后通过Android Killer定位到了关键词所在的类以及其所在的方法。

然后通过修改IF条件语句中的判断条件,实现了延长功能,或者是说将功能的时候推延了。

但是这依然不够完美,因为功能结束之后,又是一段等待期,相信很多人会厌烦这段时间,感觉很折磨人。

那么,这次我就尝试着做永久性的修改操作。

我们再看下代码:

我在此以每一个IF语句作为一个模块来拆分,将其分成了三个模块。

第一个IF语句是一个模块,它只有三个执行语句,分别是:int i2 = 1 + B;  、B = i2;  、和第二个IF语句。

在程序设计上而言,IF语句就是一条判断语句,也只是一条执行语句,无论这条IF语句内包含了多少条执行语句,它都如简单的一条赋值语句一样的存在。

第二个IF语句是一个模块,它有八条执行语句。

第三个IF语句是第三个模块,它只有一条执行语句。

从程序设计上,我们就大致把它的执行流程看了一下。

然后在上一篇:Android逆向工程——程序修改 此文中,我也大概解读了一下第一条和第二条IF语句主要是为了达到什么目的。

第一条IF语句(if (A == 1)):判断功能是否开启,即:游戏当前进行时的状态是否处于狂热期。

第二条IF语句(if (i2 >= 1200)):判断狂热期时间,即:游戏是否该关闭狂热状态,结束狂热期。

由于我们在上一篇文章中,改动了第二条IF语句的条件模块中的常量“1200”,那么,我们换一个思维考虑。

既然if (i2 >= 1200)是判断狂热期的结束,那它对应的所执行的语句,就是执行狂热期结束这一操作。

而我们此次修改的目的是什么?

实现永久狂热状态。

既然是永久狂热状态,那我们是不是就能出这样一个预想,把这条语句整个删除不要,它是不是就没有结束狂热期这一个操作,也就实现了永久狂热期这一个目的。

接着,我们再看下JAVA源码。

既然要做删除,就得知道从哪删到哪。

那么这里,我们在分析过功能之后,也都清楚,关键是第二条IF语句,它掌控着关闭状态的按钮。

删除的方式有:

1、针对性删除:也就是将关闭状态的执行语句删除。

注:这样的删除有一个好处,就是删除的代码不多,但是不好的地方就在于在并不知晓具体是哪一句的时候,操作过程显得复杂繁琐,现在这里只有八条执行语句,假如最后一条执行语句才是关闭状态的执行语句,那么,就需要测试八次。

所以,这里采用要说到的第二种方式。

2、划区间删除:也就是将内嵌式的语句划分后删除。

注:这种方式相对较好,但是不好的地方就是需要多分析,特别是在需要补充返回值的情况下。

原始的Dalvik opcodes:

sget v0, Lc/j;->A:I


    if-ne v0, v5, :cond_3


    sget v0, Lc/j;->B:I


    add-int/lit8 v0, v0, 0x1


    sput v0, Lc/j;->B:I


    const/16 v2, 0x4b0


    if-lt v0, v2, :cond_3


    sput v1, Lc/j;->A:I


    sput v1, Lc/j;->B:I


    invoke-static {}, Lb/f;->a()Lb/f;


    invoke-static {}, Lb/f;->h()I


    move-result v0


    if-nez v0, :cond_2


    move v0, v5


    :cond_2

    iget v2, p0, Lc/j;->n:I


    mul-int/lit8 v2, v2, 0x64


    div-int v0, v2, v0


    const/16 v2, 0x7d0


    const/16 v4, 0x23


    move v3, v1


    invoke-static/range {v0 .. v5}, Lb/n;->a(IIIIIZ)I


    move-result v6


    move v0, v1


    move v2, v7


    move v3, v1


    move v4, v8


    invoke-static/range {v0 .. v5}, Lb/n;->a(IIIIIZ)I


    move-result v0


    add-int/2addr v0, v6


    mul-int/lit8 v0, v0, 0x64


    sput v0, Lc/j;->x:I


    iget-object v0, p0, Lc/j;->au:Lf/a;


    iget-object v0, v0, Lf/a;->d:La/h;


    const-string v2, "\u72c2\u70ed\u671f\u7ed3\u675f\u3002"


    invoke-virtual {v0, v2, v8}, La/h;->a(Ljava/lang/String;I)V


    iget-object v0, p0, Lc/j;->au:Lf/a;


    const/4 v2, 0x3



    invoke-virtual {v0, v2}, Lf/a;->d(I)V

删除的Dalvik opcodes:

    const/16 v2, 0x4b0


    if-lt v0, v2, :cond_3


    sput v1, Lc/j;->A:I


    sput v1, Lc/j;->B:I


    invoke-static {}, Lb/f;->a()Lb/f;


    invoke-static {}, Lb/f;->h()I


    move-result v0


    if-nez v0, :cond_2


    move v0, v5


    :cond_2

    iget v2, p0, Lc/j;->n:I


    mul-int/lit8 v2, v2, 0x64


    div-int v0, v2, v0


    const/16 v2, 0x7d0


    const/16 v4, 0x23


    move v3, v1


    invoke-static/range {v0 .. v5}, Lb/n;->a(IIIIIZ)I


    move-result v6


    move v0, v1


    move v2, v7


    move v3, v1


    move v4, v8


    invoke-static/range {v0 .. v5}, Lb/n;->a(IIIIIZ)I


    move-result v0


    add-int/2addr v0, v6


    mul-int/lit8 v0, v0, 0x64


    sput v0, Lc/j;->x:I


    iget-object v0, p0, Lc/j;->au:Lf/a;


    iget-object v0, v0, Lf/a;->d:La/h;


    const-string v2, "\u72c2\u70ed\u671f\u7ed3\u675f\u3002"


    invoke-virtual {v0, v2, v8}, La/h;->a(Ljava/lang/String;I)V


    iget-object v0, p0, Lc/j;->au:Lf/a;


    const/4 v2, 0x3


    invoke-virtual {v0, v2}, Lf/a;->d(I)V

最后保留的Dalvik opcodes:


我们看这最后保留的代码:

    sget v0, Lc/j;->A:I         //【sget vx, 字段ID 】这就是这一行的定式,表示:通过字段ID得到一个数据,将数据转交给vx保存。理解就是这么理解,但是官方化就不这么说。


    if-ne v0, v5, :cond_3      //【if-ne vx,vy 目标】这就是if-ne的定式,表示:vx 不等于vy ,那么就跳转到 目标 。也就是说,假如这里的v0 不等于 v5 那么程序就不顺势往下执行,而是跳跃到:cond_3这个标记所在的地方。


    sget v0, Lc/j;->B:I        //同第一条分析。


    add-int/lit8 v0, v0, 0x1  //加法运算:vx = vy + lit8  ,书写规范:add-int/lit8 vx,vy,lit8。


    sput v0, Lc/j;->B:I       //注意,这里的意思是,将vx的数据保存到字段ID对应的存储地方。


    if (A == 1)

    {

      int i2 = 1 + B;

      B = i2;


显然,一目了然了,我们保留的Dalvik opcodes和JAVA源码是一致的。

其实,这里有一个难点,就是Dalvik opcodes太多种类了,我们对这些种类都没多少认识。

我在这里找了相关资料,给大家提供一下:

PDF版:http://pan.baidu.com/s/14pv46

网页:http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html


好了,归纳总结一下:

做安卓逆向,就得学会跟Dalvik虚拟机打交道,而能与Dalvik虚拟机沟通的方式,就是Dalvik opcodes。


提升的方式:

汇编编程原理。

我不知道大家是怎么看待的,我是在理解的时候,感觉到这个Dalvik opcodes,其实跟汇编的原理差不多的,特别是在使用寄存器赋值,用寄存器传递值量的时候,除了换了一些字节码,基本上就跟汇编差不多。

这里不要求大家都能懂汇编,但是至少能知道汇编的编程方式是怎么进行的。


希望大家能对这种编码规则看得懂,多少要理解,这对将来更深入学习,能打下基础。


好了,谢谢大家观看,若有什么不足,写得不对的,还望能指出。


这里留下一个我没写的,但是大家可以尝试去做的,就是将删除的那部分Dalvik opcodes,按JAVA源码显示出来的代码,将二者代码一一对应起来。


致谢:逆向未来技术社区

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
已有2人评分 威望 NB 贡献 荣获致谢 理由
听鬼哥说故事 + 1 + 3 + 1 + 1 赞一个!
peterdocter + 3 + 1 + 1 赞一个!

查看全部评分 总评分: 威望 +1  NB +6  贡献 +2  荣获致谢 +2 

举报 使用道具

回复

精彩评论7

听鬼哥说故事    发表于 2015-8-11 13:18:10 | 显示全部楼层
很详细的基础资料,这两天发了不少帖子,给个精华,多总结,多发贴,多讨论,这样技术才会慢慢有提升,赞楼主~~

举报 使用道具

回复 支持 反对
水波摇曳    发表于 2015-8-11 13:21:03 | 显示全部楼层
坚持!一定会学到,谢谢楼主分享
赞一个!

举报 使用道具

回复 支持 反对
花墨    发表于 2015-8-11 13:37:43 | 显示全部楼层
感谢楼主的分享!希望继续出精品!

举报 使用道具

回复 支持 反对
永恒丶    发表于 2015-8-11 13:44:59 | 显示全部楼层
谢谢楼主分享!大赞!!!

举报 使用道具

回复 支持 反对
peterdocter    发表于 2015-8-11 14:09:58 | 显示全部楼层
越来越牛!

举报 使用道具

回复
xin    发表于 2015-8-11 23:07:30 来自手机  | 显示全部楼层
支持续集

举报 使用道具

回复
大兵beyond    发表于 2015-8-12 18:10:59 | 显示全部楼层
加油。

举报 使用道具

回复
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表