飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 4349|回复: 1

[原创] Computer Alarm Clock 2.5 注册算法分析(原创首发)

[复制链接]

该用户从未签到

发表于 2010-3-5 23:08:51 | 显示全部楼层 |阅读模式
Computer Alarm Clock 2.5 注册算法分析

使用工具:OllyDbg, IDA Pro


0. 简介

   Computer Alarm Clock,顾名思义,就是一个闹钟软件。官方站点为(恕不提供直链,请自行修正):

hxxp://www.tarsoft.com/

具体有什么功能我也说不完全,我只是需要一个闹钟,让它来提醒我该收菜了。



1. 破解

   首先试用软件,输入任意用户名和序列号后,软件没有注册成功或失败的提示,而是提示重启。这就有点
小麻烦。再说,用OllyDbg加载后发现这是一个Delphi写的程序,而对于Delphi程序,我一向都有不知从何入
手的感觉,所以没有能在第一时间破解出来。

   试用两天后,发现软件会在安装目录下生成一个cac.ini文件,而输入的用户名和注册码赫然躺在其中。
原来如此。这就好办了。

   用OllyDbg加载后查找"cac.ini"字符串,找到三处地方。其中有一处是提示重启的,显然这是将输入的注
册码写入文件的地方,而不是验证的地方。再看另一处,就是483B3F这个地方,为了确认这里是不是验证注
册码的地方,在它上面下个断点,如果是的话,因为程序一旦重启就会验证,那就一定会断在这个地方。果
然,重新启动后程序被断下了。

   但是底下一大堆call令人晕头转向,难道每个call都要跟进去吗?还是先听听IDA怎么说的吧。如果IDA说
一个call是系统函数调用,那直接上网搜这个系统函数就行了,跟进就可免了。用IDA加载程序后,将Delphi
库的签名文件应用上去(关于如何应用ida库签名可参考此文),果然有很多call变成了已知系统调用的名称。

   现在再根据IDA的分析结果来给OllyDbg中的汇编代码加上适当的标签,加过标签后的汇编代码会变成下面
这样:

