• 标 题:保护机制小窥 ---- 十三少不妨来看看
  • 作 者:ljttt
  • 时 间:2000-12-3 18:00:3
  • 链 接:http://bbs.pediy.com

保护机制小窥

【声明】
我写文章以交流为主,希望大家在转载时能保持文章的完整性。

【前言】
这次以介绍保护机制为主,就不写什么脱壳方法了,其实以前我也误导了不少善良的观众。了解一个加壳软件最主要的应该是了解其保护机制,这也是我后来才领悟的一点。以下介绍的一点保护机制的知识。这里只为了让大家和我一起来共同学习和了解保护机制的知识。

水货作者:        ljttt
出厂日期:        2001-01-01    (水货的东东一般都这样写出厂日期的)

①、首先,我们来看个比较简单的自身保护方法。这段代码如下:

0040CC54: 03F3                      add esi,ebx                        <--esi指向某段代码,ecx为代码的长度(DWORD)
0040CC56: 8B06                      mov eax,dword ptr [esi]            <--取出代码中的 4 个字节
0040CC58: 33D0                      xor edx,eax                        <--XOR异或
0040CC5A: F7D2                      not edx                            <--取反
0040CC5C: 33D1                      xor edx,ecx                        <--与ecx(代码长度)异或
0040CC5E: 03D0                      add edx,eax                        <--与代码中的 4 个字节相加
0040CC60: 83C604                    add esi,00000004                    <--定位到代码中的下 4 个字节
0040CC63: 49                        dec ecx                            <--代码长度减一
0040CC64: 75F0                      jnz 0040CC56                        <--循环

先来描述一下这段代码的功能:可以看到这段代码用 ESI 作为指针,用 ECX 保存长度。把 ESI 指向的数据进行异或取反,最后在 EDX 中得到一个“值”。

那么它和自身保护有什么联系呢?我来简单说一下,如果 ESI = 0040CC54, ECX = 0040CC64 - 0040CC54。那么这段代码是不是就是把自身的代码进行运算(异或取反)呢? OK !

那么如果你这段代码中间设下一个断点,会有什么变化呢?运算得到的“值”会和原来未设断时的相同吗?

我们以SoftICE来简单介绍一下吧。当你在SoftICE的调试环境下,设下一个断点时,那么这个断点所在的代码的第一个字节就会变成 0xCC。
这样由于设下断点,代码就被改变了,那么运算的“值”就和未设断时不一样了。你可能想到有了这个值,就可以用比较的方法来判断是否自身是否被设下断点了。不错,这是一种方法,但是这很容易被......在加壳软件中,一般会把这个“值”作为还原密匙,来还原下一段代码。
也就是所谓的 SMC 技巧来还原代码。

(但是如果你在SoftICE中用 D 指令来显示这行代码,却没有发现变化。呵呵,但是,如果你再启动另一个调试器TRW2000,来显示这行代码,再看看?呵呵,明白了吗?)

②、SMC技巧来还原代码。
在加壳软件中,分段还原代码是一种很普遍的方法了。也许你有过这样的经历。(怎么,弄得象散文了?!)你跟踪过某个软件,发现了某段代码,比如 CPUID (相应的十六进制代码为 0x0F 0xA2)。却发现当你用十六进制编辑软件来查找 0x0F 0xA2 时,却怎么也找不到,Why?

其实道理很简单,就是在程序的可执行文件中保存的是加密了的数据,只有程序在运行时才会由程序在某处由一段还原代码来解密这段加密数据,(还原后的数据就是你在调试器中可以“看”到的真实的代码)。然后,程序才执行这段还原后的代码。现在你清楚了吧!比如象 ① 中 介绍的那段代码是用来形成还原密匙的,只有密匙正确时,也就是说它先四下“打量”一下,当“发现”自身没有被修改(或者被设下断点)时,才“悄悄地”还原出另一段被加密了的代码。然后继续!
有点意思吧!在加壳软件中,如果它“想”保护某段代码,就会加密它,然后在程序运行时用另一段代码形成密匙来还原它!这样就防止你静态分析它,当然为了防止你用动态跟踪的方法,所以它还结合了 ① 中介绍的自身保护方法。这样,如果你误入陷阱(在其自身保护的某处设下了一个断点),那么还原出的代码就是一堆垃圾代码。如果这样,你可不要写信给作者,说他程序编得有问题哦?!

