保护机制小窥
【声明】
我写文章以交流为主,希望大家在转载时能保持文章的完整性。
【前言】
这次以介绍保护机制为主,就不写什么脱壳方法了,其实以前我也误导了不少善良的观众。了解一个加壳软件最主要的应该是了解其保护机制,这也是我后来才领悟的一点。以下介绍的一点保护机制的知识。这里只为了让大家和我一起来共同学习和了解保护机制的知识。
水货作者: 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了解得还不全面,也希望能够抛砖引玉,能引出更多的高手,让大家来进行打假。