【文章标题】: Atlantis Word Processor V1.6.5算法分析及注册机的实现
【文章作者】: playboysen
【作者邮箱】: playboysen&126.com
【软件大小】: 3.65Mb
【下载地址】: http://www.atlantiswordprocessor.com/
【保护方式】: 注册码
【使用工具】: OD、Atlantis Word Processor
【版权声明】: 版权所有,转载注明!新手参考,老鸟飞过~
【作者声明】: 本文仅作个人学习研究之用,以探讨程序逆向方法为目的。如对软件本身有兴趣,请支持正版!
-----------------------------------------------------------------------------------------------------------

   Atlantis Word Processor 一个独立的word处理器,支持读取并编辑*.rtf *.doc *.docx *.cod *.txt等数种格式的文档,支持命令行,软件小巧界面清爽,占用内存小,处理速度快,打开保存文件时支持全word格式的预览。(两个小小遗憾--word文档中的中文引号会显示乱码;竟不支持中文txt文件,汗。期待更新)
   试运行得知软件以用户名、注册码方式保护(其实用户名不参与运算),有出错提示,UPX弱壳。
   脱壳OD加载后,查找字符串“invalid”找到以下关键部位,以用户名playboysen,注册码HelloHenu试炼(算法分析一般都是随便输入假码后粗略跟踪几次注册码的验证流程,根据验证流程一次次地修正假码,最终得出一组可用注册码,弄清算法)
   
00495FC7    E8 9CC31600       call Atlantis.00602368
00495FCC    837D FC 00        cmp dword ptr ss:[ebp-4],0
00495FD0    75 17             jnz short Atlantis.00495FE9
00495FD2    B8 88604900       mov eax,Atlantis.00496088          ; ASCII "Please enter your registration code."
00495FD7    E8 28E31400       call Atlantis.005E4304
00495FDC    8B86 9C010000     mov eax,dword ptr ds:[esi+19C]
00495FE2    8B10              mov edx,dword ptr ds:[eax]
00495FE4    FF52 68           call dword ptr ds:[edx+68]
00495FE7    EB 45             jmp short Atlantis.0049602E
00495FE9    8D55 FC           lea edx,dword ptr ss:[ebp-4]
00495FEC    8B86 9C010000     mov eax,dword ptr ds:[esi+19C]
00495FF2    E8 71C31600       call Atlantis.00602368             ; 算出注册码长度
00495FF7    8B45 FC           mov eax,dword ptr ss:[ebp-4]       ; 假码放入eax
00495FFA    E8 0D060000       call Atlantis.0049660C             ; 关键处,跟进
00495FFF    84C0              test al,al
00496001    75 17             jnz short Atlantis.0049601A
00496003    B8 B8604900       mov eax,Atlantis.004960B8          ; ASCII "The registration code you have specified is invalid..."

跟进00495FFA关键call
......
00496640    8B45 FC           mov eax,dword ptr ss:[ebp-4]
00496643    E8 4C5AF7FF       call Atlantis.0040C094             ; 注册码第一次变形,把假码中的小写字母转换成大写
00496648    8B45 F0           mov eax,dword ptr ss:[ebp-10]      ; 转换后的假码放入
0049664B    B1 30             mov cl,30                          ; 数字“0”
0049664D    B2 4F             mov dl,4F                          ; 大写字母“O”
0049664F    E8 CC711400       call Atlantis.005DD820             ; 看上面入栈的参数,猜测这个函数里面有猫腻儿
00496654    8B55 F4           mov edx,dword ptr ss:[ebp-C]       ; 上面的函数就是注册码的第二次变形了

数字“0”、大写字符“O”和假码同时入栈,葫芦里卖的什么药?进去遛遛
......
005DD83C    E8 4B5DE2FF       call Atlantis.0040358C             ; 算出注册码长度
005DD841    83F8 01           cmp eax,1
005DD844    7C 1D             jl short Atlantis.005DD863
005DD846    89C7              mov edi,eax                        ; 注册码长度放入
005DD848    8B06              mov eax,dword ptr ds:[esi]         ; 注册码放入
005DD84A    3A5C38 FF         cmp bl,byte ptr ds:[eax+edi-1]     ; 假码从后往前逐位与大写字母“O”比较
005DD84E    75 0E             jnz short Atlantis.005DD85E        ; 一路小跑过去,发现这里是把注册码中出现的字母“O”替换成数字“0”
005DD850    8BC6              mov eax,esi                        ; 企图混淆视听啊
005DD852    E8 055FE2FF       call Atlantis.0040375C

