• 标 题:破解用ASProtect v1.2x加壳后程序使用限制的通用方法 (15千字)
  • 作 者:Sam.com
  • 时 间:2002-4-27 6:25:57
  • 链 接:http://bbs.pediy.com

主要目标:
    不脱壳的情况下解除Delphi Application Peeper Pro v2.3.1.9的时间限制,并找到通用的方法(有待验证).

程序简介:
    主要用来查看Borland Delphi (versions 2-6) and Borland C++ Builder (versions 1-5)程序的窗口,控件信息,这个版本已经可以查看加壳了的程序,可能还有其它实用功能.

使用工具:
    Trw2000,SuperBPM,ASProtect v1.2注册版,纸&笔

    此软件分Pro版和Lite版,Pro是完整版,但要注册,程序用ASProtect 1.22加壳(Fi 2.49报告),注册方式完全采用ASProtect加壳时的30天试用选项,不注册会显示NAG提示剩下天数,过期后也能使用,但程序会不定时出现过期窗口.此软件已经由fwnl脱过壳,论坛精华里有,脱壳后程序同时去掉时间保护,因为ASProtect的注册保护都是在壳里的,但程序脱壳后热键Ctrl同时失去,About菜单也不能显示.

    刚开始破解时我没想到它的注册是由ASProtect来保护的,因为ASProtect 1.2的过期对话框只是一个简单的MessageBox,于是先去找Keyfile保护,再找注册表,经过连日苦战才找到重点,因为本人水平有限,请大家指正!

    我们先来看看用ASProtect加壳时的一些保护选项,主要有:天数限制,次数限制,未注册提示等,另外有个注册表选项,可以生成注册名对应的注册码,可选注册码的保存地址,生成注册码应该是用RSA的算法,随机生成一些数来参与运算,以保证每个程序不会重复.它在注册表里的保存的信息只有两个,一个是保存时间信息,一个是保存注册码,用ASProtect加壳后的程序在第一次运行时会生成保存时间的键值,保存注册码的键值要用户加入,程序第一次运行先检查注册码,如果没有就读时间信息,经过复杂运算得出剩下时间.所以我们的破解要从这两个地方入手.那怎么找到它们呢,对于注册码的保存就比较好找,因为ASProtect在加壳时注册码保存的地方有限制,它一定要保存在HKEY_CURRENT_USER\SOFTWARE\下,而且是保存在字符串值"Key"里的,所以用RegMon很容易就能找到.而时间标志的保存就没这么好找了,且来看看我是怎样找到的.

    先用SuperBPM,用TRW载入主程序DAP_Pro.exe,下bpx regqueryvalueexa,F5,程序被拦下,F12退出系统CALL以后下d edi看看程序读了什么内容,其中被我看到了53321-005-6928712-14614,这个看来是有用的东西,先到注册表里查找一下发现原来是我的ProductId,那就bpm来看看程序在干什么,程序先是用它做了个标准的MD5算法,再经过复杂的算法得到一个数据:AC 19 BC F0 4F F0 E7 29 6B 18 DB 35 EB 92 D5 F8,然后这串数据经过几个Call后得到了一个字符串"F0BC19AC-F04F-29E7-186B-35DBF8D592EB",好了,这个我还以为是注册码,谁知它最后变成了...CLSID\{.....}的字符串,呵呵,这不就是注册表里的键值吗,果然程序跟着就以此键值读取,中断在我下的断点里,读出键值"ltwZlD7mov/qsLc3",我们再下bpm设断,经过漫长的运算,发现这个键值算出的是现在的日期和安装日期,程序拿数据一比较,就得出了使用的天数.如果将此键值删除你会发现程序又可以使用30天了,当然这个主键你也可以用RegMon来找,只要找到以上形式的字符串就对了,另外过期后的形式类似这样:V4UTu+16

    来个总结:受保护的程序先用ProductId(这个每台机都不会一样)来算出一组数,将此数转成字符,用它做为保存时间标志的主键,此主键保存在HKEY_LOCAL_MACHINE\SOFTWARE\CLASSES\CLSID\下,不好找,但可以用RegMon
