• 标 题:文化财经。 (10千字)
  • 作 者:kanxue
  • 时 间:2002-3-9 18:01:43
  • 链 接:http://bbs.pediy.com

标    题: 补丁技术应用
关于本文:本文主要目的在于教学,让初学者如何应用补丁技术修补程序,只从技术角度探讨一下
          请勿将此教程用于商业目的。在这首先感谢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下顺利运行了.
===========================================================================

第四部分:结束

本文目的只是给大家一个思路,当遇到困难时,换个思路问题就迎刃而解了。由于时间创促,文章写的比较凌

乱,请将就着看吧。;)