最近挺忙的,本來需要各種為前路做准備,無奈自己天生屬於低血壓型的人,偏偏就是提不起勁兒來干正事兒,卻把大好的一天光陰全交代到SRS的分析工作上了。雖然說這多少算不務正業,不過由於本人有着嚴重的軟件新版本強迫症,故這算是給自己的一個開脫理由吧。
這款軟件的破解工作展開比較容易,沒有加殼,直接上工具分析之。首先請出OllyDBG來,通過查找字符串引用和查找API MessageBox引用的老法子,很容易就定位了大概的關鍵代碼位置。
找到關鍵位置后,順手又記錄了一下函數調用棧,然后打開了神器IDA。用IDA載入后,跳轉到之前找到的關鍵代碼RVA處,着手分析。說起來,我自己喜歡將動態和靜態調試方法結合起來用,不如果不願意太費神分析匯編代碼,有時候直接看運行時結果便是最直觀和省時的。載入后發覺之前判斷的關鍵點實際上是CWinApp::ShowAppMessageBox函數,繼續沿着剛才記錄的函數運行時調用棧往上找,依次順藤摸瓜:AfxMessageBox--->Sub44D240,這才到了SRS程序的代碼領空,於是正是着手分析。(IDA這個靜態分析功能實在是強大,它可以根據二進制代碼特征判斷出其是否是庫函數代碼。這大大節省了我的時間,應為對這類代碼我只需要看SDK文檔就行了,而不需要去分析其實際的代碼行為)
進一步根據線索判斷(Sub44D240里包含了SendMessage、AfxMessageBox、GetWindowText等函數,足以說明該函數的關鍵作用),我鎖定在了Sub44D240函數的Sub4430F0調用上,繼續追蹤到Sub442FF0上,繼而是Sub44B9E0過程,最終到了Sub44BC30過程上。在該過程中,我同時追蹤到了用戶輸入的ProductID以及Serial Number等信息,這進一步是我確認了我的判斷。
該過程具體代碼如下:

