Directory Opus 13.2 逆向分析
文章仅供学习使用,切勿用于非法用途。如有造成侵权,请及时联系作者处理。
Ⅰ.写在前面
DIrectory Opus是一款非常优秀的Windows 文件多功能资源管理器,下面是来自官网的一个简单介绍:
“Windows 系统自带的资源管理器固然好用,但是对于需求很多的用户来说它就远远不够,因此专业且强大的文件管理器成为很多用户心目中的选择,而 Directory Opus 就可以完美的替代系统默认的资源管理器,成为你管理资源中更好的帮助。与系统自带的相比它有更高度自由的界面定制,1000位使用者中有 1000 种喜爱,而它 100% 自定义性可以完美的满足每一位使用者,让大家都可以使用的满意。与系统自带的相比它使用起来更简单,不需要学习任何复杂的脚本或者是技术就可以上手使用,运作方式与 Explorer 或许有些相似,人人都可以快速的使用它。与系统自带的相比它还有更多出色的功能,如计算文件大小、切换格式、模式选择、FTP 链接、预览功能、备份与恢复等等,这些功能可以使操作文件变得更加轻而易举,不管是需要功能强大还是简单易用,它都可以成为大家心目中的首选。”
但是有朋友指出再其早期版本存在资源占用高的问题,提到“资源占用”的问题也是瞬间激起了笔者对其最新版本进行逆向分析兴趣。所以有了这篇文章。
网络上并没有对DIrectory Opus进行逆向分析的一些较为详细的文章,比较早的是cntrump前辈对DIrectory opus 9 版本的一些证书提取的思路。
[原创]偷出 Directory Opus 9 的授权证书 -- 另类提取被 Thinapp 打包的文件-软件逆向-看雪-安全社区|安全招聘|kanxue.com
此文章并没有生成注册机,而是使用其12版本注册码进行注册。文章不提供下载,读者如果有需求可与笔者进行交流学习。笔者水平有限,如果文章内容有错误或者读者有更好的思路,还请不吝赐教。
Ⅱ.整体思路
笔者在之前使用的12.3版本的DO使用的是 文件替换+证书验证 的方式,推测可能使用了类似RSA公钥替换攻击,我们可以沿用这一方法。这里笔者从网上下载了之前公布的一些破解版。

可以看到对原始的几个文件进行了替换。其中Language和Viewers文件夹从名字上就容易猜到不是我们主要进行逆向的目标,而dopuslib.dll可以推测其提供一些库函数供其他逻辑调用,根据文件大小可以猜测程序主要逻辑在dopus.exe
这里一个思路是:使用12.33版本的源文件与被patch后的文件进行bindiff比对,但因为大版本的更新,需要将12.33两个版本进行比对,然后移植到13.2版本上,不敢说这样一定会减少逆向的工作量,所以bindiff比对这里只作为一种参考辅助。
Ⅲ.分析
1. 完整性校验
首先这里笔者想关闭dopuslib.dll和dopus.exe的ASLR时很快就遇到了完整性校验问题。

可以猜测进行完整性校验时可能会使用CreateFilew,使用xdbg对其下日志断点,记录打开的文件

将<a name="CreateFileW调用日志">CreateFileW调用日志</a>导出,可以发现其并没有打开dopus.exe或者dopuslib.dll,如下,推测其可能不是使用CRC的方式计算完整性。但是留意到程序多次打开了dopus.cert文件,推测是证书的存根。读者可以记录试用时的证书,与下文注册成功的dopus.cert进行对比。

这里联想到另一种使用WIndows受信组件验证方式,其核心API:WinVerifyTrust,一个简单介绍:
【windows + 证书验证】 验证数字签名的方法 - mooooonlight - 博客园 (cnblogs.com)
同样,在其PE结构中也有相应字段暗示:

对WinVerifyTrust下日志断点,记录感兴趣的返回地址以及验证的文件。

断点被触发,简单整理

两个位于dopuslib.dll一个位于dopus.exe,转到相应位置
在dopus.exe中 VA—>0x140967A93