汗,这都行?至此,我们的假码经过了两次加工,记住
......
0049665F    8B45 FC           mov eax,dword ptr ss:[ebp-4]
00496662    E8 25CFF6FF       call Atlantis.0040358C             ; 算出注册码长度
00496667    8BF0              mov esi,eax
00496669    83FE 01           cmp esi,1
0049666C    7C 3D             jl short Atlantis.004966AB
0049666E    8B45 FC           mov eax,dword ptr ss:[ebp-4]       ; 经过以上两次加工后的变形假码放入EAX
00496671    8A4430 FF         mov al,byte ptr ds:[eax+esi-1]     ; 注册码从后往前依次参与运算
00496675    8BD0              mov edx,eax
00496677    80EA 20           sub dl,20                          ; 逐位比较是不是空格
0049667A    74 05             je short Atlantis.00496681
0049667C    80EA 0D           sub dl,0D                          ; 逐位比较是不是连字符“-”
0049667F    75 11             jnz short Atlantis.00496692
00496681    8D45 FC           lea eax,dword ptr ss:[ebp-4]
00496684    B9 01000000       mov ecx,1
00496689    8BD6              mov edx,esi
0049668B    E8 40D1F6FF       call Atlantis.004037D0             ; 去除注册码中的“-”
00496690    EB 14             jmp short Atlantis.004966A6
00496692    8B55 FC           mov edx,dword ptr ss:[ebp-4]       ; 假码放入edx
00496695    25 FF000000       and eax,0FF
0049669A    8B15 4C7E6100     mov edx,dword ptr ds:[617E4C]      ; Atlantis.0061CC30
004966A0    803C02 FF         cmp byte ptr ds:[edx+eax],0FF      ; 此时eax的值就是假码中每一位字母或数字的十六进制值
004966A4    74 77             je short Atlantis.0049671D         ; 注册码中的每一位只能是 数字或a~f或A~F或字母o/O(不明白看下面的分析)
004966A6    4E                dec esi
004966A7    85F6              test esi,esi
004966A9  ^ 75 C3             jnz short Atlantis.0049666E
004966AB    8B45 FC           mov eax,dword ptr ss:[ebp-4]       ; 无连字符“-”的大写注册码放入
004966AE    E8 D9CEF6FF       call Atlantis.0040358C             ; 求经过三次变形后的假码长度
004966B3    83F8 10           cmp eax,10                         ; 注册码16位啊
004966B6    75 65             jnz short Atlantis.0049671D

上述代码的后半部分理解有些费力
0049669A    8B15 4C7E6100     mov edx,dword ptr ds:[617E4C]      ; Atlantis.0061CC30
[617E4C]指向的地址是0061CC30,我们看一下数据窗口对应位置的数值

醒目一点的估计看出来了,截图上的地址是从0061CC60开始的,为什么呢?
0049669A    8B15 4C7E6100     mov edx,dword ptr ds:[617E4C]      ; Atlantis.0061CC30
004966A0    803C02 FF         cmp byte ptr ds:[edx+eax],0FF      ; 此时eax的值就是假码中每一位字母或数字对应的十六进制值
问题就出在第二句的[edx+eax]处,静态解释不易理解,动态跟踪一下就明白了
假设假码中其中一位字符是“8” 对应十六进制是 0x38
而edx == 0061CC30,所以[edx+eax] == 0061CC68 这个地址对应上图可知值为0x08,并不等于0xFF,证明假码中的这一位合格

好,跟踪至此,我们的假码可以进行修正了
HelloHenu --> Babe-5214-0987-acef
以上都是一些准备工作,下面就真的进入了算法核心了
……
004966B8    BE 01000000       mov esi,1                          ; 核心算法开始了  esi初始值为1
004966BD    8BC6              mov eax,esi                        ; 一个大的算法循环 开始处
004966BF    48                dec eax
004966C0    03C0              add eax,eax
004966C2    40                inc eax
004966C3    8B55 FC           mov edx,dword ptr ss:[ebp-4]       ; 几次变形后的注册码放入
004966C6    0FB65402 FF       movzx edx,byte ptr ds:[edx+eax-1]  ; 注册码从前往后依次运算
004966CB    8B0D 4C7E6100     mov ecx,dword ptr ds:[617E4C]      ; Atlantis.0061CC30
004966D1    8A1411            mov dl,byte ptr ds:[ecx+edx]
004966D4    C1E2 04           shl edx,4
004966D7    8B4D FC           mov ecx,dword ptr ss:[ebp-4]
004966DA    0FB60401          movzx eax,byte ptr ds:[ecx+eax]
004966DE    8B0D 4C7E6100     mov ecx,dword ptr ds:[617E4C]      ; Atlantis.0061CC30
004966E4    021401            add dl,byte ptr ds:[ecx+eax]
004966E7    8855 FA           mov byte ptr ss:[ebp-6],dl         ; 可以看出,从前往后依次取出注册码的每两位进行下面的运算
004966EA    B8 58674900       mov eax,Atlantis.00496758          ; 00496758地址处有段8位的密钥参与运算
004966EF    8A4430 FF         mov al,byte ptr ds:[eax+esi-1]
004966F3    C645 F9 01        mov byte ptr ss:[ebp-7],1          ; (这里 标志位哦)
004966F7    B3 08             mov bl,8
004966F9    E8 0AFFFFFF       call Atlantis.00496608             ; 把密钥的对应位数值做一次ror(循环右移)运算
004966FE    3A45 FA           cmp al,byte ptr ss:[ebp-6]         ; 比较置换后的对应位注册码值是否等于ror后的密钥值
00496701    75 06             jnz short Atlantis.00496709        ; 事实证明,如果不等于,最终就提示注册码错误
00496703    C645 F9 00        mov byte ptr ss:[ebp-7],0
00496707    EB 04             jmp short Atlantis.0049670D
00496709    FECB              dec bl                             ; 对应密钥可以循环8次ror运算,共产生8个值
0049670B  ^ 75 EC             jnz short Atlantis.004966F9        ; 每次对应取出的两位注册码值必须等于上述8个结果的其中一个

