【文章作者】: uuk
【软件名称】: RVS-Lite-2011(2.1.5.5147)
【加壳方式】: 无
【保护方式】: 机器码+序列号
【编写语言】: Borland Delphi 6.0 - 7.0 [Overlay]
【使用工具】: PEID,IDA Pro with Hex-Rays plugin,DeDe,OllyICE,Syser,VMware
【软件介绍】: 瑞泰尼尔安全防御系统软件 (英文名称: Returnil Virtual System 2010,以下简称RVS)是瑞泰尼尔公司采用最新虚拟技术实现的新一代桌面安全系统。和传统的杀毒软件完全不同,瑞泰尼尔防毒系统软件可以瞬间“克隆”用户当前的操作系统,“制造”出实际存在于用户内存中的虚拟操作系统,并利用虚拟的操作系统来替代真实的操作系统,从而以达到保护真实操作系统的目的。
【作者声明】: 大道向前,一步一个脚印!
【破解过程】
1、前期处理
RVS Lite 2011安装后会在:1)Windows\System32\Drivers目录下复制驱动文件;2)在系统盘根目录下创建RETURNIL文件夹,里面存放配置文件和数据;3)在Program Files目录下创建Returnil Virtual System Lite 2011文件夹,里面只有一个主程序RvsCore.exe。
就先从RvsCore.exe下手,用PEiD 查壳显示Borland Delphi 6.0 - 7.0 [Overlay],用KANAL插件分析有ADLER32、CRC32、MD5、ZLIB算法。直接拖到IDA里分析,并将几处算法标注上。IDA分析Delphi程序时要手动加载sig(Barland Visual Component Library & Packages)。默认加载了SHE for vc7/8,没用,但也不知道怎么去掉。Delphi程序也有异常处理,不过找不到sig就算了,影响不大。有些库函数识别不出来,可以有DeDe配合使用。
Delphi逆向分析中较困难的就是一些VCL、RTL库函数被IDA识别成unknowlib,还有许多虚函数的调用,例如:call [edx+0xC],此外Pascal的参数传递方式也比较让人不习惯。虽然有DeDe这样强大的逆向工具,但个人在处理以上问题是仍需要较强的手动分析逆向技巧。Delphi的参数传递: EAX:第一个参数; EDX:第二个参数; ECX:第三个参数; [ESP+8]第四,[ESP+C]第五。。。(如果有的话)(引自myskydog的《Delphi逆向分析---quick batch逆向》http://bbs.pediy.com/showthread.php?t=80205)
2、定位关键代码
RvsCore.exe程序有个注册界面,输入注册码确定后软件会重启计算机进行验证,在DeDe是找到确定按键的处理过程,大致看了下,没找到突破口。又看了软件的几个界面,把一些按键试了下,发现在软件主界面的下方有一个提示试用版的状态栏,在DeDe是定位此状态栏的创建过程PROC_5EBCD0。在PROC_5EBCD0里有条件判断,分别指向1)Registered; 2)Expired; 3)Trial Version。(软件支持多种语言,字符串有多层引用,有点烦人)
CODE:005EC52D mov eax, ds:ppBaseAddress
CODE:005EC532 mov eax, [eax]
CODE:005EC534 mov al, [eax+0E25h] ; al = 0(registered), 2(expired),
CODE:005EC534 ; 4(expired), 1(trial)
CODE:005EC53A sub al, 1
CODE:005EC53C jb short Registered
CODE:005EC53E dec al
CODE:005EC540 jz Expired
CODE:005EC546 sub al, 2
CODE:005EC548 jz Expired
CODE:005EC54E jmp Trial
CODE:005EC553 ; ---------------------------------------------------------------------------
CODE:005EC553
CODE:005EC553 Registered: ; CODE XREF: PROC_5EBCD0+86C j
CODE:005EC553 mov edx, ds:strRegistered
CODE:005EC559 mov edx, [edx]
CODE:005EC55B mov eax, [ebp+var_4]
CODE:005EC55E mov eax, [eax+470h]
CODE:005EC564 call @Controls@TControl@SetText$qqrx17System@AnsiString ; Controls::TControl::SetText(System::AnsiString)
[edx+0E25h]是注册标志,只要能找到对它赋值的地方,一般就找到的算法核心的地方。对于面向对象程序而言,想要用静态分析的方法找到某个变量的赋值操作是很困难的,得用动态调试。在OllyICE中加载程序,对内存中注册标志所在的位置下断点,运行程序,发现程序只有读注册标志,没有写操作。没有头绪,一顿乱翻,突然发现任务管理器里有两个RvsCore.exe,想到这种软件一般会往系统里添加服务,一看,果然。将Rvssrv服务停止,在OllyICE添加程序启动命令 –service,下断点,运行,程序直接退出,也没写注册标志的操作,服务没有启动。一步一步运行,缩小范围,发现是在某个与服务有关的Windows API调用后程序退出,猜测是系统服务不能以常规方法调试。上网搜,只看到使用注册表用Windbg调试系统服务,而我用OllyICE试不成功。看来以后要转而用Windbg了。现在只能在虚拟机里用Syser试试。添加启动命令 –service,添加断点,直接运行程序可是退出。偶然情况下,点了重启服务,发现Syser断下来了。原来注册标志是放在系统地址里(跟系统的内存管理有关),系统地址的断点在程序结束后是不会被Syser清除的,所以当RvsCore.exe以服务的方式再次启动并且访问断点所在的地址时被Syser断下。找到写注册标志的代码段:
CODE:005FF787 mov eax, ds:off_60BB0C
CODE:005FF78C mov eax, [eax]
CODE:005FF78E mov al, [eax+14h] ; [00E485E8] 取值
CODE:005FF791 mov edx, ds:ppBaseAddress
CODE:005FF797 mov edx, [edx]
CODE:005FF799 mov [edx+0E25h], al
继续跟踪对[eax+14h]的操作,找到:
CODE:005FB085 mov byte ptr [esi+14h], 1 ; [00E485E8] 赋值为1
前面的分析可知,值为1时是试用版,不是我们想要的,我们要找赋0的时候。在CODE:005FB085上下翻了翻,也没找到赋值为0的代码,估计这是注册标志的初始化,之后肯定会有别的操作,但是我们的注册码的错误的,Syser断不到赋值为0的指令。虽然静态分析的方法找到某个变量的赋值操作是很困难的,但也不是没可能。比如:
CODE:005EC534 mov al, [eax+0E25h]
CODE:005FF799 mov [edx+0E25h], al
都是[exx+0E25h],this指针加偏移的形式,举一反三,会不会有:
mov byte ptr [esi+14h], 0
这种形式的汇编代码呢?在OllyICE中查找命令,果然找到:(IDA里不知道怎么查找命令……)
CODE:005FB6B9 mov byte ptr [esi+14h], 0
在这个函数里上下浏览代码,还看到:
CODE:005FB779 mov byte ptr [esi+14h], 2
看来找到目标了,在Syser里进行验证,确实是这里。将这个函数sub_5FB5D0命名为KeyCall。
3、 算法分析
CODE:005FB657 lea ecx, [ebp+StrHex] ; StrHex
CODE:005FB65D lea eax, [ebp+Hex] ; Hex
CODE:005FB663 mov edx, 10h ; strLen
CODE:005FB668 call HexToHexString
CODE:005FB66D mov edx, [ebp+StrHex]
CODE:005FB673 lea eax, [esi+28h]
CODE:005FB676 call @System@LStrAsg ; Borland Visual Component Library & Packages
CODE:005FB67B call sub_493C04
CODE:005FB680 test al, al
CODE:005FB682 jz short loc_5FB6CA
CODE:005FB684 lea ecx, [ebp+SerialCode] ; StrHex
CODE:005FB687 lea eax, [ebp+var_136] ; Hex
CODE:005FB68D mov edx, 10h ; strLen
CODE:005FB692 call HexToHexString ; 生成机器码 SerialCode
CODE:005FB697 lea eax, [ebp+SerialNumber]
CODE:005FB69A push eax ; a4
CODE:005FB69B mov ecx, 20h ; a3
CODE:005FB6A0 mov edx, [ebp+SerialCode] ; a2
CODE:005FB6A3 mov eax, esi ; a1
CODE:005FB6A5 call GenSN ; 由机器码生成注册码 SerialNumber
CODE:005FB6AA mov eax, [esi+28h] ; 输入的注册码
CODE:005FB6AD mov edx, [ebp+SerialNumber]
CODE:005FB6B0 call SysUtils_CompareText ; Borland Visual Component Library & Packages
CODE:005FB6B5 test eax, eax
CODE:005FB6B7 jnz short loc_5FB6CA
CODE:005FB6B9 mov byte ptr [esi+14h], 0
CODE:005FB6BD xor eax, eax
CODE:005FB6BF pop edx
CODE:005FB6C0 pop ecx
CODE:005FB6C1 pop ecx
CODE:005FB6C2 mov fs:[eax], edx
CODE:005FB6C5 jmp loc_5FB7B7
CODE:005FB6CA ; ---------------------------------------------------------------------------
分析:CODE:005FB6A5 call GenSN 是关键,Syser中显示 GenSN( ) 将机器码SerialCode作为输入,生成注册码SerialNumber,GenSN( ) 汇编代码如下:
CODE:005FB4EC push ebp
CODE:005FB4ED mov ebp, esp
CODE:005FB4EF add esp, 0FFFFFF78h
CODE:005FB4F5 push ebx
CODE:005FB4F6 mov [ebp+_src], edx
CODE:005FB4F9 mov ebx, eax
CODE:005FB4FB mov eax, [ebp+_src]
CODE:005FB4FE call @System@LStrAddRef ; Borland Visual Component Library & Packages
CODE:005FB503 xor eax, eax
CODE:005FB505 push ebp
CODE:005FB506 push offset loc_5FB55C
CODE:005FB50B push dword ptr fs:[eax]
CODE:005FB50E mov fs:[eax], esp
CODE:005FB511 lea edx, [ebp+_ID_Hex]
CODE:005FB514 mov ecx, 10h
CODE:005FB519 mov eax, [ebp+_src]
CODE:005FB51C call HexStringToHex
CODE:005FB521 push 10h ; len
CODE:005FB523 lea ecx, [ebp+Hex2] ; Hex2
CODE:005FB529 lea edx, [ebp+_ID_Hex] ; Hex1
CODE:005FB52C mov eax, ebx ; this
CODE:005FB52E call sub_5FC02C
CODE:005FB533 mov ecx, [ebp+dst] ; StrHex
CODE:005FB536 lea eax, [ebp+Hex2] ; Hex
CODE:005FB53C mov edx, 10h ; strLen
CODE:005FB541 call HexToHexString
CODE:005FB546 xor eax, eax
CODE:005FB548 pop edx
CODE:005FB549 pop ecx
CODE:005FB54A pop ecx
CODE:005FB54B mov fs:[eax], edx
CODE:005FB54E push offset loc_5FB563
CODE:005FB553
CODE:005FB553 loc_5FB553: ; CODE XREF: GenSN+75 j
CODE:005FB553 lea eax, [ebp+_src]
CODE:005FB556 call @System@LStrClr ; Borland Visual Component Library & Packages
CODE:005FB55B retn
分析:CODE:005FB51C call HexStringToHex 是将HexString形式的机器码转换成Hex格式。然后调用 CODE:005FB52E call sub_5FC02C,最后是调用 CODE:005FB541 call HexToHexString(将注册码转成HexString)。推进 sub_5FC02C:
CODE:005FC02C ; ============= S U B R O U T I N E ===============
CODE:005FC02C
CODE:005FC02C ; Attributes: bp-based frame
CODE:005FC02C
CODE:005FC02C ; void __fastcall sub_5FC02C(int this, int Hex1, int Hex2, signed int len)
CODE:005FC02C sub_5FC02C proc near ; CODE XREF: GenSN+42 p
CODE:005FC02C ; sub_5FBDE4+77 p
CODE:005FC02C
CODE:005FC02C var_E8 = byte ptr -0E8h
CODE:005FC02C _Hex2 = dword ptr -8
CODE:005FC02C _Hex1 = dword ptr -4
CODE:005FC02C len = dword ptr 8
CODE:005FC02C
CODE:005FC02C push ebp
CODE:005FC02D mov ebp, esp
CODE:005FC02F add esp, 0FFFFFF18h
CODE:005FC035 push ebx
CODE:005FC036 push esi
CODE:005FC037 mov [ebp+_Hex2], ecx
CODE:005FC03A mov [ebp+_Hex1], edx
CODE:005FC03D mov ebx, eax
CODE:005FC03F lea eax, [ebp+var_E8]
CODE:005FC045 xor ecx, ecx
CODE:005FC047 mov edx, 0E0h
CODE:005FC04C call @System@@FillChar$qqrv ; System::__linkproc__ FillChar(void)
CODE:005FC051 lea eax, [ebx+4]
CODE:005FC054 push eax
CODE:005FC055 lea edx, [ebx+4]
CODE:005FC058 lea eax, [ebp+var_E8]
CODE:005FC05E mov ecx, 10h
CODE:005FC063 call sub_5FA470
CODE:005FC068 mov esi, [ebp+len]
CODE:005FC06B test esi, esi
CODE:005FC06D jns short loc_5FC072
CODE:005FC06F add esi, 7
CODE:005FC072
CODE:005FC072 loc_5FC072: ; CODE XREF: sub_5FC02C+41 j
CODE:005FC072 sar esi, 3
CODE:005FC075 dec esi
CODE:005FC076 test esi, esi
CODE:005FC078 jl short loc_5FC09C
CODE:005FC07A inc esi
CODE:005FC07B xor ebx, ebx
CODE:005FC07D
CODE:005FC07D loc_5FC07D: ; CODE XREF: sub_5FC02C+6E j
CODE:005FC07D mov eax, ebx
CODE:005FC07F shl eax, 3
CODE:005FC082 mov edx, [ebp+_Hex2]
CODE:005FC085 lea ecx, [edx+eax]
CODE:005FC088 mov edx, [ebp+_Hex1]
CODE:005FC08B add edx, eax
CODE:005FC08D lea eax, [ebp+var_E8]
CODE:005FC093 call sub_5FA75C
CODE:005FC098 inc ebx
CODE:005FC099 dec esi
CODE:005FC09A jnz short loc_5FC07D
CODE:005FC09C
CODE:005FC09C loc_5FC09C: ; CODE XREF: sub_5FC02C+4C j
CODE:005FC09C lea eax, [ebp+var_E8]
CODE:005FC0A2 call sub_5FA774
CODE:005FC0A7 lea eax, [ebp+var_E8]
CODE:005FC0AD call ComObj_ClearExcepInfo
CODE:005FC0B2 mov al, 1
CODE:005FC0B4 pop esi
CODE:005FC0B5 pop ebx
CODE:005FC0B6 mov esp, ebp
CODE:005FC0B8 pop ebp
CODE:005FC0B9 retn 4
CODE:005FC0B9 sub_5FC02C endp
分析:里面有三个未知 Call:sub_5FA470、sub_5FA75C、sub_5FA774。先看sub_5FA470,里面有一段很有规律的代码,按F5生成C代码,部分如下:
j = 6;
do
{
LOWORD(v7) = *(_WORD *)(v5 + 2);
v9 = v7 << 9;
*(_WORD *)(v5 + 16) = ((unsigned int)*(_WORD *)(v5 + 4) >> 7) | v9;
LOWORD(v9) = *(_WORD *)(v5 + 4);
v9 <<= 9;
*(_WORD *)(v5 + 18) = ((unsigned int)*(_WORD *)(v5 + 6) >> 7) | v9;
LOWORD(v9) = *(_WORD *)(v5 + 6);
v9 <<= 9;
*(_WORD *)(v5 + 20) = ((unsigned int)*(_WORD *)(v5 + 8) >> 7) | v9;
LOWORD(v9) = *(_WORD *)(v5 + 8);
v9 <<= 9;
*(_WORD *)(v5 + 22) = ((unsigned int)*(_WORD *)(v5 + 10) >> 7) | v9;
LOWORD(v9) = *(_WORD *)(v5 + 10);
v9 <<= 9;
*(_WORD *)(v5 + 24) = ((unsigned int)*(_WORD *)(v5 + 12) >> 7) | v9;
LOWORD(v9) = *(_WORD *)(v5 + 12);
v9 <<= 9;
*(_WORD *)(v5 + 26) = ((unsigned int)*(_WORD *)(v5 + 14) >> 7) | v9;
LOWORD(v9) = *(_WORD *)(v5 + 14);
v9 <<= 9;
*(_WORD *)(v5 + 28) = ((unsigned int)*(_WORD *)v5 >> 7) | v9;
LOWORD(v9) = *(_WORD *)v5;
v7 = v9 << 9;
*(_WORD *)(v5 + 30) = ((unsigned int)*(_WORD *)(v5 + 2) >> 7) | v7;
v5 += 16;
--j;
}
while ( j );
然后看看sub_5FA75C,部分如下:
_word_1 = *(_WORD *)Hex64_1;
_word_2 = *(_WORD *)(Hex64_1 + 2);
_word_3 = *(_WORD *)(Hex64_1 + 4);
_word_4 = *(_WORD *)(Hex64_1 + 6);
i = 8;
do
{
sub_5FA22C((int)&_word_1, *(_WORD *)v3);
v5 = (unsigned __int16 *)(v3 + 2);
_word_2 += *v5;
++v5;
_word_3 += *v5;
++v5;
sub_5FA22C((int)&_word_4, *v5);
++v5;
v6 = _word_3;
_word_3 ^= _word_1;
sub_5FA22C((int)&_word_3, *v5);
++v5;
v7 = _word_2;
_word_2 ^= _word_4;
_word_2 += _word_3;
sub_5FA22C((int)&_word_2, *v5);
v3 = (int)(v5 + 1);
_word_3 += _word_2;
_word_1 ^= _word_2;
_word_4 ^= _word_3;
_word_2 ^= v6;
_word_3 ^= v7;
--i;
}
while ( i );
如此简洁高效的代码,看着就像现成的算法,翻开《加密与解密(第三版)》,看看哪个算法和这类似。嘿嘿~ 原来是IDEA算法。网上找到的IDEA工具来算这个程序的注册码,结果都不对,猜测可能是工具中字节存取跟程序中的处理不一样。弄了个带有源代码的工具,果然,RvsCore.exe中是按char存取,网上找到的IDEA工具是按short int存取。
RVS注册算法的验证过程是:
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课