接着上次的分析,继续分析下去,对于这个CrackMe,其要求是写出注册机,那么我们就根据其注册码的验证过程,进行逆向分析,首先找到最关键的一个爆破点,我们先看看这个爆破点的位置,如图1可见最后的验证关键点其实在那个MagicNum的值上面,因为用户名是我们给定的,属于已知值,而序列号则是可以由我们构造的也可以认为是已知值,唯一不确定的就是那个MagicNum了,而这个值却跟我们输入的用户名及序列号密切相关。因此切实找到:MagicNum(魔数)如图2与用户名、序列号之间的关系就是最后写出注册机的关键所在了。
接下来我们大致看看这三者之间的关系 :最后的注册码正确与否取决于序列号的最后四个字符,即:我们设dword ptr[402168]为a,
dword ptr[40216c]为b,dword ptr[402179]为c,需要算出的最后四个字节为nResult,其必须满足,nResult的值必须都在ASCII码可打印表示范围内(PS:不然你无法输入对应的字符......),且nResult == a^b^MagicNum|0x40404040&0x77777777^c。其中的a\b\c皆可由我们构造,而MagciNum则与我们构造的这些数相关,到底有些什么关系呢?我们接下来看看MagicNum与我们构造的用户名及序列号之间的关系。
(图2)
其中的edi可以是用户名算出值亦可为序列号算出值。(此值即为Step1过程中算出的值),整个递归过程与edi值有关。通过阅读程序,我们需要找到edi与递归次数之间的直接关系,这样我们可以想办控制魔数的自增次数从而控制住魔数的值。通过在给定用户名的情况下,利用edi与魔数之间的关系,构造出满足条件的序列号(这个过程可以控制在可容忍范围内的穷举)。在分析的过程中比较棘手的地方是就是递归调用。OK,看到这里基本定位了关键点所在。我们先暂停一下,仅从分析如何快速制作出可行的注册机的角度来尝试一下,在这个角度上,首先要保证递归函数调用后的返回值ECX为1(这个时候关于递归与魔数的关系就可以不用考虑了),把这个条件分析出来。然后只用去利用:nResult == a^b^MagicNum|0x40404040&0x77777777这个值,自己去构造符合条件的剩余的序列号部分即可。例如:nResult值已知的情况下,设序列号剩下的两个字节分别为X1、X2,要求nResult^X1==X2,且X1,X2都在['0','~']这个范围内。根据这个思路可以写出如下的注册机:
// KeyGen.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include
#include
#include
#include
#pragma warning(disable:4244)
typedef unsigned long DWORD;
typedef unsigned int UINT;
//////////////////////////////////////////////////////////////////////////
//函数声明部分
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//扩展用户名到16字节
void ExtendNameTo16Bytes( char *pStrName );
//验证序列号Step1
void CalculateKeyStep1( const char *pStrName, const char *pStrSerialNum,
DWORD &outTempNum1, DWORD &outTempNum2 );
//抽离的递归函数
void Func( DWORD nTempNum1, DWORD nTempNum2,
UINT &uCount, DWORD &nMagicNum );
//验证序列号Step2
bool CalculateKeyStep2( DWORD nTempNum1, DWORD nTempNum2,
const char *pStrName, char *szLast8BytesSerials );
//构造最后的8字节序列号
bool GenerateCorrect8BytesSerial( const DWORD nSrc, char szOUT[] );
//N个字节字符串生成器
void GenerateNBytesStrings( const char *pStrUserName, char *pStr,
const UINT nBytes );
//////////////////////////////////////////////////////////////////////////
//函数实现部分
//////////////////////////////////////////////////////////////////////////
void ExtendNameTo16Bytes( char *pStrName )
{
assert( NULL != pStrName );
if ( NULL == pStrName ) return;
size_t nStrLen = strlen(pStrName);
if ( nStrLen >= 16 ) return;
size_t nNeedtoExtend = 16 - nStrLen;
int nCount = 0;
while ( nNeedtoExtend-- )
{
*(pStrName+nStrLen+nCount) = *(pStrName+nCount);
++nCount;
}
}
void CalculateKeyStep1( const char *pStrName, const char *pStrSerialNum,
DWORD &outTempNum1, DWORD &outTempNum2 )
{
assert( NULL != pStrName );
assert( NULL != pStrSerialNum );
if ( NULL == pStrName || NULL == pStrSerialNum )
return;
outTempNum1 = *(DWORD*)pStrName;
outTempNum2 = *(DWORD*)(pStrName+4);
DWORD nSerialTemp1 = *(DWORD*)pStrSerialNum;
DWORD nSerialTemp2 = *(DWORD*)(pStrSerialNum+4);
outTempNum1 ^= nSerialTemp1;
outTempNum2 ^= nSerialTemp2;
outTempNum1 &= 0x7F3F1F0F; //0111 1111-0011 1111-0001 1111-0000 1111
//x31=0,x23=0,x15=0,x7=0
outTempNum2 &= 0x07030100; //0111 0000-0011 0000-0001 0000-0000 0000
//x31=0,x23=0,x15=0,x7=0
for (int idx=0; idx<8; ++idx)
{
std::bitset<32> vBtsEAX( outTempNum1< vBtsEBX( outTempNum2< vBtsR(0);
vBtsR[7] = vBtsEAX[31];
vBtsR[6] = vBtsEAX[23];
vBtsR[5] = vBtsEAX[15];
vBtsR[4] = vBtsEAX[7];
vBtsR[3] = vBtsEBX[31];
vBtsR[2] = vBtsEBX[23];
vBtsR[1] = vBtsEBX[15];
vBtsR[0] = vBtsEBX[7];
int nValue = vBtsR.to_ulong();
if (idx <= 3)
{
nValue <<= 8*(3-idx);
outTempNum1 ^= nValue;
}
else
{
nValue <<= 8*(7-idx);
outTempNum2 ^=nValue;
}
}
}
void Func( DWORD nTempNum1, DWORD nTempNum2,
UINT &uCount, DWORD &nMagicNum )
{
if ( uCount <= 0x80 ) return;
UINT uTMCount = uCount;
DWORD nValue = nTempNum1;
uCount &= 0xff; //取最低一个字节,根据下面程序的执行
if ( uCount > 8 ) //其实为取CL的低四位.
{
nValue = nTempNum2;
uCount >>= 4; //取CL高4位.
}
////////////////////////////////////////////////
//nValue为DWORD,占4个字节,如下图示:
//|---|---|---|---|
//| 1 | 2 | 3 | 4 |
//|___|___|___|___|
//
UINT uRotateCount = (UINT)(log((float)uCount)/log(2.0f));
switch( uRotateCount%4 )
{
case 1:
nValue >>= 16; //|---|
break; //| 2 |
//|___|
case 2:
nValue >>= 8; //|---|
break; //| 3 |
//|___|
case 3:
break; //|---|
//| 4 |
//|___|
default:
nValue >>= 24; //|---|
break; //| 1 |
//|___|
}
uCount = (uTMCount&0xff00)>>8;
nValue &=uCount;
uCount = 0x80;
while ( uCount&nValue ? 1:uCount>>=1 )
{
if ( !(uCount&nValue) ) continue;
nValue ^= uCount;
uCount ^= ((uCount&0xff)<<8);
uTMCount &= 0xff00;
uTMCount ^= uCount;
++nMagicNum;
Func( nTempNum1, nTempNum2, uTMCount, nMagicNum );
uCount = 0x80;
}
uCount = uTMCount; //In order to retrieve the return value
}
bool GenerateCorrect8BytesSerial( const DWORD nSrc, char szOUT[] )
{
if ( NULL == szOUT )
return false;
char cCount = '0';
char cV1(0);
char cV2(0);
char cV3(0);
char cV4(0);
char cS1(0);
char cS2(0);
char cS3(0);
char cS4(0);
while ( cCount <= '~' )
{
//第一字节
cV1 = cCount^((nSrc&0xff000000)>>24);
if ( 0 == cS1 && cV1 >= '0' && cV1 <= '~' )
cS1 = cCount;
//第二字节
cV2 = cCount^((nSrc&0xff0000)>>16);
if ( 0 == cS2 &&cV2 >= '0' && cV2 <= '~' )
cS2 = cCount;
//第三字节
cV3 = cCount^((nSrc&0xff00)>>8);
if ( 0 == cS3 && cV3 >= '0' && cV3 <= '~' )
cS3 = cCount;
//第四字节
cV4 = cCount^((nSrc&0xff));
if ( 0 == cS4 &&cV4 >= '0' && cV4 <= '~' )
cS4 = cCount;
if ( 0 != cS1 && 0 != cS2 && 0 != cS3 && 0 != cS4 )
{
szOUT[0] = cS1;
szOUT[1] = cS2;
szOUT[2] = cS3;
szOUT[3] = cS4;
szOUT[4] = cV4;
szOUT[5] = cV3;
szOUT[6] = cV2;
szOUT[7] = cV1;
return true;
}
++cCount;
}
return false;
}
bool CalculateKeyStep2( DWORD nTempNum1, DWORD nTempNum2,
const char *pStrName, char *szLast8BytesSerials )
{
if ( NULL == szLast8BytesSerials || NULL == pStrName )
return false;
DWORD nMagicNum = 0xfedcba98;
UINT uCount = 0xff01;
Func( nTempNum1, nTempNum2, uCount,nMagicNum );
if ( 1 == uCount )
{
nTempNum1 = *(DWORD*)(pStrName+8);
nTempNum2 = *(DWORD*)(pStrName+ 0xc);
nTempNum1 ^= nTempNum2;
nTempNum1 ^= nMagicNum;
nTempNum1 |= 0x40404040;
nTempNum1 &= 0x77777777;
return GenerateCorrect8BytesSerial( nTempNum1, szLast8BytesSerials );
}
return false;
}
void GenerateNBytesStrings( const char *pStrUserName, char *pStr,
const UINT nBytes )
{
assert( NULL != pStr );
if( NULL == pStr ) return;
while ( pStr[nBytes-1] <= '~' )
{
//////////////////////////////////////////////////////////////////////////
//Add codes for what you want to deal with
DWORD nT1(0), nT2(0);
CalculateKeyStep1( pStrUserName, pStr, nT1, nT2 );
char szLast8BytesSerials[9] = { 0 };
if ( CalculateKeyStep2( nT1, nT2, pStrUserName, szLast8BytesSerials ) )
{
printf("%s's Serial is: %s%s\n", pStrUserName, pStr, szLast8BytesSerials );
}
//////////////////////////////////////////////////////////////////////////
++pStr[0];
for(UINT idx=0; idx '~' )
{
pStr[idx] = '0';
pStr[idx+1]++;
}
}
}
}
int _tmain(int argc, _TCHAR* argv[])
{
const char szHint0[] = "***********This is the KeyGen for CycleCrackMe***********";
const char szHint1[] = "Please Input your name:";
std::cout << szHint0 << std::endl;
std::cout << szHint1 << std::endl;
char szUserName[32] = { 0 };
std::cin >> szUserName;
std::cout << "Please Waiting......" << std::endl;
ExtendNameTo16Bytes( (char*)szUserName );
char szSerialFirst8Bytes[] = "00000000";
GenerateNBytesStrings( szUserName, szSerialFirst8Bytes, 8 );
system("pause");
return 0;
}
测试结果如下:
此次仅仅是利用了注册算法的进行的部分逆运算获取的注册码,且在程序实现过程中没有达成对所有存在的注册码的穷举,因此,需要对注册机程序做进一步的修改,使之达到既定的要求。
To be continued.......