其实,软件核心的算法就是:
1、  注册码应为16位,有没有“-”连接都可以
2、  注册码中的每一位只能是 0~9或a~f或A~F或字母o/O
3、  从前往后每次取出2位注册码值,转换成一个字节的十六进制值
如字符“a5c3” 就转换成 0xA5 0xC3
这样十六位的注册码,就变成了占8字节的一串十六进制值
4、  程序预置8位密钥,如下

5、  每位密钥都进行 至少一次的ror运算,把第一次ror后得出的值与相应位置的注册码值比较,如果不相等则在第一次算出值的基础上进行第二次的ror运算,依次循环,共可循环8次--如果还不相等,就说明是错误注册码了,不再继续运算

可以004966FE处为切入点,其中al的值就是密钥ror运算出的值--可用的注册码的其中两个值。在004966FE处下断,修改00496701处的jnz跳转为NOP,F9继续运算可以很轻易地跟出可用注册码
004966FE    3A45 FA           cmp al,byte ptr ss:[ebp-6]         ; 比较置换后的对应位注册码值是否等于ror后的密钥值

随便F9了几下
总结了几个类似A7B1-A6B4-2732-E1F2  7A1B6A4B72231E2F这样的值,不知道有什么用o_0
软件注册后,相关信息保存于注册表以下位置(软件没有对关键信息加密,也是一个败笔):
Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Rising Sun Solutions, Inc.\Atlantis.1_x\RegInfo]
"RegCode"="XXXXXXXXXXXXXXXX"
"RegTo"="playboysen"


总结:
看算法的分析有很多,但最主要的验证算法只有以下几行
Mov esi,1
mov eax,00496758               //这里是预置的一个8位数组,具体参考上图
mov al,byte ptr ds:[eax+esi-1]
mov bl,8
004966F9:
Ror al,1
cmp al,byte ptr ss:[ebp-6]        //在这里添加相应代码取出al中的值转换后连接成16位的字符串即为一组注册码
dec bl
jnz  004966F9
inc esi
cmp esi,9
jz XXXXX

其实,上周就开始分析这个程序了,一路小跑几次觉得算法不难,但很繁琐,没有耐心看,搁置一周。偶尔看到kanxue十天前留下的信息,感动老大还记得我辈之余顿悟自己今年确实堕落了,莫说文章之类,就连论坛都鲜有登陆,惭愧!新入职,一切都不稳定,有些心浮气躁。
离开几个月回来发现大家都跑去搞VM、驱动之类去了,牛X新壳如雨后春笋层出不穷,自己竟成了古董。唏嘘不已,感慨IT业发展之迅猛。也鲜有大虾写些算法分析之流的东西,自己也惧怕贻笑大方。快过年了,写篇文章凑个数,算是给自己一个交代。

补充注册机代码:

代码:
.data

szFormat      db '%s-%s-%s-%s',0
error        db 'Please input your name!',0
Temp           db 4Fh,63h,4Dh,69h,4Eh,64h,0C3h,0E5h


.data?

hInstance      dd ?

S1        db 8 dup(?)
S2        db 8 dup(?)
S3        db 8 dup(?)
S4        db 8 dup(?)
S5        db 8 dup(?)
S6        db 8 dup(?)
S7        db 8 dup(?)
S8        db 8 dup(?)
Serial      db 8 dup(?)
;注意此处不能定义成4字节,必须留5字节以上的空间,因为字符最后有一个'\0'
FinalSn1      db 5 dup(?)
FinalSn2      db 5 dup(?)
FinalSn3      db 5 dup(?)
FinalSn4      db 5 dup(?)
Sn            db 20 dup(?)
FinalSn      db 20 dup(?)

算了,代码太长 直接发Radasm工程文件吧
上传的附件 Atlantis.rar