Android 取证之微信8.0.38版本数据库解密分析

news2024/11/26 4:31:03

0x01 前言

本文以教学为基准、本文提供的可操作性不得用于任何商业用途和违法违规场景。
本人对任何原因在使用本人中提供的代码和策略时可能对用户自己或他人造成的任何形式的损失和伤害不承担责任。

0x02 软硬件环境

app 版本:8.0.38
inject:frida 12.8.0
设备:Pixel 2 XL 已 ROOT
反汇编工具:JEB、JADX、IDA

0x03 Android数据库:SQLite

想要进行微信数据库的逆向必须先了解其采用的是何种数据库及采用的防护手段!
Android中的SQLite是一种轻量级的关系型数据库,它是Android平台中默认的本地数据库存储解决方案。SQLite在Android系统中广泛使用,可用于存储和检索应用程序中的数据。
SQLite的优点包括:
1、轻量级:SQLite非常轻便,它的库文件非常小,可以轻松嵌入到Android应用程序中。
2、高效:由于SQLite是一个本地数据库,它可以快速地执行读写操作,并且具有非常高的性能。
3、可靠性:SQLite是一个可靠的数据库解决方案,它可以确保数据的完整性和一致性。
4、跨平台:SQLite可以在各种不同的操作系统和平台上运行,因此可以轻松地将数据从一个平台移植到另一个平台。
but,SQLite却有一个致命的缺陷:不支持加密。
因此存储在SQLite中的数据可以被任何人轻易地查看。如果是普通的数据还好,但是当涉及到一些账号密码,聊天内容或者个人信息的时候,我们的应用就会面临严重的安全漏洞隐患。
所以,需要在开发应用时对数据库进行加密,目前对SQLite有两种加密方式:
1、对写入数据库的数据进行加密
2、对整个数据库文件进行加密
而微信所采取的方式便是第二种:对整个数据库文件进行加密。

0x04 需求分析

微信的数据库,位于本地的/data/data/com.tencent.mm/MicroMsg/921dxxxxxxxxxx4d/路径下,名称为:EnMicroMsg.db,最后一个文件夹每个手机可能不同,需自己进入定位下:

 

ok,那么我们的需求就是获取到解密数据库的密钥。

0x05 密钥获取

将apk拖入jadx,等待反汇编完成,腾讯有着自己的加壳方案(腾讯乐固),但并未在微信中使用,应是加壳后会影响使用性能,而微信这么一个全国几亿人在用的软件对性能的要求出不了一丝偏差。
对SQLite有所了解的应该都知道,他有着自己所定义的api,要想对其进行操作必定会用到SQLiteDatabase这个类,搜索可得:

 双击进入后,查找open函数,查看open函数的具体实现:

要想打开数据库,那么必定需要密钥,看open函数传入三个参数,其中第一个bArr数组无疑是最可疑的。
使用frida编写hook脚本:

1

2

3

4

5

6

7

8

9

Java.perform(function(){

    var SQLiteDatabase = Java.use("com.tencent.wcdb.database.SQLiteDatabase");

    SQLiteDatabase["open"].implementation = function (bArr, sQLiteCipherSpec, i15) {

        console.log('open is called' + ', ' + 'bArr: ' + JSON.stringify(bArr) + ', ' + 'sQLiteCipherSpec: ' + sQLiteCipherSpec + ', ' + 'i15: ' + i15);

        var ret = this.open(bArr, sQLiteCipherSpec, i15);

        console.log('open ret value is ' + ret);

        return ret;

    };

});

打开微信进行hook,结果:

[Pixel 2 XL::微信]->open is called, bArr: [48,48,57,102,97,56,51], sQLiteCipherSpec: com.tencent.wcdb.database.SQLiteCipherSpec@2eb6bd9, i15: 0
openInner ret value is undefined

将数据转换后得到结果:009fa83
在sqlcipher软件输入该密钥点击确定:
 

图片描述


可以看到整个数据库的内容已经可以正常查看了,那么接下来需要关心的就是密钥的生成方式了!

0x06 密钥生成分析

