首页
社区
课程
招聘
[原创]第二个crack me练习
发表于: 2010-9-19 19:06 5432

[原创]第二个crack me练习

2010-9-19 19:06
5432
最近闲得蛋疼,于是又搞出加密与解密第五章那堆习题。本来想做个keyfile的习题,但是发现序列号的习题还真多,于是决定再做一个。

这次的目标是:KGM1Tal.exe

拿到文件 二话没说 先跑一次,随便填个用户名 序列号点注册,弹出对话框注册失败。用IDA载入,看看用了哪些API,一下就看到了GetDlgItemTextA.于是用OD跑,在GetDlgItemTextA里面下断点,F9,用户名,序列号。点确定。直接弹出注册失败的对话框,居然没有停下来。我一下就傻眼了,又试了2次还是这样。莫非这crackme 还反调试?又试了试硬件断点,这次停下了。一路单步 首先进入了第一个函数,这是函数中的第一个关键判断。

00401332   $  33C0          XOR EAX,EAX
00401334   .  B9 00000000   MOV ECX,0
00401339   .  BE 23304000   MOV ESI,KGM1Tal.00403023                 ;  ASCII "DEESSSSSSS"
0040133E   .  8A06          MOV AL,BYTE PTR DS:[ESI]
00401340   .  EB 10         JMP SHORT KGM1Tal.00401352
00401342   >  0FB6C0        MOVZX EAX,AL
00401345   .  80B8 50314000>CMP BYTE PTR DS:[EAX+403150],2  ;此处是关键的比较
0040134C   .  75 0A         JNZ SHORT KGM1Tal.00401358
0040134E   .  41            INC ECX
0040134F   .  8A0431        MOV AL,BYTE PTR DS:[ECX+ESI]
00401352   >  3C 00         CMP AL,0
00401354   .^ 77 EC         JA SHORT KGM1Tal.00401342
00401356   .  EB 07         JMP SHORT KGM1Tal.0040135F
00401358   >  C605 44304000>MOV BYTE PTR DS:[403044],40    ;若序列号不是大写字母则置[403044]为0x40,后面会以此处为判断,若是0x40则注册失败。
004013A9   . /75 05         JNZ SHORT KGM1Tal.004013B0
004013AB   . |E9 45010000   JMP KGM1Tal.004014F5 ;跳到注册失败
004013B0   > \E9 CB000000   JMP KGM1Tal.00401480 ;跳到401480 call 401510

00401345   .  80B8 50314000>CMP BYTE PTR DS:[EAX+403150],2 这个地方的判断比较巧妙,是通过比较内存中的值来判断的,403150 这段内存如下:
00403150  00 09 09 09 09 09 09 09 09 06 05 09 09 05 09 09  
00403160  09 09 09 09 09 09 09 09 09 09 09 09 09 09 09 09
00403170  06 05 05 05 09 05 05 05 05 05 04 04 05 04 05 04  
00403180  01 01 01 01 01 01 01 01 01 01 05 05 05 05 05 05  
00403190  09 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
004031A0  02 02 02 02 02 02 02 02 02 02 02 05 05 05 05 05  

为2的地方是从403150偏移 0x41-0x5A 刚好是大写字母A-Z。所以序列号必须是大写字母。

这个函数的后半段:
0040135F   > \BE 00304000   MOV ESI,KGM1Tal.00403000                 
00401364   .  33C9          XOR ECX,ECX
00401366   .  B8 01000000   MOV EAX,1
0040136B   .  33D2          XOR EDX,EDX
0040136D   .  C705 45304000>MOV DWORD PTR DS:[403045],0
00401377   >  B9 00000000   MOV ECX,0
0040137C   .  8A0C32        MOV CL,BYTE PTR DS:[EDX+ESI]
0040137F   .  80F9 00       CMP CL,0
00401382   .  74 09         JE SHORT KGM1Tal.0040138D
00401384   .  42            INC EDX
00401385   .  000D 45304000 ADD BYTE PTR DS:[403045],CL
0040138B   .^ EB EA         JMP SHORT KGM1Tal.00401377
0040138D   >  A1 45304000   MOV EAX,DWORD PTR DS:[403045]
00401392   .  B9 18000000   MOV ECX,18
00401397   .  99            CDQ
00401398   .  F7F9          IDIV ECX
0040139A   .  8815 4F304000 MOV BYTE PTR DS:[40304F],DL
004013A0   .  8A0D 44304000 MOV CL,BYTE PTR DS:[403044] ;前面提到的判断
004013A6   .  80F9 40       CMP CL,40                                       ;为0x40 直接结束。
004013A9   .  75 05         JNZ SHORT KGM1Tal.004013B0     ;即序列号不为大写字母就挂
004013AB   .  E9 45010000   JMP KGM1Tal.004014F5
004013B0   >  E9 CB000000   JMP KGM1Tal.00401480
004013B5   .  C3            RETN

