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,新手们可以用它的姊妹软件进行练手,是成长的不错选择. 哎,终于完了!神马都是浮云!