• 标 题:Add/Remove 4Good v2.01 注册算法分析 (18千字)
  • 作 者:炎之川
  • 时 间:2003-3-21 23:43:18
  • 链 接:http://bbs.pediy.com

Add/Remove 4Good v2.01 注册算法分析

破解目标:Add/Remove 4Good v2.01
官方主页:http://www.4developers.com/
软件简介:是一个可以帮助你管理控制台中的新增/移除程序,除了可代替反安装软件外还会连同软件的清单移除,另外也可以清除以无效的软件清单等。
下载地址:http://www.4developers.com/software/addremove.exe

使用工具:W32Dasm、Ollydbg、Windows 自带的计算器、32bit Calculator 1.6 by cybult,用来临时报佛脚的汇编课本-_-b

作者:炎之川
时间:2003.3.21
主页:http://skipli.yeah.net/

声明: 此文仅用于学习及交流,若要转载请保持文章完整。



用 PEiD 0.8 检查可知文件未加壳。用 W32Dasm 反汇编并初步分析,先在串式参考中找到注册码错误的提示,双击来到下面的代码段:

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040402F(C)
|
:00404040 E80BFDFFFF              call 00403D50  //在这里设置断点
:00404045 85C0                    test eax, eax
:00404047 0F84A8000000            je 004040F5
:0040404D 8D44240C                lea eax, dword ptr [esp+0C]
:00404051 8D4C2408                lea ecx, dword ptr [esp+08]
:00404055 33DB                    xor ebx, ebx
:00404057 50                      push eax
:00404058 51                      push ecx
:00404059 53                      push ebx
:0040405A 683F000F00              push 000F003F
:0040405F 53                      push ebx
:00404060 53                      push ebx
:00404061 53                      push ebx

* Possible Reference to Dialog: 
                                  |
:00404062 68E84B4300              push 00434BE8
:00404067 6802000080              push 80000002
:0040406C 895C2430                mov dword ptr [esp+30], ebx
:00404070 895C242C                mov dword ptr [esp+2C], ebx

* Reference To: ADVAPI32.RegCreateKeyExA, Ord:01CDh  //以下是注册成功,将注册信息写入注册表
                                  |
:00404074 FF1514904200            Call dword ptr [00429014]
:0040407A 3BC3                    cmp eax, ebx
:0040407C 7530                    jne 004040AE
:0040407E 8B542408                mov edx, dword ptr [esp+08]
:00404082 57                      push edi
:00404083 6800020000              push 00000200
:00404088 68F88F4300              push 00438FF8
:0040408D 6A03                    push 00000003
:0040408F 53                      push ebx

* Possible StringData Ref from Data Obj ->"4D"
                                  |
:00404090 68E44B4300              push 00434BE4
:00404095 52                      push edx

* Reference To: ADVAPI32.RegSetValueExA, Ord:01F9h
                                  |
:00404096 FF1510904200            Call dword ptr [00429010]
:0040409C 8BF8                    mov edi, eax
:0040409E 8B44240C                mov eax, dword ptr [esp+0C]
:004040A2 50                      push eax

* Reference To: ADVAPI32.RegCloseKey, Ord:01C9h
                                  |
:004040A3 FF1518904200            Call dword ptr [00429018]
:004040A9 3BFB                    cmp edi, ebx
:004040AB 5F                      pop edi
:004040AC 741B                    je 004040C9

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040407C(C)
|
:004040AE 53                      push ebx
:004040AF 53                      push ebx

* Possible StringData Ref from Data Obj ->"Failed to record registration "
                                        ->"information.

Please contact support@4dev.com"
                                  |
:004040B0 68D04E4300              push 00434ED0
:004040B5 E8F2F10100              call 004232AC
:004040BA 6A01                    push 00000001
:004040BC 8BCE                    mov ecx, esi
:004040BE E801940100              call 0041D4C4
:004040C3 5E                      pop esi
:004040C4 5B                      pop ebx
:004040C5 83C408                  add esp, 00000008
:004040C8 C3                      ret



* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004040AC(C)
|
:004040C9 3BF3                    cmp esi, ebx
:004040CB 7504                    jne 004040D1
:004040CD 33C0                    xor eax, eax
:004040CF EB03                    jmp 004040D4

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004040CB(C)
|
:004040D1 8B461C                  mov eax, dword ptr [esi+1C]

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004040CF(U)
|
:004040D4 53                      push ebx

* Possible Reference to Dialog: 
                                  |
:004040D5 68C04E4300              push 00434EC0