此处是用 用户名计算出一个数值,为后面计算序列号所用。
翻译成C语言如下
unsigned char u=0;   //此处其实有个陷阱。我刚开始一直用的int 型来存u ,发现计算结果一
直是错误的,后来发现 此处用的是 MOV BYTE PTR DS:[40304F],DL 只用了一个字节来存u,
所以要定义成unsigned char。
for(i=0;user[i]!='\0';i++)
{
      u+=user[i];
}
	
      u%=0x18;

往下面走call 401510
00401510   $  A0 24304000   MOV AL,BYTE PTR DS:[403024]
00401515   .  3C 45         CMP AL,45 ;判断序列号第二位是否为'E' 不是则挂。
00401517   .^ 75 DC         JNZ SHORT KGM1Tal.004014F5
00401519   $  BF 96124000   MOV EDI,KGM1Tal.00401296                 ;  入口地址
0040151E   .  B9 00010000   MOV ECX,100
00401523   .  B0 99         MOV AL,99
00401525   .  34 55         XOR AL,55
00401527   .  F2:AE         REPNE SCAS BYTE PTR ES:[EDI]
00401529   .  85C9          TEST ECX,ECX
0040152B   .  74 06         JE SHORT KGM1Tal.00401533
0040152D   .  5E            POP ESI
0040152E   .  33F6          XOR ESI,ESI
00401530   .  57            PUSH EDI
00401531   .^ EB C2         JMP SHORT KGM1Tal.004014F5
00401533   >  C3            RETN

上面这个函数先判断序列号第二位是否为'E'不是则注册失败,其后的一段是这个crackme 其中一个 debug check 0x99^0x55 = 0xCC 刚好是int 3 断点的机器码,而软断点就是利用将要下断点处的代码改为0xCC来捕获这个异常。这段代码判断了从程序开始的0x100个字节内是否有0xCC来anti-debug。
00401480   > \E8 8B000000   CALL KGM1Tal.00401510
00401485   .  33DB          XOR EBX,EBX
00401487   .  BF 80144000   MOV EDI,KGM1Tal.00401480 ;EDI=401480
0040148C   .  83EF 60       SUB EDI,60  ;EDI = 401420
0040148F   .  B8 DE000000   MOV EAX,0DE
00401494   .  83F0 12       XOR EAX,12
00401497   .  B9 59000000   MOV ECX,59 ;401420-401479
0040149C   .  F2:AE         REPNE SCAS BYTE PTR ES:[EDI]
0040149E   .  85C9          TEST ECX,ECX
004014A0   .  74 06         JE SHORT KGM1Tal.004014A8
004014A2   .  5E            POP ESI
004014A3   .  33F6          XOR ESI,ESI
004014A5   .  57            PUSH EDI
004014A6   .  EB 4D         JMP SHORT KGM1Tal.004014F5
004014A8   >  C3            RETN

以上是第二处debug check 0xDE^0X12=0xCC 判断401420-401479之间是否设置int 3断点。

再往下看一点
004014A9   $  BE 9C154000   MOV ESI,<JMP.&user32.GetDlgItemTextA>    ;  入口地址
004014AE   .  8B7E 02       MOV EDI,DWORD PTR DS:[ESI+2]
004014B1   .  8B3F          MOV EDI,DWORD PTR DS:[EDI]
004014B3   .  B9 06000000   MOV ECX,6 ;GetDlgItemTextA 函数6字节内是否有int 3 断点
004014B8   .  B0 CC         MOV AL,0CC 
004014BA   .  F2:AE         REPNE SCAS BYTE PTR ES:[EDI]
004014BC   .  85C9          TEST ECX,ECX
004014BE   .  74 06         JE SHORT KGM1Tal.004014C6
004014C0   .  5E            POP ESI
004014C1   .  33F6          XOR ESI,ESI
004014C3   .  57            PUSH EDI
004014C4   .  EB 2F         JMP SHORT KGM1Tal.004014F5
004014C6   >  C3            RETN

以上是第三处debug check 判断GetDlgItemTextA 是否被下断,只判断了函数的前6个字节所以对抗的方法是在6字节之后下断,即可成功断下。

往下走 进入第二个函数
004012E3   .  68 53304000   PUSH KGM1Tal.00403053  ;ASCII "ZWATRQLCGHPSXYENVBJDFKMU"
004012E8   .  E8 C9000000   CALL KGM1Tal.004013B6

