使用 Visual Studio 和 C++ 创建 Shellcode
常规的编译阶段优化方法
基于编译阶段的常规优化方法
编译器的优化标志可以影响生成的二进制文件的大小、行为和检测难度。通过优化标志,可以使生成的二进制文件更难以被杀毒软件和EDR系统检测,提高绕过防御的成功率。
编译器选项
CL
编译阶段
cl.exe /TP /O1 /GS- /GL /EHsc /c xxx.cpp
cl.exe:调用Microsoft C/C++编译器。
/TP:将源文件编译为C++代码(即使文件扩展名不是.cpp)。
/O1:启用最低级别的优化,旨在减少代码大小。
/GS-:禁用缓冲区安全检查,通常用于防止缓冲区溢出攻击。
/GL:启用全局优化,包括跨模块优化(需要链接阶段支持)。
/EHsc:启用C++异常处理,假设仅捕获C++异常(extern “C”的函数不会抛出异常)。/c:只进行编译,不进行链接。生成对象文件(xxx.obj)。 xxx.cpp:要编译的源文件。
链接阶段
link.exe xxx.obj /SUBSYSTEM:CONSOLE /NODEFAULTLIB:msvcrt.lib /MT:libcmt.lib /OUT:xxx.exe /OPT:REF /OPT:ICF
link.exe:调用Microsoft链接器。
xxx.obj:要链接的对象文件。
/SUBSYSTEM:CONSOLE:指定子系统类型为控制台应用程序。
/NODEFAULTLIB:msvcrt.lib:不使用MSVC运行时库(动态链接版本)。
/MT:libcmt.lib:使用多线程、静态链接版本的C运行时库。
/OUT:xxx.exe:指定输出文件的名称(xxx.exe)。
/OPT:REF:消除未引用的函数和数据以减少输出文件大小。
/OPT:ICF:合并重复的函数和数据以减少输出文件大小。
G++
g++.exe -Os -s xxx.cpp -static -static-libgcc -static-libstdc++ -Wl,--subsystem,windows -o xxx.exe
-Os:启用针对代码大小的优化,即尽量生成较小的可执行文件。这个选项会尝试在不显著降低执行速度的情况下减小生成的二进制文件的大小。
-s:移除生成的可执行文件中的所有符号表和调试信息。这有助于进一步减小可执行文件的大小。
xxx.cpp:要编译的源文件。
-static:静态链接所有依赖库。默认情况下,g++可能会动态链接一些标准库或其他依赖库,而此选项强制将这些库静态链接到最终的可执行文件中,以便生成一个完全独立的二进制文件。
-static-libgcc:静态链接GCC的运行时库libgcc。libgcc包含一些必要的低级支持函数,默认情况下可能会动态链接。
-static-libstdc++:静态链接C++标准库libstdc++。类似于-static-libgcc,这个选项强制将C++标准库静态链接到可执行文件中。
-Wl,–subsystem,windows:传递选项给链接器(ld),指定子系统类型为Windows GUI子系统。这意味着生成的可执行文件将在Windows的图形用户界面环境中运行,而不是控制台环境。
#pragma comment(linker, “/subsystem:"windows" /entry:"mainCRTStartup"”)
只可以在MSVC中使用,在Mingw的gcc编译器中无法使用
-o xxx.exe:指定输出文件的名称(xxx.exe)
CLang
clang++.exe -O2 -Ob2 -Os -fno-stack-protector -g -Xlinker -subsystem:console -o xxx.exe xxx.c -luser32 -lkernel32 -fno-unroll-loops -fno-exceptions -fno-rtti
-O2:启用优化选项,进行较高水平的优化,提升运行时性能。
-Ob2:启用内联扩展,提升运行时性能。Ob2表示启用所有适用的内联。
-Os:启用针对代码大小的优化,尝试生成更小的二进制文件。这与 -O2 一起使用时会有相互协调的效果。
-fno-stack-protector:禁用栈保护机制,避免生成用于防止栈溢出的额外代码。
-g:生成调试信息,使得可执行文件可以在调试器中使用。
-Xlinker -subsystem:console:-Xlinker 选项告诉编译器将接下来的选项直接传递给链接器,-subsystem:console 指定生成的可执行文件是一个控制台应用程序。
-o xxx.exe:指定输出文件的名称(xxx.exe)。
xxx.c:要编译的源文件(C 文件)。
-luser32:链接用户界面库 user32,这是 Windows 提供的用于创建和管理用户界面的库。
-lkernel32:链接 Windows 内核库 kernel32,这是 Windows 提供的用于系统服务和管理的核心库。
-fno-unroll-loops:禁用循环展开优化,循环展开是一种优化技术,通过减少循环控制开销来提升性能。
-fno-exceptions:禁用异常处理支持,避免生成与异常处理相关的代码。
-fno-rtti:禁用运行时类型识别(RTTI),RTTI 用于在运行时识别对象的类型,禁用它可以减少代码的大小和运行时开销。
导入表处理
删除CRT库
C 运行时库(CRT)是 C 编程语言的标准接口,提供了函数和宏的集合,为标准 C 和 C++ 程序提供基本功能。CRT 包含以下几类函数:
内存管理函数:如 malloc、memset 和 free。 字符串操作函数:如 strcpy 和 strlen。 I/O 函数:如 printf、wprintf 和 scanf。
CRT 的 DLL 文件名为 vcruntimeXXX.dll,其中 XXX 为版本号。
除此之外,一些特定功能的 DLL 文件,如 api-ms-win-crt-stdio-l1-1-0.dll、api-ms-win-crt-runtime-l1-1-0.dll 和 api-ms-win-crt-locale-l1-1-0.dll,它们也链接到 CRT 库。
这些 DLL 文件在编译时由编译器链接,可以导入表 (IAT) 中找到。
多线程 (/MT)
将 Visual Studio 编译器配置为静态链接 CRT 函数。
忽略所有默认库
将“忽略所有默认库”选项设置为“是 (/NODEFAULTLIB)”,以防止编译器将默认系统库与程序链接。这将排除 CRT 库以及其他库的链接
报错汇总
“LNK2001 - 无法解析的外部符号 mainCRTStartup”
表示编译器找不到符号“mainCRTStartup”的定义。这是预期的,因为“mainCRTStartup”是与 CRT 库关联的程序的入口点,但这里并非如此。要解决此问题,应该设置一个新的入口点符号
Link -> Advanced -> Entry Point
“LNK2001 - 无法解析的外部符号 security_check_cookie”
“security_check_cookie”。该字符用于执行堆栈 cookie 检查,这是一项防止堆栈缓冲区溢出的安全功能。要解决此问题,请将安全检查选项设置为“禁用安全检查 (/Gs-)”
C/C++ -> Code Generation -> Security Check
自写函数
例如printf、memcpy、memset
#define PRINTA( STR, ... ) \
if (1) { \
LPSTR buf = (LPSTR)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, 1024 ); \
if ( buf != NULL ) { \
int len = wsprintfA( buf, STR, __VA_ARGS__ ); \
WriteConsoleA( GetStdHandle( STD_OUTPUT_HANDLE ), buf, len, NULL, NULL ); \
HeapFree( GetProcessHeap(), 0, buf ); \
} \
}
禁用 C++ 异常 启用
由于不再链接 CRT 库,因此应禁用该选项。
C/C++ -> Code Generation -> Enable C++ Exceptions
禁用整个程序优化
C/C++ -> Optimization -> Whole Program Optimization
禁用“生成调试信息
Linker -> Debugging -> Generate Debug Info
禁用“生成清单”
Linker -> Manifest File -> Generate Manifest
隐藏控制台窗口
Linker -> System -> SubSystem Windows(/SUBSYSTEM:WINDOWS)
IAT 伪装
使用虚拟 IAT
//在 if 条件内调用多个永远不会执行的 WinAPI 函数
int i = 0;
if (i > 1) {
unsigned __int64 i = MessageBoxA(NULL, NULL, NULL, NULL);
i = GetLastError();
i = SetCriticalSectionSpinCount(NULL, NULL);
i = GetWindowContextHelpId(NULL);
i = GetWindowLongPtrW(NULL, NULL);
i = RegisterClassW(NULL);
i = IsWindowVisible(NULL);
i = ConvertDefaultLocale(NULL);
i = MultiByteToWideChar(NULL, NULL, NULL, NULL, NULL, NULL);
i = IsDialogMessageW(NULL, NULL);
}
Obfuscator LLVM编译混淆
LLVM 是一个编译器基础设施,它将编译过程分为前端和后端两个部分,使得不同语言的源代码可以通过前端转换为中间表示(IR),后端则将IR转换为目标机器代码。以下是编译过程的三个主要步骤:
编译过程
1.前端
扫描器:进行词法分析,将代码转化为标记(tokens),这些标记是具有特定含义的字符串。
解析器:将标记分组生成抽象语法树(AST),代表源代码的结构和算法。
语义分析:主要进行类型检查,确保AST中没有错误,例如使用未初始化的变量或类型不匹配。
2.中间表示的生成
基于AST生成中间表示(IR),这是代码的更抽象、更易优化的形式。
3.优化
对IR进行优化,降低代码复杂性而不改变程序逻辑。这包括预先计算等方法。
4.后端
将优化后的IR转换为目标输出,如汇编代码或字节码。
LLVM的核心思想
LLVM的设计思想是将编译器分为前端和后端两个部分:
前端:负责将源代码转换为中间表示(IR)。
后端:负责将IR转换为目标机器代码。
这种设计使得LLVM能够支持多种编程语言,只需为每种语言编写一个前端,而可以利用通用的后端支持多种目标架构。
OLLVM(Obfuscator-LLVM)
OLLVM是基于LLVM框架的混淆器,用于增强程序的安全性。它通过多种混淆操作使得程序更难以理解和分析。
主要混淆操作
代码替换:使用复杂的代码替换简单的代码。
函数内联:将函数调用内联展开,增加分析难度。
控制流平坦化:改变程序的控制流,使其更难跟踪。
加密:加密程序中的字符串和数据。
额外安全机制
字符串加密:防止直接读取字符串内容。
栈保护:防止栈溢出攻击。
位置无关代码:增加程序的安全性和灵活性。
与LLVM工具集成
由于OLLVM基于LLVM框架开发,它可以与LLVM的其他工具(如Clang和LLDB)无缝集成,开发者可以在现有的开发环境中使用OLLVM,并利用现有工具进行调试和分析。
应用领域
尽管OLLVM的主要目标是提高程序的安全性,但它也可以用于以下领域:
代码保护:防止代码被盗用或逆向工程。
代码压缩:通过混淆技术减少代码体积。
代码优化:提升代码执行效率。
用法
Visual Studio C++ 项目,在项目属性中,将平台工具集设置为 LLVM (clang-cl)。
命令行 -> 其他选项
-mllvm -bcf -mllvm -bcf_prob=73 -mllvm -bcf_loop=1 -mllvm -sub -mllvm -sub_loop=5 -mllvm -fla -mllvm -split_num=5 -mllvm -aesSeed=ABCD1234EF567890ABCD1234EF567890
参考:https://trustedsec.com/blog/behind-the-code-assessing-public-compile-time-obfuscators-for-enhanced-opsec