【文章标题】: 冰点密码破解
【文章作者】: figo
【作者邮箱】: yangtengfei@56.com
【作者QQ号】: 382174647
【软件名称】: 冰点6.00.220.1692 企业版  
【加壳方式】: 未知壳
【保护方式】: ANIT DEBUG,加壳
【编写语言】: C , ASM32
【使用工具】: SOFTICE  MASM32  VC++ 6.0
【操作平台】: WINXP
【作者声明】: 纯技术交流,不针对任何软件.请勿用于恶意破坏等非法用途,
              否则给自己或他人带来严重后果,概与本人无关.失误之处恳请批评指正,
              或有更好的方法或者技巧,欢迎互相交流.
--------------------------------------------------------------------------------
【软件介绍】: 
  Deep Freeze 是一款类似于还原精灵的系统还原软件,但它比还原精灵强悍,无论加密强度或安全性.
  据介绍这软件无解,至今为找到破解方法.一旦弄丢了管理密码,只能格式化磁盘重新安装系统了.
  笔者发布此文章只是为了交流技术,并无其它目的,请不要用于恶意破坏等非法用途.
  如果文章能丢失为密码的用户带来一点帮助的话,笔者会十分欣慰的,毕竟好几天的努力才完成这文章的,
  Deep Freeze 的下载地址也不提供了,网上到处都是,版本是 6.00.220.1692
  

