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;
}