mirror of
https://github.com/pygrum/gimmick
synced 2026-06-06 16:34:34 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
hash.py
|
||||
cmake-build-debug
|
||||
*.exe
|
||||
.idea
|
||||
@@ -0,0 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.28)
|
||||
project(Gimmick C)
|
||||
|
||||
set(CMAKE_C_STANDARD 23)
|
||||
|
||||
add_executable(Gimmick gimmick.c
|
||||
gimmick.h
|
||||
example/main.c
|
||||
)
|
||||
|
||||
target_include_directories(Gimmick PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
@@ -0,0 +1,11 @@
|
||||
.PHONY: build
|
||||
CC64 = x86_64-w64-mingw32-gcc
|
||||
|
||||
SRC = example/main.c gimmick.c
|
||||
FLAGS = -s -Os
|
||||
|
||||
OUT = -o gimmick.exe
|
||||
|
||||
build:
|
||||
$(CC64) $(FLAGS) $(SRC) $(OUT) -DDEBUG
|
||||
python3 crypt.py -f gimmick.exe -o gimmick.exe -s .vmp0 .rodata
|
||||
@@ -0,0 +1,75 @@
|
||||
# Gimmick
|
||||
|
||||
A thread-safe, section-based payload obfuscation technique.
|
||||
|
||||
## How it works
|
||||
Each section is treated as a shared resource by the application.
|
||||
To access a section, it needs to be decrypted.
|
||||
|
||||
To decrypt a section, the following conditions are satisfied:
|
||||
1. There are no other threads currently encrypting or decrypting the section simultaneously
|
||||
|
||||
To encrypt a section, the following conditions are satisfied:
|
||||
1. There are no other threads currently encrypting or decrypting the section simultaneously
|
||||
2. There are no 'references' to the section
|
||||
|
||||
## Extra features
|
||||
|
||||
- PIC (Position Independent Code) compatible library, with custom GetModuleHandle and GetProcAddress implementations
|
||||
|
||||
## Limitations
|
||||
- 64-bit only (for now)
|
||||
- Sections used by stdlib cannot be encrypted. Modify and compile with -nostdlib to have more standard section names available for use
|
||||
- If the executable is to be loaded by the OS, only sections that are untouched by Windows loader can be used to store data.
|
||||
This technique is best used with an rDLL or Shellcode.
|
||||
- All sections are marked as encrypted on initialisation.
|
||||
Gimmick attempts to encrypt / decrypt any section referenced by the API, as there is no current implementation for
|
||||
*initial* section encryption state detection. Only functions and variables marked with the `SEC` macro should be called,
|
||||
provided that the section will be encrypted with `crypt.py`. This shouldn't be an issue **provided that you only target the
|
||||
sections that you want to encrypt.**
|
||||
|
||||
## Run
|
||||
An example multithreaded application is set up for POC purposes. It is compiled with MinGW gcc.
|
||||
1. `make build`
|
||||
2. `./gimmick.exe`
|
||||
|
||||
### Output
|
||||
```
|
||||
--- Starting threads
|
||||
[*][.vmp0] attempting to decrypt section
|
||||
[*][.vmp0] decrypting section
|
||||
[+][.vmp0] done! releasing mutex and restoring protection.
|
||||
[+][.vmp0] data is now available for use.
|
||||
[*][00007ff6963e9000] -- executing callee function
|
||||
[*][.rodata] attempting to decrypt section
|
||||
[*][.rodata] decrypting section
|
||||
[*][.vmp0] attempting to decrypt section
|
||||
[!][.vmp0] section is already decrypted
|
||||
[*][00007ff6963e9000] -- executing callee function
|
||||
[+][.rodata] done! releasing mutex and restoring protection.
|
||||
[+][.rodata] data is now available for use.
|
||||
[*][.rodata] attempting to decrypt section
|
||||
[!][.rodata] section is already decrypted
|
||||
[*][.rodata] attempting to re-encrypt section
|
||||
[!][.rodata] section is in use - no re-encryption was performed
|
||||
[*][00007ff6963e9000] -- exited with code 0xdead
|
||||
[*][.vmp0] attempting to re-encrypt section
|
||||
[!][.vmp0] section is in use - no re-encryption was performed
|
||||
[*][.rodata] attempting to re-encrypt section
|
||||
[*][.rodata] re-encrypting section
|
||||
[+][.rodata] successfully re-encrypted section
|
||||
[*][00007ff6963e9000] -- exited with code 0xdead
|
||||
[*][.vmp0] attempting to re-encrypt section
|
||||
[*][.vmp0] re-encrypting section
|
||||
[+][.vmp0] successfully re-encrypted section
|
||||
```
|
||||
|
||||
## Usage
|
||||
1. Add `gimmick.c`, `gimmick.h` and `ntdll.h` to your project
|
||||
2. Assign objects to desired sections with the `SEC` macro, separating different types (e.g. functions and variables)
|
||||
3. Initialise Gimmick context (`GkInitCtx`)
|
||||
4. Use `GkGet` (+`GkRelease`), `GkRun`, or `GkRunEx` to run functions or access variables assigned to encrypted sections
|
||||
5. Compile the file
|
||||
6. Choose sections that contain data accessed with Gimmick to encrypt (`crypt.py`) and encrypt them with the same key used
|
||||
for Gimmick's context (edit in script)
|
||||
7. Run your executable
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import pefile
|
||||
import argparse
|
||||
from Crypto.Cipher import ARC4
|
||||
|
||||
RC4_KEY = b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
|
||||
|
||||
if __name__ in '__main__':
|
||||
try:
|
||||
parser = argparse.ArgumentParser(description='Encrypts PE Sections.')
|
||||
parser.add_argument('-f', required=True, help='Path to the source executable', type=str)
|
||||
parser.add_argument('-o', required=True, help='Path to store the output executable', type=str)
|
||||
parser.add_argument('-s', nargs='+', required=True, help='Sections to encrypt')
|
||||
option = parser.parse_args()
|
||||
|
||||
exe = pefile.PE(option.f)
|
||||
for section in exe.sections:
|
||||
name = section.Name.rstrip(b'\x00').decode('utf-8')
|
||||
for s in option.s:
|
||||
if name == s:
|
||||
print("[*] encrypting", name+"...", end=" ")
|
||||
section_data = section.get_data()
|
||||
|
||||
# need to recreate cipher due to RC4 limitations
|
||||
cipher = ARC4.new(RC4_KEY)
|
||||
new_section_data = cipher.encrypt(section_data)
|
||||
|
||||
exe.set_data_bytes(section.PointerToRawData, new_section_data)
|
||||
|
||||
print("done!")
|
||||
exe.write(option.o)
|
||||
|
||||
except Exception as e:
|
||||
print('[!] error: {}'.format(e))
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "../gimmick.h"
|
||||
|
||||
#ifdef DEBUG
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
PCHAR greeting;
|
||||
} greet, *pgreet;
|
||||
|
||||
// encrypted data. initialise strings with [] to prevent them from being moved
|
||||
|
||||
SEC( rodata ) CHAR hello[] = "hello from gimmick";
|
||||
SEC( rodata ) CHAR goodbye[] = "goodbye from gimmick";
|
||||
|
||||
|
||||
SEC( vmp0 ) DWORD __stdcall Message(PGK_CONTEXT Context, PVOID Args)
|
||||
{
|
||||
pgreet greet = Args;
|
||||
PCHAR greeting = greet->greeting;
|
||||
|
||||
// get encrypted data
|
||||
GkGet( Context, greeting );
|
||||
|
||||
MessageBoxA(NULL, greeting, NULL, 0);
|
||||
|
||||
// release data
|
||||
GkRelease( Context, greeting );
|
||||
return 0xDEAD;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
GKSTATUS Status = GK_SUCCESS;
|
||||
GK_CONTEXT Context = {};
|
||||
DWORD ThreadId = 0;
|
||||
UCHAR Key[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
|
||||
if ((Status = GkInitContext(GkGetModuleHandle(0), &Context, Key)) != GK_SUCCESS) {
|
||||
return Status;
|
||||
}
|
||||
greet greetH = { .greeting = hello };
|
||||
greet greetG = { .greeting = goodbye };
|
||||
|
||||
// gkrun arguments
|
||||
GK_ARGS ArgsHello = { .Context = &Context, .Function = Message, .Args = &greetH };
|
||||
GK_ARGS ArgsGoodbye = { .Context = &Context, .Function = Message, .Args = &greetG };
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("--- Starting threads\n");
|
||||
#endif
|
||||
HANDLE ThreadH = CreateThread(NULL, 0, GkRunEx, &ArgsHello, 0, &ThreadId);
|
||||
HANDLE ThreadG = CreateThread(NULL, 0, GkRunEx, &ArgsGoodbye, 0, &ThreadId);
|
||||
|
||||
WaitForSingleObject(ThreadH, INFINITE);
|
||||
WaitForSingleObject(ThreadG, INFINITE);
|
||||
|
||||
return GkFreeSectionContext(&Context);
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
#include "gimmick.h"
|
||||
|
||||
#ifdef DEBUG
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
GKSTATUS GkInitContext( LPVOID BaseAddress, PGK_CONTEXT Context, PUCHAR Key )
|
||||
{
|
||||
PIMAGE_NT_HEADERS NtHeaders = NULL;
|
||||
PIMAGE_FILE_HEADER FileHeader = NULL;
|
||||
PGK_SECTION_CONTEXT SectionContext, LastSectionContext = NULL;
|
||||
LPVOID Section = NULL;
|
||||
|
||||
// Initialize encryption key
|
||||
Context->EncryptionKey.Length = 16;
|
||||
Context->EncryptionKey.MaximumLength = 16;
|
||||
Context->EncryptionKey.Buffer = Key;
|
||||
|
||||
// load dependencies
|
||||
Context->Ntdll = GkGetModuleHandle(HASH_NTDLL);
|
||||
Context->Kernel32 = GkGetModuleHandle(HASH_KERNEL32);
|
||||
WIN_PROC(Context, Ntdll, LdrGetProcedureAddressForCaller, HASH_LDRGETPROCEDUREADDRESSFORCALLER);
|
||||
WIN_PROC(Context, Ntdll, RtlAnsiStringToUnicodeString, HASH_RTLANSISTRINGTOUNICODESTRING);
|
||||
WIN_PROC(Context, Ntdll, LdrLoadDll, HASH_LDRLOADDLL);
|
||||
WIN_PROC(Context, Kernel32, CreateMutexA, HASH_CREATEMUTEXA);
|
||||
WIN_PROC(Context, Kernel32, ReleaseMutex, HASH_RELEASEMUTEX);
|
||||
WIN_PROC(Context, Kernel32, VirtualAlloc, HASH_VIRTUALALLOC);
|
||||
WIN_PROC(Context, Kernel32, VirtualProtect, HASH_VIRTUALPROTECT);
|
||||
WIN_PROC(Context, Kernel32, VirtualFree, HASH_VIRTUALFREE);
|
||||
WIN_PROC(Context, Kernel32, WaitForSingleObject, HASH_WAITFORSINGLEOBJECT);
|
||||
|
||||
|
||||
// TODO: Obfuscate as you please----------
|
||||
CHAR AdvApi[] = { 'A', 'D', 'V', 'A', 'P', 'I', '3', '2', '\0'};
|
||||
ANSI_STRING AdvapiAnsi = { .Buffer = AdvApi, .Length = 8, .MaximumLength = 9 };
|
||||
UNICODE_STRING AdvapiUnicode = {};
|
||||
if (Context->RtlAnsiStringToUnicodeString(&AdvapiUnicode, &AdvapiAnsi, TRUE) != STATUS_SUCCESS)
|
||||
return GK_ERROR_USTRING_ALLOC_FAILED;
|
||||
// ---------------------------------------
|
||||
// load SystemFunction032
|
||||
Context->LdrLoadDll(NULL, NULL, &AdvapiUnicode, &Context->Advapi32);
|
||||
WIN_PROC(Context, Advapi32, SystemFunction032, HASH_SYSTEMFUNCTION032);
|
||||
|
||||
|
||||
NtHeaders = (PIMAGE_NT_HEADERS)((CHAR*)BaseAddress + ((PIMAGE_DOS_HEADER)BaseAddress)->e_lfanew);
|
||||
if (NtHeaders->Signature != IMAGE_NT_SIGNATURE)
|
||||
return GK_ERROR_BAD_NT_SIGNATURE;
|
||||
|
||||
FileHeader = &NtHeaders->FileHeader;
|
||||
// get first section
|
||||
Section = (CHAR*)&NtHeaders->OptionalHeader + FileHeader->SizeOfOptionalHeader;
|
||||
|
||||
for (WORD i = 0; i < FileHeader->NumberOfSections; i++)
|
||||
{
|
||||
PIMAGE_SECTION_HEADER SectionHeader = Section;
|
||||
|
||||
SectionContext = (PGK_SECTION_CONTEXT)Context->VirtualAlloc(
|
||||
NULL,
|
||||
sizeof(GK_SECTION_CONTEXT),
|
||||
MEM_COMMIT | MEM_RESERVE,
|
||||
PAGE_READWRITE
|
||||
);
|
||||
if (SectionContext == NULL) {
|
||||
return GK_ERROR_SECTION_CTX_ALLOC_FAILED;
|
||||
}
|
||||
SectionContext->Section.Buffer = (PUCHAR)BaseAddress + SectionHeader->VirtualAddress; // assumes sections are loaded correctly in memory
|
||||
SectionContext->Section.Length = SectionHeader->Misc.VirtualSize;
|
||||
SectionContext->Section.MaximumLength = SectionContext->Section.Length;
|
||||
SectionContext->Encrypted = TRUE; // (prod: TRUE) we assume it has been encrypted statically
|
||||
SectionContext->Mutex = Context->CreateMutexA(NULL, FALSE, NULL);
|
||||
SectionContext->Name = SectionHeader->Name;
|
||||
SectionContext->Next = NULL;
|
||||
|
||||
// Attach to linked list
|
||||
if (LastSectionContext)
|
||||
LastSectionContext->Next = SectionContext;
|
||||
else // otherwise, set as first section in section context list
|
||||
Context->SectionContexts = SectionContext;
|
||||
LastSectionContext = SectionContext;
|
||||
// set to next section header
|
||||
Section = (CHAR*)Section + sizeof(IMAGE_SECTION_HEADER);
|
||||
}
|
||||
return GK_SUCCESS;
|
||||
}
|
||||
|
||||
GKSTATUS GkFreeSectionContext( PGK_CONTEXT Context )
|
||||
{
|
||||
DWORD status = GK_SUCCESS;
|
||||
PGK_SECTION_CONTEXT SectionContext = Context->SectionContexts;
|
||||
do
|
||||
{
|
||||
PGK_SECTION_CONTEXT Next = SectionContext->Next;
|
||||
if (!Context->VirtualFree(SectionContext, 0, MEM_RELEASE))
|
||||
status = GK_ERROR_SECTION_FREE_FAILED;
|
||||
SectionContext = Next;
|
||||
} while (SectionContext != NULL);
|
||||
return status;
|
||||
}
|
||||
|
||||
GKSTATUS GkGet( PGK_CONTEXT Context, PVOID Data)
|
||||
{
|
||||
// find section that data lives in
|
||||
PGK_SECTION_CONTEXT SectionContext = NULL;
|
||||
for (SectionContext = Context->SectionContexts; SectionContext != NULL; SectionContext = SectionContext->Next) {
|
||||
PBUFFER Section = &SectionContext->Section;
|
||||
// if it is within the range of the section
|
||||
if ((DWORD_PTR)Data >= (DWORD_PTR)Section->Buffer &&
|
||||
(DWORD_PTR)Data <= (DWORD_PTR)Section->Buffer + Section->Length) {
|
||||
/*wait for in-progress crypt*/
|
||||
Context->WaitForSingleObject(SectionContext->Mutex, INFINITE);
|
||||
|
||||
/* if encryption just happened, then hold mutex and:
|
||||
* 1. Make RW
|
||||
* 2. Decrypt
|
||||
* 3. Restore protection
|
||||
* 4. Update encryption status
|
||||
* 5. Release mutex
|
||||
*/
|
||||
#ifdef DEBUG
|
||||
printf("[*][%s] attempting to decrypt section\n", SectionContext->Name);
|
||||
#endif
|
||||
if (SectionContext->Encrypted) {
|
||||
#ifdef DEBUG
|
||||
printf("[*][%s] decrypting section\n", SectionContext->Name);
|
||||
#endif
|
||||
// rw
|
||||
Context->VirtualProtect(Section->Buffer, Section->Length, PAGE_READWRITE, &SectionContext->OriginalProtect);
|
||||
// decrypt
|
||||
if (Context->SystemFunction032((PBUFFER)SectionContext, &Context->EncryptionKey) != STATUS_SUCCESS) {
|
||||
return GK_ERROR_CRYPT_FAILED;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
printf("[+][%s] done! releasing mutex and restoring protection.\n", SectionContext->Name);
|
||||
#endif
|
||||
// original
|
||||
DWORD op;
|
||||
Context->VirtualProtect(Section->Buffer, Section->Length, SectionContext->OriginalProtect, &op);
|
||||
// notify
|
||||
SectionContext->Encrypted = FALSE;
|
||||
#ifdef DEBUG
|
||||
printf("[+][%s] data is now available for use.\n", SectionContext->Name);
|
||||
#endif
|
||||
}
|
||||
#ifdef DEBUG
|
||||
else {
|
||||
printf("[!][%s] section is already decrypted\n", SectionContext->Name);
|
||||
}
|
||||
#endif
|
||||
// update before release so that encryptor knows that there's now an accessor
|
||||
SectionContext->Accessors += 1;
|
||||
Context->ReleaseMutex(SectionContext->Mutex);
|
||||
return GK_SUCCESS;
|
||||
}
|
||||
}
|
||||
return GK_ERROR_ADDRESS_SECTION_NOT_FOUND;
|
||||
}
|
||||
|
||||
GKSTATUS GkRelease( PGK_CONTEXT Context, PVOID Data )
|
||||
{
|
||||
// find section that data lives in
|
||||
PGK_SECTION_CONTEXT SectionContext = NULL;
|
||||
for (SectionContext = Context->SectionContexts; SectionContext != NULL; SectionContext = SectionContext->Next) {
|
||||
PBUFFER Section = &SectionContext->Section;
|
||||
// if it is within the range of the section
|
||||
if ((DWORD_PTR)Data >= (DWORD_PTR)Section->Buffer &&
|
||||
(DWORD_PTR)Data <= (DWORD_PTR)Section->Buffer + Section->Length) {
|
||||
// acquire before checking accessors, effectively wait for an accessor to register themselves
|
||||
Context->WaitForSingleObject(SectionContext->Mutex, INFINITE);
|
||||
SectionContext->Accessors -= 1; // decrement before acquiry in case another thread acquires and checks for accessors before us
|
||||
#ifdef DEBUG
|
||||
printf("[*][%s] attempting to re-encrypt section\n", SectionContext->Name);
|
||||
#endif
|
||||
if (!SectionContext->Accessors) {
|
||||
/*
|
||||
* 1. Acquire mutex
|
||||
* 2. Make RW
|
||||
* 3. Encrypt
|
||||
* 4. Restore original protection
|
||||
* 5. Update encryption status
|
||||
*/
|
||||
// rw
|
||||
#ifdef DEBUG
|
||||
printf("[*][%s] re-encrypting section\n", SectionContext->Name);
|
||||
#endif
|
||||
Context->VirtualProtect(Section->Buffer, Section->Length, PAGE_READWRITE, &SectionContext->OriginalProtect);
|
||||
// encrypt
|
||||
if (Context->SystemFunction032((PBUFFER)SectionContext, &Context->EncryptionKey) != STATUS_SUCCESS) {
|
||||
return GK_ERROR_CRYPT_FAILED;
|
||||
}
|
||||
// original
|
||||
DWORD op; // discard rx
|
||||
Context->VirtualProtect(Section->Buffer, Section->Length, SectionContext->OriginalProtect, &op);
|
||||
// notify
|
||||
SectionContext->Encrypted = TRUE;
|
||||
#ifdef DEBUG
|
||||
printf("[+][%s] successfully re-encrypted section\n", SectionContext->Name);
|
||||
#endif
|
||||
}
|
||||
#ifdef DEBUG
|
||||
else {
|
||||
printf("[!][%s] section is in use - no re-encryption was performed\n", SectionContext->Name);
|
||||
}
|
||||
#endif
|
||||
// release after notify so that decryptor has the correct encryption bool
|
||||
Context->ReleaseMutex(SectionContext->Mutex);
|
||||
return GK_SUCCESS;
|
||||
}
|
||||
}
|
||||
return GK_ERROR_ADDRESS_SECTION_NOT_FOUND;
|
||||
}
|
||||
|
||||
DWORD WINAPI GkRunEx( LPVOID Args )
|
||||
{
|
||||
PGK_ARGS GkArgs = Args;
|
||||
return GkRun(GkArgs->Context, GkArgs->Function, GkArgs->Args, &GkArgs->ReturnValue);
|
||||
}
|
||||
|
||||
GKSTATUS GkRun( PGK_CONTEXT Context, LPGK_ROUTINE Function, LPVOID Args, PDWORD ReturnValue )
|
||||
{
|
||||
|
||||
typedef struct {
|
||||
PCHAR greeting;
|
||||
} greet, *pgreet;
|
||||
GKSTATUS Status = GK_SUCCESS;
|
||||
if ((Status = GkGet(Context, Function)) != GK_SUCCESS)
|
||||
return Status;
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[*][%p] -- executing callee function\n", Function);
|
||||
#endif
|
||||
*ReturnValue = Function(Context, Args);
|
||||
#ifdef DEBUG
|
||||
printf("[*][%p] -- exited with code 0x%.4x\n", Function, *ReturnValue);
|
||||
#endif
|
||||
if ((Status = GkRelease(Context, Function)) != GK_SUCCESS)
|
||||
return Status;
|
||||
return Status;
|
||||
}
|
||||
|
||||
SIZE_T StrLenA(PCHAR String )
|
||||
{
|
||||
SIZE_T length = 0;
|
||||
while (*String++) {
|
||||
length++;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
HANDLE GkGetModuleHandle( DWORD Hash )
|
||||
{
|
||||
PPEB Peb = NtCurrentPeb();
|
||||
HANDLE hModule = NULL;
|
||||
LIST_ENTRY ModuleList;
|
||||
|
||||
PPEB_LDR_DATA LdrData = Peb->Ldr;
|
||||
if (LdrData) {
|
||||
ModuleList = LdrData->InLoadOrderModuleList;
|
||||
|
||||
PLDR_DATA_TABLE_ENTRY CurrentModule = *((PLDR_DATA_TABLE_ENTRY*)(&ModuleList));
|
||||
BOOL First = TRUE;
|
||||
for (;
|
||||
CurrentModule != NULL && CurrentModule->DllBase != NULL;
|
||||
CurrentModule = (PLDR_DATA_TABLE_ENTRY)CurrentModule->InLoadOrderLinks.Flink
|
||||
) {
|
||||
if (First && Hash == 0) {
|
||||
// first module is always the base of current process
|
||||
hModule = CurrentModule->DllBase;
|
||||
break;
|
||||
}
|
||||
First = FALSE;
|
||||
if (CurrentModule->BaseDllName.Buffer == NULL)
|
||||
continue;
|
||||
if (HASH_CMPW(CurrentModule->BaseDllName, Hash)) {
|
||||
hModule = CurrentModule->DllBase;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return hModule;
|
||||
}
|
||||
|
||||
FARPROC GkGetProcAddress( PGK_CONTEXT Context, HANDLE hModule, DWORD Hash )
|
||||
{
|
||||
if (BAD_MODULE(hModule))
|
||||
return NULL;
|
||||
|
||||
PIMAGE_NT_HEADERS NtHeaders = NT_HEADERS_PTR(hModule);
|
||||
PIMAGE_DATA_DIRECTORY ExportDirectory = &NtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||
if (ExportDirectory == NULL)
|
||||
return NULL;
|
||||
// can simply offset from imagebase since image (including export section) is already loaded properly
|
||||
PIMAGE_EXPORT_DIRECTORY ExportData = (PIMAGE_EXPORT_DIRECTORY)((DWORD_PTR)hModule + ExportDirectory->VirtualAddress);
|
||||
|
||||
if (ExportData) {
|
||||
// get absolutes from rva
|
||||
DWORD* NameTable = (DWORD*)((CHAR*)hModule + ExportData->AddressOfNames); // table of RVAs
|
||||
WORD* OrdinalTable = (WORD*)((CHAR*)hModule + ExportData->AddressOfNameOrdinals);
|
||||
DWORD* ProcTable = (DWORD*)((CHAR*)hModule + ExportData->AddressOfFunctions); // table of RVAs
|
||||
|
||||
for (SIZE_T i = 0; i < ExportData->NumberOfNames; i++) {
|
||||
LPSTR CurrentName = (LPSTR)((BYTE*)hModule + NameTable[i]); // convert from RVA
|
||||
WORD Index = OrdinalTable[i];
|
||||
FARPROC ProcAddress = (FARPROC)((BYTE*)hModule + ProcTable[Index]); // convert from RVA
|
||||
|
||||
if (HASH_CMPA(CurrentName, Hash)) {
|
||||
if (FORWARDER( ExportData , ExportDirectory->Size, ProcAddress )) {
|
||||
ANSI_STRING String = { .Buffer = CurrentName, .Length = StrLenA(CurrentName) };
|
||||
String.MaximumLength = String.Length + 1;
|
||||
if (Context->LdrGetProcedureAddressForCaller) {
|
||||
PVOID LdrProcAddress, CallbackAddress = NULL;
|
||||
if ( NT_SUCCESS(Context->LdrGetProcedureAddressForCaller(
|
||||
hModule,
|
||||
&String,
|
||||
0,
|
||||
&LdrProcAddress,
|
||||
0,
|
||||
&CallbackAddress))) {
|
||||
return LdrProcAddress;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return ProcAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
|
||||
#ifndef GIMMICK_H
|
||||
#define GIMMICK_H
|
||||
|
||||
#include "ntdll.h"
|
||||
|
||||
#define FORWARDER( ex, s, p ) (DWORD_PTR)p >= (DWORD_PTR)ex && \
|
||||
(DWORD_PTR)p < (DWORD_PTR)ex + s
|
||||
#define NT_HEADERS_PTR( x ) (PIMAGE_NT_HEADERS)((PCHAR)x + ((PIMAGE_DOS_HEADER)x)->e_lfanew)
|
||||
#define BAD_MODULE( x ) ((PIMAGE_DOS_HEADER)x)->e_magic != IMAGE_DOS_SIGNATURE
|
||||
#define TO_LOWERCASE(c) (c = (c <= 'Z' && c >= 'A') ? c + ' ': c)
|
||||
#define WIN_FUNC( x ) __typeof__(x)*x;
|
||||
#define WIN_PROC( c, m, f, h ) c->f = (__typeof__(f)*)GkGetProcAddress(c, c->m, h)
|
||||
|
||||
|
||||
typedef DWORD GKSTATUS;
|
||||
#define GK_SUCCESS 0x0
|
||||
#define GK_ERROR_BAD_NT_SIGNATURE 0x100
|
||||
#define GK_ERROR_SECTION_CTX_ALLOC_FAILED 0x101
|
||||
#define GK_ERROR_USTRING_ALLOC_FAILED 0x102
|
||||
#define GK_ERROR_CRYPT_FAILED 0x103
|
||||
#define GK_ERROR_ADDRESS_SECTION_NOT_FOUND 0x104
|
||||
#define GK_ERROR_SECTION_FREE_FAILED 0x105
|
||||
|
||||
|
||||
/* MODULE HASHES -------------------------------------------------*/
|
||||
#define HASH_KERNEL32 0x3bbc195
|
||||
#define HASH_NTDLL 0x11c9b04d
|
||||
/* PROC HASHES ---------------------------------------------------*/
|
||||
#define HASH_LDRGETPROCEDUREADDRESSFORCALLER 0x2bdda210
|
||||
#define HASH_RTLANSISTRINGTOUNICODESTRING 0x427c583a
|
||||
#define HASH_VIRTUALALLOC 0x58dacbd7
|
||||
#define HASH_VIRTUALPROTECT 0x8b9ebdcd
|
||||
#define HASH_VIRTUALFREE 0x1238036e
|
||||
#define HASH_CREATEMUTEXA 0xd8b1f26d
|
||||
#define HASH_RELEASEMUTEX 0x790e3959
|
||||
#define HASH_LDRLOADDLL 0x23a21f83
|
||||
#define HASH_SYSTEMFUNCTION032 0xd3a21dc5
|
||||
#define HASH_WAITFORSINGLEOBJECT 0xda18e23a
|
||||
|
||||
|
||||
#ifndef HASH
|
||||
#define HASH 5381
|
||||
#endif
|
||||
|
||||
/*
|
||||
* djb2 hash for wstring, case insensitive modification: http://www.cse.yorku.ca/~oz/hash.html
|
||||
*/
|
||||
static DWORD DJB2W(LPWSTR String, DWORD Length)
|
||||
{
|
||||
DWORD Hash = HASH;
|
||||
for (INT i = 0; i < Length; i++) {
|
||||
CHAR c = *((CHAR*)String + i);
|
||||
TO_LOWERCASE(c);
|
||||
Hash = ((Hash << 5) + Hash) + c;
|
||||
}
|
||||
return Hash;
|
||||
}
|
||||
|
||||
/*
|
||||
* djb2 hash, case insensitive modification: http://www.cse.yorku.ca/~oz/hash.html
|
||||
*/
|
||||
static DWORD DJB2A(LPSTR String)
|
||||
{
|
||||
CHAR c;
|
||||
DWORD Hash = HASH;
|
||||
while ((c = *String++) != 0) {
|
||||
TO_LOWERCASE(c);
|
||||
Hash = ((Hash << 5) + Hash) + c;
|
||||
}
|
||||
return Hash;
|
||||
}
|
||||
|
||||
// Hash comparison for UNICODE_STRING
|
||||
#define HASH_CMPW( x, h ) DJB2W((LPWSTR)x.Buffer, x.Length) == h
|
||||
#define HASH_CMPA( x, h ) DJB2A((unsigned char*)x) == h
|
||||
|
||||
|
||||
|
||||
// buffer struct used for encryption in SystemFunction032.
|
||||
typedef struct _BUFFER {
|
||||
DWORD Length;
|
||||
DWORD MaximumLength;
|
||||
PUCHAR Buffer;
|
||||
} BUFFER, *PBUFFER;
|
||||
|
||||
|
||||
// unexported win32 functions and symbols--------------------------------
|
||||
NTSTATUS
|
||||
SystemFunction032
|
||||
(
|
||||
PBUFFER data,
|
||||
PBUFFER key
|
||||
);
|
||||
|
||||
NTSYSAPI
|
||||
NTSTATUS
|
||||
NTAPI
|
||||
LdrGetProcedureAddressForCaller(
|
||||
IN HMODULE ModuleHandle,
|
||||
IN PANSI_STRING FunctionName OPTIONAL,
|
||||
IN WORD Oridinal OPTIONAL,
|
||||
OUT PVOID *FunctionAddress,
|
||||
IN BOOL bValue,
|
||||
IN PVOID *CallbackAddress
|
||||
);
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
|
||||
/*
|
||||
* stores section context for syncronisation.
|
||||
* Implements buffer used for RC4 encryption by SystemFunction032.
|
||||
* 1. Allow access when section is unencrypted
|
||||
* 2. Do not encrypt if there are other threads accessing the section
|
||||
* 3. Do not access while a section is being encrypted or decrypted (mutex is used for insurance)
|
||||
* 4. Decrypt a section when access is required and it is encrypted (mutex is used for insurance)
|
||||
*/
|
||||
typedef struct _GK_SECTION_CONTEXT {
|
||||
BUFFER Section;
|
||||
DWORD OriginalProtect;
|
||||
PCHAR Name;
|
||||
DWORD Accessors;
|
||||
HANDLE Mutex; // held during encryption and decryption.
|
||||
BOOL Encrypted; // changed BEFORE crypt is done
|
||||
struct _GK_SECTION_CONTEXT* Next;
|
||||
} GK_SECTION_CONTEXT, *PGK_SECTION_CONTEXT;
|
||||
|
||||
// Stores information about each section on initialisation, as well as helper functions
|
||||
typedef struct _GK_CONTEXT {
|
||||
PGK_SECTION_CONTEXT SectionContexts;
|
||||
BUFFER EncryptionKey;
|
||||
HANDLE Ntdll;
|
||||
HANDLE Kernel32;
|
||||
HANDLE Advapi32;
|
||||
WIN_FUNC( LdrGetProcedureAddressForCaller )
|
||||
WIN_FUNC( CreateMutexA )
|
||||
WIN_FUNC( ReleaseMutex )
|
||||
WIN_FUNC( VirtualAlloc )
|
||||
WIN_FUNC( VirtualProtect )
|
||||
WIN_FUNC( VirtualFree )
|
||||
WIN_FUNC( SystemFunction032 )
|
||||
WIN_FUNC( LdrLoadDll )
|
||||
WIN_FUNC( RtlAnsiStringToUnicodeString )
|
||||
WIN_FUNC( WaitForSingleObject )
|
||||
} GK_CONTEXT, *PGK_CONTEXT;
|
||||
|
||||
// Used to pass information to the GkRunner thread.
|
||||
typedef struct _GK_ARGS {
|
||||
PGK_CONTEXT Context;
|
||||
LPVOID Function;
|
||||
PVOID Args;
|
||||
DWORD ReturnValue;
|
||||
} GK_ARGS, *PGK_ARGS;
|
||||
|
||||
// Function signature for Gimmick callees. Context is passed in case a callee wants to access encrypted data.
|
||||
typedef DWORD (__stdcall *LPGK_ROUTINE) (
|
||||
IN PGK_CONTEXT Context,
|
||||
IN LPVOID Args
|
||||
);
|
||||
|
||||
// Initialises Gimmick context
|
||||
GKSTATUS GkInitContext( LPVOID BaseAddress, PGK_CONTEXT Context, PUCHAR Key );
|
||||
// Frees Gimmick section context that was allocated with GkInitContext
|
||||
GKSTATUS GkFreeSectionContext( PGK_CONTEXT Context );
|
||||
// Retrieve handle to a module from PEB loader data
|
||||
HANDLE GkGetModuleHandle( DWORD Hash );
|
||||
// Get process address from loaded module data
|
||||
FARPROC GkGetProcAddress( PGK_CONTEXT Context, HANDLE hModule, DWORD Hash );
|
||||
// Request data from an encrypted section. Gimmick ensures the section is protected while the data is being used
|
||||
GKSTATUS GkGet( PGK_CONTEXT Context, PVOID Data );
|
||||
// Signal that data accessed by GkGet is no longer in use
|
||||
GKSTATUS GkRelease( PGK_CONTEXT Context, HANDLE Data );
|
||||
// Runs the function with the provided arguments (e.g. a struct pointer) and returns its return value. decrypting the section
|
||||
// it exists in if necesssary
|
||||
GKSTATUS GkRun( PGK_CONTEXT Context, LPGK_ROUTINE Function, PVOID Args, OUT PDWORD ReturnValue );
|
||||
// Thread routine to run a function asyncronously. Pass a pointer to the GK_ARGS struct
|
||||
DWORD WINAPI GkRunEx( LPVOID Args );
|
||||
|
||||
#define SEC( s ) __attribute__( ( section("." #s ) ) )
|
||||
|
||||
#endif //GIMMICK_H
|
||||
@@ -0,0 +1,2 @@
|
||||
pefile
|
||||
pycryptodome
|
||||
Reference in New Issue
Block a user