可以看到调用方式为间接调用,这里记录
1 2 3 4 5 6 7 8 9 10 | WinVerifyTrust在dopus中的引用:Direction Type Address TextDown r 140FD0490 mov rax, cs:qword_1418DBA50Down r 140FD03D1 cmp cs:qword_1418DBA50, rsiDown r 140A49941 cmp cs:qword_1418DBA50, 0Up r 140967A7F mov rax, cs:qword_1418DBA50Up r 140967817 cmp cs:qword_1418DBA50, 0Up r 14083784B cmp cs:qword_1418DBA50, 0Up r 14082353B cmp r9, cs:qword_1418DBA50Up w 14004401F mov cs:qword_1418DBA50, rax |
dopuslib.dll中VA—>0x180035AE7 & 0x180035BDB同样可以看到对其调用方式为GetProcAddress间接调用


可以看到字符串进行了简单的拼接加密,简单解密脚本如下:
1 2 3 4 5 6 7 | def decStr(input, index): str_list = list(input)[index % 5:] output = '' for i in range(0,len(str_list),5): if(ord(str_list[i]) != 33): output+=str_list[i] print(output) |
可以看到解密出很多敏感字符串

在dopus.exe中 VA—>0x140CC19C0 & 0x140CC1A5E

可以看到同样有一些字符串加密,其实是一个简单的Base64换表以及一个异或,这里不多赘述。
接下来将patch后的文件替换会原文件,并对五个检查位置下断点,获取WinVerifyTrust的返回值。读者可能觉得多次一举,因为WinVerifyTrust验证成功返回0,其余值都是不授信的。

收集到的信息以及简单的patch如下:


可以看到对于0x140967A93处的patch需要多加小心在第一调用时,返回0x800B0100但在第二次时返回成功。这里笔者对这一处的处理是在patch掉0000000140967A8A使其eax值为0x800B0100并禁用掉第二次的调用,简单对这个位置下断点,可以找到第二次调用位置0x00140966DFE
将如下所指区域的调用全部nop掉即可。

patche之后运行一段时间会发下,程序虽然没有退出,但是出现了异常:

会发现日志里多了一条WinVerifyTrust的调用,同样patch返回值为0即可。

程序不在出现异常。
2. 程序证书替换
对于证书存放的位置,这里读者使用的方法是对使用对GetWindowsTextW下断

进行栈回溯,比较简单就可以定位到0000000140CEEB76,函数sub_140CEAF50为证书验证逻辑。

跟进0000000140CEEB76,进行一些逆向分析可以发现函数sub_140CE6EC0的第三个参数为key

猜测其可能是使用一个全局变量来存储证书内容,对qword_1418BF450下写入断点,可以定位到位置0000000140CCE331,整个函数在完成初始化操作,在这里对a1 + 0xDC0下写入断点

定位到写入位置0000000140CCE7A1:

可见0xDC0位置数据来自于dopuslib.dll,跟进dopuslib_23

这里出现了比较熟悉的字符串,跟进dopuslib_10可以发现其加载名为RSA的资源,秘钥存放在资源里,我们可以用12.33版本中的RSA公钥进行替换。看起来DO有两种的验证方式,而证书验证默认使用的是不是RSA,我们可以patch为使用RSA

替换的公钥:
1 2 3 4 5 6 7 8 9 | 00 04 00 00 C1 22 5C 36 FD 3B 9C 3F D2 A5 B6 89 8B 0E C5 77 6A 78 23 F8 A1 E3 DB 15 94 D7 83 C4 00 A8 55 0B 83 14 43 16 E3 E1 6B 68 50 FA 36 B2 0D 06 F8 86 4C AC 6A 24 81 8F 49 CD 89 F4 35 8C7F 26 0C F2 36 27 20 94 24 A3 ED 27 59 A0 AF 28 36 07 65 7B ED 31 75 EA 88 8F CF 35 E2 95 96 0D73 59 63 76 1D DA 89 48 C7 19 6C 30 F6 DE 2D 97 F4 1F 04 D6 73 73 FA 22 ED 57 E1 8C CC C6 83 FA 08 7E 73 E1 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000 01 00 01 |

再次运行后发生了栈溢出

定位到发生溢出的位置0018003CAD4,可以推测是加载语言的dll时出现了问题,如下:

跟进sub_18003CD00