* Possible StringData Ref from Data Obj ->"Welcome to the REGISTERED version "  //注册成功的提示
                                        ->"of Add/Remove 4Good.

Please restart "
                                        ->"Add/Remove 4Good so that all limitations "
                                        ->"are removed."
                                  |
:004040DA 68404E4300              push 00434E40
:004040DF 50                      push eax

* Reference To: USER32.MessageBoxA, Ord:01DEh
                                  |
:004040E0 FF158C944200            Call dword ptr [0042948C]
:004040E6 6A01                    push 00000001
:004040E8 8BCE                    mov ecx, esi
:004040EA E8D5930100              call 0041D4C4
:004040EF 5E                      pop esi
:004040F0 5B                      pop ebx
:004040F1 83C408                  add esp, 00000008
:004040F4 C3                      ret



* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00404047(C)
|
:004040F5 6AFF                    push FFFFFFFF

* Reference To: USER32.MessageBeep, Ord:01DDh
                                  |
:004040F7 FF1594944200            Call dword ptr [00429494]
:004040FD 85F6                    test esi, esi
:004040FF 7403                    je 00404104
:00404101 8B761C                  mov esi, dword ptr [esi+1C]

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004040FF(C)
|
:00404104 6A30                    push 00000030

* Possible StringData Ref from Data Obj ->"UNREGISTERED User"
                                  |
:00404106 682C4E4300              push 00434E2C

* Possible StringData Ref from Data Obj ->"The registration information you "  //注册失败的提示
                                        ->"have entered could not be validated.
Please "
                                        ->"re-enter the registration code "
                                        ->"and user name you have legally "
                                        ->"obtained from 4Developers.

If "
                                        ->"you have not registered yet you "
                                        ->"can click one of the buttons below "
                                        ->"to see how to register Add/Remove "
                                        ->"4Good.
For help e-mail support@4dev.com"
                                  |
:0040410B 68F44C4300              push 00434CF4
:00404110 56                      push esi

* Reference To: USER32.MessageBoxA, Ord:01DEh
                                  |
:00404111 FF158C944200            Call dword ptr [0042948C]
:00404117 5E                      pop esi
:00404118 5B                      pop ebx
:00404119 83C408                  add esp, 00000008
:0040411C C3                      ret



用 Ollydbg 载入文件,在 404040 处按 F2 下断点,然后 Ctrl+F2 重新开始,F9 运行程序,在 NAG 对话框中填入注册名和假注册码,我填入:
Name: lovefire (需要大于6个字符)
S/N: 6582-78787878 (为什么要这样写?原因见下面的分析)

