mirror of
https://github.com/winterknife/SILVERPICK
synced 2026-06-08 18:21:15 +00:00
initial commit
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1,9 @@
|
||||
[Bb]in/
|
||||
[Tt]emp/
|
||||
*.exe
|
||||
*.dll
|
||||
*.sys
|
||||
*.bin
|
||||
*.obj
|
||||
*.map
|
||||
*.disasm
|
||||
Vendored
+23
@@ -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
|
||||
}
|
||||
Vendored
+17
@@ -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"
|
||||
}
|
||||
Vendored
+32
@@ -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."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
@@ -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!"
|
||||
@@ -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!"
|
||||
@@ -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!"
|
||||
@@ -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!"
|
||||
@@ -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!"
|
||||
@@ -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.
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user