再战野火IM

发布时间 2023-10-17 18:59:37作者: WXjzc

起因是在盘古石决赛时,有一个手机取证的APP是基于野火IM进行的开发。当时未按照正常方式解出,没找出数据库密钥。
后续又在以前的某一次全国比武中发现了野火IM的考点,直接问密码如何来。
当时使用frida来做,但是发现要处理多进程,就暂时搁置了。
近期看到弘连的取证实录中详细介绍了如何获取密钥,参考学习。
不得不说弘连在技术分享这块,做的是真的好,不像其他厂商喜欢藏着掖着。

一开始复盘时,考虑到野火IM是开源产品,因此在GitHub中查找,但并未找到其so的源码。这次参考实录中的内容,得知源码的开源地址为野火开源协议栈
因此先来源码分析,后面用frida来做。不过通过正面的源码分析后,已经可以知道密钥的生成方式,frida那里是用于往后遇到其他闭源产品时,能通过frida去处理。

源码

克隆源码仓库并阅读,在proto目录下可以看到MessageDB.h,往src目录翻,可以看到MessageDB.cc,在这个代码文件中,可以看到生成sql语句的代码
截图
db则由DB2这个类创建
截图
DB2::Open中传入数据库密钥
截图
截图
搜索调用,能发现是在business.cc中打开了数据库
截图
setAuthInfo函数中,调用decodeToken函数来解密token并生成密钥
截图
decodeToken中,先对token解base64,随后调用decrypt_data进行解密,该函数的定义在libemqtt.cc
截图
截图
很明显用了aes来解密,key是常量0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F
截图
截图
又可以看到iv的生成方式,由于解密传入的uiKeyLen就是key的长度,根据生成方式,iv与key相同
截图
截图
接下来需要获取token,逆一下apk,很容易就能找到位置,在app的data目录下的shared_prefs/config.xml
截图
截图
得到token为800MdqKouy0Fb557abV+xAP112wQHm22bTVrr+VwQsh555Q+OCKgUQanxCX2HgAUbztEbl+RduchQfy8Msi14v8+6BATZWCIBmo3W9av2u8v4+ovfLh0mcKSDZomvFzUhYWt53KHWXp4bWmdVtG3kCKGOtsKmZDuAA3rnVy5pUA=
cyberchef直接解,按照生成方式,解密后的内容以|区分,密钥为最后一个,即62a9e3e2-7ffc-42e1-aa65-d6046e009d6b
截图
尝试解密数据库,DB Browser for SQLite使用SQLCipher 4的默认参数即可解密
截图
截图

逆向

读取idtoken传入connect,读取的配置文件是sharedPreferences目录下的config.xml
截图
不断跟进connect方法,在cn.wildfirechat.remote.ChatManager类中
截图
cn.wildfirechat.client.IRemoteClient接口中
截图
它的内部类Stub的内部类Proxy中有实现
截图
跟踪Stub,最终将idtoken传入initProto方法
截图
将token传入了setAuthInfo,是native层的方法,这就和之前源码分析那里对上了
截图
截图
找到加载的so,通过查看exports中是否存在setAuthInfo的方式确定待分析的so为libabab.so
截图
截图
截图
在这里正向看很难看出什么东西,慢慢跟进sub_ADD80
截图
截图
可以看到一些字符串,显然是通过sub_30830和源码中的函数产生了某些关联,可以直接查它的引用
截图
能找到Open函数了,进一步通过上面的open db来找函数,hook这些函数的参数,就能找到密钥
截图
截图
由于应用采用多进程的方式,网上找个脚本来跑,稍微改一下,能找到libjavacore.so去加载了libabab.so,但是没办法搞到libabab.so的module对象,并且监听数据库也没有监听到打开记录,后来发现是模拟器原因,在模拟器中没法找到。。真机中就可以找到了

# -*- coding: utf-8 -*-
import codecs
import frida
import sys
import threading

device = frida.get_device_manager().enumerate_devices()[-1]
print(device)

pending = []
sessions = []
scripts = []
event = threading.Event()

jscode = """
Java.perform(function () {
    Interceptor.attach(Module.findExportByName(null, 'open'), {
  onEnter: function (args) {
    var path = Memory.readUtf8String(args[0]);
    console.log(Process.findModuleByAddress(this.returnAddress).name,path)
  }
});
});
"""

def on_spawned(spawn):
    print('on_spawned:', spawn)
    pending.append(spawn)
    event.set()

def spawn_added(spawn):
    print('spawn_added:', spawn)
    event.set()
    if(spawn.identifier.startswith('cn.wildfirechat.chat')):
        session = device.attach(spawn.pid)
        script = session.create_script(jscode)
        script.on('message', on_message)
        script.load()
        device.resume(spawn.pid)
        
def spawn_removed(spawn):
    print('spawn_added:', spawn)
    event.set()

def on_message(spawn, message, data):
    print('on_message:', spawn, message, data)
    
def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)

device.on('spawn-added', spawn_added)
device.on('spawn-removed', spawn_removed)
device.on('child-added', on_spawned)
device.on('child-removed', on_spawned)
device.on('process-crashed', on_spawned)
device.on('output', on_spawned)
device.on('uninjected', on_spawned)
device.on('lost', on_spawned)
device.enable_spawn_gating()
event = threading.Event()
print('Enabled spawn gating')

pid = device.spawn(["cn.wildfirechat.chat"])


session = device.attach(pid)
print("[*] Attach Application id:",pid)
device.resume(pid)
sys.stdin.read()

截图
这是真机中的情况,这里换成了最新的野火IMdemo,可以看到确实加载了
截图
接下来写脚本即可(地址基于最新的野火IMdemo中arm64架构的so文件)

jscode = """
setImmediate(function () {
  Java.perform(function () {
    Interceptor.attach(Module.findExportByName(null, 'open'), {
      onEnter: function (args) {
        var path = Memory.readUtf8String(args[0]);
        if (path.indexOf("libmarsstn.so") != -1) {
          console.log(Process.findModuleByAddress(this.returnAddress).name, path)
          var targetModule = Process.findModuleByName("libmarsstn.so");
          var baseAddr = targetModule.base;
          var funcAddr = baseAddr.add(0x1F08EC);//函数地址
          Interceptor.attach(funcAddr, {
            onEnter: function (args1) {
              console.log(hexdump(args1[0]))
            },
            onLeave: function (retval1) {}
          });
        }
      },onLeave: function (retval) {}
    });
  });
})
"""

可以看到输出的密钥和解密结果一致
截图
截图
截图