按 Check,被 Ollydbg 断下,
(; 后是 Ollydbg 所分析的内容,// 后是我加的注释)

00404040  > E8 0BFDFFFF    CALL AddRem.00403D50  //断在这里,关键call,F7进入
00404045  . 85C0          TEST EAX,EAX  //eax 是否为0?
00404047  . 0F84 A8000000  JE AddRem.004040F5  //为0则跳到下面注册失败的messagebox
0040404D  . 8D4424 0C      LEA EAX,DWORD PTR SS:[ESP+C]
00404051  . 8D4C24 08      LEA ECX,DWORD PTR SS:[ESP+8]
00404055  . 33DB          XOR EBX,EBX
00404057  . 50            PUSH EAX                                ; /pDisposition
00404058  . 51            PUSH ECX                                ; |pHandle
00404059  . 53            PUSH EBX                                ; |pSecurity => NULL
0040405A  . 68 3F000F00    PUSH 0F003F                              ; |Access = KEY_ALL_ACCESS
0040405F  . 53            PUSH EBX                                ; |Options => REG_OPTION_NON_VOLATILE
00404060  . 53            PUSH EBX                                ; |Class => NULL
00404061  . 53            PUSH EBX                                ; |Reserved => 0
00404062  . 68 E84B4300    PUSH AddRem.00434BE8                    ; |Subkey = "Software\4Developers\AddRemove"
00404067  . 68 02000080    PUSH 80000002                            ; |hKey = HKEY_LOCAL_MACHINE
0040406C  . 895C24 30      MOV DWORD PTR SS:[ESP+30],EBX            ; |
00404070  . 895C24 2C      MOV DWORD PTR SS:[ESP+2C],EBX            ; |
00404074  . FF15 14904200  CALL DWORD PTR DS:[<&ADVAPI32.RegCreateK>; \RegCreateKeyExA


----------------------------------------------------------------
进入 404040 的call:


00403D50  /$ 64:A1 00000000 MOV EAX,DWORD PTR FS:[0]
00403D56  |. 6A FF          PUSH -1
00403D58  |. 68 C87F4200    PUSH AddRem.00427FC8
00403D5D  |. 50            PUSH EAX
00403D5E  |. 64:8925 000000>MOV DWORD PTR FS:[0],ESP
00403D65  |. 83EC 10        SUB ESP,10
00403D68  |. 83C9 FF        OR ECX,FFFFFFFF  //ecx 或 FFFFFFFF,结果使 ecx = FFFFFFFF
00403D6B  |. 33C0          XOR EAX,EAX  //eax 清零
00403D6D  |. 53            PUSH EBX
00403D6E  |. 56            PUSH ESI
00403D6F  |. 57            PUSH EDI
00403D70  |. BF F88F4300    MOV EDI,AddRem.00438FF8                  ;  ASCII "lovefire"  //注册用户名放入 edi
00403D75  |. F2:AE          REPNE SCAS BYTE PTR ES:[EDI//扫描字串,处理其长度。这个指令最后使 ecx=FFFFFFF6(因为填入的注册用户名是8位)
00403D77  |. F7D1          NOT ECX  //ecx 逻辑非,得 00000009
00403D79  |. 49            DEC ECX  //ecx-1=00000009-1=00000008。这是用户名的长度!
//以上操作到这里终于得到了注册用户名的长度!用了这么复杂的方法,有点晕~~~

00403D7A  |. 83F9 06        CMP ECX,6  //注册名需要大于6位
00403D7D  |. 73 12          JNB SHORT AddRem.00403D91  //大于6则跳到下面,继续计算注册码
00403D7F  |. 5F            POP EDI
00403D80  |. 5E            POP ESI
00403D81  |. 5B            POP EBX
00403D82  |. 8B4C24 10      MOV ECX,DWORD PTR SS:[ESP+10]
00403D86  |. 64:890D 000000>MOV DWORD PTR FS:[0],ECX
00403D8D  |. 83C4 1C        ADD ESP,1C
00403D90  |. C3            RETN
00403D91  |> 68 084C4300    PUSH AddRem.00434C08                    ;  ASCII "6582-"  //默认的数“6582-”
00403D96  |. 8D4C24 18      LEA ECX,DWORD PTR SS:[ESP+18]
00403D9A  |. E8 2ECB0100    CALL AddRem.004208CD    //注册码头5位“6582-”
00403D9F  |. 8BF0          MOV ESI,EAX
00403DA1  |. BF F88F4300    MOV EDI,AddRem.00438FF8  //取注册名到edi
00403DA6  |. 83C9 FF        OR ECX,FFFFFFFF  //ecx 或 FFFFFFFF,最后使ecx=FFFFFFFF
00403DA9  |. 33C0          XOR EAX,EAX  //清零
00403DAB  |. 33DB          XOR EBX,EBX  //清零
00403DAD  |. F2:AE          REPNE SCAS BYTE PTR ES:[EDI//取输入的用户名第一个字符
00403DAF  |. F7D1          NOT ECX
00403DB1  |. 49            DEC ECX
00403DB2  |. 33D2          XOR EDX,EDX  //清零
00403DB4  |. 8BC1          MOV EAX,ECX  //注册名长度送 wax
00403DB6  |. B9 0C000000    MOV ECX,0C
00403DBB  |. F7F1          DIV ECX
00403DBD  |. A1 9C484300    MOV EAX,DWORD PTR DS:[43489C]  //将一个预设的数 87ae2401my69 送到 eax,下面将用到
00403DC2  |. 895C24 24      MOV DWORD PTR SS:[ESP+24],EBX
00403DC6  |. 8A0C02        MOV CL,BYTE PTR DS:[EDX+EAX//edx=8,这里取上面的预设字串中第8个字符的下一位,即“m”
00403DC9  |. 8D5424 0C      LEA EDX,DWORD PTR SS:[ESP+C]
00403DCD  |. 51            PUSH ECX
00403DCE  |. 56            PUSH ESI
00403DCF  |. 52            PUSH EDX
00403DD0  |. E8 31710100    CALL AddRem.0041AF06  //将m连接到 6582- 之后,此时得到注册码第二部分的第一个字符“m”
//以上是一个片断,通过上面的代码可以知道,软件的注册码分为两个部分,第一部分是固定的“6582-”,第二部分的第一个字符由注册用户名的位数和软件预设的一串字符来决定,注册用户名几位,则从预设字串中找出对应位数的字符。例如注册用户名七位,则取“1”,11位则取“9”,12位从头取,取“8”

00403DD5  |. 8D4C24 14      LEA ECX,DWORD PTR SS:[ESP+14]
00403DD9  |. C64424 24 02  MOV BYTE PTR SS:[ESP+24],2
00403DDE  |. E8 7CCA0100    CALL AddRem.0042085F
00403DE3  |. BF F88F4300    MOV EDI,AddRem.00438FF8                  ;  ASCII "lovefire"
00403DE8  |. 83C9 FF        OR ECX,FFFFFFFF
00403DEB  |. 33C0          XOR EAX,EAX
00403DED  |. F2:AE          REPNE SCAS BYTE PTR ES:[EDI]
00403DEF  |. F7D1          NOT ECX
00403DF1  |. 49            DEC ECX
00403DF2  |. 0F84 8E000000  JE AddRem.00403E86

00403DF8  |> F6C3 01        /TEST BL,1  //test BL,1 即 BL and 1
00403DFB  |. 75 71          |JNZ SHORT AddRem.00403E6E  //如果等于0,则结束循环,跳到 403E6E
//因为 test BL,1 即 BL and 1,所以当取注册名的双数位时,BL and 1 的结果为0,直接跳过循环。
//即软件 *只取注册名的单数位字符* 参与计算

//下面是计算注册码的过程
00403DFD  |. 8A83 F88F4300  |MOV AL,BYTE PTR DS:[EBX+438FF8]  //第一位注册名送 al
00403E03  |. 3C 7F          |CMP AL,7F  //al与 7F 比较
00403E05  |. 0F8F BF000000  |JG AddRem.00403ECA  //小于方可继续
00403E0B  |. 3C 20          |CMP AL,20  //al与 20 比较
00403E0D  |. 0F8C B7000000  |JL AddRem.00403ECA  //大于方可继续
//以上比较,是看注册用户名是否位于ASCII表中的非控制字符部分,即注册用户名只能是英文字母、数字和可打印字符。

00403E13  |. 0FBEC0        |MOVSX EAX,AL  //al送eax,此时al中是注册名的第一位字符,以第一位注册名字符“l”为例,此时 eax=6C
00403E16  |. 99            |CDQ  //这里将edx清零(不过这个是什么指令?好像没有见过…)
00403E17  |. 2BC2          |SUB EAX,EDX  //eax= eax-edx = 6C-0 = 6C
00403E19  |. D1F8          |SAR EAX,1  //算术右移1位,等于一次除2运算,所以 eax= eax/2 = 6C/2 =36
00403E1B  |. 04 20          |ADD AL,20  //al= al+20 = 36+20 =56(即“V”)

//以下将求得的值作一系列的比较
00403E1D  |. 3C 5A          |CMP AL,5A  //al 与 5A(Z)比较
00403E1F  |. 884424 10      |MOV BYTE PTR SS:[ESP+10],AL
00403E23  |. 7E 0A          |JLE SHORT AddRem.00403E2F  //小于则跳转到403E2F
00403E25  |. 3C 61          |CMP AL,61  //如果 al 大于 5A,则到此处,继续与 61 比较
00403E27  |. 7D 06          |JGE SHORT AddRem.00403E2F  //大于等于 61 则跳转
00403E29  |. 04 06          |ADD AL,6  //如果al小于61,则 al+6
00403E2B  |. 884424 10      |MOV BYTE PTR SS:[ESP+10],AL
00403E2F  |> 3C 39          |CMP AL,39  //如果al小于5A,则跳到此处与 39 比较
00403E31  |. 7E 0A          |JLE SHORT AddRem.00403E3D  //不大于39,则跳出比较循环
00403E33  |. 3C 41          |CMP AL,41  //如果小于 39,则继续于 41 比较
00403E35  |. 7D 06          |JGE SHORT AddRem.00403E3D  //大于 41 则跳出比较循环
00403E37  |. 04 08          |ADD AL,8  //如果 al 小于 41,则 al+8
//经过以上循环及比较,最后得到注册码第二部分的第二位字符“V”
//以上循环即是计算注册码的过程
00403E39  |. 884424 10      |MOV BYTE PTR SS:[ESP+10],AL
00403E3D  |> 8B4424 10      |MOV EAX,DWORD PTR SS:[ESP+10]
00403E41  |. 8D4C24 0C      |LEA ECX,DWORD PTR SS:[ESP+C]
00403E45  |. 50            |PUSH EAX
00403E46  |. 8D5424 1C      |LEA EDX,DWORD PTR SS:[ESP+1C]
00403E4A  |. 51            |PUSH ECX
00403E4B  |. 52            |PUSH EDX
00403E4C  |. E8 B5700100    |CALL AddRem.0041AF06  //将算出的“V”加到之前算出的部分注册码之后,得“6582-mV”
00403E51  |. 50            |PUSH EAX
00403E52  |. 8D4C24 10      |LEA ECX,DWORD PTR SS:[ESP+10]
00403E56  |. C64424 28 03  |MOV BYTE PTR SS:[ESP+28],3
00403E5B  |. E8 38CB0100    |CALL AddRem.00420998
00403E60  |. 8D4C24 18      |LEA ECX,DWORD PTR SS:[ESP+18]
00403E64  |. C64424 24 02  |MOV BYTE PTR SS:[ESP+24],2
00403E69  |. E8 F1C90100    |CALL AddRem.0042085F
00403E6E  |> BF F88F4300    |MOV EDI,AddRem.00438FF8                ;  ASCII "lovefire"
00403E73  |. 83C9 FF        |OR ECX,FFFFFFFF
00403E76  |. 33C0          |XOR EAX,EAX
00403E78  |. 43            |INC EBX
00403E79  |. F2:AE          |REPNE SCAS BYTE PTR ES:[EDI]
00403E7B  |. F7D1          |NOT ECX
00403E7D  |. 49            |DEC ECX  //以上作为记数器
00403E7E  |. 3BD9          |CMP EBX,ECX 
00403E80  |.^0F82 72FFFFFF  \JB AddRem.00403DF8  //跳回 403DF8 开始新的一次循环。


算法可以总结如下:
1、注册名只允许是位于 ASCII 码表 20~7E 之间的字符。(所以也不支持中文)
2、软件的注册码分为两个部分,第一部分是固定的字符“6582-”;第二部分的第一个字符由注册名的位数和一个预设的字串“87ae2401my69”决定,若注册名有N位(N>6),则取字串的N+1位字符,循环取。
3、注册码第二部分剩下的字符,由输入的注册名的单数位字符决定,其计算如下:

设 SN1 为通过注册名字符初步计算出来的值,SN 为正确的注册码字符,则:
SN1=((注册名单数位的字符的ASCII值-0)/2)+20

a) 若 SN1<5A,SN1<39,则 SN = SN1
b) 若 SN1<5A,SN1>39,SN1>41,则 SN = SN1
c) 若 SN1<5A,SN1>39,SN1<41,则 SN = SN1+8
d) 若 SN1>5A,SN1<61,则 SN = SN1+6
e) 若 SN1>5A,SN1>61,则 SN = SN1(此项可排除)