找再以主键里的内容来判断.其实这里对于这次的目的来说已经是错误的了,因为程序只有没找到注册码或注册码错误才会运行到这里来的,这里我浪费了太多时间,但至少知道了ASProtect保护的时间标志是如何产生的.

    既然我们知道注册码保存地址的限制,那么用RegMon很容易就找到程序在读HKEY_CURRENT_USER\Software\SecondDream\DAP_Pro这个主键,我们自己建立一个,并在里面建一个字符串值Key,为了保证调试过程顺利,我用ASProtect生成了一个长度和形式都应该正确的注册码填入,当然这个注册码对于DAP_Pro来说是假的,用TRW载入程序下bpx regqueryvalueexa,很快发现程序用假注册码查表得到了一组数据,经过运算后得到一个128位的数据,然后再与其它两个128位的数据运算,然后......我就晕掉了,搞不懂程序的判断在何处,它的算法太复杂了,我只能跟着它运算的结果慢慢找!!

    差点就放弃了,抱着试一试的态度决定调试一下ASProtect 1.2是怎样生成注册码和如何验证注册码的,看看能不能找到一点线索.当然为了方便观察内存里的数据我用了和刚才一样的注册名Sam.com生成了同样的注册码,然后发现程序是这样验证注册码的,下面是ASProtect的代码:

==Part I====================================================
0187:00449268  LEA      ESI,[EBP+FFFFD800]
0187:0044926E  INC      ESI
0187:0044926F  MOV      EDI,ESI
0187:00449271  PUSH    EDI
0187:00449272  MOV      EAX,[EBX+04]
0187:00449275  PUSH    EAX
0187:00449276  MOV      EAX,[EBX+08]
0187:00449279  PUSH    EAX
0187:0044927A  PUSH    EDI
0187:0044927B  CALL    00448894    <---变形后数据的运算--重点
0187:00449280  MOV      EAX,[EBX]  <---位数80h
0187:00449282  MOV      AL,[EDI+EAX-01]  <---结果的最后一位
0187:00449286  MOV      EDX,[EBX]
0187:00449288  MOV      CL,[EDI]        <---结果的第一位
0187:0044928A  MOV      [EDI+EDX-01],CL  <---放到最后
0187:0044928E  MOV      [EDI],AL        <---最后一位放到第一
0187:00449290  MOV      EAX,[EBP+FFFFD7FC]  <---结果首地址
0187:00449296  MOV      AX,[EAX]
0187:00449299  CMP      AX,[00462814]    <---[00462814]==0
0187:004492A0  JZ      004492B9        <---跳就对啦

运算的结果:
0030:006CCFC5 00 00 02 00 2B 00 01 00-02 00 01 00 10 00 07 00 ....+...........
0030:006CCFD5 53 61 6D 2E 63 6F 6D 20-00 10 00 5C 58 84 9D 8A Sam.com ...\X劃?
0030:006CCFE5 EB 7E 26 E8 D3 76 57 9C-16 33 25 FF FF 00 00 00 雫&栌vW?3%...
.....后面全0共128位

总结:
    如果在ASProtect中生成注册码时没有输入Hardware ID的话最后结果我的注册名是明码显示的,经过分析它的内容是这样的:数据前两位必须为0,最后一位也为0,第三个字节应该是标志,第5个字节2B表示后面有多少个字节的有用数据,有用数据以FF FF结尾,第15字节7表示注册名的个数,注册名后的20应该是注册名结束的标志,再后面的数据应该是提供给程序用的,后面还要用到这16个数做运算,但用处何在我未搞清楚,DAP_Pro破解的不完全也就栽倒在这些数里,以上结果里的2个10应该是数据的开始标志.再接着往下看你会发现程序在复制我的注册名和16个字节的数据,如果这时你将内存里的Sam.com改成Sam Von,ASProtect会显示对应此注册码的注册名是Sam Von,那么我们可以基本确定这就是最终结果了.到此我有一个想法,如果在DAP_Pro中,等程序运算完后我就它的结果改成以上结果会怎样呢?结果请看后部分.另外如果在生成注册码时输入了Hardware ID的话,以上结果不会看到明码的注册名,数据也有变化,但ASProtect在这部分就做得比较简单,它只是用Hardware ID与上面的数据做xor运算而已.
    我们先进入上面的Call看看它是怎样运算的:

