Android逆向实战过掉MoMo签名验证和反调试

大家好,我是老袁,一名逆向分析人员,现主要研究Android平台逆向;今天给大家分享一篇,如何过掉MoMo的签名验证和C++层反调试。大神勿喷。

一、使用环境及工具

1.系统:windwos 10(x64)
2.设备:Google nexus 5真机。
3.反编译工具:

    Android Studio 3.2   开发和调试smali代码
    IDA pro 7.0          分析elf文件
    jeb                  Apk反编译集成工具(代码质量高)
    jadx‐gui             dex直接转换成java代码(代码质量高)
    ApkTools             集成了baksmali和smali
    ApkDB                将smali代码反编译成dex文件,并重新签名APK(自己编写)

4.抓包工具:Charles
5.分析的样本:com.immomo.momo_8.9.8.apk

二、开启日志信息

一般大型的应用都会自定义自己的日志系统,当发布时只需要去掉打印日志的标志即可,所以我一开始就是去找它的日志接口。通过 jadx打开APk随便打开一段代码,根据日志输出,即可定位到日志的类。
e1e8fdafb47c43a290e8a2a8ff87c357.png
9bb798d3c5de45c3a009eeb56d44455e.png

直接修改上面代码对应的smali代码:

image.png
image.png

三、回编译

现在很多的应用都对自己的APK做了反回编译处理,也就是修改完代码后使用回编译工具回编译时,出现各种错误。例如:对微信做回编译时,资源文件就有问题,实际上是它的资源文件解压库的问题(参见微信开源的资源压缩库[减小APK的体积])。

针对于上面的问题,我自己写了一个工具,大概的原理就是:只处理了APK里面的DEX文件和签名文件。
现在的APK基本上都有采用拆分DEX技术(由于Dex的设计缺陷,一个DEX最多只能保存的类大小为65535),所以反编译后会对应多个smali文件夹。
image.png

该回编译工具的大致实现思路:
  1. 使用smali.jar 针对于某一个smali文件,回编译成功后生成与smali文件夹名字一致的DEX文件。
  2. 使用压缩算法读写APK文件,替换对应的DEX文件。
  3. 在替换完成后,删除压缩包里面的签名文件(META-INF)
  4. 使用签名工具,对新的APK重新签名
    0eecc2e5a09e4e5f9d1f53e8d674aecf.png

四、过签名验证

在上一步中,我已经成功将APK回编译,但是在安装成功后,当我登录时,出现了如下对话框(在线的签名校验)。
5a07c8b3b0b54823ab9451b6d234785b.png

有了这个提示:怎么分析呢?

有几个思路:

  1. 在代码中搜索关键字符串。(可惜找不到,后来验证为从服务端传回来的)
  2. 搜索Java层获取签名信息的方法。(同样找不到,是在C++中获取的)
  3. 分析对话框(每个对话框最后显示都要调用show方法)
    我在跟踪show中,找到了发送消息的函数,从发送的消息中获取到,其中有一个字段中保存了APK的签名信息。
    image.png
    断在弹出对话框代码处
    image.png

根据栈回溯,跟踪找到了向服务器发送的数据
image.png

最后成功找到加密前的数据,在登录时一共获取了43个字段,其中包含了用户名,密码和签名信息等。
image.png
其中有21个字段是从MomoKit.a()中获取到的,其中就包含了签名信息。
image.png
在这个方法中,Codec.Des()解密出“apksign”字符串,Momokit.k()获取出正在的签名信息。
正常APK的签名:“apksign” -> “4f3a531caff3e37c278659cc78bfaecc”,所以我直接写死它。
image.png

最后再重新回编译和重新签名,能够成功安装,并且能成功登陆。

这里需要注意:获取签名信息的类和打印日志的类不在同一个DEX文件中,所以需要在修改过日志的APK的基础上进行修改,我写的工具一开始没有考虑过这个问题,所以我又重新进行了修改,现在可以一键回编译所有的DEX文件了。

五、过C++层的反调试

在分析协议的时候,我找到了关键的加解密函数,于是打算通过Hook框架Hook关键的函数并输出它的结果,但是它对Xposed Hook框架(基于注入的方式,只能针对于Java层)有检查,Hook不成功,然后改为用Frida Hook框架(基于调试的方式,支持C++和Java层,并且不仅适用于移动端),还是不成功,最后我只能拿出大杀器(IDA)来找它反调试的地方,一开始由于环境的问题,光是基于IDA调试的环境我都搭建了很久。