======================================================
00483B3F  |.  B9 883D4800   mov     ecx, 00483D88                    ;  cac.ini
00483B44  |.  8B93 78030000 mov     edx, dword ptr [ebx+378]
00483B4A  |.  E8 950FF8FF   call    <LStrCat3>                       ;  eax := ecx + edx
00483B4F  |.  8D45 F4       lea     eax, dword ptr [ebp-C]
00483B52  |.  E8 890CF8FF   call    <LStrClr>
00483B57  |.  8D45 F0       lea     eax, dword ptr [ebp-10]
00483B5A  |.  E8 810CF8FF   call    <LStrClr>
00483B5F  |.  8B45 F8       mov     eax, dword ptr [ebp-8]
00483B62  |.  E8 1955F8FF   call    <FileExists>                     ;  判断key文件是否存在
00483B67  |.  84C0          test    al, al
00483B69  |.  0F84 A0000000 je      00483C0F
00483B6F  |.  8B55 F8       mov     edx, dword ptr [ebp-8]
00483B72  |.  8D85 0CFEFFFF lea     eax, dword ptr [ebp-1F4]
00483B78  |.  E8 8FF2F7FF   call    00402E0C
00483B7D  |.  8D85 0CFEFFFF lea     eax, dword ptr [ebp-1F4]
00483B83  |.  E8 14F0F7FF   call    00402B9C
00483B88  |.  E8 5FEDF7FF   call    <IoTest>
00483B8D  |.  8D85 0CFEFFFF lea     eax, dword ptr [ebp-1F4]
00483B93  |.  E8 A4F4F7FF   call    <EofText>
00483B98  |.  E8 4FEDF7FF   call    <IoTest>
00483B9D  |.  84C0          test    al, al
00483B9F  |.  75 1E         jnz     short 00483BBF
00483BA1  |.  8D55 F4       lea     edx, dword ptr [ebp-C]
00483BA4  |.  8D85 0CFEFFFF lea     eax, dword ptr [ebp-1F4]
00483BAA  |.  E8 0DF6F7FF   call    <ReadLString>
00483BAF  |.  8D85 0CFEFFFF lea     eax, dword ptr [ebp-1F4]
00483BB5  |.  E8 6EF6F7FF   call    <ReadLn>
00483BBA  |.  E8 2DEDF7FF   call    <IoTest>
00483BBF  |>  8D83 3C040000 lea     eax, dword ptr [ebx+43C]
00483BC5  |.  8B55 F4       mov     edx, dword ptr [ebp-C]
00483BC8  |.  E8 670CF8FF   call    <LStrAsg>
00483BCD  |.  8D85 0CFEFFFF lea     eax, dword ptr [ebp-1F4]
00483BD3  |.  E8 64F4F7FF   call    <EofText>
00483BD8  |.  E8 0FEDF7FF   call    <IoTest>
00483BDD  |.  84C0          test    al, al
00483BDF  |.  75 1E         jnz     short 00483BFF
00483BE1  |.  8D55 F0       lea     edx, dword ptr [ebp-10]
00483BE4  |.  8D85 0CFEFFFF lea     eax, dword ptr [ebp-1F4]
00483BEA  |.  E8 CDF5F7FF   call    <ReadLString>
00483BEF  |.  8D85 0CFEFFFF lea     eax, dword ptr [ebp-1F4]
00483BF5  |.  E8 2EF6F7FF   call    <ReadLn>
00483BFA  |.  E8 EDECF7FF   call    <IoTest>
00483BFF  |>  8D85 0CFEFFFF lea     eax, dword ptr [ebp-1F4]
00483C05  |.  E8 CAF2F7FF   call    <Close>
00483C0A  |.  E8 DDECF7FF   call    <IoTest>
00483C0F  |>  33C0          xor     eax, eax
00483C11  |.  8945 FC       mov     dword ptr [ebp-4], eax
00483C14  |.  837D F4 00    cmp     dword ptr [ebp-C], 0
00483C18  |.  74 5B         je      short 00483C75
00483C1A  |.  8D4D EC       lea     ecx, dword ptr [ebp-14]
00483C1D  |.  BA 03000000   mov     edx, 3
00483C22  |.  8B45 F0       mov     eax, dword ptr [ebp-10]
00483C25  |.  E8 C64AFBFF   call    004386F0
00483C2A  |.  8D8D 08FEFFFF lea     ecx, dword ptr [ebp-1F8]
00483C30  |.  BA 0E000000   mov     edx, 0E
00483C35  |.  8B45 F0       mov     eax, dword ptr [ebp-10]
00483C38  |.  E8 B34AFBFF   call    004386F0
00483C3D  |.  8B85 08FEFFFF mov     eax, dword ptr [ebp-1F8]
00483C43  |.  8D4D E8       lea     ecx, dword ptr [ebp-18]
00483C46  |.  BA 02000000   mov     edx, 2
00483C4B  |.  E8 BC4AFBFF   call    0043870C
00483C50  |.  8B45 F4       mov     eax, dword ptr [ebp-C]
00483C53  |.  E8 400EF8FF   call    00404A98
00483C58  |.  85C0          test    eax, eax
00483C5A  |.  7E 19         jle     short 00483C75
00483C5C  |.  BA 01000000   mov     edx, 1
00483C61  |>  8B4D F4       /mov     ecx, dword ptr [ebp-C]          ;  计算用户名字符串ascii码和
00483C64  |.  8A4C11 FF     |mov     cl, byte ptr [ecx+edx-1]
00483C68  |.  81E1 FF000000 |and     ecx, 0FF
00483C6E  |.  014D FC       |add     dword ptr [ebp-4], ecx
00483C71  |.  42            |inc     edx
00483C72  |.  48            |dec     eax
00483C73  |.^ 75 EC         \jnz     short 00483C61
00483C75  |>  8D55 F4       lea     edx, dword ptr [ebp-C]
00483C78  |.  8B45 FC       mov     eax, dword ptr [ebp-4]
00483C7B  |.  E8 6451F8FF   call    <IntToStr>                       ;  转化为字符串存入ebp-C
00483C80  |.  8D4D E4       lea     ecx, dword ptr [ebp-1C]
00483C83  |.  BA 01000000   mov     edx, 1
00483C88  |.  8B45 F4       mov     eax, dword ptr [ebp-C]
00483C8B  |.  E8 7C4AFBFF   call    0043870C
00483C90  |.  8D4D E0       lea     ecx, dword ptr [ebp-20]
00483C93  |.  BA 01000000   mov     edx, 1
00483C98  |.  8B45 F4       mov     eax, dword ptr [ebp-C]
00483C9B  |.  E8 504AFBFF   call    004386F0
00483CA0  |.  8D8D 04FEFFFF lea     ecx, dword ptr [ebp-1FC]
00483CA6  |.  BA 04000000   mov     edx, 4
00483CAB  |.  8B45 F0       mov     eax, dword ptr [ebp-10]
00483CAE  |.  E8 3D4AFBFF   call    004386F0
00483CB3  |.  8B85 04FEFFFF mov     eax, dword ptr [ebp-1FC]
00483CB9  |.  8D4D DC       lea     ecx, dword ptr [ebp-24]
00483CBC  |.  BA 01000000   mov     edx, 1
00483CC1  |.  E8 464AFBFF   call    0043870C
00483CC6  |.  8D8D 00FEFFFF lea     ecx, dword ptr [ebp-200]
00483CCC  |.  BA 09000000   mov     edx, 9
00483CD1  |.  8B45 F0       mov     eax, dword ptr [ebp-10]
00483CD4  |.  E8 174AFBFF   call    004386F0
00483CD9  |.  8B85 00FEFFFF mov     eax, dword ptr [ebp-200]
00483CDF  |.  8D4D D8       lea     ecx, dword ptr [ebp-28]
00483CE2  |.  BA 01000000   mov     edx, 1
00483CE7  |.  E8 204AFBFF   call    0043870C
00483CEC  |.  C683 38040000>mov     byte ptr [ebx+438], 0            ;  关键比较
00483CF3  |.  8B45 E4       mov     eax, dword ptr [ebp-1C]
00483CF6  |.  8B55 DC       mov     edx, dword ptr [ebp-24]
00483CF9  |.  E8 DE0EF8FF   call    <LStrCmp>
00483CFE  |.  75 32         jnz     short 00483D32
00483D00  |.  8B45 E0       mov     eax, dword ptr [ebp-20]
00483D03  |.  8B55 D8       mov     edx, dword ptr [ebp-28]
00483D06  |.  E8 D10EF8FF   call    <LStrCmp>
00483D0B  |.  75 25         jnz     short 00483D32
00483D0D  |.  8B45 EC       mov     eax, dword ptr [ebp-14]
00483D10  |.  BA 983D4800   mov     edx, 00483D98                    ;  e3k
00483D15  |.  E8 C20EF8FF   call    <LStrCmp>
00483D1A  |.  75 16         jnz     short 00483D32
00483D1C  |.  8B45 E8       mov     eax, dword ptr [ebp-18]
00483D1F  |.  BA A43D4800   mov     edx, 00483DA4                    ;  n3
00483D24  |.  E8 B30EF8FF   call    <LStrCmp>
00483D29  |.  75 07         jnz     short 00483D32
00483D2B  |.  C683 38040000>mov     byte ptr [ebx+438], 1
00483D32  |>  80BB 38040000>cmp     byte ptr [ebx+438], 0
00483D39  |.  74 0D         je      short 00483D48
00483D3B  |.  33D2          xor     edx, edx
00483D3D  |.  8B83 34030000 mov     eax, dword ptr [ebx+334]
00483D43  |.  E8 00D1FCFF   call    00450E48
00483D48  |>  33C0          xor     eax, eax
00483D4A  |.  5A            pop     edx
00483D4B  |.  59            pop     ecx
======================================================

