小试Watch法分析kiTo's kiTo KGNmeAGAiN
发表于:
2006-8-14 20:46
5697
小试Watch法分析kiTo's kiTo KGNmeAGAiN
小试Watch法分析kiTo's kiTo KGNmeAGAiN
=========================================================
Watch分析法即以程序变量为分析对象,搞清楚程序的整个框架。
与一般的过程(代码)分析方法有所不同。
下面随手下载了一个crackme做示范
=========================================================
该crackme作者是这么说的:
kiTo's kiTo KGNmeAGAiN
Download kiToKGNmeAGAiN.zip, 106 kb
Browse contents of kiToKGNmeAGAiN.zip
Keygen this little baby and submit your solution.
Difficulty: 2 - Needs a little brain (or luck)
Platform: Windows
Language: Assembler
Published: 06. Aug, 2006
Downloads: 9
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
眼睛不小心在这个上面晃了一下:
Difficulty: 2 - Needs a little brain (or luck)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
首先分析程序大体是如何执行是不可避免的。
==========================================================
运行程序,看到的是一个常规界面的CRACKME。
用OD加载发现开头就有个CALL,按ENTER进去,首先是一个GetVersion(),以后会用到。
输入at eip回到入口点
向下几行,看到:
00401018 |. 68 33104000 PUSH kiToKGNm.00401033 ; |DlgProc = kiToKGNm.00401033
于是输入:
at 00401033
进入消息处理函数
(这个地址居然就在ExitProcess()下面。)
凭经验判断这个00401033函数的主干是一大堆case,于是看到很多jnz,验证了猜想。
-------------------------------------
看待这个JNZ(前者),预测它是关键的case,后者是爆破点。
按下F9运行,输入name、serial,果然在这里停了下来。
0040105B |. 817D 10 EC030>CMP [ARG.3],3EC
00401062 |. 75 3E JNZ SHORT kiToKGNm.004010A2
00401064 |. FF75 08 PUSH [ARG.1]
00401067 |. E8 9F000000 CALL kiToKGNm.0040110B
0040106C |. 0BC0 OR EAX,EAX
0040106E |. 75 16 JNZ SHORT kiToKGNm.00401086
00401070 |. 6A 40 PUSH 40 ; /Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL
00401072 |. 68 4B304000 PUSH kiToKGNm.0040304B ; |Title = "kiTo - KgnMe AGAiN!"
00401077 |. 68 03304000 PUSH kiToKGNm.00403003 ; |Text = "Bra, om du inte har gjort en keygen \xE4n, s\xE5 \xE4r det den \xE4nda l\xF6sningen ;)"
0040107C |. FF75 08 PUSH [ARG.1] ; |hOwner
0040107F |. E8 8E090000 CALL <JMP.&user32.MessageBoxA> ; \MessageBoxA
一路F8知道爆破点前面:
0040106C |. 0BC0 OR EAX,EAX
0040106E |. 75 16 JNZ SHORT kiToKGNm.00401086 ;爆破点
看看这里的EAX是怎么出来的。
就在这条语句上面有个CALL:
00401067 |. E8 9F000000 CALL kiToKGNm.0040110B
按下ENTER进去:
在这个函数的最后,发现了我们最喜欢的东西:
004011BB |. 68 6A334000 PUSH kiToKGNm.0040336A ; /String2 = "123456"
004011C0 |. 68 38334000 PUSH kiToKGNm.00403338 ; |String1 = "B150-2E4"
004011C5 |. E8 24080000 CALL <JMP.&kernel32.lstrcmpA> ; \lstrcmpA
004011CA |. C9 LEAVE
004011CB \. C2 0400 RETN 4
拿这个“B150-2E4”试试,果真是注册码。
===============================================================
下面就是要分析这个B150-2E4是怎么来的。
程序的EP处的那个CALL最先被怀疑。
它调用了GetVersion,
GetVersion结束后,其实只是变动了eax的值,在写注册机的时候可以照样画葫芦,不用管他。
PUSH 4031A8:
DS:[4031A8]这个值存储的是GetVersion返回的EAX的十六进制的ANSI串。
接下来的这行:
004010C7 |. 83C4 0C ADD ESP,0C
wprintf是__cdecl的函数,需要自己平衡堆栈。
下去直到这里:
004010CA |. 68 DA314000 PUSH kiToKGNm.004031DA ; ASCII "NTE0MDA4Mjg="
004010CF |. 6A 08 PUSH 8
004010D1 |. 68 A8314000 PUSH kiToKGNm.004031A8 ; ASCII "51400828"
004010D6 E8 C5070000 CALL kiToKGNm.004018A0
CALL kiToKGNm.004018A0是一大堆运算,处理结果只和进栈的字符串、数字有关(大概是某加密算法),若是分析程序框架,可以暂时不去管它。
这个函数的末尾,又是一次这个算法。
004010F9 |. 68 3E324000 PUSH kiToKGNm.0040323E
004010FE |. 6A 10 PUSH 10
00401100 |. 68 0C324000 PUSH kiToKGNm.0040320C ; ASCII "51400828NTE0MDA4Mjg="
00401105 |. E8 96070000 CALL kiToKGNm.004018A0
到此才分析完程序一开头的这个CALL
=========================================
回到爆破点附近
00401064 |. FF75 08 PUSH [ARG.1]
这个ARG.1因该是WndProc(消息处理函数)的 hwnd(窗口句柄)参数。
00401064 |. FF75 08 PUSH [ARG.1]
00401067 |. E8 9F000000 CALL kiToKGNm.0040110B
按下F4运行到这个CALL的地方。输入Name,Serial
Press Enter进入kiToKGNm.0040110B函数。
00401115 |. 6A 32 PUSH 32 ; /Count = 32 (50.)
00401117 |. 68 70324000 PUSH kiToKGNm.00403270 ; |Buffer = kiToKGNm.00403270
0040111C |. 68 E9030000 PUSH 3E9 ; |ControlID = 3E9 (1001.)
00401121 |. FF75 08 PUSH [ARG.1] ; |hWnd
00401124 |. E8 E3080000 CALL <JMP.&user32.GetDlgItemTextA> ; \GetDlgItemTextA
~~~~~~~~~~~~~~~~
在kiToKGNm.00403270这个地方存放NAME。
接下去第3次调用到CALL kiToKGNm.004018A0函数。
计算出字符串放在 4032A2。
接着是几个字符串cat、len操作
直到这里:
00401161 |. A3 A0314000 MOV DWORD PTR DS:[4031A0],EAX
看上去应该是第一次访问DWORD PTR DS:[4031A0],不管他
下去之后就生成了注册码。
到此,初步分析完成
====================================================================================================
下面精确分析。
这趟分析,须搞清楚每个用到的地址(变量、函数)对应的是什么。是“变量分析法”的关键!
(不要看简短的几行,这几行上花费的时间不会比上面的短。)
~~~~~~~~~~~~~~~
函数部分:
004010AB 程序一开始的CALL
004018A0 把该函数命名成f1,声明: __stdcall f1(IN LPTSTR strin,IN DWORD ncode,OUT LPTSTR strout);凭经验应该是把strin以ncode为密钥加密成strout字符串
0040110B __stdcall f2(HWND hWin) 以该CRACKME的窗口句柄为输入参数,生成明码SERIAL,经过strcmp和输入进行比较。是关键函数!
(须注意:这里的参数――窗口句柄经过观察和试验,只是用来作为其中GetDlgItemxxxx的参数,并不参与注册码运算!)
004017A8 __stdcall f3(void) 访问了6个数据段变量,无堆栈变量
004017E8 __stdcall f4(LPTSTR str1,DWORD ncode),其中调用004011D0进行运算
00401848 __stdcall f5(void)
其中很多函数调用的:
004011D0 是个大头,也是这个程序加密算法的关键,其中用了很多的lea代替add、mul做运算。此函数的细节,这里篇幅有限,不予列举。
(这个函数十分有趣,它虽然无堆栈输入,却自行读取堆栈内容,作为密钥。这给写注册机带来了干扰。我已经仔细的学习过,得到了许多防crack的技巧。建议大家仔细研究。)
变量部分: (有些是不断变动的,按程序执行的时间先后顺序排列)
类型声明:
字符串型: 403000,4031A8,4031DA,40320C,40323E,403270,4032A2,4032D4
DWORD型: 4031A0,403400
====================004010AB 中===============
403000 "%X"
4031A8 GetVersion函数返回的EAX转化成的16进制ANSI串。
4031DA 4031A8的数值以08h为密钥由函数f1()加密
40320C =ansi字符串cat(4031A8,4031DA)
40323E 以上面的40320C为原文,10h==16d为密钥f1()加密生成报文
===================0040110B 中================
4032D4 =0(从下文可以得知该0是作为sz字符串结束的'\0')
403270 输入的名字
4032A2 输入的名字经过以 名字长度为密钥的f1()运算后得出的字符串
4032D4 == 字符串 PTR [4032A2] (嘻嘻,这样表示应该看得懂吧^_^)
40323E 仍旧是上述得40323E,内容没有变化
4032D4 = ansi字符串cat(4032A2,40323E)
4031A0 =字符串4032D4的长度
===================其他函数中=================
403400 =0
403404 =0
4033F0 =67452301H
4033F4 =EFCDAB89H
4033F8 =98BADCFEH
4033FC =10325476H
……………
时间紧迫,快要吃饭了就不多列举
这个crackme没有什么trick,既不存在花指令,也没有多线程,只是一个字“长”。(当然,和某些共享软件的注册算法比起来是简练多了。)
这里还是要提一句,它的核心函数之一: 004011D0 构思十分巧妙,它独特的获取密钥方式(不破坏堆栈的条件下,从先前的堆栈中获得,写注册机的时候大部分可以用立即数代替!!),给写KeyGen带来了一定的复杂度。盖有经验的老鸟对此并不陌生。(可惜本菜鸟才第一次碰到)
总结:
任何程序所用到的数据,不可能只在寄存器里做运动,访问内存是必然的。我们可以抓住应用程序的这个通性,在调试跟踪的过程中按照代码执行的先后顺序记录内存的读、写情况。更清晰的掌握程序的脉络。尤其是在超高级语言(如VB)中更显出优势。可以看作是手动的Watch。我们无产阶级用的是OD,某些调试工具自己带有十分丰富的WATCH功能,只需要手动记录一下变量之间的转换关系就可以了。^_^
并不是所有变量都需要如此详细的分析,也不是所有变量的值都需要写出来,只要分析关键变量、就可以了。
希望大家也参与交流技巧。
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
这个crackme只适合于自己分析,若是把它的过程写出来……要帖好几页
对于这个crackme,Watch分析是最适合的方法
我原本看着这个crackme结构清晰,想可以写成tutorial,未料到此crackme虽然清晰,算法却十分长。又不想前功尽弃,就把大略帖出来。加密函数004011D0内部太长,反正大家记录下了之前的变量地址后基本上都能读懂。
=========================================================================================
关于KeyGen:
只怪我没有锻炼耐心,加上
破了此crackme没有什么功利价值(不能和破ShareWare相比啊)。于是注册机随便写一下。虽然说这不能叫做真正意义上的KeyGen(偷懒一下啦)。
!!!!!!!!!!!!!!!!!!!!
最后不好意思的说一句――
大家可千万别指责我的crackme啊~~,实在不想按正规方式写了。注册算法太长。
==========================================================================================
原版CrackMe+自己写的KeyGen下载:
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
上传的附件: