Reglo v3.5a 注册算法详析

【软件名称】:
    Reglo v3.5a

【下载地址】:
    Http://www.basta.com/

【软件简介】:
    Reglo 是一款屏幕测量工具,其实就是一把尺子,有多种单位可以选择,用于测量图片或其它.

【软件限制】:
    如果没有注册,则只有 30 天的试用期.

【破解声明】:
    我是搞软件的,知道一款软件的开发,需要付出很多很多,所以如果大家有点钱的话,还是多支持支持软件的作者,这样人家才有干劲呀.其实,这款软件对我没什么用,只是好奇它的注册算法而已,如果大家有兴趣,不妨在看完了这篇注册算法分析后,用 Reglo 的姊妹软件试试,看它们的注册算法是什么.它的姊妹软件包括:
    AppToService v4.0 一个 Windows 控制台,可以让你把常规程序或服务器,运行做 Windows 服务.
    Buzof v4.0 一款让你可以消除烦人的系统消息的工具,比如,删除文件时,系统总会提示是否真的要删除等信息.
    Deletor v4.2 一款可以按照你的要求,进行删除的工具.例如,删除满足一定大小/时间/属性等条件的文件夹等等.
    Filo v4.2 修改文件或文件夹属性的工具.
    Splitty v4.0 文件分割合并工具.
    ZMover v7.2 桌面布局管理软件,可以让你指定程序窗口位置/大小/层次等.

【破解工具】:
    PEID v0.94,OllyDBG v1.10

【破文作者】:
    WSLVIC 电邮:Crk4u@163.com

【破解时间】:
    二一一年七月二日