可以看到,有一些call后面跟的操作数从原来无意义的数字变成了有意义的函数名称。

   但即便如此,一时也看不出关键点在什么地方(不要看上面那个“关键比较”的注释,那是后来加上去的)
为了定位关键点,继续寻找有用的信息,找到这么一个字符串:

"This is the unregistered version, only 2 alarm allowed!"

意思是未注册版本只能设置两条闹铃。问题是软件怎么判断“未注册”的呢?看这条信息的上面:

======================================================
00487179   .  80B8 38040000>cmp     byte ptr [eax+438], 0
00487180   .  75 38         jnz     short 004871BA
00487182   .  8B45 FC       mov     eax, dword ptr [ebp-4]
00487185   .  8B80 F0020000 mov     eax, dword ptr [eax+2F0]
0048718B   .  8B80 2C020000 mov     eax, dword ptr [eax+22C]
00487191   .  E8 7679FEFF   call    0046EB0C
00487196   .  48            dec     eax
00487197   .  7E 21         jle     short 004871BA
00487199   .  6A 01         push    1                                ; /Style = MB_OKCANCEL|MB_APPLMODAL
0048719B   .  68 A8754800   push    004875A8                         ; |Title = "Register"
004871A0   .  68 B4754800   push    004875B4                         ; |Text = "This is the unregistered version, only 2 alarm allowed!"
004871A5   .  A1 FCE64800   mov     eax, dword ptr [48E6FC]          ; |
004871AA   .  8B00          mov     eax, dword ptr [eax]             ; |
004871AC   .  8B40 30       mov     eax, dword ptr [eax+30]          ; |
004871AF   .  50            push    eax                              ; |hOwner
004871B0   .  E8 2304F8FF   call    <jmp.&user32.MessageBoxA>        ; \MessageBoxA
======================================================