6.1 堆栈分析

得到了密钥,但是不知道密钥如何生成的,这种奇耻大辱岂是我辈逆向人员可以忍受的,必须搞它!
简单更改下hook代码,加入堆栈打印,已便于观测其执行流程:

1

2

3

4

5

6

7

8

9

10

Java.perform(function(){

    var SQLiteDatabase = Java.use("com.tencent.wcdb.database.SQLiteDatabase");

    SQLiteDatabase["open"].implementation = function (bArr, sQLiteCipherSpec, i15) {

        console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));  //java层打印堆栈

        console.log('open is called' + ', ' + 'bArr: ' + JSON.stringify(bArr) + ', ' + 'sQLiteCipherSpec: ' + sQLiteCipherSpec + ', ' + 'i15: ' + i15);

        var ret = this.open(bArr, sQLiteCipherSpec, i15);

        console.log('open ret value is ' + ret);

        return ret;

    };

});

hook结果:

[Pixel 2 XL::微信]-> java.lang.Throwable
at com.tencent.wcdb.database.SQLiteDatabase.open(Native Method)
at com.tencent.wcdb.database.SQLiteDatabase.openDatabase(SourceFile:3)
at com.tencent.wcdb.database.SQLiteDatabase.openDatabase(SourceFile:4)
at ir3.e.r(Unknown Source:185)
at ir3.a.f(Unknown Source:240)
at ir3.f.n(Unknown Source:55)
at ir3.f.m(Unknown Source:10)
at gi.bb.<init>(Unknown Source:80)
at se1.i$a.invokeSuspend(Unknown Source:185)
at q74.a.resumeWith(Unknown Source:8)
at ta4.a1.run(Unknown Source:122)
at l34.b$b.run(Unknown Source:63)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:457)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at x34.j.run(Unknown Source:246)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at q34.c.run(Unknown Source:2)
at java.lang.Thread.run(Thread.java:764)
open is called, bArr: [48,48,57,102,97,56,51], sQLiteCipherSpec: com.tencent.wcdb.database.SQLiteCipherSpec@8ea336a, i15: 0
open ret value is undefined

 

可以看到在进入SQLiteDatabase类中最后一个方法为:ir3.e.r,跟进查看: 

 

编写hook代码对e.r方法进行hook查看:

1

2

3

4

5

6

7

8

9

Java.perform(function(){

    var e = Java.use("ir3.e");

    e["r"].implementation = function (str, str2, i15, z15) {

        console.log('r is called' + ', ' + 'str: ' + str + ', ' + 'str2: ' + str2 + ', ' + 'i15: ' + i15 + ', ' + 'z15: ' + z15);

        var ret = this.r(str, str2, i15, z15);

        console.log('r ret value is ' + ret);

        return ret;

    };

});

hook结果:

[Pixel 2 XL::微信]-> r is called, str: /data/user/0/com.tencent.mm/MicroMsg/921dfcc503355cc212099213345ddc4d/enFavorite.db, str2: 009fa83, i15: 0, z15: true
r is called, str: /data/user/0/com.tencent.mm/MicroMsg/921dfcc503355cc212099213345ddc4d/enFavorite.db, str2: 009fa83, i15: 0, z15: true
r ret value is ir3.e@a3302f8
r ret value is ir3.e@41156a4

打开的另一个数据库,这个无所谓,可以看到密码bArr是由e方法的参数str2通过str2.getBytes()生成,那么就需要继续分析调用com.tencent.mm.ir3.e.r()方法的地方。
查看堆栈上一个调用位置为a.f()方法,跟进查看:
 

图片描述


没有什么可分析的代码,继续往上分析:com.tencent.mm.ir3.f.n()和com.tencent.mm.ir3.f.m()

 

参数很多,hook看看,hook代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

