010破解(二)之算法分析一
010破解(一)之暴力破解
暴力破解後,仍然想寫一個010Edit的註冊機,這篇接着上一篇分析
算法分析
分析關鍵函數:
- 單步跟蹤,找到訪問用戶名密碼的代碼,
- 一步一步分析,加註釋,
- 反覆推敲,找規律
- 寫代碼驗證
在關鍵函數出下斷點,然後註冊用戶名和密碼,監視程序運行
函數參數確定:
- 參數一:0x9
- 參數二:0x4389
- 參數三:ecx---------ecx傳參,應該是一個對象指針
內存窗口觀察ecx對象內容,我們可以發現:
- 字段一:QT數組偏移(不知道有啥用)
- 字段二:用戶名字符串地址
- 字段三:序列號字符串地址
- 字段四:一堆亂碼,(沒看出有什麼用)
通過上篇可以知道sub_40A826是算法的關鍵函數,逐行分析這個函數,逆算法
;解密序列號
013BDBC0 55 PUSH EBP
013BDBC1 8BEC MOV EBP,ESP
013BDBC3 6A FF PUSH -0x1
013BDBC5 68 595E880>PUSH 010Edito.01885E59
013BDBCA 64:A1 0000>MOV EAX,DWORD PTR FS:[0]
013BDBD0 50 PUSH EAX
013BDBD1 83EC 18 SUB ESP,0x18
013BDBD4 53 PUSH EBX
013BDBD5 56 PUSH ESI
013BDBD6 57 PUSH EDI
013BDBD7 A1 F0CFE60>MOV EAX,DWORD PTR DS:[0x2E6CFF0]
013BDBDC 33C5 XOR EAX,EBP
013BDBDE 50 PUSH EAX
013BDBDF 8D45 F4 LEA EAX,DWORD PTR SS:[EBP-0xC]
013BDBE2 64:A3 0000>MOV DWORD PTR FS:[0],EAX
013BDBE8 8BF9 MOV EDI,ECX ; this
013BDBEA 8D5F 04 LEA EBX,DWORD PTR DS:[EDI+0x4] ; ebx=this->username
013BDBED C745 F0 00>MOV DWORD PTR SS:[EBP-0x10],0x0
013BDBF4 8BCB MOV ECX,EBX ; ecx=this->username
013BDBF6 C747 30 00>MOV DWORD PTR DS:[EDI+0x30],0x0
013BDBFD C747 18 00>MOV DWORD PTR DS:[EDI+0x18],0x0
013BDC04 C747 34 00>MOV DWORD PTR DS:[EDI+0x34],0x0
013BDC0B FF15 B424E>CALL DWORD PTR DS:[<&Qt5Core.?isEmpty@QSt>; 判斷用戶名是否爲空?
013BDC11 84C0 TEST AL,AL
013BDC13 0F85 75020>JNZ 010Edito.013BDE8E ; 爲空跳到函數末尾
013BDC19 8D4F 08 LEA ECX,DWORD PTR DS:[EDI+0x8] ; ecx=this->password
013BDC1C FF15 B424E>CALL DWORD PTR DS:[<&Qt5Core.?isEmpty@QSt>; 判斷密碼是否爲空?
013BDC22 84C0 TEST AL,AL
013BDC24 0F85 64020>JNZ 010Edito.013BDE8E ; 爲空跳到函數末尾
013BDC2A 8D45 DC LEA EAX,DWORD PTR SS:[EBP-0x24] ; 局部變量var1
013BDC2D 8BCF MOV ECX,EDI ; ecx=this
013BDC2F 50 PUSH EAX
013BDC30 E8 3ABF04F>CALL 010Edito.00409B6F ; var1=password
013BDC35 BE 1446E60>MOV ESI,010Edito.02E64614 ; 獲取999字符串地址
013BDC3A 8D9B 00000>LEA EBX,DWORD PTR DS:[EBX] ; ebx=username
013BDC40 FF36 PUSH DWORD PTR DS:[ESI]
013BDC42 8BCB MOV ECX,EBX
013BDC44 FF15 E82CE>CALL DWORD PTR DS:[<&Qt5Core.??8QString@@>; ???觀察以下密碼後面的數據有變化嗎
013BDC4A 84C0 TEST AL,AL
013BDC4C 0F85 23020>JNZ 010Edito.013BDE75
013BDC52 83C6 04 ADD ESI,0x4 ; esi=02E64614+4=02E64618=指向字符串0-9A-F
013BDC55 81FE 1846E>CMP ESI,010Edito.02E64618 ; 比較02E64618 和 02E64618???
013BDC5B ^ 7C E3 JL SHORT 010Edito.013BDC40 ; 循環
013BDC5D 8A5D DF MOV BL,BYTE PTR SS:[EBP-0x21] ; BL=k[3]
013BDC60 8A7D E1 MOV BH,BYTE PTR SS:[EBP-0x1F] ; BH=k[5]
013BDC63 80FB 9C CMP BL,0x9C
013BDC66 75 70 JNZ SHORT 010Edito.013BDCD8 ; 比較k[3]=0x9C FC AC
;下面都是當 k[3]=0x9C要執行的代碼
013BDC68 8A45 DC MOV AL,BYTE PTR SS:[EBP-0x24] ; AL=k[0]
013BDC6B 3245 E2 XOR AL,BYTE PTR SS:[EBP-0x1E] ; AL=k[0]^k[6]
013BDC6E 8845 E8 MOV BYTE PTR SS:[EBP-0x18],AL ; [ebp-0x18]=al
013BDC71 8A45 DD MOV AL,BYTE PTR SS:[EBP-0x23] ; AL=k[1]
013BDC74 3245 E3 XOR AL,BYTE PTR SS:[EBP-0x1D] ; AL=k[1]^k[7]
013BDC77 FF75 E8 PUSH DWORD PTR SS:[EBP-0x18]
013BDC7A 0FB6C8 MOVZX ECX,AL ; ECX=K[1]^k[7]&0xFF
013BDC7D B8 0001000>MOV EAX,0x100
013BDC82 66:0FAFC8 IMUL CX,AX ; CX=(K[1]^k[7]&0xFF)*0x100
013BDC86 8A45 DE MOV AL,BYTE PTR SS:[EBP-0x22] ; AL=k[2]
013BDC89 32C7 XOR AL,BH ; AL=k[2]^k[5]
013BDC8B 0FB6C0 MOVZX EAX,AL ; EAX=(K[2]^k[5]&0xFF)
013BDC8E 66:03C8 ADD CX,AX
013BDC91 0FB7F1 MOVZX ESI,CX ; esi=((K[2]^k[5]&0xFF)+(K[1]^k[7]&0xFF)*0x100)&0xFFFF
013BDC94 E8 AB9904F>CALL 010Edito.00407644 ; 計算 k[0]和k[6] 結果不能等於0
013BDC99 0FB6C0 MOVZX EAX,AL ; AL=(k[0]^k[6]^0x18+0x3D)^0xA7
013BDC9C 56 PUSH ESI
013BDC9D 8947 1C MOV DWORD PTR DS:[EDI+0x1C],EAX
013BDCA0 E8 23A704F>CALL 010Edito.004083C8 ; EAX=((esi^0x7892+0x4D30)^0x3421&0xFFFF)/0xB
013BDCA5 8B4F 1C MOV ECX,DWORD PTR DS:[EDI+0x1C] ; ECX=(k[0]^k[6]^0x18+0x3D)^0xA7
013BDCA8 83C4 08 ADD ESP,0x8
013BDCAB 0FB7C0 MOVZX EAX,AX ; eax=eax&0xFFFF
013BDCAE 8947 20 MOV DWORD PTR DS:[EDI+0x20],EAX
013BDCB1 85C9 TEST ECX,ECX
013BDCB3 0F84 BC010>JE 010Edito.013BDE75 ; ecx!=0第一個call的返回值不能等於0
013BDCB9 85C0 TEST EAX,EAX
013BDCBB 0F84 B4010>JE 010Edito.013BDE75 ; eax!=0
013BDCC1 3D E803000>CMP EAX,0x3E8
013BDCC6 0F87 A9010>JA 010Edito.013BDE75 ; eax<=0x3e8
驗證用戶名
013BDD8B 8D45 EC LEA EAX,DWORD PTR SS:[EBP-0x14]
013BDD8E 50 PUSH EAX
013BDD8F 8D4F 04 LEA ECX,DWORD PTR DS:[EDI+0x4]
013BDD92 FF15 782BE>CALL DWORD PTR DS:[<&Qt5Core.?toUtf8@QStr>; 返回用戶名ASCII
013BDD98 FF77 20 PUSH DWORD PTR DS:[EDI+0x20] ; sub_4083C8的返回值
013BDD9B 33C0 XOR EAX,EAX
013BDD9D C745 FC 00>MOV DWORD PTR SS:[EBP-0x4],0x0
013BDDA4 80FB FC CMP BL,0xFC
013BDDA7 8D4D EC LEA ECX,DWORD PTR SS:[EBP-0x14] ; 用戶名
013BDDAA 56 PUSH ESI ; 0
013BDDAB 0F95C0 SETNE AL
013BDDAE 50 PUSH EAX ; 1
013BDDAF FF15 8C24E>CALL DWORD PTR DS:[<&Qt5Core.?data@QByteA>; Qt5Core.?data@QByteArray@@QAEPADXZ
013BDDB5 50 PUSH EAX ; 返回用戶名字符串
013BDDB6 E8 955004F>CALL 010Edito.00402E50 ; 用戶名轉一個加密值
013BDDBB 8BD0 MOV EDX,EAX
013BDDBD 83C4 10 ADD ESP,0x10
013BDDC0 3855 E0 CMP BYTE PTR SS:[EBP-0x20],DL ; CMP k[4] Ret&0xFF
013BDDC3 0F85 81000>JNZ 010Edito.013BDE4A ; 說明K[4]=Ret&0xFF
013BDDC9 8BCA MOV ECX,EDX
013BDDCB C1E9 08 SHR ECX,0x8 ; ecx=Ret>>8
013BDDCE 3AF9 CMP BH,CL ; k[5]
013BDDD0 75 78 JNZ SHORT 010Edito.013BDE4A ; 說明k[5]=(Ret>>8)&0xFF
013BDDD2 8BCA MOV ECX,EDX
013BDDD4 C1E9 10 SHR ECX,0x10
013BDDD7 384D E2 CMP BYTE PTR SS:[EBP-0x1E],CL
013BDDDA 75 6E JNZ SHORT 010Edito.013BDE4A ; 說明k[6]=(Ret>>0x10)%0xFF
013BDDDC C1E8 18 SHR EAX,0x18 ; ret>>0x18
013BDDDF 3845 E3 CMP BYTE PTR SS:[EBP-0x1D],AL
013BDDE2 75 66 JNZ SHORT 010Edito.013BDE4A ; k[7]=(Ret>>0x18)&0xFF
013BDDE4 80FB 9C CMP BL,0x9C
013BDDE7 75 0F JNZ SHORT 010Edito.013BDDF8 ; k[4]=0x9C
013BDDE9 8B45 08 MOV EAX,DWORD PTR SS:[EBP+0x8] ; 第一個參數0x9
013BDDEC 3B47 1C CMP EAX,DWORD PTR DS:[EDI+0x1C] ; [edi+1c]=sub_407644函數返回值
;到這裏,滿足條件後返回值就是0x2D了,算法分析到這也就算完了
013BDDEF 76 52 JBE SHORT 010Edito.013BDE43 ;滿足條件跳轉,eax=0x2D返回
分析完代碼你會發現算法最開始有一個比較k[3] (序列號第四個)與0x9C 0xFC 0xAC,不滿足直接就返回E7,這裏就可以猜測
k[3] =0x9C 0xFC 0xAC 任意一個都可以,也就是說他可能有三種不同的算法,下面我們先分析第一種k[3]=0x9C的情況
一路分析下來,做一個總結:
算法:兩個關鍵的序列號計算函數
用戶名:通過分析我們可以發現sub_00402E50 函數是把用戶名做了個加密,進去看了一下,也是做一些計算,我們用IDA看一下.
通過IDAF5
後分析用戶名是通過一個數組進行加密轉換的
用OD把數組拷貝出來,爲後面的編寫註冊機做準備
最後,用戶名加密值與k4 k5 k6 k7有對應關係,都滿足後判斷第一個參數與sub_407644函數返回值比較,返回值大於等於第一個參數就成功了.
至此算法一就分析完成了
代碼驗證
思路:
- 0x9C系列序列號是八位
- 根據用戶名的加密值可以得到 k4 k5 k6 k7
- 第一個序列號計算函數:參數相關k0 k6 可以枚舉出符合條件的k0
- 第二個序列號計算函數:參數相關k1 k2 k5 k7 可以枚舉出符合條件的k1 k2
VOID CalPassword::CrackKey1(CHAR* pUserName,CString& Password,DWORD arg)
{
srand(time(NULL));
BYTE Key[10] = { 0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xAA };
BYTE k0 = 0;
BYTE k1 = 0;
BYTE k2 = 0;
BYTE k3 = 0;
BYTE k4 = 0;
BYTE k5 = 0;
BYTE k6 = 0;
BYTE k7 = 0;
BYTE k8 = 0;
BYTE k9 = 0;
//Key[3] = 0x9C 0xAC 0xFC
//0x9C是8位序列號
Key[3] = 0x9C;
//計算用戶名加密值與k4 k5 k6 k7有對應關係
//k4 k5 k6 k7 逆用戶名加密值
//說明K[4]=Ret&0xFF
//說明k[5]=(Ret>>8)&0xFF
//說明k[6]=(Ret>>0x10)%0xFF
//說明k[7]=(Ret>>0x18)&0xFF
DWORD dwRetValue = rand() % 0x3e8 + 1; //指定商的範圍 0-0x3e8
DWORD dwValue = sub_EncodeName(pUserName, 1, 0, dwRetValue);
Key[4] = dwValue & 0xFF;
Key[5] = (dwValue >> 8) & 0xFF;
Key[6] = (dwValue >> 16) & 0xFF;
Key[7] = (dwValue >> 24) & 0xFF;
//計算k0和k6
while (TRUE)
{
k0 = rand() % 0xFF;
BYTE AL = ((k0 ^ Key[6] ^ 0x18) + 0x3D) ^ 0xA7;
if (AL >= arg)
{
Key[0] = k0;
//Key[6] = k6;
break;
}
}
while (TRUE)
{
k1 = rand() % 0xFF;
k2 = rand() % 0xFF;
//esi=((K[2]^k[5]&0xFF)+(K[1]^k[7]&0xFF)*0x100)&0xFFFF
//EAX=((esi^0x7892+0x4D30)^0x3421&0xFFFF)/0xB
//判斷餘數是否爲0,爲0返回商,不爲0返回0
//商<=0x3e8
DWORD dwTemp1 = (k2 ^ Key[5]) & 0xFF;
DWORD dwTemp2 = ((k1 ^ Key[7]) & 0xFF) * 0x100;
DWORD dwESI = (dwTemp1 + dwTemp2) & 0xFFFF;
DWORD dwEAX = ((((dwESI ^ 0x7892) + 0x4D30) ^ 0x3421) & 0xFFFF);
if (dwEAX % 0xB == 0 && dwEAX/0xB == dwRetValue)
{
Key[1] = k1;
Key[2] = k2;
break;
}
}
Password.Format(L"%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X", Key[0], Key[1],
Key[2], Key[3], Key[4], Key[5], Key[6], Key[7], Key[8], Key[9]);
}
sub_EncodeName直接從IDA拷貝出來的,數組也是拷貝的
int __cdecl CalPassword::sub_EncodeName(const char *pUserName, int a2, char a3, int a4)
{
const char *v4; // edx
signed int v5; // esi
signed int v6; // edi
unsigned __int8 v7; // bl
int v8; // eax
int v9; // ecx
int v10; // ecx
int result; // eax
unsigned __int8 v12; // [esp+8h] [ebp-10h]
unsigned __int8 v13; // [esp+Ch] [ebp-Ch]
unsigned __int8 v14; // [esp+10h] [ebp-8h]
int v15; // [esp+14h] [ebp-4h]
v4 = pUserName;
v15 = 0;
v5 = strlen(pUserName);
v6 = 0;
if (v5 <= 0)
return 0;
v12 = 0;
v13 = 0;
v7 = 15 * a4;
v14 = 17 * a3;
do
{
v8 = toupper((unsigned __int8)v4[v6]);
v9 = v15 + m_EncodeArray[v8];
if (a2)
v10 = m_EncodeArray[v13]
+ m_EncodeArray[v7]
+ m_EncodeArray[v14]
+ m_EncodeArray[(unsigned __int8)(v8 + 47)] * (m_EncodeArray[(unsigned __int8)(v8 + 13)] ^ v9);
else
v10 = m_EncodeArray[v12]
+ m_EncodeArray[v7]
+ m_EncodeArray[v14]
+ m_EncodeArray[(unsigned __int8)(v8 + 23)] * (m_EncodeArray[(unsigned __int8)(v8 + 63)] ^ v9);
result = v10;
v15 = v10;
v13 += 19;
++v6;
v14 += 9;
v7 += 13;
v12 += 7;
v4 = pUserName;
} while (v6 < v5);
return result;
}