为了让程序正常运行,需要patch dopuslib_14返回1,dopuslib_14似乎整个程序只是用于验证dll的合法性,所以直接patchdopuslib_14,patch内容以及位置如下
000000018003D05A | 0F84 CF000000 | je dopuslib.18003D12F | 000000018003D060 | 33D2 | xor edx,edx | 000000018003D08F | 0F84 9A000000 | je dopuslib.18003D12F | 000000018003D095 | 817C24 30 90000000 | cmp dword ptr ss:[rsp+30],90 | 000000018003D12F | B8 01000000 | mov eax,1 | 000000018003D134 | 90 | nop |
3. 注册以及解决证书存根验证问题
重新运行,可见程序正常运行,并输入替换公钥对应的注册码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | -----BEGIN GPSOFT PROGRAM CERTIFICATE-----DGuOAAKkWbKadvhL5aDx/289NPqpFkZ5t0nWrEKIbYUvL7UgAVPDBy44gb0YuDVV6w4ICytAywy0jYpN3GRuGg13hv/xLjn8uXiFDT1szlVhEdyEpxVsd1lFyED1y6hjPuu2hs0jHiRpiS8w1RMF7/F7GqoIhj3AqhhHYO/8Mh2kA4kF5ofGuOADAEetKGx30HIBAdFK/z/mq41/uqM3c1b3JqIfgMstgIwj/4dQquLL5cC0yK0nDOOrYxlQ5eUrLgq4fPEM6DP5kvKaI8GXiDBg4oj464qZM/XMNCFnXOmFpvWIID+oRxNHQJe+2S+7TxJb247mBMzCoqPOFV8ED0PIJqqcL46rPzllGuGuOAAAbPD2j4rzUb4KnEA6vdKTDYtreCpWBxzougWvWcQRFtyuCjPTurx49VBbCw6ePfaD9+gFUgOXBBTHOejk+9/eQxP1KMZOmsz0Od34vYEOf00l8APgxv1Ws3sDIdGmDx1bP8xk3Sk8nRfRqXcDVh1odQUhtS2l9JJKfFA0KRq4PGXrlGuOAAFZqs7+jDHV6hs9LnH/DfplhfqcTWOovrUInnzXeRW5sWIr60yHLLBJBy4F21ZgTelbiTDNnFF8xdtOaB9D101zpsIsHM3AoaKq0S7W9uHHElxI0R6zPlbIDwB3KXtLBSRDRxe7AJntH8oP2SOMBLm2xcMVeAKzDIGCim8xlczlDpveGuOAAAEYK64KAEG5D9mijNQ9wfEFhKgHH02/Gid0QY5ROhSJvkc4pBtpMEZ/Xp6t66R398I6JfSDHDMymwKgWcXhd+fcuQ9KPkCdGScfeVc8WsyvYKc/YPqUEQHbxdLTitqMFdZjtID5HFRveoaxyVlXjerup5chjI4Uvniqazq9EiunEBHce1qDfukmiuD+JLiGziFBAw==-----END GPSOFT PROGRAM CERTIFICATE----- |

但是重启程序后依然会弹出未授权的提示。这里联想到在上方的CreateFileW日志中,程序大量使用CreateFileW打开opus.cert。通过对比可以发现opus.cert在我们注册完成之后已经发生了变化。所以我们的注册逻辑是成功的,证书已经成功被写入了存根,而在此打开提示未授权的原因大概率出在对存根证书的验证逻辑上。
同样在CreateFileW下条件断点

整理得到如下调用:
1 2 3 4 5 6 7 8 9 | CreateFileW file--->C:\\ProgramData\\GPSoftware\\Directory Opus\\dopus.cert retAddr--->0x1401E3197CreateFileW file--->C:\\ProgramData\\GPSoftware\\Directory Opus\\dopus.cert retAddr--->0x1401E3197CreateFileW file--->C:\\ProgramData\\GPSoftware\\Directory Opus\\dopus.cert retAddr--->0x1401E3197CreateFileW file--->C:\\ProgramData\\GPSoftware\\Directory Opus\\dopus.cert retAddr--->0x1401E3197CreateFileW file--->C:\\ProgramData\\GPSoftware\\Directory Opus\\dopus.cert retAddr--->0x1401E3197CreateFileW file--->C:\\ProgramData\\GPSoftware\\Directory Opus\\dopus.cert retAddr--->0x1401E3197CreateFileW file--->C:\\Program Files\\GPSoftware\\Directory Opus:stockcert13 retAddr--->0x1401E3197CreateFileW file--->C:\\ProgramData\\GPSoftware\\Directory Opus\\dopus.cert:naughtypirates retAddr--->0x140CE5C0FCreateFileW file--->C:\\ProgramData\\GPSoftware\\Directory Opus\\dopus.cert retAddr--->0x1401E3197 |

