【文章标题】 : 还原精灵密码算法分析
【调试环境】 : WINXP OllyDbg1.10 PEID 0.94
【软件名称】 : 还原精灵
【破解目的】 : 研究算法分析
【加密方式】 : 加壳,未知壳
【文章作者】 : figo
【作者声明】 : 只为研究算法分析,没有其他目的. 请勿用与恶意破坏等非法用途,
否则给自己或他人带来严重后果,概与本人无关.失误之处恳请批评指正,
或有更好的方法或者技巧,欢迎互相交流. 版主若觉此帖违规,请删除,勿封我ID.
【破解过程】 :
还原精灵是一款很优秀的软件,它能够记录下一切对硬盘的写操作,
不论您对硬盘进行拷贝还是移动删除甚至是格式化分区等操作,只要一重新启动,
一切都会恢复到这个操作之前的状态,所以被广泛应用在学校机房和网吧等公共场合的
电脑上. 但是装还原精灵的用户如果忘记了密码就成麻烦事了,想卸掉要密码,想删也删不掉.
记得网上有一种能删除还原精灵的软件,是风般的男人(我也不知道他的真名,呵呵)编写的.
我个人是极不赞成用这种方法,因为这软件太 "野蛮" 了,它在取得 RING0 后,直接用硬盘 IO 把
原来的 BOOT 扇区强制写入物理 0 磁道 1 扇区.对 5.5 以上的版本会造成系统崩溃,
因为还原精灵为了与 NT/2000/XP 兼容,会替换一些驱动程序.
希望本文能对那些想卸载还原精灵而又忘记密码的用户带来帮助.
还原精灵早期版本密码加密算法很简单,甚至不加密直接明码比较.
5.5 版本加密算法是: 密文 = 密码 XOR A5A5A5A5A5A5A5A5H
6.0 版本的加密算法比较复杂,下面来分析一下
该程序加了壳,是弱壳,用OD 手动脱了
入口点是 004318A4.
下面列出程序一些关键代码片段(用 OD 分析的),至于怎么跟踪得来,由于篇幅关系暂不讨论
本文只讨论密码算法和解密器的编写.
0040160E . E8 B9F70200 call <jmp.&MFC42.#6334_CWnd::UpdateData>
00401613 . 8D86 18020000 lea eax, [esi+218]
00401619 . 8BCE mov ecx, esi
0040161B . 50 push eax
0040161C . E8 EF010000 call 00401810 ; --------> 验证密码子程序
00401621 . 85C0 test eax, eax
00401623 . 0F85 D6000000 jnz 004016FF ; ----------> 关键跳转
00401629 . 8D4C24 0C lea ecx, [esp+C]
0040162D . E8 4CF70200 call <jmp.&MFC42.#540_CString::CString>
00401632 . 8D4C24 08 lea ecx, [esp+8]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
跟进 CALL 00401810
代码如下:
00401839 |. E8 A0F50200 call <jmp.&MFC42.#2915_CString::GetBu>
0040183E |. 8BCB mov ecx, ebx
00401840 |. 8BF0 mov esi, eax
00401842 |. 8BC1 mov eax, ecx
00401844 |. 8D7C24 10 lea edi, [esp+10]
00401848 |. C1E9 02 shr ecx, 2
0040184B |. F3:A5 rep movs dword ptr es:[edi], dword p>
0040184D |. 8BC8 mov ecx, eax
0040184F |. 83E1 03 and ecx, 3
00401852 |. F3:A4 rep movs byte ptr es:[edi], byte ptr>
00401854 |. 8D4C24 10 lea ecx, [esp+10]
00401858 |. 51 push ecx
00401859 |. E8 02490100 call 00416160
注意这个CALL,入栈的是密码原文地址.
把它步过,看看各个寄存器和密码原文有何变化.
我们可以看到密码原文变成密文.
把这个CALL 置断点,重新来过
跟进这个CALL
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
00416160 /$ 8B4424 04 mov eax, [esp+4]
00416164 |. 6A 20 push 20 ; Arg3 = 00000020
00416166 |. 50 push eax ; Arg2 = 密码原文
00416167 |. 68 80254400 push 00442580 ; Arg1 = 00442580 ASCII "NJYZ-RG-60-TT-60"
0041616C |. E8 1FFFFFFF call 00416090 ; -----------------------> 转换密文子程序
00416171 |. 83C4 0C add esp, 0C
00416174 \. C3 retn
注意这个CALL 的3个参数
第一个参数是指向一个字符串 "NJYZ-RG-60-TT-60"
第二个参数是指向密码原文
第三个参数是一个常数 00000020H
不难猜出这个CALL 是一个明文转换密文的子程序
跟进去看看,下面这段代码就是密码转换算法
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
00416090 /$ 55 push ebp
00416091 |. 8BEC mov ebp, esp
00416093 |. 8B45 10 mov eax, [ebp+10]
00416096 |. 53 push ebx
00416097 |. 56 push esi
00416098 |. 57 push edi
00416099 |. 85C0 test eax, eax
0041609B |. 7E 5D jle short 004160FA
0041609D |. 60 pushad
0041609E |. 8B75 08 mov esi, [ebp+8]
004160A1 |. 8B7D 0C mov edi, [ebp+C]
004160A4 |. 57 push edi
004160A5 |. 8B1F mov ebx, [edi]
004160A7 |. 8B4F 04 mov ecx, [edi+4]
004160AA |. 33C0 xor eax, eax
004160AC |. BA B979379E mov edx, 9E3779B9
004160B1 |. 66:BF 2000 mov di, 20
这时候各个寄存器:
ESI 指向字符串 "NJYZ-RG-60-TT-60"
EDX 常数 9E3779B9
EBX 密码明文前4个字节
ECX 密码明文后4个字节
DI 常数 0020H
大家应该可以下面的代码看出:
密码密文是把明文和字符串"NJYZ-RG-60-TT-60"和常数 9E3779B9 进行加密
其中密码的前4字节和字符串"NJYZ-RG-"加密
密码的后4字节和字符串"60-TT-60"加密
而且密码的前后4字节在每次加密循环都相互加密
004160B5 |> 03C2 /add eax, edx ;--------------->加密循环
004160B7 |. 8BE9 |mov ebp, ecx
004160B9 |. C1E5 04 |shl ebp, 4
004160BC |. 03DD |add ebx, ebp
004160BE |. 8B2E |mov ebp, [esi]
004160C0 |. 33E9 |xor ebp, ecx
004160C2 |. 03DD |add ebx, ebp
004160C4 |. 8BE9 |mov ebp, ecx
004160C6 |. C1ED 05 |shr ebp, 5
004160C9 |. 33E8 |xor ebp, eax
004160CB |. 03DD |add ebx, ebp
004160CD |. 035E 04 |add ebx, [esi+4]
004160D0 |. 8BEB |mov ebp, ebx
004160D2 |. C1E5 04 |shl ebp, 4
004160D5 |. 03CD |add ecx, ebp
004160D7 |. 8B6E 08 |mov ebp, [esi+8]
004160DA |. 33EB |xor ebp, ebx
004160DC |. 03CD |add ecx, ebp
004160DE |. 8BEB |mov ebp, ebx
004160E0 |. C1ED 05 |shr ebp, 5
004160E3 |. 33E8 |xor ebp, eax
004160E5 |. 03CD |add ecx, ebp
004160E7 |. 034E 0C |add ecx, [esi+C]
004160EA |. 66:4F |dec di
004160EC |.^ 75 C7 \jnz short 004160B5 ;------------>加密循环
004160EE |. 5F pop edi
004160EF |. 891F mov [edi], ebx
004160F1 |. 894F 04 mov [edi+4], ecx
想解出密码,我们得把密文解密 20H 次,如果直接用汇编代码逆运算是算不出来的,引用 《C 语言程序设计》中的
一句话 "超出人的大脑能直接思考的范围 ",而且碰到 shr 之类的语句就郁闷了,因为你无法知道前一个值是什么.
记得《C 语言程序设计》 有提到过用伪代码描述算法,我们不妨把汇编算法用 C 伪代码描述.
密码算法 C 伪代码:
a = 密码的前4个字节
b = 密码的后4个字节
n = 20H
k = 9E3779B9H
-------------------
c = b << 4
a = a + c
c = b ^ "NJYZ"
a = a + c
c = b >> 5 =============================> 密码的前4个字节加密
c = c ^ (k * n)
a = a + c
a = a + "-RG-"
--------------------
-----------------------------------------------------------------------------
--------------------
c = a << 4
b = b + c
c = a ^ "60-T"
b = b +c
c = a >> 5 =================================> 密码的后4个字节加密
c = c ^ (k * n)
b = b + c
b = b + "T-60"
---------------------
循环加密 20H 次
经过上面的分析,大家应该对还原精灵的加密算法比较熟悉了吧!
接下来讨论一下解密器的制作:
可以看出前后4个字节的加密算法一样的,我们可以遍一个解密子程序
先解出密码的后4个字节,再解出密码的前4个字节,如此循环20H次.
下面是我自己写的还原密码破解程序的源代码,在 VC++ 6.0 下编译通过(控制台程序)
#include "windows.h"
#include "stdio.h"
unsigned long esi0 = 0x5a594a4e; // "NJYZ"
unsigned long esi4 = 0x2d47522d; // "-RG-"
unsigned long esi8 = 0x542d3036; // "60-T"
unsigned long esic = 0x30362d54; // "T-60"
unsigned long edx0 = 0x9e3779b9;
//加密常量要仔细填,不然不但算不出密码,而且也很难找出错误.
void sub(unsigned long *a, unsigned long *b, unsigned long *a1, unsigned long
*b1, unsigned long c1);
void main()
{
printf("\n\t\t\t 还原精灵 6.0 密码读取程序 \n\n\n");
printf("\t\t\t\t\t by FIGO \n\n\n");
printf("\t\t\t 仅供学习交流使用,请勿用于非法目的\n\n\n\n");
long hmov = 0;
unsigned long *pwd;
char str1[20];
char *p1;
unsigned long *p2;
pwd = (unsigned long*)str1;
unsigned long tmp1, tmp2, tmp3, i;
HANDLE hFile;
hFile = CreateFile("\\\\.\\PhysicalDrive0", GENERIC_ALL, FILE_SHARE_READ |
FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
PBYTE pBuffer = (PBYTE)malloc(512);
DWORD dwLen;
hmov = SetFilePointer(hFile, 0x00000e00, (long*)(&hmov), FILE_BEGIN);
ReadFile(hFile, pBuffer, 512, &dwLen, NULL);
p1 = (char*)pBuffer + 0x4f;
p2 = (unsigned long*)p1;
tmp1 = *p2;
p2++;
tmp2 = *p2;
for (i = 0x20; i >= 1; i--)
{
tmp3 = i * edx0;
sub(&tmp2, &tmp1, &esi8, &esic, tmp3);
sub(&tmp1, &tmp2, &esi0, &esi4, tmp3);
}
*pwd = tmp1;
pwd = pwd++;
*pwd = tmp2;
pwd++;
*pwd = 0;
printf("\t\t\t password is : %s \n\n\n\n\n\n\n\n\n\n\n\n\n\n ", str1);
CloseHandle(hFile);
free(pBuffer);
}
void sub(unsigned long *a, unsigned long *b, unsigned long *a1,
unsigned long *b1, unsigned long c1)
{
unsigned long higt, low, esi1, esi2, ebp0, edx1;
edx1 = c1;
higt = *a;
low = *b;
esi1 = *a1;
esi2 = *b1;
ebp0 = low << 4;
higt = higt - ebp0;
ebp0 = low ^ esi1;
higt = higt - ebp0;
ebp0 = low >> 5;
ebp0 = ebp0 ^ edx1;
higt = higt - ebp0;
higt = higt - esi2;
*a = higt;
}
至于还原精灵6.0 以上版本及还原精灵网络版6.x的算法也是一样(至少目前为止),
只是加密的字符串不一样.
例如:
解还原精灵网络版 6.02(目前应该算是最新版吧)的密码,只要将上面的代码中的
字符串 "NJYZ-RG-60-TT-60" 替换为 "NJZY-NetCard-V60" 即可.
实际上解还原精灵6.0密码可以直接用内存注册机来解,在 0041616C 出中断,
EAX 指向密码地址(一定要先结束掉还原精灵的进程),网络版的无此BUG.
还原精灵各个版本一般都是把密码的明文或密文写在 0 磁道 8 扇区
偏移量为 04FH,该扇区并没有什么特殊保护,可以直接用 CreateFile 进行读写.
为了便于理解,我把程序写成控制台程序(界面很简陋),大家有兴趣的话可以利用上面的代码,
自己编个破解程序(记得把 FIGO 替换成自己的名字就行了 (^_^) ).