目录

WPS Office 的 Product.dat 与 Oem.ini 配置文件哈希校验的逆向分析

本文简介

WPS 的默认配置使用 Product.dat 存储,而 Oem.ini 则存储一些用户自定义的设置,但这两个文件的配置信息都是加密且经过数字签名的,所以本文尝试通过逆向分析了解 WPS 对这两个文件数字签名的校验过程。

逆向分析

首先,我们尝试修改 office6\cfgs 目录中的 product.dat,比如这里将结尾的数字签名随便改掉一位,然后启动 WPS 后发现弹出了对话框提示 “WPS Office 启动文件损坏”,那我们就从这里入手,给 MessageBox 系列函数打上断点。

1
2
3
4
5
6
int MessageBoxW(
    [in, optional] HWND    hWnd,
    [in, optional] LPCWSTR lpText,
    [in, optional] LPCWSTR lpCaption,
    [in]           UINT    uType
);

这里使用的是 64 位版本的 WPS,所以用 x64dbg 来调试,运行直到断点,发现断在了 MessageBoxW 函数上,查看寄存器,rdx 中正是我们之前看到的错误提示。

/344ad72a-4c26-445f-a731-b9f58ca5264f/assets/Snipaste_2025-01-13_15-06-05.png
断在了 MessageBoxW 函数

运行到返回,看到调用消息框的函数在 kprometheus.dll 中,那么复制文件偏移,去 IDA 看看。

/344ad72a-4c26-445f-a731-b9f58ca5264f/assets/Snipaste_2025-01-13_18-36-04.png
调用处位于 kprometheus.dll

在 IDA 中跳转到文件偏移,发现这个动态库居然包含一部分符号信息,这就简单太多了。此时位于 KPromeApplication::configsTamperingError 函数中,大概看一下整个函数的结构,发现主要就是两部分,先调用 krt::configs::getOemIniValid,如果通过则调用 krt::configs::getProductDatValid,如果都验证没问题就直接退出函数,否则会进行一些验证失败的操作。

/344ad72a-4c26-445f-a731-b9f58ca5264f/assets/Snipaste_2025-01-13_20-29-33.png
krt::configs::getOemIniValid 的调用处
/344ad72a-4c26-445f-a731-b9f58ca5264f/assets/Snipaste_2025-01-13_20-31-13.png
krt::configs::getProductDatValid 的调用处

这两个函数是从 krt.dll 中引入的,这里分析 krt::configs::getProductDatValid,用 IDA 打开并搜索这个函数,看到 productReader_0 从全局变量中获取 krt::configs 类的地址,看得出这似乎是个全局单例类,而 sub_180016310 是这个类中标志 Product.dat 是否合法的成员变量的 Get 函数。

/344ad72a-4c26-445f-a731-b9f58ca5264f/assets/Snipaste_2025-01-13_20-35-19.png
krt::configs::getProductDatValid 的定义
/344ad72a-4c26-445f-a731-b9f58ca5264f/assets/Snipaste_2025-01-13_20-36-36.png
productReader_0 的定义
/344ad72a-4c26-445f-a731-b9f58ca5264f/assets/Snipaste_2025-01-13_20-38-08.png
sub_180016310 的定义

接下来我们要找到程序在哪里设置了这个标志位的值,所以回到 x64dbg,跟踪到 krt::configs::getProductDatValid 函数中,执行到 call productReader_0 结束以得到常量的地址,将 rax 中的地址转到内存窗口查看,然后将 0x30 处打上内存写入断点。

/344ad72a-4c26-445f-a731-b9f58ca5264f/assets/Snipaste_2025-01-13_20-55-11.png
将 0x30 处打上内存写入断点

重新运行,看到内存断点断了下来,复制文件偏移到 IDA,发现这里甚至有字符串 krt::anonymous-namespace::ensureValidProductFile。(大意了,早知道应该先查查字符串的 >_<)

/344ad72a-4c26-445f-a731-b9f58ca5264f/assets/Snipaste_2025-01-13_21-02-30.png
内存断点中断
/344ad72a-4c26-445f-a731-b9f58ca5264f/assets/Snipaste_2025-01-13_21-05-58.png
中断位置伪代码

到处看看,运气很好,校验函数就是krt::configs 类初始化附近的 sub_7FFDAA09ECD0 函数(上图第 67 行),这个函数从配置文件结尾读取了 512 个字符作为数字签名,并将文件剩余的部分做 MD5 后与其进行校验。

/344ad72a-4c26-445f-a731-b9f58ca5264f/assets/Snipaste_2025-01-13_21-12-30.png
校验函数
/344ad72a-4c26-445f-a731-b9f58ca5264f/assets/Snipaste_2025-01-13_21-15-31.png
校验函数 MD5 部分

翻到 sub_7FFDAA09D960 的时候,发现这里面很蹊跷,有一大串的硬编码,这很可能就是密钥之类的东西。

/344ad72a-4c26-445f-a731-b9f58ca5264f/assets/Snipaste_2025-01-13_21-28-03.png
校验函数的更深处
/344ad72a-4c26-445f-a731-b9f58ca5264f/assets/Snipaste_2025-01-13_21-29-56.png
硬编码的 RSA 公钥

在函数的结尾,发现了一个大宝贝!居然有符号名。这个函数的第一个参数是前面算出来的,后两个参数就是 sub_7FFDAA09D960 的两个参数。

/344ad72a-4c26-445f-a731-b9f58ca5264f/assets/Snipaste_2025-01-13_21-32-08.png
krt::kcodec::KRSAVerifyFile 的调用处

查看 krt::kcodec::KRSAVerifyFile 函数,这里就是 RSA 校验的地方,是用了 CryptoPP 库。

/344ad72a-4c26-445f-a731-b9f58ca5264f/assets/Snipaste_2025-01-13_21-37-52.png
krt::kcodec::KRSAVerifyFile 的定义

后记

又是对 WPS 下手的一天~
这次,内置的很多符号让逆向轻松了不少。
下一章写写怎么做一个过校验的补丁吧。