• 标 题:ResScope1.92算法分析
  • 作 者:yesky1
  • 时 间:004-07-21,21:06
  • 链 接:http://bbs.pediy.com

ResScope,一个将比exeScope更强的软件资源分析工具,很不错的软件。

delphi程序,无壳无反调试无校验,我喜欢,:)。
通过字符串容易找到这里,很容易看到,读取注册表中的用户名和注册码,分别计算后进行再比较。

代码:
  CODE:0051B6EB                 mov     cl, 1 CODE:0051B6ED                 mov     edx, offset aSoftwareRest_3 ; "SOFTWARE\\RESTOOLS\\ResScope" CODE:0051B6F2                 mov     eax, [ebp+var_8] CODE:0051B6F5                 call    @Registry@TRegistry@OpenKey$qqrx17System@AnsiStringo ; Registry::TRegistry::OpenKey(System::AnsiString,bool) CODE:0051B6FA                 test    alal CODE:0051B6FC                 jz      loc_51B888 CODE:0051B702                 lea     eax, [ebp+var_C] CODE:0051B705                 call    @System@@LStrClr$qqrr17System@AnsiString ; System::__linkproc__ LStrClr(System::AnsiString &) CODE:0051B70A                 lea     eax, [ebp+var_10] CODE:0051B70D                 call    @System@@LStrClr$qqrr17System@AnsiString ; System::__linkproc__ LStrClr(System::AnsiString &) CODE:0051B712                 mov     edx, offset aReguser_2 ; "reguser" CODE:0051B717                 mov     eax, [ebp+var_8] CODE:0051B71A                 call    @Registry@TRegistry@ValueExists$qqrx17System@AnsiString ; Registry::TRegistry::ValueExists(System::AnsiString) CODE:0051B71F                 test    alal CODE:0051B721                 jz      short loc_51B733 CODE:0051B723                 lea     ecx, [ebp+var_C] CODE:0051B726                 mov     edx, offset aReguser_2 ; "reguser" CODE:0051B72B                 mov     eax, [ebp+var_8] CODE:0051B72E                 call    @TRegistry@ReadString$qqrx10AnsiString ; TRegistry::ReadString(AnsiString) CODE:0051B733  CODE:0051B733 loc_51B733:                             ; CODE XREF: sub_51B6A0+81j CODE:0051B733                 mov     edx, offset aRegcode_1 ; "regcode" CODE:0051B738                 mov     eax, [ebp+var_8] CODE:0051B73B                 call    @Registry@TRegistry@ValueExists$qqrx17System@AnsiString ; Registry::TRegistry::ValueExists(System::AnsiString) CODE:0051B740                 test    alal CODE:0051B742                 jz      short loc_51B754 CODE:0051B744                 lea     ecx, [ebp+var_10] CODE:0051B747                 mov     edx, offset aRegcode_1 ; "regcode" CODE:0051B74C                 mov     eax, [ebp+var_8] CODE:0051B74F                 call    @TRegistry@ReadString$qqrx10AnsiString ; TRegistry::ReadString(AnsiString) CODE:0051B754  CODE:0051B754 loc_51B754:                             ; CODE XREF: sub_51B6A0+A2j CODE:0051B754                 mov     eax, [ebp+var_10] CODE:0051B757                 call    @System@@LStrLen ; System::__linkproc__ LStrLen CODE:0051B75C                 cmp     eax, 30h CODE:0051B75F                 jnz     loc_51B888 CODE:0051B765                 mov     eax, [ebp+var_C] CODE:0051B768                 call    @System@@LStrLen ; System::__linkproc__ LStrLen CODE:0051B76D                 test    eaxeax CODE:0051B76F                 jle     loc_51B888 CODE:0051B775                 lea     eax, [ebp+var_14] ; 保存返回值 CODE:0051B775                                         ; 指向18字节长PCHAR CODE:0051B778                 push    eax CODE:0051B779                 mov     cl, 1 CODE:0051B77B                 mov     dl, 1 CODE:0051B77D                 mov     eax, [ebp+var_C] ; "reguser" CODE:0051B780                 call    sub_51A06C      ; 处理用户名 CODE:0051B785                 mov     eax, [ebp+var_14] ; 处理用户名得到的18字节长的串,先入栈,待会好比较 CODE:0051B788                 push    eax CODE:0051B789                 lea     ecx, [ebp+var_18] ; 保存返回值。。 CODE:0051B78C                 mov     dl, 1 CODE:0051B78E                 mov     eax, [ebp+var_10] ; "regcode" CODE:0051B791                 call    sub_519298      ; 注册码变换函数 CODE:0051B796                 mov     edx, [ebp+var_18] CODE:0051B799                 pop     eax CODE:0051B79A                 call    @System@@LStrCmp$qqrv ; System::__linkproc__ LStrCmp(void) CODE:0051B79F                 jnz     short loc_51B7A5 ; 
 
