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随便打开一段代码,根据日志输出,即可定位到日志的类。
现在很多的应用都对自己的APK做了反回编译处理,也就是修改完代码后使用回编译工具回编译时,出现各种错误。例如:对微信做回编译时,资源文件就有问题,实际上是它的资源文件解压库的问题(参见微信开源的资源压缩库[减小APK的体积])。
针对于上面的问题,我自己写了一个工具,大概的原理就是:只处理了APK里面的DEX文件和签名文件。
现在的APK基本上都有采用拆分DEX技术(由于Dex的设计缺陷,一个DEX最多只能保存的类大小为65535),所以反编译后会对应多个smali文件夹。
在上一步中,我已经成功将APK回编译,但是在安装成功后,当我登录时,出现了如下对话框(在线的签名校验)。
有几个思路:
最后成功找到加密前的数据,在登录时一共获取了43个字段,其中包含了用户名,密码和签名信息等。
其中有21个字段是从MomoKit.a()中获取到的,其中就包含了签名信息。
在这个方法中,Codec.Des()解密出“apksign”字符串,Momokit.k()获取出正在的签名信息。
正常APK的签名:“apksign” -> “4f3a531caff3e37c278659cc78bfaecc”,所以我直接写死它。
最后再重新回编译和重新签名,能够成功安装,并且能成功登陆。
这里需要注意:获取签名信息的类和打印日志的类不在同一个DEX文件中,所以需要在修改过日志的APK的基础上进行修改,我写的工具一开始没有考虑过这个问题,所以我又重新进行了修改,现在可以一键回编译所有的DEX文件了。
在分析协议的时候,我找到了关键的加解密函数,于是打算通过Hook框架Hook关键的函数并输出它的结果,但是它对Xposed Hook框架(基于注入的方式,只能针对于Java层)有检查,Hook不成功,然后改为用Frida Hook框架(基于调试的方式,支持C++和Java层,并且不仅适用于移动端),还是不成功,最后我只能拿出大杀器(IDA)来找它反调试的地方,一开始由于环境的问题,光是基于IDA调试的环境我都搭建了很久。
在我成功附加上其进程后3秒不到,提示警告接着调试断开,在网上找到了相关的介绍;(网上文章:Android Native Crash [收集 一个异常的处理接口]) 在MoMo C++的代码中大概有100多个地方在检查自己是否有被其他进程附加,如果有就产生异常。
这样的代码一共在7个so文件中有上面的代码:libsmses.so,libsevenz.so,libmkjni.so,libmjni.so,libcoded_jni.so,libcoded.so,libbsdiff.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功能)。
Program dump出so(在Dump之前一定要先保存数据)
上面的内容就是今天要分享的,由于我是第一次写文章,写的不好,希望大家见谅,并且对平台的排版工具不太熟悉,暂时就只能这样了,我这篇文章里面涉及的知识比较多,可能对初学者来说有点难度,不过没关系,我会在后面的文章中,一点一点的剖析这篇文章里面的知识。感兴趣的同学可以关注我的公众号,欢迎来扰。