【详细过程】
  
    
  破解前的准备:
  先安装好 冰点 和 SOFTICE,笔者用的 DS3.2 中的 SOFTICE. 还有 IceExt 0.70 插件的安装(这并不是必须的,
  只是写文章的时候要用到,后面会介绍 IceExt 插件的妙用).装好冰点后,把客户端的密码设置为:382174647
  (呵呵,这是我的QQ号,  当然,你也可以设置为任意密码 ),把还原的盘设为 Z 盘(也可以任意,
  但一般不设为自己的硬盘分区),最后安装客户端.
  
  
  开始分析:
  
  首先,按住键盘上的 CTRL + ALT + SHIFT + F6 四个键,调出冰点的密码输入对话框. 输入任意密码,如: 234234.
  CTRL + D 调出 SOFTICE. 此时,你可千万别指望能在内存中找到 '234234' 的数据,并置断点.
  用 S 指令搜遍整个 4G 空间也一样,就算找到了,那也不是密码文本框的.

  因为当改变文本框的内容时,该文本框会自动调用 NT 的 Native API  :RtlRunEncodeUnicodeString 函数 进行加密.
  当应用程序想获取文本内容时,该文本框又会调用  RtlRunDecodeUnicodeString 函数进行解密.
  关于 RtlRunEncodeUnicodeString 和  RtlRunDecodeUnicodeString 的源代码可以在 NT 源代码中的 sertl.c 文件中找到.
  其实 RtlRunEncodeUnicodeString 只是对数据进行简单的 XOR 运算,尽管加密算法简单,却很有效的防止在内存中被找出明码.
  虽然我们可以不用知道 RtlRunDecodeUnicodeString  的具体算法,但为了利于破解,我们还是有必要知道它的定义:
  
  VOID RtlRunDecodeUnicodeString(  UCHAR  Seed,  PUNICODE_STRING String  )
  
  第一个参数是 :
  字节类型, 加密的种子的值.
  
  第二个参数是:
  是个 PUNICODE_STRING 数据类型
  指向被解密的数据的地址(注意了,是双重指针)
  
  好了,通过上面的分析,我们开始对 RtlRunDecodeUnicodeString  下断点,点击 OK 按纽,程序被中断在如下代码:
  
  
  EAX=0000002A   EBX=00000006   ECX=7C822E07   EDX=00140608   ESI=0014C2A8        
  EDI=0014CDB0   EBP=0012F088   ESP=0012F06C   EIP=7C94EF8B   o d I s Z a P c     
  CS=001B   DS=0023   SS=0023   ES=0023   FS=003B   GS=0000                       
  --------------------------------------------------byte--------------PROT---(0)--
  0023:00E20034 B0 CD 14 00 03 00 00 00-28 CF 14 00 68 00 E2 00  ........(...h.? 
  0023:00E20044 00 00 00 00 03 00 01 00-90 A5 15 00 03 00 01 00  ........`...... 
  0023:00E20054 B0 65 17 00 03 00 01 00-D0 25 19 00 03 00 01 00  .e.......%...... 
  0023:00E20064 F0 E5 1A 00 70 00 E2 00-00 00 00 00 78 00 E2 00  .?.p.?....x.? 
  ------ntdll!RtlRunEncodeUnicodeString+004D-------------------------------PROT32-
  ntdll!RtlRunDecodeUnicodeString                                                 
  001B:7C94EF8B  8BFF                MOV       EDI,EDI                            
  001B:7C94EF8D  55                  PUSH      EBP                                
  001B:7C94EF8E  8BEC                MOV       EBP,ESP                            
  
  刚才我们了解到 RtlRunDecodeUnicodeString  的第二个参数是指向密文的双重指针,输入:
  D *(ESP - 08)
  这时,密文的地址如上面DATA 窗口所示,为 14CDB0H.
  不要急着下断点,要等到它解密完毕.
  P RET ,跳出 RtlRunDecodeUnicodeString 
  然后 D 14CDB0
  
  --------------------------------------------------byte--------------PROT---(0)--
  0023:0014CDB0 32 33 34 32 33 34 00 00-00 00 00 00 00 00 00 00  234234.......... 
  0023:0014CDC0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................ 
  
  可以看到明码已经出现在我们面前. 好了,可以对它下硬件读断点
  bpm 14CDB0 R
  G 运行. 程序被中断在如下代码:
  
  ------USER32!EditWndProc+0566--------------------------------------------PROT32-
  001B:77D3352D  F3A5                REPZ MOVSD                                   
  001B:77D3352F  8BC8                MOV       ECX,EAX                            
  001B:77D33531  83E103              AND       ECX,03                             
  001B:77D33534  F3A4                REPZ MOVSB                                   
  001B:77D33536  E8E3FBFFFF          CALL      77D3311E                           
  001B:77D3353B  5F                  POP       EDI                                
  001B:77D3353C  5E                  POP       ESI                                
  001B:77D3353D  8BC3                MOV       EAX,EBX                            
  001B:77D3353F  5B                  POP       EBX                                
  001B:77D33540  5D                  POP       EBP                                
  001B:77D33541  C21000              RET       0010          
  
  不难看出,这段代码主要是实现数据的复制
  这时的 EDI = 00BC932C, 而 ESI 则是刚才明码的地址, ESI = 0014CDB0H
  同样,对 00BC932CH 下硬件读断点.G 运行 .
  接下来,程序再次中断在 RtlRunDecodeUnicodeString 上,我们再次重复上面这一过程.
  唯一不同的是,这次明码是被复制到 00BCA488H 处.于是对 00BCA488H 再下一个硬件读断点. 
  G 运行 .
  
  程序中断在如下代码:
  
  AX=00BCA488   EBX=00BCA488   ECX=0012F348   EDX=32343332   ESI=0012F33C        
  EDI=00BCAE11   EBP=0012F284   ESP=0012F254   EIP=004961F2   o d I s Z a P c     
  CS=001B   DS=0023   SS=0023   ES=0023   FS=003B   GS=0000                       
  --------------------------------------------------byte--------------PROT---(0)--
  0023:00BCA488 32 33 34 32 33 34 00 00-26 00 00 00 EC 9C BC 00  234234..&...棛.. 
  0023:00BCA498 00 00 00 00 34 9D BC 00-00 00 00 00 13 00 00 00  ....4?......... 
  0023:00BCA4A8 00 00 00 00 01 00 00 00-24 00 00 00 16 00 00 00  ........$....... 
  0023:00BCA4B8 EC 46 BC 00 24 AA BC 00-4F 70 74 69 14 00 00 00  '..$?.Opti.... 
  -------------------------------------------------------------------------PROT32-
  001B:004961F0  8B10                MOV       EDX,[EAX]                          
  001B:004961F2  83C004              ADD       EAX,04                             
  001B:004961F5  8BCA                MOV       ECX,EDX                            
  001B:004961F7  81EA01010101        SUB       EDX,01010101                       
  001B:004961FD  81E280808080        AND       EDX,80808080                       
  001B:00496203  74EB                JZ        004961F0                           
  001B:00496205  F7D1                NOT       ECX                                
  001B:00496207  23D1                AND       EDX,ECX                            
  001B:00496209  74E5                JZ        004961F0                           
  
  对每个字节减 1 ,再判断是否为负,这段代码应该是测试字符串长度的. 看看它返回的是什么值?
  
  P RET
  
  代码如下:
  
  EAX=00000006   EBX=00BCA488   ECX=00BCA488   EDX=80800000   ESI=0012F33C        
  EDI=00BCAE11   EBP=0012F284   ESP=0012F25C   EIP=0040BF90   o d I s z a P c     
  CS=001B   DS=0023   SS=0023   ES=0023   FS=003B   GS=0000   DS:00BCAE11=0014    
  -------------------------------------------------------------------------------
  001B:0040BF87  E85CA20800          CALL      004961E8                           
  001B:0040BF8C  59                  POP       ECX                                
  001B:0040BF8D  8945FC              MOV       [EBP-04],EAX                       
  001B:0040BF90  0FB707              MOVZX     EAX,WORD PTR [EDI]                 
  001B:0040BF93  85C0                TEST      EAX,EAX                            
  001B:0040BF95  7513                JNZ       0040BFAA                           
  
  EAX 返回 6,没猜错,果然是测字符串长度的. EAX 保存在 EBP -4 处,再对 EBP -4  下一个硬件读断点.
  
  G  运行.
  
  程序中断在如下代码:
  
  001B:0040BFB4  3B45FC              CMP       EAX,[EBP-04]                       
  001B:0040BFB7  7407                JZ        0040BFC0             (NO JUMP)     
  001B:0040BFB9  33C0                XOR       EAX,EAX                            
  001B:0040BFBB  E989000000          JMP       0040C049                           
  
  此时的 EAX = 9 (原来密码的长度)  ,[EBP -4] = 6 (输入密码的长度)
  不相等就清零 EAX ,并跳到 40C049
  
  
  再看看   40C049 处的代码:
  
  001B:0040C049  5F                  POP       EDI                                
  001B:0040C04A  5E                  POP       ESI                                
  001B:0040C04B  5B                  POP       EBX                                
  001B:0040C04C  8BE5                MOV       ESP,EBP                            
  001B:0040C04E  5D                  POP       EBP                                
  001B:0040C04F  C3                  RET                        
  
  这是子过程结束的标准语句. 
   CMP       EAX,[EBP-04]  和  JZ        0040BFC0 是判断输入密码和原密码的长度是否相符.
  这两句很重要,请记住它,写密码破解程序时要用到它.
  为了继续调试,把 Z 位 置 1,单步..
  
  代码如下:
  
  EAX=00000009   EBX=00BCA488   ECX=00BCA488   EDX=80800000   ESI=0012F33C        
  EDI=00BCAE11   EBP=0012F284   ESP=0012F25C   EIP=0040BFC0   o d I s Z a P c     
  CS=001B   DS=0023   SS=0023   ES=0023   FS=003B   GS=0000   DS:0012F33C=9F448C62
  --------------------------------------------------byte--------------PROT---(0)--
  0023:00BCA488 32 33 34 32 33 34 00 00-26 00 00 00 EC 9C BC 00  234234..&...棛.. 
  0023:00BCA498 00 00 00 00 34 9D BC 00-00 00 00 00 13 00 00 00  ....4?......... 
  0023:00BCA4A8 00 00 00 00 01 00 00 00-24 00 00 00 16 00 00 00  ........$....... 
  0023:00BCA4B8 EC 46 BC 00 24 AA BC 00-4F 70 74 69 14 00 00 00  '..$?.Opti.... 
  -------------------------------------------------------------------------PROT32-
  001B:0040BFC0  8B16                MOV       EDX,[ESI]                          
  001B:0040BFC2  895604              MOV       [ESI+04],EDX                       
  001B:0040BFC5  33C9                XOR       ECX,ECX                            
  001B:0040BFC7  8BD3                MOV       EDX,EBX                            
  001B:0040BFC9  894DF8              MOV       [EBP-08],ECX                       
  001B:0040BFCC  8D4702              LEA       EAX,[EDI+02]                       
  001B:0040BFCF  C745F402000000      MOV       DWORD PTR [EBP-0C],00000002        
  001B:0040BFD6  8955E4              MOV       [EBP-1C],EDX                       
  001B:0040BFD9  8BF8                MOV       EDI,EAX                            
  001B:0040BFDB  8B4DF8              MOV       ECX,[EBP-08]        ;已经与密码比较过的字节数               
  001B:0040BFDE  3B4DFC              CMP       ECX,[EBP-04]        ;密码的长度               
  001B:0040BFE1  7D64                JGE       0040C047            ;此处改为 JMP  0040C047,也可实现暴破            
  001B:0040BFE3  56                  PUSH      ESI                                
  001B:0040BFE4  E8B7FEFFFF          CALL      0040BEA0  
  ;密码算法的关键 CALL ,喜欢研究算法的朋友可以跟进瞧瞧,不过也无意义,等下解释为什么.
    
               
  001B:0040BFE9  59                  POP       ECX                                
  001B:0040BFEA  8B45E4              MOV       EAX,[EBP-1C]   ;指向输入的密码的第N个字节,N = [EBP - 8]                     
  001B:0040BFED  8A18                MOV       BL,[EAX]                           
  001B:0040BFEF  8A07                MOV       AL,[EDI]                           
  001B:0040BFF1  324604              XOR       AL,[ESI+04]    ;解密出原密码的第N个字节                
  001B:0040BFF4  8845F3              MOV       [EBP-0D],AL    ;暂存                
  001B:0040BFF7  807D1400            CMP       BYTE PTR [EBP+14],00               
  001B:0040BFFB  7409                JZ        0040C006                           
  001B:0040BFFD  3A5DF3              CMP       BL,[EBP-0D]                        
  001B:0040C000  742F                JZ        0040C031                           
  001B:0040C002  33C0                XOR       EAX,EAX                            
  001B:0040C004  EB43                JMP       0040C049                           
  001B:0040C006  0FBED3              MOVSX     EDX,BL                             
  001B:0040C009  8955EC              MOV       [EBP-14],EDX                       
  001B:0040C00C  8B4DEC              MOV       ECX,[EBP-14]                          
  001B:0040C00F  51                  PUSH      ECX                                
  001B:0040C010  E873D40800          CALL      00499488                           
  001B:0040C015  59                  POP       ECX                                
  001B:0040C016  50                  PUSH      EAX                                
  001B:0040C017  0FBE45F3            MOVSX     EAX,BYTE PTR [EBP-0D]              
  001B:0040C01B  8945E8              MOV       [EBP-18],EAX                       
  001B:0040C01E  8B55E8              MOV       EDX,[EBP-18]       
  001B:0040C021  52                  PUSH      EDX                                
  001B:0040C022  E861D40800          CALL      00499488                           
  001B:0040C027  59                  POP       ECX                                
  001B:0040C028  59                  POP       ECX                                
  001B:0040C029  3BC8                CMP       ECX,EAX     ;   开始对比,EAX 为原密码的第N个字节                 
  001B:0040C02B  7404                JZ        0040C031    ;   关键跳转,很重要,写破解程序时,要用到它.
                      
  001B:0040C02D  33C0                XOR       EAX,EAX                            
  001B:0040C02F  EB18                JMP       0040C049                           
  001B:0040C031  FF45E4              INC       DWORD PTR [EBP-1C]                 
  001B:0040C034  FF45F8              INC       DWORD PTR [EBP-08]   ;对比的字节地址加 1              
  001B:0040C037  47                  INC       EDI                                
  001B:0040C038  FF45F4              INC       DWORD PTR [EBP-0C]                 
  001B:0040C03B  47                  INC       EDI                                
  001B:0040C03C  FF45F4              INC       DWORD PTR [EBP-0C]                 
  001B:0040C03F  8B55F8              MOV       EDX,[EBP-08]                       
  001B:0040C042  3B55FC              CMP       EDX,[EBP-04]                       
  001B:0040C045  7C9C                JL        0040BFE3        ;判断是否对比完毕.未完则继续.               
  001B:0040C047  B001                MOV       AL,01                              
  001B:0040C049  5F                  POP       EDI                                
  001B:0040C04A  5E                  POP       ESI                                
  001B:0040C04B  5B                  POP       EBX                                
  001B:0040C04C  8BE5                MOV       ESP,EBP                            
  001B:0040C04E  5D                  POP       EBP                                
  001B:0040C04F  C3                  RET                                          
  
  
  
  
  
  
  这段代码便是密码比较的最重要部分,由于代码长而复杂,于是就用注释来代替跟踪.
  对冰点密码算法感兴趣的朋友,可以直接对 40BDC0H  置断点,并依照注释自己跟踪调试一下.
  
  
  解密器的编写思路 :
  既然程序可以把原密码解密出单字节并与输入密码进行循环比较,我们可以写一个程序,
  对程序每次解出的单字节密码进行拼接.
  如果把 40C029 — 40C030 之间8个字节全部用 NOP 填充,可以实现爆破.
  这说明 40C029 — 40C030 之间有8个字节可以利用.而不远处 
  0040C03F 有  MOV  EDX,[EBP-08] 这么一条指令,说明EDX 也可以利用.
  于是解密程序先为 冰点 进程远程分配一段内存,然后把远程代码写入内存.
  最后在  40C029 — 40C030 之间写如下代码:
  NOP
  MOV EDX,XXXXXXXX
  CALL EDX
  
  XXXXXXXX 为远程代码的起始地址,编译成字节码如下:
  90,BA XXXXXXXX ,FF D2 . 刚好8个字节.
  
  
  
  
  写此程序遇见的第一个问题就是: 如何确定原密码的长度?
  也许你会说[EBP -4] 不就是吗? 不是的,[EBP - 4]其实是我们输入密码的长度.
  这时,你可能更纳闷了,如果密码循环比较是由输入的密码的长度决定,那程序的安全性不就只有一个字节了?
  别忘了,我们进入这段代码是通过强制跳转的,正常情况下,只有[EBP - 4]等与原密码的长度才进行比较.
  就算使用代码补丁进入密码比较你也无法知道原密码的长度,唯一的办法就是让[EBP - 4] 等于原密码的长度,
  然后再强行跳转.于是我们就在 40BFB4H 处的  CMP  EAX,[EBP-04] 和  JZ  0040BFC0 下文章.
  把它改为 MOV [EBP-04],EAX 和 JMP  0040BFC0 就可以完美解决.
  
  
  
  
  具体代码实现:
  
  
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;code.inc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      .486                      
      .model flat, stdcall       
      option casemap :none      
  
      include \masm32\include\windows.inc
      include \masm32\include\masm32.inc
      include \masm32\include\kernel32.inc
      include \masm32\include\advapi32.inc
      include  \masm32\include\user32.inc
      
      includelib \masm32\lib\masm32.lib
      includelib \masm32\lib\kernel32.lib
      includelib \masm32\lib\advapi32.lib
      includelib \masm32\lib\user32.lib
       
       GetPidFromProcName  proto :DWORD
       WritePMem  proto :DWORD,:DWORD,:DWORD,:DWORD
   
  .data
    procname     db  'FrzState2k.exe'  ,00
    mempatch     db   0ebh,089h,045h,0fch
    farcall      db   90h,0bah,0ffh,0d2h
     
    funadd       dd     ?
    szMsgBox     db   'MessageBoxA',0
    szuserdll    db    'user32.dll',0
    lpMsgfun     dd    ?
    szsvrname    db     'MyServerName1',0  
    szstr1       db     '冰点破解程序',0 
    szstr2     db     '运行此程序后,按 CTRL + SHIFT + ALT + F6,调出密码输入对话框',13,10 

     db     '可以不用输入密码,或输入任意密码.',13,10
     db     '点击 OK ,即可显示密码,并进入控制界面!',13,10 
     db     '本程序破解时,远程分配的空间并不释放.',13,10
     db     '多次运行后请重起!',13,10,13,10
     db     'by figo (追风者)',13, 10, 'QQ : 382174647',0
    buf1         db      120h dup (00)
    buf2         db      124h dup (00)
    szfmt        db      '"%s"',0   
    var1   db      ' sys',0

  
  .code
  
  
  mycode:
  
  lpmsg   dd  ?
  sztil   db  '密码为: ',0
  szstr   db 'password is '
  szpwd   db  70h dup (0)
  
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  startcode:
  
  pushad
  
  call l1
  l1:
  pop ebx
  sub ebx,offset l1
  ;很经典的代码自定位技术,不陌生吧!
  lea edi, [ebx + offset szpwd]
  mov edx,[ebp - 8]
  add edi,edx
  mov byte ptr [edi],al
  inc edx
  mov eax,[ebp -4]
  cmp edx,eax
  jl ext1
  
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  push  MB_OK or MB_SERVICE_NOTIFICATION  
  ;由于冰点会不断把密码输入对话框置前,所以只能加上 MB_SERVICE_NOTIFICATION 常数. 
  lea edi ,[ebx + offset sztil]
  push edi
  
  lea edi ,[ebx + offset szpwd]
  push edi
  push 0
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  mov eax,[ebx + offset lpmsg]
  call eax
  
  ext1:
  popad
  ret
  
  codeend:
  
  GetPidFromProcName proc lpProcName:DWORD
  LOCAL stProcess : PROCESSENTRY32
  LOCAL hSnapshot
  LOCAL dwProcessID
  
  mov dwProcessID, 0
  invoke RtlZeroMemory, addr stProcess, sizeof stProcess
  mov stProcess.dwSize, sizeof stProcess
  invoke CreateToolhelp32Snapshot, TH32CS_SNAPPROCESS, 0
  mov hSnapshot, eax
  invoke Process32First, hSnapshot, addr stProcess
  .while eax
  invoke lstrcmpi, lpProcName, addr stProcess.szExeFile
  .if eax==0
  mov eax, stProcess.th32ProcessID
  mov dwProcessID, eax
  .break
  .endif
  invoke Process32Next, hSnapshot, addr stProcess
  .endw
  invoke CloseHandle, hSnapshot
  mov eax, dwProcessID
  ret
  GetPidFromProcName endp
  
  
  WritePMem   proc  hproc:DWORD, rwadd:DWORD  ,lpbuff:DWORD, nsize:DWORD
  local  dwrwcnt
  local  oldpct
  invoke  VirtualProtectEx,hproc,lpbuff,4096,PAGE_EXECUTE_READWRITE,addr oldpct
  .if !eax
  ret
  .endif
  invoke  WriteProcessMemory ,hproc,rwadd,lpbuff,nsize,addr dwrwcnt
  invoke   VirtualProtectEx,hproc,lpbuff,4096,oldpct,addr oldpct
  
  mov eax, dwrwcnt
  ret
  
  WritePMem   endp
  
  
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;code.inc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  
  
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;deep.asm;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  
  include  code.inc
  
  
  start:
  
  Main  proc
  local    hproc1
  local    hSCManager
  local     hService
  
  ;//////////提权///////////////////////////////////////////////////////////
  invoke  GetCommandLine
  mov esi,eax

  invoke GetModuleFileName,NULL,addr buf1,255
  invoke  wsprintf,addr buf2,addr szfmt,addr buf1
  ;如果不将文件路径加上双引号,则无在带有空格的路径名中正常运行.
  invoke lstrcat,addr buf2,addr var1
  invoke lstrcmpi,addr buf2,esi
  jz startmain 

  invoke OpenSCManager, NULL, NULL, SC_MANAGER_CREATE_SERVICE
  .if eax
  mov hSCManager, eax

  invoke OpenService, hSCManager, addr szsvrname , DELETE
  .if eax!=0
  mov    hService, eax
  invoke DeleteService, hService
  invoke CloseServiceHandle,hService
  .endif

  invoke CreateService, hSCManager,addr  szsvrname, addr  szsvrname, \
  SERVICE_START + SERVICE_QUERY_STATUS + DELETE, \
  SERVICE_WIN32_OWN_PROCESS + SERVICE_INTERACTIVE_PROCESS, SERVICE_DEMAND_START, \
  SERVICE_ERROR_IGNORE, addr buf2, NULL, NULL, NULL, NULL, NULL

  .if eax!=0
  mov    hService, eax
  invoke StartService, hService, 0, NULL
  invoke DeleteService, hService
  invoke CloseServiceHandle, hService
  .endif
  invoke CloseServiceHandle, hSCManager
  .endif

  invoke ExitProcess,0

  ;////////////////////以服务的方式运行自身////////////////////////////////////////
  
  startmain:
  
  invoke GetPidFromProcName ,addr procname
  
  invoke OpenProcess,PROCESS_ALL_ACCESS,NULL,eax
  mov hproc1,eax
  lea esi,mempatch
  
  invoke   WritePMem,hproc1,0040bfb7h,esi,1
  invoke   WritePMem,hproc1,0040c02bh,esi,1
  inc esi
  invoke   WritePMem,hproc1,0040bfb4h,esi,3
  
  invoke   GetModuleHandle, addr szuserdll
  
  invoke GetProcAddress,eax,addr szMsgBox
  mov  lpMsgfun,eax
  
  
  invoke  VirtualAllocEx,hproc1,NULL,1024,MEM_COMMIT,PAGE_EXECUTE_READWRITE
  .if eax
  mov esi,eax
  
  invoke  WritePMem,hproc1,esi,offset mycode,offset codeend - offset mycode 
  invoke  WritePMem,hproc1,esi,offset lpMsgfun ,4
  mov edi,offset startcode - offset mycode
  add esi,edi
  mov funadd,esi
  mov esi,0040c029h
  
  invoke  WritePMem,hproc1,esi,offset farcall,2
  inc esi
  inc esi
  
  invoke  WritePMem,hproc1,esi,offset funadd,4
  add esi ,4
  
  invoke  WritePMem,hproc1,esi,offset farcall + 2 ,2
  
  .endif
  
  invoke  MessageBox,NULL,addr szstr2,addr szstr1,MB_OK
  ;注意这句,笔者的用意可不是仅仅为了提示用户,而是必须要有这么个函数.
  invoke ExitProcess,0
  
  
  Main endp
  
  end start;
  
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;deep.asm;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;       
  由于冰点是以服务的方式运行,进程具有SYSTEM 权限.要对它进行内存的读写与分配,须进行提权.
  提权的方法有很多种,为了减小代码的篇幅,只好选最简单的提权方式:
  以服务的方式运行自身,这样就可以很方便得到SYSTEM 权限.
  唯一的缺憾就是另创进程,这在代码调试时,给 OD 带来不小的麻烦.
  
  
  
  
  
  一次性密码的算法:
  
  G 继续运行冰点程序,程序又被中断.....,那是开始比较一次性密码.
  具体调试方法与上面类似,这里就不再重复了,也可以把上面的代码稍加修改,用于解一次性密码.
  解一次性密码较为理智的方法就是分析服务端的主程序,下面一次性密码的算法就是跟踪服务端程序得来的.
  由于篇幅的原因,我只贴出算法,具体调试方法就不写了.因为分析一次性密码算法远远复杂于解客户端密码,
  单单一次性密码的分析,就可以再写成一篇文章.
  
  具体算法如下:
  ///////////////////////////////OT.CPP////////////////////////////////////
  #include "stdio.h"
  #include "stdlib.h"
  #include  "string.h"

  #define  UL unsigned long
  #define  SI signed short int
  #define  SL signed long
  
  UL pwd(UL a,UL b,UL c);
  SI hl(SI srt);
  UL cr(char  str1[] );
  
  void main(int argc, char* argv[])
  {
      UL pwd1,pwd2,pwd3;
      char *cstcode = new char[0x80];
      char etystr[] = {"Igor Zagoruchenko"} ;
  
  
    printf("\t\t One Time Password Generation System \n\n by figo \n\n");
  
    printf("\nPlease enter the  customization code: \n");
    if(!(scanf("%s",cstcode)))
        return;
  
      printf("\nPlease enter OTP Token : \n");
    if(!(scanf("%8x",&pwd1)))
        return;
  
  
    pwd2 = cr(etystr);
    pwd3 = cr(cstcode);
  
    pwd1 = pwd(pwd1,pwd2,pwd3);
    printf("\n\nThe One Time Password is : \n\n");
    printf("%8X (Password valid for one use only)\n\n\n\n",pwd1);
  
  
   ltoa(pwd1,cstcode,16);
   pwd2 = cr(cstcode);
  
   ltoa(pwd2,cstcode,16);
   cstcode = strupr(cstcode );  
   printf("%4.4s-%8X (Password valid for multiple uses ) \n\n\n\n\n",cstcode,pwd1);
   
   delete cstcode;
   system("pause");
  
  }
  
  UL pwd(UL a,UL b,UL c)
  {
  
      SI s1,s2,s3;
          a  ^= b;
          a  ^= c;
          b = a << 0x10;
          b >>= 0x10;
    c = a >> 0x10;
    a = b^c;
    s1 = (SI)a;
    s2 = (SI)b;
    s3 = (SI)c;
    s1 = hl (s1);
    s3 = hl  (s3);
    s3 ^= s2;
    
    a= s1;
    a <<= 0x10;
    a += s3;
     return a;
  
  }
  
  SI hl(SI srt)
  {
    SL a,b,c;
    a = srt;
    b = a;
    a /=  0xb1; 
        b = b%0xb1; 
    c = b * 0xab; 
    a += a;
        c -= a;
    a = c;
    b = a;
    a <<= 0x0f;
    a -= b;
    srt = (SI)a;
  
    return srt;
  
  }
  
  UL cr(char  str1[] )
  {
  
      int i = 0 ,k =0;
      char c1;
      unsigned long rst = 0,l1,l2;
  
    while (!(str1[i] == 0))
    { 
    c1 = str1[i];
  
    if(  (c1 >= 'A') && (c1<='Z' ) )
      c1  |= 0x20;
    else 
      c1 = c1;
    
    l1 = c1;
    rst <<= 4;
    l1 += rst;
    rst = l1;
  
    l1 &= 0xf0000000;
    
    if(l1)
    {
        l2  = l1 >> 0x18;
        rst ^= l2;
    }
     l1 = ~l1;
     rst &= l1;
  
        i++;
  
    }
  
  return rst;
  
  }    
  ////////////OT.CPP///////////////////////////////////////////////////////////////////
  
  上面代码是模拟冰点的一次性密码生成系统, 只是为了模拟演示算法,所以需要输入用户的自定义码.
  直接在客户端解出一次性密码而不用用户自定义码的详细代码见附件(一定要安装冰点客户端才能解出一次性密码).
  
  
  
  冰点的分析到此为止.文章的开始我们提到插件 IceExt 0.70.下面讲解 IceExt 0.70的妙用.
  我们都知道 SOFTICE 无法象 OD 一样有强大的文本复制功能.但是写破文的时候用到的那些代码片段怎么办呢?
  如果说是把它抄下来,然后再手动输入成文章,那这样的破文我是不会写的.
  IceExt 0.70 插件中有个转存屏幕的功能,但它保存的是RAW 格式的文件,而不是文本.
  用十六进制编辑器打开IceExt 0.70 转存的文件,发现 RAW 格式其实很简单:
  每个字符用2个字节保存:
  第一个字节保存字符的值.
  第二个字节保存字符的属性,低四位为前景色,高四位为背景色.
  玩过十六位汇编的朋友,如果有尝试在显示缓冲区内写入彩色字的,应该对此不陌生吧!
  再看一下文件的长度,刚好等于 WIDTH 的值 * LINES 的值 * 2
  
  下面我就把提取RAW中字符的代码贴一下:
  
  ///////////////////////duptxt.cpp//////////////////////////////
  /*//2007.6.7      by figo (追风者)  QQ: 382174647
    
    仅用于学习交流,代码中有任何问题请联系我...
     程序用法:
     duptxt.exe rawfilename [width] 
     rawfilename 为IceExt Dump 出来的RAW 格式的文件.
     width  为SOFTICE 中 width 指令的设置值,默认为 80。
     程序运行成功,会在 rawfilename 文件的目录下生成 rawfilename.txt 文件...
  //*/
  
  
  #include "stdio.h"
  #include "afx.h"
  
  void main(int argc, char* argv[])
  {
  
  CFile f1,f2;
  CString txtfn1;
  int width = 80,lines, i,k;
  unsigned int cr=0x0a0d;
  
  unsigned char tmp;
  
  
  if( argc < 2)
  {
  printf("正确参数格式为:\n ");
  printf("%s  rawfilename [width]  \n",argv[0]);
  
  return;
  }
  else if(argc > 2)
  {
      width = atoi( argv[2] );
  }
   
  if (!(f1.Open(argv[1],CFile::modeRead )))
  {
  printf("%s 文件打开失败!\n",argv[1]);
  return;
  }
  
  txtfn1 = argv[1];
  txtfn1 += ".txt";
  
  if(!(f2.Open(txtfn1, CFile::modeWrite | CFile::modeCreate )))
  {
  printf(" %s 文件创建失败!\n",txtfn1);
  return ;
  
  }
  
     
    f1.SeekToBegin ();
    f2.SeekToBegin ();
          
          lines = f1.GetLength ();
          lines /= width * 2;
  
    for(k=0 ;k<lines;k++) 
    {  
      for(i =0 ; i<width;i++)
      {
        f1.Read (&tmp,1);
        ////////////////// 
        if (tmp == 0xc4)
          tmp = '-';
        else if((tmp >= 0x10)&&(tmp <0x20))
          tmp = 0x20;
  
        /////////////////
        f2.Write (&tmp,1);
        f1.Seek (1,CFile::current);
        
      }
      f2.Write (&cr,2);
  
    }
  
    f1.Close ();
    f2.Close ();
  
  }
  
  //////////////////////////////duptxt.cpp///////////////////////////////////////////
  
  编译上面代码时,要注意在工程设置中选择   Use MFC in a Shared DLL .
  代码使用的是MFC类,所以只要稍加修改,就可移植到 MFC程序中.
  其实知道RAW 文件的格式,完全可以尝试自己编写一个,也相信你们会写的比我更好.
  
  
  
  
--------------------------------------------------------------------------------
【经验总结】
  
  
  
  关于冰点:
  
  冰点无解! 据网上介绍说此软件至今为找到破解方法.
  其实这话不假,冰点的加密的确强悍.强壳,反加载,反调试,定时检测,以服务进程运行,能拦截IO...等技术
  足以让众多的 Cracker 望而却步,纵然是 脱壳高手 + 静态分析高手,也很无奈.
  因为无法加载它,它需要以服务的形式运行.并且会不断把窗体置前,以干扰调试.
  采用多线程定时器保护,当某个线程检测到自己或另一个线程被暂停,就退出进程.
  以上这些技术对付 OD 很是奏效,所以很有必要认识另一款功能强悍调试器 SOFTICE
  
  一点补充:
  其实冰点6.00.XXX.XXXX  (企业版)的加密方法一样,只是版本的不同,使代码的偏移位置也不同.
  所以解密器只针对与 6.00.220.1692 版,你也可以使用上面的跟踪方法来跟踪其它版的冰点.
  找出偏移地址差,只要稍微修改一下代码,就能把解密器用于其它版本......
  文章的目的是为了学习调试方法和密码算法,所以不想花过多的时间去写一个通用与其他版本的解密器.
  我也希望朋友们看这篇文章的时候的收获是学会用 SOFTICE 调试冰点,而不是得到冰点的解密器....


  
  在代码的注释中我提到: 密码算法的关键 CALL,就算跟进也无意义......
  在此我做一下解释:
  因为就算知道冰点的密码算法你也很难做成注册机,它的算法并不难.
  只是,冰点把密文数据压缩并写入文件,  你可能知道压缩后的位置,但却无法知道它解压后的数据地址或解压它
  (应该不会去跟踪它是用哪一种压缩引擎压缩的吧? 万一发现那压缩引擎是自写的呢?).
  知道算法能奈它何? 唉..............

  
  关于调试器:
  
  OllyDbg ,易用而功能强大的调试器,除了调试与系统底层相关的一些程序(比如 驱动,ROOTKITS,RING0 程序等)
  OD 几乎无所不能,其强大的代码智能分析功能是其它众多调试器所无法比拟的.不可否认OD 是最强用户级调试器.
  Cracker 们似乎渐渐忘掉曾经调试器中的王者 -- SOFTICE,《看雪论坛精华》中渐渐没了关于 SOFTICE 破解的文章.
  
  但象冰点这种软件却只能用SOFTICE 来解,SOTFICE 是内核级调试器,中断时连系统时钟也一起停了.
  可以不用担心定时器检测,所有线程都被挂起,操作系统也不例外,所以也不用担心窗口置前的干扰.
  SOFTICE 是即时呼出,冰点的反加载和调试器的捆绑失败,不是你所考虑的问题,你只要专心置好断点就行.
  
  很难相信用 OD 可以解掉此类软件,笔者也尝试着OD 来解冰点,结果碰的一鼻子灰.
  
  虽然随着虚拟计算机技术的不断成熟和计算机硬件的性能越来越好,价格却越来越便宜(笔者学计算机近五年了,
  依然记得当初自己的 2500+ 比现在的 3800+ 贵 N 多,唉....,可怕的摩尔定律...)
  SOFTICE 很可能会被功能更为强大的双机内核调试器 WINDBG ,VISUAL SOFTICE 所替代. 
  但至少在今天,SOFTICE 的强大调试功能,灵活,稳定.都很值得我们花一些时间学它,并使用它.
  



  文章到此结束,首先,谢谢你能看到这里. 当然,由于文章写的仓促错误在所难免.
  对文章指正与建议是你对我作品最大的支持与肯定,谢谢你..............

  
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者与出处并保持文章的完整, 谢谢!

                                                       2007年06月12日 上午 09:36:06

  • 标 题:答复
  • 作 者:figo
  • 时 间:2007-06-11 15:19

一些与技术无关的话题:

关于冰点的破解其实不是偶然:
很多天以前网上的一位朋友加了我QQ,他对我说: “我在看雪论坛上找你写的一篇老文章 ——
《还原精灵密码算法分析》, 写的不错!......”.后来他又问我对冰点有没有研究?能不能解一下冰点?
因为冰点还原类软件的新秀,在此之前我根本没用过冰点,甚至不知道如何安装冰点.
但那时的我只看到“写的不错!”这些字眼(呵呵,好虚荣的我,总是喜欢把人家的客套话当回事,唉...).
于是想都没想就答应他: “最近忙,过两个礼拜帮你试试,好久没 CRACK 了,正好练练手...”
破解冰点加写成文章花了我一个礼拜多的时间,真后悔当初好强,随便答应别人试试.

不过这种事不会再有下次,不会再帮别人去专门破解某个软件了.
因为我也是计算机初学者,并且非计算机专业的,也不兴趣于 CRACK ,更不会把 CRACK 当职业.
CRACK 不过是我在学习编程和调试技术时,无心学会的技能,并不刻意学它.
对我来说,学会分析和调试程序的最大用处是,可以参考别人的程序流程和纠正自己的代码.
所以如果你也喜欢编程,喜欢 ROOTKIT ,喜欢研究系统底层....,那可能我们兴趣相似,我也
很乐意与你相互学习或交流技术.但如果你加我QQ是为了破解某个软件,那么请另请高就吧!
因为我的技术的确菜的要命,通常会让你失望的......





to 25楼的 supertu:
因为解密的方法特殊,所以解密器只专用于 冰点6.00.220.1692 企业版.
写文章的时候没有对它声明是我的疏忽,万用的冰点解密器我没兴趣编写(很浪费时间的!)
不过文章中的解法却不受版本限制,希望能认真阅读...