Clone the repository
git clone https://github.com/kokseen1/Gernig.git
Ensure that target_program.exe
is in the same directory.
cd Gernig
python example.py
The following example shows how to add noise via the Python interface:
from gernig import Noiser
from gernig.modules import *
n = Noiser("program64.exe") # Initialize with target binary
n.addNoise(PrintNoise("Hello world!")) # Add PrintNoise module
n.addNoise(DnsNoise("google.com")) # Add DnsNoise module
n.generate("output.exe")
Run the generated binary:
.\output.exe
The following shows the available modules that were implemented, what they are for, and how to use them.
NetworkNoise
is a network noise generator that generates network traffic by connecting to random IPs using the following protocols:
- ssh
- netcat
- ftp
- curl (http)
- ping
from gernig.noiser import Noiser
from gernig.modules import NetworkNoise
n = Noiser("<filename>")
n.addAnalysis(NetworkNoise())
n.generate()
FileNoise
is a file noise generator that generates file activity by creating new files in the following directories:
- Downloads
- Documents
- Pictures
- Desktop
- Videos
It creates files with the following extensions:
- .ps1
- .docx
- .txt
- .pptx
- .exe
from gernig.noiser import Noiser
from gernig.modules import FileNoise
n = Noiser("<filename>")
n.addAnalysis(FileNoise())
n.generate()
DnsNoise
is a DNS noise generator feature that queries for randomly generated domain names that are hardcoded into the binary at compile time.
Domain names are generated by using a dictionary wordlist under dns\wordlist.txt
, where two words are randomly picked out of the wordlist and concatenated together with a random top level domain using a TLD wordlist under dns\tld.txt
.
from gernig.noiser import Noiser
from gernig.modules import DnsNoise
n = Noiser("<filename>")
n.addAnalysis(DnsNoise())
n.generate()
TimeStomper
is a Time Stamp noise generator feature that modifies the file's Last Assessed, Last Modified and Last Created Date and Time.
Currently queries the directory for all user profile. Changes the file's time stamp in Documents, Downloads, Pictures and Videos.
from gernig.noiser import Noiser
from gernig.modules import TimeStomperNoise
n = Noiser("<filename>")
n.addAnalysis(TimeStomperNoise())
n.generate()
DnsAnalysis
checks for valid DNS resolvers on the system by ensuring that actual domain names are getting resolved and not all domain names. If it is unable to resolve the DNS names for a quarter or more of the valid DNS names, or a quarter or more of the invalid DNS names are being resolved, then the program exits. The domain names it uses to query are hardcoded into the binary on compile time.
Domain names are not regenerated by default if the fake-domains.txt
and resolved-domains.txt
files exist, as domain names will then be taken from these text files. Domain names can be regenerated at compile time by passing the 'force' parameter to the DnsAnalysis class. Domain names are generated using the same algorithm as the DnsNoise
class.
Parameter:
- force: pass the string "force" to it to regenerate domain names to be used for this function; optional argument
- num_domains: pass the number of domain names that you want to be used for doing DNS analysis, but keep in mind that the generation of domain names will take longer with a larger number of domain names. Default number of domain names generated for both real and fake domain names is 10. Must be used in conjunction with the 'force' parameter; optional argument
- resolved_domains: a list of domains that can be resolved that you can specify. If the force parameter is specified then this is ignored. Optional argument.
- fake_domains: a list of domains that cannot be resolved that you can specify. If the force parameter is specified then this is ignored. Optional argument.
from gernig.noiser import Noiser
from gernig.modules import DnsAnalysis
n = Noiser("<filename>")
n.addAnalysis(DnsAnalysis('force', 10))
n.generate()
It checks the MAC addresses of the host system to ensure that not all MAC address OUIs belong to virtual machine manufacturers.
Default MAC address OUIs to look out for:
00:05:69
(Vmware)
00:0C:29
(Vmware)
00:1C:14
(Vmware)
00:50:56
(Vmware)
00:15:5d
(Hyper-V)
08:00:27
(VirtualBox)
52:54:00
(VirtualBox)
00:21:F6
(VirtualBox)
00:14:4F
(VirtualBox)
00:0F:4B
(VirtualBox)
00:1C:42
(Parallels)
Parameter:
- blacklist: a list of MAC address OUIs to look out for; optional argument
from gernig.noiser import Noiser
from gernig.modules import MACAddrAnalysis
n = Noiser("<filename>")
n.addAnalysis(MACAddrAnalysis(['98:76:54', '12:34:56']))
n.generate()
It checks the CPU ID to ensure that it does not belong to a virtual machine CPU ID, and if the CPU ID belongs to the virtual machine CPU ID, then it terminates the program.
from gernig.noiser import Noiser
from gernig.modules import CPUIDAnalysis
n = Noiser("<filename>")
n.addAnalysis(CPUIDAnalysis())
n.generate()
It checks the name of the processes running on the system if there are any blacklisted process names amongst them, and if it finds a blacklisted process name, then it terminates the program.
Default process names to look out for:
"vmware.exe", "xenservice.exe", "vmsrvc.exe", "vboxservice.exe", "joeboxserver.exe", "prl_cc.exe"
Parameters:
- process_list: a list of blacklisted process names; optional argument
from gernig.noiser import Noiser
from gernig.modules import ProcessAnalysis
n = Noiser("<filename>")
n.addAnalysis(ProcessAnalysis(["vmware.exe", "sandbox_process.exe"]))
n.generate()
It delays the execution of the program while testing if the sleep function is patched by the host system, and aborts execution if the program actual sleep timing is different from the time it was supposed to sleep.
Default time in milliseconds:
10000
Parameters:
- sleep_time: time to delay program execution in milliseconds; optional argument
from gernig.noiser import Noiser
from gernig.modules import SleepAnalysis
n = Noiser("<filename>")
n.addAnalysis(SleepAnalysis(1000))
n.generate()
It kills the Event Logging service using an exploit, rendering any services that rely on the Windows Event Log useless.
from gernig.noiser import Noiser
from gernig.modules import EventlogBlind
n = Noiser("<filename>")
n.addBlind(EventlogBlind())
n.generate()
It packs the original executable with UPX to compress the binary's size.
from gernig.noiser import Noiser
from gernig.modules import EventlogBlind
n = Noiser("<filename>")
n.addBlind(UPXBlind())
n.generate()
loader32/64.exe
- Main program that will dispatch noise modules as threads and load the target binary from memoryprogram32/64.exe
- Sample target program used for testing, simulates a simple program loop
To develop and test core Gernig functionality outside of Python, build via Make:
cd gernig
make
Build for 32-bit:
make m32=1
Run on sample program:
loader64.exe program64.exe
Each noise module is separated into their respective .cpp
and .hpp
files located in gernig/src/modules
and gernig/include/modules
respectively.
Modules are implemented as thread callback functions passed to std::thread
, which will be dispatched from main
.
In the following module function, the printLoop
function will be launched as a thread and the argument msg
will be printed to the console every second, alongside the target binary.
void printLoop(std::string msg)
{
while (1)
{
std::cout << msg << std::endl;
Sleep(1000);
}
}
Preprocessor conditional directives are used to "pass information" from Python to C++, such as enabling a certain module or passing a string parameter:
defines.h
:
#define _PRINT_NOISE_ENABLED
#define _PRINT_NOISE_TEXT "Hello world!"
This is automatically generated when you run the Noiser python class, and can be ignored when running from Python.
All the modules should be placed in individual conditional directives to allow for the user to control which modules are to be enabled using Python classes of those modules as shown below. The specific functions for the different modules should be run as a thread. If a function is required to be run finish before the execution of the actual program, use .join() function of the thread class to wait for the thread to finish running first before continuing with program execution. All blinding and analysis modules are each required to be run to completion before the run of other modules or the actual program itself, and as such it is placed closer to the top of the main.cpp file.
main.cpp
:
#include <defines.h>
#ifdef _PRINT_NOISE_ENABLED
std::thread t1(printLoop, _PRINT_NOISE_TEXT);
#endif
Finally, the original target binary is embedded into the program as an array of unsigned char
in binexp.h
, and is parsed and loaded directly from memory via LoadFromMemory
by calling its entry point stored in pMemoryModule->exeEntry
.
Further obfuscation can be performed on this array if required.
binexp.h
:
unsigned char BINARY_ARRAY[] = {
0x4d, 0x5a, 0x90, 0x00, 0x03,
...
}