0187:00448894  PUSH    EBP
0187:00448895  MOV      EBP,ESP
0187:00448897  PUSH    EBX
0187:00448898  PUSH    ECX
0187:00448899  PUSH    EDX
0187:0044889A  PUSH    ESI
0187:0044889B  PUSH    EDI
0187:0044889C  CLD   
0187:0044889D  MOV      ESI,[EBP+08]  <---注册码的变形
0187:004488A0  LEA      EDI,[00465B48]
0187:004488A6  MOV      ECX,80
0187:004488AB  SHR      ECX,02
0187:004488AE  MOV      EDX,ECX
0187:004488B0  REP MOVSD
0187:004488B2  MOV      ESI,[EBP+0C] <---数据1
0187:004488B5  LEA      EDI,[00465AC8]
0187:004488BB  MOV      ECX,EDX
0187:004488BD  REP MOVSD
0187:004488BF  MOV      ESI,[EBP+10] <---数据2
0187:004488C2  LEA      EDI,[00465BC8]
0187:004488C8  MOV      ECX,EDX
0187:004488CA  REP MOVSD
0187:004488CC  LEA      ESI,[00465B48]<---注册码的变形
0187:004488D2  LEA      EDI,[00465C48]
0187:004488D8  MOV      ECX,EDX
0187:004488DA  REP MOVSD
0187:004488DC  MOV      [00465DDC],EDX
0187:004488E2  MOV      EAX,[00465AC8]

下面复杂的算法就不贴了,这里我们看到ASProtect将这3个数据连在一起去运算,它以这样的形式保存:

数据1
注册码变形
数据2
注册码变形

共512位,用它们来运算得到以上结果,如果你在上面的44889D处设一个断点,再按生成注册码,你会发现程序断下了,看esi的内容,原来注册码的变形变成了Part I里的结果,也就是说生成注册码和验证注册码用到了同一个CALL,按F12退出来到下面的地方:

==Part II===============================================
0187:00449129  CALL    00448894
0187:0044912E  MOV      ECX,[EBP-0C]
0187:00449131  MOV      ECX,[ECX]
0187:00449133  LEA      EDX,[EBP+FFFFD7C4] <---edx指向结果
0187:00449139  MOV      EAX,[EBP-08]
0187:0044913C  CALL    004052AC
0187:00449141  MOV      EAX,[EBP-10]
0187:00449144  POP      EDI
0187:00449145  POP      ESI
0187:00449146  POP      EBX
0187:00449147  MOV      ESP,EBP
0187:00449149  POP      EBP
0187:0044914A  RET   


