annihilatorq/shadow_syscall
GitHub: annihilatorq/shadow_syscall
基于 C++20 的 Windows 直接系统调用与模块解析库,通过编译期哈希和 shellcode 执行实现无字符串、无导入表残留的底层操作封装。
Stars: 234 | Forks: 22
# shadow syscalls
易于使用的 syscall/import 执行器封装。Syscall 基于 shellcode。在参数中传入的函数名会在编译期进行哈希处理。
支持 x86 架构,但在 x86 下无法使用 `.shadowsyscall()`。
本仓库为底层操作提供了一个便捷的高级封装,以及基于范围的模块及其导出枚举器。以更优雅的封装形式包含了所有 `GetModuleHandle` 和 `GetProcAddress` 的实现,且不会在二进制文件中留下任何字符串。
允许调用未公开的 DLL 函数。内置了转发导入解析器(HeapAlloc 等)。
### 支持的平台
CLANG, GCC, MSVC。库要求 C++20。
### 快速示例
```
// Execute "NtTerminateProcess" syscall
shadowsyscall( "NtTerminateProcess", reinterpret_cast< HANDLE >( -1 ), -6932 );
// Since version 1.2, the return type may not be specified
shadowsyscall( "NtTerminateProcess", reinterpret_cast< HANDLE >( -1 ), -6932 );
// Execute any export at runtime
// Since version 1.2, the return type may not be specified
shadowcall( "MessageBoxA", nullptr, "string 1", "string 2", MB_OK );
```
Shellcode 使用了基于 `NtAllocateVirtualMemory` 和 `NtFreeVirtualMemory` 的分配器
## 详细的执行器示例 (x64)
```
#include
#include
#include "shadowsyscall.hpp"
// If "set_custom_ssn_parser" was called, the handling
// of the syscall index is entirely the user's responsibility
//
// This function is gonna be called once if caching is enabled.
// If not, the function will be called on every syscall
std::optional custom_ssn_parser(shadow::syscaller& instance,
shadow::address_t export_address) {
if (!export_address) {
instance.set_last_error(shadow::error::ssn_not_found);
return std::nullopt;
}
return *export_address.ptr(4);
}
// Pass the function name as a string, and it will be converted
// into a number at compile-time by the hash64_t constructor
void execute_syscall_with_custom_ssn_parser(shadow::hash64_t function_name) {
shadow::syscaller sc{function_name};
sc.set_ssn_parser(custom_ssn_parser);
auto current_process = reinterpret_cast(-1);
std::uintptr_t debug_port{0};
auto status = sc(current_process, 7, &debug_port, sizeof(std::uintptr_t), nullptr);
if (auto error = sc.last_error(); error)
std::cerr << "Syscall error occurred: " << error.value() << '\n';
std::cout << "NtQueryInformationProcess status: 0x" << std::hex << status
<< ", debug port is: " << debug_port << "\n";
}
int main() {
execute_syscall_with_custom_ssn_parser("NtQueryInformationProcess");
// This is a replacement for: LoadLibraryA("user32.dll");
//
// Since LoadLibraryA is a function implemented in kernelbase.dll,
// and kernelbase.dll is a "pinned" DLL module, it is,
// guaranteed to be loaded into the process.
shadowcall("LoadLibraryA", "user32.dll");
// When we know which DLL the export is in, we can specify it so
// that we don't have to iterate through the exports of all DLLs
shadowcall({"MessageBoxA", "user32.dll"}, nullptr, "string 1", "string 2", MB_OK);
// Get a wrapper for lazy importing, but with the
// ability to get details of a DLL export
shadow::importer message_box_import("MessageBoxA");
int message_box_result = message_box_import(nullptr, "string 3", "string 4", MB_OK);
std::cout << "MessageBoxA returned: " << message_box_result
<< "; import data is: " << message_box_import.exported_symbol() << '\n';
HANDLE thread_handle = nullptr;
const auto current_process = reinterpret_cast(-1);
auto start_routine = [](void*) -> DWORD {
std::cout << "\nHello from thread " << std::this_thread::get_id() << "\n";
return 0;
};
// 1. Handle syscall failure
// Return type may not be specified since v1.2
shadow::syscaller create_thread_sc("NtCreateThreadEx");
auto create_thread_status = create_thread_sc(
&thread_handle, THREAD_ALL_ACCESS, NULL, current_process,
static_cast(start_routine), 0, FALSE, NULL, NULL, NULL, 0);
if (auto error = create_thread_sc.last_error(); error)
std::cout << "NtCreateThreadEx error occurred: " << error.value() << "\n";
else
std::cout << "NtCreateThreadEx call status: 0x" << std::hex << create_thread_status << '\n';
// 2. When error handling is not required, get a plain return value
auto simple_status = shadowsyscall("NtTerminateProcess", reinterpret_cast(-1), -6932);
}
```
## 详细的模块与共享数据解析器示例
```
#include "shadowsyscall.hpp"
template
void debug_log(const std::format_string fmt, Args&&... args) {
std::cout << std::format(fmt, std::forward(args)...) << '\n';
}
template
void log_value(std::string_view label, Args&&... args) {
constexpr int column_width = 32;
std::cout << std::format("{:>{}}: ", label, column_width);
if constexpr (sizeof...(Args) > 0) {
(std::cout << ... << std::forward(args));
}
std::cout << '\n';
}
void new_line(int count = 1) {
for (int i = 0; i < count; i++)
std::cout.put('\n');
}
#define obfuscate_string(str) \
[]() { \
/* Any string obfuscation */ \
return str; \
}
int main() {
// Enumerate every dll loaded into current process
const auto dlls = shadow::dlls();
debug_log("List of DLLs loaded in the current process:");
for (const auto& dll : shadow::dlls())
debug_log("{} : {}", dll.filepath().string(), dll.native_handle());
new_line();
// Find a specific DLL loaded into the current process.
// "ntdll.dll" doesn't leave the string in the executable -
// it’s hashed at compile time (consteval guarantee)
// The implementation doesn't care about the ".dll" suffix
// after compilation it will become 384989384324938
auto ntdll_name_hash = shadow::hash64_t{"ntdll"};
auto ntdll = shadow::dll(ntdll_name_hash);
auto base_module = shadow::base_module();
debug_log("Current .exe filepath: {}", base_module.filepath().string());
debug_log("Current .text section checksum: {}",
base_module.section_checksum(".text"));
debug_log("Current module handle: {}",
base_module.native_handle()); // same as GetModuleHandle(nullptr)
new_line();
auto image = ntdll.image();
auto sections = image->get_nt_headers()->sections();
auto optional_header = image->get_optional_header();
auto exports = ntdll.exports();
constexpr int export_entries_count = 5;
const auto first_n_exports = exports | std::views::take(export_entries_count);
debug_log("{} first exports of ntdll.dll", export_entries_count);
for (const shadow::win::export_t& exp : first_n_exports) {
const auto& [name, address, ordinal] = std::make_tuple(exp.name, exp.address, exp.ordinal);
debug_log("{} : {} : {}", name, address.ptr(), ordinal);
}
new_line();
auto it = exports.find_if([](const shadow::win::export_t& export_data) -> bool {
// after compilation becomes 384989384324938
constexpr auto compiletime_hash = shadow::hash64_t{"NtQuerySystemInformation"};
// operator() (called in runtime) accepts any range that have access by index
const auto runtime_hash = shadow::hash64_t{}(export_data.name);
return compiletime_hash == runtime_hash;
});
if (it == exports.end()) {
debug_log("Failed to find NtQuerySystemInformation export");
return 1;
}
const auto& export_data = *it;
debug_log("Export {} VA is {}", export_data.name, export_data.address.ptr());
constexpr int ordinal = 10;
const auto& ordinal_export =
shadow::exported_symbol(shadow::use_ordinal, ntdll_name_hash, ordinal);
debug_log("Export on ordinal {} in ntdll.dll is presented on VA {}", ordinal,
ordinal_export.address().ptr());
// "location" returns a DLL struct that contains this export
std::filesystem::path dll_path = shadow::exported_symbol("Sleep").location().name().to_path();
debug_log("The DLL that contains the Sleep export is: {}", dll_path.string());
new_line(2);
log_value("[NTDLL]");
log_value("Base Address", ntdll.base_address().ptr());
log_value("Native Handle", ntdll.native_handle());
log_value("Entry Point", ntdll.entry_point());
log_value("Name", ntdll.name().string());
log_value("Path to File", ntdll.filepath().to_path());
log_value("Reference count", ntdll.reference_count());
log_value("Image Size", optional_header->size_image);
log_value("Sections count", std::ranges::size(sections));
log_value("Exports count", exports.size());
new_line();
// shared_data parses KUSER_SHARED_DATA
// The class is a high-level wrapper for parsing,
// which saves you from pointer arithmetic
auto shared = shadow::shared_data();
// shared_data() implements the most popular getters, while you
// can access the entire KUSER_SHARED_DATA structure as follows:
auto kuser_shared_data = shared.get();
// This structure weighs 3528 bytes (on x64 architecture), so for
// obvious reasons I will not display all its fields in this example
std::ignore = kuser_shared_data->system_expiration_date;
log_value("[KERNEL]");
log_value("Safe boot", shared.safe_boot_enabled());
log_value("Boot ID", shared.boot_id());
log_value("Physical Pages Num", shared.physical_pages_num());
log_value("Kernel debugger present", shared.kernel_debugger_present());
log_value("System root", shared.system_root().to_path().string());
new_line();
auto system = shared.system();
log_value("[SYSTEM]");
log_value("Windows 11", system.is_windows_11());
log_value("Windows 10", system.is_windows_10());
log_value("Windows 7", system.is_windows_7());
log_value("Windows XP", system.is_windows_xp());
log_value("Windows Vista", system.is_windows_vista());
log_value("OS Major Version", system.major_version());
log_value("OS Minor Version", system.minor_version());
log_value("OS Build Number", system.build_number());
log_value("Formatted OS String", system.formatted());
new_line();
auto unix_timestamp = shared.unix_epoch_timestamp();
auto windows_timestamp = shared.windows_epoch_timestamp();
log_value("[TIME]");
log_value("Unix Time", unix_timestamp.utc().time_since_epoch());
log_value("Unix Time", unix_timestamp.utc().format_iso8601());
log_value("Unix Time (Local)", unix_timestamp.local().time_since_epoch());
log_value("Unix Time (Local) (ISO 8601)", unix_timestamp.local().format_iso8601());
log_value("Windows Time", shared.windows_epoch_timestamp());
log_value("Timezone ID", shared.timezone_id());
log_value("Timezone offset", shared.timezone_offset());
// Iterators are compatible with the ranges library
static_assert(std::bidirectional_iterator);
static_assert(std::bidirectional_iterator);
// Code below DOES NOT COMPILE. hash*_t requires a string literal
// because the hashing of the string happens at compile time, so there
// is no point in you obfuscating the string in any way, because it
// will turn into a number and disappear from the binary after compilation.
//
// constexpr auto hash_that_causes_ct_error = shadow::hash64_t{string_obfuscator("string")};
//
// The right way to do it is:
// constexpr auto valid_hash = shadow::hash64_t{"string"};
}
```
## 硬件处理器解析器
```
#include
#include
#include "shadowsyscall.hpp"
int main() {
auto support_message = []( std::string_view isa_feature, bool is_supported ) {
constexpr int width = 12;
std::cout << std::left << std::setw( width ) << isa_feature << ( is_supported ? "[+]" : "[-]" ) << std::endl;
};
std::cout << shadow::cpu().vendor() << std::endl;
std::cout << shadow::cpu().brand() << std::endl;
const auto& caches = shadow::cpu().caches();
// CPU caches parsing is supported for current processor
if ( caches ) {
std::cout << "L1 cache size: " << caches->l1_size() << "\n";
std::cout << "L2 cache size: " << caches->l2_size() << "\n";
std::cout << "L3 cache size: " << caches->l3_size() << "\n";
std::cout << "Total caches size: " << caches->total_size().as_bytes() << "\n";
} else {
// Otherwise - the library does not yet support parsing for the existing processor
std::cout << "Cache parsing is not supported by `shadow` on your processor architecture\n";
}
support_message( "IS_INTEL", shadow::cpu().is_intel() );
support_message( "IS_AMD", shadow::cpu().is_amd() );
support_message( "ABM", shadow::cpu().supports_abm() );
support_message( "ADX", shadow::cpu().supports_adx() );
support_message( "AES", shadow::cpu().supports_aes() );
support_message( "AVX", shadow::cpu().supports_avx() );
support_message( "AVX2", shadow::cpu().supports_avx2() );
support_message( "AVX512CD", shadow::cpu().supports_avx512cd() );
support_message( "AVX512ER", shadow::cpu().supports_avx512er() );
support_message( "AVX512F", shadow::cpu().supports_avx512f() );
support_message( "AVX512PF", shadow::cpu().supports_avx512pf() );
support_message( "BMI1", shadow::cpu().supports_bmi1() );
support_message( "BMI2", shadow::cpu().supports_bmi2() );
support_message( "CLFLUSH", shadow::cpu().supports_clflush() );
support_message( "CMPXCHG16B", shadow::cpu().supports_cmpxchg16b() );
support_message( "CX8", shadow::cpu().supports_cx8() );
support_message( "ERMS", shadow::cpu().supports_erms() );
support_message( "F16C", shadow::cpu().supports_f16c() );
support_message( "FMA", shadow::cpu().supports_fma() );
support_message( "FSGSBASE", shadow::cpu().supports_fsgsbase() );
support_message( "FXSR", shadow::cpu().supports_fxsr() );
support_message( "HLE", shadow::cpu().supports_hle() );
support_message( "INVPCID", shadow::cpu().supports_invpcid() );
support_message( "LAHF", shadow::cpu().supports_lahf() );
support_message( "LZCNT", shadow::cpu().supports_lzcnt() );
support_message( "MMX", shadow::cpu().supports_mmx() );
support_message( "MMXEXT", shadow::cpu().supports_mmxext() );
support_message( "MONITOR", shadow::cpu().supports_monitor() );
support_message( "MOVBE", shadow::cpu().supports_movbe() );
support_message( "MSR", shadow::cpu().supports_msr() );
support_message( "OSXSAVE", shadow::cpu().supports_osxsave() );
support_message( "PCLMULQDQ", shadow::cpu().supports_pclmulqdq() );
support_message( "POPCNT", shadow::cpu().supports_popcnt() );
support_message( "PREFETCHWT1", shadow::cpu().supports_prefetchwt1() );
support_message( "RDRAND", shadow::cpu().supports_rdrand() );
support_message( "RDSEED", shadow::cpu().supports_rdseed() );
support_message( "RDTSCP", shadow::cpu().supports_rdtscp() );
support_message( "RTM", shadow::cpu().supports_rtm() );
support_message( "SEP", shadow::cpu().supports_sep() );
support_message( "SHA", shadow::cpu().supports_sha() );
support_message( "SSE", shadow::cpu().supports_sse() );
support_message( "SSE2", shadow::cpu().supports_sse2() );
support_message( "SSE3", shadow::cpu().supports_sse3() );
support_message( "SSE4.1", shadow::cpu().supports_sse4_1() );
support_message( "SSE4.2", shadow::cpu().supports_sse4_2() );
support_message( "SSE4a", shadow::cpu().supports_sse4a() );
support_message( "SSSE3", shadow::cpu().supports_ssse3() );
support_message( "SYSCALL", shadow::cpu().supports_syscall() );
support_message( "TBM", shadow::cpu().supports_tbm() );
support_message( "XOP", shadow::cpu().supports_xop() );
support_message( "XSAVE", shadow::cpu().supports_xsave() );
}
```
## 🚀 特性
- 每次调用均进行缓存(可禁用缓存)
- 枚举当前进程加载的每一个 DLL
- 在运行时计算 DLL 任意节的校验和
- 查找当前进程加载的已知 DLL
- 枚举模块的 EAT (导出地址表)
- 解析模块的 PE 头和目录
- 编译期字符串哈希器
- 哈希种子基于头文件位置进行伪随机化
- Syscall 执行器
- 重写 syscall SSN 解析器
- 在运行时执行任何导出
- 不在可执行文件中留下任何导入
- CPU 指令集支持检查器和缓存解析器
## 📜 Windows 中的 syscall 是什么?