Java.perform(function(){

    var f = Java.use("ir3.f");

    f["m"].implementation = function (str, str2, j15, str3, hashMap, z15) {

        console.log('m is called' + ', ' + 'str: ' + str + ', ' + 'str2: ' + str2 + ', ' + 'j15: ' + j15 + ', ' + 'str3: ' + str3 + ', ' + 'hashMap: ' + hashMap + ', ' + 'z15: ' + z15);

        var ret = this.m(str, str2, j15, str3, hashMap, z15);

        console.log('m ret value is ' + ret);

        return ret;

    };

    var f = Java.use("ir3.f");

    f["n"].implementation = function (str, str2, str3, j15, str4, hashMap, z15) {

        console.log('n is called' + ', ' + 'str: ' + str + ', ' + 'str2: ' + str2 + ', ' + 'str3: ' + str3 + ', ' + 'j15: ' + j15 + ', ' + 'str4: ' + str4 + ', ' + 'hashMap: ' + hashMap + ', ' + 'z15: ' + z15);

        var ret = this.n(str, str2, str3, j15, str4, hashMap, z15);

        console.log('n ret value is ' + ret);

        return ret;

    };

});

hook结果:

[Pixel 2 XL::微信]-> m is called, str: , str2: /data/user/0/com.tencent.mm/MicroMsg/921dfcc503355cc212099213345ddc4d/enFavorite.db, j15: 1721974820, str3: 1234567890ABCDEF, hashMap: {-403906948=gi.bb�@�405�6�,268557265=��.��a@d405a6a,268557265=gi.bbb@7eefb5b, -491946003=gi.bb�@4�8���8,1810537579=��.��e@4b8eff8,1810537579=gi.bbf@dabecd1, 1692712704=gi.bb�@�01�336,20610547=��.��d@f01b336,20610547=gi.bbc@a17f237, -1687968802=gi.bb$g@58913a4}, z15: true
n is called, str: , str2: /data/user/0/com.tencent.mm/MicroMsg/921dfcc503355cc212099213345ddc4d/enFavorite.db, str3: , j15: 1721974820, str4: 1234567890ABCDEF, hashMap: {-403906948=gi.bb�@�405�6�,268557265=��.��a@d405a6a,268557265=gi.bbb@7eefb5b, -491946003=gi.bb�@4�8���8,1810537579=��.��e@4b8eff8,1810537579=gi.bbf@dabecd1, 1692712704=gi.bb�@�01�336,20610547=��.��d@f01b336,20610547=gi.bbc@a17f237, -1687968802=gi.bb$g@58913a4}, z15: true
m is called, str: , str2: /data/user/0/com.tencent.mm/MicroMsg/921dfcc503355cc212099213345ddc4d/enFavorite.db, j15: 1721974820, str3: 1234567890ABCDEF, hashMap: {-403906948=gi.bb�@�405�6�,268557265=��.��a@d405a6a,268557265=gi.bbb@7eefb5b, -491946003=gi.bb�@4�8���8,1810537579=��.��e@4b8eff8,1810537579=gi.bbf@dabecd1, 1692712704=gi.bb�@�01�336,20610547=��.��d@f01b336,20610547=gi.bbc@a17f237, -1687968802=gi.bb$g@58913a4}, z15: true
n is called, str: , str2: /data/user/0/com.tencent.mm/MicroMsg/921dfcc503355cc212099213345ddc4d/enFavorite.db, str3: , j15: 1721974820, str4: 1234567890ABCDEF, hashMap: {-403906948=gi.bb�@�405�6�,268557265=��.��a@d405a6a,268557265=gi.bbb@7eefb5b, -491946003=gi.bb�@4�8���8,1810537579=��.��e@4b8eff8,1810537579=gi.bbf@dabecd1, 1692712704=gi.bb�@�01�336,20610547=��.��d@f01b336,20610547=gi.bbc@a17f237, -1687968802=gi.bb$g@58913a4}, z15: true
n ret value is true
m ret value is true
n ret value is true
m ret value is true

可以看到有两个参数是固定的,j15: 1721974820, str4: 1234567890ABCDEF。

6.2 ir3.f.m()函数分析

查看m的调用,分析这两个参数的来源,定位到com.tencent.mm.gi.bb()函数中:

貌似是动态生成的,可反复操作n次,其值总是1721974820,这不经让我想到一种可能它会不会是从某个文件中取出的?
在jadx中搜索该值,把所有选项都勾选了,可以看到并无任何结果:

