- UID
- 32344
注册时间2007-6-1
阅读权限8
最后登录1970-1-1
初入江湖
该用户从未签到
|
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 编辑 ] |
评分
-
查看全部评分
|