(当然 SMC 技巧可以被用作多种用途。不要大家有先入为主的概念,SMC的技巧既可以被加壳软件用作自身保护的一种方法,也可以被Cracker作为一种破解的方法,所以......怎么用,还在于你!......你想用来编“病毒”.....老天,为什么天才总有点反叛因子!)

前面介绍了简单的自身保护技巧,也许你早就想好了对付它的方法,比如:
不设任何断点,用单步跟踪的方法
或者先跟踪一次记下正确的密匙,下次跟踪时任意设断,只在它取密匙时“给”个正确的给它。
或者用 BPM 断点只跟踪数据、不跟踪代码的方法进行动态跟踪。
你还可以想得更多。。。。

当然,为了更好地保护自身,所以在加壳软件中也不会只是这么简单地保护自己,你猜加壳软件中又会用到什么保护方法可以防止以上方法呢?

③、一种反跟踪方法:API调用的变形。
你也许喜欢首先在某个关键的API函数处设断,然后才进入程序代码中进行跟踪。但是在加壳软件中这种方法可就要小心了。
我们来看看这段代码:

015F:00411B6A  33C0                XOR      EAX,EAX                    <--ESI指向API函数地址的入口,如CreateFileA( )
015F:00411B6C  AC                  LODSB                                <--获取一个字节
015F:00411B6D  3C50                CMP      AL,50                        <--判断是否为 50
015F:00411B6F  720F                JB        00411B80                    <--小于 50 ,则跳转到 00411B80
015F:00411B71  3C57                CMP      AL,57                        <--判断是否为 57
015F:00411B73  770B                JA        00411B80                    <--大于 57 ,则跳转到 00411B80
......
015F:00411BD3  897A01              MOV      [EDX+01],EDI
015F:00411BD6  8B831F7B0000        MOV      EAX,[EBX+00007B1F]
015F:00411BDC  8B8B237B0000        MOV      ECX,[EBX+00007B23]
015F:00411BE2  8B93277B0000        MOV      EDX,[EBX+00007B27]
015F:00411BE8  8BBB3B7B0000        MOV      EDI,[EBX+00007B3B]
015F:00411BEE  8BB3377B0000        MOV      ESI,[EBX+00007B37]
015F:00411BF4  8BAB337B0000        MOV      EBP,[EBX+00007B33]
015F:00411BFA  8B9B2B7B0000        MOV      EBX,[EBX+00007B2B]
015F:00411C00  E900000000          JMP      00411C05                    <--此处的跳转将被修改为API函数地址内的一条指令处

(说明:进入这段代码时,ESI指向了某个 API 函数的入口地址,ESP指向的堆栈中压入了该 API 函数需要的各个参数。由于代码较长,没有完整列出)

以上这段代码的作用,就是通过分析(或者说反汇编) API 函数开始的部分代码,然后把这部分代码“复制”到自己的进程空间中执行后,再进入该 API 函数的内部某处代码继续执行 API 函数。

这样,当你在此 API 函数入口处(比如: bpx CreateFileA)设断时,就会“拦”不到它,为什么?因为,它不从 API 函数入口处执行。而是绕了个弯从“侧门”进入的。瞧,加壳软件多有意思!也许,这时你会想,那么就在 API 函数的内部某处代码设断不就行了吗?你要小心,加壳软件有善良的,也有喜欢“恶作剧”的,它通过分析 API 函数开始部分的代码,一方面进行“反汇编”,另一方面,如果你在它分析的代码中设下了断点时,也可能他会痛下杀手,因为断点的代码为 0xCC,它可不喜欢在 API 函数中出现这种指令。如果你由此当机了..........


④、反动态跟踪的方法。
当然,加壳软件也可能不只是把眼光放在防范你设断点,也可能直接就把“眼光”放在防范调试状态上了。比如最直接的就是检测你的当前环境中是否加载了调试器或者是某些工具软件。比如:

This method is most known as 'MeltICE' because it has been freely distributed
via www.winfiles.com. However it was first used by NuMega people to allow Symbol
Loader to check if SoftICE was active or not (the code is located inside nmtrans.dll).

The way it works is very simple:
It tries to open SoftICE drivers handles (SICE, SIWVID for Win9x, NTICE for WinNT)
with the CreateFileA API.

Here is a sample (checking for 'SICE'):

