welink 软件逆向-用户密码数据解析

背景

在使用Welink 的时候发现Welink 可以在免登录的情况下打开程序,到用户的IM界面。之后发现是在登录界面勾选了记住密码和自动登录,当勾选了记住密码后就会把密码保存在用户目录下的sys.db 文件中,以下是分析过程。

而且在调试过程中,内存保存着welink 的明文密码

image-20241101100334595

可以实现一个搜索内存的程序获取明文密码。

解析sys.db 配置文件
调试进程

welink 是 electron 编译的Windows可执行文件,将Chromium和Node.js嵌入到二进制文件中。查看app文件夹下的JS 文件,由于经过了js混淆,分析js文件比较困难。最终选择直接调试的方法。

Electron 分为主进程和渲染进程

下CreateProcessW 断点,当启动进程如下是,则启动了主进程

修改启动参数为挂起0x80004,启动x32DBG 附加新启动的进程

修改X32DBG 增加DLL 入口断点,恢复线程。

sys.db 文件解析

查看welink 用户目录,发现保存着挺多db 后缀的文件,如sys.db、user.db、history.db。这些文件都有一个类似的结构。即前八个字节同一目录下是一样的,0x10 出的8个字节不同目录下的文件也一致。

使用调试器下打开文件断点

strstr( utf16([esp+4])  ,"sys.db")

image-20241101103003771

查看调用堆栈发现存在初始化数据库的函数名,CreateFileW 函数调用发生在wxsqlite.dll 动态链接库,偏移为145BD

sqlite_plugin.tup_sqlite_init_db
断点 wxsqlite+0x145BD

下读取文件ReadFile 断点,当读取sys.db 数据库时停下来查看堆栈。偏移为 14BB1

向上回溯调试,在函数off_14BD8(使用off_表示偏移)调用off_5BA70 函数会解密数据库文件部分数据,有参数决定解密的大小,经调试发现在正式解密中每次解密0x1000 字节大小的数据。

在off_14BD8 下断点调试分析

[esp+8]==0x1000

多次断下来对比源文件数据发现

函数off_5BA70 
参数一表示一个加密使用的对象
参数二表述数据的大小
参数三表示数据在源文件中的偏移

手动解析db 文件,需要在真正解密对应文件的时候断下来,然后通过修改反汇编的方式使解密函数进行,不同的文件参数一的值有所不同

1.x32 dbg 申请文件大小的内存
alloc size
2. 修改反汇编
add dword ptr ss:[ebp+0x14], 0x1000
add esi, 0x1000
jmp 0x5E134BCF/.
3.修改 esi 寄存器的值
4.复制源文件数据到申请的用户内存空间

解析部分文件

im_userdb

sys_db

解密db 文件

函数off_5BA70调用off_8DC0 解密

off_8DC0 函数首先判断数据偏移,如果是首页,则进行替换。将off_8 处的8字节数据复制到off_0x10 处,之后才进程解密

构建一个计算sha256 的缓冲区

off_5BA70 参数一中确定的32字节数据,为 [arg+1c]+14,这部分称为cipher_salt
四字节页偏移,如如果为首页,则为1
四字节固定值, 0x546C4173  “sAlT” 

计算得到sha256值,作为aes 解密的密钥

四字节页偏移经过一定转换生成16字节数据,计算得到MD5 值,作为aes 解密的IV

解密之后如果是第一页则替换解密数据首部的前16字节的数据,替换为Sqlite的标头

image-20241101112122240

通过向上追踪和内存硬件断点分析,都没有找到赋值时候的代码。观察cipher_salt前面部分的数据发现一个关键数值。0xFA1

搜索自己定位到函数off_8AB0

image-20241101113635324

调试确定,就是该函数计算得到了cipher_salt的值

查看调用,发现在sqlite3_key 函数中调用

分析sqlite3_key 函数,其参数为对应的db 文件路径和一段字符串数据

后续的cipher_salt 为参数3,就是传入的字符串值取前一半多次计算sha256 所得。

