2007/06/13 | 冰点密码破解 — 强悍的调试器 SOFTICE
类别(软件破解及黑客技术) | 评论(0) | 阅读(723) | 发表于 13:00
标 题: 【原创】冰点密码破解 — 强悍的调试器 SOFTICE
作 者: figo
时 间: 2007-06-11,15:17
链 接: bbs.pediy.com/showthread.php?t=46153

【文章标题】: 冰点密码破解
【文章作者】: 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.20.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 的强大调试功能,灵活,稳定.都很值得我们花一些时间学它,并使用它.




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

评论Comments