## 向后兼容性
## 感谢
invers1on :heart:
https://github.com/can1357/linux-pe
控制台输出
``` List of DLLs loaded in the current process: C:\WINDOWS\SYSTEM32\ntdll.dll : 0x7ff933ca0000 C:\WINDOWS\System32\KERNEL32.DLL : 0x7ff933310000 C:\WINDOWS\System32\KERNELBASE.dll : 0x7ff930f40000 C:\WINDOWS\SYSTEM32\VCRUNTIME140D.dll : 0x7ff916180000 C:\WINDOWS\SYSTEM32\MSVCP140D.dll : 0x7ff8de2f0000 C:\WINDOWS\SYSTEM32\VCRUNTIME140_1D.dll : 0x7ff92dde0000 C:\WINDOWS\SYSTEM32\ucrtbased.dll : 0x7ff8afe90000 Current .exe filepath: C:\artem\cpp\shadow_syscall\build\examples\module_parser.exe Current .text section checksum: 18446744073709543893 Current module handle: 0x7ff609e80000 5 first exports of ntdll.dll A_SHAFinal : 0x7ff933ca1200 : 9 A_SHAInit : 0x7ff933d8a840 : 10 A_SHAUpdate : 0x7ff933ca3090 : 11 AlpcAdjustCompletionListConcurrencyCount : 0x7ff933daed50 : 12 AlpcFreeCompletionListMessage : 0x7ff933d780d0 : 13 Export NtQuerySystemInformation VA is 0x7ff933dfc670 Export on ordinal 10 in ntdll.dll is presented on VA 0x7ff933d8a840 The DLL that contains the Sleep export is: KERNEL32.DLL [NTDLL]: Base Address: 00007FF933CA0000 Native Handle: 00007FF933CA0000 Entry Point: 0000000000000000 Name: ntdll.dll Path to File: "C:\\WINDOWS\\SYSTEM32\\ntdll.dll" Reference count: 65535 Image Size: 2490368 Sections count: 15 Exports count: 2513 [KERNEL]: Safe boot: 0 Boot ID: 30 Physical Pages Num: 8368911 Kernel debugger present: 0 System root: C:\WINDOWS [SYSTEM]: Windows 11: 1 Windows 10: 0 Windows 7: 0 Windows XP: 0 Windows Vista: 0 OS Major Version: 10 OS Minor Version: 0 OS Build Number: 26100 Formatted OS String: Windows 10.0 (Build 26100) [TIME]: Unix Time: 1746661511 Unix Time: 2025-05-07T23:45:11 Unix Time (Local): 1746668711 Unix Time (Local) (ISO 8601): 2025-05-08T01:45:11 Windows Time: 133911351118390449 Timezone ID: 2 Timezone offset: 7200s ```标签:C++20, Clang, DNS 反向解析, EDR绕过, GCC, KUSER_SHARED_DATA, MSVC, Shellcode, Syscall, Web开发, Windows系统编程, XML 请求, 中高交互蜜罐, 云资产清单, 动态链接库枚举, 头文件库, 字符串加密, 安全开发, 导出函数解析, 底层开发, 恶意软件开发, 技术调研, 数据展示, 现代C++, 端点可见性, 红队, 编译时哈希, 跨平台编译器, 逆向工程, 高交互蜜罐