最终的函数也是sqlite_plugin.tup_sqlite_init_db,继续回溯发现为处理接收消息。

sysdb sqlite_key 的生成
初始化kmc 解密 kmc.dll

WsecInitializeEx 函数

1.初始化全局变量随机数

2.解析kmcStore.dat 文件,路径为传入参数

kmc Rootkey 头部的结构如下

调用off_10170 函数读取头部获取RootKey,并校验,之后传入off_C5A0用于 派生密钥

根密钥异或一个硬编码数据作为pbkdf2_derive 派生密钥的key

硬编码为

经派生后生成0x40字节的数据Root_pbkdf2_derive_key,这部分数据用于kmc 数据部分(MKDATA)解析

判断ksf 文件判断,然后解密。版本几位头部的前两个字节,这里版本为2

image-20241101125816677

版本为1或2,则调用off_E8A0 函数解密

读取MKDATA 数据,使用Root_pbkdf2_derive_key 的前0x20字节数据作为AES的key,MKDATA 数据中0x48偏移的16字节数据作为IV,解密0x60偏移处的0x30大小的数据。解密数据的大小具体由偏移为0x58的数据决定,解密数据的大小是0x5c 数据,计算得到MK

之后解密后的数据异或开头的随机全局变量

解密过程

解密函数为SdpDecryptEx 函数,解密数据为appdata/IM config.db 文件中的第一部分数据转为二进制数据,如下的内存部分。

解密时先获取初始化生成的主密钥MK,然后使用 pbkdf2_derive 派生获得WorkKey

pbkdf2_derive 派生密钥的salt 值为config_data的0x1c 偏移的8字节数据

WorkKey 经过AES 算法解密

config 结构为

将这部分与config 的第二部分数据作为参数传入 tup_pkcs5_derivekey_ex 函数计算得到derive salt,derive salt即为sqlite3_key的参数。

image-20241101133600561

解析sys 数据解密密码

在 TUP_CPYPT_Encrypt 函数下断点

在tup_cvt_hex 下断点记录

