Welcome to Geneva’s documentation!¶
Disclaimer: Running Geneva or Geneva’s strategies may place you at risk if you use it within a censoring regime. Geneva takes overt actions that interfere with the normal operations of a censor and its strategies are detectable on the network. During the training process, Geneva will intentionally trip censorship many times. Geneva is not an anonymity tool, nor does it encrypt any traffic. Understand the risks of running Geneva in your country before trying it.
Introduction¶
Geneva is an artificial intelligence tool that defeats censorship by exploiting bugs in censors, such as those in China, India, and Kazakhstan. Unlike many other anti-censorship solutions which require assistance from outside the censoring regime (Tor, VPNs, etc.), Geneva runs strictly on one side of the connection (either the client or server side).
Under the hood, Geneva uses a genetic algorithm to evolve censorship evasion strategies and has found several previously unknown bugs in censors. Geneva’s strategies manipulate the network stream to confuse the censor without impacting the client/server communication. This makes Geneva effective against many types of in-network censorship (though it cannot be used against IP-blocking censorship).
Geneva is composed of two high level components: its genetic algorithm (which it uses to evolve new censorship evasion strategies) and its strategy engine (which is uses to run an individual censorship evasion strategy over a network connection).
Geneva’s Github page contains the Geneva’s full implementation: its genetic algorithm, strategy engine, Python API, and a subset of published strategies. With these tools, users and researchers alike can evolve new strategies or leverage existing strategies to evade censorship. To learn more about how Geneva works, see How it Works.
Setup¶
Geneva has been developed and tested for Centos or Debian-based systems. Due to limitations of netfilter and raw sockets, Geneva does not work on OS X or Windows at this time and requires python3.6.
Install netfilterqueue dependencies:
# sudo apt-get install build-essential python-dev libnetfilter-queue-dev libffi-dev libssl-dev iptables python3-pip
Install Python dependencies:
# python3 -m pip install -r requirements.txt
Docker (Optional)¶
Geneva has an internal system that can be used to test strategies using Docker. This is largely used for testing fitness functions with the mock censors provided - it is not used for training against real censors. Due to limitations of raw sockets inside docker containers in many builds of Docker, Geneva cannot be used inside a docker container to communicate with hosts outside of Docker’s internal network.
When used with Docker, Geneva will spin up three docker containers: a client, a censor, and a server, and configure the networking routes such that the client and server communicate through the censor. To evaluate strategies (see much more detail in the evaluation section), Geneva will run the plugin client inside the client and attempt to communicate with the server through the censor.
Each docker container used by the evaluator runs out of the same base container.
Build the base container with:
docker build -t base:latest -f docker/Dockerfile .
Optionally, to manually run/inspect the docker image to explore the image, run:
docker run -it base
Getting Started¶
See our website or our research papers for an in-depth read on how Geneva works.
This documentation will provide a walkthrough of the main concepts behind Geneva, the main components of the codebase, and how they can be used.
This section will give a high level overview on how Geneva works; before using it, you are strongly recommended to read through How it Works.
What is a Strategy?¶
A censorship evasion strategy is simply a description of how network traffic should be modified. A strategy is not code, it is a description that tells the strategy engine how it should manipulate network traffic.
The goal of a censorship evasion strategy is to modify the network traffic in a such a way that the censor is unable to censor it, but the client/server communication is unimpacted.
Strategies & Species¶
Because Geneva commonly identifies many different strategies, we have defined a taxonomy to classify strategies into.
- The Strategy taxonomy is as follows, ordered from most general to most specific:
- Species: The overarching bug a strategy exploits
- Subspecies: The mechanism used to exploit the bug
- Variant: Salient wireline differences using the same bug mechanism
The highest level classification is species,
a broad class of strategies classified by the type of weakness it exploits in a
censor implementation. TCB Teardown
is an example of one such species; if the
censor did not prematurely teardown TCBs, all the strategies in this species
would cease to function.
Within each species, different subspecies represent
unique ways to exploit the weakness that defines the strategy. For example,
injecting an insertion TCP RST
packet would comprise one subspecies within the
TCB Teardown species; injecting a TCP FIN
would comprise another.
Within each
subspecies, we further record variants, unique strategies that leverage the same
attack vector, but do so slightly differently: corrupting the checksum field on
a RST
packet is one variant of the TCB Teardown w/ RST
subspecies of the TCB Teardown
species;
corrupting the ack
field is another.
We refer to specific individuals as extinct if they once worked against a censor but are no longer effective (less than 5% success rate). That formerly successful approaches could, after a few years, become ineffective lends further motivation for a technique that can quickly learn new strategies.
Running a Strategy¶
For a fuller description of the DNA syntax, see Censorship Evasion Strategies.
# python3 engine.py --server-port 80 --strategy "\/" --log debug
2019-10-14 16:34:45 DEBUG:[ENGINE] Engine created with strategy \/ (ID bm3kdw3r) to port 80
2019-10-14 16:34:45 DEBUG:[ENGINE] Configuring iptables rules
2019-10-14 16:34:45 DEBUG:[ENGINE] iptables -A OUTPUT -p tcp --sport 80 -j NFQUEUE --queue-num 1
2019-10-14 16:34:45 DEBUG:[ENGINE] iptables -A INPUT -p tcp --dport 80 -j NFQUEUE --queue-num 2
2019-10-14 16:34:45 DEBUG:[ENGINE] iptables -A OUTPUT -p udp --sport 80 -j NFQUEUE --queue-num 1
2019-10-14 16:34:45 DEBUG:[ENGINE] iptables -A INPUT -p udp --dport 80 -j NFQUEUE --queue-num 2
Note that if you have stale iptables
rules or other rules that rely on Geneva’s default queues,
this will fail. To fix this, remove those rules.
Strategy Library¶
Geneva has found dozens of strategies that work against censors in China, Kazakhstan, India, and Iran. We include several of these strategies in strategies.md . Note that this file contains success rates for each individual country; a strategy that works in one country may not work as well as other countries.
Researchers have observed that strategies may have differing success rates based on your exact location. Although we have not observed this from our vantage points, you may find that some strategies may work differently in a country we have tested. If this is the case, don’t be alarmed. However, please feel free to reach out to a member of the team directly or open an issue on this page so we can track how the strategies work from other geographic locations.
Disclaimer¶
Running these strategies may place you at risk if you use it within a censoring regime. Geneva takes overt actions that interfere with the normal operations of a censor and its strategies are detectable on the network. During the training process, Geneva will intentionally trip censorship many times. Geneva is not an anonymity tool, nor does it encrypt any traffic. Understand the risks of running Geneva in your country before trying it.
How it Works¶
See our website or our research papers for an in-depth read on how Geneva works.
This documentation will provide a walkthrough of the main concepts behind Geneva, the main components of the codebase, and how they can be used.
Censorship Evasion Strategies¶
A censorship evasion strategy is simply a description of how network traffic should be modified. A strategy is not code, it is a description that tells Geneva’s strategy engine how it should manipulate network traffic.
The goal of a censorship evasion strategy is to modify the network traffic in a such a way that the censor is unable to censor it, but the client/server communication is unimpacted.
A censorship evasion strategy composed of one or more packet-level building blocks. Geneva’s core building blocks are:
duplicate
: takes one packet and returns two copies of the packetdrop
: takes one packet and returns no packets (drops the packet)tamper
: takes one packet and returns the modified packetfragment
: takes one packet and returns two fragments or two segments
Since duplicate
and fragment
introduce branching, these actions are composed into a binary-tree structure called an action tree.
Each tree also has a trigger. The trigger describes which packets the tree should run on, and the tree describes what should happen to each of those packets when the trigger fires. Once a trigger fires on a packet, it pulls the packet into the tree for modifications, and the packets that emerge from the tree are sent on the wire. Recall that Geneva operates at the packet level, therefore all triggers are packet-level triggers.
Multiple action trees together form a forest. Geneva handles outbound and inbound packets differently, so strategies are composed of two forests: an outbound forest and an inbound forest.
Consider the following example of a simple Geneva strategy.
+---------------+ triggers on TCP packets with the flags
| TCP:flags:A | <-- field set to 'ACK' - matching packets
+-------+-------+ are captured and pulled into the tree
|
+---------v---------+ makes two copies of the given packet.
duplicate <-- the tree is processed with an inorder
+---------+---------+ traversal, so the left side is run first
|
+-------------+------------+
| |
+------------v----------+ v <-- dupilcate has no right child
tamper
{TCP:flags:replace:R} <-- parameters to this action describe how
+------------+----------+ the packet should be tampered
|
+------------v----------+
tamper
{TCP:chksum:corrupt}
+------------+----------+
|
v <-- packets that emerge from an in-order traversal
of the leaves are sent on the wire
Strategy DNA Syntax¶
These strategies can be arbitrarily complicated, and Geneva defines a well-formatted string syntax for unambiguously expressing strategies.
A strategy divides how it handles outbound and inbound packets: these are separated in the DNA by a
“\/”. Specifically, the strategy format is <outbound forest> \/ <inbound forest>
. If \/
is not
present in a strategy, all of the action trees are in the outbound forest.
Both forests are composed of action trees, and each forest is allowed an arbitrarily many trees.
Action trees always start with a trigger, which is formatted as: [<protocol>:<field>:<value>]
. For example, the trigger: [TCP:flags:S]
will run its corresponding tree whenever it sees a TCP
packet with the flags
field set to SYN
. If the corresponding action tree is [TCP:flags:S]-drop-|
, this action tree will cause the engine to drop any SYN
packets. [TCP:flags:S]-duplicate-|
will cause the engine to duplicate any SYN packets.
Triggers also can contain an optional 4th parameter for gas, which describes the number of times a trigger can fire. The triger [IP:version:4:4]
will run only on the first 4 IPv4 packets it sees. If the gas is negative, the trigger acts as a bomb trigger, which means the trigger will not fire until a certain number of applicable packets have been seen. For example, the trigger [IP:version:4:-2]
will trigger only after it has seen two matching packets (and it will not trigger on those first packets).
Syntactically, action trees end with -|
.
Depending on the type of action, some actions can have up to two children (such as duplicate
). These are represented
with the following syntax: [TCP:flags:S]-duplicate(<left_child>,<right_child>)-|
, where
<left_child>
and <right_child>
themselves are trees. If (,)
is not specified, any packets
that emerge from the action will be sent on the wire. If an action only has one child (such as tamper
), it is always the left child. [TCP:flags:S]-tamper{<parameters>}(<left_child>,)-|
Actions that have parameters specify those parameters within {}
. For example, giving parameters to the tamper
action could look like: [TCP:flags:S]-tamper{TCP:flags:replace:A}-|
. This strategy would trigger on TCP SYN
packets and replace the TCP flags
field to ACK
.
Putting this all together, below is the strategy DNA representation of the above diagram:
[TCP:flags:A]-duplicate(tamper{TCP:flags:replace:R}(tamper{TCP:chksum:corrupt},),)-| \/
Geneva has code to parse this strategy DNA into strategies that can be applied to network traffic using the engine.
Note
Due to limitations of Scapy and NFQueue, actions that introduce branching (fragment
, duplicate
) are disabled for incoming action forests.
Engine¶
The strategy engine (engine.py
) applies a strategy to a network connection. The engine works by capturing all traffic to/from a specified port. Packets that match an active trigger are run through the associated action-tree, and packets that emerge from the tree are sent on the wire.
# python3 engine.py --server-port 80 --strategy "\/" --log debug
2019-10-14 16:34:45 DEBUG:[ENGINE] Engine created with strategy \/ (ID bm3kdw3r) to port 80
2019-10-14 16:34:45 DEBUG:[ENGINE] Configuring iptables rules
2019-10-14 16:34:45 DEBUG:[ENGINE] iptables -A OUTPUT -p tcp --sport 80 -j NFQUEUE --queue-num 1
2019-10-14 16:34:45 DEBUG:[ENGINE] iptables -A INPUT -p tcp --dport 80 -j NFQUEUE --queue-num 2
2019-10-14 16:34:45 DEBUG:[ENGINE] iptables -A OUTPUT -p udp --sport 80 -j NFQUEUE --queue-num 1
2019-10-14 16:34:45 DEBUG:[ENGINE] iptables -A INPUT -p udp --dport 80 -j NFQUEUE --queue-num 2
The engine also has a Python API for using it in your application. It can be used as a context manager or invoked in the background as a thread. For example, consider the following simple application.
import os
import engine
# Port to run the engine on
port = 80
# Strategy to use
strategy = "[TCP:flags:A]-duplicate(tamper{TCP:flags:replace:R}(tamper{TCP:chksum:corrupt},),)-| \/"
# Create the engine in debug mode
with engine.Engine(port, strategy, log_level="debug") as eng:
os.system("curl http://example.com?q=ultrasurf")
This script creates an instance of the engine with a specified strategy, and that strategy will be running for everything within the context manager. When the context manager exits, the engine will clean itself up. See the examples/
folder for more use cases of the engine.
Note
Due to limitations of scapy and NFQueue, the engine cannot be used to communicate with localhost.
Evolution¶
Now that we have a concrete definition for a censorship evasion strategies and a way to use them, we can begin evolving new strategies with Geneva’s genetic algorithm.
A genetic algorithm is a type of machine learning inspired by natural selection. Over the course of many generations, it optimizes a numerical fitness score of individuals; within each generation, individuals that have a low fitness will not survive to the next generation, and those with a high fitness score will survive and propagate.
Each individual in Geneva is a censorship evasion strategy. evolve.py
is the main driver for Geneva’s genetic algorithm and maintains the population pool of these strategies.
- Each generation is comprised of the following:
- Mutation/Crossover - randomly mutating/mating strategies in the pool
- Strategy Evaluation - assigning a numerical fitness score to each strategy - decides which strategies should live on to the next generation
- Selection - the selection process of which strategies should survive to the next generation
For more detail on mutation/crossover or the selection process, see our papers or the code in evolve.py
for more detail. evolve.py
exposes options to control hyperparameters of mutation/crossover, as well as other advanced options for rejecting mutations it has seen before.
Strategy evaluation is significantly more complex, and will be covered in depth in the next section.
For most of this documentation, we will show examples of using evolve.py
with the --eval-only <strategy_here>
option. This instructs evolve.py
not to start the full genetic algorithm, but to instead perform a single strategy evaluation with the given parameters. --eval-only
can be replaced with parameters to the genetic algorithm to start strategy evolution.
Argument Parsing¶
Geneva uses a pass-through system of argument parsing. Different parts of the system define which arguments they care about, and they will parse just those args out.
If --help
is used, evolve.py
will collect the help messages for the relevant components (evaluator, plugins, etc).
Population Control¶
The two most important command line options for controlling evolutions are --population
and
--generations
. These control the population size and number of generations evolution will
take place for, respectively.
Geneva will not automatically halt evolution after population convergence occurs.
Seeding Population¶
Geneva allows you to seed the starting population pool of strategies using the --seed <strategy>
parameter. This will create the initial population pool with nothing but copies of the given strategy. Mutation is applied to each individual before evaluation begins, so the first generation is not solely one individual being evaluated over and over again.
Strategy Evaluation¶
Strategy evaluation tries to answer the question, “which censorship evasion
strategies should survive and propagate in the next generation?” This is the
job of the evaluator (evaluator.py
). The evaluator
assigns a numerical
fitness score to each strategy based on a fitness function. The actual numerical
fitness score itself is unimportant: as long as a ‘better’ strategy has a higher
fitness score than a ‘worse’ strategy, our fitness function will be sound.
Specific fitness numbers will be used in this section as examples, but all that
matters is the _comparison_ between fitness scores.
Since the goal of Geneva is to find a strategy that evades censorship, to test a
strategy, we can simply send a forbidden query through a censor with a strategy
running and see if the request gets censored. Geneva operates at the network
(TCP/IP) layer, so it is completely application agnostic: whether curl
or
Google Chrome is generating network traffic, the engine can capture and modify
it.
Environment IDs¶
During each evaluation, every strategy under test is given a random identifier, called an ‘environment id’. This is used to track each strategy during evaluation. As each strategy is evaluated, a log file is written out named after the environment ID. See Logging for more information on how logs are stored.
Plugins¶
To allow for evolution for many different applications, Geneva has a system of plugins (in plugins/). A plugin is used to drive an application to make a forbidden request, and defines the fitness function for that application. These plugins provide evaluator with a common interface to launch them and get the fitness score back.
Geneva provides some plugins with fitness functions for common protocols out of
the box. Whenever the evaluator or evolve.py
is run, you must specify
which plugin it should use with --test-type <plugin>
, such as
--test-type http
.
Plugins can define a client and optionally a server. The client will attempt to make a forbidden connection through the censor to an external server, or optionally if run from another computer, to an instance of the server plugin.
To evaluate a strategy, the evaluator will initialize the engine with the strategy, launch the plugin client, and record the fitness returned by the plugin.
See Adding New Plugins for more details on how plugins work, how they can override behavior in the evaluator, and how to add new ones to Geneva.
Fitness Functions¶
Unlike many other machine learning scenarios, we have no gradient to learn against; censors give us only a binary signal (censored or not) to learn from. Therefore, we can’t just write a fitness function that will directly guide us to the answer. Instead, we will use the fitness function to encourage the genetic algorithm to search the space of strategies that keeps the TCP connection alive.
As mentioned above, this guide might have specific fitness #s in examples, but the actual numeric values are not important - what is important is that a “bad” strategy gets a lower fitness number than a “good” strategy. We will use the fitness function to define a hierarchy of strategies.
- The comparison order used by Geneva, ordered from best to worst:
- Strategy that does not get censored and generates a minimal number of packets, no unused triggers, and minimal size
- Strategy that does not get censored, but has unused actions, is too large, or imparts overhead
- Strategy that gets censored
- Strategy that does not trigger on any packets but gets censored
- Strategy that breaks the underlying TCP connection
- Empty strategy
Accomplishing this hierarchy is relatively straightforward - when evaluating a strategy, we assign a numerical fitness score such that strategies can be compared to one another and will be sorted according to this list.
This is done to guide the genetic algorithm in its search through the space of strategies. For example, consider a population pool that has 4 empty strategies, and one strategy that breaks the TCP connection. After evaluation, the empty strategies would be killed off, and the strategy that runs is propagated. When the offspring of this strategy mutate, if one of them no longer breaks the underlying TCP connection, it will be considered more fit than the other strategies, and so on.
This hierarchy accomplishes a significant search space reduction. Instead of Geneva fuzzing the entire space of possible strategies (for which there are many!), it instead quickly eliminates strategies that break the underlying connection and encourages the genetic algorithm to concentrate effort on only those strategies that keep the underlying connection alive.
Said another way, this hierarchy allows Geneva to differentiate between a strategy shooting ourselves in the foot, and being caught by the censor.
Running the Evaluator¶
Since Geneva only needs to run on one side of the connection, Geneva is flexible as to where and how it can be run. Strategy evaluation can be done either on the server-side or the client-side. When used on the server-side, it can drive an external client to itself (or another server) for testing, or even act as a NAT-ing middlebox to interpose between the external client and true server.
The evaluator also allows plugins to override its default logic for single strategy evaluation, or for evaluating the entire strategy pool. See Adding New Plugins for more detail on writing your own plugin.
Client-side Evaluation¶
The most common use case for Geneva is client-side evaluation. In this mode, Geneva is run at the client-side inside a censored regime, trying to make a query to a forbidden resource located outside the censored regime. For this example, we will use the HTTP plugin. The HTTP plugin creates a forbidden HTTP GET request (such as example.com?q=ultrasurf in China).
The evaluator will start the engine, launch the client http plugin (which will make a request), and then the client plugin will record if the request succeeded or not. Under the hood, the GET request generated by the client HTTP plugin is caught by the engine (which modifies the traffic according to the strategy it is running) before sending it.
This effectively looks like this:
Censored Regime
---------------------------------------------------------+
|
Client - Geneva |
+---------------------------------------------+ |
| evaluator | | Server
| +-----------+ strategy | | +-----------------+
| | http | GET /bad +----------+ | +----------+ | |
| | plugin <--------------> engine <-----------> censor <--------> forbidden.org |
| | | +----------+ | +----------+ | |
| +-----------+ | | +-----------------+
| | |
+---------------------------------------------+ |
|
|
|
---------------------------------------------------------+
Note
For the purpose of this guide, examples will be given using evolve.py
. evolve.py
has an option --eval-only
to run the evaluator on a strategy with given parameters and exit. As usual, --eval-only
can also be replaced with parameters to the genetic algorithm to start the full genetic algorithm, instead of a single strategy evaluation.
To accomplish this, we can simply run:
# python3 evolve.py --eval-only "\/" --test-type http --server forbidden.org --log debug
This will start evolve.py
, which will launch the evaluator with the
http plugin, configured to make a request to forbidden.org, in debug mode.
Server-side Evaluation¶
Beyond using Geneva from the client-side, we can also use it from the server-side.
In this mode, Geneva can learn strategies that work from the server-side, subverting censorship on client’s behalf.
This requires a copy of Geneva to be checked out on both sides of the connection. To do this, we must specify the --server-side
flag.
Geneva will first start up a server for the specified plugin, and start the engine with the given strategy in front of the server. It next SSHes into an external client worker located inside a censored regime and runs the specified plugin through the SSH session. That plugin will generate a forbidden request to our server, and the plugin on the external client records if the request succeeded or not; the evaluator retrieves this fitness over the SSH session and evaluation is complete. This looks like this:
Censored Regime
+------------------------------+
| Server - Geneva
| +---------------------------------+
Client | XXXXXXXXXX | |
+------------+ | XX XX | strategy +-----------+ |
| | GET /bad +----------+ X X | +--------+ | | |
| curl <-------------> censor <------> Internet <----------> engine <---> evaluator | |
| | +----------+ X X | +--------+ | | |
+-----^------+ | XX XX | +-----+-----+ |
| | XXXXXXXXXX | | |
| | +-----------------------+---------+
| | |
+-----+------------------------+ |
| |
| 1. SSHs into the client and drives the request |
+----------------------------------------------------------------------------+
To run this with evolve.py
to evaluate an empty strategy (\/
), we can do:
# python3 evolve.py --test-type http --public-ip <mypublicip> --external-client example
--log debug --eval-only "\/" --server-side
Note that we specified two additional options here: the public IP address of the test computer (used for making the request), and the external client worker we want to use.
Note
See Adding a Worker on how to configure external workers for Geneva.
External Clients with Local Servers¶
Alternatively, we can evaluate the strategies on the client side, but have Geneva host the server for us so we can observe the traffic on both sides of the connection. By simply not specifying the --server-side
flag, we can accomplish this.
Censored Regime
+------------------------------------------+
| Server + Geneva
| +-------------------+
External Client | | |
+------------+ strategy | | +-----------+ |
| | GET /bad +--------+ +----+-----+ | | | |
| curl <------------> engine <----> censor <--------> evaluator | |
| | +--------+ +----+-----+ | | | |
+-----^------+ | | +-----+-----+ |
| | | | |
| | +-------------------+
| | |
+------------------------------------------+ |
| |
| 1. SSHs into the client and drives the request |
+--------------------------------------------------------+
# python3 evolve.py --test-type http --public-ip <mypublicip> --external-client example
--log debug --eval-only "\/" --server-side
External Clients with External Servers¶
Alternatively, the client can be driven to a different server from outside the censored regime with the '--external-server'
flag, and the server can be specified with '--server'
:
# python3 evolve.py --test-type http --public-ip <mypublicip> --external-client example
--log debug --eval-only "\/" --external-server --server http://wikipedia.org
Geneva
+--------------------------+
| |
| +-----------+ |
| | evaluator | |
| +-----+-----+ |
| | |
+-------------+------------+
|
SSHes & drives request |
+----------------+
|
| Censored Regime
+---------+----------------------------------------------+
| |
| External Client |
+---------+-----------------------------------+ |
| | | | Server
| +-----v-----+ strategy | | +-----------------+
| | http | GET /bad +----------+ | +-----+----+ | |
| | plugin <--------------> engine <-----------> censor <--------> forbidden.org |
| | | +----------+ | +-----+----+ | |
| +-----------+ | | +-----------------+
| | |
+---------------------------------------------+ |
|
|
|
+--------------------------------------------------------+
Engine as a Middlebox¶
There are many cases in which we cannot trigger a censor while communicating with servers we control. In these cases, we can use Geneva as a middlebox, and interpose between the client and server and apply the strategy in between.
To accomplish this, we will specify --act-as-middlebox
, and specify three additional routing options:
- Routing options:
--routing-ip
: Internal IP address of the middlebox--forward-ip
: IP address we are forwarding to--sender-ip
: IP address we are forwarding from
Note
Because Geneva operates at the packet-level, it cannot be used out of the box as a general purpose NAT, as it does not do connection tracking.
In this mode, the external client will communicate directly with our IP address, which will in turn forward the packets to the specified destination after they are modified according to the strategy under evaluation.
To run the strategy engine as a middlebox and drive an external client, we can add additional routing options:
# python3 evolve.py --test-type http --public-ip <mypublicip> --external-client example --log
debug --eval-only "\/" --server-side --act-as-middlebox --routing-ip <myinternalip>
--forward-ip <iptoforwardto> --sender-ip <ipofexternalclient>
1. SSHs into the client and drives the request
+------------------------------------------------+
| |
| +---------------------+
| Censored Regime | | |
+----------------------------+ | +-----+-----+ |
| | | | evaluator | |
v | | +-----------+ |
External Client | | | Server
+------------+ | | strategy | +---------+
| | GET /bad +----+-----+ | +--------+ | | |
| curl <------------> censor <------------> engine <------------> bad.com |
| | +----+-----+ | +--------+ | | |
+------------+ | | | +---------+
| +---------------------+
|
+----------------------------+
Note
The --routing-ip
is NOT the public IP address of your machine, its the internal address. This means that if you’re running on an EC2 machine, the --public-ip
is your external IP, but the --routing-ip
is the IP you see when you run 'ifconfig'
.
Internal Evaluation with Docker¶
For the purpose of internal testing and fitness function development, Geneva provides a set of simple mock censors (see censors/
). The evaluator can be configured to train against these censors.
Note
In order to do internal evaluation against these mock censors, you must have set up Geneva’s base Docker container. See Setup for how to do this.
When used with Docker, Geneva will spin up three docker containers: a client, a censor, and a server, and configure the networking routes such that the client and server communicate through the censor. To evaluate strategies, Geneva will run the plugin client inside the client and attempt to communicate with the server through the censor.
Each docker container used by the evaluator runs out of the same base container.
evaluator
+--------------------------------------------------------------+
| |
| Client Censor Server |
| +----------+ +----------+ +----------+ |
| | | | | | | |
| | client | | censor | | server | |
| | <-----+-----> <-----+-----> | |
| | plugin | ^ | | ^ | plugin | |
| | | | | | | | | |
| +----------+ | +----------+ | +----------+ |
| container | container | container |
+--------------------------------------------------------------+
| |
| |
+------------------+
engine can be run on either side
We can accomplish this by specifying the --censor <censor>
option. This will enable running with Docker, start the censor, and perform evaluation.
Depending on whether --server-side
is specified, the engine will be run on either the client or server side.
# python3 evolve.py --eval-only "" --test-type echo --censor censor2 --log debug
Note
--censor
cannot be used with --external-client
or --external-server
.
Adding a Worker¶
Below is how to add a new external client worker to Geneva. An external client worker is an external, SSH-accessible machine under the control of the user running Geneva for the purpose of performing strategy evaluation from outside the censored regime.
Geneva expects each of its worker to be defined in the workers
folder.
For this section, let us assume we are trying to allow Geneva to use a new external worker located in China.
First, make a new subfolder for that worker under the workers/
directory.
# mkdir workers/test
# ls workers/
example test
Each worker is defined by a worker.json
file located inside its subfolder.
The structure of the worker looks like this:
{
"name": "test",
"ip": "<ip_here>",
"hostname": "<hostname>",
"username": "user",
"password": null,
"port": 22,
"python": "python3",
"city": "Bejing",
"keyfile": "example.pem",
"country": "China",
"geneva_path": "~/geneva"
}
If passwordless SSH is used, you can optionally specify a keyfile for it to SSH with.
Once this is defined, we can specify --external-client test
during strategy evaluation, and the evaluator will SSH to this worker for training!
Note
Remember, external client workers must have Geneva cloned to the directory specified in geneva_path
and depencies set up before use.
Logging¶
Geneva uses multiple loggers during execution. evolve.py
creates the parent logger, and creates a subfolder of the current time under the trials/
directory.
- Within this directory, it creates 5 subfolders:
data
: used for misc. data related to strategy evaluationflags
: used to write status files to set eventsgenerations
: used to store the full generations and hall of fame after each generationlogs
: stores logs for evaluationpackets
: stores packet captures during strategy evolution
The two main log files used by evolve.py
are ga.log
and ga_debug.log
(everything in debug mode). As each strategy is evaluated, a <id>_engine.log
, <id>_server.log
, and <id>_client.log
files are generated.
For example, one run’s output could be:
# ls trials
2020-03-23_20:03:08
# ls trials/2020-03-23_20:03:08
data flags generations logs packets
# ls trials/2020-03-23_20:03:08/logs
ga.log ga_debug.log zhak1n81_client.log zhak1n81_engine.log zhak1n81_server.log
Automated Tests¶
Geneva has a system of automated tests in the tests/
directory, powered by pytest. Unless you are doing
modifications to the source code, you can generally ignore these.
If you need to run them yourself, you can do so with:
# python3 -m pytest -sv tests/
To put the tests in debug mode, you can add --evolve-logger debug
.
Putting it all Together¶
Now that we know how to leverage the framework, this short section will give a high level of how to put it all together and run Geneva’s genetic algorithm yourself.
The two most important command line options for controlling evolutions are --population
and
--generations
. These control the population size and number of generations evolution will
take place for, respectively.
Generally, these need not be very large. Geneva intentionally does strategy evaluation serially & slowly, so increasing the size dramatically will make evolution take longer. For our existing research papers, the population count rarely exceeded 300.
For example, to run a client-side evolution against HTTP censorship with a population pool of 200 and 25 generations, the following can be used:
# python3 evolve.py --population 200 --generations 25 --test-type http --server forbidden.org
Before running any evolution, it is recommended to spot test the plugins against whatever censor is used as the adversary to confirm the fitness function properly sets up the desirable hierarchy of individuals as specified in Fitness Functions: strategies that break the connection get lower fitness than those that get censored, which get a lower fitness than those that succeed.
And that’s it!
Adding New Plugins¶
This section will describe the process to add a new application plugin to Geneva. Application plugins serve as the fitness function for Geneva during evolution, and allow it to evolve strategies to defeat certain types of censorship.
Plugins are run by the Evaluator; if you have not yet read how the Evaluator works, see the Strategy Evaluation section.
There are three types of plugins: clients, servers, and overriding plugins. A developer can choose to implement any one of, or all three of these plugins.
For this section, we will build an example plugin and walk through the existing plugins to tour through the plugin API.
Plugins are expected to be in the plugins/
folder in geneva’s repo. The
folder name is the plugin name, and Geneva will discover these automatically.
Plugins are specified to the evaluator or evolve with the --test-type
flag. Within the plugin folder, plugins must adhere to the following naming
scheme:
client.py
- for plugin clientsserver.py
- for plugin servers [optional]plugin.py
- for an overriding plugin to customize logic [optional]
server.py
and plugin.py
are optional. The server plugin is
required to do server-side evaluation, but the overriding plugin definition is
only required to if a developer wishes to override the evaluator’s default
behavior.
If an overriding plugin is provided, the evaluator will simply invoke it at the start of strategy evaluation and the overriding plugin will be responsible for calling the client and server. This section will assume that no overriding plugin is specified to describe the evaluator’s default behavior with plugins, and cover use cases for overriding plugins at the end.
Depending on the evaluation setup, some (or all) of these plugins will be used during evaluation. For example, during an exclusively client-side evaluation, only the client plugin is needed.
Client Plugins¶
During exclusively client-side evolution, the evaluator will start the engine with the strategy under evaluation, and then run the client plugin. During server-side evolution, the evaluator will run the engine on the server-side, start the server plugin, and then start the client plugin via an SSH session to the remote client worker. (See Adding a Worker on how external workers can be used).
The client plugin subclasses from the PluginClient object. To tour through the API, we will walk through the development of a custom client plugin.
Writing Our Own¶
Let us write a fitness function to test Iran’s whitelisting system.
Iran’s protocol whitelister was a recently deployed new censorship mechanism to censor non-whitelisted protocols on certain ports (53, 80, 443). We deployed Geneva against the whitelister, and discovered multiple ways to evade it in just one evolution of the genetic algorithm. (The results of that investigation is located here).
The whitelister worked by checking the first 2 packets of a flow, and if they did not match a fingerprint, it would destroy the flow.
In order to run Geneva against whitelister, we will define a client plugin that will try to trigger the whitelister and record whether the whitelister successfully censored its connection or not.
First, let’s make a new folder in the plugins/
directory called
“whitelister”. We’ll create a “client.py” and create the plugin object as
a subclass of ClientPlugin.
class WhitelisterClient(ClientPlugin):
"""
Defines the whitelister client.
"""
name = "whitelister"
def __init__(self, args):
"""
Initializes the whitelister client.
"""
ClientPlugin.__init__(self)
self.args = args
Done! Next, let’s define argument parsing for this plugin. Geneva uses a pass-through system of argument parsing: when command-line arguments are specified, evolve.py parses the options it knows and passes the rest to the evaluator. The evaluator prases the options it knows, and passes the list to the plugins. This allows developers to easily add their own arguments just to their plugin and use them from the command-line without changing any of the intermediate code.
In this case, we need our client to make a TCP connection to a server located outside of Iran to send our whitelister triggering messages to. Let’s add an argument so the user can specify which server to connect to.
We can do this by adding a get_args
static method. The evaluator will
call this method when the plugin is created and give it the full command line
list, so the plugin is free to parse it how it chooses. For this example, we
will use the standard argparse
library.
Since the superclass also defines args, we’ll pass the command line list up to the super class as well to collect those arguments.
@staticmethod
def get_args(command):
"""
Defines args for this plugin
"""
super_args = ClientPlugin.get_args(command)
parser = argparse.ArgumentParser(description='Whitelister Client')
parser.add_argument('--server', action='store', help="server to connect to")
args, _ = parser.parse_known_args(command)
args = vars(args)
super_args.update(args)
return super_args
Now, we just need to define a run()
method. The run()
method is
called by the evaluator to run the plugin. It provides the parsed arguments, a
logger to log with, and a reference to an instance of the strategy engine that
is running the strategy (see Engine for more information on how the
engine works.)
Let’s start by defining the run method. We’ll pull out the argument for the server we defined earlier, connect to it with a python socket, and then just send “G”, “E”, and “T” in separate messages to trigger the whitelister. Since the whitelister censors connections by blackholing them, if the strategy failed to defeat the whitelister, we would expect our network connection to timeout; if we can send our messages and get a response from the server, the strategy under evaluation may have defeated the whitelister.
def run(self, args, logger, engine=None):
"""
Try to open a socket, send two messages, and see if the messages
time out.
"""
fitness = 0
port = int(args["port"])
server = args["server"]
try:
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.settimeout(3)
client.connect((server, port))
client.sendall(b"G")
time.sleep(0.25)
client.sendall(b"E")
time.sleep(0.25)
client.sendall(b"T\r\n\r\n")
server_data = client.recv(1024)
logger.debug("Data recieved: %s", server_data.decode('utf-8', 'ignore'))
if server_data:
fitness += 100
else:
fitness -= 90
client.close()
# ...
Now we just need to define the error handling for this code. This is critical to the fitness function: we want to kill off strategies that damage the underlying TCP connection, so Geneva does not waste time searching this space of strategies.
Our goal is to set the fitness metric such that a censorship event has a higher fitness than the strategy damaging the connection. Since we can distinguish these cases based on the socket error, we will set a lower fitness if any other exception is raised besides the timeout.
Lastly, we’ll inflate the numerical fitness metric to make it a larger number. The evaluator does additional punishments to the fitness score based on the strategy (see Strategy Evaluation), so we want the number to be sufficiently large to not push succeeding strategies to negative numbers.
except socket.timeout:
logger.debug("Client: Timeout")
fitness -= 90
except socket.error as exc:
fitness -= 100
logger.exception("Socket error caught in client echo test.")
finally:
logger.debug("Client finished whitelister test.")
return fitness * 4
Putting it all together:
class WhitelisterClient(ClientPlugin):
"""
Defines the whitelister client.
"""
name = "whitelister"
def __init__(self, args):
"""
Initializes the whitelister client.
"""
ClientPlugin.__init__(self)
self.args = args
@staticmethod
def get_args(command):
"""
Defines args for this plugin
"""
super_args = ClientPlugin.get_args(command)
parser = argparse.ArgumentParser(description='Whitelister Client')
parser.add_argument('--server', action='store', help="server to connect to")
args, _ = parser.parse_known_args(command)
args = vars(args)
super_args.update(args)
return super_args
def run(self, args, logger, engine=None):
"""
Try to open a socket, send two messages, and see if the messages
time out.
"""
fitness = 0
port = int(args["port"])
server = args["server"]
try:
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.settimeout(3)
client.connect((server, port))
client.sendall(b"G")
time.sleep(0.25)
client.sendall(b"E")
time.sleep(0.25)
client.sendall(b"T\r\n\r\n")
server_data = client.recv(1024)
logger.debug("Data recieved: %s", server_data.decode('utf-8', 'ignore'))
if server_data:
fitness += 100
else:
fitness -= 90
client.close()
except socket.timeout:
logger.debug("Client: Timeout")
fitness -= 90
except socket.error as exc:
fitness -= 100
logger.exception("Socket error caught in client echo test.")
finally:
logger.debug("Client finished whitelister test.")
return fitness * 4
Server Plugins¶
Coming soon!
Override Plugins¶
Coming soon!
Defining New Actions¶
It is simple to add a new packet-level action to Geneva.
Let us assume we are adding a new action, called “mytamper”, which simply sets the ipid
field of a packet.
Our action will take 1 packet and return 1 packet, and we’ll start by making it always set the IPID to 1.
We will subclasss the Action
class, and specify an __init__
method and a run
method.
In the __init__
method, we will specify that our action’s name is ‘mytamper’ and can run in both
inbound and outbound.
Then, in the run()
method, we will use Geneva’s packet API to set the ipid
field to 1 and simply return the packet.
from actions.action import Action
class MyTamperAction(Action):
"""
Geneva action to set the IPID to 1.
"""
# Controls frequency with which this action is chosen by the genetic algorithm
# during mutation
frequency = 0
def __init__(self, environment_id=None):
Action.__init__(self, "mytamper", "both")
def run(self, packet, logger):
"""
The mytamper action returns a modified packet as the left child.
"""
logger.debug(" - Changing IPID field to 1")
packet.set("IP", "ipid", 1)
return packet, None
And that’s it! Now, we can specify this action in our normal strategy DNA: Geneva will discover it dynamically on startup, import it, and we can use it.
Adding Parameters¶
Let’s now assume we want to make our action take parameters. We will add two new methods: parse()
and __str__()
.
We’ll start by adding a new instance variable self.ipid_value
.
def __init__(self, environment_id=None, ipid_value=1):
Action.__init__(self, "mytamper", "both")
self.ipid_value = ipid_value
Next, we’ll add the __str__
method so when our action is printed in the strategy DNA, its components are too:
def __str__(self):
"""
Returns a string representation.
"""
s = Action.__str__(self)
s += "{%g}" % self.ipid_value
return s
Finally, we’ll add the parse()
method so we can parse the value from a string strategy DNA to a live action.
def parse(self, string, logger):
"""
Parses a string representation for this object.
"""
try:
if string:
self.ipid_value = float(string)
except ValueError:
logger.exception("Cannot parse ipid_value %s" % string)
return False
return True
Putting it all together:
from actions.action import Action
class MyTamperAction(Action):
"""
Geneva action to set the IPID to 1.
"""
# Controls frequency with which this action is chosen by the genetic algorithm
# during mutation
frequency = 0
def __init__(self, environment_id=None, ipid_value=1):
Action.__init__(self, "mytamper", "both")
self.ipid_value = ipid_value
def run(self, packet, logger):
"""
The mytamper action returns a modified packet as the left child.
"""
logger.debug(" - Changing IPID field to 1")
packet.set("IP", "ipid", 1)
return packet, None
def __str__(self):
"""
Returns a string representation.
"""
s = Action.__str__(self)
s += "{%g}" % self.ipid_value
return s
def parse(self, string, logger):
"""
Parses a string representation for this object.
"""
try:
if string:
self.ipid_value = float(string)
except ValueError:
logger.exception("Cannot parse ipid_value %s" % string)
return False
return True
And we’re done! Now, we can write strategies like: [TCP:flags:PA]-mytamper{10}-|
, and any TCP packet with the flags field set to PA
will have its ipid
field set to 10.
Contributing¶
Contributions are welcome! You are encouraged to fork the repository, open Github issues with us, or just make a pull request. If you are interested in becoming more involved with the team or development, checkout our website a https://censorship.ai and drop us a line!
geneva.engine¶
Geneva Strategy Engine
Given a strategy and a server port, the engine configures NFQueue to capture all traffic into and out of that port so the strategy can run over the connection.
-
class
engine.
Engine
(server_port, string_strategy, environment_id=None, server_side=False, output_directory='trials', log_level='info', file_log_level='info', enabled=True, in_queue_num=None, out_queue_num=None, forwarder=None, save_seen_packets=True, demo_mode=False)¶ Bases:
object
-
__init__
(server_port, string_strategy, environment_id=None, server_side=False, output_directory='trials', log_level='info', file_log_level='info', enabled=True, in_queue_num=None, out_queue_num=None, forwarder=None, save_seen_packets=True, demo_mode=False)¶ Parameters: - server_port (str) – The port(s) the engine will monitor
- string_strategy (str) – String representation of strategy DNA to apply to the network
- environment_id (str, None) – ID of the given strategy
- server_side (bool, False) – Whether or not the engine is running on the server side of the connection
- output_directory (str, 'trials') – The path logs and packet captures should be written to
- enabled (bool, True) – whether or not the engine should be started (used for conditional context managers)
- in_queue_num (int, None) – override the netfilterqueue number used for inbound packets. Used for running multiple instances of the engine at the same time. Defaults to None.
- out_queue_num (int, None) – override the netfilterqueue number used for outbound packets. Used for running multiple instances of the engine at the same time. Defaults to None.
- save_seen_packets (bool, True) – whether or not the engine should record and save packets it sees while running. Defaults to True, but it is recommended this be disabled on higher throughput systems.
- demo_mode (bool, False) – whether to replace IPs in log messages with random IPs to hide sensitive IP addresses.
-
configure_iptables
(remove=False)¶ Handles setting up ipables for this run
-
delayed_send
(packet, delay)¶ Method to be started by a thread to delay the sending of a packet without blocking the main thread.
-
do_nat
(packet)¶ NATs packet: changes the sources and destination IP if it matches the configured route, and clears the checksums for recalculating
Parameters: packet (layers.packet.Packet) – packet to modify before sending Returns: the modified packet Return type: layers.packet.Packet
-
handle_packet
(packet)¶ Handles processing an outbound packet through the engine.
-
in_callback
(nfpacket)¶ Callback bound to the incoming nfqueue rule. Since we can’t manually send packets to ourself, process the given packet here.
-
initialize_nfqueue
()¶ Initializes the nfqueue for input and output forests.
-
mysend
(packet)¶ Helper scapy sending method. Expects a Geneva Packet input.
-
out_callback
(nfpacket)¶ Callback bound to the outgoing nfqueue rule to run the outbound strategy.
-
run_nfqueue
(nfqueue, nfqueue_socket, direction)¶ Handles running the outbound nfqueue socket with the socket timeout.
-
shutdown_nfqueue
()¶ Shutdown nfqueue.
-
-
engine.
get_args
()¶ Sets up argparse and collects arguments.
-
engine.
main
(args)¶ Kicks off the engine with the given arguments.
geneva.evaluator¶
The Evaluator is charged with evaluating a given strategy and assigning a numerical fitness metric to it.
-
class
evaluator.
Evaluator
(command, logger)¶ Bases:
object
-
__init__
(command, logger)¶ Initialize the global evaluator for this evolution.
Parameters: - command (list) – sys.argv or list of arguments
- logger (
logging.Logger
) – logger passed in from the main driver to log from
-
assign_ids
(ind_list)¶ Assigns random environment ids to each individual to be evaluated.
Parameters: ind_list (list) – List of individuals to assign random IDs to
-
canary_phase
(canary)¶ Learning phase runs the client against the censor to collect packets.
Parameters: canary ( actions.strategy.Strategy
) – A (usually empty) strategy object to evaluateReturns: canary id used (“canary”) Return type: str
-
create_test_environment
(worker_id)¶ Creates a test environment in docker.
Parameters: worker_id (int) – Worker ID of this worker Returns: Environment dictionary to use Return type: dict
-
dump_logs
(environment_id)¶ Dumps client, engine, server, and censor logs, to be called on test failure at ERROR level.
Parameters: environment_id (str) – Environment ID of a strategy to dump
-
evaluate
(ind_list)¶ Perform the overall fitness evaluation driving.
Parameters: ind_list (list) – list of individuals to evaluate Returns: Population list after evaluation Return type: list
-
exec_cmd
(command, timeout=60)¶ Runs a subprocess command at the correct log level.
Parameters: - command (list) – Command to execute.
- timeout (int, optional) – Timeout for execution
-
exec_cmd_output
(command, timeout=60)¶ Runs a subprocess command at the correct log level. This is a separate method from the above exec_cmd, since that is used to stream output to the screen (so check_output is not appropriate).
Parameters: - command (list) – Command to execute.
- timeout (int, optional) – Timeout for execution
Returns: Output of command
Return type: str
-
get_ip
()¶ Gets IP of evaluator computer.
Returns: Public IP provided Return type: str
-
get_log
(remote, worker, log_name, logger)¶ Retrieves a log from the remote server and writes it to disk.
Parameters: - remote – A Paramiko SSH channel to execute over
- worker (dict) – Dictionary describing external client worker
- log_name (str) – Log name to retrieve
- logger (
logging.Logger
) – A logger to log with
-
get_pid
(container)¶ Returns PID of first actively running python process.
Parameters: container – Docker container object to query Returns: PID of Python process Return type: int
-
initialize_base_container
(name)¶ Builds a base container with a given name and connects it to a given network. Also retrieves lower level settings and the IP address of the container.
Parameters: name (str) – Name of this docker container Returns: Dictionary containing docker container object and relevant information Return type: dict
-
parse_ip
(container, iface)¶ Helper method to parse an IP address from ifconfig.
Parameters: - container – Docker container object to execute within
- iface (str) – Interface to parse from
Returns: IP address
Return type: str
-
read_fitness
(ind)¶ Looks for this individual’s fitness file on disk, opens it, and stores the fitness in the given individual.
Parameters: ind ( actions.strategy.Strategy
) – Individual to read fitness for
-
remote_exec_cmd
(remote, command, logger, timeout=15, verbose=True)¶ Given a remote SSH session, executes a string command. Blocks until command completes, and returns the stdout and stderr. If the SSH connection is broken, it will try again.
Parameters: - remote – Paramiko SSH channel to execute commands over
- command (str) – Command to execute
- logger (
logging.Logger
) – A logger to log with - timeout (int, optional) – Timeout for the command
- verbose (bool, optional) – Whether the output should be printed
Returns: (stdout, stderr) of command, each is a list
Return type: tuple
-
run_client
(args, environment, logger)¶ Runs the plugin client given the current configuration
Parameters: - args (dict) – Dictionary of arguments
- environment (dict) – Dictionary describing environment configuration for this evaluation
- logger (
logging.Logger
) – A logger to log with
Returns: Fitness of individual
Return type: float
-
run_docker_client
(args, environment, logger)¶ Runs client within the docker container. Does not return fitness; instead fitness is written via the flags directory and read back in later.
Parameters: - args (dict) – Dictionary of arguments
- environment (dict) – Dictionary describing environment configuration for this evaluation
- logger (
logging.Logger
) – A logger to log with
-
run_docker_server
(args, environment, logger)¶ Runs server and censor in their respective docker containers.
Parameters: - args (dict) – Dictionary of arguments
- environment (dict) – Dictionary describing environment configuration for this evaluation
- logger (
logging.Logger
) – A logger to log with
-
run_local_client
(args, environment, logger)¶ Runs client locally. Does not return fitness.
Parameters: - args (dict) – Dictionary of arguments
- environment (dict) – Dictionary describing environment configuration for this evaluation
- logger (
logging.Logger
) – A logger to log with
-
run_local_server
(args, environment, logger)¶ Runs local server.
Parameters: - args (dict) – Dictionary of arguments
- environment (dict) – Dictionary describing environment configuration for this evaluation
- logger (
logging.Logger
) – A logger to log with
-
run_remote_client
(args, environment, logger)¶ Runs client remotely over SSH, using the given SSH channel
Parameters: - args (dict) – Dictionary of arguments
- environment (dict) – Dictionary describing environment configuration for this evaluation
- logger (
logging.Logger
) – A logger to log with
Returns: Fitness of individual
Return type: float
-
run_test
(environment, ind)¶ Conducts a test of a given individual in the environment.
Parameters: - environment (dict) – Dictionary of environment variables
- ind (
actions.strategy.Strategy
) – A strategy object to test with
Returns: (ind.environment_id, ind.fitness) environment ID of strategy and fitness
Return type: tuple
-
setup_remote
()¶ Opens an SSH tunnel to the remote client worker.
-
shutdown
()¶ Shuts down all active environments
-
shutdown_container
(container)¶ Tries to shutdown a given container and eats a NotFound exception if the container has already exited.
Parameters: container – docker container object to call stop() on
-
shutdown_environment
(environment)¶ Shuts down the evaluation environment. If Docker, shuts down server and client container. If a remote SSH connection, the connection is shut down.
-
start_censor
(environment, environment_id)¶ Starts the censor in the server environment container.
Parameters: - environment (dict) – Environment dictionary
- environment_id (str) – Environment ID of the censor to stop
-
start_server
(args, environment, logger)¶ Launches the server.
Parameters: - args (dict) – Dictionary of arguments
- environment (dict) – Dictionary describing environment configuration for this evaluation
- logger (
logging.Logger
) – A logger to log with
Returns: fitness of individual (if one is provided)
Return type: float
-
stop_censor
(environment)¶ Send SIGKILL to all remaining python processes in the censor container. This is done intentionally over a SIGINT or a graceful shutdown mecahnism - due to dynamics with signal handling in nfqueue callbacks (threads), SIGINTs can be ignored and graceful shutdown mechanisms may not be picked up (or be fast enough).
The output this method parses is below:
# ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.1 0.0 21944 3376 pts/0 Ss+ 13:30 0:00 /bin/bash root 14 24.0 0.4 200376 38564 ? Ss 13:30 0:00 python code/censor_driver.py censor2 jgsko1rf trials/2018-10-30_06:30:48 60181 root 32 0.0 0.0 19188 2400 ? Rs 13:30 0:00 ps aux
Parameters: environment (dict) – Environment dictionary
-
stop_server
(environment, server)¶ Stops server.
Parameters: - environment (dict) – Environment dictionary
- server (
plugins.plugin_server.ServerPlugin
) – A plugin server to stop
-
terminate_docker
()¶ Terminates any hanging running containers.
-
update_ports
(environment)¶ Checks that the chosen port is open inside the docker container - if not, it chooses a new port.
Parameters: environment (dict) – Dictionary describing docker environment
-
worker
(ind_list, worker_id, environment)¶ Perform the actual fitness evaluation as a multithreaded worker. The worker pops off an individual from the list and evaluates it.
Parameters: - ind_list (list) – List of strategy objects to evaluate
- worker_id (int) – ID of this worker
- environment (dict) – Environment dictionary
-
-
evaluator.
collect_plugin
(test_plugin, plugin_type, command, full_args, plugin_args)¶ Import a given plugin
Parameters: - test_plugin (str) – Plugin name to import (“http”)
- plugin_type (str) – Component of plugin to import (“client”)
- command (list) – sys.argv or list of arguments
- full_args (dict) – Parsed full list of arguments already maintained by the parent plugin
- plugin_args (dict) – Dictionary of args specific to this plugin component
Returns: Imported plugin class for instantiation later
-
evaluator.
get_arg_parser
(single_use=False)¶ Sets up argparse. This is done separately to enable collection of help messages.
Parameters: single_use (bool, optional) – whether this evaluator will only be used for one strategy, used to configure sane defaults
-
evaluator.
get_args
(cmd, single_use=False)¶ Creates an argparser and collects arguments.
Parameters: single_use (bool, optional) – whether this evaluator will only be used for one strategy, used to configure sane defaults Returns: parsed args Return type: dict
-
evaluator.
get_random_open_port
()¶ Selects a random ephemeral port that is open.
Returns: Open port Return type: int
geneva.evolve¶
Main evolution driver for Geneva (GENetic EVAsion). This file performs the genetic algorithm, and relies on the evaluator (evaluator.py) to provide fitness evaluations of each individual.
-
evolve.
add_to_hof
(hof, population)¶ Iterates over the current population and updates the hall of fame. The hall of fame is a dictionary that tracks the fitness of every run of every strategy ever.
Parameters: - hof (dict) – Current hall of fame
- population (list) – Population list
Returns: Updated hall of fame
Return type: dict
-
evolve.
collect_plugin_args
(cmd, plugin, plugin_type, message=None)¶ Collects and prints arguments for a given plugin.
Parameters: - cmd (list) – sys.argv or a list of args to parse
- plugin (str) – Name of plugin to import (“http”)
- plugin_type (str) – Component of plugin to import (“client”)
- message (str) – message to override for printing
-
evolve.
collect_results
(hall_of_fame)¶ Collect final results from offspring.
Parameters: hall_of_fame (dict) – Hall of fame of individuals Returns: Formatted printout of the hall of fame Return type: str
-
evolve.
driver
(cmd)¶ Main workflow driver for the solver. Parses flags and input data, and initiates solving.
Parameters: cmd (list) – sys.argv or a list of arguments Returns: Hall of fame of individuals Return type: dict
-
evolve.
eval_only
(logger, requested, ga_evaluator, runs=1)¶ Parses a string representation of a given strategy and runs it through the evaluator.
Parameters: - logger (
logging.Logger
) – A logger to log with - requested (str) – String representation of requested strategy or filename of strategies
- ga_evaluator (
evaluator.Evaluator
) – An evaluator to evaluate with - runs (int) – Number of times each strategy should be evaluated
Returns: Success rate of tested strategies
Return type: float
- logger (
-
evolve.
fitness_function
(logger, population, ga_evaluator)¶ Calls the evaluator to evaluate a given population of strategies. Sets the .fitness attribute of each individual.
Parameters: - logger (
logging.Logger
) – A logger to log with - population (list) – List of individuals to evaluate
- ga_evaluator (
evaluator.Evaluator
) – An evaluator object to evaluate with
Returns: Population post-evaluation
Return type: list
- logger (
-
evolve.
generate_strategy
(logger, num_in_trees, num_out_trees, num_in_actions, num_out_actions, seed, environment_id=None, disabled=None)¶ Generates a strategy individual.
Parameters: - logger (
logging.Logger
) – A logger to log with - num_in_trees (int) – Number of trees to initialize in the inbound forest
- num_out_trees (int) – Number of trees to initialize in the outbound forest
- num_in_actions (int) – Number of actions to initialize in the each inbound tree
- num_out_actions (int) – Number of actions to initialize in the each outbound tree
- environment_id (str, optional) – Environment ID to assign to the new individual
- disabled (str, optional) – List of actions that should not be considered in building a new strategy
Returns: A new strategy object
Return type: actions.strategy.Strategy
- logger (
-
evolve.
genetic_solve
(logger, options, ga_evaluator)¶ Run genetic algorithm with given options.
Parameters: - logger (
logging.Logger
) – A logger to log with - options (dict) – Options to respect.
- ga_evaluator (
evaluator.Evaluator
) – Evaluator to evaluate strategies with
Returns: Hall of fame of individuals
Return type: dict
- logger (
-
evolve.
get_args
(cmd)¶ Sets up argparse and collects arguments.
Parameters: cmd (list) – sys.argv or a list of args to parse Returns: Parsed arguments Return type: namespace
-
evolve.
get_unique_population_size
(population)¶ Computes number of unique individuals in a population.
Parameters: population (list) – Population list
-
evolve.
initialize_population
(logger, options, canary_id, disabled=None)¶ Initializes the population from either random strategies or strategies located in a file.
Parameters: - logger (
logging.Logger
) – A logger to log with - options (dict) –
Options to respect in generating initial population. Options that can be specified as keys:
”load_from” (str, optional): File to load population from population_size (int): Size of population to initialize
”in-trees” (int): Number of trees to initialize in inbound forest of each individual
”out-trees” (int): Number of trees to initialize in outbound forest of each individual
”in-actions” (int): Number of actions to initialize in each inbound tree of each individual
”out-actions” (int): Number of actions to initialize in each outbound tree of each individual
”seed” (str): Strategy to seed this pool with
- canary_id (str) – ID of the canary strategy, used to associate each new strategy with the packets captured during the canary phase
- disabled (list, optional) – List of actions that are disabled
Returns: New population of individuals
Return type: list
- logger (
-
evolve.
load_generation
(logger, filename)¶ Loads strategies from a file
Parameters: - logger (
logger.Logger
) – A logger to log with - filename (str) – Filename of file containing newline separated strategies to read generation from
- logger (
-
evolve.
mutate_individual
(logger, ind)¶ Simply calls the mutate function of the given individual.
Parameters: - logger (
logging.Logger
) – A logger to log with - ind (
actions.strategy.Strategy
) – A strategy individual to mutate
Returns: Mutated individual
Return type: actions.strategy.Strategy
- logger (
-
evolve.
mutation_crossover
(logger, population, hall, options)¶ Apply crossover and mutation on the offspring.
Hall is a copy of the hall of fame, used to accept or reject mutations.
Parameters: - logger (
logging.Logger
) – A logger to log with - population (list) – Population of individuals
- hall (dict) – Current hall of fame
- options (dict) – Options to override settings. Accepted keys are: “crossover_pb” (float): probability of crossover “mutation_pb” (float): probability of mutation “allowed_retries” (int): number of times a strategy is allowed to exist in the hall of fame. “no_reject_empty” (bool): whether or not empty strategies should be rejected
Returns: New population after mutation
Return type: list
- logger (
-
evolve.
print_results
(hall_of_fame, logger)¶ Prints hall of fame.
Parameters: - hall_of_fame (dict) – Hall of fame to print
- logger (
logging.Logger
) – A logger to log results with
-
evolve.
restrict_headers
(logger, protos, filter_fields, disabled_fields)¶ Restricts which protocols/fields can be accessed by the algorithm.
Parameters: - logger (
logging.Logger
) – A logger to log with - protos (str) – Comma separated string of protocols that are allowed
- filter_fields (str) – Comma separated string of fields to allow
- disabled_fields (str) – Comma separated string of fields to disable
- logger (
-
evolve.
run_collection_phase
(logger, ga_evaluator)¶ Individual mutation works best when it has seen real packets to base action and trigger values off of, instead of blindly fuzzing packets. Usually, the 0th generation is useless because it hasn’t seen any real packets yet, and it bases everything off fuzzed data. To combat this, a canary phase is done instead.
In the canary phase, a single dummy individual is evaluated to capture packets. Once the packets are captured, they are associated with all of the initial population pool, so all of the individuals have some packets to base their data off of.
Since this phase by necessity requires the evaluator, this is only run if –no-eval is not specified.
Parameters: - logger (
logging.Logger
) – A logger to log with - ga_evaluator (
evaluator.Evaluator
) – An evaluator object to evaluate with
Returns: ID of the test ‘canary’ strategy evaluated to do initial collection
Return type: str
- logger (
-
evolve.
sel_random
(individuals, k)¶ Implementation credit to DEAP: https://github.com/DEAP/deap Select k individuals at random from the input individuals with replacement. The list returned contains references to the input individuals.
Parameters: - individuals (list) – A list of individuals to select from.
- k (int) – The number of individuals to select.
Returns: A list of selected individuals.
Return type: list
-
evolve.
selection_tournament
(individuals, k, tournsize, fit_attr='fitness')¶ Implementation credit to DEAP: https://github.com/DEAP/deap Select the best individual among tournsize randomly chosen individuals, k times. The list returned contains references to the input individuals.
Parameters: - individuals (list) – A list of individuals to select from.
- k (int) – The number of individuals to select.
- tournsize (int) – The number of individuals participating in each tournament.
- fit_attr – The attribute of individuals to use as selection criterion (defaults to “fitness”)
Returns: A list of selected individuals.
Return type: list
-
evolve.
setup_logger
(log_level)¶ Sets up the logger. This will log at the specified level to “ga.log” and at debug level to “ga_debug.log”. Logs are stored in the trials/ directory under a run-specific folder. Example: trials/2020-01-01_01:00:00/logs/ga.log
Parameters: log_level (str) – Log level to use in setting up the logger (“debug”)
-
evolve.
write_generation
(filename, population)¶ Writes the population pool for a specific generation.
Parameters: - filename (str) – Name of file to write the generation out to
- population (list) – List of individuals to write out
-
evolve.
write_hall
(filename, hall_of_fame)¶ Writes hall of fame out to a file.
Parameters: - filename (str) – Filename to write results to
- hall_of_fame (dict) – Hall of fame of individuals
geneva.actions.action¶
Geneva superclass object for defining a packet-level action.
-
class
action.
Action
(action_name, direction)¶ Bases:
object
Defines the superclass for a Geneva Action.
-
__init__
(action_name, direction)¶ Initializes this action object.
Parameters: - action_name (str) – Name of this action (“duplicate”)
- direction (str) – Direction of this action (“out”, “both”, “in”)
-
applies
(direction)¶ Returns whether this action applies to the given direction, as branching actions are not supported on inbound trees.
Parameters: direction (str) – Direction to check if this action applies (“out”, “in”, “both”) Returns: whether or not this action can be used to a given direction Return type: bool
-
static
get_actions
(direction, disabled=None, allow_terminal=True)¶ Dynamically imports all of the Action classes in this directory.
Will only return terminal actions if terminal is set to True.
Parameters: - direction (str) – Limit imported actions to just those that can run to this direction (“out”, “in”, “both”)
- disabled (list, optional) – list of actions that are disabled
- allow_terminal (bool) – whether or not terminal actions (“drop”) should be imported
Returns: Dictionary of imported actions
Return type: dict
-
mutate
(environment_id=None)¶ Mutates packet.
-
static
parse_action
(str_action, direction, logger)¶ Parses a string action into the action object.
Parameters: - str_action (str) – String representation of an action to parse
- direction (str) – Limit actions searched through to just those that can run to this direction (“out”, “in”, “both”)
- logger (
logging.Logger
) – a logger to log with
Returns: A parsed action object
Return type:
-
frequency
= 0¶
-
ident
= 0¶
-
geneva.actions.drop¶
-
class
drop.
DropAction
(environment_id=None)¶ Bases:
actions.action.Action
Geneva action to drop the given packet.
-
__init__
(environment_id=None)¶ Initializes this drop action.
Parameters: environment_id (str, optional) – Environment ID of the strategy we are a part of
-
run
(packet, logger)¶ The drop action returns None for both it’s left and right children, and does not pass the packet along for continued use.
-
frequency
= 1¶
-
geneva.actions.duplicate¶
-
class
duplicate.
DuplicateAction
(environment_id=None)¶ Bases:
actions.action.Action
Defines the DuplicateAction - returns two copies of the given packet.
-
__init__
(environment_id=None)¶ Initializes this action object.
Parameters: - action_name (str) – Name of this action (“duplicate”)
- direction (str) – Direction of this action (“out”, “both”, “in”)
-
mutate
(environment_id=None)¶ Swaps its left and right child
-
run
(packet, logger)¶ The duplicate action duplicates the given packet and returns one copy for the left branch, and one for the right branch.
-
frequency
= 3¶
-
geneva.actions.fragment¶
-
class
fragment.
FragmentAction
(environment_id=None, correct_order=None, fragsize=-1, segment=True, overlap=0)¶ Bases:
actions.action.Action
Defines the FragmentAction for Geneva - fragments or segments the given packet.
-
__init__
(environment_id=None, correct_order=None, fragsize=-1, segment=True, overlap=0)¶ Initializes a fragment action object.
Parameters: - environment_id (str, optional) – Environment ID of the strategy this object is a part of
- correct_order (bool, optional) – Whether or not the fragments/segments should be returned in the correct order
- fragsize (int, optional) – The index this packet should be cut. Defaults to -1, which cuts it in half.
- segment (bool, optional) – Whether we should perform fragmentation or segmentation
- overlap (int, optional) – How many bytes the fragments/segments should overlap
-
fragment
(original, fragsize)¶ Fragments a packet into two, given the size of the first packet (0:fragsize) Always returns two packets
-
get_rand_order
()¶ Randomly decides if the fragments should be reversed.
-
ip_fragment
(packet, logger)¶ Perform IP fragmentation.
-
mutate
(environment_id=None)¶ Mutates the fragment action - it either chooses a new segment offset, switches the packet order, and/or changes whether it segments or fragments.
-
parse
(string, logger)¶ Parses a string representation of fragmentation. Nothing particularly special, but it does check for a the fragsize.
Note that the given logger is a DIFFERENT logger than the logger passed to the other functions, and they cannot be used interchangeably. This logger is attached to the main GA driver, and is run outside the evaluator. When the action is actually run, it’s run within the evaluator, which by necessity must pass in a different logger.
-
run
(packet, logger)¶ The fragment action fragments each given packet.
-
tcp_segment
(packet, logger)¶ Segments a packet into two, given the size of the first packet (0:fragsize) Always returns two packets, since fragment is a branching action, so if we are unable to segment, it will duplicate the packet.
If overlap is specified, it will select n bytes from the second packet and append them to the first, and increment the sequence number accordingly
-
frequency
= 2¶
-
geneva.layers.layer¶
geneva.layers.packet¶
geneva.actions.sleep¶
-
class
sleep.
SleepAction
(time=1, environment_id=None)¶ Bases:
actions.action.Action
Defines the SleepAction - causes the engine to pause before sending a packet.
-
__init__
(time=1, environment_id=None)¶ Initializes the sleep action.
Parameters: - time (float) – How much time the packet should delay before sending
- environment_id (str, optional) – Environment ID of the strategy this action is a part of
-
parse
(string, logger)¶ Parses a string representation for this object.
-
run
(packet, logger)¶ The sleep action simply passes along the packet it was given with an instruction for how long the engine should sleep before sending it.
-
frequency
= 0¶
-
geneva.actions.strategy¶
-
class
strategy.
Strategy
(in_actions, out_actions, environment_id=None)¶ Bases:
object
-
__init__
(in_actions, out_actions, environment_id=None)¶ Initialize self. See help(type(self)) for accurate signature.
-
act_on_packet
(packet, logger, direction='out')¶ Runs the strategy on a given scapy packet.
-
init_from_scratch
(num_in_trees, num_out_trees, num_in_actions, num_out_actions, disabled=None)¶ Initializes this individual by drawing random actions.
-
initialize
(logger, num_in_trees, num_out_trees, num_in_actions, num_out_actions, seed, disabled=None)¶ Initializes a new strategy object randomly.
-
mutate
(logger)¶ Top level mutation function for a strategy. Simply mutates the out and in trees.
-
mutate_dir
(trees, direction, logger)¶ Mutates a list of trees. Requires the direction the tree operates on (in or out).
-
pretty_print
()¶
-
pretty_str_forest
(forest)¶ Returns a string representation of a given forest (inbound or outbound)
-
run_on_packet
(packet, logger, direction)¶ Runs the strategy on a given packet given the forest direction.
-
str_forest
(forest)¶ Returns a string representation of a given forest (inbound or outbound)
-
-
strategy.
do_mate
(forest1, forest2)¶ Performs mating between two given forests (lists of trees). With 80% probability, a random tree from each forest are mated, otherwise, a random tree is swapped between them.
-
strategy.
mate
(ind1, ind2, indpb)¶ Executes a uniform crossover that modify in place the two individuals. The attributes are swapped according to the indpb probability.
-
strategy.
swap_one
(forest1, forest2)¶ Swaps a random tree from forest1 and forest2.
It picks a random element within forest1 and a random element within forest2, chooses a random index within each forest, and inserts the random element
geneva.actions.tamper¶
TamperAction
One of the four packet-level primitives supported by Geneva. Responsible for any packet-level modifications (particularly header modifications). It supports the following primitives: - no operation: it returns the packet given - replace: it changes a packet field to a fixed value - corrupt: it changes a packet field to a randomly generated value each time it is run - add: adds a given value to the value in a field - compress: performs DNS decompression on the packet (if applicable)
-
class
tamper.
TamperAction
(environment_id=None, field=None, tamper_type=None, tamper_value=None, tamper_proto='TCP')¶ Bases:
actions.action.Action
Defines the TamperAction for Geneva.
-
__init__
(environment_id=None, field=None, tamper_type=None, tamper_value=None, tamper_proto='TCP')¶ Creates a tamper object.
Parameters: - environment_id (str, optional) – environment_id of a previously run strategy, used to find packet captures
- field (str, optional) – field that the object will tamper. If not set, all the parameters are chosen randomly
- tamper_type (str, optional) – primitive this tamper will use (“corrupt”)
- tamper_value (str, optional) – value to tamper to
- tamper_proto (str, optional) – protocol we are tampering
-
mutate
(environment_id=None)¶ Mutate can switch between the tamper type, field.
-
parse
(string, logger)¶ Parse out a given string representation of this action and initialize this action to those parameters.
Note that the given logger is a DIFFERENT logger than the logger passed to the other functions, and they cannot be used interchangeably. This logger is attached to the main GA driver, and is run outside the evaluator. When the action is actually run, it’s run within the evaluator, which by necessity must pass in a different logger.
-
run
(packet, logger)¶ The tamper action runs its tamper procedure on the given packet, and returns the edited packet down the left branch.
Nothing is returned to the right branch.
-
tamper
(packet, logger)¶ Edits a given packet according to the action settings.
-
frequency
= 5¶
-
geneva.actions.trace¶
-
class
trace.
TraceAction
(start_ttl=1, end_ttl=64, environment_id=None)¶ Bases:
actions.action.Action
The Trace Action is used to TTL probe/traceroute a censor. When the action fires, it sends the captured packet with increasing ttls within a certain range
TraceAction is an experimental action that is never used in actual evolution
-
__init__
(start_ttl=1, end_ttl=64, environment_id=None)¶ Initializes the trace action.
Parameters: - start_ttl (int) – Starting TTL to use
- end_ttl (int) – TTL to end with
- environment_id (str, optional) – Environment ID associated with the strategy we are a part of
-
parse
(string, logger)¶ Parses a string representation for this object.
-
run
(packet, logger)¶ The trace action sends the captured packet repeatedly with increasing ttl probes defined between the range of start_ttl to end_ttl. This is an experimental action, and is not used for training.
-
frequency
= 0¶
-
geneva.actions.tree¶
Defines an action tree. Action trees are comprised of a trigger and a tree of actions.
-
exception
tree.
ActionTreeParseError
¶ Bases:
Exception
Exception thrown when an action tree is malformed or cannot be parsed.
-
class
tree.
ActionTree
(direction, trigger=None)¶ Bases:
object
Defines an ActionTree for the Geneva system.
-
__init__
(direction, trigger=None)¶ Creates this action tree.
Parameters: - direction (str) – Direction this tree is facing (“out”, “in”)
- trigger (
actions.trigger.Trigger
) – Trigger to use with this tree
-
add_action
(new_action)¶ Adds an action to this action tree.
-
check
(packet, logger)¶ Checks if this action tree should run on this packet.
-
choose_one
()¶ Picks a random element in the tree.
-
contains
(action)¶ Checks if an action is contained in the tree.
-
count_leaves
()¶ Counts the number of leaves.
-
do_parse
(node, string, logger)¶ Handles the preorder recursive parsing.
-
do_run
(node, packet, logger)¶ Handles recursively running a packet down the tree.
-
get_parent
(node)¶ Returns the parent of the given node and direction of the child.
-
get_rand_action
(direction, request=None, allow_terminal=True, disabled=None)¶ Retrieves and initializes a random action that can run in the given direction.
-
get_slots
()¶ Returns the number of locations a new action could be added.
-
initialize
(num_actions, environment_id, allow_terminal=True, disabled=None)¶ Sets up this action tree with a given number of random actions. Note that the returned action trees may have less actions than num_actions if terminal actions are used.
-
mate
(other_tree)¶ Mates this tree with another tree.
-
mutate
()¶ Mutates this action tree with respect to a given direction.
-
parse
(string, logger)¶ Parses a string representation of an action tree into this object.
-
preorder
(node)¶ Yields a preorder traversal of the tree.
-
pretty_print
(visual=False)¶ Pretty prints the tree.
-
pretty_print_help
(root, visual=False, parent=None)¶ - Pretty prints the tree.
- root is the highest-level node you wish to start printing
- [visual] controls whether a png should be created, by default, this is false.
- [parent] is an optional parameter specifying the parent of a given node, should only be used by this function.
Returns the root with its children as an anytree node.
-
remove_action
(action)¶ Removes a given action from the tree.
-
remove_one
()¶ Removes a random leaf from the tree.
-
run
(packet, logger)¶ Runs a packet through the action tree.
-
string_repr
(node)¶ Yields a preorder traversal of the tree to create a string representation.
-
swap
(my_donation, other_tree, other_donation)¶ Swaps a node in this tree with a node in another tree.
-
geneva.actions.trigger¶
-
class
trigger.
Trigger
(trigger_type, trigger_field, trigger_proto, trigger_value=0, environment_id=None, gas=None)¶ Bases:
object
-
__init__
(trigger_type, trigger_field, trigger_proto, trigger_value=0, environment_id=None, gas=None)¶ - Params:
- trigger_type: the type of trigger. Only “field” (matching based on a field value) is currently supported.
- trigger_field: the field the trigger should check in a packet to trigger on
- trigger_proto: the protocol the trigger should look in to retrieve the trigger field
- trigger_value: the value in the trigger_field that, upon a match, will cause the trigger to fire
- environment_id: environment_id the current trigger is running under. Used to retrieve previously saved packets
- gas: how many times this trigger can fire before it stops triggering. gas=None disables gas (unlimited triggers.)
- has_wildcard: represents if the trigger will match a specific value, or any value containing trigger_value
-
add_gas
(gas)¶ Adds gas to this trigger, gas is an integer
-
disable_gas
()¶ Disables the use of gas.
-
enable_gas
()¶ Sets gas to 0
-
static
get_gas
()¶ Returns a random value for gas for this trigger.
-
static
get_rand_trigger
(environment_id, real_packet_probability)¶ Creates a random trigger.
-
is_applicable
(packet, logger)¶ Checks if this trigger is applicable to a given packet.
-
mutate
(environment_id, real_packet_probability=0.5)¶ Mutates this trigger object by picking a new protocol, field, and value.
-
static
parse
(string)¶ Given a string representation of a trigger, define a new Trigger represented by this string.
-
set_gas
(gas)¶ Sets the gas to the specified value
-
geneva.actions.utils¶
-
exception
utils.
SkipStrategyException
(msg, fitness)¶ Bases:
Exception
Raised to signal that this strategy evaluation should be cut off.
-
__init__
(msg, fitness)¶ Creates the exception with the fitness to pass back
-
-
class
utils.
CustomAdapter
(logger, extras)¶ Bases:
logging.LoggerAdapter
Used for demo mode, to change sensitive IP addresses where necessary. Can be used (mostly) like a regular logger.
-
__init__
(logger, extras)¶ Initialize the adapter with a logger and a dict-like object which provides contextual information. This constructor signature allows easy stacking of LoggerAdapters, if so desired.
You can effectively pass keyword arguments as shown in the following example:
adapter = LoggerAdapter(someLogger, dict(p1=v1, p2=”v2”))
-
critical
(msg, *args, **kwargs)¶ Print a critical message, uses logger.critical.
-
debug
(msg, *args, **kwargs)¶ Print a debug message, uses logger.debug.
-
error
(msg, *args, **kwargs)¶ Print an error message, uses logger.error.
-
get_ip
(ip)¶ Lookup the assigned random IP for a given real IP. If no random IP exists, a new one is created and a message is logged indicating it.
-
info
(msg, *args, **kwargs)¶ Print an info message, uses logger.info.
-
process
(msg, args, kwargs)¶ Modify the log message to replace any instance of an IP in msg or args with its assigned random IP.
-
warning
(msg, *args, **kwargs)¶ Print a warning message, uses logger.warning.
-
regex
= re.compile('\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}')¶
-
-
class
utils.
Logger
(log_dir, logger_name, log_name, environment_id, log_level='DEBUG')¶ Bases:
object
Logging class context manager, as a thin wrapper around the logging class to help handle closing open file descriptors.
-
__init__
(log_dir, logger_name, log_name, environment_id, log_level='DEBUG')¶ Initialize self. See help(type(self)) for accurate signature.
-
-
utils.
build_command
(args)¶ Given a dictionary of arguments, build it back into a command line string.
-
utils.
close_logger
(logger)¶ Closes open file handles for a given logger.
-
utils.
get_console_log_level
()¶ returns log level of console handler
-
utils.
get_from_fuzzed_or_real_packet
(environment_id, real_packet_probability, enable_options=True, enable_load=True)¶ Retrieves a protocol, field, and value from a fuzzed or real packet, depending on the given probability and if given packets is not None.
-
utils.
get_id
()¶ Returns a random ID
-
utils.
get_interface
()¶ Chooses an interface on the machine to use for socket testing.
-
utils.
get_logger
(basepath, log_dir, logger_name, log_name, environment_id, log_level='DEBUG', file_log_level='DEBUG', demo_mode=False)¶ Configures and returns a logger.
-
utils.
get_plugins
()¶ Iterates over this current directory to retrieve plugins.
-
utils.
get_worker
(name, logger)¶ Returns information dictionary about a worker given its name.
-
utils.
import_plugin
(plugin, side)¶ Imports given plugin. :param - plugin: plugin to import (e.g. “http”) :param - side: which side of the connection should be imported (“client” or “server”)
-
utils.
parse
(requested_trees, logger)¶ Parses a string representation of a solution into its object form.
-
utils.
punish_complexity
(fitness, logger, ind)¶ Reduces fitness based on number of actions - optimizes for simplicity.
-
utils.
punish_fitness
(fitness, logger, eng)¶ Adjusts fitness based on additional optimizer functions.
-
utils.
punish_unused
(fitness, logger, ind)¶ Punishes strategy for each action that was not run.
-
utils.
read_packets
(environment_id)¶ Reads the pcap file associated with the last evaluation of this strategy. Returns a list of Geneva Packet objects.
-
utils.
setup_dirs
(output_dir)¶ Sets up Geneva folder structure.
-
utils.
string_to_protocol
(protocol)¶ Converts string representations of scapy protocol objects to their actual objects. For example, “TCP” to the scapy TCP object.
-
utils.
write_fitness
(fitness, output_path, eid)¶ Writes fitness to disk.
geneva.plugins.dns¶
Client
Run by the evaluator, tries to make a GET request to a given server
-
class
plugins.dns.client.
DNSClient
(args)¶ Bases:
plugins.plugin_client.ClientPlugin
Defines the DNS client.
-
__init__
(args)¶ Initializes the DNS client.
-
dns_test
(to_lookup, dns_server, output_dir, environment_id, logger, timeout=3, use_tcp=False)¶ Makes a DNS query to a given censored domain.
-
static
get_args
(command)¶ Defines required args for this plugin
-
run
(args, logger, engine=None)¶ Try to make a forbidden DNS query.
-
name
= 'dns'¶
-
Code influenced from: - https://github.com/emileaben/scapy-dns-ninja/blob/master/dns-ninja-server.py - https://thepacketgeek.com/scapy-p-09-scapy-and-dns/
-
class
plugins.dns.server.
DNSServer
(args, logger=None)¶ Bases:
plugins.plugin_server.ServerPlugin
Purpose: Handle incoming DNS queries and respond with resource records defined in a zone configuration file (if exists for that domain) or respond with the answer given by a DNS resolver
Features: - Loads zone configuration files (–zones-dir) - Forwards DNS requests to a DNS resolver for domains that it does not know the answer to (–dns-resolver) - DNS forwarding can be disabled with (–no-forwarding) - Can act as the authority server for all DNS responses
Zones: - Support for A, MX, NS, TXT and CNAME - Other records may be automatically supported through the default action (no special case) - Only the first string per TXT record will be retrieved to avoid duplicated quotes
Logging: - Logs are created for each run and saved in the directory specified (–log-dir) - Logs can be disabled with (–no-log)
Python Test: tests/test_dns_server.py
-
__init__
(args, logger=None)¶ Initializes the DNS Server.
-
build_dns_response
(packet)¶ Build the DNS response packet using one of the following methods: 1) Load the resource record(s) from a manually configured DNS zone file (if exists) OTHERWISE, if enabled: 2) Send a DNS query to a DNS resolver and copy the DNS resource records
-
build_response_packet
(listener_packet, raw_socket=True)¶ Build the DNS response packet - If raw_socket is enabled include the Network and Transport Layer
-
forward_dns_query
(packet: scapy.layers.inet.IP)¶ Forwards the DNS query to a real DNS resolver and returns the DNS response
-
get_args
()¶ Sets up argparse and collects arguments.
-
get_dns_query_info
(packet: scapy.layers.inet.IP)¶ Extract information from the DNS query
-
get_resource_records
(domain_name, question_name, question_type)¶ Gets the appropriate resource record loaded earlier from the zone file
-
load_zones
()¶ Loads the DNS Zones in the zones directory specified (zones_dir)
-
process_packet_netfilter
(listener_packet)¶ Callback function for each packet received by netfilter
-
run
(args, logger)¶ Starts the DNS Service
-
stop
()¶ Stops this server.
-
name
= 'dns'¶
-
netfilter_queue
= 'netfilterqueue'¶
-
socket_TCP
= 'socket_TCP'¶
-
socket_UDP
= 'socket_UDP'¶
-
-
plugins.dns.server.
main
(args)¶ Run the DNS server
DNS Plugin driver
Overrides the default evaluator plugin handling so we can check for legit IPs for UDP tests.
-
class
plugins.dns.plugin.
DNSPluginRunner
(args)¶ Bases:
plugins.plugin.Plugin
Defines the DNS plugin runner.
-
__init__
(args)¶ Marks this plugin as enabled
-
check_legit_ip
(ip, logger, domain='facebook')¶ Helper method to check if the given IP address is serving web content.
-
static
get_args
(command)¶ Defines required global args for this plugin
-
start
(args, evaluator, environment, ind, logger)¶ Runs the plugins
-
name
= 'dns'¶
-
geneva.plugins.echo¶
Client
Run by the evaluator, echo’s data back and forth to the server
-
class
plugins.echo.client.
EchoClient
(args)¶ Bases:
plugins.plugin_client.ClientPlugin
Defines the Echo client.
-
__init__
(args)¶ Initializes the echo client.
-
static
get_args
(command)¶ Defines required args for this plugin
-
run
(args, logger, engine=None)¶ Try to make a forbidden GET request to the server.
-
name
= 'echo'¶
-
-
class
plugins.echo.server.
EchoServer
(args)¶ Bases:
plugins.plugin_server.ServerPlugin
Defines the Echo client.
-
__init__
(args)¶ Initializes the Echo client.
-
static
get_args
(command)¶ Defines arguments for this plugin
-
get_request
(control_socket)¶ Get a request from the socket.
-
run
(args, logger)¶ Initializes the Echo server.
-
stop
()¶ Stops this server.
-
name
= 'echo'¶
-
geneva.plugins.http¶
Run by the evaluator, tries to make a GET request to a given server
-
class
plugins.http.client.
HTTPClient
(args)¶ Bases:
plugins.plugin_client.ClientPlugin
Defines the HTTP client.
-
__init__
(args)¶ Initializes the HTTP client.
-
static
get_args
(command)¶ Defines required args for this plugin
-
run
(args, logger, engine=None)¶ Try to make a forbidden GET request to the server.
-
name
= 'http'¶
-
-
class
plugins.http.server.
HTTPServer
(args)¶ Bases:
plugins.plugin_server.ServerPlugin
Defines the HTTP client.
-
__init__
(args)¶ Initializes the HTTP client.
-
static
get_args
(command)¶ Defines arguments for this plugin
-
run
(args, logger)¶ Initializes the HTTP server.
-
stop
()¶ Stops this server.
-
name
= 'http'¶
-
HTTP Plugin driver
Overrides the default evaluator plugin handling so we can negotiate a clear port and track jailed sites to avoid residual censorship.
-
class
plugins.http.plugin.
HTTPPluginRunner
(args)¶ Bases:
plugins.plugin.Plugin
Defines the HTTP plugin runner.
-
__init__
(args)¶ Marks this plugin as enabled
-
static
get_args
(command)¶ Defines required global args for all plugins
-
negotiate_clear_port
(args, evaluator, environment, logger)¶ Since residual censorship might be affecting our IP/port combo that was randomly chosen, this method is to find a port on which no residual censorship is present. This is done simply by picking a port, running the server, running curl to confirm it’s accessible, and then returning that port.
-
start
(args, evaluator, environment, ind, logger)¶ Runs the plugins
-
name
= 'http'¶
-
-
class
plugins.http.plugin.
TestServer
(requested_site, evaluator, environment, logger)¶ Bases:
object
Context manager to retrieve a test server from the external server pool.
-
__init__
(requested_site, evaluator, environment, logger)¶ Initialize self. See help(type(self)) for accurate signature.
-
-
plugins.http.plugin.
check_censorship
(site, evaluator, environment, logger)¶ Make a request to the given site to test if it is censored. Used to test a site for residual censorship before using it.
geneva.plugins.plugin¶
geneva.plugins.plugin_client¶
-
class
plugin_client.
ClientPlugin
¶ Bases:
plugins.plugin.Plugin
Defines superclass for each application plugin.
-
__init__
()¶ Initialize self. See help(type(self)) for accurate signature.
-
static
get_args
(command)¶ Defines required global args for all plugins
-
start
(args, logger)¶ Runs this plugin.
-
wait_for_censor
(serverip, port, environment_id, log_dir)¶ Sends control packets to the censor for up to 20 seconds until it’s ready.
-
-
plugin_client.
main
(command)¶ Used to invoke the plugin client from the command line.
geneva.plugins.plugin_server¶
-
class
plugin_server.
ServerPlugin
¶ Bases:
plugins.plugin.Plugin
Defines superclass for each application plugin.
-
__init__
()¶ Initialize self. See help(type(self)) for accurate signature.
-
static
get_args
(command)¶ Defines required global args for all plugins
-
punish_fitness
(fitness, logger)¶ Punish fitness.
-
start
(args, logger)¶ Runs this plugin.
-
start_thread
(args, logger)¶ Calls the given run function, designed to be run in a separate process.
-
stop
()¶ Terminates the given process.
-
wait_for_server
(args, logger)¶ Waits for server to startup - returns when the server port is bound to by the server.
-
wait_for_shutdown
(args, logger)¶ Checks for the <eid>.server_shutdown flag to shutdown this server.
-
write_startup_file
(args, logger)¶ Writes a flag file to disk to signal to the evaluator it has started up
-
-
plugin_server.
main
(command)¶ Used to invoke the server plugin from the command line.
geneva.plugins.sni¶
Runs an SNI request, confirms the connection was not torn down
-
class
plugins.sni.client.
SNIClient
(args)¶ Bases:
plugins.plugin_client.ClientPlugin
Defines the SNI client.
-
__init__
(args)¶ Initializes the sni client.
-
static
get_args
(command)¶ Defines required args for this plugin
-
run
(args, logger, engine=None)¶ Try to make a forbidden SNI request to the server.
-
name
= 'sni'¶
-