逆向序列号生成算法(三)
?接著上次的分析,繼續分析下去,對于這個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 <assert.h> #include <iostream> #include <math.h> #include <bitset> #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=0outTempNum2 &= 0x07030100; //0111 0000-0011 0000-0001 0000-0000 0000//x31=0,x23=0,x15=0,x7=0for (int idx=0; idx<8; ++idx){std::bitset<32> vBtsEAX( outTempNum1<<idx );std::bitset<32> vBtsEBX( outTempNum2<<idx );std::bitset<32> 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 withDWORD 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<nBytes && pStr[nBytes-1] <= '~'; ++idx){if( pStr[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.......
總結
以上是生活随笔為你收集整理的逆向序列号生成算法(三)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计时器操作--打点计时器
- 下一篇: 怎样用c语言编出旗子的图案,三色棋解法的