1 .text:0044BC30 2 .text:0044BC30 ; =============== S U B R O U T I N E ======================================= 3 .text:0044BC30 4 .text:0044BC30 ; Attributes: bp-based frame 5 .text:0044BC30 6 .text:0044BC30 check_PID_SN proc near ; CODE XREF: realCheckSN+182p 7 .text:0044BC30 8 .text:0044BC30 productId_segs_temp= dword ptr -14h 9 .text:0044BC30 new_ProdectID = dword ptr -10h 10 .text:0044BC30 sn_lowerDWord = dword ptr -4 11 .text:0044BC30 SN = dword ptr 8 12 .text:0044BC30 13 .text:0044BC30 push ebp 14 .text:0044BC31 mov ebp, esp 15 .text:0044BC33 and esp, 0FFFFFFF8h 16 .text:0044BC36 sub esp, 14h 17 .text:0044BC39 push ebx 18 .text:0044BC3A push esi 19 .text:0044BC3B push edi 20 .text:0044BC3C push 14h ; unsigned int 21 .text:0044BC3E mov ebx, eax ; ebx指向ProductID首位置 22 .text:0044BC40 call j_??2@YAPAXI@Z ; operator new(uint) 23 .text:0044BC45 mov esi, eax 24 .text:0044BC47 lea eax, [esi+6] 25 .text:0044BC4A push eax 26 .text:0044BC4B mov eax, [ebp+SN] 27 .text:0044BC4E lea ecx, [esi+4] 28 .text:0044BC51 push ecx 29 .text:0044BC52 lea edx, [esi+2] 30 .text:0044BC55 push edx 31 .text:0044BC56 push esi ; 新申請的14h的空間 32 .text:0044BC57 push offset a4x4x4x4x ; "%4x-%4x-%4x-%4x" 33 .text:0044BC5C push eax ; SN首地址 34 .text:0044BC5D call String2Integer ; 將字符串SN轉換為數組,比如 35 .text:0044BC5D ; “E6E9-24CB-2968-09BC”變為 36 .text:0044BC5D ; E9 E6 ... 37 .text:0044BC62 mov ecx, [esi+4] ; ECX為后半部分SN 38 .text:0044BC65 mov edi, [esi] ; EDI為前半部分SN 39 .text:0044BC67 push esi ; void * 40 .text:0044BC68 mov [esp+40h+sn_lowerDWord], ecx 41 .text:0044BC6C call j__free 42 .text:0044BC71 lea edx, [esp+40h+productId_segs_temp] 43 .text:0044BC75 push edx 44 .text:0044BC76 push offset a4x ; "%4x" 45 .text:0044BC7B push ebx 46 .text:0044BC7C call String2Integer ; 將ProductID第一段轉換為Word 47 .text:0044BC81 mov esi, [esp+4Ch+productId_segs_temp] ; esi儲存productId_1 48 .text:0044BC85 lea eax, [esp+4Ch+productId_segs_temp] 49 .text:0044BC89 push eax 50 .text:0044BC8A lea ecx, [ebx+0Ah] 51 .text:0044BC8D push offset asc_48C804 ; "%x" 52 .text:0044BC92 push ecx 53 .text:0044BC93 call String2Integer 54 .text:0044BC98 mov edx, [esp+58h+productId_segs_temp] ; EDX儲存ProductId_2 55 .text:0044BC9C add esp, 38h 56 .text:0044BC9F xor eax, eax 57 .text:0044BCA1 push eax 58 .text:0044BCA2 push esi 59 .text:0044BCA3 push eax 60 .text:0044BCA4 push edx 61 .text:0044BCA5 call __allmul 62 .text:0044BCAA mov esi, eax ; product_1*product_2乘積的低word放入esi 63 .text:0044BCAC lea eax, [esp+20h+productId_segs_temp] 64 .text:0044BCB0 push eax 65 .text:0044BCB1 lea ecx, [ebx+14h] 66 .text:0044BCB4 push offset asc_48C804 ; "%x" 67 .text:0044BCB9 push ecx 68 .text:0044BCBA mov [esp+2Ch+new_ProdectID+4], edx 69 .text:0044BCBE call String2Integer 70 .text:0044BCC3 mov edx, [esp+2Ch+new_ProdectID+4] 71 .text:0044BCC7 mov eax, [esp+2Ch+productId_segs_temp] ; eax保存productId_3 72 .text:0044BCCB add esp, 0Ch 73 .text:0044BCCE push edx 74 .text:0044BCCF push esi 75 .text:0044BCD0 push 0 76 .text:0044BCD2 push eax 77 .text:0044BCD3 call __allmul 78 .text:0044BCD8 lea ecx, [esp+20h+productId_segs_temp] 79 .text:0044BCDC push ecx 80 .text:0044BCDD push offset asc_48C804 ; "%x" 81 .text:0044BCE2 add ebx, 1Eh 82 .text:0044BCE5 push ebx 83 .text:0044BCE6 mov esi, eax 84 .text:0044BCE8 mov [esp+2Ch+new_ProdectID+4], edx 85 .text:0044BCEC call String2Integer 86 .text:0044BCF1 mov edx, [esp+2Ch+new_ProdectID+4] 87 .text:0044BCF5 mov eax, [esp+2Ch+productId_segs_temp] 88 .text:0044BCF9 add esp, 0Ch 89 .text:0044BCFC push edx 90 .text:0044BCFD push esi 91 .text:0044BCFE push 0 92 .text:0044BD00 push eax 93 .text:0044BD01 call __allmul ; 結果edx高位,eax低位 94 .text:0044BD06 mov esi, edx 95 .text:0044BD08 shr esi, 10h 96 .text:0044BD0B mov [esp+20h+new_ProdectID], eax 97 .text:0044BD0F mov [esp+20h+new_ProdectID+4], edx 98 .text:0044BD13 xor ebx, ebx 99 .text:0044BD15 mov cl, 10h 100 .text:0044BD17 call __allshr ; {edx,eax}==new_prodectID >> 10h 101 .text:0044BD1C mov ecx, [esp+20h+new_ProdectID+4] 102 .text:0044BD20 xor edx, edx 103 .text:0044BD22 push 1 104 .text:0044BD24 and eax, 0FFFF0000h 105 .text:0044BD29 push edx 106 .text:0044BD2A add esi, eax 107 .text:0044BD2C adc ebx, edx 108 .text:0044BD2E mov edx, [esp+28h+new_ProdectID] 109 .text:0044BD32 push ecx 110 .text:0044BD33 push edx 111 .text:0044BD34 call __allmul ; eax=0, edx為new_productID的低位 112 .text:0044BD39 push 0 113 .text:0044BD3B add esi, eax 114 .text:0044BD3D push 8475h 115 .text:0044BD42 adc ebx, edx 116 .text:0044BD44 push ebx 117 .text:0044BD45 push esi 118 .text:0044BD46 call __alldiv ; {edx,eax} == {edx,esi} / 0x8475 119 .text:0044BD4B push 0 120 .text:0044BD4D push 0AE6000h 121 .text:0044BD52 push edx 122 .text:0044BD53 push eax 123 .text:0044BD54 call __allmul 124 .text:0044BD59 add eax, 91F2884Dh 125 .text:0044BD5E adc edx, 2DCh 126 .text:0044BD64 mov cl, 0Ah 127 .text:0044BD66 call __allshr 128 .text:0044BD6B push 0 129 .text:0044BD6D push 2046h 130 .text:0044BD72 push edx 131 .text:0044BD73 push eax 132 .text:0044BD74 call __allmul 133 .text:0044BD79 mov ecx, 0FFFFFFFEh 134 .text:0044BD7E sub ecx, eax 135 .text:0044BD80 mov eax, 0FFFFFFFFh 136 .text:0044BD85 sbb eax, edx 137 .text:0044BD87 mov edx, [esp+20h+sn_lowerDWord] 138 .text:0044BD8B shld edx, edi, 1 139 .text:0044BD8F add edi, edi 140 .text:0044BD91 cmp edi, ecx 141 .text:0044BD93 jnz short loc_44BDA5 142 .text:0044BD95 cmp edx, eax 143 .text:0044BD97 jnz short loc_44BDA5 144 .text:0044BD99 mov eax, 1 145 .text:0044BD9E pop edi 146 .text:0044BD9F pop esi 147 .text:0044BDA0 pop ebx 148 .text:0044BDA1 mov esp, ebp 149 .text:0044BDA3 pop ebp 150 .text:0044BDA4 retn 151 .text:0044BDA5 ; --------------------------------------------------------------------------- 152 .text:0044BDA5 153 .text:0044BDA5 loc_44BDA5: ; CODE XREF: check_PID_SN+163j 154 .text:0044BDA5 ; check_PID_SN+167j 155 .text:0044BDA5 pop edi 156 .text:0044BDA6 pop esi 157 .text:0044BDA7 xor eax, eax 158 .text:0044BDA9 pop ebx 159 .text:0044BDAA mov esp, ebp 160 .text:0044BDAC pop ebp 161 .text:0044BDAD retn 162 .text:0044BDAD check_PID_SN endp
基本思路
通過分析,我了解到,其算法大概思路如下:首先,注冊流程有效輸入為Serial Number和Product ID,Registration No實際上沒有參與注冊的驗證計算過程。SRS通過系統各種信息生成Product ID,然后用戶需要提供與Product ID匹配的Serial Number方能注冊。當然,Serial Number是需要你拿美刀換的。
簡而言之,SRS將Product ID作fp變換,得到一個64bit長的整數,並將用戶輸入的序列號做fs變換同樣得到一個64bit長整數。為了使注冊成功,需要滿足:
fp(gen()) = fs(SerialNumber) 成立 (1)
這其中,gen()的算法我們不需要管,因為其結果在界面Product ID框中已經顯示了。我們需要找到fp和fs的實現算法,並順利推出fs-1的實現。從而:
SerialNumber = fs-1( fp(ProductID) ) (2)
Product ID變換算法描述
我們首先描述fp的實現。fp基本上是由我不知道原理的各種數值變換組成,為了精確表述,我直接用C語言描述:

