• 逆向project第005篇:跨越CM4验证机制的鸿沟(下)


    一、前言

            本文是逆向分析CM4系列的最后一篇,我会将该游戏的序列号验证机制分析完成,进而编写出注冊码生成器。

     

    二、分析第二个验证循环

            延续上一篇文章的内容,来到例如以下代码处:


    图1

            上述代码并没有特别须要注意的地方。仅仅是知道了接下来的循环须要运行4次。

    以下就是重要的验证部分:


    图2

            这是注冊码中第二组四个字符的生成代码。主要是利用[ebp+var_20]进行运算。将结果作为字符串的偏移值,从而得到注冊码。回想一下,这里的[ebp+var_20]是之前运算所得到的余数。可见这个游戏的验证过程中的取余运算还是比較多的。接下来的两段代码,与图2代码较为类似:


    图3


    图4

            上述两段代码在取余并获取对应字符的同一时候,还更改了[ebp+var_20]、[ebp+var_2C]与[ebp+var_38]中的值。用于接下来的运算。因为比較简单,这里就不再赘述。

            至此,CM4的注冊码验证机制彻底分析完成,那么接下来就能够開始注冊码生成器的编写了。

    三、编写注冊码生成器

            结合之前的分析。我们非常easy就能够编写出注冊机。

    可是要注意,我们在生成注冊码的时候。是须要利用“cm4.epe”这个文件的。须要将二者放置在同一文件夹。让注冊机程序便于读取“password本”中的内容以进行运算,代码例如以下:

    #include<stdio.h>
    #include<windows.h>
    //////////////////////////////////////////////////////////////
    // GetNum函数用于计算cm4.epe文件里对应偏移值处的DWORD大小的
    // 十六进制数值,用于接下来的运算,该函数有一个參数var。保存
    // 有偏移值
    //////////////////////////////////////////////////////////////
    DWORD GetNum( DWORD dwOffset )
    {
        HANDLE hFile = NULL;
    	DWORD dwSigNum = 0;         // 用于保存位于偏移位置的DWORD字节的内容
    	DWORD dwNum = 0;            // 恒为0,用作ReadFile的參数
    	// 打开名为cm4.epe的文件,该文件与本程序应处于同一文件夹下
    	hFile = CreateFile("cm4.epe",
    		               GENERIC_READ,
    					   0,
    					   NULL,
    					   OPEN_EXISTING,
    					   FILE_ATTRIBUTE_NORMAL,
    					   NULL
                          );
    	// 假设文件打开失败,则提示出错信息并退出
    	if (hFile == INVALID_HANDLE_VALUE)
    	{
             printf("Could not open cm4.epe
    "); 
             return 0;
    	}
        // 设置文件指针到指定的位置
    	SetFilePointer(hFile, dwOffset, 0, FILE_BEGIN);
    	// 读取起始于文件指针位置的十六进制代码,读取长度为4个字节(DWORD)
    	ReadFile(hFile, &dwSigNum, sizeof(DWORD), &dwNum, NULL);
        
    	CloseHandle(hFile);
    	return dwSigNum;
    }
    
    int main()
    {
        int a, b, c;             // 用于控制循环次数
    	int i, j, m, n;          // 用于保存第一组验证码的四个ASCII码值
    	int count = 10;          // 用于保存生成的注冊码的组数
    	int tmp[4];              // 用于暂时保存前四位验证码的ASCII码减去0x30或0x37后的值
    	int temp;                // 用于保存暂时的运算结果
    	int edx;                 // 用于保存运算的余数
    	
    	DWORD Num;               // 用于保存位于cm4.epe对应偏移处的十六进制代码
    	DWORD var_9C = 0x800000; // 这是一个固定的值,作为之后验证中的除数
    	DWORD var_14;            // 用于保存第一循环算法终于运算的结果
    	DWORD var_20 = 0;
    	DWORD var_2C = 0;
    	DWORD var_38 = 0;        // 这三个变量用于保存第二循环算法中的运算结果	
    	
    	char Reg[4][4] = { "0" };// 这个二维数组保存终于得出的注冊码
    	char letter[37] = { "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" };// 字母表,用于生成注冊码
    //////////////////////////////////////////////////////////////
    // 这里是注冊码算法的第一处验证循环。这里通过四重循环。来不断
    // 验证各种不同的ASCII码值的组合,也就是从0000到ZZZZ,从而生成
    // 第一组的验证码(4个字符)
    //////////////////////////////////////////////////////////////
    	// 此处循环生成第一组验证码的第一个字符
    	for ( i = 48; i <= 90; i++ )
    	{	    
    		if( i >= 58 && i <= 64 ) continue;
    		// 假设注冊码是数字,则减去48
    		if( i >= 48 && i <= 57 )
    		{
    		    tmp[0] = i - 48;
    		}
    		// 假设注冊码是大写字母,则减去55
    		else
    		{
    			tmp[0] = i - 55;
    		}
    		// 此处循环生成第一组验证码的第二个字符
    		for ( j = 48; j <= 90; j++ )
    		{
    		    if( j >= 58 && j <= 64 ) continue;
    			// 假设注冊码是数字,则减去48
    			if( j >= 48 && j <= 57 )
    			{
    			    tmp[1] = j - 48;
    			}
    			// 假设注冊码是大写字母。则减去55
    			else
    			{
    			    tmp[1] = j - 55;
    			}
    			// 此处循环生成第一组验证码的第三个字符
    			for ( m = 48; m <= 90; m++ )
    			{
    			    if( m >= 58 && m <= 64 ) continue;
    				// 假设注冊码是数字,则减去48
    			    if( m >= 48 && m <= 57 )
    				{
    			        tmp[2] = m - 48;
    				}
    			    // 假设注冊码是大写字母,则减去55
    		    	else
    				{
    			        tmp[2] = m - 55;
    				}
    				// 此处循环生成第一组验证码的第四个字符
    				for ( n = 48; n <= 90; n++ )
    				{
    				    if( n >= 58 && n <= 64 ) continue; 
    					// 假设注冊码是数字,则减去48
    			        if( n >= 48 && n <= 57 )
    					{
    			            tmp[3] = n - 48;
    					}
    			        // 假设注冊码是大写字母,则减去55
    			        else
    					{
    			            tmp[3] = n - 55;
    					}
    				    var_14 = 0;
    					// 依照算法进行运算
    					for( a = 3; a >= 0; a-- )
    					{
    					    var_14 *= 36;
                            var_14 += tmp[a];
    					}
    					if (var_14 % 36 != 0 )
    					{					    												
                            // loc_4132CB
                            Num = GetNum(var_14);
                            var_20 = Num % var_9C;
    						temp = var_20;
    						temp %= 36;
    						if ( temp == 0 )
    						{
    						    continue;
    						}
    						else
    						{
    						    // loc_41330F
                                Num = GetNum(var_20);
                                var_2C = Num % var_9C;
    							temp = var_2C;
    							temp %= 36;
    							if ( temp == 0 )
    							{
    						        continue;
    							}
    						    else
    							{
    							    // loc_413353
                                    Num = GetNum(var_2C);
                                    var_38 = Num % var_9C;
    								temp = var_38;
    								temp %= 36;
    							    if ( temp == 0 )
    								{
    						            continue;
    								}
    						        else
    								{					
    									// 第一组(前四个)注冊码验证完成并赋值
    									Reg[0][0] = i;
    									Reg[0][1] = j;
    									Reg[0][2] = m;
    									Reg[0][3] = n;
    //////////////////////////////////////////////////////////////
    // 这里是注冊码算法的第二处验证循环,这里通过之前运算的结果,
    // 经过运算得到余数(edx)。作为letter[]字母表的偏移,从而生成
    // 注冊码字符
    //////////////////////////////////////////////////////////////
                                        // loc_4133C5,第二处循环算法
    									for( b = 0; b < 4; b++ )
    									{
    									    c = 1;
    										edx = var_20 % 36;
    										Reg[c][b] = letter[edx];
    										
    										c += 1;	
    									    temp = var_20 / 36;
    										var_20 = temp;
    										edx = var_2C % 36;
    										Reg[c][b] = letter[edx];
    										
    										c += 1;
    										temp = var_2C / 36;
    										var_2C = temp;
    										edx = var_38 % 36;
    										Reg[c][b] = letter[edx];
    										temp = var_38 / 36;
    										var_38 = temp;
    									}
    									// 输出已经运算完成的注冊码
    									for ( a = 0; a <= 3; a++)
    									{
    									    for( b = 0; b <= 3; b++ )
    										{
    										    printf("%c", Reg[a][b]);	    								
    										}
    										if(a != 3)
    										{
    										    printf("-");
    										}
    									}
    									printf("
    ");
    								}
    							}
    						}
    						count--;											
    					}
    					if ( count == 0 )
    					{
    					    getchar();
    						return 0;
    					}
    				}
    			}
    		}
    	}	
    	return 0;
    }

            结合之前的分析。代码并不难理解。仅仅是各种验证与循环比較多。这里我仅仅在乎实现。而不考虑代码的优化等问题。

    四、程序測试

            这里我先生成10个注冊码。因为后三组注冊码字符是严格取决于第一组注冊码字符的取值的,而第一组注冊码字符的取值范围是在0000到FFFF之间。那么我这里生成的10个注冊码事实上也就全部注冊码中的前十个,执行结果例如以下:


    图5 所生成的前10个注冊码

            为了測试这些注冊码,我们无需又一次安装游戏,由于游戏在安装时会在注冊表中建立对应的键值。用于保存注冊码。而游戏每次启动又会查询注冊表获取该注冊码。所以我们仅仅需改动该键值就可以:


    图6

            我不可能验证全部注冊码,可是验证这十个。结果是可行的。那么能够觉得上面的程序是可行的,这里不再赘述。

    五、后记

            这三篇关于CM4逆向分析的文章,可以说是我到眼下为止写作时间跨度最长的了。本系列的上篇,我是在研究完FIFA07《仙剑奇侠传》之后就打算写作的,大概是在今年6月中旬的时候。那时写了个初稿,也就是通过“爆破”的方式来绕过验证机制,想继续写注冊机的编写部分,无奈水平有限。一直拖到如今才完毕。这大半年的时间也是我的知识水平增长最快的半年,要不然我依然不能分析出CM4的验证机制。这三篇文章的中篇和下篇。是利用了一个多星期的时间袭击完毕的。这段时间每天仅仅有非常少的时间用于分析,并且分析过程中也走了非常多的弯路,也正是由于这些弯路,我才可以在文章中始终展示出一片坦途。由于是第一次分析注冊码验证机制,没有经验,耗时也比較长。可是我的收获却是巨大的。我也希望各位读者也可以从这些文章中有所收获,多多练习,多多思考,不断尝试,将自己所学。真正运用于自己的身边以及自己的工作中。
  • 相关阅读:
    关于框架
    如何理解scrapy Selector
    Excel表格如何设置密码 Excel2003/2007/2010设置密码教程
    windows 下 iptables
    学习使用windows下类似iptables的防火墙软件
    看懂SqlServer查询计划
    SQL Server 2008如何创建定期自动备份任务
    開啟活動監視器 (SQL Server Management Studio)
    CentOS 6.5安全加固及性能优化
    Linux系统部署规范v1.0
  • 原文地址:https://www.cnblogs.com/llguanli/p/6757951.html
Copyright © 2020-2023  润新知