010Editor逆向分析

010Editor 逆向分析


用于测试的用户名为 foobar,序列号为 1112-2233-3444-5556-6677

1. 暴力破解分析

思路:

  1. 找到注册窗口
  2. 测试注册窗口反应
  3. 根据反应做出下一步分析(猜测api,敏感字符串)
  4. 动态分析关键跳转、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 0xE7
  • ecx = (serial[1] ^ serial[7]) * 0x100 + serial[2] ^ serial[5], ecx &= 0xFFFF
  • 010Edito.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 & 0xFF
  • serial[5] == (calc3>>8) & 0xFF
  • serial[6] == (calc3>>0x10) & 0xFF
  • serial[7] == (calc3>>0x18) & 0xFF
  • serial[3] == 0x9C || 0xFC || 0xAC
  • calc1 >= 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)]

在这里插入图片描述

3. 网络验证分析

注册成功一段时间后,再次注册会发生网络验证失败的情况。OD跟踪,check2中以及主流程中各有一处条件跳转,打补丁改为jmp即可通过网络验证。

4. 小结

010Editor其实有3种算法,由serial[3]是0x9C,0xAC,0xFC判断,这里只分析了0x9C一种情况。

网络验证部分也可以跳进关键函数分析数据包,自己搭建服务器通过验证,当然比较复杂。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值