zoukankan      html  css  js  c++  java
  • 逆向工程第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的注册码验证机制彻底分析完毕,那么接下来就可以开始注册码生成器的编写了。

    三、编写注册码生成器

            结合之前的分析,我们很容易就可以编写出注册机。但是要注意,我们在生成注册码的时候,是需要利用“cm4.epe”这个文件的,需要将二者放置在同一目录,让注册机程序便于读取“密码本”中的内容以进行运算,代码如下:

    #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的验证机制。这三篇文章的中篇和下篇,是利用了一个多星期的时间突击完成的,这段时间每天只有很少的时间用于分析,而且分析过程中也走了非常多的弯路,也正是因为这些弯路,我才能够在文章中始终展示出一片坦途。因为是第一次分析注册码验证机制,没有经验,耗时也比较长。但是我的收获却是巨大的。我也希望各位读者也能够从这些文章中有所收获,多多练习,多多思考,不断尝试,将自己所学,真正运用于自己的身边以及自己的工作中。
  • 相关阅读:
    堆栈学习
    需要阅读的书籍
    Rust Book Lang Ch.19 Fully Qualified Syntax, Supertraits, Newtype Pattern, type aliases, never type, dynamic sized type
    Rust Lang Book Ch.19 Placeholder type, Default generic type parameter, operator overloading
    Rust Lang Book Ch.19 Unsafe
    Rust Lang Book Ch.18 Patterns and Matching
    Rust Lang Book Ch.17 OOP
    Rust Lang Book Ch.16 Concurrency
    Rust Lang Book Ch.15 Smart Pointers
    HDU3966-Aragorn's Story-树链剖分-点权
  • 原文地址:https://www.cnblogs.com/csnd/p/11785780.html
Copyright © 2011-2022 走看看