esp+0xc {ansi([esp+0xc])}  esp+18  {ansi([esp+0x18])}
27A6+wk_tup_commonlib
线程 6084 已创建,入口:mswsock.6EE91B50,参数:1BACC968
esp+0xc 30654ac2b3fae18b61b94892d508d07a6f3d13579323ec21bcc241a4171d3aff  esp+18  {\"description\":\"tup_commonlib_crypt_pkcs5_derive_key_new\",\"cmd\":262153,\"param\":{\"salt\":\"3b5ff508ca97d9c7834f6e322661bd81\",\"saltLen\":32,\"dklen\":32,\"iv\":\"82a4b6a9e3eabbead6d44c8a7094a134\"},\"sno\":35}
线程 5448 已创建,入口:ucrtbase.0D6B2450,参数:1B9BC088
线程 2192 已创建,入口:ucrtbase.0D6B2450,参数:1BA87EA0
线程 5448 退出
线程 5800 已创建,入口:ucrtbase.0D6B2450,参数:1BB1CB60
线程 5800 退出
线程 8136 退出
esp+0xc 30654ac2b3fae18b61b94892d508d07a6f3d13579323ec21bcc241a4171d3aff  esp+18  {\"description\":\"tup_commonlib_crypt_pkcs5_derive_key_new\",\"cmd\":262153,\"param\":{\"salt\":\"3b5ff508ca97d9c7834f6e322661bd81\",\"saltLen\":32,\"dklen\":32,\"iv\":\"82a4b6a9e3eabbead6d44c8a7094a134\"},\"sno\":75}
esp+0xc c3fdc09d0e2e8f95e81f4e90428edcbb  esp+18  {\"description\":\"tup_commonlib_crypt_realrandom\",\"cmd\":262148,\"param\":{\"RandLen\":16},\"sno\":78}
esp+0xc e3f0986d04ed56df16a53e8b3b14f17f  esp+18  {\"description\":\"tup_commonlib_crypt_realrandom\",\"cmd\":262148,\"param\":{\"RandLen\":16},\"sno\":79}
esp+0xc 00000002000000000000000000000001000000074f9486c9aae79cfa8ff031ecd16db6121e536eeb602afcf187a949d0057496ee000000000000000000000030d6d9001e1be9fda077d3bd1f9f06f700e7cb4a1c0690d8f8babeb856490c0f62671aa1f787b5f227e9feb9927a10ef70  esp+18  
esp+0xc 3af0f12e9927dd4d518aa1c9ce34860729c7e6cef79b518ea43fe39119a08b06  esp+18  {\"description\":\"tup_commonlib_crypt_pkcs5_derive_key_new\",\"cmd\":262153,\"param\":{\"salt\":\"c3fdc09d0e2e8f95e81f4e90428edcbb\",\"saltLen\":32,\"dklen\":32,\"iv\":\"e3f0986d04ed56df16a53e8b3b14f17f\"},\"sno\":83}
esp+0xc 4d24b8706d8c427c4c844f893f061f98  esp+18  {\"description\":\"tup_commonlib_crypt_encrypt\",\"cmd\":262146,\"param\":{\"Key\":\"3af0f12e9927dd4d518aa1c9ce34860729c7e6cef79b518ea43fe39119a08b06\",\"IV\":\"c3fdc09d0e2e8f95e81f4e90428edcbb\",\"PlainText\":\"plain\"},\"sno\":85}
线程 3268 已创建,入口:ucrtbase.0D6B2450,参数:1BA46818


0F96DEC8  348373D0  "{\"description\":\"tup_commonlib_crypt_pkcs5_derive_key_new\",\"cmd\":262153,\"param\":{\"salt\":\"4999e9982e8083c92d5e2b1434711c5c\",\"saltLen\":32,\"dklen\":32,\"iv\":\"26b0d63de8d69e424b4f86eff60a765b\"},\"sno\":58}"

分析上面的日志

程序生成两个随机数

其中一个随机数加密后保存为sys里面的IV

一个数据保存为sys的随机数数据

这两个数据作为参数tup_commonlib_crypt_pkcs5_derive_key_new 使用这个命令加密,得到加密的密钥

c++ 测试

   char test_user_name1[] = "\x49\x99\xe9\x98\x2e\x80\x83\xc9\x2d\x5e\x2b\x14\x34\x71\x1c\x5c";
   char test_user_name2[] = "\x26\xb0\xd6\x3d\xe8\xd6\x9e\x42\x4b\x4f\x86\xef\xf6\x0a\x76\x5b";
   ((pfn_PKCS5_deriveKey)func)(0x31, 0x1, (char*)test_user_name2, 0x10, (char*)test_user_name1, 0x10, 0, 0x20, cipher_salt);


   typedef void (*pfn_CRYPT_decrypt)(int,char* key,int key_len,char* iv,int iv_len,char* text,int text_len,char* out,int* out_len,int );
   HMODULE libipsi_crypto = LoadLibraryA("libipsi_crypto.dll");
   auto CRYPT_decrypt = GetProcAddress(libipsi_crypto, "CRYPT_decrypt");
   char text[0x10] = {
0x55, 0xC0, 0x3F, 0x61, 0xA0, 0x72, 0x68, 0xE6, 0xBF, 0x92, 0xE0, 0xED, 0xB2, 0x7D, 0xF5, 0x00
   };



   char out_text[0x100] = {};
   int out_text_len = 0x100;
   //最后一个参数为密文长度+1
   ((pfn_CRYPT_decrypt)CRYPT_decrypt)(0x1001b, cipher_salt, 0x20, test_user_name1, 0x10, text, 0x10, out_text, &out_text_len, 0x11);

完整代码

Parsedb.cpp

流程图

解析程序开发

im /appdata 下文件解密失败

手动解析出user.db 文件,与开发程序解密出的文件做对比。发现每页末尾有16个字节不同。解密程序的数据都是0

调试发现,aes 解密时得到的大小比输入文本小 。输入0x1000 输出0xff0

aes 解密时设置禁止填充

所需dll

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值