010Editor 逆向分析
用于测试的用户名为
foobar,序列号为
1112-2233-3444-5556-6677
1. 暴力破解分析
思路:
- 找到注册窗口
- 测试注册窗口反应
- 根据反应做出下一步分析(猜测api,敏感字符串)
- 动态分析关键跳转、call
1.1 API下断
注册失败会弹框,OD中搜索当前模块中的名称(字符串),查看导入的API,会发现这是个QT程序(不熟悉,GG)。
也可以用IDA。因为0xeditor.exe本身有45MB,所以分析完后的数据库会很大。
因为底层肯定还是调用了API,所以在OD的E窗口中找到user32.dll(和窗口有关),QT底层实际上调用了CreaetWindow。断下后栈回溯分析,用户领空调用来自010editor.01CE69BB。

但是点进去后没有发现什么信息,所以继续看上一层调用,010editor.011B8912

发现了关键字符串,所以栈回溯找到这个函数的开头。试着找一下关键跳转。

有一个QT的判断字符串为空的函数,猜测是用户名或序列号。经过分析,isEmpty的上一个函数,<&Qt5Widgets.?text@QLineEdit@@QB>获取了用户名,并且是unicode编码。

01435675 8B4E 74 MOV ECX,DWORD PTR DS:[ESI+0x74]
01435678 8D45 D8 LEA EAX,DWORD PTR SS:[EBP-0x28] ; 序列号
0143567B 50 PUSH EAX
0143567C FFD7 CALL EDI ; 获取序列号
继续跟进,发现再次调用这个函数,获取序列号。

继续往下跟进,就会找到关键跳转。
有两个验证函数,我加了标签,check1, check2
014357F3 MOV ECX,DWORD PTR DS:[0x3E10F20]
014357F9 PUSH 0x4389
014357FE PUSH 0x9
01435800 CALL <010Edito.check1> ;第一次验证
01435805 MOV ECX,DWORD PTR DS:[0x34D0F20]
0143580B MOV EBX,EAX ; ebx = eax
0143580D PUSH 0x4389
01435812 PUSH 0x9
01435814 CALL <010Edito.check2> ;第二次验证
01435819 MOV ECX,DWORD PTR DS:[0x34D0F20]
0143581F MOV EDI,EAX ; edi = eax( 0xD8 is right)
01435821 CMP EBX,0xE7
01435827 JE 010Edito.01435920 ; if(ebx == 0xE7) jmp
0143582D CMP DWORD PTR DS:[ECX+0x2C],0x0
01435831 JE 010Edito.01435920
;...
01435920 CMP EDI,0xDB ; if(edi!=0xdb) fail
01435926 JNZ 010Edito.01435A58 ; 关键跳转!!!!
;...
01435A84 PUSH 010Edito.0297D5FC ; ASCII "Password accepted. Your trial period has been extended."
01435A89 CALL DWORD PTR DS:[<&Qt5Core.??0QString@@QAE@PBD@Z>] ; Qt5Core.??0QString@@QAE@PBD@Z
如果序列号错误在0x01435926处会跳转,弹出无效序列号对话框。如果把它改为nop,就可以验证通过。

但这样更改后dump,每次运行还是需要验证注册,并没有完美破解。
如果让验证始终正确,也就是edi == 0xDB,或者说check2返回0xDB,就可以完美破解了。
分析一下check2:
01CFE4E0 PUSH EBP
01CFE4E1 MOV EBP,ESP
01CFE4E3 PUSH ESI
01CFE4E4 MOV ESI,ECX
;...
01A1E4FE CALL <010Edito.check1>
01A1E503 CMP EAX,0x2D
01A1E506 JE 010Edito.01A1E5AF
;...
01A1E5AF POP EDI
01A1E5B0 MOV EAX,0xDB ;返回0xDB
01A1E5B5 POP ESI
01A1E5B6 POP EBP
01A1E5B7 RETN 0x8
check2再次调用check1,如果返回0x2D,check2就会返回0xDB.
直接修改check2开头:

