欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

对某个CAD绘图控件的分析

程序员文章站 2022-03-27 21:26:02
【文章标题】: 某个CAD绘图控件的分析 【文章作者】: LiXMX 【保护方式】: 序列号 【编写语言】: Borland Delphi 6.0 - 7.0 【使用工具】: PEiD...
【文章标题】: 某个CAD绘图控件的分析
【文章作者】: LiXMX
【保护方式】: 序列号
【编写语言】: Borland Delphi 6.0 - 7.0
【使用工具】: PEiD,Regmon_fix,DEDE,UltraEdit
【操作平台】: Win XP
【软件介绍】: 一款Delphi和C++Builder环境下的矢量图绘制工具插件,功能简单但是较实用。
【作者声明】: 本人菜鸟一枚,发帖原因是从注册后到现在没发过像样的文章,注册好多年了,惭愧哇,主要是技术不到家啊。如果和版规冲突,请管理员删除,谢谢。
【分析原因】: 本菜分析它的原因仅仅是因为这个软件的授权方式让我很不爽(PS:我已经花钱买下来了>_<)。目前使用序列号方式授权,每次重装系统都要重新申请,虽
 
然提供加密狗,但是加密狗居然只支持Delphi,不能再C++Builder中使用,让只会C/C++的本小菜很受伤,深感受到了歧视,所以破解之。
 
-------------------------------------------------------------------------------
 
【流程】:
 
初看对方提供的程序,分为三个部分:控件安装程序、机器码生成程序、加密狗一枚。
 
加密狗:
这个就不说了,等着过段时间在研究一下吧。
 
 
机器码生成程序:
该程序运行后就会生成一个8位机器码,机器码一般多为CpuID,分区卷序号之类的。
然后通过“只要不重装系统,每次的码都是一样的”排除了CpuID,初步判断是分区卷序号。
之后使用PEiD查看Import,发现确实有GetVolumeInformation函数被调用,几乎就确定是分区卷序号了。
最搞笑的是当我在CMD中输入dir命令时,我发现C盘的序列号赫然就是生成的机器码,晕,明文啊明文……
再看看他把我的注册号放到哪里了?
使用Regmon_fix监视了一下,发现是写道注册表中了。
 
那么大概就可以确定他的工作流程了:
1.获取C盘的序列号作为机器码发送给作者;
2.作者根据用户提供的机器码算出注册码后发送给用户;
3.用户使用“机器码生成程序”导入注册码到本地注册表中;
4.控件在启动时会检查C盘的序列号和注册表中的注册码是否匹配。
 
所以到了这里,第一种破解方式已经有了:用户只要把自己的C盘的序列号修改成和正版用户一样的就可以正常使用该控件了。
 
 
控件安装程序:
知道了大概的授权流程了之后,从授权流程的4步就可以看出,控件本身在启动时也需要获取C盘的序列号,一边使用C盘的序列号和注册码进行某种校验。
于是我们从控件程序本身下手,首先查看控件安装目录中的全部DCU文件,看看有没有什么可以的DCU文件。
于是发现了两个的文件:Encryption.hpp 和Encryption.dcu(十分明目张胆的文件名啊)。
既然如此那就先看看他吧,看看是个诱饵还是个果子。
在Encryption.hpp头文件中看到有三个函数定义:
extern PACKAGE AnsiString __fastcall GetDiskSerialID(char cDriveName);
extern PACKAGE AnsiString __fastcall Encrypt(const AnsiString str);
extern PACKAGE AnsiString __fastcall Decrypt(const AnsiString str);
我勒个去的,这不就是:获取磁盘序号,编码,解码三个步骤,全齐了(看来作者压根就没想过防破解啥的,嘿嘿,不差钱哦)
 
之后,使用DEDE的DCU Dump功能获得该DCU的反汇编代码。
 
在DCU中的USES中发现有如下声明:
Windows
{
T:DWORD, A:SetErrorMode,
A:SEM_FAILCRITICALERRORS,
A:MAX_PATH,
A:GetVolumeInformation
}
出现了GetVolumeInformation,看来是找对地方了。
 
既然有了GetVolumeInformation,那么之后就应该是使用Encrypt和Decrypt对C盘序号和注册码进行加密解密了。
 
最初我的想法是修改DCU中判断注册码正误的相关跳转,来一个爆破算了,但是继续往下看,发现作者在这里又给了我一个惊喜……
 
extern PACKAGE AnsiString __fastcall Encrypt(const AnsiString str)函数的反汇编如下:
 
PS:不得不感慨,DEDE确实是DELPHI程序员的噩梦哇!!!
 
function Encrypt (str: System.AnsiString): System.AnsiString;
var
  result Result: System.AnsiString;
  i: System.Integer;
  sn: System.AnsiString;