0x1401E3197位置所在的函数是一个公共函数,其有很多的引用。这里的话只能硬着头皮去分析了,8个调用工作量也不算太大。
对函数CreateFileW所在的函数sub_1401E30F0下断点。

最终定位VA0000000140CE721A中的如下位置:

经过上面的分析我们知道qword_1418BF450 + 0xDC0所在的内存区域,这里对公钥进行了哈希,与程序内置的一串哈希进行比对,这里肯定是会失败的。所以我们强跳在这个检测就可以。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 0000000140CE8533 | 48:3B45 A0 | cmp rax,qword ptr ss:[rbp-60] |0000000140CE8537 | EB 27 | jmp dopus.140CE8560 |0000000140CE8539 | 48:8B05 208CB800 | mov rax,qword ptr ds:[141871160] |0000000140CE8540 | 48:3B45 A8 | cmp rax,qword ptr ss:[rbp-58] |0000000140CE8544 | EB 1A | jmp dopus.140CE8560 |0000000140CE8546 | 45:33FF | xor r15d,r15d |0000000140CE8549 | 41:8BDF | mov ebx,r15d |0000000140CE854C | 48:895C24 78 | mov qword ptr ss:[rsp+78],rbx |0000000140CE8551 | BA 00020000 | mov edx,200 |0000000140CE8556 | 48:8BCF | mov rcx,rdi | 0000000140CE8559 | E8 62800400 | call dopus.140D305C0 |0000000140CE855E | EB 03 | jmp dopus.140CE8563 |0000000140CE8560 | 45:33FF | xor r15d,r15d | |
4. 公钥完整性检测
重新运行后进入主程序,但是马上就出现了完整性校验失败,我们可以排除WinVerifyTrust校验问题

前面我们分析到程序大概率没有使用CRC对自身完整性进行校验,但是可能会对证书进行校验呢,事实上证实如此,我们可以在dopus.exe的函数窗口搜索CRC,如下,可以看到虽然没有对CalcCRC32的直接引用,但是对其中的函数sub_1412A4960有些许引用。这里我们可以对此函数下断,根据传入的数据跟我们已知的内容(比如证书存根或者我们替换的publickey)进行比对,来找到可疑位置。

因为考虑到对CRC函数进行下断分析工作可能会非常繁重,所以这里笔者通过bindiff比对的方式,将原12.33版本的opus.exe与网上公布的同版本破解版进行比对,发现其大体的修复思路相同。我们注意到如下位置:

一个简单的分析结果如下

所以我们使用正常的程序拿到一个正常的哈希值来替换这个结果即可。patch如下
1 | 0000000140CD6D6E | B8 F90F2B14 | mov eax,142B0FF9 |0x142B0FF9为正常公钥的哈希值 |
最终结果:

Ⅳ.总结
总的来说,整个程序的反逆向工程结构如下
- 通过Windows受信组件(WinVerifyTurst)对文件完整性进行校验
- 使用程序中自己实现的Hash对公钥进行校验
- 将程序运行的关键组件(窗口类名)与公钥信息Hash进行绑定
比较有意思的是,程序维护一个区域0x00000001801C0230并将检测结果维护其中,其中的某些字段会引起程序的异常行为而导致逆向成本的增加,对产品安全性有一定的提升。
2024年3月5日更新
在docs.dll RVA:EC72 中新增一处过完整性校验Patch,Patch为jmp即可,在调试过程中出现出现一些域名信息,可能系网络验证,找到这个点的思路同样是对WinverifuTrust下断记录即可,出于时间原因不再进一步逆向,有兴趣的朋友可以深入研究。

2024年3月29日更新
dopus.exe RVA:0xcc7a0f出触发了一些文件完整性校验

定位方法使用比较简单的对检测弹出的窗口进行附加查看窗口启动的命令行信息:/BADSHOW1,然后对CreateProcessW下断,以命令行特征为断点,很快可以定位到线程函数,向上回溯就可。