-
-
[原创]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期)