三元表达式,可以看出是写死"1234567890ABCDEF"。

6.3 加密方式分析

那么是怎么通过这两个参数变化生成了密钥值呢?
必然是经过某些算法得出的密钥,至于是什么算法,可以有两种方式确认:
一:继续分析apk源码,耗时久。
二:上算法通杀脚本,只要是常见的算法全给它hook一边从而确定采用了何种算法。
那我肯定是采用性价比最高的方法,直接上算法通杀脚本hook一遍,可得:

 

 

标准MD5算法得出的值,只是密钥只采用了值的前7位,先把hook脚本给一下,太多了有限制,这边仅把MD5的脚本贴上:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

function stack_print() {

    console.log(

        Java.use("android.util.Log")

            .getStackTraceString(

                Java.use("java.lang.Throwable").$new()

            )

    );

}

Java.perform(function(){

    var messageDigest = Java.use("java.security.MessageDigest");

    var ByteString = Java.use("com.android.okhttp.okio.ByteString");

    //tag为标签,data为数据

    function toBase64(tag, data) {

        console.log(tag + " Base64: ", ByteString.of(data).base64());

    }

    function toHex(tag, data) {

        console.log(tag + " Hex: ", ByteString.of(data).hex());

    }

    function toUtf8(tag, data) {

        console.log(tag + " Utf8: ", ByteString.of(data).utf8());

    }

    messageDigest.update.overload('byte').implementation = function (data) {

        console.log("MessageDigest.update('byte') is called!");

        return this.update(data);

    }

    messageDigest.update.overload('java.nio.ByteBuffer').implementation = function (data) {

        console.log("MessageDigest.update('java.nio.ByteBuffer') is called!");

        return this.update(data);

    }

    messageDigest.digest.overload().implementation = function () {

        console.log("MessageDigest.digest() 被调用了!");

        var result = this.digest();

        var algorithm = this.getAlgorithm();

        var tag = algorithm + " 调用digest返回输出的数据:";

        toHex(tag, result);

        toBase64(tag, result);

        console.log("=======================================================");

        return result;

    }

    messageDigest.digest.overload('[B').implementation = function (data) {

        console.log("MessageDigest.digest('[B') 被调用了!");

        var algorithm = this.getAlgorithm();

        var tag = algorithm + " 调用digest得到的数据:";

        toUtf8(tag, data);

        toHex(tag, data);

        toBase64(tag, data);

        var result = this.digest(data);

        var tags = algorithm + " 调用digest返回输出的数据:";

        toHex(tags, result);

        toBase64(tags, result);

        console.log("=======================================================");

        return result;

    }

    messageDigest.digest.overload('[B''int''int').implementation = function (data, start, length) {

        console.log("MessageDigest.digest('[B', 'int', 'int') 被调用了!");

        var algorithm = this.getAlgorithm();

        var tag = algorithm + " 调用digest得到的数据:";

        toUtf8(tag, data);

        toHex(tag, data);

        toBase64(tag, data);

        var result = this.digest(data, start, length);

        var tags = algorithm + " 调用digest返回输出的数据:";

        toHex(tags, result);

        toBase64(tags, result);

        console.log("=======================================================", start, length);

        return result;

    }

});

6.4 算法还原

再将MD5算法得出密钥的过程用python还原:

1

2

3

4

5

6

7

8

9

10

11

12

13

from hashlib import md5

def get_encode_mes(mes):

    # 创建 MD5 对象

    new_md5 = md5()

    # 这里必须用encode()函数对字符串进行编码,不然会报 TypeError: Unicode-objects must be encoded before hashing

    new_md5.update(mes.encode(encoding='utf-8'))

    # 加密

    return new_md5.hexdigest()

if __name__ == '__main__':

    print(get_encode_mes('17219748201234567890ABCDEF'))

    print(get_encode_mes('1234567890ABCDEF1721974820'))

结果:

d33514c7133ec8ddeeb741db284c3b62
009fa83591e1b8d1655857d83b03b71d

