Computer Alarm Clock 2.5 注册算法分析(原创首发)
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
00483B4A|.E8 950FF8FF call <LStrCat3> ;eax := ecx + edx
00483B4F|.8D45 F4 lea eax, dword ptr
00483B52|.E8 890CF8FF call <LStrClr>
00483B57|.8D45 F0 lea eax, dword ptr
00483B5A|.E8 810CF8FF call <LStrClr>
00483B5F|.8B45 F8 mov eax, dword ptr
00483B62|.E8 1955F8FF call <FileExists> ;判断key文件是否存在
00483B67|.84C0 test al, al
00483B69|.0F84 A0000000 je 00483C0F
00483B6F|.8B55 F8 mov edx, dword ptr
00483B72|.8D85 0CFEFFFF lea eax, dword ptr
00483B78|.E8 8FF2F7FF call 00402E0C
00483B7D|.8D85 0CFEFFFF lea eax, dword ptr
00483B83|.E8 14F0F7FF call 00402B9C
00483B88|.E8 5FEDF7FF call <IoTest>
00483B8D|.8D85 0CFEFFFF lea eax, dword ptr
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
00483BA4|.8D85 0CFEFFFF lea eax, dword ptr
00483BAA|.E8 0DF6F7FF call <ReadLString>
00483BAF|.8D85 0CFEFFFF lea eax, dword ptr
00483BB5|.E8 6EF6F7FF call <ReadLn>
00483BBA|.E8 2DEDF7FF call <IoTest>
00483BBF|>8D83 3C040000 lea eax, dword ptr
00483BC5|.8B55 F4 mov edx, dword ptr
00483BC8|.E8 670CF8FF call <LStrAsg>
00483BCD|.8D85 0CFEFFFF lea eax, dword ptr
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
00483BE4|.8D85 0CFEFFFF lea eax, dword ptr
00483BEA|.E8 CDF5F7FF call <ReadLString>
00483BEF|.8D85 0CFEFFFF lea eax, dword ptr
00483BF5|.E8 2EF6F7FF call <ReadLn>
00483BFA|.E8 EDECF7FF call <IoTest>
00483BFF|>8D85 0CFEFFFF lea eax, dword ptr
00483C05|.E8 CAF2F7FF call <Close>
00483C0A|.E8 DDECF7FF call <IoTest>
00483C0F|>33C0 xor eax, eax
00483C11|.8945 FC mov dword ptr , eax
00483C14|.837D F4 00 cmp dword ptr , 0
00483C18|.74 5B je short 00483C75
00483C1A|.8D4D EC lea ecx, dword ptr
00483C1D|.BA 03000000 mov edx, 3
00483C22|.8B45 F0 mov eax, dword ptr
00483C25|.E8 C64AFBFF call 004386F0
00483C2A|.8D8D 08FEFFFF lea ecx, dword ptr
00483C30|.BA 0E000000 mov edx, 0E
00483C35|.8B45 F0 mov eax, dword ptr
00483C38|.E8 B34AFBFF call 004386F0
00483C3D|.8B85 08FEFFFF mov eax, dword ptr
00483C43|.8D4D E8 lea ecx, dword ptr
00483C46|.BA 02000000 mov edx, 2
00483C4B|.E8 BC4AFBFF call 0043870C
00483C50|.8B45 F4 mov eax, dword ptr
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 ;计算用户名字符串ascii码和
00483C64|.8A4C11 FF |mov cl, byte ptr
00483C68|.81E1 FF000000 |and ecx, 0FF
00483C6E|.014D FC |add dword ptr , ecx
00483C71|.42 |inc edx
00483C72|.48 |dec eax
00483C73|.^ 75 EC \jnz short 00483C61
00483C75|>8D55 F4 lea edx, dword ptr
00483C78|.8B45 FC mov eax, dword ptr
00483C7B|.E8 6451F8FF call <IntToStr> ;转化为字符串存入ebp-C
00483C80|.8D4D E4 lea ecx, dword ptr
00483C83|.BA 01000000 mov edx, 1
00483C88|.8B45 F4 mov eax, dword ptr
00483C8B|.E8 7C4AFBFF call 0043870C
00483C90|.8D4D E0 lea ecx, dword ptr
00483C93|.BA 01000000 mov edx, 1
00483C98|.8B45 F4 mov eax, dword ptr
00483C9B|.E8 504AFBFF call 004386F0
00483CA0|.8D8D 04FEFFFF lea ecx, dword ptr
00483CA6|.BA 04000000 mov edx, 4
00483CAB|.8B45 F0 mov eax, dword ptr
00483CAE|.E8 3D4AFBFF call 004386F0
00483CB3|.8B85 04FEFFFF mov eax, dword ptr
00483CB9|.8D4D DC lea ecx, dword ptr
00483CBC|.BA 01000000 mov edx, 1
00483CC1|.E8 464AFBFF call 0043870C
00483CC6|.8D8D 00FEFFFF lea ecx, dword ptr
00483CCC|.BA 09000000 mov edx, 9
00483CD1|.8B45 F0 mov eax, dword ptr
00483CD4|.E8 174AFBFF call 004386F0
00483CD9|.8B85 00FEFFFF mov eax, dword ptr
00483CDF|.8D4D D8 lea ecx, dword ptr
00483CE2|.BA 01000000 mov edx, 1
00483CE7|.E8 204AFBFF call 0043870C
00483CEC|.C683 38040000>mov byte ptr , 0 ;关键比较
00483CF3|.8B45 E4 mov eax, dword ptr
00483CF6|.8B55 DC mov edx, dword ptr
00483CF9|.E8 DE0EF8FF call <LStrCmp>
00483CFE|.75 32 jnz short 00483D32
00483D00|.8B45 E0 mov eax, dword ptr
00483D03|.8B55 D8 mov edx, dword ptr
00483D06|.E8 D10EF8FF call <LStrCmp>
00483D0B|.75 25 jnz short 00483D32
00483D0D|.8B45 EC mov eax, dword ptr
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
00483D1F|.BA A43D4800 mov edx, 00483DA4 ;n3
00483D24|.E8 B30EF8FF call <LStrCmp>
00483D29|.75 07 jnz short 00483D32
00483D2B|.C683 38040000>mov byte ptr , 1
00483D32|>80BB 38040000>cmp byte ptr , 0
00483D39|.74 0D je short 00483D48
00483D3B|.33D2 xor edx, edx
00483D3D|.8B83 34030000 mov eax, dword ptr
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 , 0
00487180 .75 38 jnz short 004871BA
00487182 .8B45 FC mov eax, dword ptr
00487185 .8B80 F0020000 mov eax, dword ptr
0048718B .8B80 2C020000 mov eax, dword ptr
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 ; |
004871AA .8B00 mov eax, dword ptr ; |
004871AC .8B40 30 mov eax, dword ptr ; |
004871AF .50 push eax ; |hOwner
004871B0 .E8 2304F8FF call <jmp.&user32.MessageBoxA> ; \MessageBoxA
======================================================
有两处地方可以跳过这条“未注册”的信息。而根据IDA的分析结果,487191处的call是一个用来获取列表框条
目数的系统调用,那么下面的jle显然是判断闹铃条数是否超过二的,因此,判断“未注册”的地方只能是上面
的那个jnz了。再看jnz的条件,是看里的内容是否为零。438这个数字你在前一段代码里是否似曾相
识?哈,现在问题很清楚了。就是软件是否注册的标志位。
现在回到前一段代码。我们看到影响软件注册标志位的代码就是从483CF9到483D24处的几个LStrCmp调用。
顾名思义,LStrCmp是比较两个字符串的,那么被比较的字符串是什么呢?容易想到它们都跟用户名和序列号有
关,但具体的关系是什么呢?
为了弄清这个问题,单步运行到483C22这个地方,发现中是用户名,再往下几步,中是序
列号。到了483C61这里有个循环,作用是计算用户名字符串各字符的ASCII码之和,接下来,在483C7B这里,用
IntToStr把这个和数转换成字符串(现在里存放的变成这个新的字符串——把它记做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处开始,依次记下程序所做的事,如下:
===============================================
= 序列号
= 序列号前3位
= 序列号前14位
= 的末尾2位 (若序列号>14字符则=序列号第13,14两位否则=序列号末二位)
S= 用户名字符串ASCII码和数值转化成的字符串
= S 的末位
= S 的首位
= 序列号的前4位
= 的末位
= 序列号的前9位
= 的末位
注册成功条件:
== 且
== 且
== "e3k" 且
== "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 procuses ebx esi edi_lpszUserName, _lpszSerial
;入口参数——_lpszUserName:用户名字符串
;出口参数——_lpszSerial:序列号字符串
;返回值——eax:TRUE成功,FALSE失败
local loc_szS: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,
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 , 'e'
mov byte ptr , '3'
mov byte ptr , 'k'
mov byte ptr , 'a'
mov byte ptr , 'b'
mov byte ptr , 'c'
mov byte ptr , 'd'
mov byte ptr , 'n'
mov byte ptr , '3'
mov byte ptr , 0
;填入验证数码
invoke lstrlen, esi
mov bl,
mov byte ptr , bl
mov bl,
mov byte ptr , bl
mov eax, TRUE
locret_1:
ret
_KeyGen endp
=================================================
[ 本帖最后由 歌姬的亲家 于 2010-3-5 23:23 编辑 ] 顶上 /:good
页:
[1]