这个软件的爆破起来不是很容易
代码:
  CODE:00525CBB                 mov     eaxds:off_525214  ; * Reference to class THexDumpPass CODE:00525CC0                 call    sub_425EEC      ; * Reference to: Classes.TComponent.Create(TComponent;boolean;TComponent); CODE:00525CC5                 mov     ebxeax CODE:00525CC7                 lea     eax, [ebp+var_130] CODE:00525CCD                 call    sub_518E18      ; 用户名进行变换转为一串字符 CODE:00525CD2                 mov     edx, [ebp+var_130] CODE:00525CD8                 mov     eaxebx CODE:00525CDA                 mov     ecx, [eax] CODE:00525CDC                 call    dword ptr [ecx+18h] ; Classes::TComponent::SetName()
 
在此处设置用户名变换而来得串设置组件名字,然后使用注册码变换而来得串来FindComponent(),如果注册码正确,两个串应该相等,则能得到正确的句柄,
保存起来,然后在校验注册时候使用该句柄,若没找到则例外出错。

下面来看看注册算法:
使用peid来探测一下用了什么算法,一下出来8种算法,够吓人,不过还有不少没探测出来,:)。
DEDE反汇编看看,注意到类名中有大量的类似TCipher_MD5、TCipher_Blowfish、TCipher_IDEA类似这样的类名,
熟悉的人一眼就看出来使用DEC控件,这是Hagen Reddmann写的Delphi Encryption Compendium 控件,控件支持20余种Hash,40多种分组密码算法,
而且附带源码,是很好的学习算法的教程。

在分析过程中可以用delphi编译一个小例子,然后跟踪看看,并参考源码效果很好。
先来看看处理用户名的函数
CODE:0051B780                 call    sub_51A06C      ; 处理用户名
用户ID的生成是调用CPUID指令获取cpu信息,然后编码至16字节。与用户名连接起来,通过简单算法变换为18h字节,
代码:
  CODE:0051A0C3                 mov     edxds:dword_578440 ; 用户ID,类似"92F1L8EACT2FFNFF"一串字符。。。 CODE:0051A0C9                 call    @System@@LStrCat$qqrv ; System::__linkproc__ LStrCat(void) CODE:0051A4A6                 push    offset unk_575B10 ; IVector,初始化向量 CODE:0051A4AB                 mov     edx, offset byte_575B08 ; keyCAST..64位密钥 CODE:0051A4B0                 lea     eax, [ebp+var_F4_TCast128Data] ; 0012FAF8 CODE:0051A4B0                                         ;   TCast128Data= record CODE:0051A4B0                                         ;     InitBlock: array[0..7] of byte;    { initial IV } CODE:0051A4B0                                         ;     LastBlock: array[0..7] of byte;    { current IV } CODE:0051A4B0                                         ;     xKey: array[0..31] of DWord; CODE:0051A4B0                                         ;     Rounds: integer; CODE:0051A4B0                                         ;   end; CODE:0051A4B6                 mov     ecx, 8          ; sizeof(Key) CODE:0051A4BB                 call    Cast128Init     ; 注意此处Cast128非调用DEC的函数,乃是独立的类。                                                       ; 注意其sbox乃是作者随机生成
 

采用CBC模式调用Cast128算法,即密码分组链接模式即每次分组加密结果,与下组明文xor后,作为输入。
代码:
  CODE:0051A50E                 lea     edx, [ebp+eax+var_48] ; 输入串,输出也保存再该地址 CODE:0051A512                 lea     eax, [ebp+var_F4_TCast128Data] ; 密钥初始化结果 CODE:0051A518                 call    Cast128EncryptCBC ; procedure Cast128EncryptCBC(var Data: TCast128Data; InData, OutData: pointer); CODE:0051A518                                         ;   { encrypts the data in a 64bit block using the CBC chaining mode }
 
下面就是调用DEC中的密码算法来进行加密
代码:
  CODE:0051A6FB                 mov     edxds:off_4F31E0 ; Reference to class TCipher_3Way,这个可由DEDE反汇编代码中看到 CODE:0051A701                 mov     eax, [ebp+var_14] CODE:0051A704                 call    @TCipherManager@@SetClass ; TCipherManager::__linkproc__ SetClass CODE:0051A709                 xor     ecxecx CODE:0051A70B                 mov     edx, [ebp+var_18] CODE:0051A70E                 mov     eax, [ebp+var_14] CODE:0051A711                 call    @TCipherManager@@InitKey ; procedure TCipherManager.InitKey(const Key: String; IVector: Pointer); CODE:0051A716                 push    18h             ; 待Encode串长度 CODE:0051A718                 lea     ecx, [ebp+var_60] ; 返回串 CODE:0051A71B                 lea     edx, [ebp+var_48] ; Source串,由Cast128而来 CODE:0051A71E                 mov     eax, [ebp+var_14] CODE:0051A721                 call    @TCipherManager@@EncodeBuffer ; procedure TCipherManager.EncodeBuffer CODE:0051A721                                         ; (const Source; var Dest; DataSize: Integer);
 
