├── .gitignore ├── CMakeLists.txt ├── Makefile ├── README.md ├── crypt.py ├── example └── main.c ├── gimmick.c ├── gimmick.h ├── ntdll.h └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | hash.py 2 | cmake-build-debug 3 | *.exe 4 | .idea 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | project(Gimmick C) 3 | 4 | set(CMAKE_C_STANDARD 23) 5 | 6 | add_executable(Gimmick gimmick.c 7 | gimmick.h 8 | example/main.c 9 | ) 10 | 11 | target_include_directories(Gimmick PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | CC64 = x86_64-w64-mingw32-gcc 3 | 4 | SRC = example/main.c *.c 5 | FLAGS = -s -Os -nostdlib -fno-asynchronous-unwind-tables 6 | 7 | OUT = -o gimmick.exe 8 | 9 | build: 10 | $(CC64) $(FLAGS) $(SRC) $(OUT) -DDEBUG 11 | python3 crypt.py -f gimmick.exe -o gimmick.exe -s .xdata .rodata 12 | 13 | release: 14 | $(CC64) $(FLAGS) $(SRC) $(OUT) 15 | python3 crypt.py -f gimmick.exe -o gimmick.exe -s .xdata .rodata -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gimmick 2 | 3 | A thread-safe, section-based payload obfuscation technique. 4 | 5 | ## How it works 6 | This technique allows safe, on-demand access to compile-time encrypted global variables and functions. 7 | How? Gimmick provides an API to allow sections to be dynamically decrypted and accessed at runtime in a thread-safe way. 8 | It also re-encrypts sections when no threads are using them. 9 | Depending on its usage, this technique introduces just a small window for your payload to exist fully decrypted in memory. 10 | 11 | 12 | To **decrypt** a section, Gimmick checks for the following conditions: 13 | 1. There are no other threads currently encrypting or decrypting the section simultaneously 14 | 15 | To **encrypt** a section, Gimmick checks for the following conditions: 16 | 1. There are no other threads currently encrypting or decrypting the section simultaneously 17 | 2. There are no 'references' to the section 18 | 19 | 20 | ## Extra features 21 | 22 | - PIC (Position Independent Code) friendly, with custom GetModuleHandle and GetProcAddress implementations 23 | - Dynamically loaded functions and modules that can be passed to a global instance at runtime. 24 | - Inbuilt RC4 implementation 25 | 26 | ## Limitations 27 | - 64-bit only (for now) 28 | - If the executable is to be loaded by the OS, only sections that are untouched by Windows loader can be used to store data. 29 | This technique is best used with an rDLL or Shellcode. 30 | - All sections are marked as encrypted on initialisation, as Gimmick has no awareness of section states before they have been accessed. 31 | It will attempt to encrypt / decrypt any section referenced by the API. Only functions and variables designated a section with the `SEC` 32 | macro should be called, provided that the section will also be encrypted with `crypt.py` after. This really shouldn't be an issue 33 | **provided that you only target the sections that you want to encrypt.** 34 | - Section page protections are flipped to RW briefly during encryption and decryption. 35 | 36 | ## Run 37 | An example multithreaded application is set up for POC purposes. It is compiled with MinGW gcc. 38 | 1. `make build` or `make release` 39 | 2. `./gimmick.exe` 40 | 41 | ### Output 42 | ``` 43 | --- Starting threads 44 | [*][.xdata] attempting to decrypt section 45 | [*][.xdata] decrypting section 46 | [+][.xdata] done! releasing mutex and restoring protection. 47 | [+][.xdata] data is now available for use. 48 | [*][00007FF6EAE64000] -- executing callee function 49 | [*][.rodata] attempting to decrypt section 50 | [*][.rodata] decrypting section 51 | [*][.xdata] attempting to decrypt section 52 | [!][.xdata] section is already decrypted 53 | [*][00007FF6EAE64000] -- executing callee function 54 | [+][.rodata] done! releasing mutex and restoring protection. 55 | [+][.rodata] data is now available for use. 56 | [*][.rodata] attempting to decrypt section 57 | [!][.rodata] section is already decrypted 58 | [*][.rodata] attempting to decrypt section 59 | [!][.rodata] section is already decrypted 60 | [*][.rodata] attempting to decrypt section 61 | [!][.rodata] section is already decrypted 62 | [*][.rodata] attempting to re-encrypt section 63 | [!][.rodata] section is in use - no re-encryption was performed 64 | [*][.rodata] attempting to re-encrypt section 65 | [!][.rodata] section is in use - no re-encryption was performed 66 | [*][00007FF6EAE64000] -- exited with code 0xdead 67 | [*][.xdata] attempting to re-encrypt section 68 | [!][.xdata] section is in use - no re-encryption was performed 69 | [*][.rodata] attempting to re-encrypt section 70 | [!][.rodata] section is in use - no re-encryption was performed 71 | [*][.rodata] attempting to re-encrypt section 72 | [*][.rodata] re-encrypting section 73 | [+][.rodata] successfully re-encrypted section 74 | [*][00007FF6EAE64000] -- exited with code 0xdead 75 | [*][.xdata] attempting to re-encrypt section 76 | [*][.xdata] re-encrypting section 77 | [+][.xdata] successfully re-encrypted section 78 | ``` 79 | 80 | ## Usage 81 | NOTE: This project is a Proof of Concept. It will likely be buggy, and I do NOT recommend using it as-is in production. 82 | You may open a PR to fix existing issues, or simply fix these yourself privately. 83 | 84 | 1. Add `gimmick.c`, `gimmick.h` and `ntdll.h` to your project 85 | 2. Assign objects to desired sections with the `SEC` macro, separating different types (e.g. functions and variables) 86 | 3. Initialise Gimmick context with `GkInitContext`, and free with `GkFreeSectionContext` 87 | 4. Use `GkGet` (+`GkRelease`), `GkRun`, or `GkRunEx` to run functions or access variables assigned to encrypted sections 88 | 5. Compile the file with -Os and other desired flags 89 | 6. Choose sections that contain data accessed with Gimmick to encrypt (`crypt.py`) and encrypt them with the same key used 90 | for Gimmick's context (edit in script) 91 | 7. Run your executable 92 | 93 | ## Disclaimer 94 | This code is provided for educational and ethical 95 | purposes only. The authors and contributors are not responsible for any 96 | misuse of the code, including but not limited to the unlawful creation or 97 | distribution of malware. Use this code responsibly and in accordance 98 | with all applicable laws and regulations. 99 | -------------------------------------------------------------------------------- /crypt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import pefile 4 | import argparse 5 | from Crypto.Cipher import ARC4 6 | 7 | RC4_KEY = b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f' 8 | 9 | if __name__ in '__main__': 10 | try: 11 | parser = argparse.ArgumentParser(description='Encrypts PE Sections.') 12 | parser.add_argument('-f', required=True, help='Path to the source executable', type=str) 13 | parser.add_argument('-o', required=True, help='Path to store the output executable', type=str) 14 | parser.add_argument('-s', nargs='+', required=True, help='Sections to encrypt') 15 | option = parser.parse_args() 16 | 17 | exe = pefile.PE(option.f) 18 | for section in exe.sections: 19 | name = section.Name.rstrip(b'\x00').decode('utf-8') 20 | for s in option.s: 21 | if name == s: 22 | print("[*] encrypting", name+"...", end=" ") 23 | section_data = section.get_data() 24 | 25 | # need to recreate cipher due to RC4 limitations 26 | cipher = ARC4.new(RC4_KEY) 27 | new_section_data = cipher.encrypt(section_data) 28 | 29 | exe.set_data_bytes(section.PointerToRawData, new_section_data) 30 | 31 | print("done!") 32 | exe.write(option.o) 33 | 34 | except Exception as e: 35 | print('[!] error: {}'.format(e)) 36 | -------------------------------------------------------------------------------- /example/main.c: -------------------------------------------------------------------------------- 1 | #include "../gimmick.h" 2 | 3 | typedef struct { 4 | PCHAR greeting; 5 | } greet, *pgreet; 6 | 7 | // encrypted data. initialise strings with [] to prevent them from being moved 8 | 9 | SEC( rodata ) CHAR hello[] = "hello from gimmick"; 10 | SEC( rodata ) CHAR goodbye[] = "goodbye from gimmick"; 11 | SEC( rodata ) CHAR user32[] = "user32"; 12 | 13 | SEC( xdata ) DWORD __stdcall Message(PGK_CONTEXT Context, PVOID Args) 14 | { 15 | pgreet greet = Args; 16 | PCHAR greeting = greet->greeting; 17 | 18 | // get encrypted data 19 | GkGet( Context, greeting ); 20 | GkGet( Context, user32 ); 21 | 22 | UNICODE_STRING User32 = {}; 23 | ANSI_STRING User32Ansi = { .Buffer = user32, .Length = 6, .MaximumLength = 7 }; 24 | Context->RtlAnsiStringToUnicodeString(&User32, &User32Ansi, TRUE); 25 | 26 | DWORD hashMessageBoxA = 0xe3f74914; 27 | HANDLE hUser32 = NULL; 28 | Context->LdrLoadDll(NULL, 0, &User32, &hUser32); 29 | __typeof__(MessageBoxA)* MessageBoxAProc = (__typeof__(MessageBoxA)*)GkGetProcAddress(Context, hUser32, hashMessageBoxA); 30 | MessageBoxAProc(NULL, greeting, NULL, 0); 31 | 32 | // release data 33 | GkRelease( Context, user32 ); 34 | GkRelease( Context, greeting ); 35 | return 0xDEAD; 36 | } 37 | 38 | int WINAPI WinMain( 39 | IN HINSTANCE hInstance, 40 | IN OPTIONAL HINSTANCE hPrevInstance, 41 | IN LPSTR lpCmdLine, 42 | IN int nShowCmd 43 | ) 44 | { 45 | NTSTATUS Status = STATUS_SUCCESS; 46 | GK_CONTEXT Context = {}; 47 | DWORD ThreadId = 0; 48 | UCHAR Key[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}; 49 | if ((Status = GkInitContext(&Context, GkGetModuleHandle(0), Key, 16)) != STATUS_SUCCESS) { 50 | return Status; 51 | } 52 | greet greetH = { .greeting = hello }; 53 | greet greetG = { .greeting = goodbye }; 54 | 55 | // gkrun arguments 56 | GK_ARGS ArgsHello = { .Context = &Context, .Function = Message, .Args = &greetH }; 57 | GK_ARGS ArgsGoodbye = { .Context = &Context, .Function = Message, .Args = &greetG }; 58 | #ifdef DEBUG 59 | Context.printf("--- Starting threads\n"); 60 | #endif 61 | HANDLE ThreadH = Context.CreateThread(NULL, 0, GkRunEx, &ArgsHello, 0, &ThreadId); 62 | HANDLE ThreadG = Context.CreateThread(NULL, 0, GkRunEx, &ArgsGoodbye, 0, &ThreadId); 63 | 64 | Context.WaitForSingleObject(ThreadH, INFINITE); 65 | Context.WaitForSingleObject(ThreadG, INFINITE); 66 | 67 | return GkFreeSectionContext(&Context); 68 | } -------------------------------------------------------------------------------- /gimmick.c: -------------------------------------------------------------------------------- 1 | #include "gimmick.h" 2 | #include 3 | #ifdef DEBUG 4 | #define PRINTF( ... ) Context->printf( __VA_ARGS__ ); 5 | #else 6 | #define PRINTF( ... ) 7 | #endif 8 | 9 | NTSTATUS GkInitContext( PGK_CONTEXT Context, LPVOID BaseAddress, PUCHAR Key, DWORD KeySize ) 10 | { 11 | PIMAGE_NT_HEADERS NtHeaders = NULL; 12 | PIMAGE_FILE_HEADER FileHeader = NULL; 13 | PGK_SECTION_CONTEXT SectionContext, LastSectionContext = NULL; 14 | LPVOID Section = NULL; 15 | 16 | // Initialize encryption key 17 | Context->EncryptionKey.Length = KeySize; 18 | Context->EncryptionKey.MaximumLength = KeySize; 19 | Context->EncryptionKey.Buffer = Key; 20 | 21 | // load dependencies 22 | Context->Ntdll = GkGetModuleHandle(HASH_NTDLL); 23 | Context->Kernel32 = GkGetModuleHandle(HASH_KERNEL32); 24 | WIN_PROC(Context, Ntdll, LdrGetProcedureAddressForCaller, HASH_LDRGETPROCEDUREADDRESSFORCALLER); 25 | WIN_PROC(Context, Ntdll, RtlAnsiStringToUnicodeString, HASH_RTLANSISTRINGTOUNICODESTRING); 26 | WIN_PROC(Context, Ntdll, LdrLoadDll, HASH_LDRLOADDLL); 27 | WIN_PROC(Context, Kernel32, CreateMutexA, HASH_CREATEMUTEXA); 28 | WIN_PROC(Context, Kernel32, ReleaseMutex, HASH_RELEASEMUTEX); 29 | WIN_PROC(Context, Kernel32, VirtualAlloc, HASH_VIRTUALALLOC); 30 | WIN_PROC(Context, Kernel32, VirtualProtect, HASH_VIRTUALPROTECT); 31 | WIN_PROC(Context, Kernel32, VirtualFree, HASH_VIRTUALFREE); 32 | WIN_PROC(Context, Kernel32, WaitForSingleObject, HASH_WAITFORSINGLEOBJECT); 33 | WIN_PROC(Context, Kernel32, CreateThread, HASH_CREATETHREAD); 34 | 35 | #ifdef DEBUG 36 | CHAR Msvcrt[] = { 'm', 's', 'v', 'c', 'r', 't', '\0'}; 37 | ANSI_STRING MsvcrtAnsi = { .Buffer = Msvcrt, .Length = 6, .MaximumLength = 7 }; 38 | UNICODE_STRING MsvcrtUnicode = {}; 39 | if (Context->RtlAnsiStringToUnicodeString(&MsvcrtUnicode, &MsvcrtAnsi, TRUE) != STATUS_SUCCESS) 40 | return STATUS_UNSUCCESSFUL; 41 | Context->LdrLoadDll(NULL, 0, &MsvcrtUnicode, &Context->Msvcrt); 42 | WIN_PROC(Context, Msvcrt, printf, HASH_PRINTF); 43 | #endif 44 | 45 | 46 | NtHeaders = (PIMAGE_NT_HEADERS)((CHAR*)BaseAddress + ((PIMAGE_DOS_HEADER)BaseAddress)->e_lfanew); 47 | if (NtHeaders->Signature != IMAGE_NT_SIGNATURE) 48 | return STATUS_INVALID_SIGNATURE; 49 | 50 | FileHeader = &NtHeaders->FileHeader; 51 | // get first section 52 | Section = (CHAR*)&NtHeaders->OptionalHeader + FileHeader->SizeOfOptionalHeader; 53 | 54 | for (WORD i = 0; i < FileHeader->NumberOfSections; i++) 55 | { 56 | PIMAGE_SECTION_HEADER SectionHeader = Section; 57 | 58 | SectionContext = (PGK_SECTION_CONTEXT)Context->VirtualAlloc( 59 | NULL, 60 | sizeof(GK_SECTION_CONTEXT), 61 | MEM_COMMIT | MEM_RESERVE, 62 | PAGE_READWRITE 63 | ); 64 | if (SectionContext == NULL) { 65 | return STATUS_UNSUCCESSFUL; 66 | } 67 | SectionContext->Section.Buffer = (PUCHAR)BaseAddress + SectionHeader->VirtualAddress; // assumes sections are loaded correctly in memory 68 | SectionContext->Section.Length = SectionHeader->Misc.VirtualSize; 69 | SectionContext->Section.MaximumLength = SectionContext->Section.Length; 70 | SectionContext->Encrypted = TRUE; // (prod: TRUE) we assume it has been encrypted statically 71 | SectionContext->Mutex = Context->CreateMutexA(NULL, FALSE, NULL); 72 | SectionContext->Name = SectionHeader->Name; 73 | SectionContext->Next = NULL; 74 | 75 | // Attach to linked list 76 | if (LastSectionContext) 77 | LastSectionContext->Next = SectionContext; 78 | else // otherwise, set as first section in section context list 79 | Context->SectionContexts = SectionContext; 80 | LastSectionContext = SectionContext; 81 | // set to next section header 82 | Section = (CHAR*)Section + sizeof(IMAGE_SECTION_HEADER); 83 | } 84 | return STATUS_SUCCESS; 85 | } 86 | 87 | NTSTATUS GkFreeSectionContext( PGK_CONTEXT Context ) 88 | { 89 | DWORD status = STATUS_SUCCESS; 90 | PGK_SECTION_CONTEXT SectionContext = Context->SectionContexts; 91 | do 92 | { 93 | PGK_SECTION_CONTEXT Next = SectionContext->Next; 94 | if (!Context->VirtualFree(SectionContext, 0, MEM_RELEASE)) 95 | status = STATUS_UNSUCCESSFUL; 96 | SectionContext = Next; 97 | } while (SectionContext != NULL); 98 | return status; 99 | } 100 | 101 | NTSTATUS GkGet( PGK_CONTEXT Context, PVOID Data) 102 | { 103 | // find section that data lives in 104 | PGK_SECTION_CONTEXT SectionContext = NULL; 105 | for (SectionContext = Context->SectionContexts; SectionContext != NULL; SectionContext = SectionContext->Next) { 106 | PBUFFER Section = &SectionContext->Section; 107 | // if it is within the range of the section 108 | if ((DWORD_PTR)Data >= (DWORD_PTR)Section->Buffer && 109 | (DWORD_PTR)Data <= (DWORD_PTR)Section->Buffer + Section->Length) { 110 | /*wait for in-progress crypt*/ 111 | Context->WaitForSingleObject(SectionContext->Mutex, INFINITE); 112 | 113 | /* if encryption just happened, then hold mutex and: 114 | * 1. Make RW 115 | * 2. Decrypt 116 | * 3. Restore protection 117 | * 4. Update encryption status 118 | * 5. Release mutex 119 | */ 120 | 121 | PRINTF("[*][%s] attempting to decrypt section\n", SectionContext->Name); 122 | if (SectionContext->Encrypted) { 123 | 124 | PRINTF("[*][%s] decrypting section\n", SectionContext->Name); 125 | 126 | // rw 127 | Context->VirtualProtect(Section->Buffer, Section->Length, PAGE_READWRITE, &SectionContext->OriginalProtect); 128 | // decrypt 129 | GkRC4(Context->EncryptionKey.Buffer, 130 | Context->EncryptionKey.Length, 131 | SectionContext->Section.Buffer, 132 | SectionContext->Section.Length, 133 | SectionContext->Section.Buffer); 134 | PRINTF("[+][%s] done! releasing mutex and restoring protection.\n", SectionContext->Name); 135 | 136 | // original 137 | DWORD op; 138 | Context->VirtualProtect(Section->Buffer, Section->Length, SectionContext->OriginalProtect, &op); 139 | // notify 140 | SectionContext->Encrypted = FALSE; 141 | PRINTF("[+][%s] data is now available for use.\n", SectionContext->Name); 142 | } 143 | else { 144 | PRINTF("[!][%s] section is already decrypted\n", SectionContext->Name); 145 | } 146 | 147 | // update before release so that encryptor knows that there's now an accessor 148 | SectionContext->Accessors += 1; 149 | Context->ReleaseMutex(SectionContext->Mutex); 150 | return STATUS_SUCCESS; 151 | } 152 | } 153 | return STATUS_SECTION_NOT_IMAGE; 154 | } 155 | 156 | NTSTATUS GkRelease( PGK_CONTEXT Context, PVOID Data ) 157 | { 158 | // find section that data lives in 159 | PGK_SECTION_CONTEXT SectionContext = NULL; 160 | for (SectionContext = Context->SectionContexts; SectionContext != NULL; SectionContext = SectionContext->Next) { 161 | PBUFFER Section = &SectionContext->Section; 162 | // if it is within the range of the section 163 | if ((DWORD_PTR)Data >= (DWORD_PTR)Section->Buffer && 164 | (DWORD_PTR)Data <= (DWORD_PTR)Section->Buffer + Section->Length) { 165 | // acquire before checking accessors, effectively wait for an accessor to register themselves 166 | Context->WaitForSingleObject(SectionContext->Mutex, INFINITE); 167 | SectionContext->Accessors -= 1; // decrement before acquiry in case another thread acquires and checks for accessors before us 168 | 169 | PRINTF("[*][%s] attempting to re-encrypt section\n", SectionContext->Name); 170 | if (!SectionContext->Accessors) { 171 | /* 172 | * 1. Acquire mutex 173 | * 2. Make RW 174 | * 3. Encrypt 175 | * 4. Restore original protection 176 | * 5. Update encryption status 177 | */ 178 | // rw 179 | 180 | PRINTF("[*][%s] re-encrypting section\n", SectionContext->Name); 181 | 182 | Context->VirtualProtect(Section->Buffer, Section->Length, PAGE_READWRITE, &SectionContext->OriginalProtect); 183 | // encrypt 184 | GkRC4(Context->EncryptionKey.Buffer, 185 | Context->EncryptionKey.Length, 186 | SectionContext->Section.Buffer, 187 | SectionContext->Section.Length, 188 | SectionContext->Section.Buffer); 189 | // original 190 | DWORD op; // discard rx 191 | Context->VirtualProtect(Section->Buffer, Section->Length, SectionContext->OriginalProtect, &op); 192 | // notify 193 | SectionContext->Encrypted = TRUE; 194 | 195 | PRINTF("[+][%s] successfully re-encrypted section\n", SectionContext->Name); 196 | } 197 | else { 198 | PRINTF("[!][%s] section is in use - no re-encryption was performed\n", SectionContext->Name); 199 | } 200 | // release after notify so that decryptor has the correct encryption bool 201 | Context->ReleaseMutex(SectionContext->Mutex); 202 | return STATUS_SUCCESS; 203 | } 204 | } 205 | return STATUS_SECTION_NOT_IMAGE; 206 | } 207 | 208 | DWORD WINAPI GkRunEx( LPVOID Args ) 209 | { 210 | PGK_ARGS GkArgs = Args; 211 | return GkRun(GkArgs->Context, GkArgs->Function, GkArgs->Args, &GkArgs->ReturnValue); 212 | } 213 | 214 | NTSTATUS GkRun( PGK_CONTEXT Context, LPGK_ROUTINE Function, LPVOID Args, PDWORD ReturnValue ) 215 | { 216 | NTSTATUS Status = STATUS_SUCCESS; 217 | if ((Status = GkGet(Context, Function)) != STATUS_SUCCESS) 218 | return Status; 219 | 220 | 221 | PRINTF("[*][%p] -- executing callee function\n", Function); 222 | 223 | *ReturnValue = Function(Context, Args); 224 | 225 | PRINTF("[*][%p] -- exited with code 0x%.4x\n", Function, *ReturnValue); 226 | 227 | if ((Status = GkRelease(Context, Function)) != STATUS_SUCCESS) 228 | return Status; 229 | return Status; 230 | } 231 | 232 | SIZE_T __attribute__((optimize("O0"))) StrLenA(PCHAR String ) 233 | { 234 | SIZE_T length = 0; 235 | while (*String++) { 236 | length++; 237 | } 238 | return length; 239 | } 240 | 241 | HANDLE GkGetModuleHandle( DWORD Hash ) 242 | { 243 | PPEB Peb = NtCurrentPeb(); 244 | HANDLE hModule = NULL; 245 | LIST_ENTRY ModuleList; 246 | 247 | PPEB_LDR_DATA LdrData = Peb->Ldr; 248 | if (LdrData) { 249 | ModuleList = LdrData->InLoadOrderModuleList; 250 | 251 | PLDR_DATA_TABLE_ENTRY CurrentModule = *((PLDR_DATA_TABLE_ENTRY*)(&ModuleList)); 252 | BOOL First = TRUE; 253 | for (; 254 | CurrentModule != NULL && CurrentModule->DllBase != NULL; 255 | CurrentModule = (PLDR_DATA_TABLE_ENTRY)CurrentModule->InLoadOrderLinks.Flink 256 | ) { 257 | if (First && Hash == 0) { 258 | // first module is always the base of current process 259 | hModule = CurrentModule->DllBase; 260 | break; 261 | } 262 | First = FALSE; 263 | if (CurrentModule->BaseDllName.Buffer == NULL) 264 | continue; 265 | if (HASH_CMPW(CurrentModule->BaseDllName, Hash)) { 266 | hModule = CurrentModule->DllBase; 267 | break; 268 | } 269 | } 270 | } 271 | return hModule; 272 | } 273 | 274 | FARPROC GkGetProcAddress( PGK_CONTEXT Context, HANDLE hModule, DWORD Hash ) 275 | { 276 | if (BAD_MODULE(hModule)) 277 | return NULL; 278 | 279 | PIMAGE_NT_HEADERS NtHeaders = NT_HEADERS_PTR(hModule); 280 | PIMAGE_DATA_DIRECTORY ExportDirectory = &NtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; 281 | if (ExportDirectory == NULL) 282 | return NULL; 283 | // can simply offset from imagebase since image (including export section) is already loaded properly 284 | PIMAGE_EXPORT_DIRECTORY ExportData = (PIMAGE_EXPORT_DIRECTORY)((DWORD_PTR)hModule + ExportDirectory->VirtualAddress); 285 | 286 | if (ExportData) { 287 | // get absolutes from rva 288 | DWORD* NameTable = (DWORD*)((CHAR*)hModule + ExportData->AddressOfNames); // table of RVAs 289 | WORD* OrdinalTable = (WORD*)((CHAR*)hModule + ExportData->AddressOfNameOrdinals); 290 | DWORD* ProcTable = (DWORD*)((CHAR*)hModule + ExportData->AddressOfFunctions); // table of RVAs 291 | 292 | for (SIZE_T i = 0; i < ExportData->NumberOfNames; i++) { 293 | LPSTR CurrentName = (LPSTR)((BYTE*)hModule + NameTable[i]); // convert from RVA 294 | WORD Index = OrdinalTable[i]; 295 | FARPROC ProcAddress = (FARPROC)((BYTE*)hModule + ProcTable[Index]); // convert from RVA 296 | 297 | if (HASH_CMPA(CurrentName, Hash)) { 298 | if (FORWARDER( ExportData , ExportDirectory->Size, ProcAddress )) { 299 | ANSI_STRING String = { .Buffer = CurrentName, .Length = StrLenA(CurrentName) }; 300 | String.MaximumLength = String.Length + 1; 301 | if (Context->LdrGetProcedureAddressForCaller) { 302 | PVOID LdrProcAddress, CallbackAddress = NULL; 303 | if ( NT_SUCCESS(Context->LdrGetProcedureAddressForCaller( 304 | hModule, 305 | &String, 306 | 0, 307 | &LdrProcAddress, 308 | 0, 309 | &CallbackAddress))) { 310 | return LdrProcAddress; 311 | } 312 | return NULL; 313 | } 314 | } 315 | return ProcAddress; 316 | } 317 | } 318 | } 319 | return NULL; 320 | } 321 | 322 | // RC4 implementation - modified https://gist.github.com/rverton/a44fc8ca67ab9ec32089 ----------------- 323 | 324 | void swap(PUCHAR a, PUCHAR b) { 325 | int tmp = *a; 326 | *a = *b; 327 | *b = tmp; 328 | } 329 | 330 | VOID KSA(PUCHAR Key, DWORD KeySize, PUCHAR S) { 331 | 332 | int j = 0; 333 | 334 | for(int i = 0; i < N; i++) 335 | S[i] = i; 336 | 337 | for(int i = 0; i < N; i++) { 338 | j = (j + S[i] + Key[i % KeySize]) % N; 339 | 340 | swap(&S[i], &S[j]); 341 | } 342 | } 343 | 344 | VOID PRGA(PUCHAR S, PUCHAR Plaintext, PUCHAR Ciphertext, DWORD TextSize) { 345 | 346 | int i = 0; 347 | int j = 0; 348 | 349 | for(size_t n = 0, len = TextSize; n < len; n++) { 350 | i = (i + 1) % N; 351 | j = (j + S[i]) % N; 352 | 353 | swap(&S[i], &S[j]); 354 | int rnd = S[(S[i] + S[j]) % N]; 355 | 356 | Ciphertext[n] = rnd ^ Plaintext[n]; 357 | 358 | } 359 | } 360 | 361 | VOID GkRC4(PUCHAR Key, DWORD KeySize, PUCHAR Plaintext, DWORD TextSize, PUCHAR Ciphertext) { 362 | 363 | UCHAR S[N]; 364 | KSA(Key, KeySize, S); 365 | 366 | PRGA(S, Plaintext, Ciphertext, TextSize); 367 | } 368 | -------------------------------------------------------------------------------- /gimmick.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef GIMMICK_H 3 | #define GIMMICK_H 4 | 5 | #include 6 | #include "ntdll.h" 7 | 8 | #define FORWARDER( ex, s, p ) (DWORD_PTR)p >= (DWORD_PTR)ex && \ 9 | (DWORD_PTR)p < (DWORD_PTR)ex + s 10 | #define NT_HEADERS_PTR( x ) (PIMAGE_NT_HEADERS)((PCHAR)x + ((PIMAGE_DOS_HEADER)x)->e_lfanew) 11 | #define BAD_MODULE( x ) ((PIMAGE_DOS_HEADER)x)->e_magic != IMAGE_DOS_SIGNATURE 12 | #define TO_LOWERCASE(c) (c = (c <= 'Z' && c >= 'A') ? c + ' ': c) 13 | #define WIN_FUNC( x ) __typeof__(x)*x; 14 | #define WIN_PROC( c, m, f, h ) c->f = (__typeof__(c->f))GkGetProcAddress(c, c->m, h) 15 | 16 | #define STATUS_INVALID_SIGNATURE 0xC000A000 17 | #define STATUS_SECTION_NOT_IMAGE 0xC0000049 18 | 19 | // RC4 Constant 20 | #define N 256 // 2^8 21 | /* MODULE HASHES -------------------------------------------------*/ 22 | #define HASH_KERNEL32 0x3bbc195 23 | #define HASH_NTDLL 0x11c9b04d 24 | /* PROC HASHES ---------------------------------------------------*/ 25 | #define HASH_LDRGETPROCEDUREADDRESSFORCALLER 0x2bdda210 26 | #define HASH_RTLANSISTRINGTOUNICODESTRING 0x427c583a 27 | #define HASH_VIRTUALALLOC 0x58dacbd7 28 | #define HASH_VIRTUALPROTECT 0x8b9ebdcd 29 | #define HASH_VIRTUALFREE 0x1238036e 30 | #define HASH_CREATEMUTEXA 0xd8b1f26d 31 | #define HASH_RELEASEMUTEX 0x790e3959 32 | #define HASH_LDRLOADDLL 0x23a21f83 33 | #define HASH_SYSTEMFUNCTION032 0xd3a21dc5 34 | #define HASH_WAITFORSINGLEOBJECT 0xda18e23a 35 | #define HASH_PRINTF 0x156b2bb8 36 | #define HASH_CREATETHREAD 0xe819b491 37 | 38 | 39 | #ifndef HASH 40 | #define HASH 5381 41 | #endif 42 | 43 | /* 44 | * djb2 hash for wstring, case insensitive modification: http://www.cse.yorku.ca/~oz/hash.html 45 | */ 46 | static DWORD DJB2W(LPWSTR String, DWORD Length) 47 | { 48 | DWORD Hash = HASH; 49 | for (INT i = 0; i < Length; i++) { 50 | CHAR c = *((CHAR*)String + i); 51 | TO_LOWERCASE(c); 52 | Hash = ((Hash << 5) + Hash) + c; 53 | } 54 | return Hash; 55 | } 56 | 57 | /* 58 | * djb2 hash, case insensitive modification: http://www.cse.yorku.ca/~oz/hash.html 59 | */ 60 | static DWORD DJB2A(LPSTR String) 61 | { 62 | CHAR c; 63 | DWORD Hash = HASH; 64 | while ((c = *String++) != 0) { 65 | TO_LOWERCASE(c); 66 | Hash = ((Hash << 5) + Hash) + c; 67 | } 68 | return Hash; 69 | } 70 | 71 | // Hash comparison for UNICODE_STRING 72 | #define HASH_CMPW( x, h ) DJB2W((LPWSTR)x.Buffer, x.Length) == h 73 | #define HASH_CMPA( x, h ) DJB2A((unsigned char*)x) == h 74 | 75 | 76 | 77 | // buffer struct used for encryption in SystemFunction032. 78 | typedef struct _BUFFER { 79 | DWORD Length; 80 | DWORD MaximumLength; 81 | PUCHAR Buffer; 82 | } BUFFER, *PBUFFER; 83 | 84 | 85 | // unexported win32 functions and symbols-------------------------------- 86 | NTSYSAPI 87 | NTSTATUS 88 | NTAPI 89 | LdrGetProcedureAddressForCaller( 90 | IN HMODULE ModuleHandle, 91 | IN PANSI_STRING FunctionName OPTIONAL, 92 | IN WORD Oridinal OPTIONAL, 93 | OUT PVOID *FunctionAddress, 94 | IN BOOL bValue, 95 | IN PVOID *CallbackAddress 96 | ); 97 | // --------------------------------------------------------------------- 98 | 99 | 100 | /* 101 | * stores section context for syncronisation. 102 | * 1. Allow access when section is unencrypted 103 | * 2. Do not encrypt if there are other threads accessing the section 104 | * 3. Do not access while a section is being encrypted or decrypted (mutex is used for insurance) 105 | * 4. Decrypt a section when access is required and it is encrypted (mutex is used for insurance) 106 | */ 107 | typedef struct _GK_SECTION_CONTEXT { 108 | BUFFER Section; 109 | DWORD OriginalProtect; 110 | PCHAR Name; 111 | DWORD Accessors; // section reference count, used to check for accessors before re-encryption 112 | HANDLE Mutex; // held during encryption and decryption. 113 | BOOL Encrypted; 114 | struct _GK_SECTION_CONTEXT* Next; 115 | } GK_SECTION_CONTEXT, *PGK_SECTION_CONTEXT; 116 | 117 | // Stores information about each section on initialisation, as well as helper functions 118 | typedef struct _GK_CONTEXT { 119 | PGK_SECTION_CONTEXT SectionContexts; 120 | BUFFER EncryptionKey; 121 | HANDLE Ntdll; 122 | HANDLE Kernel32; 123 | 124 | WIN_FUNC( LdrGetProcedureAddressForCaller ) 125 | WIN_FUNC( CreateMutexA ) 126 | WIN_FUNC( ReleaseMutex ) 127 | WIN_FUNC( VirtualAlloc ) 128 | WIN_FUNC( VirtualProtect ) 129 | WIN_FUNC( VirtualFree ) 130 | WIN_FUNC( LdrLoadDll ) 131 | WIN_FUNC( RtlAnsiStringToUnicodeString ) 132 | WIN_FUNC( WaitForSingleObject ) 133 | WIN_FUNC( CreateThread ) 134 | 135 | #ifdef DEBUG 136 | HANDLE Msvcrt; 137 | WIN_FUNC( printf ) 138 | #endif 139 | 140 | } GK_CONTEXT, *PGK_CONTEXT; 141 | 142 | // Used to pass information to a GkRunEx thread. 143 | typedef struct _GK_ARGS { 144 | PGK_CONTEXT Context; 145 | LPVOID Function; 146 | PVOID Args; 147 | DWORD ReturnValue; 148 | } GK_ARGS, *PGK_ARGS; 149 | 150 | // Function signature for Gimmick callees. Context is passed in case a callee wants to access encrypted data. 151 | typedef DWORD (__stdcall *LPGK_ROUTINE) ( 152 | IN PGK_CONTEXT Context, 153 | IN LPVOID Args 154 | ); 155 | 156 | // Initialises Gimmick context 157 | NTSTATUS GkInitContext( PGK_CONTEXT Context, LPVOID BaseAddress, PUCHAR Key, DWORD KeySize ); 158 | // Frees Gimmick section context that was allocated with GkInitContext 159 | NTSTATUS GkFreeSectionContext( PGK_CONTEXT Context ); 160 | // Retrieve handle to a module from PEB loader data 161 | HANDLE GkGetModuleHandle( DWORD Hash ); 162 | // Get process address from loaded module data 163 | FARPROC GkGetProcAddress( PGK_CONTEXT Context, HANDLE hModule, DWORD Hash ); 164 | // Request data from an encrypted section. Gimmick ensures the section is protected while the data is being used 165 | NTSTATUS GkGet( PGK_CONTEXT Context, PVOID Data ); 166 | // Signal that data accessed by GkGet is no longer in use 167 | NTSTATUS GkRelease( PGK_CONTEXT Context, HANDLE Data ); 168 | // Runs the function with the provided arguments (e.g. a struct pointer) and returns its return value. decrypting the section 169 | // it exists in if necesssary 170 | NTSTATUS GkRun( PGK_CONTEXT Context, LPGK_ROUTINE Function, PVOID Args, OUT PDWORD ReturnValue ); 171 | // Thread routine to run a function asyncronously. Pass a pointer to the GK_ARGS struct 172 | DWORD WINAPI GkRunEx( LPVOID Args ); 173 | // Inbuilt RC4 174 | VOID GkRC4(PUCHAR Key, DWORD KeySize, PUCHAR Plaintext, DWORD TextSize, PUCHAR Ciphertext); 175 | 176 | #define SEC( s ) __attribute__( ( section("." #s ) ) ) 177 | 178 | #endif //GIMMICK_H 179 | -------------------------------------------------------------------------------- /ntdll.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pygrum/gimmick/d05a6b1026dbb636757a8245b087c59e40e40720/ntdll.h -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pefile 2 | pycryptodome --------------------------------------------------------------------------------