由于不知道两个参数的拼接顺序,所以两种方式都进行了尝试,看加密后的值,显然1234567890ABCDEF在前,_auth_uin在后。

0x07 总结

现今各大厂商对数据的保护也是是越来越强,路漫漫其修远兮,吾辈当上下求索。

 

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/859137.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

统信UOS下eclipse使用lombok报错的问题

lombok不兼容问题 lombok不支持高版本jdk&#xff0c;本人在应用商店下载eclipse安装的&#xff0c;默认用的jdk17&#xff0c;不兼容lombok插件&#xff0c;需要调整eclipse.ini配置文件&#xff0c;如下&#xff1a; #/opt/apps/org.eclipse.java-ee/files/eclipse.ini -ja…

香港站群服务器为什么适合seo优化?

​  香港站群为什么适合seo优化?本文主要从以下四点出发进行原因阐述。 1.香港站群服务器的优势 2.香港站群服务器与国内服务器的对比 3.多IP站群服务器的优势 4.香港站群服务器在SEO优化中的注意事项 1.香港站群服务器的优势 香港站群服务器是为了满足企业SEO优化需求而提供…

iconfont 使用

官网地址 iconfont-阿里巴巴矢量图标库 常规操作&#xff1a;注册账号 首页 搜索想要的图片 加入购物车并添加项目没有就创建一个 在线生成链接 复制生成的css 在前端软件创建相关的wxss文件 全局 import "/static/iconfont/iconfont.wxss";page {height: 100%; }…

Jenkins 插件下载速度慢安装失败?这篇文章可能解决你头等难题!

Jenkins部署完毕&#xff0c;如果不安装插件的话&#xff0c;那它就是一个光杆司令&#xff0c;啥事也做不了&#xff01; 所以首先要登陆管理员账号然后点击系统管理再点击右边的插件管理安装CI/CD必要插件。 但是问题来了&#xff0c;jenkins下载插件速度非常慢&#xff0…

【腾讯云 Cloud Studio 实战训练营】用于编写、运行和调试代码的云 IDE泰裤辣

文章目录 一、引言✉️二、什么是腾讯云 Cloud Studio&#x1f50d;三、Cloud Studio优点和功能&#x1f308;四、Cloud Studio初体验&#xff08;注册篇&#xff09;&#x1f386;五、Cloud Studio实战演练&#xff08;实战篇&#xff09;&#x1f52c;1. 初始化工作空间2. 安…

(十)人工智能应用--深度学习原理与实战--模型的保存与加载使用

目的:将训练好的模型保存为文件,下次使用时直接加载即可,不必重复建模训练。 神经网络模型训练好之后,可以保存为文件以持久存储,这样下次使用时就不重新建模训练,直接加载就可以。TensorfLow提供了灵活的模型保存方案,既可以同时保存网络结构和权重(即保存全模型),也可…

这回稳了!电力巡检摄像头解决方案全新来袭

最近的狂飙成为大家的话题&#xff0c;现在是互联网的时代&#xff0c;想要的信息总能在互联网获取…这也是我一直喜欢分享科技话题给大家的原因。 疫情已经终于离我们而去&#xff0c;在这春回大地的时候&#xff0c;是时候要分享一下电力行业智能巡检的一些解决方案给大家。这…

从零开始学python(十六)爬虫集群部署

前言 今天讲述Python框架源码专题最后一个部分&#xff0c;爬虫集群部署&#xff0c;前面更新了十五个从零开始学python的系列文章&#xff0c;分别是&#xff1a; 1.编程语法必修篇 2.网络编程篇 3.多线程/多进程/协程篇 4.MySQL数据库篇 5.Redis数据库篇 6.MongoDB数据库篇 …

备战金九银十 I 没有自动化测试项目经验的测试人快快看过来!

学习自动化测试最难的是没有合适的项目练习。测试本身既要讲究科学&#xff0c;又有艺术成分&#xff0c;单单学几个 API 的调用很难应付工作中具体的问题。 你得知道什么场景下需要添加显性等待&#xff0c;什么时候元素定位需要写得更加优雅&#xff0c;为什么需要断言这个元…

