首页
社区
课程
招聘
[原创]MMKV文件解析及实现
发表于: 2022-12-1 13:02 22580

[原创]MMKV文件解析及实现

2022-12-1 13:02
22580

移动端在逆向登录协议时,在使用账号 密码登录成功后,接口(/appMobileLogin )一般都会返回一个 token,以后再次打开 app 时,凭借采用账号密码登录返回的 token,就可以无需使用账号密码登录了,此时登录接口地址改为(/login)。app 正常启动后,一般所有的业务接口都会携带此 token 进行访问。

这个token 在下次使用账号密码登录之前,一般都不会改变,那么这个字段必然会save 到磁盘的某个文件中。

当然这个token 的破解可能简单,也可能很困难。不论简单与困难,都需要你再次打开动态调试 app,你会进行下面一系列操作:

打开 pc(虚拟机)

adb connect

启动 frida-server

找到之前的破解日志(hook 了哪些函数)

编写 hook.js

启动 app

frida -l hook.js package_name

jadx 开发 package_name

找到算法函数(可能调用栈很多)

经过不懈努力终于找到 token,可能花费半小时甚至更久

重大喜讯:现在这个过程可以简化了

移动端一般都会采用MMKV来做序列化及持久化,这里不分析 MMKV 的优势和劣势,我们又不搞工程,分析它干嘛~
我们只是要里面的数据。如果 app 使用 mmkv,那么通常会把 token 存在 mmkv.default里,当然这个文件还会存其他关键数据,你需要知道token 的 key 是什么名称即可。

mmkv原理可以参考这篇文章: mmkv原理
这篇文章在协议图里有个问题,如图:
图片描述
在总长度与 第一个key 长度之间还有一个 无用的 key 长度,这里贴上源码:
图片描述
第7行这里会多读一次 key len,然后直接跳过。这段代码的含义是:如果 position为0是,直接跳过第一个 ken_len
源码并未给出原因,可能是保留字段吧(通常在设计协议的时候,都会有保留字段),有懂的大神请指正。
就是因为第一个 key_len,我老是解析不对 mmkv.default, 逼不得已开始读源码~
源码链接:mmkv 源码

下面给出两种读取 mmkv.default的方式:

官方版本源码安装: mmkv python 安装
另外就是自实现的简略版本
两个版本的对比:

官方版本需要编译,需要把 编译生成的 so 文件放到 python 的 sitepackages 里才能使用 impor mmkv
官方版本完全加载 mmkv依赖的环境,比如 mmap 映射,文件锁啥的,比如 windows需要更复杂的安装
另外在加载的时候还需要crc 校验
有兴趣的同学可以尝试编译官方版本

简略版本只需要有 python 即可
问:为啥不需要 crc 校验?
答:app 在使用的文件肯定是校验后的呀,拿来即用即可!

当你使用简略版本时, 只需要 adb pull /data/data/com.packagename/file/mmkv/mmkv.default . 到本地即可
然后
mr = MMKVReader('./mmkv.default')
mr.get_data()
token = mr.d['token_key'] # 这里的token_key,改为你需要的即可
想象一下这个场景:
你需要大规模利用时, 你自己一个账号不足时,只需要别人把 mmkv.default发给你,你就可以用他的账号登录并做爱做的事了,岂不美哉?

其实这个文件里除了 token ,可能登录环境也有哦~,用起来更丝滑哦~
题外话:python 没有int8类型,需要使用 numpy.int8

 
 
 
 
 
'''
see https://github.com/Tencent/MMKV/wiki/python_setup
build for python
 
cp mmkv.cpython-38-darwin.so /PYTHONPATH/sitepackages/
这样就可以使用 import mmkv
'''
 
import numpy as np
 
import mmkv
 
 
ed = 'little'
 
 
class MMKVReader():
 
    def __init__(self, f_path) -> None:
        self.f = open(f_path, 'rb')
 
        self.real_size = int.from_bytes(self.f.read(4), ed)
 
        # first, 第一个 key 长度删掉
        self.get_key_len()
        self.d = {}
 
    def read_int8(self):
        size = int.from_bytes(self.f.read(1), ed)
        tmp = np.int8(size)
        return tmp
 
    def get_key_len(self):
        tmp = self.read_int8()
        if tmp >= 0:
            return tmp
 
        result = tmp & 0x7f
        tmp = self.read_int8()
        if tmp >= 0:
            result |= tmp << 7
        else:
            result |= (tmp & 0x7f) << 7
            tmp = self.read_int8()
            if tmp >= 0:
                result |= tmp << 14
            else:
                result |= (tmp & 0x7f) << 14
                tmp = self.read_int8()
                if tmp >= 0:
                    result |= tmp << 21
                else:
                    result |= (tmp & 0x7f) << 21
                    tmp = self.read_int8()
                    result |= tmp << 28
                    if tmp < 0:
                        for i in range(5):
                            tmp = self.read_int8()
                            if tmp >= 0:
                                return result
                        raise ValueError('协议错误')
        return result
 
    def get_data(self):
        while True:
            if (self.f.tell() - 4) >= self.real_size:
                break
            ken_len = self.get_key_len()
            key = self.f.read(ken_len)
            data_len = self.get_key_len()
            data = self.f.read(data_len)
            self.d[key] = data
        # 释放文件描述符
        self.f.close()
 
 
if __name__ == "__main__":
 
    # 实现1: 简略版本
    mr = MMKVReader('./mmkv.default')
    print(mr.get_data())
 
    # 实现2 libmmkv,官方版本
    mmkv.MMKV.initializeMMKV('.', mmkv.MMKVLogLevel.Debug)
    kv = mmkv.MMKV.defaultMMKV()
 
    # 查看所有的 keys
    for key in kv.keys():
        print(key)
        # 不是所有的 key 都是string 类型
        # print(kv.getString(key))
'''
see https://github.com/Tencent/MMKV/wiki/python_setup
build for python
 
cp mmkv.cpython-38-darwin.so /PYTHONPATH/sitepackages/
这样就可以使用 import mmkv
'''
 
import numpy as np
 
import mmkv
 
 
ed = 'little'
 
 
class MMKVReader():
 
    def __init__(self, f_path) -> None:
        self.f = open(f_path, 'rb')
 
        self.real_size = int.from_bytes(self.f.read(4), ed)
 
        # first, 第一个 key 长度删掉
        self.get_key_len()
        self.d = {}
 
    def read_int8(self):
        size = int.from_bytes(self.f.read(1), ed)
        tmp = np.int8(size)
        return tmp
 
    def get_key_len(self):
        tmp = self.read_int8()
        if tmp >= 0:
            return tmp
 
        result = tmp & 0x7f
        tmp = self.read_int8()
        if tmp >= 0:

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2022-12-1 13:05 被gwljt编辑 ,原因: 错别字
收藏
免费 3
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//