BOOL IsSoftIce95Loaded()
{
  HANDLE hFile; 
  hFile = CreateFile( "\\\\.\\SICE", GENERIC_READ | GENERIC_WRITE,
                      FILE_SHARE_READ | FILE_SHARE_WRITE,
                      NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  if( hFile != INVALID_HANDLE_VALUE )
  {
      CloseHandle(hFile);
      return TRUE;
  }
  return FALSE;
}

以上是摘自FrogsICE的文档。利用 CreateFile( ) 打开一些特殊的“文件”,如果返回值不是 -1,它就可以“发现”加载了SoftICE。当然这种方法由于可以在WIN98/NT下通用,所以很常见。
类似的,如果你把检测的字符串 \\.\SICE 改成
\\.\NTICE                检测NT下的SoftICE
\\.\FILEMON                检测FileMon
\\.\REGMON                检测RegMon
\\.\TRW                    检测Trw
\\.\TRWDEBUG            检测Trw
\\.\ICEDUMP                检测IceDump
就可以“发现”其他的跟踪了。

另一种常见的检测SoftICE的方法如下,同样摘自FrogsICE的文档。

* * SOFTICE SHOULD NOT BE LOADED SO THAT FROGSICE CAN DETECT THIS METHOD * *

This method of detection of SoftICE (as well as the following one) is
used by the majority of packers/encryptors found on Internet.
It seeks the signature of BoundsChecker in SoftICE

    mov    ebp, 04243484Bh        ; 'BCHK'
    mov    ax, 04h
    int    3     
    cmp    al,4
    jnz    SoftICE_Detected

其实检测SoftICE方法有很多,在 FrogsICE 的文档中介绍了一些,这里的介绍只是窥其一斑而已。如果你有最“新”的反跟踪方法,可一定要通知我哦。^_^


⑤、自身保护和反动态跟踪相结合:
最后我们来看看这段代码:

015F:0040DDD8  01FF                ADD      EDI,EDI
015F:0040DDDA  C783CB18000090010000MOV      DWORD PTR [EBX+000018CB],00000190
015F:0040DDE4  8BEB                MOV      EBP,EBX
015F:0040DDE6  BA561E0000          MOV      EDX,00001E56
015F:0040DDEB  03D3                ADD      EDX,EBX
015F:0040DDED  52                  PUSH      EDX
015F:0040DDEE  6467FF360000        PUSH      DWORD PTR FS:[0000]        <--SEH
015F:0040DDF4  646789260000        MOV      FS:[0000],ESP                <--SEH
015F:0040DDFA  89A3A3760000        MOV      [EBX+000076A3],ESP
015F:0040DE00  BECD1E0000          MOV      ESI,00001ECD
015F:0040DE05  03F3                ADD      ESI,EBX
015F:0040DE07  8BFE                MOV      EDI,ESI                    <--EDI=40DECD
015F:0040DE09  B90F0A0000          MOV      ECX,00000A0F                <--ECX=0xA0F,ECX保存的是循环次数
015F:0040DE0E  8B93FC760000        MOV      EDX,[EBX+000076FC]

;--------------------------------------------------------------------------------------------------
;以下这一段代码同 1 中介绍的代码类似。
;也就是把代码段 ( 015F:40D8E6 - 015F: 40DECA ) 之间的代码和 EDX 的初始值进行运算来形成一个“密匙”
;结果仍然保存在 EDX 中。
;--------------------------------------------------------------------------------------------------
015F:0040DE14  56                  PUSH      ESI                        <--循环开始处
015F:0040DE15  51                  PUSH      ECX                        <--入栈保存,ECX保存的是循环的次数=A0F。
015F:0040DE16  B979010000          MOV      ECX,00000179
015F:0040DE1B  BEE6180000          MOV      ESI,000018E6
015F:0040DE20  03F3                ADD      ESI,EBX                    <--ESI=40C000+18E6=40D8E6。(40C000是程序入口)
015F:0040DE22  8B06                MOV      EAX,[ESI]                    <--取代码中的 4 个字节
015F:0040DE24  33D0                XOR      EDX,EAX
015F:0040DE26  33D1                XOR      EDX,ECX
015F:0040DE28  83C604              ADD      ESI,04
015F:0040DE2B  49                  DEC      ECX                        <--循环次数减一,ECX初始值为179
015F:0040DE2C  75F4                JNZ      0040DE22                    <--这一段是和前面介绍的相同的方法进行自身保护
015F:0040DE2E  59                  POP      ECX                        <--出栈
015F:0040DE2F  5E                  POP      ESI                        <--出栈
;--------------------------------------------------------------------------------------------------
;这段代码用 EDX 中的“密匙”来还原 EDI 指向中的被加密了的代码,EDI初始值为 40DECD
;--------------------------------------------------------------------------------------------------
015F:0040DE30  AD                  LODSD
015F:0040DE31  33C2                XOR      EAX,EDX
015F:0040DE33  AB                  STOSD
;--------------------------------------------------------------------------------------------------
;这段代码用来进行反跟踪
;--------------------------------------------------------------------------------------------------
015F:0040DE34  0F018BA57A0000      SIDT      FWORD PTR [EBX+00007AA5]    <--取IDTR的内容
015F:0040DE3B  8BB3A77A0000        MOV      ESI,[EBX+00007AA7]            <--取IDT表基地址
015F:0040DE41  894E08              MOV      [ESI+08],ECX                <--修改 int 1 的处理程序地址为 ECX,让你死翘翘。
;--------------------------------------------------------------------------------------------------
;把“密匙”进行变换。
;--------------------------------------------------------------------------------------------------
015F:0040DE44  3393521E0000        XOR      EDX,[EBX+00001E52]
015F:0040DE4A  8BF7                MOV      ESI,EDI
015F:0040DE4C  EB70                JMP      0040DEBE
......(省略)
015F:0040DEBE  FF834E1E0000        INC      DWORD PTR [EBX+00001E4E]
015F:0040DEC4  33D1                XOR      EDX,ECX
;--------------------------------------------------------------------------------------------------
; 判断循环是否结束,即此时后面的所有被加密的代码都已经被还原
;--------------------------------------------------------------------------------------------------
015F:0040DEC6  49                  DEC      ECX                        <--循环次数减一,ECX初始值为A0F
015F:0040DEC7  0F8547FFFFFF        JNZ      0040DE14                    <--循环结束处
015F:0040DECD  5F                  POP      EDI                        <--被加密了的代码
015F:0040DECE  44                  INC      ESP                        <--未还原的代码

这段代码比较长,所以要看懂它得花点时间。这是一种把“反动态跟踪”和“自身保护”结合的一种方法。
可以看到 015F:0040DECD 之后的代码已经被加密了,这段代码就是用来还原被加密了的代码的。当这段代码循环结束后,后面的被加密了的代码就已经还原出来了,这就是 SMC 技巧的应用。在这段代码中“密匙”是由前面所有的代码来运算得到的。并且每循环一次就变换一次。这就是 1 中介绍的自身保护。防止你修改它的代码或者设下断点跟踪。
另外,程序中还加入了一种“反动态跟踪”的方法,就是修改了 单步中断 的中断处理程序的地址。这样,你的跟踪环境就被破坏了。

015F:0040DE34  0F018BA57A0000      SIDT      FWORD PTR [EBX+00007AA5]    <--取IDTR的内容
015F:0040DE3B  8BB3A77A0000        MOV      ESI,[EBX+00007AA7]            <--取IDT表基地址
015F:0040DE41  894E08              MOV      [ESI+08],ECX                <--修改 int 1 的处理程序地址为 ECX,让你死翘翘。


这就是加壳软件的特点,攻防结合。所以一不小心,你就可能落入了它设下的陷阱,见到了“如来佛”。所以为了防止被它送到西天。你就得下点功夫,了解自身保护、反跟踪的特点和技巧。加壳软件一般就是这样一个陷阱重重的地方。当然加壳软件的保护机制也有它的弱点,只要你的程序指令机器“读”得懂,那么动态跟踪不行,就有静态分析,或者动静结合。所以才会各种相应的脱壳机的出现。当然这是一个“矛”和“盾”的关系,孰强孰弱,我想关键在于运用得当和推陈出新。比如,我常会结合MD5/RSA/BLOWFISH等加密算法进行注册码计算,怎么样?!哈哈,然后用IF指令进行注册判断!?...................

【后记】
由于这些保护机制的方法出现的频率比较多,所以介绍一下也无妨。其实,这些东西很早就有了。但是对于生产水货的我,常常喜欢来点新瓶装旧酒。另外,我对ANTI-DEBUG了解得还不全面,也希望能够抛砖引玉,能引出更多的高手,让大家来进行打假。


【有个问题】
最近上网,总是拔不上来。
后来我打开了"拔号号出现终端窗口"的功能,发现拔号后会出现两种提示。比如:
Welcome to xxx xxx !
Welcome to yyy yyy !
如果出现第一种,那么我总是拔不上来,即使正常的密码也上不来,会出现而如果出现第二种提示,那么我总能拔上来。真奇怪!这会是什么原因?最近,上网实在是件痛苦的事件,总是很难拔上来,一般只有到0:00点以后才会正常。