目标是EmEditor Professional V6.000,是一款相当好用的的文字处理软件,比起记事本来,有很多好的地方,而且打开也不占用系统资源,速度相当快,至此我以决定破解,不管是爆破还是破序列号,一定要破解!
先说一下EmEditor Professional的序列号组成。一个序列号有四个部分,都只能输入数字,每个部分有四位,形如
xxxx-xxxx-xxxx-xxxx
用Peid查壳,显示无法辨认。在ollyDebugger下加载,加载顺利完成。看来程序并没有加壳。在GetDlgItemInt()处下断点,轻松拦截到,起初搞的不是太清楚,在代码段7xxxxxxx处找到处理输入序列号的地方,以为序列号生成算法就在附近,但是找了半天没有,而起在这一区域设的断点有很多变为无效,提示可能是“自修改代码”,好奇怪的东东,从来没听过这些玩意儿。在这里游走了半个小时以后,仍然没有找到程序的关键。只好通过堆栈上溯,找到调用这些函数的代码。层层追踪,找到如下代码:
0102B3FD |. 8945 F8 mov dword ptr ss:[ebp-8],eax
0102B400 |. EB 04 jmp short EmEditor.0102B406
0102B402 |> 8365 F8 00 and dword ptr ss:[ebp-8],0
0102B406 |> 6A 01 push 1
0102B408 |. 8D45 F8 lea eax,dword ptr ss:[ebp-8]
0102B40B |. 56 push esi
0102B40C |. FF30 push dword ptr ds:[eax]
0102B40E 1.----> |. FFD3 call ebx ; SHOW DIALOGUE
0102B410 |. 57 push edi ; /hMutex
0102B411 |. 8BF0 mov esi,eax ; |
0102B413 |. FF15 F4A20601 call dword ptr ds:[<&KERNEL32.ReleaseMu>; \ReleaseMutex
0102B419 |. 81FE F1030000 cmp esi,3F1
0102B41F |. 75 69 jnz short EmEditor.0102B48A
0102B421 |. E8 51FCFFFF call EmEditor.0102B077
0102B426 |. 85C0 test eax,eax
0102B428 2.----> |. 7E 18 jle short EmEditor.0102B442 ; KEY
0102B42A |. 33C9 xor ecx,ecx
0102B42C |. 83F8 02 cmp eax,2
0102B42F |. 0F94C1 sete cl
0102B432 |. 6A 40 push 40
0102B434 |. A3 10850701 mov dword ptr ds:[1078510],eax
0102B439 |. 81C1 55040000 add ecx,455
0102B43F |. 51 push ecx
0102B440 |. EB 25 jmp short EmEditor.0102B467
0102B442 |> 74 2D je short EmEditor.0102B471
0102B444 |. 83F8 FD cmp eax,-3
0102B447 |. 75 07 jnz short EmEditor.0102B450
0102B449 |. B8 70040000 mov eax,470
0102B44E |. EB 14 jmp short EmEditor.0102B464
0102B450 |> 33C9 xor ecx,ecx
0102B452 |. 83F8 FE cmp eax,-2
0102B455 |. 0F95C1 setne cl
0102B458 |. 49 dec ecx
0102B459 |. 83E1 03 and ecx,3
0102B45C |. 81C1 54040000 add ecx,454
0102B462 |. 8BC1 mov eax,ecx
0102B464 |> 6A 30 push 30
0102B466 3.----> |. 50 push eax ; DIALOGUE
0102B467 |> FF75 08 push dword ptr ss:[ebp+8] ; |Arg1
0102B46A |. 33F6 xor esi,esi ; |
0102B46C |. E8 005F0000 call EmEditor.01031371 ; \EmEditor.01031371
0102B471 |> 837D 0C 00 cmp dword ptr ss:[ebp+C],0
0102B475 |. 74 09 je short EmEditor.0102B480
0102B477 |. FF75 0C push dword ptr ss:[ebp+C] ; /hLibModule
0102B47A |. FF15 E4A10601 call dword ptr ds:[<&KERNEL32.FreeLibra>; \FreeLibrary
0102B480 |> 33C0 xor eax,eax
0102B482 |. 40 inc eax
0102B483 |> 5F pop edi
0102B484 |. 5E pop esi
0102B485 |. 5B pop ebx
0102B486 |. C9 leave
0102B487 |. C2 0800 retn 8
层层上溯,终于追到箭头3的地方,看来这就是显示注册成功与否的对话框,前面的一系列压栈操作,其中就有提示字符串!继续向上寻找代码,找到一个关键的跳转语句,箭头2所指,判断注册成功还是失败。然后LaodString,“Thank you for Registrtion!”再往上,到了一个Call语句,总的来说这个函数调用就是负责注册对话框的显示和序列号的生成和检测,进入这个函数继续寻找!函数体如下: 0102B077 /$ 51 push ecx
0102B078 |. 53 push ebx
0102B079 |. 55 push ebp
0102B07A |. 56 push esi
0102B07B 1.----> |. 57 push edi ; OPEN KEY IN REGTAB
0102B07C |. 6A 0F push 0F ; /Arg2 = 0000000F
0102B07E |. BF 7CDE0601 mov edi,EmEditor.0106DE7C ; |UNICODE "Regist"
0102B083 |. 33DB xor ebx,ebx ; |
0102B085 |. 57 push edi ; |Arg1 => 0106DE7C
0102B086 |. 895C24 18 mov dword ptr ss:[esp+18],ebx ; |
0102B08A |. E8 10F1FFFF call EmEditor.0102A19F ; \EmEditor.0102A19F
0102B08F |. 8B2D 04A00601 mov ebp,dword ptr ds:[<&ADVAPI32.RegClo>;
ADVAPI32.RegCloseKey
0102B095 |. 8BF0 mov esi,eax
0102B097 |. 3BF3 cmp esi,ebx
0102B099 |. 74 16 je short EmEditor.0102B0B1 ; GET KEY VALUE AT 00C0ECAC
0102B09B |. 56 push esi ; /Arg1
0102B09C |. E8 5AFFFFFF call EmEditor.0102AFFB ; \EmEditor.0102AFFB
0102B0A1 |. 56 push esi
0102B0A2 |. 894424 14 mov dword ptr ss:[esp+14],eax
0102B0A6 |. FFD5 call ebp
0102B0A8 |. 33C0 xor eax,eax
0102B0AA |. 40 inc eax
0102B0AB |. 394424 10 cmp dword ptr ss:[esp+10],eax
0102B0AF |. 74 2E je short EmEditor.0102B0DF
0102B0B1 2.----> |> 6A 0D push 0D ; /Arg2 = 0000000D
0102B0B3 |. 57 push edi ; |Arg1
0102B0B4 |. E8 E6F0FFFF call EmEditor.0102A19F ; \EmEditor.0102A19F
0102B0B9 |. 8BF0 mov esi,eax
0102B0BB |. 85F6 test esi,esi
0102B0BD |. 74 12 je short EmEditor.0102B0D1
0102B0BF |. 56 push esi ; /Arg1
0102B0C0 3.----> |. E8 36FFFFFF call EmEditor.0102AFFB ; \EmEditor.0102AFFB
0102B0C5 |. 56 push esi
0102B0C6 |. 8BD8 mov ebx,eax
0102B0C8 |. FFD5 call ebp
0102B0CA |. 33C0 xor eax,eax
0102B0CC |. 40 inc eax
0102B0CD |. 3BD8 cmp ebx,eax
0102B0CF |. 74 0E je short EmEditor.0102B0DF
0102B0D1 |> 8B4424 10 mov eax,dword ptr ss:[esp+10]
0102B0D5 |. 85C0 test eax,eax
0102B0D7 |. 74 04 je short EmEditor.0102B0DD
0102B0D9 |. 85DB test ebx,ebx
0102B0DB |. 74 02 je short EmEditor.0102B0DF
0102B0DD |> 8BC3 mov eax,ebx
0102B0DF |> 5F pop edi
0102B0E0 |. 5E pop esi
0102B0E1 |. 5D pop ebp
0102B0E2 |. 5B pop ebx
0102B0E3 |. 59 pop ecx
0102B0E4 \. C3 retn
箭头1和箭头2所指的地方涉及到了注册表的调用,经追查是从注册表中提取信息,提取的是上次输入的注册号。记得刚才在追踪“自修改”代码时,有注册表的写入操作,这回是从注册表读入信息。看来这是一个通法了。再继续下去,找到一处 cmp [xxxx], 712 ,这就是拿序列号的第一部分和0x712作比较,比较不成功则跳出,成功以后进入另一处函数,这样才有资格进行序列号后面三部分的比较。在下面的这部分有很多比较和跳转,还有很多除法运算。我猜测这就是序列号的检测部分。但我还不知道序列号是生成了在检测还是用是把序列号执行某种运算,再作判断。先看代码,如下:
010452D9 /$ 51 push ecx
010452DA |. 8B4424 08 mov eax,dword ptr ss:[esp+8]
010452DE |. 0FB750 02 movzx edx,word ptr ds:[eax+2]
010452E2 1.----> |. 66:8360 06 00 and word ptr ds:[eax+6],0
010452E7 |. 66:81FA 0F27 cmp dx,270F ; CMP 9999
010452EC |. 0F87 23010000 ja EmEditor.01045415
010452F2 |. 0FB748 04 movzx ecx,word ptr ds:[eax+4]
010452F6 |. 66:81F9 0F27 cmp cx,270F
010452FB |. 0F87 14010000 ja EmEditor.01045415
01045301 2.----> |. 0FB700 movzx eax,word ptr ds:[eax] ; MOV FIRST 4 NUMBERS
01045304 |. 66:3D 1507 cmp ax,715
01045308 |. 890424 mov dword ptr ss:[esp],eax
0104530B |. 75 16 jnz short EmEditor.01045323
0104530D |. 66:81FA 1E1C cmp dx,1C1E
01045312 |. 75 0F jnz short EmEditor.01045323
01045314 |. 66:81F9 9D15 cmp cx,159D
01045319 |. 75 08 jnz short EmEditor.01045323
0104531B |> 6A FE push -2
0104531D |> 58 pop eax
0104531E |. E9 F5000000 jmp EmEditor.01045418
01045323 |> 66:3D 1A07 cmp ax,71A
01045327 |. 75 0D jnz short EmEditor.01045336
01045329 |. 66:81FA 0910 cmp dx,1009
0104532E |. 75 06 jnz short EmEditor.01045336
01045330 |. 66:83F9 19 cmp cx,19
01045334 |.^ 74 E5 je short EmEditor.0104531B
01045336 |> 66:3D 1407 cmp ax,714
0104533A |. 75 0E jnz short EmEditor.0104534A
0104533C |. 66:81FA 2113 cmp dx,1321
01045341 |. 75 07 jnz short EmEditor.0104534A
01045343 |. 66:81F9 6C0B cmp cx,0B6C
01045348 |.^ 74 D1 je short EmEditor.0104531B
0104534A |> 66:3D 1207 cmp ax,712
0104534E |. 75 1C jnz short EmEditor.0104536C
01045350 |. 66:81FA FF01 cmp dx,1FF
01045355 |. 75 07 jnz short EmEditor.0104535E
01045357 |. 66:81F9 6712 cmp cx,1267
0104535C |.^ 74 BD je short EmEditor.0104531B
0104535E |> 66:81FA 8C14 cmp dx,148C
01045363 |. 75 07 jnz short EmEditor.0104536C
01045365 |. 66:81F9 051C cmp cx,1C05
0104536A |.^ 74 AF je short EmEditor.0104531B
0104536C |> 66:3D 1407 cmp ax,714
01045370 |. 75 0E jnz short EmEditor.01045380
01045372 |. 66:81FA 5808 cmp dx,858
01045377 |. 75 07 jnz short EmEditor.01045380
01045379 |. 66:81F9 7222 cmp cx,2272
0104537E |.^ 74 9B je short EmEditor.0104531B
01045380 |> 66:3D 1707 cmp ax,717
01045384 |. 75 0E jnz short EmEditor.01045394
01045386 |. 66:81FA 781D cmp dx,1D78
0104538B |. 75 07 jnz short EmEditor.01045394
0104538D |. 66:81F9 560A cmp cx,0A56
01045392 |.^ 74 87 je short EmEditor.0104531B
01045394 |> 53 push ebx
01045395 |. 55 push ebp
01045396 |. 56 push esi
01045397 |. 57 push edi
01045398 3.----> |. 0FB7FA movzx edi,dx
0104539B |. 0FB7F1 movzx esi,cx
0104539E |. 0FB7C8 movzx ecx,ax
010453A1 |. 6A 64 push 64
010453A3 |. 5B pop ebx
010453A4 |. 8BC6 mov eax,esi ; No3
010453A6 |. 99 cdq
010453A7 |. F7FB idiv ebx
010453A9 |. 6A 0A push 0A
010453AB |. 5D pop ebp
010453AC |. 6A 64 push 64
010453AE |. 8BD8 mov ebx,eax
010453B0 |. 8BC1 mov eax,ecx
010453B2 |. 99 cdq
010453B3 |. F7FD idiv ebp
010453B5 |. 03DF add ebx,edi
010453B7 |. 03C3 add eax,ebx
010453B9 |. 03C6 add eax,esi
010453BB |. 99 cdq
010453BC |. 5E pop esi
010453BD |. 8BDE mov ebx,esi
010453BF |. F7FB idiv ebx
010453C1 |. 8BC7 mov eax,edi
010453C3 |. 8BFE mov edi,esi
010453C5 |. 8BEE mov ebp,esi
010453C7 4.----> |. 66:8B1C95 306007>mov bx,word ptr ds:[edx*4+1076030]
010453CF |. 99 cdq
010453D0 |. 66:6BDB 64 imul bx,bx,64
010453D4 |. F7FF idiv edi
010453D6 |. 8BF8 mov edi,eax
010453D8 |. 8BC1 mov eax,ecx
010453DA |. 99 cdq
010453DB |. F7FD idiv ebp
010453DD |. 03F9 add edi,ecx
010453DF |. 03C7 add eax,edi
010453E1 |. 99 cdq
010453E2 |. F7FE idiv esi
010453E4 |. 8B4424 18 mov eax,dword ptr ss:[esp+18]
010453E8 |. 5F pop edi
010453E9 |. 5E pop esi
010453EA |. 5D pop ebp
010453EB 5.----> |. 66:031C95 306007>add bx,word ptr ds:[edx*4+1076030]
010453F3 |. 66:817C24 04 170>cmp word ptr ss:[esp+4],717
010453FA |. 66:8958 06 mov word ptr ds:[eax+6],bx
010453FE |. 5B pop ebx
010453FF |. 74 0D je short EmEditor.0104540E
01045401 |. 66:813C24 1607 cmp word ptr ss:[esp],716
01045407 |. 74 05 je short EmEditor.0104540E
01045409 |. 33C0 xor eax,eax
0104540B |. 40 inc eax
0104540C |. EB 0A jmp short EmEditor.01045418
0104540E |> 6A 02 push 2
01045410 |.^ E9 08FFFFFF jmp EmEditor.0104531D
01045415 |> 83C8 FF or eax,FFFFFFFF
01045418 |> 59 pop ecx
01045419 \. C2 0400 retn 4
0104541C /$ 0FB706 movzx eax,word ptr ds:[esi]
0104541F |. 6A 0A push 0A
01045421 |. 99 cdq
01045422 |. 59 pop ecx
01045423 |. F7F9 idiv ecx
01045425 |. 3D B5000000 cmp eax,0B5
0104542A |. 74 11 je short EmEditor.0104543D
0104542C |. 33C9 xor ecx,ecx
0104542E |. 3D AB000000 cmp eax,0AB
01045433 |. 0F95C1 setne cl
01045436 |. 8D4C09 FD lea ecx,dword ptr ds:[ecx+ecx-3]
0104543A |. 8BC1 mov eax,ecx
0104543C |. C3 retn
0104543D |> 57 push edi
0104543E |. 0FB77E 06 movzx edi,word ptr ds:[esi+6]
01045442 |. 56 push esi
01045443 |. E8 91FEFFFF call EmEditor.010452D9
01045448 |. 83F8 FE cmp eax,-2
0104544B |. 75 02 jnz short EmEditor.0104544F
0104544D |. 5F pop edi
0104544E |. C3 retn
0104544F |> 66:3B7E 06 cmp di,word ptr ds:[esi+6] ; 08B3
01045453 |. 74 03 je short EmEditor.01045458
01045455 |. 83C8 FF or eax,FFFFFFFF
01045458 |> 5F pop edi
01045459 \. C3 retn
箭头1处比较取得的数是否大于9999,大于则返回。这明显就是判断序列号的四个部分每个部分的取值合理性。从箭头2开始,下面有一系列的判断和跳转,我走了一次,最后现实的信息是序列号不是授权给你的,不管那么多,看下面,我感觉重要的在下面,从箭头3开始,做一系列的除法和加法,数次追踪终于找到关键在箭头4和箭头5的bx的值,比较来比较去终于找到了判断的算法,现在写注册机的时候,我用c语言还原了他的算法。如下,用变量名模拟CPU寄存器,NoX代表序列号的第X部分:
eax = 0xB5;
eax += No3 / 0x64;
eax += No2;
eax += No3;
No4 = data[eax%0x64]; ****
No4 *= 0x64;
No4 &= 0x0000FFFF;
ebx = No2 / 0x64;
ebx += 1810;
eax = 0x12 +ebx;
No4 += data[eax%0x64]; ****
箭头4和箭头5分别对应上面两处打星号的位置,汇编代码里位于[01076030]处的是一个数组的首地址,就是我设的
data[],有一百个元素,其值为:
0x26, 0x5B, 0x62, 0x36, 0x34, 0x60, 0x13, 0x35,
0x19, 0x54, 0x3F, 0x44, 0x4C, 0x38, 0x5D, 0x33,
0x56, 0x61, 0x42, 0x21, 0x3E, 0x2D, 0x23, 0x0E,
0x1E, 0x5F, 0x57, 0x12, 0x1B, 0x17, 0x22, 0x58,
0x2C, 0x63, 0x5C, 0x18, 0x37, 0x41, 0x59, 0x4D,
0x15, 0x5A, 0x53, 0x0B, 0x05, 0x1C, 0x10, 0x2E,
0x49, 0x40, 0x0D, 0x07, 0x50, 0x3D, 0x32, 0x46,
0x0A, 0x43, 0x2B, 0x00, 0x3B, 0x48, 0x5E, 0x4E,
0x51, 0x1F, 0x20, 0x3A, 0x01, 0x2A, 0x45, 0x55,
0x4A, 0x02, 0x52, 0x27, 0x03, 0x4B, 0x08, 0x3C,
0x0F, 0x14, 0x24, 0x25, 0x28, 0x29, 0x16, 0x1D,
0x1A, 0x11, 0x2F, 0x39, 0x09, 0x47, 0x06, 0x4F,
0x04, 0x31, 0x0C, 0x30
这个判断算法大致就是将序列号前三部分通过一系列的运算,的到一个结果,到存在内存某处。然后用序列号的第四部分和这个结果进行比较,相等就注册成功。至于具体细节,看C语言代码就很明了了。到此为止,程序基本已经解完了。
回想在解的过程中,碰到了很奇怪的“自修改”代码,现在想来,那一段是在USER32.dll里调用的,可能加断点会无效,但具体为什么无效,我也说不出其所以然。程序一开始运行时,就会提取注册表信息,判断是否注册,就是重复刚才那几步,只不过使用者并不知道。在输入新的序列号之前,也就是输入框出现之前,程序也会检测是否注册,这是为了如果未注册,显示使用天数,这并不难理解。最后才到用户输入序列号并检测。直到注册成功!
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课