在我成功附加上其进程后3秒不到,提示警告接着调试断开,在网上找到了相关的介绍;(网上文章:Android Native Crash [收集 一个异常的处理接口]) 在MoMo C++的代码中大概有100多个地方在检查自己是否有被其他进程附加,如果有就产生异常。

代码如下:
在这里插入图片描述

这样的代码一共在7个so文件中有上面的代码:libsmses.solibsevenz.solibmkjni.solibmjni.so,libcoded_jni.so,libcoded.solibbsdiff.so

一开始我是手动修改了几处fopen后面的跳转(if(v9)),让获取状态的代码都不执行,但是编译后发现程序加载不了图片(当然替换so文件,也可以用我写的那个工具,只是需要手动的打开APK包替换掉目标so文件,然后回编译重新签名)。

最后我还是选择只修改上图中“JUMPOUT”也就是“pop {R0-R2,pc}”将这两个字节的代码直接nop掉,但是这样的代码一共有大概100多处,光修改就要差不多两个小时,而且还不确定还有没有其他问题,所以我选择花两个小时学IDA的IDAPython脚本编写。

脚本代码如下:(IDA默认安装python 2.7)

import idautils

def Main():
    pattern = '0x07 0xBD'        #pop {R0-R2,pc}
    addr    = MinEA()
    first   = False
    index   = 0
    firstaddr = idc.FindBinary(addr, SEARCH_NEXT, pattern)
    for x in xrange(0,30):
        addr = idc.FindBinary(addr, SEARCH_NEXT, pattern)
        if addr == -1:
            continue
        if first == True:
            if firstaddr == addr:
                break
        print '------------------- %d --------------------------'%x
        print 'addr:%s' % hex(addr)
        print 'firstaddr:%s'%hex(firstaddr)

        if addr != idc.BADADDR:
            find_start = addr-0x100
            find_end   = addr
            print "funcstart:%s funcend:%s"%(find_start,find_end)
            cur_first      = False
            cur_first_addr = idc.FindText(find_start, SEARCH_DOWN, 0, 0, 'TracerPid')
            while find_start < find_end:
                cur_addr = idc.FindText(find_start, SEARCH_DOWN, 0, 0, 'TracerPid')
                if cur_first == True:
                    if cur_addr == cur_first_addr:
                        break
                if cur_addr == idc.BADADDR:
                    break
                else:
                    print "  TracerPid %s %s"%(hex(cur_addr), idc.GetDisasm(cur_addr))
                    print "  nop_cpde:%s %s"% (hex(addr),idc.GetDisasm(addr))

                    idc.PatchByte(addr,0x00)     # 因为nop只需要1个字节,所以另一个用BF
                    idc.PatchByte(addr+1,0xBF)
                    print "  ----------"
                    print "new_code:%s %s"%(hex(addr),idc.GetDisasm(addr))
                    index += 1
                cur_first_addr = cur_addr
                cur_first      = True
                cur_addr       = idc.NextHead(find_start)
        first     = True
        firstaddr = addr
    print "find index:%d"%index

Main()

上面的代码能够成功的将一个so文件中所有符合要求的“pop {R0-R2,pc}”nop掉。

当修改完成后,先保存,再dump出so文件。(这是IDA的一个特点,在界面上修改的数据,只是修改了它为真实文件创建的数据库文件而已)。

这里顺便介绍一下IDA的两个插件:KeyPatch(能够直接输入对应的汇编代码),Patch Program(dump功能)。

KeyPatch修改代码

在这里插入图片描述

Program dump出so(在Dump之前一定要先保存数据)

6132e27410504a24912a547613365092.png
image.png

最终成功可以调试C++的代码了,下面是跟踪加密算法的位置
在这里插入图片描述

六、总结

上面的内容就是今天要分享的,由于我是第一次写文章,写的不好,希望大家见谅,并且对平台的排版工具不太熟悉,暂时就只能这样了,我这篇文章里面涉及的知识比较多,可能对初学者来说有点难度,不过没关系,我会在后面的文章中,一点一点的剖析这篇文章里面的知识。感兴趣的同学可以关注我的公众号,欢迎来扰。
在这里插入图片描述

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值