begin
   00000000 : 55                            PUSH EBP
   00000001 : 8B EC                         MOV EBP,ESP
   00000003 : 83 C4 EC                      ADD ESP,-20
   00000006 : 53                            PUSH EBX
   00000007 : 33 C9                         XOR ECX,ECX // ECX清零
   00000009 : 89 4D EC                      MOV DWORD PTR [EBP-20],ECX  // 初始化为0,此处应该是编译器生成的temp临时变量,存放密码异或的结果
   0000000C : 89 4D F0                      MOV DWORD PTR [EBP-16{sn}],ECX // sn初始化为0(用于存放处理后的VolumeSerial)
   0000000F : 89 55 F8                      MOV DWORD PTR [EBP-8{Result}],EDX // result用于返回结果
   00000012 : 89 45 FC                      MOV DWORD PTR [EBP-4{str}],EAX // str就是输入的VolumeSerial
   00000015 : 33 C0                         XOR EAX,EAX // EAX清零
   00000017 : 55                            PUSH EBP
   00000018 : 68(79 00 00 00                PUSH Encrypt{0x3A}+121
   0000001D : 64 FF 30                      PUSH DWORD PTR FS:[EAX]
   00000020 : 64 89 20                      MOV DWORD PTR FS:[EAX],ESP
   00000023 : C7 45 F4 01 00 00 00          MOV DWORD PTR [EBP-12{i}],$00000001 // i计数器初值为1,应该是个for循环起始位置
   0000002A : 8D 45 EC                      LEA EAX,DWORD PTR [EBP-20]
   0000002D : 8A 55 F4                      MOV DL,BYTE PTR [EBP-12{i}] // i放入到DL中
   00000030 : 8B 4D F4                      MOV ECX,DWORD PTR [EBP-12{i}] // 计数器i放入到ECX中
   00000033 : 8B 5D FC                      MOV EBX,DWORD PTR [EBP-4{str}] // str的首地址放入到EBX中
   00000036 : 32 54 0B FF                   XOR DL,BYTE PTR [EBX+ECX-1] // str[i]和DL异或
   0000003A : E8(00 00 00 00                CALL @LStrFromChar{0x21} // 异或后的结果转换成char型
   0000003F : 8B 55 EC                      MOV EDX,DWORD PTR [EBP-20] // @LStrCat的参数,fastcall调用使用了EDX寄存器
   00000042 : 8D 45 F0                      LEA EAX,DWORD PTR [EBP-16{sn}] // @LStrCat的参数,fastcall调用使用了EAX寄存器
   00000045 : E8(00 00 00 00                CALL @LStrCat{0x22} // 追加EDX中的内容到字符串sn尾部
   0000004A : FF 45 F4                      INC DWORD PTR [EBP-12{i}] // i计数器自增
   0000004D : 83 7D F4 09                   CMP DWORD PTR [EBP-12{i}],9 // i计数器和9比较
   00000051 : 75 D7                         JNE -41; (0x2A) // 是否是小于9,小于9继续循环,大于等于则终止(因为VolumeSerial是8个字符长度)
   00000053 : 8B 45 F8                      MOV EAX,DWORD PTR [EBP-8{Result}] // @LStrAsg的参数,fastcall调用使用了EAX寄存器
   00000056 : 8B 55 F0                      MOV EDX,DWORD PTR [EBP-16{sn}] // @LStrAsg的参数,fastcall调用使用了EDX寄存器 
   00000059 : E8(00 00 00 00                CALL @LStrAsg{0x26} // @LStrAsg用于将字符串sn赋值给Result
   0000005E : 33 C0                         XOR EAX,EAX
   00000060 : 5A                            POP EDX
   00000061 : 59                            POP ECX
   00000062 : 59                            POP ECX
   00000063 : 64 89 10                      MOV DWORD PTR FS:[EAX],EDX
   00000066 : 68(80 00 00 00                PUSH Encrypt{0x3A}+128
   0000006B : 8D 45 EC                      LEA EAX,DWORD PTR [EBP-20]
   0000006E : BA 02 00 00 00                MOV EDX,$00000002
   00000073 : E8(00 00 00 00                CALL @LStrArrayClr{0x29}
   00000078 : C3                            RET NEAR
   00000079 : E9(00 00 00 00                JMP @HandleFinally{0x23}
   0000007E : EB EB                         JMP -21; (0x6B)
   00000080 : 5B                            POP EBX
   00000081 : 8B E5                         MOV ESP,EBP
   00000083 : 5D                            POP EBP
   00000084 : C3                            RET NEAR
end;
 
通过读代码发现关键的编码部分是一个for循环,而且使用的是xor运算……
这下子Decrypt函数连看都不用看了,关键代码肯定是一样的,因为A xor B xor B = A
 
上边的汇编的核心代码换成C++大概就是:
 
AnsiString __fastcall Encrypt(AnsiString str)
{  
    String sn = "";
    for(int i=1; i<9; i++)
    {
        sn.cat_sprintf("%c", str[i]^((char)i) );
    }
    return sn;
}
 
如果函数入口的str存放的是C盘序列号,那么返回的就是注册码,
反之如果函数入口的str存放的事注册码,那么返回的就是C盘序列号。
 
所以最后发现注册码的算法仅仅是将C盘序列号的每一个字符和自己在字符串中的偏移量进行了异或运算,要制作注册机的话,使用上面那个函数就足够了