Skip to content

Commit

Permalink
📦 First release!
Browse files Browse the repository at this point in the history
  • Loading branch information
leonjza committed Oct 9, 2016
1 parent 44f88fa commit bc34f02
Show file tree
Hide file tree
Showing 17 changed files with 3,729 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,6 @@ ENV/

# Rope project settings
.ropeproject

# Ignore Jetbrains editors
.idea/
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ooktools/share/template.grc
93 changes: 92 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,93 @@
# ooktools
<h4 align="center">
ooktools
<br>
<img src="images/banner.png">
</h4>

**ooktools** aims to help with the reverse engineering of [on-off keying](https://en.wikipedia.org/wiki/On-off_keying) data sources such as wave files or raw frames captured using [RfCat](https://bitbucket.org/atlas0fd00m/rfcat).

---

### why?
I recently [played around a little with static key remotes](https://virtualenv.pypa.io/en/stable/), and wrote some code to help with the reverse engineering thereof.

### major features

- Binary string extraction from wave file recordings.
- Wave file cleanups to remove noise in On-off keying recordings.
- Graphing capabilities for wave files.
- General information extraction of wave files.
- Signal recording and playback using `json` definition files that can be shared.
- Plotting of data from the previously mentioned `json` recordings.
- Signal searching for On-off keying type data.
- Sending signals in both binary, complete PWM formatted or hex strings using an RfCat dongle.
- Gnuradio `.grc` template file generation.

### installation
You can install `ooktools` in two ways. Either from `pip` or from source. In case of a source installation, you may want to optionally consider installing it in a [virtualenv](https://virtualenv.pypa.io/en/stable/).

#### rfcat
In both installation cases, you need to install [RfCat](https://bitbucket.org/atlas0fd00m/rfcat). This too can be done in two ways. On Kali Linux, you can install it with a simple `apt` command:

```
$ apt install rfcat
```

Or, if you need to manually install it, download the latest [RfCat sources](https://bitbucket.org/atlas0fd00m/rfcat/downloads) and run the `setup.py` script:

```
$ wget -c https://bitbucket.org/atlas0fd00m/rfcat/downloads/rfcat_150225.tgz
$ tar xjvf rfcat_150225.tgz
$ cd rfcat_150225
$ python setup.py install
```
#### ooktools
Pip Package:
```
$ pip install ooktools
```

Using this method, you should have the `ooktools` command available globally.

From source:
```
$ git clone https://github.com/leonjza/ooktools.git
$ cd ooktools
$ pip install -r requirements.txt
```

If you installed from source then you can invoke `ooktools` with as a module using `python -m ooktools.console` from the directory you cloned to.

### usage
There are a number of sub commands that are grouped by major category. At anytime, add the `--help` argument to get a full description of any other sub commands and or arguments available.

```
$ ooktools --help
_ _ _
___ ___| |_| |_ ___ ___| |___
| . | . | '_| _| . | . | |_ -|
|___|___|_,_|_| |___|___|_|___| v0.1
On-off keying tools for your SD-arrrR
https://github.com/leonjza/ooktools
Usage: ooktools [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
gnuradio GNU Radio Commands.
signal Signal Commands.
wave Wave File Commands.
```

For examples, please refer to the blogpost [here](https://leonjza.github.io/blog/2016/10/08/introducing-ooktools.-on-off-keying-tools-for-your-sdr/).

### known issues
Nothing is perfect I guess. One of the biggest problems would be test cases and variations. So, here is the stuff that I know is not 100% perfect. Pull requests welcome!

- Wave file operations such as `graph` and `clean` break when the wave file is too long. ~50M samples seem to start hitting the point of breakage.
- The `matplotlib` usage is silly from a performance perspective. Its the main reason I don't have live graphs in too as I just cant get it working great.

## license
Please refer to the [LICENSE](https://github.com/leonjza/ooktools/blob/master/LICENSE) file.
Binary file added images/banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions ooktools/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# The MIT License (MIT)
#
# Copyright (c) 2016 Leon Jacobs
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import click
from pkg_resources import get_distribution

banner = (""" _ _ _
___ ___| |_| |_ ___ ___| |___
| . | . | '_| _| . | . | |_ -|
|___|___|_,_|_| |___|___|_|___| v{}
On-off keying tools for your SD-arrrR""".format(get_distribution('ooktools').version))

click.secho('{}'.format(banner), bold=True)
click.secho('https://github.com/leonjza/ooktools\n', dim=True)
Empty file added ooktools/commands/__init__.py
Empty file.
77 changes: 77 additions & 0 deletions ooktools/commands/converstions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# The MIT License (MIT)
#
# Copyright (c) 2016 Leon Jacobs
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from __future__ import absolute_import

import struct
import wave

import click
import numpy

from ..utilities import cleanup_wave_data


def clean_pwm_wave(source, destination):
"""
Clean up a source wave file with PWM encoded data.
The basic idea here is to read the source data and calculate
the average height of the samples. Using this height, we iterate
over all of the samples and replace the actual value with
a 1 or a 0.
Finally, in order to improve the graphing ability, we amplify
the 1's value by 10000 and write the results out to
a new wave file with the same attributes as the source.
:param destination:
:param source:
:return:
"""

# Read the frames into a numpy array
signal = numpy.fromstring(source.readframes(-1), dtype=numpy.int16)
signal = cleanup_wave_data(signal)

# Prepare the output wave file. We will use exactly
# the same parameters as the source
output_wave = wave.open(destination, 'w')
output_wave.setparams(source.getparams())

click.secho('Normalizing values..')

frames = []

for _, value in numpy.ndenumerate(signal):

# If the value is one, amplify it so that it is
# *obviously* not 0. This makes graphing easier too.
if value == 1:
value *= 10000

frames.append(struct.pack('h', value))

click.secho('Writing output to file: {}'.format(destination))
output_wave.writeframes(''.join(frames))

return
165 changes: 165 additions & 0 deletions ooktools/commands/graphing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# The MIT License (MIT)
#
# Copyright (c) 2016 Leon Jacobs
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from datetime import datetime

import click
import numpy
import peakutils

try:
import matplotlib.pyplot as plt
import matplotlib.animation as animation

plotting = True

except RuntimeError:
plotting = False


def _can_plot():
if not plotting:
click.secho('Plotting library was not sucesfully imported.\n'
'Ensure your python installation can run `import '
'matplotlib.pyplot` without errors.', fg='red')

return False

return True


def generate_wave_graph(source, peaks):
"""
Generate a plot from a wave file source.
Optionally, include peak calculations.
Source:
https://github.com/MonsieurV/py-findpeaks/blob/master/tests/vector.py
:param source:
:param peaks:
:return:
"""

if not _can_plot():
return

click.secho('Reading {} frames from source.'.format(source.getnframes()), fg='green')
click.secho('Preparing plot.', fg='green', dim=True)

# Read the source data
signal = source.readframes(-1)
signal = numpy.fromstring(signal, dtype=numpy.int16)

_, ax = plt.subplots(1, 1, figsize=(8, 4))
ax.plot(signal, 'b', lw=1)

# If we have to include peak information, calculate that
if peaks:
click.secho('Calculating peak information too.', dim=True)
indexes = peakutils.indexes(signal, thres=0.02 / max(signal), min_dist=100)

if indexes.size:
label = 'peak'
label = label + 's' if indexes.size > 1 else label
ax.plot(indexes, signal[indexes], '+', mfc=None, mec='r', mew=2, ms=8,
label='%d %s' % (indexes.size, label))
ax.legend(loc='best', framealpha=.5, numpoints=1)

# Continue graphing the source information
ax.set_xlim(-.02 * signal.size, signal.size * 1.02 - 1)
ymin, ymax = signal[numpy.isfinite(signal)].min(), signal[numpy.isfinite(signal)].max()
yrange = ymax - ymin if ymax > ymin else 1
ax.set_ylim(ymin - 0.1 * yrange, ymax + 0.1 * yrange)
ax.set_xlabel('Frame #', fontsize=14)
ax.set_ylabel('Amplitude', fontsize=14)

# Finally, generate the graph
plt.show()

return


def generage_saved_recording_graphs(source, count, series):
"""
Plot frames from a recording
:param source:
:param count:
:param series:
:return:
"""

if not _can_plot():
return

click.secho('Source Information:')
click.secho('Recording Date: {}'.format(datetime.fromtimestamp(source['date'])), bold=True, fg='green')
click.secho('Recording Frequency: {}'.format(source['frequency']), bold=True, fg='green')
click.secho('Recording Baud: {}'.format(source['baud']), bold=True, fg='green')
click.secho('Recording Framecount: {}'.format(source['framecount']), bold=True, fg='green')

# If we dont have a series to plot, plot the number of frames
# from the start to count
if not series:
data = source['frames'][:count]
click.secho('Preparing Graph for {} plots...'.format(count))
else:
start, end = series
data = source['frames'][start:end]
click.secho('Preparing Graph for {} plots from {} to {}...'.format(len(data), start, end))

# Place holder to check if we have set the first plot yet
fp = False

# Start the plot.
fig = plt.figure(1)
fig.canvas.set_window_title('Frame Data Comparisons')

# Loop over the frames, plotting them
for (index,), frame in numpy.ndenumerate(data):

# If it is not the first plot, set it keep note of the
# axo variable. This is the original plot.
if not fp:

axo = plt.subplot(len(data), 1, index + 1)
axo.grid(True)
axo.set_xlabel('Symbols')
axo.set_ylabel('Aplitude')
axo.xaxis.set_label_position('top')

# Flip the first plot variable as this is done
fp = True

# If we have plotted before, set the new subplot and share
# the X & Y axis with axo
else:
ax = plt.subplot(len(data), 1, index + 1, sharex=axo, sharey=axo)
ax.grid(True)

# Plot the data
plt.plot(numpy.frombuffer(buffer=str(frame), dtype=numpy.int16))

# Show the plot!
click.secho('Launching the graphs!')
plt.show()
Loading

0 comments on commit bc34f02

Please sign in to comment.