WinRAR623的破解小记
最近看书的时候发现一个 20 年前的练手题——破解 WinRAR。 当年的版本是 3.42,现在的最新版已经是 6.23 了,源码层面应该有比较大的改动,于是手痒想尝试一下。
仅供个人参考学习,请不要拿作它用。
WinRAR官网链接
试用版激活
下载试用版之后,第一次打开会出现一个弹窗,提示用户需要购买。 如果不购买而直接试用,标题栏会带有提示 evaluation copy。
打开 Help > About WinRAR... ,可以看见试用期是 40 天。
选择 License > License and purchasing information > Purchasing information ,可以看到注册 WinRAR 的提示。 简单来说就是从官网购买授权,然后下载一个激活密钥 rarkey 放在程序目录下。
If you use WinRAR, you will need to copy the registration key file (rarreg.key) to a WinRAR folder or to %APPDATA%\WinRAR folder folder. By default WinRAR folder is "C:\Program Files\WinRAR", but it can be changed by a user when installing WinRAR. You can also drag rarreg.key file and drop it to WinRAR window to register.
If the key is archived in a .rar or .zip file, please extract rarreg.key from the archive before copying it. If archive name is rarkey.rar, another way to install the key file is to open such archive in WinRAR and answer "Yes" to confirmation prompt.
You can place rarreg.key to the same folder as WinRAR installer .exe file if you wish the installer to apply it automatically. Such rarreg.key file is used only if no previously installed key files are found.
IDA 打开 WinRAR.exe,首先看看这个字串的引用。 String 视图中没有它,直接在反汇编页面搜索 Text,找到 4 个函数
sub_45E7D0
sub_508040
sub_514830
sub_514E00
进入这几个函数检查字串周围,终于在 sub_514E00 找到这一段(函数名已经改过了)
1
2
3
v29 = (
const
WCHAR
*)GetFileNameW(FileName);
if
( wstrcmp(v29, L
"rarkey"
, 6) )
sub_4D12A0(0, 0);
sub_4D12A0 最终触发一个模板为 "REMINDER" 的 DialogBox,其处理函数中有一个链接 www.rarlab.com/reminder.php
。 浏览器打开是 WinRAR 的购买页面(200 多大洋),显然这个 DialogBox 就是第一次打开时的购买弹窗。 根据检测是否是第一次打开的逻辑,可以将代码还原如下
1
2
3
4
5
6
7
8
9
10
11
if
( hasUsedBefore && !a2 )
return
;
notRegistered = !sub_47ED80() && (trialDays > 40 || trialDays < 0);
if
( notRegistered )
{
hasUsedBefore = 1;
v26 = GetFocus();
DialogBoxParamW(hInst, L
"REMINDER"
, v26, sub_511C20, 0);
}
接下来点入 sub_47ED80 函数,发现它是全局变量 byte_5CC130 的 get 函数。 按 X 键查询引用找到另一个函数 sub_47EFD0,是这个全局变量的 set 函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
char
sub_47ED80()
{
return
byte_5CC130;
}
char
__stdcall sub_47EFD0(
char
a1)
{
char
result;
result = a1;
byte_5CC130 = a1;
return
result;
}
这时候逻辑就呼之欲出了:如果试用天数已经超过了 40 天并且还没有注册,那么就弹出窗口提示购买。 其中注册的信息 isRegistered 最终是储存在 byte_5CC130 中,sub_47ED80 是 getRegisterStatus,sub_47EFD0 是 setRegisterStatus。
猜想 setRegisterStatus 是注册验证的最终归宿,如果让这个函数始终将 isRegistered 设置为 1,就可以绕过验证算法。
1
2
3
4
5
setRegisterStatus proc near
mov al, [esp
+
4
]
mov isRegistered, al
retn
4
setRegisterStatus endp
mov al, [esp+4]
占 4 字节,可以改成 xor al, al
和 inc al
各 2 字节,完美。 应用 patch,打开看一下,标题的 evaluation copy 没有了,原本的 40 天提示也变成了字串 "registered to"。
修改注册信息
由于我们强行绕过了验证算法,"registered to" 后面并没有用户名。 秉着一贯到底的原则,接下来破解 About 对话框并署名。
由于已经知道弹窗调用的 API 是 DialogBoxParamW,故用 OllyDBG 附加 WinRAR,在 DialogBoxParamW 处下断点。 选择 枚举目标导入表函数 > USER32.dll > DialogBoxParamW ,然后打开 About 对话框,可以看到断点命中。
查看当前的栈
00F05E9B CALL 到 DialogBoxParamW 来自 WinRAR.00F05E95
00DF0000 hInst = 00DF0000
00F5B448 pTemplate = "ABOUTRARDLG"
011A06CC h0wner = 011A06CC
00EF7280 DlgProc = WinRAR.00EF7280
00000000 lParam = NULL
在 IDA 中定位 DlgProc,浏览一遍之后,找到下面这一段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
v21 = (
const
WCHAR
*)sub_47ED70();
if
( getRegisterStatus() )
{
v22 = (
const
WCHAR
*)sub_4B6AC0(0x3C0u);
SetDlgItemTextW(v4, 102, v22);
SetDlgItemTextW(v4, 103, v21);
SetDlgItemTextW(v4, 104, v21 + 256);
}
else
{
v24 = v21 + 512;
if
( *v24 )
SetDlgItemTextW(v4, 104, v24);
}
可以发现这些控件信息都是动态生成的 UTF-16LE 字串。 在没有注册的情况下,104 号控件显示 "40 days trial copy",因此猜测 sub_47ED70 显示和注册用户有关的信息,可能包含用户名;而 sub_4B6AC0 应该是直接输出 "registered to"。 在这些地方下断点,可以验证之。
接下来就是想办法输出自己的署名:该字串可以放在栈上,也可以放在数据节区。这里我选择后者。 .rdata 节后有大概 1024 字节的空间,正好给我们操作,注意要用 UTF-16LE。
1
2
005820A0
xx xx
68
00
61
00
63
00
6B
00
65
00
64
00
20
00
005820B0
62
00
79
00
20
00
D7
72
66
65
50
5B
00
00
xx xx
重命名这段空间为 wAbout,选择 Options > String literals > Currently > UTF-16LE ,字串就插入成功。
1
2
.rdata:
005820A2
wAbout:
.rdata:
005820A2
text
"UTF-16LE"
,
'hacked by 狗敦子'
,
0
接下来要想办法将字串地址传入 SetDlgItemTextW。 由于重定位的存在,想要传入位于 .rdata 节的字串地址,得要修改重定位表才行,显然这过于麻烦。 观察到 call ds:SetDlgItemTextW
使用了导入表里的条目,因此这里一定有一条 4 字节的重定位要求,那么只要将字串的地址偷偷替换上去,就可以骗过加载器让其帮我们完成重定位。 这个 call 为 FF 15,占 6 字节,将其替换为 nop
和 push wAbout
(68 A2 20 58 00,共 5 字节)。 这时,push 的 4 字节立即数正好对上了 call 的 4 字节立即数。 这样,我们消耗了一条 call ds:SetDlgItemTextW
指令,并将字串传入后面的 SetDlgItemTextW。
1
2
3
4
5
6
7
8
.text:
0050763F
nop
.text:
00507640
push wAbout ;
"hacked by 狗敦子"
.text:
00507640
; 这里本来是上一个 call ds:SetDlgItemTextW
.text:
00507645
nop ; lpString
.text:
00507645
; 这里本来是 push esi,我们直接 nop 掉
.text:
00507646
push
102
; nIDDlgItem
.text:
00507648
push ebx ; hDlg
.text:
00507649
call ds:SetDlgItemTextW
应用 patch,大功告成!
总结
超级简单啊有没有!
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2024-1-29 19:22
被狗敦子编辑
,原因: 修改了一些内容