/glycine

A C++ self-encrypting API

Primary LanguagePython

Glycine

A C++ self-encrypting API.

I wrote this a while ago for fun but had no use for it, so I'm releasing it here. I have no idea how well this works as I only used a small/simple test case with it. YMMV.

Glycine is a simple function encryption framework that allows functions to decrypt and encrypt themselves when they are invoked.

Usage

Using glycine in a program is simple and straightforward:

#include "glycine.hpp"
#include <iostream>

int main()
{
	// Invoke<function>(args...)
	glycine::Invoke<printf>("Hello World!\n");
	return 0;
}

The only catch is that this requires work to be done after building the program in a post-build script. This is supplied in the scripts/ folder.

To implement glycine properly into your project, perform the following:

  • Assure that you are building in MSVC++ x64.
  • Assure that you are using C++20+.
  • Assure that you are generating debug information with /DEBUG.
  • Turn off ASLR (/DYNAMICBASE:NO).
  • Set a fixed base address (/FIXED).
  • Set the base address to whatever you like (e.g. /BASE:0x00007FF140000000)
  • Set a Post-Build event that executes the glycine post-build script with the first argument being the produced executable and the second argument the PDB file produced by the linker. This will be along the lines of py C:\dev\glycine\scripts\postbuild.py $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName).exe $(OutDir)$(TargetName).pdb

You can probably copy the settings from glycine_test.sln.

glycine::Invoke will not work with imported functions, nor will it work with non-literal functions passed as its templated parameter.

How it Works

Glycine's post-build script mutates the compiled program by encrypting each function that is used in glycine::Invoke with a simple XOR, which, on the C++ side, assumes that the function it is about to call is encrypted.

This is done by parsing the PDB produced by the linker, which is essential for this to work.

After invoking the function, Glycine re-encrypts it directly afterwards.

The way that glycine knows if a function is encrypted and its size in bytes is because the post-build script patches in a structure into a readable section of the program that contains the CRC32 of the each Invoked function's address, the function's byte size, and the CRC32 of the unencrypted function bytes. If the current function's bytes are CRC32'd and are not equal to the unencrypted CRC32, then the function is deemed encrypted and Glycine decrypts it before calling it.