也可以修改check1返回0x2D,但我们看check1开头:

地址处有下划线,如果修改的话需要关闭随机基址。而且,check1返回值会影响外部主验证流程,所以最终还是修改了check2返回值。

1.2 字符串
弹出无效窗口后,点击窗口,ctrl+c复制到记事本,OD搜索所有参考文本字符串。找到关键字符串,后面步骤同上。
2. check1算法分析
014357F3 8B0D 200FE103 MOV ECX,DWORD PTR DS:[0x3E10F20]
014357F9 68 89430000 PUSH 0x4389
014357FE 6A 09 PUSH 0x9
01435800 CALL <010Edito.check1> ;第一次验证,check1(this, 0x4389, 0x9)
check1是关键函数,我们应该分析一下,如何让它返回0x2D。
因为有ecx传参,所以应该也是个c++对象的函数,另外还有两个常量参数。这个函数很长,跟踪时应该关注数据(用户名和序列号)的访问和修改。
先分析一下this对象。
this:
002CAA68 699EB740 OFFSET Qt5Core.?shared_null@QArrayData@@2QBU1@B
002CAA6C 07732198
002CAA70 0774ACA0
002CAA74 0771B8B8
[this]暂时不知道用途。
[this+4]`中保存了4个数字和用户名:
07732198 02 00 00 00 06 00 00 00 0D 00 00 00 10 00 00 00 .....
077321A8 66 00 6F 00 6F 00 62 00 61 00 72 00 00 00 00 foobar..
[this+8]中保存了4个数字和输入的序列号:
0774ACA0 02 00 00 00 18 00 00 00 38 00 00 00 10 00 00 00 ..8..
0774ACB0 31 00 31 00 31 00 32 00 2D 00 32 00 32 00 33 00 1112-223
0774ACC0 33 00 2D 00 33 00 34 00 34 00 34 00 2D 00 35 00 3-3444-5
0774ACD0 35 00 35 00 36 00 2D 00 36 00 36 00 37 00 37 00 556-6677
而且用户名和序列号都是unicode编码。
然后进入check1,分析验证流程。
0235DBE8 8BF9 MOV EDI,ECX ; edi = this
0235DBEA 8D5F 04 LEA EBX,DWORD PTR DS:[EDI+0x4] ; ebx = this+4 = stcName
0235DBED C745 F0 0000000>MOV DWORD PTR SS:[EBP-0x10],0x0
0235DBF4 8BCB MOV ECX,EBX ; ecx = this+4 = stcName
0235DBF6 C747 30 0000000>MOV DWORD PTR DS:[EDI+0x30],0x0
0235DBFD C747 18 0000000>MOV DWORD PTR DS:[EDI+0x18],0x0
0235DC04 C747 34 0000000>MOV DWORD PTR DS:[EDI+0x34],0x0
0235DC0B FF15 B424E103 CALL DWORD PTR DS:[<&Qt5Core.?isEmpty@QS>; Qt5Core.?isEmpty@QString@@QBE_NXZ
0235DC11 84C0 TEST AL,AL
0235DC13 0F85 75020000 JNZ 010Edito.0235DE8E
0235DC19 8D4F 08 LEA ECX,DWORD PTR DS:[EDI+0x8] ; ecx = 序列号
0235DC1C FF15 B424E103 CALL DWORD PTR DS:[<&Qt5Core.?isEmpty@QS>; Qt5Core.?isEmpty@QString@@QBE_NXZ
0235DC22 84C0 TEST AL,AL
0235DC24 0F85 64020000 JNZ 010Edito.0235DE8E
以上一段校验字符串是否为空的代码。继续往下分析。
0235DC2A 8D45 DC LEA EAX,DWORD PTR SS:[EBP-0x24]
0235DC2D 8BCF MOV ECX,EDI ; ecx = this
0235DC2F 50 PUSH EAX ; 传出参数
0235DC30 E8 3ABF04FF CALL 010Edito.013A9B6F
某函数传入了一个局部变量地址ebp-0x24,调用前后观察一下这个变量:
0018A230 05658AD8
0018A234 0018A258
0018A238 697CC033 返回到 Qt5Core.697CC033 来自 msvcr120.free
调用后:
0018A230 11 12 22 33 34 44 55 56
0018A238 66 77 7C 69 D8 8A 65 05
也就是说,这个函数把输入的序列号字符串转换成了十六进制数。
这时我意识到,序列号应该用
0011-2233-4455-6677-8899,这样后面的下标定位可能会方便些。
022FDC40 PUSH DWORD PTR DS:[ESI] ; ascii "999"
022FDC42 MOV ECX,EBX ; ecx = this+4 = name
022FDC44 CALL DWORD PTR DS:[<&Qt5Core.??8QString@>; Qt5Core.??8QString@@QBE_NPBD@Z
022FDC4A TEST AL,AL
022FDC4C JNZ 010Edito.022FDE75
022FDC52 ADD ESI,0x4
022FDC55 CMP ESI,010Edito.03DA4618
022FDC5B ^ JL SHORT 010Edito.022FDC40 ; while(esi < xxx )
接下来的这个函数,通过分析只发现了“999”字符串,暂时跳过。下面会遇到关键的3个函数,我给它们加了标签,calc1, calc2, calc3.
第1个call
022FDC5D MOV BL,BYTE PTR SS:[EBP-0x21] ; bl = serial[3]
022FDC60 MOV BH,BYTE PTR SS:[EBP-0x1F] ; bh = serial[5]
022FDC63 CMP BL,0x9C ; if(serial[3]!=[0x9C,0xFC,0xAC]) return 0xE7
022FDC66 JNZ SHORT 010Edito.022FDCD8
022FDC68 MOV AL,BYTE PTR SS:[EBP-0x24]
022FDC6B XOR AL,BYTE PTR SS:[EBP-0x1E] ; al = serial[0] ^ serial[6]
022FDC6E MOV BYTE PTR SS:[EBP-0x18],AL ; [ebp-0x18] = serial[0] ^ serial[6]
022FDC71 MOV AL,BYTE PTR SS:[EBP-0x23]
022FDC74 XOR AL,BYTE PTR SS:[EBP-0x1D] ; al = serial[1] ^ serial[7]
022FDC77 PUSH DWORD PTR SS:[EBP-0x18] ; ARG: serial[0] ^ serial[6]
022FDC7A MOVZX ECX,AL
022FDC7D MOV EAX,0x100
022FDC82 IMUL CX,AX ; ecx = (serial[1] ^ serial[7]) * 0x100
022FDC86 MOV AL,BYTE PTR SS:[EBP-0x22]
022FDC89 XOR AL,BH ; al = serial[2] ^ serial[5]
022FDC8B MOVZX EAX,AL
022FDC8E ADD CX,AX ; cl = serial[2] ^ serial[5]
022FDC91 MOVZX ESI,CX ; esi = ecx & 0xFFFF
022CDC94 CALL <010Edito.calc1>
上面流程做了以下内容:
if(serial[3]!=[0x9C,0xFC,0xAC]) return 0xE7ecx = (serial[1] ^ serial[7]) * 0x100 + serial[2] ^ serial[5], ecx &= 0xFFFF010Edito.01347644( serial[0]^serial[6] )
这个call经过jmp之后,执行下面的逻辑。
022FD0B0 PUSH EBP
022FD0B1 MOV EBP,ESP
022FD0B3 MOV EAX,DWORD PTR SS:[EBP+0x8] ; serial[0] ^ serial[6]
022FD0B6 XOR AL,0x18
022FD0B8 ADD AL,0x3D
022FD0BA XOR AL,0xA7 ; al = ((al ^ 0x18) + 0x3D) ^ 0xA7
022FD0BC POP EBP
022FD0BD RETN
是一段简单的计算返回新的数字。
第2个call
022FDC99 0FB6C0 MOVZX EAX,AL ; eax = eax & 0xFF
022FDC9C 56 PUSH ESI ; ARG:esi = ecx & 0xFFFF
022FDC9D 8947 1C MOV DWORD PTR DS:[EDI+0x1C],EAX ; [this+0x1c] = eax
022FDCA0 E8 23A704FF CALL <010Edito.calc2>
022FDCA5 8B4F 1C MOV ECX,DWORD PTR DS:[EDI+0x1C] ; ecx = [this+0x1c]
022FDCA8 83C4 08 ADD ESP,0x8
022FDCAB 0FB7C0 MOVZX EAX,AX ; eax = eax & 0xFFFF
022FDCAE 8947 20 MOV DWORD PTR DS:[EDI+0x20],EAX ; [this+0x20] = eax
第一个call之后,结果会保存在对象的偏移0x1c处。calc2的结果也会保存在对象偏移0x20处。
紧接着会对之前得到的esi,也就是ecx进行计算。这个call经过jmp进入下面的流程。
022FD020 PUSH EBP
022FD021 MOV EBP,ESP
022FD023 MOV EAX,DWORD PTR SS:[EBP+0x8]
022FD026 MOV ECX,0xB ; ecx = 0xb
022FD02B XOR EAX,0x7892
022FD030 ADD EAX,0x4D30
022FD035 XOR EAX,0x3421
022FD03A MOVZX EAX,AX ; ax = ((ARG ^ 0x7892) + 0x4D30) ^ 0x3421
022FD03D CDQ
022FD03E IDIV ECX ; edx = eax % 0xB, eax = eax / 0xB
022FD040 TEST EDX,EDX
022FD042 JE SHORT 010Edito.022FD046 ; if(eax % 0xB == 0) return eax / 0xB
022FD044 XOR EAX,EAX ; else return 0
022FD046 POP EBP
022FD047 RETN
这个函数内部计算出一个ax,然后除以11,如果整除就返回商,不整除就返回0.
022FDCA5 MOV ECX,DWORD PTR DS:[EDI+0x1C] ; ecx = [this+0x1c]
022FDCA8 ADD ESP,0x8
022FDCAB MOVZX EAX,AX ; eax = eax & 0xFFFF
022FDCAE MOV DWORD PTR DS:[EDI+0x20],EAX ; [this+0x20] = eax
022FDCB1 TEST ECX,ECX
022FDCB3 JE 010Edito.022FDE75
022FDCB9 TEST EAX,EAX
022FDCBB JE 010Edito.022FDE75
022FDCC1 CMP EAX,0x3E8
022FDCC6 JA 010Edito.022FDE75 ; if(ecx==0 || eax==0 || eax>0x3E8) return 0xE7
022FDCCC CMP ECX,0x2
022FDCCF SBB ESI,ESI
022FDCD1 AND ESI,ECX
022FDCD3 JMP 010Edito.022FDD8B ;跳转
这里要注意,第2个call要返回商才行,而且这个商不能大于0x3E8。到这里,第二个call的流程已经可以实现了。
while (1)
{
BYTE k1 = 0, k7 = 0, k2 = 0, k5 = 0;
k1 = rand() % 0xFF;
k7 = rand() % 0xFF;
k2 = rand() % 0xFF;
k5 = rand() % 0xFF;
dwArg1 = ((k1 ^ k7 & 0xFF) * 0x100 + k2 ^ k5 & 0xFF) & 0xFFFF;
dwRet1 = (((dwArg1 ^ 0x7892) + 0x4D30) ^ 0x3421) & 0xFFFF;
if (dwRet1 % 0xB == 0
&& dwRet1 / 0xB <= 0x3E8)
{
dwRet1 /= 0xB;
serial[1] = k1;
serial[7] = k7;
serial[2] = k2;
serial[5] = k5;
break;
}
}
/*
eg. 110ff89c345f55976677
check1 返回0x27664033
*/
第3个call
0225DD8B LEA EAX,DWORD PTR SS:[EBP-0x14]
0225DD8E PUSH EAX ; 传出参数,调用后得到utf8 stcName(\0\0结尾)
0225DD8F LEA ECX,DWORD PTR DS:[EDI+0x4] ; &this->stcName
022CDD92 CALL DWORD PTR DS:[<&Qt5Core.?toUtf8]
跳转后,程序将用户名转为了utf8编码,在这里其实就是以两个0结尾的ascii字符串,地址保存在栈里。

这个结构体和对象中的结构体一致,其中有个0x10代表字符串相对结构体的偏移。
0225DD98 PUSH DWORD PTR DS:[EDI+0x20]
0225DD9B XOR EAX,EAX
0225DD9D MOV DWORD PTR SS:[EBP-0x4],0x0
0225DDA4 CMP BL,0xFC
0225DDA7 LEA ECX,DWORD PTR SS:[EBP-0x14] ; ecx = &stcName utf8
0225DDAA PUSH ESI
0225DDAB SETNE AL ; if(bl = serial[3] != 0xFC) al = 1
0225DDAE PUSH EAX
0225DDAF CALL DWORD PTR DS:[<&Qt5Core.?data@QByteArray@@QAEPADXZ>>; Qt5Core.?data@QByteArray@@QAEPADXZ
0225DDB5 PUSH EAX ; szName utf8
0225DDB6 CALL <010Edito.calc3> ; calc(szName, 1, 0, [this+0x20])!!!!!!!!!!!!!!!
经过分析,calc3有4个参数:用户名, 1, 0, calc2结果。 之后要想让check1返回0x2D,就需要[this+0x1c],也就是calc1计算出来的结果大于等于arg0 = 9。 它应该是结合用户名和calc2计算出一个验证码。
分析一下calc3.
0225D120 PUSH EBP
0225D121 MOV EBP,ESP
0225D123 SUB ESP,0x10
0225D126 MOV EDX,DWORD PTR SS:[EBP+0x8] ; edx = utf8 szName
0225D129 XOR ECX,ECX
0225D12B PUSH ESI
0225D12C MOV ESI,EDX ; esi = urf8 szName
0225D12E MOV DWORD PTR SS:[EBP-0x4],ECX ; unset
0225D131 PUSH EDI ; this
0225D132 LEA EDI,DWORD PTR DS:[ESI+0x1] ; edi = esi+1
0225D135 MOV AL,BYTE PTR DS:[ESI] ; do{ al = szName[esi]
0225D137 INC ESI ; ++esi
0225D138 TEST AL,AL ; }
0225D13A ^ JNZ SHORT 010Edito.0225D135 ; while(al != 0) ;
0225D13C SUB ESI,EDI ; esi = len(name)
0225D13E XOR EDI,EDI ; edi = 0
0225D140 TEST ESI,ESI
0225D142 JLE 010Edito.0225D238 ; if(len(name) <= 0) return 0
0225D148 PUSH EBX
0225D149 MOV EBX,DWORD PTR SS:[EBP+0x14]
0225D14C MOV DWORD PTR SS:[EBP-0x10],ECX ; unset
0225D14F MOV DWORD PTR SS:[EBP-0xC],ECX ; unset
0225D152 MOV ECX,DWORD PTR SS:[EBP+0x10] ; ecx = 0
0225D155 SHL EBX,0x4
0225D158 SUB EBX,DWORD PTR SS:[EBP+0x14] ;ebx = (calc2<<0x4) - calc2
0225D15B SHL ECX,0x4
0225D15E ADD ECX,DWORD PTR SS:[EBP+0x10]
0225D161 MOV DWORD PTR SS:[EBP-0x8],ECX ; unset
0225D164 MOVZX EAX,BYTE PTR DS:[EDI+EDX] ; do{ eax = name[edi]
0225D168 PUSH EAX
0225D169 CALL DWORD PTR DS:[<&MSVCR120.toupper>] ; msvcr120.toupper
0225D16F MOV EDX,EAX ; edx = eax = upper(name[0])
0225D171 ADD ESP,0x4
0225D174 MOV ECX,DWORD PTR DS:[EDX*4+0x3D04148]
0225D17B ADD ECX,DWORD PTR SS:[EBP-0x4] ; ecx = g_array[4 * upper(name[0]) ] + [ebp-4]
0225D17E CMP DWORD PTR SS:[EBP+0xC],0x0
0225D182 JE SHORT 010Edito.0225D1CE ; if(arg1 == 0) jmp
0225D184 LEA EAX,DWORD PTR DS:[EDX+0xD] ; if(arg1 != 0){
0225D187 AND EAX,0xFF ; eax = ( upper(name[0]) + 0xD ) & 0xFF
0225D18C XOR ECX,DWORD PTR DS:[EAX*4+0x3D04148] ; ecx ^= g_array[eax * 4]
0225D193 LEA EAX,DWORD PTR DS:[EDX+0x2F]
0225D196 AND EAX,0xFF ; eax = ( upper(name[0]) + 0x2F ) & 0xFF
0225D19B IMUL ECX,DWORD PTR DS:[EAX*4+0x3D04148] ; ecx *= g_array[eax * 4]
0225D1A3 MOV EAX,DWORD PTR SS:[EBP-0x8]
0225D1A6 MOVZX EAX,AL ; eax = [ebp-8] & 0xFF
0225D1A9 ADD ECX,DWORD PTR DS:[EAX*4+0x3D04148] ; ecx += g_array[eax * 4]
0225D1B0 MOVZX EAX,BL ; eax = ebx & 0xFF
0225D1B3 ADD ECX,DWORD PTR DS:[EAX*4+0x3D04148] ; ecx += g_array[eax * 4]
0225D1BA MOV EAX,DWORD PTR SS:[EBP-0xC]
0225D1BD MOVZX EAX,AL ; eax = [ebp-0xc] & 0xFF
0225D1C0 ADD ECX,DWORD PTR DS:[EAX*4+0x3D04148] ; ecx += g_array[eax * 4]
0225D1C7 MOV EAX,ECX ; eax = ecx
0225D1C9 MOV DWORD PTR SS:[EBP-0x4],EAX ; [ebp-4] = eax
0225D1CC JMP SHORT 010Edito.0225D216 ; }
0225D1CE LEA EAX,DWORD PTR DS:[EDX+0x3F] ; else{
0225D1D1 AND EAX,0xFF ; eax = (upper(name[edi) + 0x3F) & 0xFF
0225D1D6 XOR ECX,DWORD PTR DS:[EAX*4+0x3D04148] ; ecx ^= g_array[eax * 4]
0225D1DD LEA EAX,DWORD PTR DS:[EDX+0x17]
0225D1E0 AND EAX,0xFF ; eax = (upper(name[edi) + 0x17) & 0xFF
0225D1E5 IMUL ECX,DWORD PTR DS:[EAX*4+0x3D04148] ; ecx *= g_array[eax * 4]
0225D1ED MOV EAX,DWORD PTR SS:[EBP-0x8]
0225D1F0 MOVZX EAX,AL ; eax = [EBP-0x8] & 0xFF
0225D1F3 ADD ECX,DWORD PTR DS:[EAX*4+0x3D04148] ; ecx += g_array[eax * 4]
0225D1FA MOVZX EAX,BL ; eax = bl & 0xFF
0225D1FD ADD ECX,DWORD PTR DS:[EAX*4+0x3D04148] ; ecx += g_array[eax * 4]
0225D204 MOV EAX,DWORD PTR SS:[EBP-0x10]
0225D207 MOVZX EAX,AL ; eax = [EBP-0x10] & 0xFF
0225D20A ADD ECX,DWORD PTR DS:[EAX*4+0x3D04148] ; ecx += g_array[eax * 4]
0225D211 MOV EAX,ECX ; eax = ecx
0225D213 MOV DWORD PTR SS:[EBP-0x4],ECX ; [ebp-4] = ecx }
0225D216 ADD DWORD PTR SS:[EBP-0xC],0x13 ; [ebp-c] += 0x13
0225D21A INC EDI ; ++edi
0225D21B ADD DWORD PTR SS:[EBP-0x8],0x9 ; [ebp-8] += 9
0225D21F ADD EBX,0xD ; ebx += 0xD
0225D222 ADD DWORD PTR SS:[EBP-0x10],0x7 ; [ebp-0x10] += 7
0225D226 MOV EDX,DWORD PTR SS:[EBP+0x8] ; edx = [ebp+8]
0225D229 CMP EDI,ESI ; }
0225D22B ^ JL 010Edito.0225D164 ; while(edi < len(name))
0225D231 POP EBX
0225D232 POP EDI
0225D233 POP ESI
0225D234 MOV ESP,EBP
0225D236 POP EBP
0225D237 RETN
0225D238 POP EDI
0225D239 MOV EAX,ECX
0225D23B POP ESI
0225D23C MOV ESP,EBP
0225D23E POP EBP
0225D23F RETN
这个函数在计算时用到了一个全局数组,实现注册机就需要在OD中数据窗口把数组以DWORD的形式拷贝下来。
代码验证:
DWORD calc3(char* pName, DWORD dwArg1 /*=1*/, DWORD dwArg2 /*=0*/, DWORD dwCalc2)
{
int nLenName = strlen(pName);
DWORD ebx = (dwCalc2 << 0x4) - dwCalc2;
DWORD eax = 0, ecx = 0, edx;
DWORD loc_4 = 0, loc_8 = 0, loc_c = 0, loc_10 = 0;
for (int i = 0; i < nLenName; ++i)
{
edx = upper(pName[i]);
ecx = g_array[edx] + loc_4;
if (dwArg1 != 0)
{
eax = (edx + 0xD) & 0xFF;
ecx ^= g_array[eax];
eax = (edx + 0x2F) & 0xFF;
ecx *= g_array[eax];
eax = loc_8 & 0xFF;
ecx += g_array[eax];
eax = ebx & 0xFF;
ecx += g_array[eax];
eax = loc_c & 0xFF;
ecx += g_array[eax];
eax = ecx;
loc_4 = eax;
}
else
{
eax = (edx + 0x3F) & 0xFF;
ecx ^= g_array[eax];
eax = (edx + 0x17) & 0xFF;
ecx *= g_array[eax];
eax = loc_8 & 0xFF;
ecx += g_array[eax];
eax = ebx & 0xFF;
ecx += g_array[eax];
eax = loc_10 & 0xFF;
ecx += g_array[eax];
eax = ecx;
loc_4 = eax;
}
//printf(" eax: 0x%X\n", eax);
loc_c += 0x13;
loc_8 += 0x9;
ebx += 0xD;
loc_10 += 0x7;
}
return eax;
}

至此,3个计算函数都已经实现了。
修改第二个call
之后,还有对于这个验证码的验证过程。
0225DDBB MOV EDX,EAX ; edx = calc3
0225DDBD ADD ESP,0x10
0225DDC0 CMP BYTE PTR SS:[EBP-0x20],DL
0225DDC3 JNZ 010Edito.0225DE4A ; if( serial[4] != dl)
0225DDC9 MOV ECX,EDX
0225DDCB SHR ECX,0x8
0225DDCE CMP BH,CL
0225DDD0 JNZ SHORT 010Edito.0225DE4A ; || bh != cl (serial[5] != (edx>>8) & 0xFF)
0225DDD2 MOV ECX,EDX
0225DDD4 SHR ECX,0x10
0225DDD7 CMP BYTE PTR SS:[EBP-0x1E],CL ; || serial[6] != (edx>>0x10) & 0xFF
0225DDDA JNZ SHORT 010Edito.0225DE4A
0225DDDC SHR EAX,0x18
0225DDDF CMP BYTE PTR SS:[EBP-0x1D],AL
0225DDE2 JNZ SHORT 010Edito.0225DE4A ; || serial[7] != (eax>>0x18) & 0xFF
0225DDE4 CMP BL,0x9C
0225DDE7 JNZ SHORT 010Edito.0225DDF8 ; || (serial[3] != [0x9C, 0xFC, 0xAC]) return 0xE7
0225DDE9 MOV EAX,DWORD PTR SS:[EBP+0x8]
0225DDEC CMP EAX,DWORD PTR DS:[EDI+0x1C]
0225DDEF JBE SHORT 010Edito.0225DE43 ; else if(arg0 <= [this+0x1C]) return 0x2D!!!!!!!!!!!!!!!
0225DDF1 MOV ESI,0x4E
0225DDF6 JMP SHORT 010Edito.0225DE4F ; return 0x4E
需要满足以下几点:
serial[4] == calc3 & 0xFFserial[5] == (calc3>>8) & 0xFFserial[6] == (calc3>>0x10) & 0xFFserial[7] == (calc3>>0x18) & 0xFFserial[3] == 0x9C || 0xFC || 0xACcalc1 >= 9
所以上面的代码,尤其是calc2生成随机序列号字节的部分,还需要满足这几个条件,而且因为calc1, calc3都涉及serial[6],calc2,calc3都涉及serial[5], serial[7],所以calc3中需要重新生成这几个序列号字节。
DWORD Obj::calc2(DWORD dwArg)
{
DWORD eax = 0;
BYTE k1 = 0, k7 = 0, k2 = 0, k5 = 0;
srand(time(NULL));
while (1)
{
k1 = rand() % 0xFF;
k7 = rand() % 0xFF;
k2 = rand() % 0xFF;
k5 = rand() % 0xFF;
dwArg = ((k1 ^ k7 & 0xFF) * 0x100 + k2 ^ k5 & 0xFF) & 0xFFFF;
eax = (((dwArg ^ 0x7892) + 0x4D30) ^ 0x3421) & 0xFFFF;
if (eax % 0xB == 0
&& eax / 0xB <= 0x3E8)
{
eax /= 0xB;
dwCalc2 = eax;
dwCalc3 = calc3(name, 1, 0, dwCalc2);
/***********************************
* serial[5],serial[7]与calc3冲突
*************************************/
if (
((dwCalc3 >> 0x18) & 0xFF) != k7
|| ((dwCalc3 >> 0x8) & 0xFF) != k5)
{
continue;
}
serial[1] = k1;
serial[7] = k7;
serial[2] = k2;
serial[5] = k5;
serial[4] = dwCalc3 & 0xFF;
serial[5] = (dwCalc3 >> 0x8) & 0xFF;
serial[6] = (dwCalc3 >> 0x10) & 0xFF;
/********************************
* serial[6],calc3与calc1冲突
*********************************/
while (1)
{
dwCalc1 = calc1(serial[0] ^ serial[6]);
if (dwCalc1 >= 9)
{
break;
}
serial[0] = rand() % 0xFF;
}
break;
}
}
return eax;
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-32uMnm7q-1573782008059)(010Editor逆向.assets/1573734143844.png)]](https://i-blog.csdnimg.cn/blog_migrate/5b86dcd68b3d560a2acfd09c28d3ef6e.png)

3. 网络验证分析
注册成功一段时间后,再次注册会发生网络验证失败的情况。OD跟踪,check2中以及主流程中各有一处条件跳转,打补丁改为jmp即可通过网络验证。
4. 小结
010Editor其实有3种算法,由serial[3]是0x9C,0xAC,0xFC判断,这里只分析了0x9C一种情况。
网络验证部分也可以跳进关键函数分析数据包,自己搭建服务器通过验证,当然比较复杂。
1万+

被折叠的 条评论
为什么被折叠?



