声明
本文所探讨的内容仅供学习和测试,请支持正版软件,学习测试中产生的后果由您自行承担。
环境描述
前言
跟着 utools V5 分析学习 这篇帖子搞 utools 逆向,但是遇到了一些问题,最大的问题就是修改之后程序无法启动,并且什么信息都没有,猜测应该是没通过完整性校验,而且跟 v5 版本不同的是,这次并没有弹窗。
将原本的 app.asar 替换回去,程序可以正常运行,于是我又在原本的 app.asar 添加了个字节,就无法启动了(我试了其他的 electron 应用,添加个字节是可以正常启动的),初步猜测是某种 hash 校验。
如无特别说明,本文出现的路径均为以 utools\resources 为工作目录的相对路径。如果你之前安装过 utools,最好用卸载工具把数据都清理一下。
动态分析
跟 v5 版本不同,这次没有弹窗,也没有任何信息提示,所以静态分析就不太好定位代码了。备份一下原文件。
cp .\app.asar .\app.asar.bak
把程序的进程都结束,然后给 app.asar 随便添加个字节,让程序无法校验通过。程序拉到 x64dbg 中,给 kernel32.CreateFileW 下断点,看程序都打开了哪些文件。程序可能会多次打开 app.sar,昨晚我调试的时候程序打开了三次这个文件,但今天写帖子重新调试的时候行为又不一样了。不过因为在前人的分析中已经得知 app.asar.unpacked\node_modules\addon\win32-x64.node 这个模块是负责完整性校验的,所以我们关心打开 app.asar 的时候 node 是否已经加载即可。如下图所示,程序断在了 CreateFileW,从寄存器窗口可见打开的文件为 app.asar,从栈窗口可见调用来自某个模块而不是 utools 主程序。
1
也可以在 符号 窗口中搜索 node 看到已经加载了这个 node 模块。这里的行为也可能不一样,我昨晚调试的时候加载的是类似 uuid.tmp.node 的文件,但是实际上是同一个模块,文件 MD5 都一模一样。
2
猜测这里接下来就要计算文件 hash 进行校验了,计算文件 hash 肯定需要读取文件内容,可以断 ReadFile 继续跟。不过这里我返回到调用处往下翻一下就看到相关函数了,如下图绿框所示。
3
很显然这里就是要计算 hash 进行比较了,不过我在这一层看了好久没找到比较的地方,于是便返回上一层去找。到上一层之后可以看 RAX 寄存器的返回值,看起来像是文件的 hash,长度为 32 字节,可能是 MD5。计算一下修改后的 app.asar 文件 hash 值,发现确实是 MD5。于是可以给函数备注为计算文件 MD5。
openssl dgst -md5 .\app.asar
MD5(.\app.asar)= 500b5264b5150569e78e1ed96e8912e0
4
在这一层继续分析接下来几个 call,可以看到 win32-x64.7FFB06CB4C00 返回了另一个 MD5,验证可知这就是原始文件的 MD5。我在 ida 中搜索了一下没有找到这个字符串,也就是说是经过编码或加密了。
openssl dgst -md5 .\app.asar.bak
MD5(.\app.asar.bak)= c95bd24c8e6b2454b838bf3afa6b700c
5
点进去可以看到 win32-x64.7FFB06CB4C00 这个函数非常简短,核心操作就是异或,这里也可以在 ida 中看伪代码分析,不过我图方便就直接在 x64dbg 中动态分析了,还能看看传了什么参数。如下图可以看到 RCX 传了一个字符串,RDX 传了 0x3E。在主循环中就是每异或一个字节就把 RDX 加一,然后继续异或下一个字节,重复此操作异或 32 个字节之后得到的就是原始文件的 MD5,所以这个函数就是负责解码的。
6
用 python 编写了一个编码解码函数方便后续 patch。
def bytes_to_hex_str(bs: bytes) -> str:
# 将bytes转成16进制表示的字符串,方便查看和patch进文件
return " ".join([f"{b:0{2}X}" for b in bs])
def decode_utools_md5(enc_md5: list, start_byte: int) -> list:
# 复刻 win32-x64.node 中的解码
# 异或
dec_md5 = enc_md5
for i in range(32):
dec_md5[i] = dec_md5[i] ^ start_byte
start_byte = start_byte + 1
return dec_md5
def encode_utools_md5(md5: str, start_byte: int) -> list:
# 把MD5编码回去,方便进行 patch
# 根据异或的特性,其实就是再异或一次,写个函数方便调用而已
enc_md5: list = list(bytes(md5.lower(), encoding="ascii")) # str转 int list
return decode_utools_md5(enc_md5, start_byte)
if __name__ == "__main__":
# 先把原来的MD5编码出来看看
orig_md5: str = "c95bd24c8e6b2454b838bf3afa6b700c"
orig_enc_md5 = encode_utools_md5(orig_md5, 0x3E)
print("原始MD5:" + orig_md5)
print("编码后的原始MD5:")
print(bytes_to_hex_str(orig_enc_md5[0:16]))
print(bytes_to_hex_str(orig_enc_md5[16:32]))
现在已经搞清楚是用 MD5 校验的,并且储存的 MD5 还经过编码,接下来就是分析这个编码之后的 MD5 储存在哪。返回之前的调用处可以看到编码后的 MD5 分为前后两个部分,每部分 16 字节,都来自数据段。在 ida 中可以找到这两个地址,查看交叉引用发现就只有一个地方引用了这俩。
7
8
Patch
搞清楚了是 MD5 校验,还知道编码解码方式,那么接下来 patch 的思路就很简单了:修改 app.asar 之后计算新的 MD5,编码 MD5 之后替换掉原本的两处即可。说个题外话,其实我也尝试把某些跳转改成不跳转,或者不跳转改成跳转来尝试跳过验证,但是修改了好几处都没能成功,可能是暗桩比较多吧,于是我选择直接从源头解决校验问题,就是直接替换 MD5。
接下来说一下 patch 过程中可能遇到的一些问题,以修改搜索框候补文字为例,已知占位符的位置为 node_modules\configuration\index.js。
解包 asar
在解包 app.asar 过程中可能会遇到一些问题,类似下面这样的错误,推测是打包发行的时候把一些开发用的依赖给去掉了。解决方法就是缺啥补啥,比如这里是 leveldown 这个模块缺了,就在 app.asar.unpacked 目录下执行 npm i leveldown@6.1.1 即可。注意版本要对应,注意第一次执行 npm i 可能会删除原来的 node_modules 文件夹。补全之后再次运行解包指令就没有报错了。
asar e .\app.asar unapp
Error: Unable to extract some files:
Error: ENOENT: no such file or directory, open 'C:\Users\Username\AppData\Local\Programs\utools\resources\app.asar.unpacked\node_modules\leveldown\deps\leveldb\leveldb-1.20\LICENSE'
修改 asar
打开解包出来的 unapp 文件夹,修改 node_modules\configuration\index.js 中对应的位置即可。
9
打包 asar
打包的时候有些注意事项,有些模块是不需要打包进去 asar 中的,addon 肯定不能打包进去,这里面包含了校验模块。leveldown 最好也不要打包进去,因为原本就没打包进去,configuration 模块建议不打包,方便之后随意修改占位符。打包指令如下:
asar p --unpack-dir "{node_modules\addon,node_modules\leveldown,node_modules\configuration}" .\unapp\ .\app.asar
修补
计算打包后的 app.asar MD5,编码之后替换 win32-x64.node 里的值即可,你可以用 ida 也可以用其他 hex 编辑器替换。
openssl dgst -md5 .\app.asar
MD5(.\app.asar)= c675acbf955fc260c48df863bf5759ef
python .\utools_hack.py
原始MD5:c95bd24c8e6b2454b838bf3afa6b700c
编码后的原始MD5:
5D 06 75 23 26 71 70 26 7E 22 7E 2B 78 7F 79 79
2C 77 63 69 30 35 67 34 30 36 6E 3B 6D 6B 6C 3E
新的MD5:c675acbf955fc260c48df863bf5759ef
编码后的新的MD5:
5D 09 77 74 23 20 26 23 7F 72 7D 2F 29 79 7A 7D
2D 7B 68 35 34 6B 62 66 34 31 6D 6E 6F 62 39 3B
就是简单的搜索替换,在 ida 中按 Alt+B 可搜索,替换的话建议用插件;在 010editor 中按 Ctrl+F 可搜索,Ctrl+Shift+V 直接就可以替换了。
10
成果展示
11
如果没有把 configuration 打包进去,可以在 app.asar.unpacked\node_modules\configuration\index.js 直接修改占位符,不用重新修补。说实话感觉 utools 的会员好像没啥用,基本都是云端的服务性质的功能,像那种服务器一般都会验证的,本地修改了也用不了。