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 v3.5a 注册算法详析
- 作 者:wslvic
- 时 间:2011-07-04 09:59:38
- 链 接:http://bbs.pediy.com/showthread.php?t=136505