1 __int64 getProductID(char* id){ 2 __int64 temp = 1; 3 DWORD elem; 4 5 for(int i = 0; i < 4; i++){ 6 sscanf(id + (i * 5), "%x", &elem); 7 temp *= elem; 8 } 9 10 //高地位變換 11 DWORD lowerDWord = temp; 12 DWORD upperDWord = temp >> 0x20; 13 WORD lower = upperDWord; 14 WORD upper = upperDWord >> 0x10; 15 upperDWord = lower; 16 upperDWord <<= 0x10; 17 upperDWord |= upper; 18 19 temp = lowerDWord; 20 temp <<= 0x20; 21 temp |= upperDWord; 22 23 24 temp /= 0x8475; 25 temp *= 0xAE6000; 26 temp += 0x2DC91F2884D; 27 temp >>= 0xA; 28 temp *= 0x2046; 29 temp = 0xfffffffffffffffe - temp; 30 31 return temp; 32 }
Serial Number變換算法描述
接下來描述fs函數的實現。我們稱變換后的Product ID為TransPID,並且LowerDW和HighDW表示一個64bit整數的低雙字和高雙字,shld表示對應匯編指令的函數,TransSerialNumberHighDW和TransSerialNumberLowDW分別表示變換后的序列號高雙字和低雙字。則有:
TransSerialNumberHighDW(SerialNumber) = shld(UpperDW(SerialNumber), LowerDW(SerialNumber), 1); (3)
TransSerialNumberLowDW(SerialNumber) = LowerDW(SerialNumber) + LowerDW(SerialNumber); (4)
fs(SerialNumber) = TransSerialNumberHighDW<< 32 | TransSerialNumberLowDW; (5)
Serial Number逆向變換算法描述
了解了fs的實現,我們接下來需要着手研究實現fs-1的思路。由於fs高雙字和低雙字分別由不同的方式變換的(3)、(4),所以我們需要分別求出SerialNumber的高低雙字。得到Serial Number的低雙字很簡單,由(4)可知,我們只要將LowerDW(fp(ProductID))除以2就行。但需注意的是,整個雙字值域中,LowerDW(fp(ProductID)) / 2 + (2<<31)同樣能滿足條件(由於溢出導致的相等),這點很重要!
再來考慮高位的算法。我們需要把HighDW(fp(ProductID))往右移一位,這是左邊補入的一位可以任意。這時需要注意,考慮到shld性質,有以下兩種情況:
①如果HighDW(fp(ProductID))最低位是1則需要LowerDW(SerialNumber)的最高位為1
②如果HighDW(fp(ProductID))最低位是0則需要LowerDW(SerialNumber)的最高位為0
考慮到LowerDW(fp(ProductID)) / 2和LowerDW(fp(ProductID)) / 2 + (2<<31)均可滿足條件,若是情況①則選取后者,因為此時可保證LowerDW(SerialNumber)的最高位為1。同理,若是情況②則需選擇前者。
該算法的C語言描述如下:

1 void getSerialNumber(__int64 productId, char* buffer){ 2 DWORD upper = productId >> 0x20; 3 DWORD lower = productId; 4 assert((lower & 1) == 0); //lower = snLower + snLower因此lower必須為偶數 5 6 7 8 DWORD snUpper = upper >> 1; 9 10 DWORD snLower =0; 11 if((upper & 1) == 1){ 12 snLower = lower / 2; 13 snLower += (1<<31); 14 }else{ 15 snLower = lower / 2; 16 } 17 18 DWORDLONG sn = snUpper; 19 sn <<= 0x20; 20 sn |= snLower; 21 22 WORD *p = (WORD*)&sn; 23 sprintf(buffer, "%04x-%04x-%04x-%04x", p[0], p[1], p[2], p[3]); 24 }
小結
IDA很強大,Crack很費時間,收獲的免費SRS使用權和投入的大量時間不成正比。結論:以后還是盡量少搞Crack吧。