longchute

about

A Clueless Agent Generator for Python 3.2

07 Apr 2012

NOTE: I originally posted this on Snipplr.

This is an implementation of a clueless agent generator which creates self-decrypting clueless agents as described in "Environmental Key Generation towards Clueless Agents" by J. Riordan and B. Schneier.

It requires Python 3.2 and PyCrypto of a recent build (tested with 2.4 and higher).

To use, pass a python file (or other file) to be encrypted, followed by a series of "observations" on the command line. These observations are hashed to yield the encryption key. A signature is generated by hashing the key, and this signature will be expected to be present in the target environment. Pipe the resulting agent to a file or see the agent code directly on stdout. Additionally, there is an is_debug flag that can be specified (see the source) or tweaked in the resulting agent, to be more verbose.

To attempt decryption/execution of a clueless agent, simply run the generated python script (agent) and pass a set of observations on the command line. If the hash of the hash of the observations match the signature, the hash of the observations will be used as the decryption key. If the signature does not match, the agent will exit with no output.

The code previously directly exec()'d the resulting code, however, it simply outputs to stdout now. The resulting code would otherwise execute directly in-line, at that location in the program, which has many undesirable consequences. Piping it to a file and executing, piping it to a memory-backed temporary file and executing it, or placing the resulting code directly in memory afterward and then executing it, are all ways to run the code contained within. This makes it fundamentally little different from encrypting a file directly, except that the key is environmentally generated, perhaps by a daemon that feeds environmental observations on the command line to the agent.

Note, you can encrypt more than Python scripts, and agents can be made to contain themselves.

$ ./agent_generator.py plaincode.py 0 > cipheragent.py
$ ./agent_generator.py cipheragent.py some more observations > double_agent.py
$ ./double_agent.py wrong observations
--nothing here--
$ ./double_agent.py some more observations > cipheragent_2.py
--cipheragent_2.py now holds the same content as cipheragent.py--
$ ./cipheragent.py 0 > plaincode_2.py
--plaincode_2.py now holds the same content as plaincode.py--
$ ./plaincode_2.py
--should yield the same as--
$ ./plaincode.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

#   This code is public domain.
#
#   usage: generate_agent plaincode.py [observation...] > agent.py
#
#   This is an implementation of a clueless agent generator which creates self-decrypting clueless
#   agents as described in "Environmental Key Generation towards Clueless Agents" by  J. Riordan
#   and B. Schneier.
#
#   It requires Python 3.2 and PyCrypto of a recent build (tested with 2.4 and higher).
#   To use, pass a python file (or other file) to be encrypted, followed by a series of
#   "observations" on the command line. These observations are hashed to yield the encryption key.
#   A signature is generated by hashing the key, and this signature will be expected to be present
#   in the target environment. Pipe the resulting agent to a file or see the agent code directly
#   on stdout.  Additionally, there is an is_debug flag that can be specified (see the source) or
#   tweaked in the resulting agent, to be more verbose.
#
#   To attempt decryption/execution of a clueless agent, simply run the generated python script
#   (agent) and pass a set of observations on the command line. If the hash of the hash of the
#   observations match the signature, the hash of the observations will be used as the decryption
#   key. If the signature does not match, the agent will exit with no output.
#
#   The code previously directly exec()'d the resulting code, however, it simply outputs to stdout
#   now. The resulting code would otherwise execute directly in-line, at that location in the
#   program, which has many undesirable consequences. Piping it to a file and executing, piping
#   it to a memory-backed temporary file and executing it, or placing the resulting code directly
#   in memory afterward and then executing it, are all ways to run the code contained within. This
#   makes it fundamentally little different from encrypting a file directly, except that the key
#   is environmentally generated, perhaps by a daemon that feeds environmental observations on the
#   command line to the agent.
#
#   Note, that you can encrypt more than Python scripts, and agents can be made to contain themselves.
#
#   $ ./agent_generator.py plaincode.py 0 > cipheragent.py
#   $ ./agent_generator.py cipheragent.py some more observations > double_agent.py
#   $ ./double_agent.py wrong observations
#   --nothing here--
#   $ ./double_agent.py some more observations > cipheragent_2.py
#   --cipheragent_2.py now holds the same content as cipheragent.py--
#   $ ./cipheragent.py 0 > plaincode_2.py
#   --plaincode_2.py now holds the same content as plaincode.py--
#   $ ./plaincode_2.py
#   --should yield the same as--
#   $ ./plaincode.py

import functools
import base64

from Crypto.Hash import RIPEMD
from Crypto.Cipher import ARC4

agent_template = """#!/usr/bin/env python
# -*- coding: utf-8 -*-

#   usage:  python agent.py [observation...] > plaincode.py

is_debug = %s

import functools
import base64

from Crypto.Hash import RIPEMD
from Crypto.Cipher import ARC4

def execute_agent(observations, signature, ciphercode):
    key             = RIPEMD.new()
    signature_check = RIPEMD.new()

    key.update(functools.reduce(lambda a, b: a + b, observations))
    key_digest = key.hexdigest()
    signature_check.update(key_digest)
    signature_check_digest = bytes(signature_check.hexdigest(), encoding='utf-8')

    if is_debug:
        print("expecting: %%s" %% (signature,))
        print("sig check: %%s" %% (signature_check_digest,))

    if (signature_check_digest == signature):
        decrypter = ARC4.new(key_digest)
        plaincode = decrypter.decrypt(base64.b64decode(ciphercode))

        if is_debug:
            print("key digest: %%s" %% (key_digest,))
            print("decrypted: %%s" %% (plaincode,))

        print(plaincode)

if __name__ == "__main__":
    import sys
    observations    = sys.argv[1:]
    signature       = b'%s'
    ciphercode      = %s
    execute_agent(observations, signature, ciphercode)
"""

def ciphercode_from_string(plaincode, observations):
    key       = RIPEMD.new()
    signature = RIPEMD.new()

    key.update(functools.reduce(lambda a, b: a + b, observations))
    key_digest  = key.hexdigest()
    encrypter   = ARC4.new(key_digest)
    ciphercode  = base64.b64encode(encrypter.encrypt(plaincode))
    signature.update(key_digest)

    return (signature.hexdigest(), ciphercode)

def generate_agent(plaincode, observations, is_debug=False):
    return agent_template % tuple([is_debug] + list(ciphercode_from_string(plaincode, observations)))

if __name__ == "__main__":
    import sys

    with open(sys.argv[1], 'rb') as plaincode:
        print(generate_agent(plaincode.read(), sys.argv[2:], is_debug=False))