Browse Source

Update/Correct python-elmo server

master
Thibauld Feneuil 4 years ago
parent
commit
136f6246cb
8 changed files with 207 additions and 172 deletions
  1. 34
    34
      README.md
  2. 5
    31
      elmo/__main__.py
  3. 110
    0
      elmo/executor.py
  4. 3
    1
      elmo/manage.py
  5. 14
    7
      elmo/project_base.py
  6. 0
    87
      elmo/server/executorthread.py
  7. 20
    12
      elmo/server/servicethread.py
  8. 21
    0
      test.py

+ 34
- 34
README.md View File

@@ -1,6 +1,6 @@
# Python ELMO
# Python-ELMO
_Python ELMO_ is a Python library which proposes an encapsulation of the project _ELMO_.
_Python-ELMO_ is a Python library which proposes an encapsulation of the project _ELMO_.
[MOW17] **Towards Practical Tools for Side
Channel Aware Software Engineering : ’Grey Box’ Modelling for Instruction Leakages**
@@ -9,11 +9,13 @@ https://www.usenix.org/conference/usenixsecurity17/technical-sessions/presentati
**ELMO GitHub**: https://github.com/sca-research/ELMO
**Python-ELMO Contributors**: Thibauld Feneuil
## Requirements
To use _Python ELMO_, you need at least Python3.5 and ```numpy```.
To use _Python-ELMO_, you need at least Python3.5 and ```numpy```.
The library will install and compile ELMO. So, you need the GCC compiler collection and the command/utility 'make' (for more details, see the documentation of ELMO). On Ubuntu/Debian,
The library will install and compile ELMO. So, you need the GCC compiler collection and the command/utility ```make``` (for more details, see the documentation of ELMO). On Ubuntu/Debian,
```bash
sudo apt install build-essential
@@ -23,13 +25,13 @@ To use ELMO on a leaking binary program, you need to compile the C implementatio
## Installation
First, download _Python ELMO_.
First, download _Python-ELMO_.
```bash
git clone https://git.aprilas.fr/tfeneuil/python-elmo
```
And then, install ELMO thanks to the script of installation.
And then, install ELMO thanks to the installation script. It will use Internet to download the [ELMO project](https://github.com/sca-research/ELMO).
```bash
python setup.py install
@@ -47,7 +49,7 @@ What is a _simulation project_ ? It is a project to simulate the traces of _one_
To start a new project, you can use the following function.
```python
from elmo.manage import create_simulation
from elmo import create_simulation
create_simulation(
'dilithium', # The (relative) path of the project
'DilithiumSimulation' # The classname of the simulation
@@ -62,32 +64,29 @@ This function will create a repository _dilithium_ with all the complete squelet
### List all the available simulation
```python
from elmo.manage import search_simulations
from elmo import search_simulations
search_simulations('.')
```
```python
```text
{'DilithiumSimulation': <class 'DilithiumSimulation'>,
'KyberNTTSimulation': <class 'KyberNTTSimulation'>}
```
_Python ELMO_ offers a example project to you in the repository _projects/Examples_ of the module. This example is a project to generate traces of the execution of the NTT implemented in the cryptosystem [Kyber](https://pq-crystals.org/kyber/).
_Python-ELMO_ offers a example project to you in the repository _projects/Examples_ of the module. This example is a project to generate traces of the execution of the NTT implemented in the cryptosystem [Kyber](https://pq-crystals.org/kyber/).
### Use a simulation project
Warning! Before using it, you have to compile your project thanks to the provided Makefile.
```python
from elmo.manage import get_simulation
KyberNTTSimulation = get_simulation_via_classname('KyberNTTSimulation')
from elmo import get_simulation
KyberNTTSimulation = get_simulation('KyberNTTSimulation')
import numpy as np
Kyber512 = {'k': 2, 'n': 256}
challenges = [
np.ones((Kyber512['k'], Kyber512['n']), dtype=int),
]
simulation = KyberNTTSimulation()
challenges = simulation.get_random_challenges(10)
simulation.set_challenges(challenges)
simulation = KyberNTTSimulation(challenges)
simulation.run() # Launch the simulation
traces = simulation.get_traces()
# And now, I can draw and analyse the traces
@@ -95,33 +94,30 @@ traces = simulation.get_traces()
### Use a simulation project thanks to a server
Sometimes, it is impossible to run the simulation thanks the simple method _run_ of the project class. Indeed, sometimes the Python script is executed in the environment where _Python ELMO_ cannot launch the ELMO tool. For example, it is the case where _Python ELMO_ is used in SageMath on Windows. On Windows, SageMath installation relies on the Cygwin POSIX emulation system and it can be a problem.
Sometimes, it is impossible to run the simulation thanks the simple method ```run``` of the project class. Indeed, sometimes the Python script is executed in the environment where _Python-ELMO_ cannot launch the ELMO tool. For example, it is the case where _Python-ELMO_ is used in SageMath on Windows. On Windows, SageMath installation relies on the Cygwin POSIX emulation system and it can be a problem.
To offer a solution, _Online ELMO_ can be used thanks to a link client-server. The idea is you must launch the script _run\_server.py_ which will listen (by default) at port 5000 in localhost.
To offer a solution, _Python-ELMO_ can be used thanks to a client-server link. The idea is you must launch the script ```run_server.py``` which will listen (by default) at port 5000 in localhost.
```bash
python -m elmo run-server
```
And after, you can manipulate the projects as described in the previous section by replacing _run_ to _run\_online_.
And after, you can manipulate the projects as described in the previous section by replacing ```run``` to ```run_online```.
```python
from elmo.manage import get_simulation
KyberNTTSimulation = get_simulation('KyberNTTSimulation')
import numpy as np
Kyber512 = {'k': 2, 'n': 256}
challenges = [
np.ones((Kyber512['k'], Kyber512['n']), dtype=int),
]
simulation = KyberNTTSimulation()
challenges = simulation.get_random_challenges(10)
simulation.set_challenges(challenges)
simulation = KyberNTTSimulation(challenges)
simulation.run_online() # Launch the simulation THANKS TO A SERVER
traces = simulation.get_traces()
# And now, I can draw and analyse the traces
```
Warning! Using the _run\_online_ method doesn't exempt you from compiling the project with the provided Makefile.
Warning! Using the ```run_online``` method doesn't exempt you from compiling the project with the provided Makefile.
### Use the ELMO Engine
@@ -131,12 +127,12 @@ The engine exploits the model of ELMO to directly give the power consumption of
- the type of the next assembler instruction
The type of the instructions are:
- "_**EOR**_" for ADD(1-4), AND, CMP, CPY, EOR, MOV, ORR, ROR, SUB;
- "_**LSL**_" for LSL(2), LSR(2);
- "_**STR**_" for STR, STRB, STRH;
- "_**LDR**_" for LDR, LDRB, LDRH;
- "_**MUL**_" for MUL;
- "_**OTHER**_" for the other instructions.
- ```EOR``` for ADD(1-4), AND, CMP, CPY, EOR, MOV, ORR, ROR, SUB;
- ```LSL``` for LSL(2), LSR(2);
- ```STR``` for STR, STRB, STRH;
- ```LDR``` for LDR, LDRB, LDRH;
- ```MUL``` for MUL;
- ```OTHER``` for the other instructions.
```python
from elmo.engine import ELMOEngine, Instr
@@ -152,6 +148,10 @@ power = engine.power # Numpy 1D array with an entry for each previous point
engine.reset_points() # Reset the engine to study other points
```
## Limitations
Since the [ELMO project](https://github.com/sca-research/ELMO) takes its inputs and outputs from files, _Python-ELMO_ **can not** manage simultaneous runs.
## Licences
[MIT](LICENCE.txt)

+ 5
- 31
elmo/__main__.py View File

@@ -51,39 +51,13 @@ if command == 'create-simulation':
exit()

if command == 'run-server':
from .server.servicethread import ListeningThread
from .executorthread import ExecutorThread

def do_main_program():
global thread, stop
thread = ListeningThread('localhost', 5000, ExecutorThread)
thread.start()

def program_cleanup(signum, frame):
global thread, stop
thread.stop()
stop = True

thread = None
stop = False

# Execute
print("Executing...")
do_main_program()
print("Done ! And now, listening...")

import signal
signal.signal(signal.SIGINT, program_cleanup)
signal.signal(signal.SIGTERM, program_cleanup)
from .executor import launch_executor
host = sys.argv[2] if len(sys.argv) >= 3 else 'localhost'
port = int(sys.argv[3]) if len(sys.argv) >= 4 else 5000

# Wait
import time
while not stop:
time.sleep(1)
launch_executor(host, port)
exit()


print(Color.FAIL + 'Unknown Command.' + Color.ENDC)
exit()

+ 110
- 0
elmo/executor.py View File

@@ -0,0 +1,110 @@
import shutil
import os, re
import sys
from .server.servicethread import OneShotServiceThread
from .config import MODULE_PATH, ELMO_TOOL_REPOSITORY, ELMO_INPUT_FILE_NAME
from .project_base import SimulationProject
from .manage import execute_simulation
from .utils import Color
class Executor(OneShotServiceThread):
def execute(self):
data = self.protocol.get_data()
self.protocol.please_assert(data)
elmo_path = os.path.join(MODULE_PATH, ELMO_TOOL_REPOSITORY)
# Set the input of ELMO
self.protocol.please_assert('input' in data)
with open(os.path.join(elmo_path, ELMO_INPUT_FILE_NAME), 'w') as _input_file:
_input_file.write(data['input'])
self.protocol.send_ack()
# Get the binary
binary_content = self.protocol.get_file()
binary_path = os.path.join(elmo_path, 'project.bin')
with open(binary_path, 'wb') as _binary_file:
_binary_file.write(binary_content)
self.protocol.send_ack()
### Generate the traces by launching ELMO
print(Color.OKGREEN + ' - Simulation accepted...' + Color.ENDC)
simulation = SimulationProject()
simulation.get_binary_path = lambda: os.path.abspath(binary_path)
data = execute_simulation(simulation)
if data['error']:
print(Color.FAIL + ' - Simulation failed.' + Color.ENDC)
self.protocol.send_data(results)
self.protocol.close()
return
print(Color.OKGREEN + ' - Simulation finished: {} traces, {} instructions'.format(
data['nb_traces'],
data['nb_instructions'],
) + Color.ENDC)
output_path = os.path.join(elmo_path, 'output')
### Get the trace
data['results'] = []
for i in range(data['nb_traces']):
filename = os.path.join(output_path, 'traces', 'trace%05d.trc' % (i+1))
with open(filename, 'r') as _file:
data['results'].append(
list(map(float, _file.readlines()))
)
### Get asmtrace and printed data
asmtrace = None
if ('asmtrace' not in data) or data['asmtrace']:
with open(os.path.join(output_path, 'asmoutput', 'asmtrace00001.txt'), 'r') as _file:
data['asmtrace'] = _file.read()
printed_data = None
if ('printdata' not in data) or data['printdata']:
with open(os.path.join(output_path, 'printdata.txt'), 'r') as _file:
data['printed_data'] = list(map(lambda x: int(x, 16), _file.readlines()))
### Send results
print(Color.OKCYAN + ' - Sending results...' + Color.ENDC, end='')
sys.stdout.flush()
self.protocol.send_data(data)
print(Color.OKGREEN + ' Sent!' + Color.ENDC)
self.protocol.close()
def launch_executor(host, port, waiting_function=True):
from .server.servicethread import ListeningThread
def do_main_program():
thread = ListeningThread(host, port, Executor, debug=True)
thread.start()
return thread
def program_cleanup(signum, frame):
thread.stop()
thread = do_main_program()
import signal
signal.signal(signal.SIGINT, program_cleanup)
signal.signal(signal.SIGTERM, program_cleanup)
if waiting_function is True:
import time
while thread.is_running():
time.sleep(1)
return
return_value = None
if waiting_function:
return_value = waiting_function()
if thread.is_running():
program_cleanup(None, None)
return return_value

+ 3
- 1
elmo/manage.py View File

@@ -194,7 +194,9 @@ def execute_simulation(project):
if not isinstance(project, SimulationProject):
raise TypeError('The project is not an instance of \'SimulationProject\' class.')
leaking_binary_path = pjoin(project.get_project_directory(), project.get_binary_path())
leaking_binary_path = project.get_binary_path()
if not os.path.isabs(leaking_binary_path):
leaking_binary_path = pjoin(project.get_project_directory(), project.get_binary_path())
if not os.path.isfile(leaking_binary_path):
raise BinaryNotFoundError('Binary not found. Did you compile your project?')

+ 14
- 7
elmo/project_base.py View File

@@ -129,21 +129,28 @@ class SimulationProject:
'input': input.get_string(),
})
if not SocketTool.get_ack(s):
raise RuntimeError("NACK received: The request has been refused !")
else:
SocketTool.send_file(s, '{}/{}'.format(self.get_project_directory(), self.get_binary()))
data = SocketTool.get_data(s)
if data['error']:
raise Exception("The simulation returned an error: {}".format(data['error']))
raise RuntimeError("NACK received: The request has been refused!")
SocketTool.send_file(s, '{}/{}'.format(self.get_project_directory(), self.get_binary_path()))
if not SocketTool.get_ack(s):
raise RuntimeError("NACK received: The binary file has been refused!")
data = SocketTool.get_data(s)
if data['error']:
raise Exception("The simulation returned an error: {}".format(data['error']))
s.close()
except IOError as err:
raise RuntimeError("The connection refused. Has the ELMO server been switch on ?") from err
raise RuntimeError("The connection refused. Has the ELMO server been switch on?") from err
self.is_executed = True
self.has_been_online = True
self._complete_asmtrace = data['asmtrace']
self._complete_results = data['results']
self._complete_printed_data = data['printed_data']
return { key: value
for key, value in data.items()
if key not in ['results', 'asmtrace', 'printed_data']
}
### Manipulate the ASM trace
def get_asmtrace_filename(self):

+ 0
- 87
elmo/server/executorthread.py View File

@@ -1,87 +0,0 @@
from .server.servicethread import OneShotServiceThread
import subprocess
import shutil
import os, re
class ExecutorThread(OneShotServiceThread):
def __init__(self, ip, port, clientsocket, **kwargs):
super().__init__(ip, port, clientsocket)
def execute(self):
data = self.protocol.get_data()
self.protocol.please_assert(data)
# Set the input of ELMO
self.protocol.please_assert('input' in data)
with open('elmo/input.txt', 'w') as _input_file:
_input_file.write(data['input'])
self.protocol.send_ack()
# Get the binary
binary_content = self.protocol.get_file()
with open('elmo/project.bin', 'wb') as _binary_file:
_binary_file.write(binary_content)
### Generate the traces by launching ELMO
command = './elmo ./project.bin'
cwd = './elmo/'
process = subprocess.Popen(
command, shell=True, cwd=cwd,
executable='/bin/bash',
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
output, error = process.communicate()
output = output.decode('latin-1') if output else None
error = error.decode('latin-1') if error else None
if error:
self.protocol.send_data({
'output': output,
'error': error,
})
self.protocol.close()
return
### Get traces
nb_traces = output.count('TRACE NO')
trace_filenames = []
for filename in os.listdir('elmo/output/traces/'):
if len(trace_filenames) < nb_traces:
if re.search(r'^trace\d+\.trc$', filename):
trace_filenames.append('elmo/output/traces/{}'.format(filename))
else:
break
assert len(trace_filenames) == nb_traces
results = trace_filenames
for i in range(len(results)):
with open(results[i], 'r') as _file:
results[i] = list(map(float, _file.readlines()))
### Get asmtrace and printed data
asmtrace = None
if not ('asmtrace' in data and not data['asmtrace']):
with open('elmo/output/asmoutput/asmtrace00001.txt', 'r') as _file:
asmtrace = ''.join(_file.readlines())
printed_data = None
if not ('printdata' in data and not data['printdata']):
with open('elmo/output/printdata.txt', 'r') as _file:
printed_data = list(map(lambda x: int(x, 16), _file.readlines()))
### Send results
self.protocol.send_data({
'output': output,
'error': error,
'nb_traces': nb_traces,
'results': results,
'asmtrace': asmtrace,
'printed_data': printed_data,
})
self.protocol.close()

+ 20
- 12
elmo/server/servicethread.py View File

@@ -1,5 +1,5 @@
import threading
from .server.protocol import Protocol, ClosureException
from .protocol import Protocol, ClosureException
import socket
@@ -44,12 +44,13 @@ class PermanentServiceThread(ServiceThread):
class ListeningThread(PermanentServiceThread):
def __init__(self, host, port, threadclass, **kwargs):
def __init__(self, host, port, threadclass, debug=False, **kwargs):
super().__init__()
self.hostname = host
self.port = port
self.threadclass = threadclass
self.kwargs = kwargs
self.debug = debug
def execute(self):
self.tcpsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -58,24 +59,31 @@ class ListeningThread(PermanentServiceThread):
# self.tcpsock.setsockopt(socket.SOL_SOCKET, socket.SO_ATTACH_REUSEPORT_CBPF, 1)
self.tcpsock.bind((self.hostname, self.port))
self.tcpsock.listen(5)
print('[port][%s] Listening' % self.port)
if self.debug:
print('[port][%s] Listening' % self.port)
while self.is_running():
try:
(clientsocket, (ip, port)) = self.tcpsock.accept()
print('[port][{}] Accepted: {} <=> {}'.format(
self.port,
clientsocket.getsockname(),
clientsocket.getpeername(),
))
newthread = self.threadclass(ip, port, clientsocket, **self.kwargs)
newthread.start()
if self.is_running():
if self.debug:
print('[port][{}] Accepted: {} <=> {}'.format(
self.port,
clientsocket.getsockname(),
clientsocket.getpeername(),
))
newthread = self.threadclass(ip, port, clientsocket, **self.kwargs)
newthread.start()
else:
break
except socket.timeout:
pass
print('[port][%s] Stop listening' % self.port)
def stop(self):
super().stop()
clientsocker = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
clientsocker.connect( (self.hostname, self.port) )
clientsocker.connect((self.hostname, self.port))
self.tcpsock.close()
print('[port][%s] Stop listening' % self.port)

+ 21
- 0
test.py View File

@@ -79,5 +79,26 @@ assert res['nb_instructions']
print_success(' - Test 3 "Use A Real Simulation": Success!')

#########################################################
# TEST 4 : USE ELMO BY RUNNING ONLINE #
#########################################################

def test_online_server():
from elmo import get_simulation
KyberNTTSimulation = get_simulation('KyberNTTSimulation')
simulation = KyberNTTSimulation()
simulation.set_challenges(simulation.get_random_challenges(10))
return simulation.run_online()

from elmo.executor import launch_executor
# Launch the server and realize the test
res = launch_executor('localhost', 5000, waiting_function=test_online_server)

assert not res['error']
assert res['nb_traces'] == 10
assert res['nb_instructions']

print_success(' - Test 4 "Use ELMO By Running Online": Success!')

#########################################################

print_success('All seems fine!')

Loading…
Cancel
Save