initial commit

This commit is contained in:
winterknife
2025-11-12 08:58:41 -05:00
commit 23d4eabf42
25 changed files with 1636 additions and 0 deletions
+33
View File
@@ -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
+9
View File
@@ -0,0 +1,9 @@
[Bb]in/
[Tt]emp/
*.exe
*.dll
*.sys
*.bin
*.obj
*.map
*.disasm
+23
View File
@@ -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
}
+17
View File
@@ -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"
}
+32
View File
@@ -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."
}
]
}
+74
View File
@@ -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
+223
View File
@@ -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 <Windows.h>
#include <winternl.h>
#include <winnt.h>
#include <intrin.h>
#include <bit>
#include <string>
#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<Type>(Expression)
#define CONST_CAST(Type, Expression) const_cast<Type>(Expression)
#define DYNAMIC_CAST(Type, Expression) dynamic_cast<Type>(Expression)
#define REINTERPRET_CAST(Type, Expression) reinterpret_cast<Type>(Expression)
#define BIT_CAST(Type, Expression) std::bit_cast<Type>(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<unsigned char*>(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<unsigned char*>(Destination), reinterpret_cast<const unsigned char*>(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 <typename Type>
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<Type>::length(pctyString);
}
#pragma endregion
+113
View File
@@ -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 <stdint.h>
#include <sal.h>
// ========================================================================
// 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/<hash value in decimal> | 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 <typename Type>
__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<unsigned char>(*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 <typename Type, size_t Size>
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
+82
View File
@@ -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 <typename Type>
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
+79
View File
@@ -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 <array>
#include <sal.h>
// ========================================================================
// 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 <typename Type, Type First, Type... Rest>
__attribute__((always_inline)) inline void store_character(
_In_ volatile Type* ptyString
) {
*ptyString++ = First;
if constexpr(sizeof...(Rest) != 0) {
store_character<Type, Rest...>(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 <typename Type, Type... Rest>
__attribute__((always_inline)) inline constexpr auto operator""_stackstring(
void
) {
std::array<Type, sizeof...(Rest)+1> array = {};
store_character<Type, Rest...>(array.data());
return array;
}
#pragma GCC diagnostic pop
#pragma endregion
+70
View File
@@ -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
+201
View File
@@ -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."
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
# powershell.exe -f disable_eaf.ps1 blobrunner64.exe
$processname = $args[0]
Set-ProcessMitigation -Name $processname -Disable EnableExportAddressFilter,EnableExportAddressFilterPlus,AuditEnableExportAddressFilter,AuditEnableExportAddressFilterPlus
Write-Output "[+] Export Address Filtering (EAF), Export Address Filtering Plus (EAF+) exploit mitigations disabled!"
+4
View File
@@ -0,0 +1,4 @@
# powershell.exe -f disable_iaf.ps1 blobrunner64.exe
$processname = $args[0]
Set-ProcessMitigation -Name $processname -Disable EnableImportAddressFilter,AuditEnableImportAddressFilter
Write-Output "[+] Import Address Filtering (IAF) exploit mitigation disabled!"
+4
View File
@@ -0,0 +1,4 @@
# powershell.exe -f disable_rop_mitigations.ps1 blobrunner64.exe
$processname = $args[0]
Set-ProcessMitigation -Name $processname -Disable EnableRopStackPivot,AuditEnableRopStackPivot,EnableRopCallerCheck,AuditEnableRopCallerCheck,EnableRopSimExec,AuditEnableRopSimExec
Write-Output "[+] SimExec (Simulate execution), CallerCheck (Validate API invocation), StackPivot (Validate stack integrity) exploit mitigations disabled!"
+6
View File
@@ -0,0 +1,6 @@
# powershell.exe -f enable_eaf.ps1 blobrunner64.exe kernel32.dll,kernelbase.dll,ntdll.dll
$processname = $args[0]
#$modulenamelist = $args[1]
#Set-ProcessMitigation -Name $processname -Enable EnableExportAddressFilter,EnableExportAddressFilterPlus -EAFModules $modulenamelist
Set-ProcessMitigation -Name $processname -Enable EnableExportAddressFilter,EnableExportAddressFilterPlus
Write-Output "[+] Export Address Filtering (EAF), Export Address Filtering Plus (EAF+) exploit mitigations enabled!"
+4
View File
@@ -0,0 +1,4 @@
# powershell.exe -f enable_iaf.ps1 blobrunner64.exe
$processname = $args[0]
Set-ProcessMitigation -Name $processname -Enable EnableImportAddressFilter
Write-Output "[+] Import Address Filtering (IAF) exploit mitigation enabled!"
+4
View File
@@ -0,0 +1,4 @@
# powershell.exe -f enable_rop_mitigations.ps1 blobrunner64.exe
$processname = $args[0]
Set-ProcessMitigation -Name $processname -Enable EnableRopStackPivot,EnableRopCallerCheck,EnableRopSimExec
Write-Output "[+] SimExec (Simulate execution), CallerCheck (Validate API invocation), StackPivot (Validate stack integrity) exploit mitigations enabled!"
Binary file not shown.
+4
View File
@@ -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
+280
View File
@@ -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:
<PicEntry>:
push rbp
mov rbp,rsp
and rsp,0xfffffffffffffff0
sub rsp,0x20
call <PicEntry+0x11> 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)
+170
View File
@@ -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 <algorithm>
// ========================================================================
// 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
+79
View File
@@ -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
+121
View File
@@ -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 <algorithm>
// ========================================================================
// 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