【破解过程】:

    不必多说,首先是查查壳,用 PEID 一看,原来是 Microsoft Visual C++ 7.0,也好,省的脱壳了.然后当然是用 OD 载入,载入后停在这里:
    00416197 > $  6A 60         PUSH 60
    00416199   .  68 F0284300   PUSH Reglo2.004328F0
    0041619E   .  E8 451A0000   CALL Reglo2.00417BE8
    004161A3   .  BF 94000000   MOV EDI,94
    004161A8   .  8BC7          MOV EAX,EDI
    004161AA   .  E8 C10E0000   CALL Reglo2.00417070
    004161AF   .  8965 E8       MOV DWORD PTR SS:[EBP-18],ESP
    004161B2   .  8BF4          MOV ESI,ESP
    004161B4   .  893E          MOV DWORD PTR DS:[ESI],EDI
    004161B6   .  56            PUSH ESI                                 ; /pVersionInformation
    004161B7   .  FF15 A8014300 CALL DWORD PTR DS:[<&KERNEL32.GetVersion>; \GetVersionExA
    ......
    典型的 VC 入口呀,不急下断点,先看看注册窗口是什么样的,按 F9 运行,在标尺上点击右键选择 "&About Reglo...",在弹出窗口中选择 "&Purchasing Information...",终于出现了,原来注册码分两部分,每部分都 8 位,总长度是 16,也就是十六进制的 0x10,好了先看看能不能输入中文(呵呵,习惯了),输入 "瑶淼"---原来可以输入中文,当然也可以输入英文字母和数字,胡乱输一通,比如 "11111111-22222222" 点击 "Ok",弹出对话框 "The password you have entered is incorrect."---现在当然不能注册,不过记下了这串 E 文,点击 "确定" 回到 OD 中,用 OD 自带的字符串搜索功能搜索 "所有参考文本字符串",很快就搜索完毕了,可惜上面那串 E 文,连影子都没有,再用 OD 插件 "超级字串参考" 和 "中文搜索引擎",也没找到,晕,只好下对话框断点了,试试 MessageBoxA 吧,在命令条中输入 "bpx messageboxa",然后点击 F9 运行,输入注册码 "11111111-22222222",点击 "确定",呵呵,运气不错,被 OD 断了下来,断点停在:
    0042B8C7  |.  FF15 9C034300 CALL DWORD PTR DS:[<&USER32.MessageBoxA>>; \MessageBoxA (1)
    不管它,向上看,直到该段代码的开始处,
    0042B7FB  /> /55            PUSH EBP (2)
    点击该行,这时,OD 显示 "跳转来自 0042B924",一看,竟然是这段代码的最后一行
    0042B924  \.^\E9 D2FEFFFF   JMP Reglo2.0042B7FB (3)
    于是想到应该是从其它某处调用到 (1) 之后的某个地方,再执行到 (3),然后再跳转到 (2),仔细观察代码,发现 (1) 之后的 0042B904 处
    0042B904  |.  C2 0C00       RETN 0C
    非常可疑,其下一行为
    0042B907  |$  55            PUSH EBP
    在该行点击,OD 显示 "本地调用来自 00401698,00406C15,00425097,0042B96A,0042C086,0042C163",到底从这里的哪个调用来的呢,不忙,先在该处按 F2,下一个断点,按 F9 运行程序,胡乱输入 "1111111122222222" 点击 "确定",发现 OD 已经断在了该处,在 OD 工具栏中点击图标 "K" --- 调用堆栈,发现 OD 显示 "函数过程 Reglo.0042B907,调用来自 Reglo.0042B96A",于是在 OD 信息窗口的 "本地调用来自 00401698,00406C15,00425097,0042B96A...",上点右键选择 "转到 Call 来自 0042B96A",于是 OD 立刻来到了 0042B96A 所在的代码段,在该段的段头:
    0042B929  /$  B8 6DED4200   MOV EAX,Reglo.0042ED6D
    上点击,发现 OD 显示 "本地调用来自 004018F9, 00401F57, 0040777D, 00407CB3, 0040887B, 0040A7CC, 0040B31A, 0040B464, 00412AC9, 00412AE0, 004250B4, 004286E2, 0042875A, 0042BF9A",晕,又是一堆,咋办呢,还是老办法,F2 断点→F9 运行→输入注册码→点击 "确定",OD 断在了 0042B929,在点击图标 "K",OD 显示 "函数过程 Reglo.0042B929,调用来自 Reglo.0040B464",再次转到 0040B464 所在的代码段,该段的段头为:
    0040B350  /$  64:A1 0000000>MOV EAX,DWORD PTR FS:[0]
    下断点,运行到该行,F8 单步到
    0040B3AC  |.  E8 519A0100   CALL Reglo.00424E02
    处时,注册窗口弹出,呵呵,该行是调用注册窗口的.继续 F8,直到
    0040B3BA  |.  8B8424 D80000>MOV EAX,DWORD PTR SS:[ESP+D8]
    这时在 OD 信息窗口中看到了已经输入的注册码.
    哈哈,万里长征终于走到了第一站!
    这点很重要,这是进行下一步调试的关键,于是整理一下,删除除了 0040B3BA 处以外的所有其它断点.注册码分析就要开始了.在 0040B3BA 下方看到一个 Call
    0040B3DA  |.  E8 A17B0000   CALL Reglo.00412F80
    F7 跟进去一看,果真是进行注册码测试,好,先下个断点.然后按 Ctrl+F2 重新载入,输入注册码时输入 "1111111122222222",OD 断在了 0040B3DA,F7 跟进,便来到了下方,
    ┌──────────────────────────────────────────────┐
    │00412F80  PUSH EBX                                                                          │
    │00412F81  PUSH EBP                                                                          │
    │00412F82  MOV EBX,EAX                        ;  EBX=输入的注册码                            │
    │00412F84  PUSH ESI                                                                          │
    │00412F85  XOR EBP,EBP                                                                       │
    │00412F87  TEST EBX,EBX                                                                      │
    │00412F89  PUSH EDI                                                                          │
    │00412F8A  JE Reglo.004130C7                  ;  一跳就死                                    │
    │00412F90  LEA EDX,DWORD PTR DS:[EAX+1]       ;  EDX=第二个字符的地址                        │
    │00412F93  /MOV CL,BYTE PTR DS:[EAX]          ;  CL=第一个字符                               │
    │00412F95  |INC EAX                           ;  下一个                                      │
    │00412F96  |TEST CL,CL                        ;  测试 CL,是否为全零                          │
    │00412F98  \JNZ SHORT Reglo.00412F93          ;  循环测试 CL 是否为 00                       │
    │00412F9A  SUB EAX,EDX                        ;  EAX=尾字符的地址,EDX=第二个字符的地址       │
    │00412F9C  CMP EAX,10                         ;  比较字符串长度是否为 16                     │
    │00412F9F  JNZ Reglo.004130C7                 ;  不等就跳,一跳就完                           │
    │00412FA5  MOV ESI,EAX                        ;  ESI 指向尾字符                              │
    └──────────────────────────────────────────────┘
    这段代码没什么难度,就是测试下输入的注册码的长度,及每个字符是否为 00,之后则来到,
    ┌──────────────────────────────────────────────┐
    │00412FA7  /MOVSX EAX,BYTE PTR DS:[ESI+EBX-1]                                                │
    │00412FAC  |DEC ESI                                                                          │
    │00412FAD  |PUSH EAX                                                                         │
    ┌──────────────────────────────────────────────┐
    │00412FAE  |CALL Reglo.00415ACF               ;  判断是否是数字                           (4)│
    └──────────────────────────────────────────────┘
    │00412FB3  |ADD ESP,4                                                                        │
    │00412FB6  |TEST EAX,EAX                                                                     │
    │00412FB8  |JE Reglo.004130C7                 ;  这里一跳就完蛋                              │
    │00412FBE  |TEST ESI,ESI                                                                     │
    │00412FC0  \JNZ SHORT Reglo.00412FA7                                                         │
    └──────────────────────────────────────────────┘
    这是一段循环,中间有个 Call,其过程是逐字读入注册码的每一位,然后用 Call 进行测试,看来是关键测试了,F7 步入,OD 转到这里,
    ┌──────────────────────────────────────────────┐
    │00415ACF  PUSH EBP                                                                          │
    │00415AD0  MOV EBP,ESP                                                                       │
    │00415AD2  PUSH ECX                                                                          │
    │00415AD3  PUSH EBX                                                                          │
    │00415AD4  MOV EBX,DWORD PTR SS:[EBP+8]             ;  地址移动8位                           │
    │00415AD7  CMP EBX,0FF                              ;  判定是否是 ASCII 字符                 │
    │00415ADD /JBE SHORT Reglo.00415B49                 ;  小于等于 255 时,这里一定跳,否则必死   │
    └──────────────────────────────────────────────┘
    这段的关键是上面的最后两句,0FF 是 255,EBX 中是当前位置注册码的 ASCII 值(别忘了这段是在循环中),所以这两句是测试,如果当前位置注册码的 ASCII 值 <= 255 时,发生跳转,由 255 可知,要求输入字符是 "基本拉丁字符集" 和 "增补拉丁字符集 1" 中的字符,由于当前输入的测试注册码是 "1111111122222222",故满足要求,不过你可以试试输入注册码 "!111111122222222",这时 Reglo 会弹出对话框 "Please enter no more than 8 characters." 注册就失败了.然后跳转到 00415B49 处,注意,中间有段代码 00415ADF-00415B47 是处理当字符的 ASCII>255 时,进行的操作,我没跟过去,如果谁有兴趣不妨看看 Reglo 在这段干了什么.跳转到了这里,
    ┌──────────────────────────────────────────────┐
    │00415B49 \PUSH EBX                                                                          │
    │00415B4A  CALL Reglo.00419636                      ;  判断是否是数字:否,则跳出.             │
    │00415B4F  POP ECX                                                                           │
    │00415B50  POP EBX                                                                           │
    │00415B51  LEAVE                                                                             │
    │00415B52  RETN                                                                              │
    └──────────────────────────────────────────────┘
    又是一个 Call! 烦哪,还得跟进去,来到,
    ┌──────────────────────────────────────────────┐
    │00419636  /$ CALL Reglo.00419174                      ;  别管,给 EAX 一个地址               │
    │0041963B  |. MOV EAX,DWORD PTR DS:[EAX+64]                                                  │
    │0041963E  |. CMP EAX,DWORD PTR DS:[43FF84]            ;  Reglo.0043FF30                     │
    │00419644  |. JE SHORT Reglo.0041964B                  ;  必跳                               │
    │00419646  |. CALL Reglo.0041911B                                                            │
    │0041964B  |> CMP DWORD PTR DS:[EAX+28],1                                                    │
    │0041964F  |. JLE SHORT Reglo.00419661                 ;  必跳                               │
    │00419651  |. PUSH 4                                                                         │
    │00419653  |. PUSH DWORD PTR SS:[ESP+8]                                                      │
    │00419657  |. PUSH EAX                                                                       │
    │00419658  |. CALL Reglo.004198A3                                                            │
    │0041965D  |. ADD ESP,0C                                                                     │
    │00419660  |. RETN                                                                           │
    │00419661  |> MOV EAX,DWORD PTR DS:[EAX+48]            ;  UNICODE "         (((((            │
    │00419664  |. MOV ECX,DWORD PTR SS:[ESP+4]                                                   │
    │00419668  |. MOVZX EAX,BYTE PTR DS:[EAX+ECX*2]        ;  若 ECX 为 0-9 的数字,则 EAX=84     │
    │0041966C  |. AND EAX,4                                ;  EAX=84 And 4=4                     │
    │0041966F  \. RETN                                                                           │
    └──────────────────────────────────────────────┘
    怎么又是一堆 Call!而且第一句就是一个 Call,F7 进入,发现一堆系统调用,包括 GetLastError/TlsGetValue/TlsSetValue/GetCurrentThreadID/SetLastError 等等,它们是干什么的呢?为什么这么做?到现在也没搞清楚,不过至少知道这个 Call 改变了 EAX,也就是给 EAX 了一个新地址,在这个 Call 后的两句,可以看到它是在进行地址比较,不管它了,来到 00419644 处,这里一定要跳,之后来到 0041964B,与 1 进行比较,之后到了 0041964F 处跳转至 00419661,关键来了,这句给了 EAX 一个 UniCode 字符串,这是干啥用的?先不急,看看它的下一句,在 OD 的信息窗口看到了 SS:[ESP+4] 中放置的其实就是当前位置注册码的 ASCII 值,并赋给了 ECX,ok,再下一句看到了 DS:[EAX+ECX*2],这又是干什么呢,分析看看:当前 EAX 是一个指向 UniCode 的地址,ECX 是 ASCII 值,所以EAX+ECX*2 必是一个地址,运行到该句 OD 显示 "DS:[00432AC6]=84",在其上点击右键,选择 "数据窗口中跟随地址",发现数据窗口中是这样一串数据 "84 00 84 00 84 00 84 00..."---共 10 组 "84 00",于是终于明白了这段到底是在干什么,也就是说从该段的第一个 Call 给了 EAX 一个地址,通过这个地址加上一定的偏移量,指向一些特定地址,例如 00419661 处,EAX=DS:[EAX+48],如果当前位置的字符是 0-9 (ASCII 的 30-39),那么从 DS:[EAX+48] 开始,偏移 30*2 至 39*2,都将得到 84 这个值,也就是上面的 10 组 "84 00",那么为什么是 84 呢,从 0041966C 处,可看出 84 与 4 进行 "与" 操作,EAX 将等于 4,不等于 0,换句话说 EAX 就是一个 flag,当 EAX 非零时,说明匹配成功,当前字符是 0-9 的数字,否则 EAX=0 说明匹配失败.其实在 UltraEdit 中也可找到这两个字符串,其中,
    UNICODE "         (((((                  H" 在实偏移 31C62H 处
    "84 00 84 00 84 00 84 00 84 00 84 00 84 00..." 在实偏移 31CC2H 处
    谁有兴趣的话,不妨算算它们之间的偏移是否是 0x30*2 - 0x39*2
    至此,万里长征第二步算完成了,可是只是知道注册码由数字组成,却不知道形成规则是什么,怎么办,只好一路返回,回到 00412FB3 处,此处仍在循环中,在 00412FC2 处按 F4,运行到该行,
    ┌──────────────────────────────────────────────┐
    │00412FC2  MOVSX EDI,BYTE PTR DS:[EBX+F]      ;  EDI=末字符                                  │
    │00412FC6  SUB EDI,30                                                                        │
    │00412FC9  MOV EAX,EDI                        ;  EAX=末字符的数值                            │
    │00412FCB  IMUL EAX,EDI                       ;  EAX=末位数的平方                            │
    │00412FCE  CDQ                                ;  CDQ 双字扩展成四字                          │
    │00412FCF  MOV ECX,0A                                                                        │
    │00412FD4  IDIV ECX                           ;  末位数的平方(EAX)除 10,商放 EAX,余放 EDX    │
    │00412FD6  MOV AL,BYTE PTR DS:[EDI+EBX]       ;  AL=第 EDI+1 个字符                          │
    │00412FD9  ADD DL,30                          ;  加 30                                       │
    │00412FDC  CMP AL,DL                          ;                                              │
    │00412FDE  JNZ Reglo.004130C7                 ;  一跳就完                                    │
    └──────────────────────────────────────────────┘
    这段代码的关键是:
    AL 是注册码的第 EDI+1(EDI=末字符的数值) 个字符,而 DL 是注册码的最后一个字符的平方,除以 10,所得的余数,由 CMP Al,DL 及其下一行的 JNZ Reglo.004130C7 可知,注册码的最后一位的数值 i,决定着注册码第 i+1 位的值必须为最后一位的数值的平方除 10 所得的余数,即:
    最后一位数 i    第 i+1 位数          模型
    i=0        第 1(i+1) 位 为 0(0^2 Mod 10)      0xxxxxxx xxxxxxx0
    i=1        第 2(i+1) 位 为 1(1^2 Mod 10)      x1xxxxxx xxxxxxx1
    i=2        第 3(i+1) 位 为 4(2^2 Mod 10)      xx4xxxxx xxxxxxx2
    i=3        第 4(i+1) 位 为 9(3^2 Mod 10)      xxx9xxxx xxxxxxx3
    i=4        第 5(i+1) 位 为 6(4^2 Mod 10)      xxxx6xxx xxxxxxx4
    i=5        第 6(i+1) 位 为 5(5^2 Mod 10)      xxxxx5xx xxxxxxx5
    i=6        第 7(i+1) 位 为 6(6^2 Mod 10)      xxxxxx6x xxxxxxx6
    i=7        第 8(i+1) 位 为 9(7^2 Mod 10)      xxxxxxx9 xxxxxxx7
    i=8        第 9(i+1) 位 为 4(8^2 Mod 10)      xxxxxxxx 4xxxxxx8
    i=9        第 10(i+1) 位 为 1(9^2 Mod 10)      xxxxxxxx x1xxxxx9
    由于输入的测试注册码 "1111111122222222" 不满足这个要求,在 00412FDE 处,就跳走了.不妨把测试注册码改为 "1141111122222222",Ctrl+F2 重新运行,终于第一个坎 00412FDE 过了,然后代码来到,
    ┌──────────────────────────────────────────────┐
    │00412FE4  |.LEA EAX,DWORD PTR DS:[EDI+1]             ;  EAX=末字符数值+1                    │
    │00412FE7  |.CMP EAX,0F                                                                      │
    │00412FEA  |.JL SHORT Reglo.00412FEF                  ;  必跳                                │
    │00412FEC  |.SUB EAX,0F                                                                      │
    │00412FEF  |>MOVSX ECX,BYTE PTR DS:[EAX+EBX]          ;  ECX=第 末字符数值+2 个数字          │
    │00412FF3  |.LEA ESI,DWORD PTR DS:[ECX-30]            ;  ESI=第 末字符数值+2 个数字的数值    │
    │00412FF6  |.CMP ESI,1                                ;  第 末字符数值+2 位数字必须大于等于 1│
    │00412FF9  |.JGE SHORT Reglo.00413000                 ;  必跳                                │
    └──────────────────────────────────────────────┘
    测试注册码 "1141111122222222" 的末位为 "2",故第 4(2+2) 个数字必须大于等于 1,测试注册码已满足要求,呵呵,继续啊,第二个坎 00412FF9 过了.继续来到这里,
    ┌──────────────────────────────────────────────┐
    │00413000  |>XOR ECX,ECX                                                                     │
    │00413002  |.CMP ESI,1                                                                       │
    │00413005  |.SETG CL                            ;  根据标志寄存器,大于 1 时,置 CL=1          │
    │00413008  |>MOV EDX,DWORD PTR SS:[ESP+14]                                                   │
    │0041300C  |.INC EAX                            ;  EAX=末字符数值+2                          │
    │0041300D  |.CMP EAX,0F                                                                      │
    │00413010  |.MOV DWORD PTR DS:[EDX],ECX         ;  DS:[0012F3C0]=00000000                    │
    │00413012  |.JL SHORT Reglo.00413017                                                         │
    │00413014  |.SUB EAX,0F                                                                      │
    │00413017  |>MOV ECX,EAX                        ;  ECX=末字符数值+2                          │
    │00413019  |.ADD EAX,3                          ;  EAX=末字符数值+5                          │
    │0041301C  |.CMP EAX,0F                                                                      │
    │0041301F  |.JL SHORT Reglo.00413024                                                         │
    │00413021  |.SUB EAX,0F                                                                      │
    │00413024  |>MOVSX ESI,BYTE PTR DS:[ECX+EBX]    ;  ESI=第 末字符数值+3 个数的 ASCII 值       │
    │00413028  |.MOV ECX,EAX                        ;  ECX=末字符数值+5                          │
    │0041302A  |.ADD EAX,3                          ;  EAX=末字符数值+8                          │
    │0041302D  |.SUB ESI,30                         ;  ESI=第 末字符数值+3 个数的数值            │
    │00413030  |.CMP EAX,0F                                                                      │
    │00413033  |.JL SHORT Reglo.00413038                                                         │
    │00413035  |.SUB EAX,0F                                                                      │
    │00413038  |>MOVSX ECX,BYTE PTR DS:[ECX+EBX]    ;  ECX=第 末字符数值+6 个数的 ASCII 值       │
    │0041303C  |.SUB ECX,30                         ;  ECX=第 末字符数值+6 个数的数值            │
    │0041303F  |.MOV EDX,EAX                                                                     │
    │;EDX=末字符数值+8(末字符数值+8<15 时)或末字符数值+8-15(末字符数值+8>=15 时)                 │
    │00413041  |.IMUL ECX,ECX,64                    ;  ECX=(第 末字符数值+6 个数的数值)*64       │
    │00413044  |.ADD EAX,3                          ;  EAX=末字符数值+11                         │
    │00413047  |.CMP EAX,0F                                                                      │
    │0041304A  |.JL SHORT Reglo.0041304F                                                         │
    │0041304C  |.SUB EAX,0F                                                                      │
    │0041304F  |>MOVSX EDX,BYTE PTR DS:[EDX+EBX]                                                 │
    │;EDX=第{末字符数值+9(末字符数值+8<15)或末字符数值+8-14(末字符数值+8>=15)}个数字的ASCII值    │
    │00413053  |.MOVSX EAX,BYTE PTR DS:[EAX+EBX]                                                 │
    │;EAX=第{末字符数值+12(末字符数值+11<15)或末字符数值+11-14(末字符数值+11>=15)}个数字的ASCII值│
    │00413057  |.SUB EDX,30                                                                      │
    │;@EDX=第{末字符数值+9(末字符数值+8<15)或末字符数值+8-14(末字符数值+8>=15)}个数字的数值      │
    │0041305A  |.ADD ECX,EAX                        ;  ECX=ECX+EAX                               │
    │0041305C  |.LEA EDX,DWORD PTR DS:[EDX+EDX*4]   ;  EDX=@EDX*5                                │
    │0041305F  |.LEA ECX,DWORD PTR DS:[ECX+EDX*2-30];  ECX=ECX+@EDX*A-30                         │
    │00413063  |.CMP ECX,7                          ;  ECX=ECX+@EDX*A-30=7(@EDX 是 0-9 的数字)   │
    │00413066  |.JNZ SHORT Reglo.004130C7           ;  这里一跳就完蛋了                          │
    └──────────────────────────────────────────────┘
    这段代码比较复杂,注释写的老长,目的是方便修改测试注册码,否则,注册无法继续下去.其关键是 00413063 处,要求 ECX+@EDX*A-30=7,这里:
    ECX=(第 末字符数值+6 个数的数值)*64+第{末字符数值+12(末字符数值+11<15)或末字符数值+11-14(末字符数值+11>=15)}个数字的 ASCII 值.
    @EDX 是 0-9 中的某个数字.
    对当前的测试注册码 "1141111122222222" 而言,第 8(2+6) 个数是 "1",1*64=64;第 14(2+12) 个数是 "2",其 ASCII=32,所以 ECX=1*64+32=96.
    同时 @EDX=第 11(2+9) 个数是 "2",故 ECX+@EDX*A-30=96+2*A-30=7A 不等于 7,故无法继续,如何修改测试注册码呢,观察 ECX,@EDX 可知,ECX>=30,@EDX>=0,故若 ECX+@EDX*A-30=7,则 ECX+@EDX*A=37,就必有 @EDX*A<=7,所以 @EDX=0,ECX=37,换句话说,
    1.@EDX=0,意味着:
      第{末字符数值+9(末字符数值+8<15)或末字符数值+8-14(末字符数值+8>=15)}个数字的数值必为 0.
    2.ECX=37,意味着:
      第 末字符数值+6 个数的数值必为 0,
      第{末字符数值+12(末字符数值+11<15)或末字符数值+11-14(末字符数值+11>=15)}个数字的数值必为 7.
    由此可以看出,末位数对整个注册码具有标志性作用,我们不妨试着修改一下,对于 "1141111122222222" 而言,末位数为 "2",因为 2+8<15,故第 11(2+9) 位数为 0.又从 "第 末字符数值+6 个数的数值必为 0" 可知,第 8 位数为 0,因为 2+11<15,故 第 14(2+12) 位数必为 7.测试注册码可改为 "1141111022022722"
    整理一下当末位数为 0-9 时的注册模型,如下:
    最后一位数 i        模型
    i=0            0xxxx0xx 0xx7xxx0
    i=1            x1xxxx0x x0xx7xx1
    i=2            xx4xxxx0 xx0xx7x2
    i=3            xxx9xxxx 0xx0xx73
    i=4            7xxx6xxx x0xx0xx4
    i=5            x7xxx5xx xx0xx0x5
    i=6            xx7xxx6x xxx0xx06
    i=7            0xx7xxx9 xxxx0xx7
    i=8            x0xx7xxx 4xxxx0x8
    i=9            xx0xx7xx x1xxxx09
    继续吧,还有不少工作要做,越过第三个坎 00413066,我们来到这里,
    ┌──────────────────────────────────────────────┐
    │00413068  |.XOR EAX,EAX                                                                     │
    │0041306A  |.MOV ECX,10                                                                      │
    │0041306F  |.NOP                                                                             │
    │00413070  |>/MOVSX EDX,BYTE PTR DS:[ECX+EBX-1];EDX=第 ECX 个数字                            │
    │00413075  |.|DEC ECX                                                                        │
    │00413076  |.|IMUL EDX,ECX                                                                   │
    │00413079  |.|ADD EAX,EDX                      ;@EAX=Sum{(数字的 ACSII 值)*(其所在位置-1)}   │
    │0041307B  |.|TEST ECX,ECX                                                                   │
    │0041307D  |.\JNZ SHORT Reglo.00413070                                                       │
    │0041307F  |.LEA EDX,DWORD PTR DS:[EDI+E]      ;EDI=末位数的数值                             │
    │00413082  |.CMP EDX,0F                        ;EDX=14(末位数为 0 时)                        │
    │00413085  |.JL SHORT Reglo.0041308A           ;必跳                                         │
    │00413087  |.SUB EDX,0F                                                                      │
    │0041308A  |>MOVSX ECX,BYTE PTR DS:[EDX+EBX]   ;EDX=末位数的数值+14-15(末位数不为 0 时)      │
    │;@ECX=第 15 个数的 ASCII(当末位数为 0 时)或第 末位数数值 位数的 ASCII 值(当末位数不为 0 时) │
    │0041308E  |.IMUL ECX,EDX                      ;ECX=@ECX*EDX                                 │
    │00413091  |.SUB EAX,ECX                       ;EAX=@EAX-ECX                                 │
    │00413093  |.DEC EDX                                                                         │
    │00413094  |.JNS SHORT Reglo.00413099          ;结果为正就跳(SF=0)                           │
    │00413096  |.ADD EDX,0F                                                                      │
    │00413099  |>MOV CL,BYTE PTR DS:[EDX+EBX]                                                    │
    │;@CL=第 14 个数的 ASCII(当末位数为 0 时)或第 末位数数值-1 位数的 ASCII 值(当末位数不为 0 时)│
    │0041309C  |.MOVSX EDI,CL                      ;                                             │
    │0041309F  |.IMUL EDI,EDX                      ;                                             │
    │004130A2  |.SUB EAX,EDI                       ;EAX=@EAX-@ECX*EDX-@CL*EDI                    │
    │004130A4  |.CDQ                                                                             │
    │004130A5  |.MOV EDI,0A                        ;EDI=除数 10                                  │
    │004130AA  |.IDIV EDI                                                                        │
    │004130AC  |.ADD DL,30                         ;DL=余数的 ASCII 值                           │
    │004130AF  |.CMP DL,CL                         ;CL=第 14 个数或第 末位数数值-1 位数的 ASCII  │
    │004130B1  |.JNZ SHORT Reglo.004130C7          ;一跳就死                                     │
    └──────────────────────────────────────────────┘
    一进来,就看见一个循环,不过这个循环倒也简单,就是从尾到头,将每个数字的 ASCII 值与其(所在位置-1)相乘,并累加起来送给 EAX,也就是说:@EAX=n1*0+n2*1+n3*2+n4*3+n5*4+...+n13*12+n14*13+n15*14+n16*15,循环完毕后,来到了 0041307F,觉得有点奇怪,因为在这段代码附近,没看到 EDI 是何时赋值的,仔细找了好一段代码才发现,上次 EDI 是在 00412FC6 处赋值的,其值是末位数的数值,对当前测试注册码 "1141111022022722" 而言,它就是 "2".这段代码的关键是 004130AF,它事实上是要求:

    当末位数为 0 时,
    设置被除数 EAX=@EAX-n15*14-n14*13
    设置第 14 位数的数值为 EAX/10 的余数

    当末位数为 1 时,
    设置被除数 EAX=@EAX-n1*0-n15*14=@EAX-n15*14
    设置第 15 位数的数值为 EAX/10 的余数

    当末位数为 2 时,
    设置被除数 EAX=@EAX-n2*1-n1*0=@EAX-n2*1
    设置第 1 位数的数值为 EAX/10 的余数

    当末位数不为 0,1,2 时,
    设置被除数 EAX=@EAX-ni*(i-1)-n(i-1)*(i-2)
    设置第 i-1 位数的数值为 EAX/10 的余数,(其中,i=末位数数值)

    先在的问题是如何修改测试注册码,算出 EAX,再求余,显然不是一个好办法,解决方法是在 4130AF 处下一个断点,看 OD 信息窗口中的 DL,是什么数字,再退出 Reglo,修改指定位置的数字为 DL 中的值.例如,当前测试注册码,末位数是 "2",故需要修改第 1 位数为 DL 中的值 7,修改后,测试注册码变为 "7141111022022722",试试,呵呵,又过一关呐!如此以来,第四个坎也过了.之后,来到这里,
    ┌──────────────────────────────────────────────┐
    │004130B3  PUSH ESI                                                                          │
    │004130B4  PUSH 10                                                                           │
    ┌──────────────────────────────────────────────┐
    │004130B6  CALL Reglo.00412ED0                                                            (5)│
    └──────────────────────────────────────────────┘
    │004130BB  ADD ESP,8                                                                         │
    │004130BE  TEST EAX,EAX                                                                      │
    │004130C0  MOV EAX,1                                                                         │
    │004130C5  JNZ SHORT Reglo.004130C9                                                          │
    │004130C7  MOV EAX,EBP                                                                       │
    │004130C9  POP EDI                                                                           │
    │004130CA  POP ESI                                                                           │
    │004130CB  POP EBP                                                                           │
    │004130CC  POP EBX                                                                           │
    │004130CD  RETN                                                                              │
    └──────────────────────────────────────────────┘
    这里有个 Call,应该是调用注册成功的对话框吧,哈哈,迫不及待了,按 F9 直接运行!什么也没弹出来,但仔细一看原来关于对话框中的两行字符 "THIS SOFTWARE IS NOT REGISTERED" 以及 "Expiration date:08/01/11" 不见了,呵呵,真的注册成功了!测试注册码 "7141111022022722" 真的是注册码!
    高兴归高兴,可是别得意忘形,凡事都要留个心眼.其实在写这篇破文之前,我用的测试注册码不是 "1111111122222222" 而是 "0100000000070200",这个注册码和 "7141111022022722" 一样可以通过以上所有的坎,可是当你用它来注册的时候,会让 Reglo 爆掉!不信你试试!为什么呢?---其实原因很简单,还有暗桩!在那里?当然不能放过上面的那个 Call (5) 了,F7 步入,到了这里,
    ┌──────────────────────────────────────────────┐
    │00412ED0  /$SUB ESP,8                           ;  指向注册码                               │
    │00412ED3  |.TEST EBX,EBX                                                                    │
    │00412ED5  |.JNZ SHORT Reglo.00412EDD            ;  这里必跳                                 │
    │00412ED7  |.XOR EAX,EAX                                                                     │
    │00412ED9  |.ADD ESP,8                                                                       │
    │00412EDC  |.RETN                                                                            │
    │00412EDD  |>CMP DWORD PTR SS:[ESP+C],-1         ;  -1 是在堆栈中设定的某种标志              │
    │00412EE2  |.JNZ SHORT Reglo.00412EFD            ;  必跳                                     │
    │00412EE4  |.MOV EAX,EBX                                                                     │
    │00412EE6  |.LEA EDX,DWORD PTR DS:[EAX+1]                                                    │
    │00412EE9  |.LEA ESP,DWORD PTR SS:[ESP]                                                      │
    │00412EF0  |>/MOV CL,BYTE PTR DS:[EAX]                                                       │
    │00412EF2  |.|INC EAX                                                                        │
    │00412EF3  |.|TEST CL,CL                                                                     │
    │00412EF5  |.\JNZ SHORT Reglo.00412EF0                                                       │
    │00412EF7  |.SUB EAX,EDX                                                                     │
    │00412EF9  |.MOV DWORD PTR SS:[ESP+C],EAX                                                    │
    │00412EFD  |>MOV EAX,DWORD PTR SS:[ESP+C]                                                    │
    │00412F01  |.PUSH EBP                                                                        │
    │00412F02  |.PUSH ESI                                                                        │
    │00412F03  |.PUSH EDI                                                                        │
    │00412F04  |.XOR EBP,EBP                                                                     │
    │00412F06  |.XOR ESI,ESI                                                                     │
    │00412F08  |.XOR EDI,EDI                                                                     │
    │00412F0A  |.TEST EAX,EAX                                                                    │
    │00412F0C  |.MOV DWORD PTR SS:[ESP+C],2          ;  12F38C 设置标志 2                        │
    │00412F14  |.MOV DWORD PTR SS:[ESP+10],1         ;  12F390 设置标志 1                        │
    │00412F1C  |.JLE SHORT Reglo.00412F76            ;  一跳就死                                 │
    │00412F1E  |.MOV EDI,EDI                         ;  EDI=0                                    │
    │00412F20  |>/MOVSX EAX,BYTE PTR DS:[EDI+EBX]    ;  读入注册码第 EDI+1 位                    │
    │00412F24  |.|PUSH EAX                                                                       │
    │00412F25  |.|CALL Reglo.00415ACF                ;  判断是否为 0-9 的数字                    │
    │00412F2A  |.|ADD ESP,4                                                                      │
    │00412F2D  |.|TEST EAX,EAX                       ;  是,则 EAX=4,否,则 EAX=0                  │
    │00412F2F  |.|JE SHORT Reglo.00412F52            ;  若不是数字,就跳过本次循环                │
    │00412F31  |.|MOVSX ECX,BYTE PTR DS:[EDI+EBX]    ;  读入注册码第 EDI+1 位                    │
    │00412F35  |.|MOV EAX,DWORD PTR SS:[ESP+ESI*4+C] ;  EAX=2(ESI=0 时)或EAX=1(ESI=1 时)         │
    │00412F39  |.|SUB ECX,30                         ;  ECX=注册码第 EDI+1 位的数值              │
    │00412F3C  |.|IMUL EAX,ECX                                                                   │
    │; 奇数位用 2 乘以当前位置数字的数值,偶数位用 1 乘以当前位置数字的数值                       │
    │00412F3F  |.|CMP EAX,0A                                                                     │
    │00412F42  |.|JL SHORT Reglo.00412F47                                                        │
    │00412F44  |.|SUB EAX,9                          ;  如果 EAX>=10,则 EAX=EAX-9                │
    │00412F47  |>|XOR EDX,EDX                        ;  EBP=Sum(EAX)                             │
    │00412F49  |.|ADD EBP,EAX                                                                    │
    │00412F4B  |.|TEST ESI,ESI                                                                   │
    │00412F4D  |.|SETE DL                            ;  若 ZF=1,则 DL 置 1                       │
    │00412F50  |.|MOV ESI,EDX                        ;  ESI=0(奇数位) 或 1(偶数位)               │
    │00412F52  |>|MOV EAX,DWORD PTR SS:[ESP+18]      ;  EAX=10h=16d                              │
    │00412F56  |.|INC EDI                                                                        │
    │00412F57  |.|CMP EDI,EAX                                                                    │
    │00412F59  |.\JL SHORT Reglo.00412F20                                                        │
    │00412F5B  |.TEST EBP,EBP                                                                    │
    │00412F5D  |.JE SHORT Reglo.00412F76             ;  一跳就死                                 │
    │00412F5F  |.MOV EAX,EBP                         ;  设置被除数                               │
    │00412F61  |.CDQ                                                                             │
    │00412F62  |.IDIV DWORD PTR SS:[ESP+1C]                                                      │
    │;第 末字符数值+3 个数的数值作为除数放在堆栈 SS:[12F39C] 中                                  │
    │00412F66  |.TEST EDX,EDX                        ;  测试余数是否为 0                         │
    │00412F68  |.JNZ SHORT Reglo.00412F76            ;  一跳就死                                 │
    │00412F6A  |.POP EDI                                                                         │
    │00412F6B  |.POP ESI                                                                         │
    │00412F6C  |.MOV EAX,1                                                                       │
    │00412F71  |.POP EBP                                                                         │
    │00412F72  |.ADD ESP,8                                                                       │
    │00412F75  |.RETN                                                                            │
    │00412F76  |>POP EDI                                                                         │
    │00412F77  |.POP ESI                                                                         │
    │00412F78  |.XOR EAX,EAX                                                                     │
    │00412F7A  |.POP EBP                                                                         │
    │00412F7B  |.ADD ESP,8                                                                       │
    │00412F7E  \.RETN                                                                            │
    └──────────────────────────────────────────────┘
    这段代码比较简单,其主要过程是,在堆栈中设置两个标志 2 和 1,然后逐字读入注册码,奇数位数字*2,偶数位数字*1,当奇数位数字*2 的值大于等于 10 时,对其进行减 9 操作,将各位结果累加到 EBP 中,作为被除数.同时取出 SS:[12F39C] 中的数字作为除数,只要整除成功,就可通过测试.但值得注意的是 00412F62 处,关于除数的选择,由于该处除数位于堆栈 SS:[12F39C] 中,改变该堆栈值的操作是在 004130B3 处的 PUSH ESI 操作,而上一次改变 ESI 是在 0041302D 处,由此可知所谓的除数其实是:第 末字符数值+3 个数的数值.所以测试注册码 "7141111022022722",恰巧其第 5(2+3) 个数是 "1",所以可以整除,而最终注册成功.但对于测试注册码 "0100000000070200" 来说,其第 3(0+3) 个数是 "0",所以 Reglo 发生了除 0 操作,而崩溃了.

    至此,第五个坎也过了,软件的注册码算法已基本清晰.可以写总结了,但作为一个软件从业人员,希望提醒大家,Reglo 竟然可以通过非法注册码导致软件崩溃,说明其作者在验证注册码的算法中存在 Bug,也就是除零操作,这是一个软件开发人员应尽量避免的低级错误,希望大家在自己的软件中不要犯同样的毛病.


【破解总结】:

    从 Reglo 的注册算法中,可以明确的感受到,"末位数"的轴心作用,几乎每一步都涉及到末位数的操作,要么用作指定某位为操作位,要么用作固定某位的数值,要么用作判定某条件是否达成,这可能是 Reglo 注册算法的最大特色吧.
    对于 Reglo v3.5a 的注册码的形制从现在的观点来看,其模型可描述为:

     k        注册码模型
    k=0        0!#xx0xx 0xx7x?x0
    k=1        x1!#xx0x x0xx7x?1
    k=2        ?x4!#xx0 xx0xx7x2
    k=3        x?x9!#xx 0xx0xx73
    k=4        7x?x6!#x x0xx0xx4
    k=5        x7x?x5!# xx0xx0x5
    k=6        xx7x?x6! #xx0xx06
    k=7        0xx7x?x9 !#xx0xx7
    k=8        x0xx7x?x 4!#xx0x8
    k=9        xx0xx7x? x1!#xx09

    说明:
    k 表示末位数数值,
    x 表示 0-9 的数字,任选
    ! 位的数值必须大于等于 1
    ? 位的数值必须等于 EAX 除 10 所得的余数,其中
      @EAX=n1*0+n2*1+n3*2+n4*3+n5*4+...+n13*12+n14*13+n15*14+n16*15 (ni 表示注册码第 i 位的数值)
      当末位数为 0 时,EAX=@EAX-n15*14-n14*13
      当末位数为 1 时,EAX=@EAX-n15*14
      当末位数为 2 时,EAX=@EAX-n2*1
      当末位数为 3-9 时,EAX=@EAX-ni*(i-1)-n(i-1)*(i-2)
    # 位的数值不能为 0,且能被 SUM 整除,其中:
      SUM=2*(n1+n3+n5+n7+n9+n11+n13+n15)-9*y+(n2+n4+n6+n8+n10+n12+n14+n16)
      (y 表示:大于等于 5 的奇数位的个数)

    那么如何快速的得到一个注册码呢?
    根据上面的模型,首先选择一个末位数,不妨选 "9" 吧,其注册码模型为 "xx0xx7x? x1!#xx09",对于 x 部分可让它们为 "0",这样注册码变为 "0000070? 01!#0009",对于 # 位的数字来说,当 # = "1" 时,一定可以被整除,故令 #="1",对于 ! 位的数字来说,! 位恰好是第 11 位,该位数字乘以 10(11-1) 一定被 10 整除,故对 ? 位没有影响,所以可以令 ! 位的数字为 1-9 中的任意一个,这样注册码变为 "0000070? 01510009",就剩最后一位了,0 这么多,好办,7*(6-1)+1*(10-1)+5*(11-1)+1*(12-1)+9*(16-1)=35+9+50+11+135=240,240 Mod 10 = 0,故 ? 位为 "0",这样注册码就为 "00000700 01510009".其实上面的 # 位可以不参加运算,原因已经说过,这里写出来只是为了大家看着方便.

    当然使用注册机 KeyGen 是最方便的,不过我不想写,有兴趣的朋友可以试试---我本来的目的也不是破解.又抽空看了看 Reglo 的姊妹软件 Deletor,呵呵,注册算法很相近呀,不过把注册失败的对话框弹出方式变为了 MessageBoxW,注册码也变为了 UniCode,新手们可以用它的姊妹软件进行练手,是成长的不错选择.

    哎,终于完了!神马都是浮云!

上传的附件 Reglo.TXT