有两处地方可以跳过这条“未注册”的信息。而根据IDA的分析结果,487191处的call是一个用来获取列表框条
目数的系统调用,那么下面的jle显然是判断闹铃条数是否超过二的,因此,判断“未注册”的地方只能是上面
的那个jnz了。再看jnz的条件,是看[eax+438]里的内容是否为零。438这个数字你在前一段代码里是否似曾相
识?哈,现在问题很清楚了。[eax+438]就是软件是否注册的标志位。

   现在回到前一段代码。我们看到影响软件注册标志位的代码就是从483CF9到483D24处的几个LStrCmp调用。
顾名思义,LStrCmp是比较两个字符串的,那么被比较的字符串是什么呢?容易想到它们都跟用户名和序列号有
关,但具体的关系是什么呢?

   为了弄清这个问题,单步运行到483C22这个地方,发现[ebp-C]中是用户名,再往下几步,[ebp-10]中是序
列号。到了483C61这里有个循环,作用是计算用户名字符串各字符的ASCII码之和,接下来,在483C7B这里,用
IntToStr把这个和数转换成字符串(现在[ebp-C]里存放的变成这个新的字符串——把它记做S吧——而不是原
来的用户名了)。除了这些之外,还有若干次函数调用,所调用的函数基本上就两个——004386F0和0043870C。
在OllyDbg里这些函数看不大清楚,切换到IDA里看,好比说0043870C这个函数,在IDA里看来是这样的:

=======================================================
sub_43870C        proc near                ; CODE XREF: sub_483B1C+12Fp
                                        ; sub_483B1C+16Fp ...
                push        ebx
                push        esi
                push        edi
                mov        edi, ecx
                mov        esi, edx
                mov        ebx, eax
                push        edi
                mov        eax, ebx
                call        Del_LStrlen
                mov        edx, eax
                inc        edx
                sub        edx, esi
                mov        ecx, esi
                mov        eax, ebx
                call        @System@@LStrCopy$qqrv ; System::__linkproc__ LStrCopy(void)
                pop        edi
                pop        esi
                pop        ebx
                retn
sub_43870C        endp
=======================================================

注意到一点,Delphi传递参数的方式跟其他语言不太一样,它总是把头三个参数分别放在eax, edx和ecx中,第
四个及以后的参数才压入栈中。因此这个函数反编译出来就是:

procedure sub_43870C (eax, edx, ecx);
begin
  LStrCopy (eax, strlen(eax)+1-edx, edx, ecx);
end;

而系统过程LStrCopy (eax, edx, ecx, arg4)的作用,可以上网搜到,简单的说,它是在eax指定的字符串中,
从第edx个字符开始截取ecx个字符,然后写入到arg4指定的内存单元。因此sub_43870C的作用可以概括为:在
eax指定的字符串中截取末尾的edx个字符,写到ecx指定的内存单元中。明白了这一点后,回到OllyDbg中,给
43870C这个地方加个标签,叫做_RightStr。

