免杀的个人经验及技巧详谈
序
回望免杀的初始:用WinHex逐字节修改木马免杀 -> 到后来软件保护(加壳)的加入、特征码免杀技术的公开、多层加壳的发现、反调试技术、针对于PE文件格式的免杀,到如今盛行的源码免杀。
再看反病毒的历程:从文件扫描技术、到通配符扫描技术、内存特征码扫描技术、虚拟机扫描技术、主动防御、启发式病毒查杀技术、云查杀。
我们不难发现,反病毒与反-反病毒这两项处于对立面的技术慢慢的壮大,并形成了各自所拥有的领域。
我们很想让我们心爱的黑软、木马在遇到杀软时依旧装TM牛B,反-反病毒技术的重要性就可想而知了。
但,任何技术都需要一个最简单的东西引领你:
C语言,从一个简单的"Hello World!"开始;
而反-反病毒:我想,还是得从MyCCL来打开免杀世界的大门……
这里,MyCCL陪你开始.
(注:这里只谈及使用技巧,非教程)
分块个数
体积小于200KB的木马在最开始选择50-100之间.
体积大于200KB的木马在最开始选择10-30之间.
尔后由大到小缩小范围定位。
单位长度
事实上单位长度是由文件大小和分块个数决定。相反,分块个数由文件大小和单位长度决定。单位长度决定了每次填充的区域大小。
计算公式:
文件大小 ÷ 分块个数 == 单位长度
(其它自己推)
填充方式
不仅限于00、20或者90,可以是任意00-FF之间的16进制数值。
相比单一的填充方式,MultiCCL的随机数值填充方式更值得借鉴。
开始位置、结束位置、分段长度
开始位置
决定了从特定的偏移处开始填充,默认情况下在E0处开始填充。有时候杀软会将特征码假定在PE头,因此可以填第一个区段的偏移量,一般第一个区段为.text,起始位置为00000600。
结束位置
一般情况下为文件的末尾。
分段长度
从 开始位置 到 结束位置 所经过的偏移大小。
而某些情况下,若要对某个特定区段进行特征码定位。可以在 开始位置 填入区段的起始位置,在 结束位置 填入区段的结束地址,然后进行常规的定位流程。
正反填充
一般情况下,进行正向填充。若定位时进入死循环或者定位错误,可选择反向定位。
定位精度
个人喜欢定位到4个字节的大小,因为一般特征码大小为4个字节的大小,甚至更大。2个字节大小的特征码许多人修改时在潜意识中会把特征码定义为2个字节,换句话说就是下定决心跟这条特征码干上了。而这样的思维定视会严重影响你的免杀质量和效率,还有心情。
从杀软的角度来看,越小的特征码越容易出现误杀。作为专业的杀毒软件来说,出现误杀的情况是不允许的,当出现这样的情况,会使用户感到困扰和不安。所以他们尽量会选择适当大小的特征码。
MultiCCL
由于这款工具功能的强大性和易使用性,这里只对MultiCCL进行一些说明:
MultiCCL定位出特征码的大小是精确的,MyCCL定位精度是由使用者的决定的。而特征码定位长度精确的代价是花费更长的定位时间。(对于这里的定位精确,是指最终定位出的特征码大小,非定位时的定位精度。)
但是回报也是非常可观的,精确的特征码大小告诉了你可以在多大的范围内修改。
若在免杀时遇到疑难特征码、或者在修改多次失败时,可将那段特征码保护起来,再进行定位。有一个例子就是曾经瑞星把特征码定位在了TLS表,当时修改多次出现错误。无奈将TLS保护起来时,重新定位,结果发现了新大陆......
个人习惯把木马的输入表保护起来,再进行定位。
特征码修改技巧
指令互换
代码:ADD EAX,521
ADC EAX,521
SUB EAX,521
SBB EAX,521
SUB EAX,521
ADD EAX,-521
TEST EAX,EAX
AND EAX,EAX
OR EAX,EAX
AND EAX,EAX
CMP EAX,EAX
OR EAX,EAX
XOR EAX,EAX
MOV EAX,0
(XOR EAX,EAX为对寄存器进行清0操作,与赋值为0其结果无异。ps:是否有误?)
PUSH 0
PUSH (EAX\EBX\ECX\EDX,若它们其中一个寄存器在当时的值为空)
JLE Address
JBE Address
JB Address
JL Address
JNB Address
JNL Address
???
NOP(其余见8086指令参考手册)
指令位移
这个是我最喜欢的修改方式之一。
若发现特征码上句或下句与本身的操作数不同,则可进行位移。需要注意的是必须下面的指令先移到上面,再将上面的指令移动到下面。否则若下面的指令大小大于上面的指令,先对上面的指令进行移动后,会破坏原先的完整性。
代码:MOV EAX,EBX
PUSH 0
PUSH 0
CALL xxxxxxxx
修改后:
PUSH 0
MOV EAX,EBX
PUSH 0
CALL xxxxxxxx(关于更多的位移演示,可以看看我朋友alph4曾经写的“灰鸽子,依稀依然的邪恶”这篇文章。里面提到了很多指令位移的使用。)
字符串
对于字符串类型的特征码,除了NOP、00填充、修改字符大小写之外,还可对字符类型进行移动。
步骤:
1、用C32载入,记录你想修改的字符串的偏移量。
2、用LPE载入,在"FLC"里的Offset处输入记录的偏移量,记录下计算出的VA值。
3、用OD载入,搜索你想修改的字符串,搜索到后双击。把MOV指令后面的地址改为记录的VA值,保存。
4、回到C32,将原来的字符串00填充。
不仅仅在于特征码修改,很多时候都可以这么用。
二重跳
特征码:
代码:0x400001 JMP 0x400201 ;假设的地址
........
........
0x400201 xxxxxxxx ;some codeJMP的最终地址是0x400201,那么多跳一次,就构成了二重跳。
比作去学校,我们可以直接去学校;也可以经过图书馆,再去学校。只要目的地不变,就可以。
修改后:
代码:0x400001 JMP 0x400101 ;一块空白区
........
........
0x400101 JMP 0x400201 ;在空白区填上我们的特征码
........
........
0x400201 xxxxxxxx ;some code还有一种情况:
代码:0x400001 CMP EAX,0
0x400002 JNZ 0x400201
0x400003 xxxxxxxx ;some code
........
........
0x400201 xxxxxxxx ;some code很多人会直接把JNZ 0x400201这句改为JMP 0x400201,但这样做是错的。为什么呢,因为JNZ是一个条件跳转指令,它只有在条件成立时才跳转。
从8086指令手册里我们可以知道JNZ是当条件不等于时才进行跳转,而JMP则是无条件跳转指令。不要忘了,JNZ前面还有个CMP指令,通过这个指令我们显然知道程序原本是想让在EAX不等于0时进行跳转,如果鲁莽的改为JMP,那么即使EAX为0,那么程序也会进行跳转。这样的修改会增加出错的几率。
但我们可以修改的是,把判断和进行跳转的流程转到一个空白区,修改后:
代码:0x400001 NOP ;NOP之
0x400002 JMP 0x400101 ;空白区的地址
0x400003 xxxxxxxx ;some code
........
........
0x400101 CMP EAX,0
0x400102 JNZ 0x400201
0x400103 JMP 0x400003
........
........
0x400201 xxxxxxxx ;some code关于内存地址0x400103那个JMP,是因为若CMP EAX,0成立的时候,显然JNZ跳转时不会执行的。在EAX等于0时,程序要执行JNZ后面的代码,由于JNZ已经移动到另一个区域了,为了程序能继续完成流程,所以还需要一个JMP跳回那后面的地址。
CALL指令
CALL +1、-1
这里想要提的是CALL -1这种方法。有时候,CALL -1的时候并未出错,却修改保存后双击没有反应。笔者曾经遇到过这种一个情况:
代码:0x400001 CALL 0x400050
........
........
0x400050 xxxxxxxx ;some code使用了CALL -1后程序出现了上面提到的状况,于是想跟进入,看看CALL的内容。无意间按了键盘的向上键,发现了新大陆:
代码:0x400001 CALL 0x400050
........
........
0x400049 ADD [EAX],AL ;这段代码为在16进制中显示00,即空。
0x400050 xxxxxxxx ;some code原来是CALL遇到了00直接使程序退出,但是出现了两种修改方法。
1、将0x400049处的ADD [EAX],AL这段代码NOP。
2、将0x400050的代码NOP,移动到0x400049处。
方法一:
代码:0x400001 CALL 0x400050
........
........
0x400049 NOP
0x400050 xxxxxxxx ;some code方法二:
代码:0x400001 CALL 0x400050
........
........
0x400049 xxxxxxxx ;some code
0x400050 NOP参数修改法
若杀软将某个子程序段作为特征码,可以在周围修改。下面这个例子:
代码:0x400076 MOV EAX,EDX
0x400077 PUSH 0 ;parameter 1
0x400078 PUSH 0 ;parameter 2
0x400079 PUSH EAX ;parameter 3
0x400080 CALL xxxxxxx ;call it可以尝试将MOV EAX,EDX改为MOV EBX,EDX,然后把下面的PUSH EAX改为PUSH EBX。
或者把PUSH 0改为PUSH ECX等。
修改的寄存器要确定为空或之前未被其它重要代码使用,否则可能出错!
输入表修改
函数A改W
MessageBoxA
MessageBoxW
这样做修改的原理是:在Win32环境中,WinAPI函数有两个版本,一个为ASCII版本,还有一个则为Unicode版本。
在函数最后加A则表示为ASCII版本,而Unicode的表现形式为W。
在某些情况下,这样修改的修改可以成功。但笔者不保证其修改方法的一定性!
(有时候会出错的原因我估计:可能在函数参数传入的时候,由于使用了不同的编码模式(一个为窄字符型,一个为宽字元版),参数传入类型错误,从而导致了函数的调用失败,程序才会崩溃。还望牛人指点)
以上是个人特征码修改的一些经验和技巧,有些方法可能针对性很强,局限性很大。但对于修改特征码而言,修改方法是死的,思路才是活的。希望不要照搬本文的方法,要适当的进行修改。由于其他输入输出表免杀方法已经多如牛毛了,所以这里只谈一点个人的经验。
# By Miku_fl0
# 2010.06.08
序
回望免杀的初始:用WinHex逐字节修改木马免杀 -> 到后来软件保护(加壳)的加入、特征码免杀技术的公开、多层加壳的发现、反调试技术、针对于PE文件格式的免杀,到如今盛行的源码免杀。
再看反病毒的历程:从文件扫描技术、到通配符扫描技术、内存特征码扫描技术、虚拟机扫描技术、主动防御、启发式病毒查杀技术、云查杀。
我们不难发现,反病毒与反-反病毒这两项处于对立面的技术慢慢的壮大,并形成了各自所拥有的领域。
我们很想让我们心爱的黑软、木马在遇到杀软时依旧装TM牛B,反-反病毒技术的重要性就可想而知了。
但,任何技术都需要一个最简单的东西引领你:
C语言,从一个简单的"Hello World!"开始;
而反-反病毒:我想,还是得从MyCCL来打开免杀世界的大门……
这里,MyCCL陪你开始.
(注:这里只谈及使用技巧,非教程)
分块个数
体积小于200KB的木马在最开始选择50-100之间.
体积大于200KB的木马在最开始选择10-30之间.
尔后由大到小缩小范围定位。
单位长度
事实上单位长度是由文件大小和分块个数决定。相反,分块个数由文件大小和单位长度决定。单位长度决定了每次填充的区域大小。
计算公式:
文件大小 ÷ 分块个数 == 单位长度
(其它自己推)
填充方式
不仅限于00、20或者90,可以是任意00-FF之间的16进制数值。
相比单一的填充方式,MultiCCL的随机数值填充方式更值得借鉴。
开始位置、结束位置、分段长度
开始位置
决定了从特定的偏移处开始填充,默认情况下在E0处开始填充。有时候杀软会将特征码假定在PE头,因此可以填第一个区段的偏移量,一般第一个区段为.text,起始位置为00000600。
结束位置
一般情况下为文件的末尾。
分段长度
从 开始位置 到 结束位置 所经过的偏移大小。
而某些情况下,若要对某个特定区段进行特征码定位。可以在 开始位置 填入区段的起始位置,在 结束位置 填入区段的结束地址,然后进行常规的定位流程。
正反填充
一般情况下,进行正向填充。若定位时进入死循环或者定位错误,可选择反向定位。
定位精度
个人喜欢定位到4个字节的大小,因为一般特征码大小为4个字节的大小,甚至更大。2个字节大小的特征码许多人修改时在潜意识中会把特征码定义为2个字节,换句话说就是下定决心跟这条特征码干上了。而这样的思维定视会严重影响你的免杀质量和效率,还有心情。
从杀软的角度来看,越小的特征码越容易出现误杀。作为专业的杀毒软件来说,出现误杀的情况是不允许的,当出现这样的情况,会使用户感到困扰和不安。所以他们尽量会选择适当大小的特征码。
MultiCCL
由于这款工具功能的强大性和易使用性,这里只对MultiCCL进行一些说明:
MultiCCL定位出特征码的大小是精确的,MyCCL定位精度是由使用者的决定的。而特征码定位长度精确的代价是花费更长的定位时间。(对于这里的定位精确,是指最终定位出的特征码大小,非定位时的定位精度。)
但是回报也是非常可观的,精确的特征码大小告诉了你可以在多大的范围内修改。
若在免杀时遇到疑难特征码、或者在修改多次失败时,可将那段特征码保护起来,再进行定位。有一个例子就是曾经瑞星把特征码定位在了TLS表,当时修改多次出现错误。无奈将TLS保护起来时,重新定位,结果发现了新大陆......
个人习惯把木马的输入表保护起来,再进行定位。
特征码修改技巧
指令互换
代码:ADD EAX,521
ADC EAX,521
SUB EAX,521
SBB EAX,521
SUB EAX,521
ADD EAX,-521
TEST EAX,EAX
AND EAX,EAX
OR EAX,EAX
AND EAX,EAX
CMP EAX,EAX
OR EAX,EAX
XOR EAX,EAX
MOV EAX,0
(XOR EAX,EAX为对寄存器进行清0操作,与赋值为0其结果无异。ps:是否有误?)
PUSH 0
PUSH (EAX\EBX\ECX\EDX,若它们其中一个寄存器在当时的值为空)
JLE Address
JBE Address
JB Address
JL Address
JNB Address
JNL Address
???
NOP(其余见8086指令参考手册)
指令位移
这个是我最喜欢的修改方式之一。
若发现特征码上句或下句与本身的操作数不同,则可进行位移。需要注意的是必须下面的指令先移到上面,再将上面的指令移动到下面。否则若下面的指令大小大于上面的指令,先对上面的指令进行移动后,会破坏原先的完整性。
代码:MOV EAX,EBX
PUSH 0
PUSH 0
CALL xxxxxxxx
修改后:
PUSH 0
MOV EAX,EBX
PUSH 0
CALL xxxxxxxx(关于更多的位移演示,可以看看我朋友alph4曾经写的“灰鸽子,依稀依然的邪恶”这篇文章。里面提到了很多指令位移的使用。)
字符串
对于字符串类型的特征码,除了NOP、00填充、修改字符大小写之外,还可对字符类型进行移动。
步骤:
1、用C32载入,记录你想修改的字符串的偏移量。
2、用LPE载入,在"FLC"里的Offset处输入记录的偏移量,记录下计算出的VA值。
3、用OD载入,搜索你想修改的字符串,搜索到后双击。把MOV指令后面的地址改为记录的VA值,保存。
4、回到C32,将原来的字符串00填充。
不仅仅在于特征码修改,很多时候都可以这么用。
二重跳
特征码:
代码:0x400001 JMP 0x400201 ;假设的地址
........
........
0x400201 xxxxxxxx ;some codeJMP的最终地址是0x400201,那么多跳一次,就构成了二重跳。
比作去学校,我们可以直接去学校;也可以经过图书馆,再去学校。只要目的地不变,就可以。
修改后:
代码:0x400001 JMP 0x400101 ;一块空白区
........
........
0x400101 JMP 0x400201 ;在空白区填上我们的特征码
........
........
0x400201 xxxxxxxx ;some code还有一种情况:
代码:0x400001 CMP EAX,0
0x400002 JNZ 0x400201
0x400003 xxxxxxxx ;some code
........
........
0x400201 xxxxxxxx ;some code很多人会直接把JNZ 0x400201这句改为JMP 0x400201,但这样做是错的。为什么呢,因为JNZ是一个条件跳转指令,它只有在条件成立时才跳转。
从8086指令手册里我们可以知道JNZ是当条件不等于时才进行跳转,而JMP则是无条件跳转指令。不要忘了,JNZ前面还有个CMP指令,通过这个指令我们显然知道程序原本是想让在EAX不等于0时进行跳转,如果鲁莽的改为JMP,那么即使EAX为0,那么程序也会进行跳转。这样的修改会增加出错的几率。
但我们可以修改的是,把判断和进行跳转的流程转到一个空白区,修改后:
代码:0x400001 NOP ;NOP之
0x400002 JMP 0x400101 ;空白区的地址
0x400003 xxxxxxxx ;some code
........
........
0x400101 CMP EAX,0
0x400102 JNZ 0x400201
0x400103 JMP 0x400003
........
........
0x400201 xxxxxxxx ;some code关于内存地址0x400103那个JMP,是因为若CMP EAX,0成立的时候,显然JNZ跳转时不会执行的。在EAX等于0时,程序要执行JNZ后面的代码,由于JNZ已经移动到另一个区域了,为了程序能继续完成流程,所以还需要一个JMP跳回那后面的地址。
CALL指令
CALL +1、-1
这里想要提的是CALL -1这种方法。有时候,CALL -1的时候并未出错,却修改保存后双击没有反应。笔者曾经遇到过这种一个情况:
代码:0x400001 CALL 0x400050
........
........
0x400050 xxxxxxxx ;some code使用了CALL -1后程序出现了上面提到的状况,于是想跟进入,看看CALL的内容。无意间按了键盘的向上键,发现了新大陆:
代码:0x400001 CALL 0x400050
........
........
0x400049 ADD [EAX],AL ;这段代码为在16进制中显示00,即空。
0x400050 xxxxxxxx ;some code原来是CALL遇到了00直接使程序退出,但是出现了两种修改方法。
1、将0x400049处的ADD [EAX],AL这段代码NOP。
2、将0x400050的代码NOP,移动到0x400049处。
方法一:
代码:0x400001 CALL 0x400050
........
........
0x400049 NOP
0x400050 xxxxxxxx ;some code方法二:
代码:0x400001 CALL 0x400050
........
........
0x400049 xxxxxxxx ;some code
0x400050 NOP参数修改法
若杀软将某个子程序段作为特征码,可以在周围修改。下面这个例子:
代码:0x400076 MOV EAX,EDX
0x400077 PUSH 0 ;parameter 1
0x400078 PUSH 0 ;parameter 2
0x400079 PUSH EAX ;parameter 3
0x400080 CALL xxxxxxx ;call it可以尝试将MOV EAX,EDX改为MOV EBX,EDX,然后把下面的PUSH EAX改为PUSH EBX。
或者把PUSH 0改为PUSH ECX等。
修改的寄存器要确定为空或之前未被其它重要代码使用,否则可能出错!
输入表修改
函数A改W
MessageBoxA
MessageBoxW
这样做修改的原理是:在Win32环境中,WinAPI函数有两个版本,一个为ASCII版本,还有一个则为Unicode版本。
在函数最后加A则表示为ASCII版本,而Unicode的表现形式为W。
在某些情况下,这样修改的修改可以成功。但笔者不保证其修改方法的一定性!
(有时候会出错的原因我估计:可能在函数参数传入的时候,由于使用了不同的编码模式(一个为窄字符型,一个为宽字元版),参数传入类型错误,从而导致了函数的调用失败,程序才会崩溃。还望牛人指点)
以上是个人特征码修改的一些经验和技巧,有些方法可能针对性很强,局限性很大。但对于修改特征码而言,修改方法是死的,思路才是活的。希望不要照搬本文的方法,要适当的进行修改。由于其他输入输出表免杀方法已经多如牛毛了,所以这里只谈一点个人的经验。
# By Miku_fl0
# 2010.06.08