得到:
0030:006CA7D9 E2 D5 91 A8 1A 50 51 3A-58 7A 15 8D 3A 5D F6 6A 庹懆.PQ:Xz.?]鰆
0030:006CA7E9 63 96 BA C8 6B 49 81 EA-F4 B2 A4 FD CB F8 BB 76 c柡萲I侁舨锁籿
0030:006CA7F9 7B 85 D2 3C 8C 5B F7 6A-9F 86 C7 F6 B7 86 CA AB {呉<孾鱦焼泅穯诗
......共128位

    这个结果好眼熟呀,原来这个就是注册码的变形,在DAP_Pro我也看到过,程序后面会用此数据查表得到注册码.好了,原来它生成注册码不是直接用用户名的,程序之前就用注册名做了一些运算,Part I里的结果就是算注册码的数据,它们都用了同一个CALL,两个结果能否可逆呢?如果我找到DAP_Pro里的两个数据,将它复制到这里来,生成的注册码不就是正确的吗?于是我做了个试验,在生成注册码时我将数据1里的一个字节修改,生成另外一个注册码,在验证此注册码时同样修改数据1里的同一字节,结果....."错误的注册码",呵呵,看来我的想法是行不通的,程序应该还有其它的数据参与运算,这部分我就没精力再追了,这么强劲的算法应该是不可逆的.

    既然软的不行我们就来硬的,我刚才讲过,如果在加壳的程序验证注册码算出结果后将内存里的结果改成Part I里的结果会怎样呢,因为它可以算是一个正确的结果呀,于是用了一些小程序来做试验,结果是完全可以的,它的判断很简单,只要结果里的格式符合标准就行了,后面那16个数据对一般程序一点影响也没有,程序就和已经注册的一模一样,这就是我所说的通用的方法.现在我们回到DAP_Pro中,方法是可行的,问题是如何找到Part I中的代码,因为算法CALL后的一大段代码都是比较用的,还必须跳到很远的地方,也就是说我们有足够的地方来利用,我的想法是做一个Loader.

    下面是DAP_Pro的代码,和Part I是否很相似,其它凡用ASProtect保护的程序都会有这一段,要注意的是因为这一段是壳解压缩后产生的,每台机都不一样,但同一台机第次运行都是一样的,这一段是不在任何领空的.

0187:0114EA2A 8B4304          MOV      EAX,[EBX+04]
0187:0114EA2D 8A4407FF        MOV      AL,[EDI+EAX-01]
0187:0114EA31 8B5304          MOV      EDX,[EBX+04]
0187:0114EA34 8A0F            MOV      CL,[EDI]
0187:0114EA36 884C17FF        MOV      [EDI+EDX-01],CL
0187:0114EA3A 8807            MOV      [EDI],AL
0187:0114EA3C 8B85FC6FFEFF    MOV      EAX,[EBP+FFFE6FFC]
0187:0114EA42 668B00          MOV      AX,[EAX]
0187:0114EA45 663B0530231501  CMP      AX,[01152330]
0187:0114EA4C 7417            JZ      0114EA65  <---必须跳
0187:0114EA4E C785F06FFEFF02000000 MOV      DWORD [EBP+FFFE6FF0],02
0187:0114EA58 33C0            XOR      EAX,EAX
0187:0114EA5A 5A              POP      EDX
0187:0114EA5B 59              POP      ECX
0187:0114EA5C 59              POP      ECX
0187:0114EA5D 648910          MOV      [FS:EAX],EDX
0187:0114EA60 E976010000      JMP      0114EBDB
0187:0114EA65 8B8DF46FFEFF    MOV      ECX,[EBP+FFFE6FF4]
0187:0114EA6B 49              DEC      ECX
0187:0114EA6C 8B7B04          MOV      EDI,[EBX+04]
0187:0114EA6F 3BCF            CMP      ECX,EDI
0187:0114EA71 7E25            JNG      0114EA98 <---一定跳,就是说我们至少有
0187:0114EA73 8D0431          LEA      EAX,[ECX+ESI]  114EA2A<->114EA98这一段
0187:0114EA76 8BF0            MOV      ESI,EAX        是可以拿来Patch的
0187:0114EA78 8BC1            MOV      EAX,ECX
0187:0114EA7A 99              CDQ   
0187:0114EA7B F7FF            IDIV    EDI
0187:0114EA7D 85C0            TEST    EAX,EAX
0187:0114EA7F 7E17            JNG      0114EA98
0187:0114EA81 89C7            MOV      EDI,EAX
0187:0114EA83 2B7304          SUB      ESI,[EBX+04]
0187:0114EA86 56              PUSH    ESI
0187:0114EA87 8B4308          MOV      EAX,[EBX+08]
0187:0114EA8A 50              PUSH    EAX
0187:0114EA8B 8B430C          MOV      EAX,[EBX+0C]
0187:0114EA8E 50              PUSH    EAX
0187:0114EA8F 56              PUSH    ESI
0187:0114EA90 E8B7F7FFFF      CALL    0114E24C
0187:0114EA95 4F              DEC      EDI
0187:0114EA96 75EB            JNZ      0114EA83

    我用了注册机编写器的内存补丁,补完后运行,哈哈!程序的NAG没有了,不过~~~~~~~倒是出现了一个提示框,显示Access violation at address 0114C6F3. Write of address 8D07E07F.唉,写内存错误,于是下bpx 114C6F3

0187:0114C6C8  PUSH    EBP
0187:0114C6C9  MOV      EBP,ESP
0187:0114C6CB  PUSH    EBX
0187:0114C6CC  PUSH    ESI
0187:0114C6CD  PUSH    EDI
0187:0114C6CE  MOV      EDI,01153554
0187:0114C6D3  MOV      EBX,[01153654]<---指向16位数据运算的结果
0187:0114C6D9  MOV      EAX,[01153658]
0187:0114C6DE  MOV      ECX,0C
0187:0114C6E3  CDQ   
0187:0114C6E4  IDIV    ECX
0187:0114C6E6  MOV      ESI,EAX
0187:0114C6E8  TEST    ESI,ESI
0187:0114C6EA  JNG      0114C733
0187:0114C6EC  MOV      EAX,[EDI]
0187:0114C6EE  MOV      EAX,[EAX]    <---eax=40000
0187:0114C6F0  ADD      EAX,[EBX]    <---eax+[ebx]=8D07E07F
0187:0114C6F2  INC      EAX
0187:0114C6F3  MOV      DWORD [EAX],01<---错误在这里,[eax]等于8D07E07F
0187:0114C6F9  MOV      EAX,[EDI]
0187:0114C6FB  MOV      EAX,[EAX]
0187:0114C6FD  ADD      EAX,[EBX+04]
0187:0114C700  MOV      EDX,[EBX+08]
0187:0114C703  CALL    0114C68C

    也就是说Part I里的结果最后的16位数据被程序利用了,如果这里修改一下代码跳过的话出了这个CALL就到了DAP_Pro的领空,分析它的代码,是DAP_Pro在调用解压后的一个子程序,也许那16位数据是指向我们的注册信息或者某个窗体的数据,因为我们的数据和原定的不同,所以指针乱了,可能程序在启动时会显示一些信息,应该影响不大,因为关掉提示框后程序一切正常,只是About选项没了,这个情形和fwnl脱掉壳以后是一样的,还好这些数据不是拿来做功能限制的.由于我没有ASProtect的高版本,这一步只能跳过(谁能提供???),于是想办法跳过这个提示框.

    出现提示框后下bpx lockmytask,分析代码:

0187:0044E46D  MOV      EDX,[00407C60] <---指向提示框的标题
0187:0044E473  CALL    00402FA4      <---比较是否是发生错误的提示框
0187:0044E478  TEST    AL,AL
0187:0044E47A  JNZ      0044E4BF      <---跳就没事
0187:0044E47C  CMP      WORD [EBX+BA],BYTE +00

    Patch的时候把这里也改掉,问题是以后再有错误就没有提示啦!方法如下,用注册机编写器的内存补丁:

地址      长度        原码        修改
114EA2A      6    8B43048A4407      C70700000200
114EA30      7    FF8B53048A0F88    C747042B000100
114EA37      7    4C17FF88078B85    C7470802000100
114EA3E      7    FC6FFEFF668B00    C7470C10000700
114EA45      7    663B0530231501    C7471053616D2E  <---此行原码各机不同,请自行修改
114EA4C      7    7417C785F06FFE    C74714636F6D20      其它的照搬
114EA53      7    FF0200000033C0    C747180010005C
114EA5A      7    5A5959648910E9    C7471C58849D8A
114EA61      7    760100008B8DF4    C74720EB7E26E8
114EA68      7    6FFEFF498B7B04    C74724D376579C
114EA6F      7    3BCF7E258D0431    C74728163325FF
114EA76      7    8BF08BC199F7FF    C7472CFF000000
114EA7D      7    85C07E1789C72B    C7477C00000000
114EA84      7    7304568B430850    C6057AE44400EB  <---Patch提示框
114EA8B      2    8B43            EB0B

    现在,整个世界清静了,用此方法DAP_Pro的热键可用,其它功能未发现缺少,虽然此法对此程序破解并不算完美,但相信此方法对ASProtect 1.2以上加壳的程序破解时间限制都有效,至少我试过好几个都行.请大家帮忙测试.但此方法有个问题就是难以找到以上要修改的代码,我总结了个方法:先找到程序注册码保存的主键,输入任意大于8位的注册码,用TRW载入,下bpx regqueryvalueexa,中断后下d *(esp+8),当看到内存里出现"Key"时,查找8A 0F 88 4C 17 FF或33 C0 5A 59 59 64 89 10 E9,附近就是了.如果找不到的话就只能慢慢跟了----痛苦呀.如果你想用你自己的注册名可以用ASProtect生成,方法参阅Part I&II

    另外在追注册码时经常会碰到一些SEH,这东西我不大懂,如下:

0187:0114FDE9  PUSH    BYTE +10
0187:0114FDEB  PUSH    DWORD 0115366C
0187:0114FDF0  MOV      EAX,[EBX+02]  <---EBX=0
0187:0114FDF3  PUSH    EAX
0187:0114FDF4  LEA      EAX,[EBX+06]
0187:0114FDF7  PUSH    EAX
0187:0114FDF8  CALL    0114BFF4

    F8或F12的话就完了,如果不幸来到此只能重来,记住这个Call不要进入,只能F10代过,真是所谓的步步为营呀,所以我对脱壳不感冒:-)

-=End=-


      _/_/_/
    _/        _/_/_/  _/_/_/  _/_/
    _/_/    _/    _/  _/    _/    _/
        _/  _/  _/  _/    _/    _/
_/_/_/      _/_/_/  _/    _/    _/

                                              Sam.com
                                          6:15 2002-4-27