与此相仿的还有004386F0这个函数,实现在eax指定的字符串中截取开头的edx个字符,写到ecx指定的内存单元
中。给它加个标签,叫做_LeftStr。

现在我们能够从483C1A处开始,依次记下程序所做的事,如下:

===============================================

[ebp-10] = 序列号

[ebp-14] = 序列号前3位

[ebp-1F8] = 序列号前14位

[ebp-18] = [ebp-1F8] 的末尾2位 (若序列号>14字符则=序列号第13,14两位否则=序列号末二位)

S= 用户名字符串ASCII码和数值转化成的字符串

[ebp-1c] = S 的末位
[ebp-20] = S 的首位
[ebp-1FC] = 序列号的前4位
[ebp-24] = [ebp-1FC]的末位
[ebp-200] = 序列号的前9位
[ebp-28] = [ebp-200]的末位

注册成功条件:

[ebp-1C] == [ebp-24] 且
[ebp-20] == [ebp-28] 且
[ebp-14] == "e3k" 且
[ebp-18] == "n3"
================================================


总结起来就是:

序列号头3个字符必须是"e3k",最后两个字符(如序列号长度大于14,则为第13和14两个字符)必须是"n3",第
4和第9个字符分别等于S的尾字符和首字符,其中S为用户名ascii码之和转换成的字符串。


2. 注册机

首先准备一个11字符的模板,开头三位填"e3k",最后两位填"n3",然后算出用户名的ascii码和,转换成字符串,
首位填入模板第9位,末位填入模板第4位,再选取随机字符填入模板的5~8位即可。

注册函数可编写如下:

=================================================
szFmt         db     '%d',0

_KeyGen     proc  uses ebx esi edi  _lpszUserName, _lpszSerial
;入口参数——_lpszUserName:用户名字符串
;出口参数——_lpszSerial:序列号字符串
;返回值——eax:TRUE成功,FALSE失败
   local    loc_szS[12]:byte
   
   mov      esi, _lpszUserName
   invoke   lstrlen, esi
   test     eax, eax
   jz       locret_1
   mov      ebx, eax
  ;计算用户名ASCII码和
   xor      ecx, ecx
   xor      edi, edi
   xor      eax, eax
  @@:
   mov      al,  [esi+ecx]
   inc      ecx
   add      edi, eax
   cmp      ecx, ebx
   jb       @B
   
  ;转换成字符串存入loc_szS
  
   lea      esi, loc_szS
   invoke   wsprintf, esi, offset szFmt, edi
  ;制造模版
  
   mov      edi, _lpszSerial
   mov      byte ptr [edi], 'e'
   mov      byte ptr [edi+1], '3'
   mov      byte ptr [edi+2], 'k'
   mov      byte ptr [edi+4], 'a'
   mov      byte ptr [edi+5], 'b'
   mov      byte ptr [edi+6], 'c'
   mov      byte ptr [edi+7], 'd'
   mov      byte ptr [edi+9], 'n'
   mov      byte ptr [edi+10], '3'
   mov      byte ptr [edi+11], 0
  ;填入验证数码
   invoke   lstrlen, esi
   mov      bl, [esi]
   mov      byte ptr [edi+8], bl
   mov      bl, [esi+eax-1]
   mov      byte ptr [edi+3], bl
   
   mov      eax, TRUE
  locret_1:  
   ret
_KeyGen   endp
=================================================

[ 本帖最后由 歌姬的亲家 于 2010-3-5 23:23 编辑 ]

评分

参与人数 1威望 +40 飘云币 +60 收起 理由
Nisy + 40 + 60 您发布的主题属于精品!

查看全部评分

PYG19周年生日快乐!
  • TA的每日心情
    开心
    2017-10-25 13:07
  • 签到天数: 15 天

    [LV.4]偶尔看看III

    发表于 2010-3-5 23:18:47 | 显示全部楼层
    顶上 /:good
    PYG19周年生日快乐!
    您需要登录后才可以回帖 登录 | 加入我们

    本版积分规则

    快速回复 返回顶部 返回列表