004013B6   $  55            PUSH EBP
004013B7   .  8BEC          MOV EBP,ESP
004013B9   .  68 23304000   PUSH KGM1Tal.00403023                    ;  ASCII "KEJTMPWUDM"
004013BE   .  E8 7D010000   CALL KGM1Tal.00401540  ;子函数 判断序列号长度
004013C3   .  83F8 0A       CMP EAX,0A     ;EAX 为 function 401540的返回值
004013C6   .  0F85 29010000 JNZ KGM1Tal.004014F5
004013CC   .  BE 23304000   MOV ESI,KGM1Tal.00403023                 ;  ASCII "KEJTMPWUDM"
004013D1   .  B8 00000000   MOV EAX,0
004013D6   .  BB 00000000   MOV EBX,0
004013DB   .  33C9          XOR ECX,ECX
004013DD   .  EB 06         JMP SHORT KGM1Tal.004013E5
004013DF   >  8A0C30        MOV CL,BYTE PTR DS:[EAX+ESI]
004013E2   .  03D9          ADD EBX,ECX
004013E4   .  40            INC EAX
004013E5   >  83F8 09       CMP EAX,9
004013E8   .^ 72 F5         JB SHORT KGM1Tal.004013DF
004013EA   .  8BC3          MOV EAX,EBX
004013EC   .  B9 09000000   MOV ECX,9
004013F1   .  99            CDQ
004013F2   .  F7F9          IDIV ECX
004013F4   .  A3 4A304000   MOV DWORD PTR DS:[40304A],EAX
004013F9   .  8B7D 08       MOV EDI,DWORD PTR SS:[EBP+8]
004013FC   .  8A15 4F304000 MOV DL,BYTE PTR DS:[40304F]
00401402   .  8AC2          MOV AL,DL
00401404   .  3C 18         CMP AL,18
00401406   .  76 02         JBE SHORT KGM1Tal.0040140A
00401408   .  2C 18         SUB AL,18
0040140A   >  A2 4E304000   MOV BYTE PTR DS:[40304E],AL
0040140F   .  33C0          XOR EAX,EAX
00401411   .  A0 4E304000   MOV AL,BYTE PTR DS:[40304E]
00401416   .  8A2438        MOV AH,BYTE PTR DS:[EAX+EDI]
00401419   .  8A36          MOV DH,BYTE PTR DS:[ESI]
0040141B   .  38F4          CMP AH,DH
0040141D   .  0F85 D2000000 JNZ KGM1Tal.004014F5
00401423   .  80EE 41       SUB DH,41
00401426   .  8AF2          MOV DH,DL
00401428   .  B4 00         MOV AH,0
0040142A   .  A2 4E304000   MOV BYTE PTR DS:[40304E],AL
0040142F   .  33C0          XOR EAX,EAX
00401431   .  A0 4E304000   MOV AL,BYTE PTR DS:[40304E]
00401436   .  02C2          ADD AL,DL
00401438   .  3C 18         CMP AL,18
0040143A   .  76 02         JBE SHORT KGM1Tal.0040143E
0040143C   .  2C 18         SUB AL,18
0040143E   >  B9 02000000   MOV ECX,2
00401443   .  8A2438        MOV AH,BYTE PTR DS:[EAX+EDI]
00401446   .  8A3431        MOV DH,BYTE PTR DS:[ECX+ESI]
00401449   .  38F4          CMP AH,DH
0040144B   .  0F85 A4000000 JNZ KGM1Tal.004014F5
00401451   .  EB 24         JMP SHORT KGM1Tal.00401477
00401453   >  A2 4E304000   MOV BYTE PTR DS:[40304E],AL
00401458   .  33C0          XOR EAX,EAX
0040145A   .  A0 4E304000   MOV AL,BYTE PTR DS:[40304E]
0040145F   .  80EE 41       SUB DH,41
00401462   .  8AD6          MOV DL,DH
00401464   .  41            INC ECX
00401465   .  02C2          ADD AL,DL
00401467   .  3C 18         CMP AL,18
00401469   .  76 02         JBE SHORT KGM1Tal.0040146D
0040146B   .  2C 18         SUB AL,18
0040146D   >  8A2438        MOV AH,BYTE PTR DS:[EAX+EDI]
00401470   .  8A3431        MOV DH,BYTE PTR DS:[ECX+ESI]
00401473   .  38F4          CMP AH,DH
00401475   .  75 7E         JNZ SHORT KGM1Tal.004014F5
00401477   >  83F9 08       CMP ECX,8
0040147A   .^ 72 D7         JB SHORT KGM1Tal.00401453
0040147C   .  C9            LEAVE
0040147D   .  C2 0400       RETN 4