又因为注册用户名介于[20,7F),所以 SN1 取值范围只能是[30,5F],画个简单的数轴就可以看出:
SN1 介于 [30,39] 与 [41,5A] 时,SN = SN1
SN1 介于 [39,41] 时,SN = SN1+8
SN1 介于 [5A,5F] 时,SN = SN1+6

反推回注册用户名可知:
在 ASCII 码表中,当用户名的值位于:
20~32 时,SN =((注册名字符的ASCII值-0)/2)+20
33~42 时,SN =(((注册名字符的ASCII值-0)/2)+20)+8
43~74 时,SN =(((注册名字符的ASCII值-0)/2)+20)
75~7E 时,SN =(((注册名字符的ASCII值-0)/2)+20)+6

为了证实以上算法,我们手工构造一个注册用户名,使之符合以上范围限制:+060S0z (双数位无作用,随便取)
+  0  6  0  S  0  z (字符)
2B    36      53      7A (ASCII 值)

则:
+: ((2B-0)/2)+20 = 35 = 5
6: (((36-0)/2)+20)+8 = 43 = C
S: ((53-0)/2)+20 = 49 = I
z: (((7A-0)/2)+20)+6 = 63 = c

再加上注册名7位,取“87ae2401my69”的第7+1位数“1”,最后构成注册码“6582-15CIc”,测试一下,注册成功!

注册信息保存在 HKEY_LOCAL_MACHINE\SOFTWARE\4Developers\AddRemove 下的“4D”子键中。

再往下是软件将输入的注册码和计算出的注册码做对比,与算法无关,所以这里不再详细分析。

至此算法分析完成,一个可用的注册码:Name: lovefire  S/N: 6582-mVaSY

最后特别感谢小楼兄的指点:)