文章仅供学习使用,切勿用于非法用途。如有造成侵权,请及时联系作者处理。
一
前言
DIrectory Opus是一款非常优秀的Windows 文件多功能资源管理器,下面是来自官网的一个简单介绍:
“Windows 系统自带的资源管理器固然好用,但是对于需求很多的用户来说它就远远不够,因此专业且强大的文件管理器成为很多用户心目中的选择,而 Directory Opus 就可以完美的替代系统默认的资源管理器,成为你管理资源中更好的帮助。与系统自带的相比它有更高度自由的界面定制,1000位使用者中有 1000 种喜爱,而它 100% 自定义性可以完美的满足每一位使用者,让大家都可以使用的满意。与系统自带的相比它使用起来更简单,不需要学习任何复杂的脚本或者是技术就可以上手使用,运作方式与 Explorer 或许有些相似,人人都可以快速的使用它。与系统自带的相比它还有更多出色的功能,如计算文件大小、切换格式、模式选择、FTP 链接、预览功能、备份与恢复等等,这些功能可以使操作文件变得更加轻而易举,不管是需要功能强大还是简单易用,它都可以成为大家心目中的首选。”
但是有朋友指出再其早期版本存在资源占用高的问题,提到“资源占用”的问题也是瞬间激起了笔者对其最新版本进行逆向分析兴趣。所以有了这篇文章。
网络上并没有对DIrectory Opus进行逆向分析的一些较为详细的文章,比较早的是cntrump(https://bbs.kanxue.com/user-home-48065.htm)前辈对DIrectory opus 9 版本的一些证书提取的思路。
[原创]偷出 Directory Opus 9 的授权证书 -- 另类提取被 Thinapp 打包的文件-软件逆向-看雪-安全社区|安全招聘|kanxue.com
https://bbs.kanxue.com/thread-120978.htm
此文章并没有生成注册机,而是使用其12版本注册码进行注册。文章不提供下载,读者如果有需求可与笔者进行交流学习。笔者水平有限,如果文章内容有错误或者读者有更好的思路,还请不吝赐教。
二
整体思路
笔者在之前使用的12.3版本的DO使用的是文件替换+证书验证的方式,推测可能使用了类似RSA公钥替换攻击,我们可以沿用这一方法。这里笔者从网上下载了之前公布的一些破解版。
可以看到对原始的几个文件进行了替换。其中Language
和Viewers
文件夹从名字上就容易猜到不是我们主要进行逆向的目标,而dopuslib.dll
可以推测其提供一些库函数供其他逻辑调用,根据文件大小可以猜测程序主要逻辑在dopus.exe。
这里一个思路是:使用12.33版本的源文件与被patch后的文件进行bindiff比对,但因为大版本的更新,需要将12.33两个版本进行比对,然后移植到13.2版本上,不敢说这样一定会减少逆向的工作量,所以bindiff比对这里只作为一种参考辅助。
三
分析
首先这里笔者想关闭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)
https://www.cnblogs.com/mooooonlight/p/14034592.html
同样,在其PE结构中也有相应字段暗示:
对WinVerifyTrust
下日志断点,记录感兴趣的返回地址以及验证的文件。
断点被触发,简单整理。
两个位于dopuslib.dll
一个位于dopus.exe
,转到相应位置。
在dopus.exe
中 VA—>0x140967A93
可以看到调用方式为间接调用,这里记录。
WinVerifyTrust在dopus中的引用:
Direction Type Address Text
Down r 140FD0490 mov rax, cs:qword_1418DBA50
Down r 140FD03D1 cmp cs:qword_1418DBA50, rsi
Down r 140A49941 cmp cs:qword_1418DBA50, 0
Up r 140967A7F mov rax, cs:qword_1418DBA50
Up r 140967817 cmp cs:qword_1418DBA50, 0
Up r 14083784B cmp cs:qword_1418DBA50, 0
Up r 14082353B cmp r9, cs:qword_1418DBA50
Up w 14004401F mov cs:qword_1418DBA50, rax
dopuslib.dll
中VA—>0x180035AE7 & 0x180035BDB同样可以看到对其调用方式为GetProcAddress
间接调用。
可以看到字符串进行了简单的拼接加密,简单解密脚本如下:
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即可。
程序不在出现异常。
对于证书存放的位置,这里读者使用的方法是对使用对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。
替换的公钥:
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 8C
7F 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 0D
73 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 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 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 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 00 00 00 00 00 00 00
00 01 00 01
再次运行后发生了栈溢出。
定位到发生溢出的位置0018003CAD4
,可以推测是加载语言的dll时出现了问题,如下:
跟进sub_18003CD00。
为了让程序正常运行,需要patchdopuslib_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 |
重新运行,可见程序正常运行,并输入替换公钥对应的注册码。
-----BEGIN GPSOFT PROGRAM CERTIFICATE-----
DGuOAAKkWbKadvhL5aDx/289NPqpFkZ5t0nWrEKIbYUvL7UgAVPDBy44gb0Yu
DVV6w4ICytAywy0jYpN3GRuGg13hv/xLjn8uXiFDT1szlVhEdyEpxVsd1lFyE
D1y6hjPuu2hs0jHiRpiS8w1RMF7/F7GqoIhj3AqhhHYO/8Mh2kA4kF5ofGuOA
DAEetKGx30HIBAdFK/z/mq41/uqM3c1b3JqIfgMstgIwj/4dQquLL5cC0yK0n
DOOrYxlQ5eUrLgq4fPEM6DP5kvKaI8GXiDBg4oj464qZM/XMNCFnXOmFpvWII
D+oRxNHQJe+2S+7TxJb247mBMzCoqPOFV8ED0PIJqqcL46rPzllGuGuOAAAbP
D2j4rzUb4KnEA6vdKTDYtreCpWBxzougWvWcQRFtyuCjPTurx49VBbCw6ePfa
D9+gFUgOXBBTHOejk+9/eQxP1KMZOmsz0Od34vYEOf00l8APgxv1Ws3sDIdGm
Dx1bP8xk3Sk8nRfRqXcDVh1odQUhtS2l9JJKfFA0KRq4PGXrlGuOAAFZqs7+j
DHV6hs9LnH/DfplhfqcTWOovrUInnzXeRW5sWIr60yHLLBJBy4F21ZgTelbiT
DNnFF8xdtOaB9D101zpsIsHM3AoaKq0S7W9uHHElxI0R6zPlbIDwB3KXtLBSR
DRxe7AJntH8oP2SOMBLm2xcMVeAKzDIGCim8xlczlDpveGuOAAAEYK64KAEG5
D9mijNQ9wfEFhKgHH02/Gid0QY5ROhSJvkc4pBtpMEZ/Xp6t66R398I6JfSDH
DMymwKgWcXhd+fcuQ9KPkCdGScfeVc8WsyvYKc/YPqUEQHbxdLTitqMFdZjtI
D5HFRveoaxyVlXjerup5chjI4Uvniqazq9EiunEBH
ce1qDfukmiuD+JLiGziFBAw==
-----END GPSOFT PROGRAM CERTIFICATE-----
但是重启程序后依然会弹出未授权的提示。这里联想到在上方的CreateFileW日志中,程序大量使用CreateFileW
打开opus.cert
。通过对比可以发现opus.cert
在我们注册完成之后已经发生了变化。所以我们的注册逻辑是成功的,证书已经成功被写入了存根,而在此打开提示未授权的原因大概率出在对存根证书的验证逻辑上。
同样在CreateFileW
下条件断点。
整理得到如下调用:
CreateFileW file--->C:\\ProgramData\\GPSoftware\\Directory Opus\\dopus.cert retAddr--->0x1401E3197
CreateFileW file--->C:\\ProgramData\\GPSoftware\\Directory Opus\\dopus.cert retAddr--->0x1401E3197
CreateFileW file--->C:\\ProgramData\\GPSoftware\\Directory Opus\\dopus.cert retAddr--->0x1401E3197
CreateFileW file--->C:\\ProgramData\\GPSoftware\\Directory Opus\\dopus.cert retAddr--->0x1401E3197
CreateFileW file--->C:\\ProgramData\\GPSoftware\\Directory Opus\\dopus.cert retAddr--->0x1401E3197
CreateFileW file--->C:\\ProgramData\\GPSoftware\\Directory Opus\\dopus.cert retAddr--->0x1401E3197
CreateFileW file--->C:\\Program Files\\GPSoftware\\Directory Opus:stockcert13 retAddr--->0x1401E3197
CreateFileW file--->C:\\ProgramData\\GPSoftware\\Directory Opus\\dopus.cert:naughtypirates retAddr--->0x140CE5C0F
CreateFileW file--->C:\\ProgramData\\GPSoftware\\Directory Opus\\dopus.cert retAddr--->0x1401E3197
0x1401E3197
位置所在的函数是一个公共函数,其有很多的引用。这里的话只能硬着头皮去分析了,8个调用工作量也不算太大。
对函数CreateFileW
所在的函数sub_1401E30F0
下断点。
最终定位VA0000000140CE721A
中的如下位置:
经过上面的分析我们知道qword_1418BF450 + 0xDC0
所在的内存区域,这里对公钥进行了哈希,与程序内置的一串哈希进行比对,这里肯定是会失败的。所以我们强跳在这个检测就可以。
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 |
重新运行后进入主程序,但是马上就出现了完整性校验失败,我们可以排除WinVerifyTrust
校验问题。
前面我们分析到程序大概率没有使用CRC对自身完整性进行校验,但是可能会对证书进行校验呢,事实上证实如此,我们可以在dopus.exe
的函数窗口搜索CRC,如下,可以看到虽然没有对CalcCRC32
的直接引用,但是对其中的函数sub_1412A4960
有些许引用。这里我们可以对此函数下断,根据传入的数据跟我们已知的内容(比如证书存根或者我们替换的publickey)进行比对,来找到可疑位置。
因为考虑到对CRC函数进行下断分析工作可能会非常繁重,所以这里笔者通过bindiff比对的方式,将原12.33版本的opus.exe
与网上公布的同版本破解版进行比对,发现其大体的修复思路相同。我们注意到如下位置:
一个简单的分析结果如下:
所以我们使用正常的程序拿到一个正常的哈希值来替换这个结果即可。patch如下:
0000000140CD6D6E | B8 F90F2B14 | mov eax,142B0FF9 |0x142B0FF9为正常公钥的哈希值
最终结果:
四
总结
总的来说,整个程序的反逆向工程结构如下:
1.通过Windows受信组件(WinVerifyTurst)对文件完整性进行校验;
2.使用程序中自己实现的Hash对公钥进行校验;
3.将程序运行的关键组件(窗口类名)与公钥信息Hash进行绑定。
比较有意思的是,程序维护一个区域0x00000001801C0230
并将检测结果维护其中,其中的某些字段会引起程序的异常行为而导致逆向成本的增加,对产品安全性有一定的提升。