下面级连的还有
代码:
  CODE:0051A7F6                 mov     edxds:off_4F29DC ; * Reference to class TCipher_Blowfish  (注: pbox,sbox非标) CODE:0051A8F1                 mov     edxds:off_4F2940 ; * Reference to class TCipher_Gost      (注: Gost_Data非标) CODE:0051AA15                 mov     edxds:off_4F2A7C ; * Reference to class TCipher_IDEA CODE:0051AB39                 mov     edxds:off_4F3144 ; * Reference to class TCipher_Q128      (注: Q128_Data非标) CODE:0051AC5D                 mov     edxds:off_4F2B18 ; * Reference to class TCipher_SAFER     (注: DEC库中默认没有添加入列表,需修改代码注册该Cipher类到控件)  CODE:0051AD58                 mov     edxds:off_4F2E34 ; * Reference to class TCipher_SAFER_K128 CODE:0051AE7C                 mov     edxds:off_4F2BB4 ; * Reference to class TCipher_SAFER_K40 CODE:0051AF77                 mov     edxds:off_4F2CF4 ; * Reference to class TCipher_SAFER_K64
 
对用户名的变换用了10种算法,不过调用DEC库格式比较标准,跟踪起来也什么困难.

下面看注册码变换算法
代码:
  CODE:0051B791                 call    sub_519298      ; 注册码变换函数 注册码为30h字符,先解码后为18h字节数据,每2字符变换为一个字节 然后 CODE:005193C2                 push    offset unk_575AE0 ; GOST初始化向量 CODE:005193C7                 mov     edx, offset unk_575AE8 ; GOST密钥 20h字节 CODE:005193CC                 lea     eax, [ebp+var_98] ;   TGOSTData= record CODE:005193CC                                         ;     InitBlock: array[0..7] of byte;    { initial IV } CODE:005193CC                                         ;     LastBlock: array[0..7] of byte;    { current IV } CODE:005193CC                                         ;     XKey: array[0..7] of DWord; CODE:005193CC                                         ;   end; CODE:005193D2                 mov     ecx, 20h CODE:005193D7                 call    GOSTInit        ; procedure GOSTInit(var Data: TGOSTData; Key: pointer; Len: integer; IV: pointer                                                       ; 注意此处Cast128非调用DEC的函数,乃是独立的类。其Gost_Data非标
       

同样采用CBC模式解码数据,每次处理8字节,共处理3次。
代码:
  CODE:00519429                 lea     edx, [ebp+eax+var_50] ; 输出 CODE:0051942D                 lea     eax, [ebp+var_98] ; var Data: TGOSTData CODE:00519433                 call    GOSTDecryptCBC  ; //procedure GOSTDecryptCBC(var Data: TGOSTData; InData, OutData: pointer); CODE:00519433                                         ;   { decrypts the data in a 64bit block using the CBC chaining mode }
                   
同样下面级连了一串变换,不过此处调用的都是@TCipherManager@@DecodeBuffer,解码函数

代码:
  CODE:005195FB                 mov     edxds:off_4F327C ; * Reference to class TCipher_Twofish    (注: Twofish_Data,Twofish_8x8值非标) CODE:00519714                 mov     edxds:off_4F300C ; * Reference to class TCipher_TEAN CODE:00519810                 mov     edxds:off_4F2F74 ; * Reference to class TCipher_TEA CODE:0051990C                 mov     edxds:off_4F33B4 ; * Reference to class TCipher_Square CODE:00519A2D                 mov     edxds:off_4F3318 ; * Reference to class TCipher_Shark CODE:00519B29                 mov     edxds:off_4F30A8 ; * Reference to class TCipher_SCOP CODE:00519C4A                 mov     edxds:off_4F2D94 ; * Reference to class TCipher_SAFER_SK64 CODE:00519D6B                 mov     edxds:off_4F2C54 ; * Reference to class TCipher_SAFER_SK40 CODE:00519E67                 mov     edxds:off_4F2ED4 ; * Reference to class TCipher_SAFER_SK128
 

最后将用户名变换输出结果和注册码变换输出进行比较。
对于1.75版只使用了Cast128和Gost两种算法,而1.92版堆叠了大概有18种密码学算法,
但由于该库格式又比较固定,总体说来所以不难做出注册机。基本上就照着作者的流程,用户名变换部分全部照抄,
注册码变换部分从后往前逆向使用。类似这样,只是把DecodeBuffer改为EncodeBuffer就可以了,类似
代码:
      CipherManager1.Algorithm := 'Twofish';     CipherManager1.InitKey(keystr, nil);     CipherManager1.EncodeBuffer(Source,Out,$18);
 
另外注意其中有些DEC库函数的s盒被更改,所以还需要修改DEC控件中的cipher.inc里面的值,重新编译该控件。

感谢DiKeN/IPB提供ResScope1.75注册机源码,省下了我不少时间,:)。

yesky1/IPB
7.21