From 23d4eabf423442d3ffe3745f8f343a475475717c Mon Sep 17 00:00:00 2001 From: winterknife Date: Wed, 12 Nov 2025 08:58:41 -0500 Subject: [PATCH] initial commit --- .github/workflows/build.yml | 33 ++++ .gitignore | 9 + .vscode/c_cpp_properties.json | 23 +++ .vscode/settings.json | 17 ++ .vscode/tasks.json | 32 ++++ Inc/BaseDataTypes.h | 74 ++++++++ Inc/Common.h | 223 ++++++++++++++++++++++++ Inc/HashString.h | 113 +++++++++++++ Inc/PEParse.h | 82 +++++++++ Inc/StackString.h | 79 +++++++++ Inc/UserModuleBase.h | 70 ++++++++ Makefile | 201 ++++++++++++++++++++++ Misc/capa_results.json | Bin 0 -> 114434 bytes Misc/disable_eaf.ps1 | 4 + Misc/disable_iaf.ps1 | 4 + Misc/disable_rop_mitigations.ps1 | 4 + Misc/enable_eaf.ps1 | 6 + Misc/enable_iaf.ps1 | 4 + Misc/enable_rop_mitigations.ps1 | 4 + Misc/floss_results.json | Bin 0 -> 20902 bytes Misc/get_process_mitigations.ps1 | 4 + README.md | 280 +++++++++++++++++++++++++++++++ Src/PEParse.cpp | 170 +++++++++++++++++++ Src/PicMain.cpp | 79 +++++++++ Src/UserModuleBase.cpp | 121 +++++++++++++ 25 files changed, 1636 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 .gitignore create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 Inc/BaseDataTypes.h create mode 100644 Inc/Common.h create mode 100644 Inc/HashString.h create mode 100644 Inc/PEParse.h create mode 100644 Inc/StackString.h create mode 100644 Inc/UserModuleBase.h create mode 100644 Makefile create mode 100644 Misc/capa_results.json create mode 100644 Misc/disable_eaf.ps1 create mode 100644 Misc/disable_iaf.ps1 create mode 100644 Misc/disable_rop_mitigations.ps1 create mode 100644 Misc/enable_eaf.ps1 create mode 100644 Misc/enable_iaf.ps1 create mode 100644 Misc/enable_rop_mitigations.ps1 create mode 100644 Misc/floss_results.json create mode 100644 Misc/get_process_mitigations.ps1 create mode 100644 README.md create mode 100644 Src/PEParse.cpp create mode 100644 Src/PicMain.cpp create mode 100644 Src/UserModuleBase.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..a7b826e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,33 @@ +name: build + +on: + workflow_dispatch: + +jobs: + build-x64-gcc: + runs-on: windows-latest + timeout-minutes: 5 + defaults: + run: + shell: msys2 {0} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Set up MSYS2 (MINGW64) + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + update: true + install: base-devel mingw-w64-x86_64-toolchain vim + - name: Build PIC + run: | + make clean + make pic + - name: 'Upload artifacts' + uses: actions/upload-artifact@v4 + with: + name: SilverPick + path: | + Bin/SilverPick_x64.bin + Bin/Shellcode.h + retention-days: 7 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f118ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +[Bb]in/ +[Tt]emp/ +*.exe +*.dll +*.sys +*.bin +*.obj +*.map +*.disasm diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..7946cc4 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,23 @@ +{ + // https://code.visualstudio.com/docs/cpp/customize-cpp-settings + // https://code.visualstudio.com/docs/cpp/configure-intellisense-crosscompilation + "configurations": [ + { + "name": "Win32", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [ + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "windowsSdkVersion": "10.0.26100.0", + "cStandard": "c17", + "cppStandard": "c++20", + "intelliSenseMode": "windows-gcc-x64", + "compilerPath": "C:/msys64/mingw64/bin/gcc.exe" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f4f0f18 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + // https://code.visualstudio.com/docs/terminal/profiles + "terminal.integrated.profiles.windows": { + "MSYS2": { + "path": "C:\\msys64\\usr\\bin\\bash.exe", + "args": [ + "--login", + "-i" + ], + "env": { + "MSYSTEM": "MINGW64", + "CHERE_INVOKING": "1" + } + } + }, + "terminal.integrated.defaultProfile.windows": "MSYS2" +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..a6dcb1d --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,32 @@ +{ + // https://code.visualstudio.com/docs/debugtest/tasks + "version": "2.0.0", + "tasks": [ + { + "label": "Build (Makefile)", + "type": "shell", + "command": "make", + "args": [ + "${input:maketarget}" + ], + "problemMatcher": [ + "$gcc" + ], + "options": { + "cwd": "${workspaceFolder}" + }, + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "Default build task." + } + ], + "inputs": [ + { + "id": "maketarget", + "type": "promptString", + "description": "Make target input." + } + ] +} \ No newline at end of file diff --git a/Inc/BaseDataTypes.h b/Inc/BaseDataTypes.h new file mode 100644 index 0000000..2167586 --- /dev/null +++ b/Inc/BaseDataTypes.h @@ -0,0 +1,74 @@ +// ======================================================================== +// File: BaseDataTypes.h +// +// Author: winterknife +// +// Description: Type definitions for common Windows base data types made +// for maintaining uniformity across all User Mode and Kernel Mode code +// +// Modifications: +// 2021-07-31 Created +// 2025-08-06 Updated +// ======================================================================== + +// ======================================================================== +// Pragmas +// ======================================================================== + +#pragma once + +// ======================================================================== +// Type definitions +// ======================================================================== + +#pragma region TYPEDEFS + +// 8-bit unsigned integer, range = 0 - 255, unsigned char +typedef unsigned char BYTE, *PBYTE; + +// 16-bit unsigned integer, range = 0 - 65535, unsigned short +typedef unsigned short WORD, *PWORD; + +// 32-bit unsigned integer, range = 0 - 4294967295, unsigned int or unsigned long +typedef unsigned long int DWORD, *PDWORD; + +// 64-bit unsigned integer, range = 0 - 18446744073709551615, unsigned long long +typedef unsigned long long QWORD, *PQWORD; + +// Should be TRUE (1) or FALSE (0), unsigned char or bool +typedef unsigned char BOOLEAN, *PBOOLEAN; + +// 8-bit UTF-8/Multibyte/ANSI character, char +typedef char CHAR, *PCHAR; + +// 16-bit UTF-16/Wide/UNICODE character, unsigned short +typedef wchar_t WCHAR, *PWCHAR; + +// Pointer to any type, size = 4 bytes or 8 bytes depending on code bitness +typedef void* PVOID; + +// Pointer to a constant of any type +typedef const void* PCVOID; + +// Pointer to constant null-terminated string of ANSI characters +typedef const char* PCSTR; + +// Pointer to constant null-terminated string of UNICODE characters +typedef const wchar_t* PCWSTR; + +// Pointer to constant null-terminated string of ANSI or UNICODE characters depending on character encoding scheme +#if defined(UNICODE) +typedef const wchar_t* PCTSTR; +#else +typedef const char* PCTSTR; +#endif + +// 32-bit or 64-bit unsigned integer, range = 0 - 4294967295 or 18446744073709551615 depending on compiler bitness +// Used for casting pointers +#if defined(_WIN64) +typedef unsigned long long DWORD_PTR, *PDWORD_PTR; +#else +typedef unsigned long int DWORD_PTR, *PDWORD_PTR; +#endif + +#pragma endregion \ No newline at end of file diff --git a/Inc/Common.h b/Inc/Common.h new file mode 100644 index 0000000..e910497 --- /dev/null +++ b/Inc/Common.h @@ -0,0 +1,223 @@ +// ======================================================================== +// File: Common.h +// +// Author: winterknife +// +// Description: Contains the common stuff for this project +// +// Modifications: +// 2021-08-21 Created +// 2025-11-02 Updated +// ======================================================================== + +// ======================================================================== +// Header guards +// ======================================================================== + +#pragma once + +// ======================================================================== +// Predefined macros +// ======================================================================== + +// References: +// 1: https://blog.kowalczyk.info/article/j/guide-to-predefined-macros-in-c-compilers-gcc-clang-msvc-etc..html +// 2: https://github.com/cpredef/predef + +// Emit error message at compile time if build target is not 64-bit x86 Windows +#if not defined(__x86_64__) || not defined(_WIN64) +#error Unsupported build target! +#endif + +// Emit error message at compile time if compiler is not GCC 15.2 +#if not defined(__GNUC__) || (__GNUC__ != 15) || (__GNUC_MINOR__ != 2) +#error Unsupported compiler! +#endif + +// Emit error message at compile time if C++ standard is not C++20 +#if __cplusplus != 202002L +#error Unsupported C++ standard! +#endif + +// ======================================================================== +// Includes +// ======================================================================== + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include +#include "BaseDataTypes.h" + +// ======================================================================== +// Macros +// ======================================================================== + +#pragma region MACROS + +// Major version number +#define MAJOR_VERSION 1 + +// Minor version number +#define MINOR_VERSION 0 + +// Macro to specify C linkage (prevent name mangling) +#define EXTERN_C extern "C" + +// References: +// 1: https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html +// 2: https://gcc.gnu.org/onlinedocs/gcc/x86-Function-Attributes.html + +// Macro to prevent a function from being considered for inlining +#define NO_INLINE __attribute__((noinline)) + +// Macro to inline a function independent of any restrictions that otherwise apply to inlining +#define FORCE_INLINE __attribute__((always_inline)) inline + +// Macro to place a function at the beginning of the .text section +#define CODE_BEGIN __attribute__((section(".init"))) + +// Macro to generate an alternate prologue and epilogue to realign the stack +#define ALIGN_STACK __attribute__((force_align_arg_pointer)) + +// Macro to disable compiler optimizations for a specific function +#define DISABLE_OPTIMIZATION __attribute__((optimize("O0"))) + +// Convert a macro argument into a string constant +#define STRINGIZE_EX(x) #x +#define STRINGIZE(x) STRINGIZE_EX(x) + +// Merge two tokens into one while expanding macros +#define CONCATENATE_EX(x, y) x##y +#define CONCATENATE(x, y) CONCATENATE_EX(x, y) + +// Pragma macro +#define PRAGMA(x) _Pragma(#x) + +// Macros to suppress compiler warnings +#define SUPPRESS_WARNING_PUSH PRAGMA(GCC diagnostic push) +#define SUPPRESS_WARNING(warning) PRAGMA(GCC diagnostic ignored warning) +#define SUPPRESS_WARNING_POP PRAGMA(GCC diagnostic pop) + +// Macro to get the type of an expression +#define TYPEOF(x) decltype(x) + +// Macros for C++ type casting operators +// Reference: https://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-const-cast-and-reinterpret-cast-be-used +#define STATIC_CAST(Type, Expression) static_cast(Expression) +#define CONST_CAST(Type, Expression) const_cast(Expression) +#define DYNAMIC_CAST(Type, Expression) dynamic_cast(Expression) +#define REINTERPRET_CAST(Type, Expression) reinterpret_cast(Expression) +#define BIT_CAST(Type, Expression) std::bit_cast(Expression) + +// References: +// 1: https://devblogs.microsoft.com/oldnewthing/20190129-00/?p=100825 +// 2: https://medium.com/@ophirharpaz/a-summary-of-x86-string-instructions-87566a28c20c + +// Macro to fill a block of memory with zeroes given its address and length in bytes by generating the store string instruction (REP STOSB) +// Enhanced REP MOVSB and STOSB operation (ERMSB) is only available since Ivy Bridge Intel microarchitecture (2012/2013) +// Processors that provide support for enhanced MOVSB/STOSB operations are enumerated by the CPUID feature flag: CPUID:(EAX=7H, ECX=0H):EBX.ERMSB[bit 9] = 1 +#define ZERO_MEMORY(Destination, Length) __stosb(reinterpret_cast(Destination), 0, Length) + +// Macro to copy the contents of a source memory block to a destination memory block given its source address, destination address, and the number of bytes to copy by generating the move string instruction (REP MOVSB) +#define COPY_MEMORY(Destination, Source, Count) __movsb(reinterpret_cast(Destination), reinterpret_cast(Source), Count) + +#pragma endregion + +// ======================================================================== +// Inline routines +// ======================================================================== + +#pragma region INLINES + +/// @brief Compare the contents of two memory blocks byte by byte using the compare string instruction (REPE CMPSB) +/// @param pcSource1 Pointer to the first block of memory +/// @param pcSource2 Pointer to the second block of memory +/// @param dwptrLength Number of bytes to compare +/// @return The return value is one if all bytes match up to the specified length value, or zero otherwise +__attribute__((always_inline)) inline bool compare_memory( + _In_ const void* pcSource1, + _In_ const void* pcSource2, + _In_ size_t dwptrLength +) { + bool bResult; + void* pRSI; + void* pRDI; + void* pRCX; + + asm volatile( + "test rcx, rcx;" + "repz cmpsb;" + : "=S" (pRSI), "=D" (pRDI), "=c" (pRCX), "=@ccz" (bResult) + : "0" (pcSource1), "1" (pcSource2), "2" (dwptrLength) + : "memory" + ); + + return bResult; +} + +/// @brief Scan the contents of a memory block for a byte using the scan string instruction (REPNE SCASB) +/// @param pcSource Pointer to the block of memory +/// @param byTarget Byte to look for +/// @param dwptrLength Number of bytes to check +/// @return Returns a pointer to the first matching byte if successful, or NULL otherwise +__attribute__((always_inline)) inline void* scan_memory( + _In_ const void* pcSource, + _In_ unsigned char byTarget, + _In_ size_t dwptrLength +) { + void* pRDI; + void* pRCX; + + asm volatile( + "jrcxz notfound;" + "repnz scasb;" + "jz found;" + "notfound: mov %0, 1;" + "found: dec %0;" + : "=D" (pRDI), "=c" (pRCX) + : "a" (byTarget), "0" (pcSource), "1" (dwptrLength) + : "memory" + ); + + return pRDI; +} + +#pragma endregion + +// ======================================================================== +// Immediate routines +// ======================================================================== + +#pragma region IMMEDIATES + +/// @brief Force compile-time only evaluation of a function +/// @param value Input constant-expression function +/// @return Returns a compile-time constant +consteval auto as_constant( + auto value +) { + // References: + // 1: https://andreasfertig.com/blog/2021/07/cpp20-a-neat-trick-with-consteval/ + + return value; +} + +/// @brief Get the length of a compile-time string constant at compile time +/// @tparam Type Type template parameter that acts as a placeholder for the type of character literals that make up the string +/// @param pctyString Pointer to a constant null-terminated string of ANSI/UNICODE characters +/// @return Returns the number of characters in the string, excluding the terminal null +template +consteval size_t get_string_length_as_constant( + const Type* pctyString +) { + // References: + // 1: https://devblogs.microsoft.com/oldnewthing/20221114-00/?p=107393 + + return std::char_traits::length(pctyString); +} + +#pragma endregion \ No newline at end of file diff --git a/Inc/HashString.h b/Inc/HashString.h new file mode 100644 index 0000000..c0fdf4e --- /dev/null +++ b/Inc/HashString.h @@ -0,0 +1,113 @@ +// ======================================================================== +// File: HashString.h +// +// Author: winterknife +// +// Description: This header file contains inline routines to hash short +// strings using non-cryptographic hash functions +// +// Modifications: +// 2025-08-24 Created +// 2025-09-22 Updated +// ======================================================================== + +// ======================================================================== +// Header guards +// ======================================================================== + +#pragma once + +// ======================================================================== +// Includes +// ======================================================================== + +#include +#include + +// ======================================================================== +// Macros +// ======================================================================== + +#pragma region MACROS + +// 64-bit magic prime for Fowler/Noll/Vo (FNV) hash +// May not be modified +#define FNV_PRIME_64 0x100000001B3ULL + +// 64-bit non-zero initial basis for FNV-1 hash +// May be modified as long as it is non-zero +#define FNV_OFFSET_BASIS_64 0xCBF29CE484222325ULL + +// Macro to hash a short string at compile time +#define HASH_STRING_COMPILE_TIME(String) hash_string_fnv1a_64_as_constant(String, 0x95DD92DBA3D9E736ULL) + +// Macro to hash a short string at run time +#define HASH_STRING_RUN_TIME(String, Length) hash_string_fnv1a_64(String, Length, 0x95DD92DBA3D9E736ULL) + +#pragma endregion + +// ======================================================================== +// Inline routines +// ======================================================================== + +#pragma region INLINES + +// Original implementation: http://www.isthe.com/chongo/tech/comp/fnv/index.html +// Authors: Phong Vo, Glenn Fowler, Landon Curt Noll + +// Online hash lookup: +// curl -s -D "/dev/stderr" https://hashdb.openanalysis.net/hash/fnv1a_64/ | jq + +/// @brief Hash a short string using 64-bit Fowler/Noll/Vo (FNV-1a) hash function +/// @tparam Type Type template parameter that acts as a placeholder for the type of character literals that make up the string +/// @param pctyString Pointer to a constant null-terminated string of ANSI/UNICODE characters +/// @param dwptrSize Number of characters in the string, excluding the terminal null +/// @param qwHash FNV offset basis (initial hash value) +/// @return 64-bit unsigned integer representing the hash value +template +__attribute__((always_inline)) inline constexpr unsigned long long hash_string_fnv1a_64( + _In_z_ const Type* pctyString, + _In_ size_t dwptrSize, + _In_ unsigned long long qwHash = FNV_OFFSET_BASIS_64 +) noexcept { + // Init local variables + unsigned char byCurrent = 0; + + // Hash each byte of the buffer + while (dwptrSize--) { + // Get current byte + byCurrent = static_cast(*pctyString++); + + // XOR the lower 8-bits of the hash value with the current byte + qwHash ^= byCurrent; + + // Multiply the hash value by the 64-bit FNV magic prime mod 2^64 + qwHash *= FNV_PRIME_64; + } + + return qwHash; +} + +#pragma endregion + +// ======================================================================== +// Immediate routines +// ======================================================================== + +#pragma region IMMEDIATES + +/// @brief Hash a compile-time string constant at compile time using 64-bit Fowler/Noll/Vo (FNV-1a) hash function +/// @tparam Type Type template parameter that acts as a placeholder for the type of character literals that make up the string +/// @tparam Size Number of characters in the string, including the terminal null +/// @param tyarrInputData Reference to an array of constant ANSI/UNICODE characters +/// @param qwHash FNV offset basis (initial hash value) +/// @return 64-bit unsigned integer representing the hash value +template +consteval auto hash_string_fnv1a_64_as_constant( + _In_z_ const Type (&tyarrInputData)[Size], + _In_ unsigned long long qwHash = FNV_OFFSET_BASIS_64 +) { + return hash_string_fnv1a_64(tyarrInputData, (Size - 1), qwHash); +} + +#pragma endregion \ No newline at end of file diff --git a/Inc/PEParse.h b/Inc/PEParse.h new file mode 100644 index 0000000..e9fad23 --- /dev/null +++ b/Inc/PEParse.h @@ -0,0 +1,82 @@ +// ======================================================================== +// File: PEParse.h +// +// Author: winterknife +// +// Description: Header file for PEParse.cpp source file +// +// Modifications: +// 2025-09-25 Created +// 2025-11-06 Updated +// ======================================================================== + +// ======================================================================== +// Header guards +// ======================================================================== + +#pragma once + +// ======================================================================== +// Includes +// ======================================================================== + +#include "Common.h" +#include "HashString.h" + +// ======================================================================== +// Macros +// ======================================================================== + +#pragma region MACROS + +// Hardcoded value for the exported symbol name maximum length +#define MAX_EXPORTED_SYMBOL_NAME_LEN 64ULL + +// Macro to get the exported symbol address from a loaded user-mode module +#define GET_EXPORTED_SYMBOL_ADDRESS(ModuleBase, Name) get_exported_symbol_address_by_hash(ModuleBase, HASH_STRING_COMPILE_TIME(Name)) + +// Macro to declare and initialize a function pointer for run-time dynamic linking, use on a local scope +#define INITIALIZE_FUNCTION_POINTER(Function) typedef TYPEOF(Function) CONCATENATE(__type_, Function); CONCATENATE(__type_, Function*) Function = nullptr; + +// Macro to resolve a function pointer using run-time dynamic linking, use on a local scope +#define RESOLVE_FUNCTION_POINTER(ModuleBase, Function) Function = BIT_CAST(CONCATENATE(__type_, Function*), GET_EXPORTED_SYMBOL_ADDRESS(ModuleBase, STRINGIZE(Function))); + +#pragma endregion + +// ======================================================================== +// Inline routines +// ======================================================================== + +#pragma region INLINES + +/// @brief Convert a Relative Virtual Address (RVA) to a Virtual Address (VA) +/// @tparam Type Type template parameter that acts as a placeholder for the type of the pointer +/// @param ptyBase The base address of an image that is mapped into memory +/// @param dwptrOffset The relative virtual address to be converted +/// @return The return value is the virtual address in the mapped image, performs type safe math +template +FORCE_INLINE Type* convert_rva_to_va( + _In_ Type* ptyBase, + _In_ DWORD_PTR dwptrOffset +) noexcept { + return BIT_CAST(Type*, (BIT_CAST(PBYTE, ptyBase) + dwptrOffset)); +} + +#pragma endregion + +// ======================================================================== +// C routine declarations +// ======================================================================== + +#pragma region DECLARATIONS + +/// @brief Get the address of an exported symbol from the export table of the specified loaded PE module, custom implementation of kernel32!GetProcAddress routine +/// @param pImageBase Image base address of the loaded module +/// @param qwTargetExportHash 64-bit Fowler/Noll/Vo (FNV-1a) hash of the exported symbol name, can also be the exported symbol ordinal for nameless exports +/// @return Pointer to the target export, returns nullptr if the symbol is not found in the PE file's export table or if it is forwarded to an external module +EXTERN_C NO_INLINE PVOID __stdcall get_exported_symbol_address_by_hash( + _In_ PVOID pImageBase, + _In_ QWORD qwTargetExportHash +); + +#pragma endregion \ No newline at end of file diff --git a/Inc/StackString.h b/Inc/StackString.h new file mode 100644 index 0000000..47fa6f1 --- /dev/null +++ b/Inc/StackString.h @@ -0,0 +1,79 @@ +// ======================================================================== +// File: StackString.h +// +// Author: winterknife +// +// Description: This header file contains inline routines to create +// string literals that are stored on the program stack +// using template metaprogramming +// +// Modifications: +// 2025-08-14 Created +// 2025-08-26 Updated +// ======================================================================== + +// ======================================================================== +// Header guards +// ======================================================================== + +#pragma once + +// ======================================================================== +// Includes +// ======================================================================== + +#include +#include + +// ======================================================================== +// Macros +// ======================================================================== + +#pragma region MACROS + +// Macro to build a stack string +#define STACK_STRING(Name, String) auto Name = String##_stackstring + +#pragma endregion + +// ======================================================================== +// Inline routines +// ======================================================================== + +#pragma region INLINES + +// Original implementation: https://godbolt.org/z/5hrKvsY85 +// Author: Can Bölük (@_can1357) + +/// @brief Construct a stack string character-by-character using recursion to unpack the template parameter pack +/// @tparam Type Type template parameter that acts as a placeholder for the type of character literals +/// @tparam First Non-type template parameter representing the leading character in the sequence +/// @tparam ...Rest Non-type template parameter pack consisting of a sequence of characters +/// @param ptyString Pointer to a character in the std::array representing the stack string +template +__attribute__((always_inline)) inline void store_character( + _In_ volatile Type* ptyString +) { + *ptyString++ = First; + if constexpr(sizeof...(Rest) != 0) { + store_character(ptyString); + } +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" +/// @brief Literal operator template to create a compile-time user-defined string literal that is stored on the program stack +/// @tparam Type Type template parameter that acts as a placeholder for the type of character literals +/// @tparam ...Rest Non-type template parameter pack consisting of a sequence of characters +/// @return Returns a std::array representing the stack string +template +__attribute__((always_inline)) inline constexpr auto operator""_stackstring( + void +) { + std::array array = {}; + store_character(array.data()); + return array; +} +#pragma GCC diagnostic pop + +#pragma endregion \ No newline at end of file diff --git a/Inc/UserModuleBase.h b/Inc/UserModuleBase.h new file mode 100644 index 0000000..c144ec3 --- /dev/null +++ b/Inc/UserModuleBase.h @@ -0,0 +1,70 @@ +// ======================================================================== +// File: UserModuleBase.h +// +// Author: winterknife +// +// Description: Header file for UserModuleBase.cpp source file +// +// Modifications: +// 2025-09-23 Created +// 2025-09-25 Updated +// ======================================================================== + +// ======================================================================== +// Header guards +// ======================================================================== + +#pragma once + +// ======================================================================== +// Includes +// ======================================================================== + +#include "Common.h" +#include "HashString.h" + +// ======================================================================== +// Macros +// ======================================================================== + +#pragma region MACROS + +// Hardcoded ntdll!_LDR_DATA_TABLE_ENTRY.BaseDllName.MaximumLength value +#define MAX_BASE_DLL_NAME_LEN 64U + +// Macro to get the image base address of a loaded user-mode module +#define GET_USER_MODULE_BASE(Name) get_user_module_base_by_hash(HASH_STRING_COMPILE_TIME(Name)) + +#pragma endregion + +// ======================================================================== +// Inline routines +// ======================================================================== + +#pragma region INLINES + +/// @brief Get a pointer to the Thread Environment Block (TEB) of the current thread +/// @return A pointer to the TEB of the current thread +FORCE_INLINE PTEB get_current_teb( + VOID +) { + // GS segment base address points to the base address of the current thread's Thread Environment Block (TEB) in user-mode + return BIT_CAST(TEB*, __readgsqword(FIELD_OFFSET(NT_TIB, Self))); +} + +#pragma endregion + +// ======================================================================== +// C routine declarations +// ======================================================================== + +#pragma region DECLARATIONS + +/// @brief To retrieve the base address of a loaded user-mode module (EXE/DLL) from user-mode, does not increment module reference count, custom implementation of kernel32!GetModuleHandle routine +/// @param qwTargetModuleHash 64-bit Fowler/Noll/Vo (FNV-1a) hash of the target module name in lowercase with the file name extension, can also be nullptr to get the base address of the calling module +/// @return Returns the base address of the loaded module if successful, or nullptr otherwise +EXTERN_C NO_INLINE PVOID __stdcall get_user_module_base_by_hash( + _In_opt_ QWORD qwTargetModuleHash +); + +#pragma endregion \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..db87f01 --- /dev/null +++ b/Makefile @@ -0,0 +1,201 @@ +# References +# 01. https://makefiletutorial.com/ +# 02. https://web.stanford.edu/class/archive/cs/cs107/cs107.1174/guide_make.html +# 03. https://www.gnu.org/software/make/manual/make.html +# 04. https://gcc.gnu.org/onlinedocs/gcc-15.1.0/gcc/Option-Index.html +# 05. https://www3.ntu.edu.sg/home/ehchua/programming/cpp/gcc_make.html +# 06. https://nullprogram.com/blog/2023/02/15/ +# 07. https://github.com/pts/pts-tinype +# 08. https://caiorss.github.io/C-Cpp-Notes/compiler-flags-options.html +# 09. https://www.man7.org/linux/man-pages/man1/ld.1.html +# 10. https://gcc.gnu.org/onlinedocs/gcc-15.1.0/gcc/Cygwin-and-MinGW-Options.html +# 11. https://www.renenyffenegger.ch/notes/development/languages/C-C-plus-plus/GCC/options/ +# 12. https://gist.github.com/tomdaley92/190c68e8a84038cc91a5459409e007df +# 13. https://keasigmadelta.com/blog/cmake-vs-make-a-real-life-comparison-with-actual-code/ +# 14. https://www.man7.org/linux/man-pages/man1/windres.1.html +# 15. https://airbus-seclab.github.io/c-compiler-security/ +# 16. https://nullprogram.com/blog/2023/04/29/ +# 17. https://interrupt.memfault.com/blog/code-size-optimization-gcc-flags +# 18. https://interrupt.memfault.com/blog/best-and-worst-gcc-clang-compiler-flags +# 19. https://www.incredibuild.com/blog/compiling-with-clang-optimization-flags +# 20. https://clang.llvm.org/docs/genindex.html +# 21. https://sourceware.org/binutils/docs/ld/WIN32.html +# 22. https://www.man7.org/linux/man-pages/man1/objdump.1.html +# 23. https://www.man7.org/linux/man-pages/man1/objcopy.1.html +# 24. https://stackoverflow.com/questions/2214575/passing-arguments-to-make-run/14061796#14061796 +# 25. https://dev.to/deciduously/how-to-make-a-makefile-1dep/ + +# Project name +PROJECT := SilverPick + +# The flags given to make +MAKEFLAGS += --no-builtin-rules # eliminate use of the built-in implicit rules +MAKEFLAGS += --no-builtin-variables # eliminate use of the built-in rule-specific variables + +# Program for compiling C++ programs +CXX = gcc + +# Extra flags to give to the C preprocessor and programs that use it +CPPFLAGS := $(addprefix -I,Inc) # Specify the directory to be searched for header files during preprocessing + +# Extra flags to give to the C++ compiler +CXXFLAGS := -c # compile the source files, but do not link +CXXFLAGS += -Wall # enable all the warnings +CXXFLAGS += -Wextra # enable some extra warnings +CXXFLAGS += -Werror # turn all warnings into errors +CXXFLAGS += -Wshadow # issue a warning when a variable declaration hides a previous declaration +CXXFLAGS += -pedantic-errors # disable compiler extensions +CXXFLAGS += -Wconversion # warn for implicit conversions that may alter a value +CXXFLAGS += -Wundef # warn if an undefined identifier is evaluated in an #if directive +CXXFLAGS += -Wpadded # warn if padding is added to a structure due to alignment requirements +CXXFLAGS += -fdiagnostics-color=always # use color in diagnostics +CXXFLAGS += -std=c++20 # use C++20 standard +CXXFLAGS += -Os # optimize for size +CXXFLAGS += -flto # enable whole program Link Time Optimization (LTO) +CXXFLAGS += -ffat-lto-objects # generate a fat LTO object that has both a true object and a discardable IR section +CXXFLAGS += -m64 # generate code for the x86-64 architecture +CXXFLAGS += -fno-stack-protector # disable stack protection +CXXFLAGS += -mno-stack-arg-probe # disable stack probing +CXXFLAGS += -fno-ident # do not emit metadata containing compiler name and version +CXXFLAGS += -fno-exceptions # disable exception handling +CXXFLAGS += -fno-unwind-tables # suppress generation of static unwind tables +CXXFLAGS += -fno-asynchronous-unwind-tables # prevent generation of the .pdata section that contains the unwind tables +CXXFLAGS += -fno-builtin # don't recognize built-in functions that do not begin with __builtin_ as prefix +CXXFLAGS += -ffunction-sections # place each function in a separate section in the resulting object file +CXXFLAGS += -fdata-sections # place each data item in a separate section in the resulting object file +CXXFLAGS += -fno-jump-tables # do not use jump tables for switch statements +CXXFLAGS += -fno-toplevel-reorder # do not reorder top-level functions, variables, and asm statements +CXXFLAGS += -fpack-struct=8 # specify maximum alignment for structure member packing +CXXFLAGS += -fms-extensions # accept some non-standard constructs used in Microsoft header files +CXXFLAGS += -fno-rtti # disable run-time type information +CXXFLAGS += -fomit-frame-pointer # omit the frame pointer in functions that don't need one +CXXFLAGS += -masm=intel # output assembly instructions using selected dialect +CXXFLAGS += -v # enable verbose mode + +# Extra flags to give to the compiler when they are supposed to invoke the linker +LDFLAGS := -fuse-ld=bfd # use the bfd linker instead of the default linker +LDFLAGS += -nostdlib # do not use the standard system startup files or libraries when linking +ifeq (pic,$(firstword $(MAKECMDGOALS))) +LDFLAGS += -Wl,--entry=PicEntry # specify the program entry point +else ifeq (exe,$(firstword $(MAKECMDGOALS))) +LDFLAGS += -Wl,--entry=ExeEntry # specify the program entry point +else ifeq (dll,$(firstword $(MAKECMDGOALS))) +LDFLAGS += -Wl,--entry=DllEntry # specify the program entry point +LDFLAGS += -shared # create a DLL instead of a regular executable +LDFLAGS += -Wl,--dll # create a DLL instead of a regular executable +LDFLAGS += -Wl,--exclude-all-symbols # specify that no symbols should be automatically exported +endif +LDFLAGS += -Wl,--subsystem=windows:6.1 # specify the subsystem and the subsystem version targeted by the executable +LDFLAGS += -Wl,--dynamicbase # use ASLR +LDFLAGS += -Wl,--high-entropy-va # use HEASLR +ifeq (dll,$(firstword $(MAKECMDGOALS))) +LDFLAGS += -Wl,--image-base=0x180000000 # set the base address for the program +else +LDFLAGS += -Wl,--image-base=0x140000000 # set the base address for the program +endif +LDFLAGS += -Wl,--nxcompat # indicate that the executable is compatible with DEP +ifeq (pic,$(firstword $(MAKECMDGOALS))) +LDFLAGS += -Wl,--no-seh # the image does not use SEH +else +LDFLAGS += -Wl,--disable-no-seh # the image uses SEH +endif +ifeq (dll,$(firstword $(MAKECMDGOALS))) +LDFLAGS += -Wl,--disable-tsaware # the image isn't Terminal Server aware +else +LDFLAGS += -Wl,--tsaware # the image is Terminal Server aware +endif +LDFLAGS += -Wl,--no-insert-timestamp # suppress generation of the PE file header timestamp +LDFLAGS += -Wl,--strip-all # remove all symbol table and relocation information from the executable +LDFLAGS += -Wl,--gc-sections # enable garbage collection of unused input sections +LDFLAGS += -Wl,--oformat=pei-x86-64 # specify the binary format for the output object file +LDFLAGS += -Wl,--major-os-version=6 # set the major number of the OS version +LDFLAGS += -Wl,--minor-os-version=1 # set the minor number of the OS version +LDFLAGS += -Wl,--cref # output a cross reference table +LDFLAGS += -Wl,-Map=Temp/$(PROJECT)_x64.map # generate linker map file +LDFLAGS += -Wl,--disable-auto-import # don't automatically import data symbols from other DLLs without dllimport +LDFLAGS += -Wl,-static # do not link against shared libraries +LDFLAGS += -Wl,--verbose # enable verbose mode + +# Library flags or names given to the compiler when they are supposed to invoke the linker +ifneq (pic,$(firstword $(MAKECMDGOALS))) +LDLIBS := -lkernel32 # search kernel32 import library when linking +LDLIBS += -luser32 # search user32 import library when linking +LDLIBS += -lmsvcrt # search msvcrt import library when linking +endif + +# Source files +SOURCES := $(wildcard Src/*.cpp) + +# Resource script files +RESOURCES := $(wildcard Src/*.rc) + +# Object files +OBJECTS64 := $(patsubst Src/%.cpp, Temp/%_x64.obj, ${SOURCES}) +OBJECTS64 += $(patsubst Src/%.rc, Temp/%_x64.obj, ${RESOURCES}) + +# Default target +.DEFAULT_GOAL := help + +# Display help +.PHONY: help +help: + @ echo "[*] usage: make {exe|dll|pic|clean|help}" + +# PIC target +.PHONY: pic +pic: Bin/$(PROJECT)_x64.bin + +# DLL target +.PHONY: dll +dll: Bin/$(PROJECT)_x64.dll + +# EXE target +.PHONY: exe +exe: Bin/$(PROJECT)_x64.exe + +# Build PIC (x64) +Bin/$(PROJECT)_x64.bin: Bin/$(PROJECT)_x64.exe + @ echo "[*] extracting .text section from PE file..." + @ objcopy --target=pei-x86-64 --dump-section .text=$@ $< + @ xxd -include -u $@ $(dir $@)/Shellcode.h + @ echo "[+] done." + +# Build DLL (x64) +Bin/$(PROJECT)_x64.dll: $(OBJECTS64) + @ echo "[*] linking COFF file(s) into PE file..." + @ mkdir -p $(dir $@) + @ $(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@ + @ echo "[+] done." + +# Build EXE (x64) +Bin/$(PROJECT)_x64.exe: $(OBJECTS64) + @ echo "[*] linking COFF file(s) into PE file..." + @ mkdir -p $(dir $@) + @ $(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@ + @ echo "[+] done." + +# Compile (x64) +Temp/%_x64.obj: Src/%.cpp + @ echo "[*] compiling C/C++ code into COFF file..." + @ mkdir -p $(dir $@) + @ $(CXX) $(CPPFLAGS) $(CXXFLAGS) $< -o $@ + @ objdump --disassemble --disassembler-options=intel --reloc --wide --no-addresses --no-show-raw-insn $@ > $@.disasm + @ echo "[+] done." + +Temp/%_x64.obj: Src/%.rc + @ echo "[*] compiling resource file into COFF file..." + @ mkdir -p $(dir $@) + @ windres --input=$< --output=$@ --output-format=coff --target=pe-x86-64 --include-dir=Inc --verbose + @ echo "[+] done." + +# Cleanup +.PHONY: clean +clean: + @ echo "[*] cleaning up..." + @ find . -name "*.obj" -type f -printf "removed '%f'\n" -delete + @ find . -name "*.exe" -type f -printf "removed '%f'\n" -delete + @ find . -name "*.dll" -type f -printf "removed '%f'\n" -delete + @ find . -name "*.bin" -type f -printf "removed '%f'\n" -delete + @ find . -name "*.map" -type f -printf "removed '%f'\n" -delete + @ find . -name "*.disasm" -type f -printf "removed '%f'\n" -delete + @ echo "[+] done." diff --git a/Misc/capa_results.json b/Misc/capa_results.json new file mode 100644 index 0000000000000000000000000000000000000000..1d908147b889ee610c7cfb87cc4b1723a68d27ca GIT binary patch literal 114434 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 b=S3_@%Nb@ryIl0YD|}8lDpCK3)TR6jtC%Ru literal 0 HcmV?d00001 diff --git a/Misc/get_process_mitigations.ps1 b/Misc/get_process_mitigations.ps1 new file mode 100644 index 0000000..d009b8c --- /dev/null +++ b/Misc/get_process_mitigations.ps1 @@ -0,0 +1,4 @@ +# https://learn.microsoft.com/en-us/defender-endpoint/exploit-protection +# powershell.exe -f get_process_mitigations.ps1 blobrunner64.exe +$processname = $args[0] +Get-ProcessMitigation -Name $processname \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..686df8e --- /dev/null +++ b/README.md @@ -0,0 +1,280 @@ +# SILVERPICK + +## VERSION + +- `v1.0` + +## BRIEF + +Project `SILVERPICK` is a `Windows User-Mode Shellcode Development Framework (WUMSDF)` whose sole purpose is to empower capability developers to build `Position Independent Code (PIC)` blobs for `Windows` `x64` using `C/C++` in an easy manner so as to reduce the development costs of such an endeavour. + +It derives from project [WILDBEAST](https://github.com/winterknife/WILDBEAST) and, as such, leverages: +1. `Visual Studio Code` as the code editor +2. `MinGW-w64` toolchain as the compiler toolchain +3. `GNU Make` as the build system + +## SETUP + +You may find the setup instructions from here: [GCC-Clang-Setup-Windows](https://gist.github.com/winterknife/0b177a75a55bad895b19aad64cffa14f) + +Please note that this project is using [MSYS2](https://www.msys2.org/). + +## FEATURES + +Writing shellcode in higher-level programming languages is nothing new, and countless blog posts and research papers have been published on the same since 2010. So, what is new in `SILVERPICK`? + +Well, I am glad that you asked. + +`SILVERPICK` has a nice little bag of tricks hidden up its sleeve, but most of all, this is my take on the subject. + +So, without further ado, I present to you my first trick. + +### TRICK 01 + +Ever since Matt Graeber popularised writing shellcode in `C`, most people have been using his [16-byte stack alignment stub written in `Assembly` language](https://github.com/mattifestation/PIC_Bindshell/blob/master/PIC_Bindshell/AdjustStack.asm). + +While this is not a problem, seeing as how we aren't `IKEA`, assembly should not be necessary, and indeed it is not. + +There exists a [`GCC` Function Attribute](https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html) that will emit the stack alignment stub for you. + +Meet the `force_align_arg_pointer` function attribute in the form of a helpful [ALIGN_STACK](/Inc/Common.h#L84) macro, which generates the following assembly: +```asm +Disassembly of section .init: + +: + push rbp + mov rbp,rsp + and rsp,0xfffffffffffffff0 + sub rsp,0x20 + call IMAGE_REL_AMD64_REL32 .text$payload + leave + ret +``` + +What's the `.init` section, you ask? Well, that serves as a nice segue into my second trick. + +### TRICK 02 + +Matt Graeber might have popularised writing shellcode in `C` at some point, but really, it was Paul Ungur who revived this black art with [Stardust](https://github.com/Cracked5pider/Stardust). + +Now, `Stardust` uses a [Binutils linker script](https://sourceware.org/binutils/docs/ld/Scripts.html) to control the placement of functions and data into the appropriate `PE` section in the correct order. This technique itself is derived from Austin Hudson's work, and many people use a variant of his linker scripts. + +While linker scripts are great for linker section ordering, if all you need is to place a certain function at the beginning of the code section, they are unnecessary. + +Enter the `section` function attribute with a special section name called `.init`, which indicates to the linker that the function contains pre-`main()` runtime initialization code and must be _first_ in the link order. + +To this effect, the [CODE_BEGIN](/Inc/Common.h#L81) macro has been created. + +### TRICK 03 + +For my third trick, I present to you the [STACK_STRING](/Inc/StackString.h#L35) macro. + +In `C`, you can create a stack string (a string that is dynamically built on the stack) by declaring the string literal as an array of `ANSI` characters: +```c +char charrHelloKitty[] = { 'H', 'e', 'l', 'l', 'o', 'K', 'i', 't', 't', 'y', '\0' }; +``` + +In `C++`, you can create a stack string by simply marking a `char` array as `constexpr`: +```cpp +constexpr char charrHelloKitty[]{ "HelloKitty" }; +``` + +However, both of these techniques are rendered useless in the face of compiler optimizations _if_ the string literals are sufficiently large, unlike our solution, which will work regardless of the string length and the level of compiler optimizations, thanks to some clever `C++` template metaprogramming hack courtesy of Can Bölük. + +Using this macro is pretty straightforward: +```c +STACK_STRING(sstrText, "an extra long hello world!"); +STACK_STRING(sstrCaption, "Demo"); + +MessageBoxA(nullptr, sstrText.data(), sstrCaption.data(), MB_OK); +``` + +This will generate the following assembly: +```asm +mov [rsp+58h+var_23], 61h ; 'a' +mov [rsp+58h+var_22], 6Eh ; 'n' +mov [rsp+58h+var_21], 20h ; ' ' +mov [rsp+58h+var_20], 65h ; 'e' +mov [rsp+58h+var_1F], 78h ; 'x' +mov [rsp+58h+var_1E], 74h ; 't' +mov [rsp+58h+var_1D], 72h ; 'r' +mov [rsp+58h+var_1C], 61h ; 'a' +mov [rsp+58h+var_1B], 20h ; ' ' +mov [rsp+58h+var_1A], 6Ch ; 'l' +mov [rsp+58h+var_19], 6Fh ; 'o' +mov [rsp+58h+var_18], 6Eh ; 'n' +mov [rsp+58h+var_17], 67h ; 'g' +mov [rsp+58h+var_16], 20h ; ' ' +mov [rsp+58h+var_15], 68h ; 'h' +mov [rsp+58h+var_14], 65h ; 'e' +mov [rsp+58h+var_13], 6Ch ; 'l' +mov [rsp+58h+var_12], 6Ch ; 'l' +mov [rsp+58h+var_11], 6Fh ; 'o' +mov [rsp+58h+var_10], 20h ; ' ' +mov [rsp+58h+var_2F], 0 +mov [rsp+58h+var_F], 77h ; 'w' +mov [rsp+58h+var_E], 6Fh ; 'o' +mov [rsp+58h+var_D], 72h ; 'r' +mov [rsp+58h+var_C], 6Ch ; 'l' +mov [rsp+58h+var_B], 64h ; 'd' +mov [rsp+58h+var_A], 21h ; '!' +mov [rsp+58h+var_33], 44h ; 'D' +mov [rsp+58h+var_32], 65h ; 'e' +mov [rsp+58h+var_31], 6Dh ; 'm' +mov [rsp+58h+var_30], 6Fh ; 'o' +``` + +### TRICK 04 + +Speaking of `C++`, I present to you compile-time string hashing for my fourth trick. + +While this is not a novel concept, `SILVERPICK` offers some improvements over existing public implementations. + +Firstly, we use the 64-bit variant of the popular `FNV-1a` non-cryptographic hash function in order to reduce the probability of a successful hash collision attack. + +Secondly, we use a modified parameter for the hash function to defend against precalculated hash table lookups, such as [HashDB](https://hashdb.openanalysis.net/). Crucially, this does _not_ change the properties of the hash function. + +To hash a short string at run time, simply use the [HASH_STRING_RUN_TIME](/Inc/HashString.h#L45) macro. + +To hash a short string literal at compile time, simply use the [HASH_STRING_COMPILE_TIME](/Inc/HashString.h#L42) macro. Compile-time only evaluation is guaranteed via `consteval`. + +### TRICK 05 + +It turns out that you can implement quite a handful of `C Runtime Library (CRT)` functions using `x86` string instructions. So, of course, I had to implement them using a mix of compiler intrinsics and inline assembly. + +Do you want to use the `msvcrt!memset` function in your code? Use the [ZERO_MEMORY](/Inc/Common.h#L123) macro instead, which uses the `rep stosb` instruction emitted via a compiler intrinsic. + +How about the `msvcrt!memcpy` function or the `msvcrt!memmove` function, you ask? Meet the [COPY_MEMORY](/Inc/Common.h#L126) macro as a replacement, which uses the `rep movsb` instruction emitted via a compiler intrinsic. + +But what about an alternative to the `msvcrt!memcmp` function? It turns out that there isn't exactly a compiler intrinsic available to emit the `repe cmpsb` instruction. So, we write a [compare_memory](/Inc/Common.h#L141) function using inline assembly instead. + +Finally, if you seek a replacement for the `msvcrt!memchr` function, meet the [scan_memory](/Inc/Common.h#L167) function, which once again uses inline assembly since there is no compiler intrinsic available to emit the `repne scasb` instruction. + +Oh, and did I forget to mention that you could write your own safer version of the `msvcrt!strlen` function using the `scan_memory` routine like so: +```cpp +DWORD_PTR dwptrExportNameLength = std::min(BIT_CAST(DWORD_PTR, scan_memory(strExportName, 0x00, MAX_EXPORTED_SYMBOL_NAME_LEN)) - BIT_CAST(DWORD_PTR, strExportName), MAX_EXPORTED_SYMBOL_NAME_LEN); +``` + +Please note that these implementations may not produce the most performant code, depending on the target `CPU` microarchitecture. However, they are guaranteed to get the job done. + +### TRICK 06 + +Interested in more parlour tricks? + +There are a lot of other small macros in [Common.h](/Inc/Common.h) that exist to abstract away some of the complexities of massaging the compiler. + +A dependency-less implementation of the [GetModuleHandle](https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlea) function is provided in [UserModuleBase.cpp](/Src/UserModuleBase.cpp). To simplify ease-of-use, a helpful macro named [GET_USER_MODULE_BASE](/Inc/UserModuleBase.h#L36) has been created. + +Similarly, a dependency-less implementation of the [GetProcAddress](https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getprocaddress) function is provided in [PEParse.cpp](/Src/PEParse.cpp), which is then wrapped in a handy macro aptly named [GET_EXPORTED_SYMBOL_ADDRESS](/Inc/PEParse.h#L36). Furthermore, two more macros have been provided to aid in run-time dynamic linking - [INITIALIZE_FUNCTION_POINTER](/Inc/PEParse.h#L39) to declare and initialize a function pointer to 0, and [RESOLVE_FUNCTION_POINTER](/Inc/PEParse.h#L42) to resolve said function pointer. + +[Visual Studio Code integration](/.vscode/) is built into the project so that developers may use the `Ctrl+Shift+B` keyboard shortcut for a no-fuss build process. + +[GitHub Actions integration](/.github/workflows/build.yml) is also built into the project to enable `CI` builds. + +The project takes some pride in its well-organized structure, as well as the thoroughly commented and relatively clean code. + +Finally, take a gander at the project [Makefile](/Makefile), which contains the choicest selection of compiler and linker flags that will generate small, secure, and `OPSEC`-friendly code. Meanwhile, verbose logging and a generated linker map file will provide visibility into the build process to facilitate a deeper understanding of the toolchain. Additionally, each translation unit also produces a disassembly file that, upon inspection, will often make you say things like "the compiler did what now?", etc. + +## USAGE + +If you are sold on the framework, this section describes how you would make use of it. + +The following is an excerpt taken from [PicMain.cpp](/Src/PicMain.cpp): +```cpp +/// @brief PIC start function +/// @param None +/// @return None +EXTERN_C NO_INLINE VOID __stdcall payload( + VOID +) { + // Init local variables + PVOID pKernel32 = nullptr; + INITIALIZE_FUNCTION_POINTER(LoadLibraryA); + HMODULE hUser32 = nullptr; + STACK_STRING(sstrUser32, "user32.dll"); + INITIALIZE_FUNCTION_POINTER(MessageBoxA); + STACK_STRING(sstrText, "an extra long hello world!"); + STACK_STRING(sstrCaption, "Demo"); + + // Get the image base address of kernel32.dll + pKernel32 = GET_USER_MODULE_BASE("kernel32.dll"); + if (pKernel32 == nullptr) + goto cleanup; + + // Resolve kernel32!LoadLibraryA + RESOLVE_FUNCTION_POINTER(pKernel32, LoadLibraryA); + if (LoadLibraryA == nullptr) + goto cleanup; + + // Load User32.dll into the process VAS + hUser32 = LoadLibraryA(sstrUser32.data()); + if (hUser32 == nullptr) + goto cleanup; + + // Resolve user32!MessageBoxA + RESOLVE_FUNCTION_POINTER(hUser32, MessageBoxA); + if (MessageBoxA == nullptr) + goto cleanup; + + // Display a message box + MessageBoxA(nullptr, sstrText.data(), sstrCaption.data(), MB_OK); + + // Cleanup +cleanup: + return; +} +``` + +Seems easy enough, no? + +While writing `PIC` in `C/C++` using the `SILVERPICK` framework, you have to take into consideration the following rules: +1. Treat the `payload` function as you would the `main` function of a traditional program, i.e., as the _(pseudo) entry point_. +2. All string literals must be declared as _stack strings_. +3. _Global variables_ may not be used anywhere in the code. +4. `Windows API` or `Native API` functions may be used only via _run-time dynamic linking_ after ensuring that the function prototype is available in the corresponding header file. + +## FUTURE ENHANCEMENTS + +The section contains a non-exhaustive list of planned enhancements that will be integrated into a future project. + +- [ ] Switch to `Clang/LLVM` toolchain. +- [ ] Compile-time string obfuscation that works with stack strings and is resistant to [FLOSS](https://github.com/mandiant/flare-floss). +- [ ] Compile-time string hashing using seeded non-cryptographic custom hash function. +- [ ] Alternate method to retrieve `GS` segment base address. +- [ ] Capability to bypass `Export Address Filtering (EAF)` exploit mitigation. + +## REFERENCES + +The following is a list of references arranged in chronological order that proved invaluable to me during my research and were extensively used as inspiration for this project: + +1. [Writing Shellcode with a C Compiler](https://nickharbour.wordpress.com/2010/07/01/writing-shellcode-with-a-c-compiler/) by Nick Harbour (2010) + +2. [Shellcode with a C-compiler](https://blog.didierstevens.com/programs/shellcode/) by Didier Stevens (2010) + +3. [Writing Optimized Windows Shellcode in C](https://web.archive.org/web/20201202085848/http://www.exploit-monday.com/2013/08/writing-optimized-windows-shellcode-in-c.html) by Matt Graeber (2013) + +4. [Shellcode the better way, or how to just use your compiler](https://phrack.org/issues/69/4) by Justin Fisher (2016) + +5. [ShellcodeStdio](https://winternl.com/shellcodestdio/) by Jack Ullrich (2016) + +6. [Shellcode: A Windows PIC using RSA-2048 key exchange, AES-256, SHA-3](https://web.archive.org/web/20240316160314/https://modexp.wordpress.com/2016/12/26/windows-pic/) by Odzhan (2016) + +7. [Writing Optimized Windows Shellcode](https://dimitrifourny.github.io/2017/04/28/optimized-shellcode.html) by Dimitri Fourny (2017) + +8. [Writing and Compiling Shellcode in C](https://www.ired.team/offensive-security/code-injection-process-injection/writing-and-compiling-shellcode-in-c) by Aleksandra Doniec and Mantvydas Baranauskas (2021) + +9. [Creating Shellcode from any Code Using Visual Studio and C++](https://www.codeproject.com/articles/Creating-Shellcode-from-any-Code-Using-Visual-Stud#comments-section) by Hamid Memar (2021) + +10. [Writing Optimized Windows Shellcode in C](https://phasetw0.com/malware/writing-optimized-windows-shellcode-in-c/) by Philip Woldhek (2021) + +11. [From C, with inline assembly, to shellcode](https://steve-s.gitbook.io/0xtriboulet/archive/notice/just-malicious/from-c-with-inline-assembly-to-shellcode) by Steve Salinas (2023) + +12. [How To Craft Your Own Windows x86/64 Shellcode with Visual Studio](https://xacone.github.io/custom_shellcode.html) by Yazid Benjamaa (2023) + +13. [Modern implant design: position independent malware development](https://5pider.net/blog/2024/01/27/modern-shellcode-implant-design) by Paul Ungur (2024) + +14. [From C to shellcode (simple way)](https://print3m.github.io/blog/from-c-to-shellcode) by Print3M (2024) + +15. [relocatable](https://github.com/tijme/relocatable) by Tijme Gommers (2025) + +16. [PIC Development Crash Course](https://player.vimeo.com/video/1100089433) by Raphael Mudge (2025) \ No newline at end of file diff --git a/Src/PEParse.cpp b/Src/PEParse.cpp new file mode 100644 index 0000000..4a481fe --- /dev/null +++ b/Src/PEParse.cpp @@ -0,0 +1,170 @@ +// ======================================================================== +// File: PEParse.cpp +// +// Author: winterknife +// +// Description: This source file contains routine(s) for parsing Windows +// Portable Executable (PE) files mapped in UVAS without any external +// dependencies including CRT, Win32, or Native API routines +// +// Modifications: +// 2025-09-25 Created +// 2025-11-12 Updated +// ======================================================================== + +// ======================================================================== +// Includes +// ======================================================================== + +#include "PEParse.h" +#include + +// ======================================================================== +// Routines +// ======================================================================== + +#pragma region ROUTINES + +_Use_decl_annotations_ +PVOID __stdcall get_exported_symbol_address_by_hash( + PVOID pImageBase, + QWORD qwTargetExportHash +) { + // Init local variables + PIMAGE_DOS_HEADER pImageDosHeader = nullptr; + DWORD dwElfanew = 0; + PIMAGE_NT_HEADERS pImageNtHeaders = nullptr; + PIMAGE_OPTIONAL_HEADER64 pImageOptionalHeader64 = nullptr; + PIMAGE_DATA_DIRECTORY pImageDataDirectory = nullptr; + DWORD dwExportDirectoryRva = 0; + DWORD dwExportDirectorySize = 0; + PIMAGE_EXPORT_DIRECTORY pImageExportDirectory = nullptr; + DWORD dwTotalExportCount = 0; + DWORD dwNamedExportCount = 0; + PDWORD pdwAddressTable = nullptr; + PDWORD pdwNameTable = nullptr; + PWORD pwoNameOrdinalTable = nullptr; + WORD woOrdinalBase = 0; + WORD woExportOrdinal = 0; + WORD woExportEatIndex = 0; + DWORD dwIndex = 0; + PCSTR strExportName = nullptr; + DWORD_PTR dwptrExportNameLength = 0; + QWORD qwExportHash = 0; + BOOLEAN bFlag = false; + PVOID pExportSymbol = nullptr; + + // Sanity check the function parameters + if (pImageBase == nullptr || pImageBase == INVALID_HANDLE_VALUE) + return nullptr; + + // Get the address of the DOS header - nt!_IMAGE_DOS_HEADER structure + pImageDosHeader = STATIC_CAST(PIMAGE_DOS_HEADER, pImageBase); + if (pImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE) + return nullptr; // not a valid PE + + // Get the offset to the NT headers + dwElfanew = pImageDosHeader->e_lfanew; + if (dwElfanew >= (256U * 1024U * 1024U)) + return nullptr; // offset must not be larger than 256 MB + + // Get the address of the NT headers - nt!_IMAGE_NT_HEADERS32/64 structure + pImageNtHeaders = STATIC_CAST(PIMAGE_NT_HEADERS, convert_rva_to_va(pImageBase, dwElfanew)); + if (pImageNtHeaders->Signature != IMAGE_NT_SIGNATURE) + return nullptr; // not a valid PE + + // Check if the module is a 64-bit binary (PE32+) + if (pImageNtHeaders->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) + return nullptr; // PE32 + + // Get the address of the Optional header - nt!_IMAGE_OPTIONAL_HEADER64 structure + pImageOptionalHeader64 = &(pImageNtHeaders->OptionalHeader); + + // Get the address of the first data directory - nt!_IMAGE_DATA_DIRECTORY structure + pImageDataDirectory = pImageOptionalHeader64->DataDirectory; + + // Get the RVA of the data for the export directory + dwExportDirectoryRva = pImageDataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + if (dwExportDirectoryRva == 0) + return nullptr; + + // Get the size of the data for the export directory + dwExportDirectorySize = pImageDataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; + + // Get the AVA of the data for the export directory - nt!_IMAGE_EXPORT_DIRECTORY structure + pImageExportDirectory = STATIC_CAST(PIMAGE_EXPORT_DIRECTORY, convert_rva_to_va(pImageBase, dwExportDirectoryRva)); + + // Get the total number of exports + // WARNING: If EAF/EAF+ exploit mitigation is enabled, then the following line of code will crash the host process + dwTotalExportCount = pImageExportDirectory->NumberOfFunctions; + if (dwTotalExportCount == 0) + return nullptr; // no exports + + // Get the number of named exports + dwNamedExportCount = pImageExportDirectory->NumberOfNames; + + // Get the address of the array of RVAs of exported symbols - Export Address Table (EAT) + pdwAddressTable = STATIC_CAST(PDWORD, convert_rva_to_va(pImageBase, pImageExportDirectory->AddressOfFunctions)); + + // Get the address of the array of RVAs of exported symbol names as ANSI strings - Export Name Table (ENT) + pdwNameTable = STATIC_CAST(PDWORD, convert_rva_to_va(pImageBase, pImageExportDirectory->AddressOfNames)); + + // Get the address of the array of exported symbol name ordinals/zero-indexed indices for export names into the EAT - Export Name Ordinal Table (ENOT) + pwoNameOrdinalTable = STATIC_CAST(PWORD, convert_rva_to_va(pImageBase, pImageExportDirectory->AddressOfNameOrdinals)); + + // Get the starting ordinal number + woOrdinalBase = LOWORD(pImageExportDirectory->Base); // default = 1 + + // Check if we received an ordinal number + if (HIWORD(qwTargetExportHash) == 0) { + // Get the target ordinal value + woExportOrdinal = LOWORD(qwTargetExportHash); + if (woExportOrdinal < woOrdinalBase) + return nullptr; + + // Get the zero-indexed index for the export ordinal into the EAT + woExportEatIndex = woExportOrdinal - woOrdinalBase; + } + // Else we received a hash of the export name + else if (qwTargetExportHash) { + // Lookup the desired name in the name table using linear search + for (dwIndex = 0; dwIndex < dwNamedExportCount; dwIndex++) { + // Get the name of the current exported symbol + strExportName = STATIC_CAST(PCSTR, convert_rva_to_va(pImageBase, pdwNameTable[dwIndex])); + + // Get the length of the null-terminated ANSI string representing the name of the current exported symbol + dwptrExportNameLength = std::min(BIT_CAST(DWORD_PTR, scan_memory(strExportName, 0x00, MAX_EXPORTED_SYMBOL_NAME_LEN)) - BIT_CAST(DWORD_PTR, strExportName), MAX_EXPORTED_SYMBOL_NAME_LEN); + + // Get the 64-bit FNV-1a hash of the current exported symbol name + qwExportHash = HASH_STRING_RUN_TIME(strExportName, dwptrExportNameLength); + + // Check if the hash of the current exported symbol name matches the hash of the target exported symbol name + if (qwExportHash == qwTargetExportHash) { + bFlag = true; + break; + } + } + + // If the flag is not set, then a matching table entry was not found + if (bFlag == false) + return nullptr; + + // Get the zero-indexed index for the export name into the EAT + woExportEatIndex = pwoNameOrdinalTable[dwIndex]; + } + + // Validate the ordinal by checking if the index is within the bounds of the EAT + if (woExportEatIndex >= dwTotalExportCount) + return nullptr; + + // Get the address of the exported symbol + pExportSymbol = convert_rva_to_va(pImageBase, pdwAddressTable[woExportEatIndex]); + + // Check if the exported symbol RVA is a forwarder RVA by checking if the function pointer lies within the target module's export directory range + if ((pExportSymbol > pImageExportDirectory) && (pExportSymbol < convert_rva_to_va(pImageExportDirectory, dwExportDirectorySize))) + return nullptr; + + return pExportSymbol; +} + +#pragma endregion \ No newline at end of file diff --git a/Src/PicMain.cpp b/Src/PicMain.cpp new file mode 100644 index 0000000..7166762 --- /dev/null +++ b/Src/PicMain.cpp @@ -0,0 +1,79 @@ +// ======================================================================== +// File: PicMain.cpp +// +// Author: winterknife +// +// Description: Source file that contains the PIC entry point +// +// Modifications: +// 2025-09-29 Created +// 2025-09-29 Updated +// ======================================================================== + +// ======================================================================== +// Includes +// ======================================================================== + +#include "StackString.h" +#include "UserModuleBase.h" +#include "PEParse.h" + +// ======================================================================== +// Routines +// ======================================================================== + +#pragma region ROUTINES + +/// @brief PIC start function +/// @param None +/// @return None +EXTERN_C NO_INLINE VOID __stdcall payload( + VOID +) { + // Init local variables + PVOID pKernel32 = nullptr; + INITIALIZE_FUNCTION_POINTER(LoadLibraryA); + HMODULE hUser32 = nullptr; + STACK_STRING(sstrUser32, "user32.dll"); + INITIALIZE_FUNCTION_POINTER(MessageBoxA); + STACK_STRING(sstrText, "an extra long hello world!"); + STACK_STRING(sstrCaption, "Demo"); + + // Get the image base address of kernel32.dll + pKernel32 = GET_USER_MODULE_BASE("kernel32.dll"); + if (pKernel32 == nullptr) + goto cleanup; + + // Resolve kernel32!LoadLibraryA + RESOLVE_FUNCTION_POINTER(pKernel32, LoadLibraryA); + if (LoadLibraryA == nullptr) + goto cleanup; + + // Load User32.dll into the process VAS + hUser32 = LoadLibraryA(sstrUser32.data()); + if (hUser32 == nullptr) + goto cleanup; + + // Resolve user32!MessageBoxA + RESOLVE_FUNCTION_POINTER(hUser32, MessageBoxA); + if (MessageBoxA == nullptr) + goto cleanup; + + // Display a message box + MessageBoxA(nullptr, sstrText.data(), sstrCaption.data(), MB_OK); + + // Cleanup +cleanup: + return; +} + +/// @brief PIC entry point +/// @param None +/// @return None +EXTERN_C NO_INLINE CODE_BEGIN ALIGN_STACK VOID __stdcall PicEntry( + VOID +) { + payload(); +} + +#pragma endregion \ No newline at end of file diff --git a/Src/UserModuleBase.cpp b/Src/UserModuleBase.cpp new file mode 100644 index 0000000..f2233d7 --- /dev/null +++ b/Src/UserModuleBase.cpp @@ -0,0 +1,121 @@ +// ======================================================================== +// File: UserModuleBase.cpp +// +// Author: winterknife +// +// Description: This source file contains routine(s) to query the +// image base address of user modules mapped in UVAS without any +// external dependencies including CRT, Win32, or Native API routines +// +// Modifications: +// 2025-09-23 Created +// 2025-09-25 Updated +// ======================================================================== + +// ======================================================================== +// Includes +// ======================================================================== + +#include "UserModuleBase.h" +#include + +// ======================================================================== +// Routines +// ======================================================================== + +#pragma region ROUTINES + +_Use_decl_annotations_ +PVOID __stdcall get_user_module_base_by_hash( + QWORD qwTargetModuleHash +) { + // Init local variables + PTEB pTeb = nullptr; + PPEB pPeb = nullptr; + PVOID pImageBase = nullptr; + PPEB_LDR_DATA pPebLdrData = nullptr; + PLIST_ENTRY pListHead = nullptr; + PLIST_ENTRY pNextEntry = nullptr; + PLDR_DATA_TABLE_ENTRY pLdrDataTableEntry = nullptr; + PUNICODE_STRING pUnicodeString = nullptr; + CHAR strModuleName[MAX_BASE_DLL_NAME_LEN]; ZERO_MEMORY(strModuleName, sizeof(strModuleName)); + DWORD_PTR dwptrModuleNameLength = 0; + DWORD dwIndex = 0; + WCHAR wchLetter = 0; + QWORD qwModuleHash = 0; + + // Get the base address of the current thread's Thread Environment Block (TEB) + pTeb = get_current_teb(); + if (pTeb == nullptr) + return nullptr; + + // Get the base address of the current process's Process Environment Block (PEB) + pPeb = pTeb->ProcessEnvironmentBlock; + if (pPeb == nullptr) + return nullptr; + + // Check if the hash of the target module name is 0, if true then return the base address of the calling module + if (qwTargetModuleHash == 0) { + pImageBase = pPeb->Reserved3[1]; // ntdll!_PEB.ImageBaseAddress (0x10) + return pImageBase; + } + + // Get the address of the PEB loader data - ntdll!_PEB_LDR_DATA structure (0x18) + // Caution: race condition below since we cannot acquire the loader lock here before walking the loaded module list + pPebLdrData = pPeb->Ldr; + + // Get the address of the circular doubly linked list head - ntdll!_LIST_ENTRY structure (0x20) + pListHead = &(pPebLdrData->InMemoryOrderModuleList); + + // Get the address of the first entry - ntdll!_LIST_ENTRY structure (0x00) + pNextEntry = pListHead->Flink; + + // Loop through all the list entries/loaded modules till we reach the head again + while (pNextEntry != pListHead) { + // Get the address of the current loader module entry - ntdll!_LDR_DATA_TABLE_ENTRY structure (0x10) + pLdrDataTableEntry = CONTAINING_RECORD(pNextEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); + + // Get the address of the Unicode string representing the current module name - ntdll!_UNICODE_STRING structure + pUnicodeString = BIT_CAST(PUNICODE_STRING, &pLdrDataTableEntry->Reserved4); // ntdll!_LDR_DATA_TABLE_ENTRY.BaseDllName (0x58) + + // Reset the ANSI string buffer for the current module + ZERO_MEMORY(strModuleName, sizeof(strModuleName)); + + // Get the length of the Unicode string representing the current module name + dwptrModuleNameLength = std::min(pUnicodeString->Length / sizeof(WCHAR), sizeof(strModuleName)); + + // Convert the current module name from Unicode string to ANSI string + for (dwIndex = 0; dwIndex < dwptrModuleNameLength; dwIndex++) { + // Extract each wide character letter + wchLetter = *(pUnicodeString->Buffer + dwIndex); + + // Convert each wide character letter to lowercase + if (wchLetter != L'_') + wchLetter = wchLetter | 0x20U; + + // Check if each wide character letter is a valid US-ASCII character, else replace with "?" + if (wchLetter > 0x7FU) + wchLetter = '?'; + + // Put each wide character letter into the ANSI string buffer + strModuleName[dwIndex] = STATIC_CAST(CHAR, wchLetter); + } + + // Get the 64-bit FNV-1a hash of the current module name + qwModuleHash = HASH_STRING_RUN_TIME(strModuleName, dwptrModuleNameLength); + + // Check if the hash of the current module name matches the hash of the target module name + if (qwModuleHash == qwTargetModuleHash) { + // Return the base address of the current module + pImageBase = pLdrDataTableEntry->DllBase; + return pImageBase; + } + + // Get the address of the next entry + pNextEntry = pNextEntry->Flink; + } + + return nullptr; +} + +#pragma endregion \ No newline at end of file