标 题: 补丁技术应用
关于本文:本文主要目的在于教学,让初学者如何应用补丁技术修补程序,只从技术角度探讨一下
请勿将此教程用于商业目的。在这首先感谢dreamtheater在Win32Asm编程方面的帮助!
===========================================================================
第一部分:程序分析
该程序是一网络客户端程序,每台机子可免费试用10天,如需继续使用则要购买正式帐号。程序拦截过程就不
写了,主要验证代码如下:
0167:004ABEC0 MOV ECX,EAX
0167:004ABEC2 CALL 00547B69
0167:004ABEC7 JMP 004AC228
0167:004ABECC MOV DWORD [0067B9C8],01
0167:004ABED6 MOV EAX,[EBX+64]
0167:004ABED9 PUSH EAX //机器号入栈(一机一号,并在其服务器登记,只能用10天)
0167:004ABEDA CALL `CHANNEL!CheckTryUserWhenLogOn`//在服务上验证用户合法性
0167:004ABEDF CMP EAX,BYTE -01 //EAX=0用户试用期己到;EAX=FFFFFFFF,网络连接超时;
//EAX为其它值时可以继续试用(若强行修改其值以欺骗服务器,无效)。
0167:004ABEE2 MOV [ESP+30],EAX
0167:004ABEE6 JZ NEAR 004AC1F4//当EAX=FFFFFFFF时跳,报“网络连接超时”;
0167:004ABEEC CMP EAX,ESI
0167:004ABEEE JNZ 004ABF51
0167:004ABEF0 CALL `CHANNEL!CloseChannel`//如上述EAX==0,则断开与服务器连接
0167:004ABEF5 PUSH ESI
所以该程序破解相当简单,当过期后,只要将0167:004ABED9 PUSH EAX 入栈的机器号换一数又可使用十天,
但问题来了,每到十天要修改程序一次,实在是让人烦脑,能不能让程序自动修改机器号呢?
===========================================================================
第二部分:确定方案
对这个问题,我采取的是这个方案:在该程序目录建一配制文件(如1212.ini),有如下内容:
/////////////////////////////////////////////////////////////////////
[kanxue]
count=0x00000003 //记数器,以十六进制表示(字符0x表示十六进制)
/////////////////////////////////////////////////////////////////////
程序上服务器验证时,从1212.ini文件读取count值作为机器号(0167:004ABED9 PUSH EAX)入栈,十天试用
期用完后(也就是`CHANNEL!CheckTryUserWhenLogOn`反回值为FFFFFFFF),将1212.ini文件中的count值加1,
如此流程,该软件就可长时间使用了。
实现这个功能,需要如下win32函数:
1、GetPrivateProfileIntA函数(用来读取1212.ini文件的count值)
UINT GetPrivateProfileInt(
LPCTSTR lpAppName, // address of section name
("kanxue")
LPCTSTR lpKeyName, // address of key name
("count")
INT nDefault, // return value if key name is
not found(默认值)
LPCTSTR lpFileName // address of initialization
filename(初始化文件的名字1212.ini)
);
2、WritePrivateProfileString函数(用来将数据写入1212.ini文件里)
BOOL WritePrivateProfileString(
LPCTSTR lpAppName, // pointer to section name
("kanxue")
LPCTSTR lpKeyName, // pointer to key name
("count")
LPCTSTR lpString, // pointer to string to add
(要写的字串)
LPCTSTR lpFileName // pointer to initialization
filename (文件1212.ini)
);
3、wsprintf函数(将字串用十六进制形式放入1212.ini,你也可不用该函数,结果就以十进制表示了)
int wsprintf(
LPTSTR lpOut, // 指向一个接收格式化文本的buffer
LPCTSTR lpFmt, // 指向一个带有格式控制的零终止字符串
...
// 参数选项
);
其中:lpFmt指向的格式字符串中每一个格式设定必须是以下形式:
%[-][#][0][width][.precision]type
由于我们希望1212.ini中的count是以十六进制(0x0000000)表示,所以在这定义:
Fmt db "%#08lx",0
它含义输出的数值是带有0x(#)8位(8)小写的hex(lx),如果输出结果不足8位,补零(0)
为了更好地理解,我们先以一Win32ASM程序来实现这个功能:
/////////////////////////////////////////////////////////////////////
.386
.model flat,stdcall
option casemap:none
include windows.inc
include user32.inc
include kernel32.inc
includelib user32.lib
includelib kernel32.lib
.const
Default equ 1
.data
iniFile db ".\1212.ini",0
AppName db "kanxue",0
KeyName db "count",0
Fmt db "%#08lx",0
Opt db 10 dup(0)
.code
; ---------------------------------------------------------------------------
start:
invoke GetPrivateProfileInt, addr AppName, addr KeyName, Default,
addr iniFile
mov eax, 5
invoke wsprintf, addr Opt, addr Fmt, eax
invoke WritePrivateProfileString, addr AppName, addr KeyName,
addr Opt, addr iniFile
invoke ExitProcess, NULL
end start
/////////////////////////////////////////////////////////////////////
将上面程序编译后的EXE文件,反汇编后,关键代码如下:
/////////////////////////////////////////////////////////////////////
* Possible StringData Ref from Data Obj ->".\1212.ini"
|
//******************** Program Entry Point ********
:00401000 6800304000 push 00403000
:00401005 6A01
push 00000001
* Possible StringData Ref from Data Obj ->"count"
|
:00401007 6812304000 push 00403012
* Possible StringData Ref from Data Obj ->"kanxue"
|
:0040100C 680B304000 push 0040300B
* Reference To: KERNEL32.GetPrivateProfileIntA, Ord:011Fh //从1212.ini文件读取count的值
|
:00401011 E844000000 Call 0040105A
:00401016 B805000000 mov eax,
00000005
:0040101B 50
push eax //将EAX的值以十六进制格式写入1212.ini文件中(count)
* Possible StringData Ref from Data Obj ->"%#08lx"
|
:0040101C 6818304000 push 00403018
:00401021 681F304000 push 0040301F
* Reference To: USER32.wsprintfA, Ord:02A5h //字串格式转换为0x???????的形式
|
:00401026 E823000000 Call 0040104E
:0040102B 83C40C
add esp, 0000000C
* Possible StringData Ref from Data Obj ->".\1212.ini"
|
:0040102E 6800304000 push 00403000
:00401033 681F304000 push 0040301F
* Possible StringData Ref from Data Obj ->"count"
|
:00401038 6812304000 push 00403012
* Possible StringData Ref from Data Obj ->"kanxue"
|
:0040103D 680B304000 push 0040300B
* Reference To: KERNEL32.WritePrivateProfileStringA, Ord:02BFh//写入1212.ini文件
|
:00401042 E819000000 Call 00401060
:00401047 6A00
push 00000000
* Reference To: KERNEL32.ExitProcess, Ord:0075h
|
:00401049 E806000000 Call 00401054
/////////////////////////////////////////////////////////////////////
===========================================================================
第三部分:补丁程序
方案基本确定,现在就需要在原程序将上述三个函数功能加进去,打开程序Import表查看,所幸的是
GetPrivateProfileInt、wsprintf、WritePrivateProfileString三个函数原程序皆有,因此我们就省了构造
Import表这一块了。只需要bpx GetPrivateProfileInt等三个API设断拦截后,查看相应的机器码就可,如:
FF153CC35700 CALL `KERNEL32!GetPrivateProfileIntA`
因此调用GetPrivateProfileInt时的机器码就是FF153CC35700 。
用十六进制工具打开主程序,我们先找一空白地方放入三个函数的参数:
---------------------------------------------------------------------------------------
17B65E 90909090909090902E5C313231322E696E69006B616E .........\1212.ini.kan
17B674 78756500636F756E7400252330386C78000000000000 xue.count.%#08lx......
---------------------------------------------------------------------------------------
\1212.INI:对应的文件地址:0017B650,内存地址为0057B650
kanxue:对应的文件地址:0017B65B,内存地址为0057B65B
count :对应的文件地址:0017B662,内存地址为0057B662
%#08lx:对应的文件地址:0017B668,内存地址为0057B668
最后一步只需在程序找段空白处,将如下代码插入即可:
/////////////////////////////////////////////////////////////////////
修改后代码:
.................
.................
jmp
Lable01
Lable00: MOV EAX,[EBX+64]
PUSH EAX
//机器号入栈(一机一号,并在其服务器登记,只能用10天)
CALL `CHANNEL!CheckTryUserWhenLogOn`//在服务上验证用户合法性
CMP
EAX,BYTE -01
MOV
[ESP+30],EAX
JZ
Lable03//当EAX=FFFFFFFF时跳到Lable03,报“网络连接超时”
CMP
EAX,ESI
JNZ
004ABF51
JMP
Lable02//如EAX返回值为0,则跳到 Lable02处,将count加1.
.................
.................
Lable03: Callxxxxxx 网络连接超时
补丁代码一:(读取1212.ini文件count值)
Lable01: 6850B65700 PUSH
DWORD 0057B650 ->".\1212.ini"
6A01
PUSH BYTE +01
6862B65700
PUSH DWORD 0057B662 ->"count"
685BB65700
PUSH DWORD 0057B65B ->"kanxue"
FF153CC35700 CALL
`KERNEL32!GetPrivateProfileIntA`//读取count的值
E93008F3FF
JMP Lable00
补丁代码二:(将1212.ini文件count值加1)
Lable02: 6850B65700 PUSH DWORD
0057B650->".\1212.ini"
6A01
PUSH BYTE +01
6862B65700 PUSH
DWORD 0057B662->"count"
685BB65700 PUSH
DWORD 0057B65B ->"kanxue"
FF153CC35700 CALL
`KERNEL32!GetPrivateProfileIntA`//读取count值到EAX
40
INC EAX//count值加1
50
PUSH EAX
6868B65700 PUSH
DWORD 0057B668->"%#08lx"
686FB65700 PUSH
DWORD 0057B66F->"0000000000"
FF158CC65700 CALL
`USER32!wsprintfA`//将count的值转换成0x00000004之类形式
83C40C
ADD ESP,BYTE +0C //调整堆栈平衡(wsprintfA需手动调整堆栈)
(其它函数自动平衡堆栈)
6850B65700 PUSH
DWORD 0057B650->".\1212.ini"
686FB65700 PUSH
DWORD 0057B66F
6862B65700 PUSH
DWORD 0057B662->"count"
685BB65700 PUSH
DWORD 0057B65B"kanxue"
FF151CC35700 CALL
`KERNEL32!WritePrivateProfileStringA`//将计算过的count
//写入1212.ini文件
E9F10AF3FF JMP
Lable03
将程序修改后,在Win98下顺利运行,但在XP下报错(内存写超出范围),用工具PEditor打开程序,将文件检验和纠正(Checksum),此时程序就可在XP下顺利运行了.
===========================================================================
第四部分:结束
本文目的只是给大家一个思路,当遇到困难时,换个思路问题就迎刃而解了。由于时间创促,文章写的比较凌
乱,请将就着看吧。;)
- 标 题:文化财经。 (10千字)
- 作 者:kanxue
- 时 间:2002-3-9 18:01:43
- 链 接:http://bbs.pediy.com