idea 加入 .so文件

背景 做项目的时候&#xff0c;遇到需要查看native 方法 涉及到c源码的查看&#xff0c;因此需要加载.so文件去查看。 操作 idea-file-project structure 找到lib&#xff0c;把你的.so文件添加进来就可以啦 然后你就可以查看对应的源码了。

【WebService】使用postman调用WebService方法

1、需求 公司原来有一个项目使用的是WebService&#xff0c;想模拟一下怎么调用WebService的方法&#xff0c;使用postman调用怎么调用。 2、postman方式 接口&#xff1a;http://127.0.0.1:8080/SecurityWebService/SecurityCommand?wsdl 对应你的代码配置&#xff1a; …

Java课题笔记~ Response响应

1.响应消息格式 HTTP响应也由四个部分组成&#xff0c;分别是&#xff1a;状态行、消息报头、空行和响应正文。 响应消息&#xff1a;服务器端发送给客户端的数据 数据格式说明&#xff1a; 1. 响应行 ​ 1. 组成&#xff1a;协议/版本 响应状态码 状态码描述 ​ 2. …

差异性分析傻瓜版

path1输入你的第一个Excel path2输入你的第二个Excel DEG.dig <- function(path1,path2) { require(xlsx) require(tidyverse) require(limma) require(edgeR) E<- read.xlsx (path1,sheetIndex 1,header 1) %>% column_to_rownames(var &…

六、web应用程序技术——编码

文章目录 一、状态与会话二、编码方案2.1 URL编码2.2 Unicode编码2.3 HTML编码2.4 Base64编码2.5 十六进制编码 一、状态与会话 web应用程序服务器和客户端组件除了以各种方式进行数据交换和处理&#xff0c;应用程序还需要追踪每位用户通过不同的请求与应用程序交互的状态。例…

appuploader使用教程

转载&#xff1a;appuploader使用教程 目录 问题解决秘籍 登录失败 don’t have access,提示没权限或同意协议 上传后在app管理中心找不到版本提交 不是等待上传状态 提示已经上传过包 上传提示tcpPort or udpPorts错误 上传提示已经有进程在上传 保存上传专用密码提示…

Oracle 开发篇+Java通过DRCP访问Oracle数据库

标签&#xff1a;DRCP、Database Resident Connection Pooling、数据库驻留连接池释义&#xff1a;DRCP&#xff08;全称Database Resident Connection Pooling&#xff09;数据库驻留连接池&#xff08;Oracle自己的数据库连接池技术&#xff09; ★ Oracle开启并配置DRCP sq…

通义千问开源模型部署使用

首先可以参考modelScope社区给出的使用文档&#xff0c;已经足够全面 通义千问-7B-Chat 但在按照文档中步骤部署时&#xff0c;还是有些错误问题发生&#xff0c;可以搜索参考的解决方式不多&#xff0c;所以记录下来 个人电脑部署 这里不太建议使用自己的笔记本部署通义千…

【墙裂推荐!】十款开源测试开发工具(自动化、性能、造数据、流量复制)​

目录 1、AutoMeter-API 自动化测试平台 2、QA Wolf 浏览器自动化测试工具 3、Mimesis 用于 Python 的高性能虚假数据生成器 4、Ddosify 高性能负载测试工具 5、AutoCannon HTTP/1.1 基准测试工具 6、Sharingan 流量录制回放工具 7、randdata 随机测试数据生成工具 8、D…

【求两个数二进制中不同位的个数】

求两个数二进制中不同位的个数 1.题目 编程实现&#xff1a;两个int&#xff08;32位&#xff09;整数m和n的二进制表达中&#xff0c;有多少个位(bit)不同&#xff1f; 输入例子:1999 2299 输出例子:7 2.题目分析 先将m和n进行按位异或&#xff0c;此时m和n相同的二进制比特位…

多线程知识点总结

这个更清晰一点 JAVA多线程-ProcessOnhttps://www.processon.com/mindmap/64d06633b9f7806c73e70d92