在函数最开始的部分call了一个子函数 00401540
00401540  /$  8B4424 04     MOV EAX,DWORD PTR SS:[ESP+4]
00401544  |.  83E8 04       SUB EAX,4
00401547  |>  83C0 04       /ADD EAX,4
0040154A  |.  8038 00       |CMP BYTE PTR DS:[EAX],0
0040154D  |.  74 30         |JE SHORT KGM1Tal.0040157F
0040154F  |.  8078 01 00    |CMP BYTE PTR DS:[EAX+1],0
00401553  |.  74 20         |JE SHORT KGM1Tal.00401575
00401555  |.  8078 02 00    |CMP BYTE PTR DS:[EAX+2],0
00401559  |.  74 10         |JE SHORT KGM1Tal.0040156B
0040155B  |.  8078 03 00    |CMP BYTE PTR DS:[EAX+3],0
0040155F  |.^ 75 E6         \JNZ SHORT KGM1Tal.00401547
00401561  |.  2B4424 04     SUB EAX,DWORD PTR SS:[ESP+4]
00401565  |.  83C0 03       ADD EAX,3
00401568  |.  C2 0400       RETN 4
0040156B  |>  2B4424 04     SUB EAX,DWORD PTR SS:[ESP+4]
0040156F  |.  83C0 02       ADD EAX,2
00401572  |.  C2 0400       RETN 4
00401575  |>  2B4424 04     SUB EAX,DWORD PTR SS:[ESP+4]
00401579  |.  83C0 01       ADD EAX,1
0040157C  |.  C2 0400       RETN 4
0040157F  |>  2B4424 04     SUB EAX,DWORD PTR SS:[ESP+4]
00401583  \.  C2 0400       RETN 4

仔细分析上面这段代码 发现序列号长度为10的时候,返回值为0x0A,满足条件.(应该还有满足条件的长度,我没有算了。)

4013CC - 4013F4 是利用序列号的前9位来计算出一个值
大意翻译成C 如下:
	for(i=0;i<9;i++)
	{
		sum+=code[i];
	}
	sum/=9;

4013FC-40144B 判断 序列号第1位和第3位是否满足条件
table[25]="ZWATRQLCGHPSXYENVBJDFKMU";
code[0]=table; //第一位要等于table u为前面用户名所计算得出
code[2]=table[u*2];//第二位等于table[u*2]  
//这一段花指令甚多,看起来比较麻烦。要多分析一下才能得出。

401451-401477 为判断序列号的4-9位是否符合规则
具体的算法描述如下:
for(i=3;i<=8;i++)
	{
		u=(table[u]-0x41+u)%0x18;
		if(code[i]!=table[u])
                                   //如果有一位不满足验证失败
               }


最后的一部分判断在4014CE这段函数中,判断序列号的第10位,看第十位code[9]是否等于sum(此处sum是之前通过序列号前9位计算得出的。)。 判断成功则大功告成~

分析完毕。
这次花了我3个多小时才破解成功。。累死了。。呵呵。 不过这个crackme还是蛮有意思的,加入了一些反调试的检测,不过只检测了软断点,所以用硬件断点还是轻松断下。另外,这个作者故意为难加了超多无用代码,分析起来还真纠结。好在序列号生成机制简单,轻松写出注册机,呵呵。

KGM1Tal.exe下载 :

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 0
支持
分享
最新回复 (3)
雪    币: 125
活跃值: (161)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
注册机实现:
#include<stdio.h>
#include<string.h>

int main()
{
	char table[25]="ZWATRQLCGHPSXYENVBJDFKMU";
	char user[11],code[11];
	scanf("%s",&user);
	int i,sum=0; 
	unsigned char u=0;
	for(i=0;user[i]!='\0';i++)
	{
		u+=user[i];
	}
	
	u%=0x18;
	code[0]=table[u];
	code[1]='E';
	u=u*2%0x18;
	code[2]=table[u];
	for(i=3;i<=8;i++)
	{
		u=(table[u]-0x41+u)%0x18;
		code[i]=table[u];
	}
	code[i]='\0';
	for(i=0;i<9;i++)
	{
		sum+=code[i];
	}
	sum/=9;
	char ch[2];
	ch[0] = (char)sum;
	ch[1]='\0';
	strcat(code,ch);
	printf("%s\n",code);
	return 0;
}

2010-9-19 19:07
0
雪    币: 2362
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
真不错  有耐心 有激情  支持
2010-9-19 19:34
0
雪    币: 421
活跃值: (60)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
顶。值得学习
2010-9-21 21:57
0
游客
登录 | 注册 方可回帖
返回
//